ARM(STM32)
stm32plusを使う - STM32 †
概要 †
Andy Brown氏によるSTM32向けC++ライブラリである stm32plus(andysworkshop / stm32plus) を使用する際のメモ.
便利そうなんだが,ザ・C++という感じのコードでC++にあまり馴染みがない人にとっては使いづらいと思うので,コードの解説も加えながらメモを残したい.
セットアップ †
stm32plusはスタティックライブラリとしてプロジェクトに組み込むことを想定しているため,事前にビルドが必要である.
下準備 †
もちろんSTM32向けの開発ツールチェインは必要なので,Windowsならyagartoなど,Linuxなら,Linux上でSTM32のプログラミング・デバッグ環境を整える - STM32を参考にしてセットアップを完了させておく.
確かWindowsだとコマンドプロンプトの仕様上8192文字以上のコマンドを受け付けてくれなくてビルドが失敗するので,CygwinやMingW32などを使用したほうが良いかもしれない.
ほかにもSConsと呼ばれるmake系のツールを使用するので,それを予めインストールしておく.
Ubuntuなら,
$ sudo apt-get install scons
でインストールできる.
また,Doxygenでドキュメントを生成できるようになっているため,
$ sudo apt-get install doxygen
しておくと便利.
レポジトリをclone
$ git clone https://github.com/andysworkshop/stm32plus.git
$ cd stm32plus
Linux上でSTM32のプログラミング・デバッグ環境を整える - STM32でビルドしたツールチェインやgcc-4.7.0以降のYAGARTOを使っている場合は,バージョンの都合上SConstructファイルを編集する必要がある.
env.Replace(CXXFLAGS=["-Wextra","-Werror","-pedantic-errors","-fno-rtti","-std=gnu++0x","-fno-threadsafe-statics","-pipe"])
env.Append(CCFLAGS="-DHSE_VALUE="+hse)
- env.Append(LINKFLAGS=["-Xlinker","--gc-sections","-mthumb","-g3","-gdwarf-2"])
+ env.Append(LINKFLAGS=["-Xlinker","--gc-sections","-mthumb","-g3","-gdwarf-2","-nostartfiles"])
# add on the MCU-specific definitions
要は"-notstartfiles"を追加すればよい.
こうしないと"crt0.oがありません"と怒られる.
(関連事項:yagartoのGCCを4.7以降にする際の注意 - STM32)
ビルド †
$ scons mode=debug mcu=f4 hse=12000000 -j9
SConsに渡すオプションは以下の通り.
名称 | 説明 | 値 |
mode | 最適化のかけ方を変える | debug(デバッグ情報あり,最適化なし) fast(-O3に相当) small(-Osに相当) |
mcu | シリーズの選択 | f1hd(STM32F103) f1cle(STM32F107) f1mdvl(STM32F100) f4(STM32F4XX) |
hse | 外部クロックの周波数(Hz) | 数値(12MHzなら12000000) |
上記の -j9 はコンパイルに使用するスレッド数.
どっかで((論理コアの数)+1)を指定すると良いという噂を聞いたので9にしてみた.
ドキュメントの生成 †
$ cd lib
$ doxygen Doxyfile
すると,lib/build/doc/htmlの下にhtmlファイルが大量にできる.
index.htmlをブラウザで開けばドキュメントを見られる.
各機能 †
GPIO †
まず最初にstm32plusを使用するにあたって最もベーシックなGPIOを取り上げる.
といってもベーシックでないように見えるのはC++の機能がふんだんに用いられているからである.(それによるメリットも後述する.)
LED点灯 †
はじめにLEDを点灯させるだけのコードを見てみよう.
このコードはstm32plusのexamples/blinkディレクトリにあるものに少し手を加えたものである.
#include "config/stm32plus.h"
#include "config/gpio.h"
using namespace stm32plus;
class Led {
public:
void on() {
GpioC<DefaultDigitalOutputFeature<8> > pc;
pc[8].reset();
}
};
int main() {
Led led;
led.on();
while(1);
}
PC8のピンをLOWにするという動作を行うコードである.
STM32VLDiscovery上で実行すると,右下にある青色のLEDが点灯する.
main関数の中では,Ledクラスの実体化と,on関数の呼び出しが行われている.
Ledクラスのon関数の内容を見ていく.
GpioC<DefaultDigitalOutputFeature<8> > pc;
この行では,PC8をデジタル出力のピンとして設定し,GPIOCを扱うためのインスタンスpcを生成している.
pc[8].reset();
そして,この行でPC8をLOWに設定している.
<バリエーション>
- GpioCでなくGpioA
GpioA<DefaultDigitalOutputFeature<8> > pa;
pa[8].reset();
- PA8でなくPA9
GpioA<DefaultDigitalOutputFeature<9> > pa;
pa[9].reset();
- PA8も9も10も
GpioA<DefaultDigitalOutputFeature<8,9,10> > pa;
pa[8].reset();
pa[9].reset();
pa[10].reset();
もしくは
GpioA<DefaultDigitalOutputFeature<8,9,10> > pa;
pa.reset((1<<8)|(1<<9)|(1<<10));
LED消灯 †
消灯する場合はresetをsetに書き換えれば良い.
だがそれだけでは芸がないのでC++風に書いてみる.
#include "config/stm32plus.h"
#include "config/gpio.h"
using namespace stm32plus;
class Led {
public:
void on() {
pc[8].reset();
}
void off() {
pc[8].set();
}
private:
GpioC<DefaultDigitalOutputFeature<8> > pc;
};
int main() {
Led led;
led.on();
for(volatile int i=0;i<60000;i++); // kuzu
led.off();
while(1);
}
Ledクラスのメンバ変数としてpcを宣言した.
これによってLedクラスがインスタンス化される時にGPIOCが初期化される.
ボタンによる入力 †
EXTIを用いたボタン入力の割り込み処理 †
Timing †
内部の詳しい解説は
stm32plus-Timing - STM32?を参照.
SysTickによるLED点滅 †
最も単純に時間を得る方法としてSysTickを用いることが多い.
stm32plusではSysTick自体は隠蔽されていて,timingという区分でRTCなどと一緒に実装されている.
LEDを点滅させるいわゆる「Lチカ」を行うために,SysTickを利用したdelay関数を使用してみる.
#include "config/stm32plus.h"
#include "config/gpio.h"
#include "config/timing.h"
using namespace stm32plus;
class Led {
public:
void on() {
pc[8].reset();
}
void off() {
pc[8].set();
}
void blinkOnce() {
on();
MillisecondTimer::delay(1000);
off();
MillisecondTimer::delay(1000);
}
private:
GpioC<DefaultDigitalOutputFeature<8> > pc;
};
int main() {
MillisecondTimer::initialise();
Led led;
while(1){
led.blinkOnce();
}
return 0;
}
まず
MillisecondTimer::initialise();
でミリ秒精度のSysTickタイマを設定し,
MillisecondTimer::delay(1000);
で1000msのディレイを作り出している.
内部の詳しい解説は
stm32plus-Timing - STM32?を参照.
<バリエーション>
MicrosecondDelayを用いた高精度ディレイ †
通常のタイマをマイクロ秒オーダーの高精度ディレイを実現するために使用する機能が実装されている.
MicrosecondDelayはデフォルトでTIM6を使用する設定になっているが,MicrosecondDelayTemplateを用いると,その他のタイマにこの機能を割り当てることができる.
<バリエーション>
RTCを用いた時刻の取得 †
デバイスないので未検証>
Timer †
TimerはTimingとは異なりSTM32のタイマの機能をそのまま使うためのものである.
STM32のタイマの数と機能は非常に豊富かつ多彩で,それをオブジェクト指向的にかつ簡単に扱うことができるのもstm32plusの魅力である.
内部の詳しい解説は
stm32plus-Timer - STM32?を参照.
Timerを走らせるだけ †
PWM出力的なのを手動で書いてみた.
変なことをやっているが,これはTIMx->CNTを取得するための苦肉の策である.
重要なコードは以下の2つである.
timer.setTimeBaseByFrequency(5000, 6000,TIM_CounterMode_Up);
これは,タイマをカウントアップモードにし,5kHz(1秒間に5000回)でカウントし,6000回カウントしたら0にリセットするという設定をするコードである.
StdPeriphLibでいうTIM_TimeBaseInit関数にあたる.
もうひとつはこのコードである.
timer.enablePeripheral();
これは,タイマを有効化する関数で,StdPeriphLibでいうTIM_InitとTIM_Cmd関数にあたる.
内部の詳しい解説は
stm32plus-Timer - STM32?を参照.
<バリエーション>
- TIM7を使う
Timer7<Timer7InternalClockFeature> timer;
- 6000Hzでカウントアップする
timer.setTimeBaseByFrequency(5000, 6000,TIM_CounterMode_Up);
コンペアマッチ出力 †
タイマの機能であるコンペアマッチ出力を使用して,カウンタがある値に等しくなったときにピンの出力を反転させるプログラムを書く.
STM32VLDiscoveryの青LED(PC8)は,ピン機能のリマップを使用するとTIM3のCH3に割り当てることができる.(データシート参照)
出力を反転させる処理はハードウェア的に行われるため,設定だけしてタイマを走らせれば勝手にLEDがチカチカする.
timer.initCompare(1000);
このコードによって,コンペアマッチに指定する値(と処理内容:デフォルトでは出力反転)を記述できる.
内部の詳しい解説は
stm32plus-Timer - STM32?を参照.
<バリエーション>
PWMの出力 †
前項のバリエーションで記述した指定方法ではDuty比を変えるのが面倒である.
そこで,このような記法が用意されている.
内部の詳しい解説は
stm32plus-Timer - STM32?を参照.
<バリエーション>
- 初期Duty比を設定したい
- PWM2モードを使いたい
- 百分率ではなくカウント数を直接指定したい
Timerによる割り込み †
TimerInterruptFeatureをテンプレート引数に追加して,割り込みハンドラをバインド(紐付け)する処理を行うだけで,割り込みの設定も記述することができる.
ロータリエンコーダを読む †
STM32のタイマにはエンコーダインタフェース機能が存在するが,stm32plusではこれを使用するものは実装されていない.(要検討・実装)