STM32F4Discovery + stm32plusでUSARTしてみた
長らく放置していたSTM32F4Discoveryを使う
先週ロ技研合宿に行ってきました。合宿のおかげでSTM32欲が高まりました。
そういえばSTM32F4Discoveryを買ったのに放置していたなあと。shitaro-happy-physics.hatenablog.jp
当時は1750円で買えたんですね…。とりあえず入門としてUSARTを使ってみることにしました。
USBよりUSART?
実は2ヶ月ほど前にPICでUSBをしようと奮闘していました。しかしUSBフレームワークが大きすぎて全然小回りが効かないし実装も大変。結局USARTを使うことにしました。とりあえずPCと会話するという用途であれば、USARTの方が開発コストがかなり小さく済むので積極的に使うべきだと合宿で聞きました。当面はキーボードを作るというようなUSBを積極的に使わなければならない理由がなければ、USARTを使うことにします。
stm32plusで開発
さて、STM32F4Discoveryを開発するために、まずは環境構築をします。Eclipse + OpenOCD + stm32StandardPeripheralLibraries という選択肢もあったのですが、
などの理由によりstm32plusを選択しました。
STM32を開発するにあたって必要となるものは以下の3つです。
README.mdとINSTALL.mdに従えば環境ができます。
USARTができた
とりあえずUSARTするためにstm32plus/examples/usart_send_interruptsを書き込みました。
stm32plus/examples/usart_send_interrupts at master · andysworkshop/stm32plus · GitHub
USARTをUSBに変換するためにFT232RLを使いました。
FT232RL USBシリアル変換モジュール: 半導体 秋月電子通商 電子部品 ネット通販
接続は簡単で、RXとTXをそれぞれつなげるだけ。
STM32F4DiscoveryはUSART1を使えない(他のペリフェラルと干渉する)のでUSART2を使いました。
STM32F4DiscoveryのPA2はUSART2_TX、PA3はUSART2_RXです。
STM32F4DISCOVERY Discovery kit with STM32F407VG MCU - STMicroelectronicsのUser Manual参照。
STM32F4Discovery
Test uart with interrupt
という文字列が無事にマイコンからPCへ転送されました。
サンプルコードほぼそのままで動いてしまうのですから頭を使わなくていいですね!
なんで動くの?
さて、ここから頭を使います。どのようにして動くのか。自分が理解できた範囲で。
USARTの初期化手順
USARTを使う場合、以下の手順で初期化します。
- USARTにクロックを供給する
- GPIOをUSARTに初期化する
- USARTの各種値を設定する
- USARTを有効化する
USARTを使用するために必要な初期化は、この"usart/UsartPeripheral.h"で定義される、Usartクラスをpublic継承したUsartPeripheralクラステンプレートの引数付きコンストラクタ内で、上記の手順通りに行われます。
stm32plus/UsartPeripheral.h at master · andysworkshop/stm32plus · GitHub
具体的には次の通りです。
// "usart/UsartPeripheral.h" // UsartPeripheralクラステンプレートの引数付きコンストラクタ template<class TPinPackage,PeripheralName TPeripheralName> inline UsartPeripheral<TPinPackage,TPeripheralName>::UsartPeripheral(const Parameters& params){ // USARTにクロックを供給する ClockControl<TPeripheralName>::On(); // GPIOをUSARTに初期化する UsartPinInitialiser<TPinPackage,TPeripheralName>::initialise(params.usart_mode, params.usart_baudRate, params.usart_synchronous); // USARTの各種値を設定する USART_Init((USART_TypeDef *)PeripheralTraits<TPeripheralName>::PERIPHERAL_BASE,&init); // USARTを有効化する enablePeripheral(); }
USARTにクロックを供給する
STM32 Standard Peripheral Librariesでは関数
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);
を使ってクロックを供給します。stm32plusではどうでしょうか。
stm32plusでは"rcc/f4/ClockControl.h"にClockControlクラステンプレートの特殊化として記述されています。
stm32plus/ClockControl.h at master · andysworkshop/stm32plus · GitHub
// USART2の部分を抜粋 // ClockControl<PeripheralName TPeripheral>は"rcc/ClockControl.h"で宣言されている。 template<> struct ClockControl<PERIPHERAL_USART2> { static void On() { RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE); } static void Off() { RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,DISABLE); } };
テンプレートクラスのメンバ関数で呼び出されている関数、その引数として渡されているマクロはいずれも
STM32 Standard Peripheral Librariesで定義されているものです。
このように、stm32plus内部ではSTM32 Standard Peripheral Librariesを巧妙にテンプレートで包み込んで抽象化しているのです。この抽象化によってSTM32Fxシリーズに対してソースコードを流用できるのですね。
ところでSTM32 Standard Peripheral Libraries自体はstm32plusのどこにあるのでしょうか。それは"lib/fwlib"にありました。
GPIOをUSARTに初期化する
STM32 Standard Peripheral Librariesでは次のように初期化します。
// GPIO初期化用構造体を作る GPIO_InitTypeDef GPIO_InitStructure; // GPIOAのPIN2をオルタネートファンクションでUSART2_TXに設定する GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; GPIO_InitStructure.Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_PinAFConfig(GPIOA, GPIO_PinSource2, GPIO_AF_USART2); // GPIOAのPIN3をオルタネートファンクションでUSART2_RXに設定する GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_PinAFConfig(GPIOA, GPIO_PinSource3, GPIO_AF_USART2);
"usart/f4/UsartPinInitialiser.h"にUsartPinInitialiserクラステンプレートのメンバ関数として記述されています。
stm32plus/UsartPinInitialiser.h at master · andysworkshop/stm32plus · GitHub
//関数GpioPinInitialiser::initialise()から該当箇所を抜粋 template<class TPinPackage,PeripheralName TPeripheralName> inline void UsartPinInitialiser<TPinPackage,TPeripheralName>::initialise(uint16_t mode,uint16_t flowControl,bool synchronous) { // USART_TXの設定 if((mode & USART_Mode_Tx)!=0) GpioPinInitialiser::initialise((GPIO_TypeDef *)TPinPackage::Port_TX, TPinPackage::Pin_TX, Gpio::ALTERNATE_FUNCTION, (GPIOSpeed_TypeDef)PeripheralTraits<TPeripheralName>::GPIO_SPEED, Gpio::PUPD_UP, Gpio::PUSH_PULL, GpioAlternateFunctionMapper<TPeripheralName,TPinPackage::Port_TX,TPinPackage::Pin_TX>::GPIO_AF); // USART_RXの設定 if((mode & USART_Mode_Rx)!=0) GpioPinInitialiser::initialise((GPIO_TypeDef *)TPinPackage::Port_RX, TPinPackage::Pin_RX, Gpio::ALTERNATE_FUNCTION, (GPIOSpeed_TypeDef)PeripheralTraits<TPeripheralName>::GPIO_SPEED, Gpio::PUPD_UP, GpioAlternateFunctionMapper<TPeripheralName,TPinPackage::Port_RX,TPinPackage::Pin_RX>::GPIO_AF); }
関数GpioPinInitialiser::initialise()は"gpio/GpioPinInitialiser.h"に宣言、"gpio/GpioPinInitialiser.cpp"に定義が記述されています。
stm32plus/GpioPinInitialiser.h at master · andysworkshop/stm32plus · GitHub
namespace GpioPinInitialiser { // AFピンを設定するタイプのGpioInitialiser()を抜粋 void initialise(GPIO_TypeDef *port, uint16_t pin, Gpio::GpioModeType mode, GPIOSpeed_TypeDef speed, Gpio::GpioPullUpDownType pupdType, Gpio::GpioOutputType outputType, uint8_t afSelection); }
USARTの各種値を設定する
STM32 Standard Peripheral Librariesでは次のようにUSARTの設定をします。
// USART初期化用構造体を作る USART_InitTypeDef USART_InitStructure; // 8bit,stop bit1,parity無,flow control無 USART_InitStructure.USART_BaudRate = 9600; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART2, &USART_InitStructure);
stm32plusでは"usart/UsartPeripheral.h"にParameters構造体で記述されています。
stm32plus/UsartPeripheral.h at master · andysworkshop/stm32plus · GitHub
template<class TPinPackage,PeripheralName TPeripheralName> class UsartPeripheral : public Usart{ public: struct Parameters{ uint32_t usart_baudRate; ///< Your choice uint16_t usart_wordLength; ///< USART_Word_Length uint16_t usart_parity; ///< USART_Parity uint16_t usart_stopBits; ///< USART_Stop_Bits uint16_t usart_flowControl; ///< USART_HardwareFlowControl uint16_t usart_mode; ///< USART_Mode bool usart_synchronous; ///< true if there's a USART clock signal // デフォルトでBaudRate 9600,8bit,stop bit1,parity無,flow control無 Parameters(uint32_t baudRate=9600) { usart_baudRate=baudRate; usart_wordLength=USART_WordLength_8b; usart_parity=USART_Parity_No; usart_stopBits=USART_StopBits_1; usart_flowControl=USART_HardwareFlowControl_None; usart_mode=USART_Mode_Rx | USART_Mode_Tx; usart_synchronous=false; } }; UsartPeripheral(const Parameters& params){ USART_InitTypeDef init; USART_StructInit(&init); init.USART_BaudRate=params.usart_baudRate; init.USART_WordLength=params.usart_wordLength; init.USART_Parity=params.usart_parity; init.USART_StopBits=params.usart_stopBits; init.USART_HardwareFlowControl=params.usart_flowControl; init.USART_Mode=params.usart_mode; USART_Init((USART_TypeDef *)PeripheralTraits<TPeripheralName>::PERIPHERAL_BASE,&init); } }
USARTを有効化する
STM32 Standard Peripheral Librariesでは関数
USART_Cmd(USART2, ENABLE);
を使ってUSARTを有効化します。
stm32plusでは"usart/Usart.h"にUsartクラスのメンバ関数enablePeripheral()として記述されています。
stm32plus/Usart.h at master · andysworkshop/stm32plus · GitHub
inline void Usart::enablePeripheral() const { USART_Cmd(_peripheralAddress,ENABLE); }
割り込み手順
サンプルコードでは送信割り込みが使用されています。
USART割り込みを使用するためには以下の手順で初期化します。
- NVICを設定する
- 割り込みハンドラを作成する
- USART割り込みを有効化する
STM32 Standard Peripheral Librariesでは次のように初期化します。
// NVIC構造体を作る NVIC_InitTypeDef NVIC_InitStructure; // 割り込み対象となるペリフェラルを設定する NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn; // 割り込みのグループ優先度を設定する NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; // 割り込みのサブ優先度を設定する NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; // 指定したペリフェラルによる割り込みを有効にする NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure);
stm32plusでは"nvic/f4/NvicPeripheral.h"にNvicクラスのメンバ関数configureIrq()として記述されています。
stm32plus/NvicPeripheral.h at master · andysworkshop/stm32plus · GitHub
class Nvic{ public: static void configureIrq(uint8_t interrupt,FunctionalState state=ENABLE,uint8_t preemptionPriority=0,uint8_t subPriority=0){ NVIC_InitTypeDef nit; nit.NVIC_IRQChannel=interrupt; nit.NVIC_IRQChannelPreemptionPriority=preemptionPriority; nit.NVIC_IRQChannelSubPriority=subPriority; nit.NVIC_IRQChannelCmd=state; NVIC_Init(&nit); } };
関数configureIrq()は"usart/features/f4/UsartInterruptFeature.h"の関数UsartInterruptFeatureEnabler
また、関数UsartInterruptFeatureEnabler
template<uint8_t TUsartNumber> inline void UsartInterruptFeature<TUsartNumber>::enableInterrupts(uint16_t interruptMask) { _interruptMask|=interruptMask; UsartInterruptFeatureEnabler<TUsartNumber>::enable(); USART_ITConfig(_usart,interruptMask,ENABLE); } template<> inline void UsartInterruptFeatureEnabler<2>::enable() { _forceLinkage=&USART2_IRQHandler; Nvic::configureIrq(USART2_IRQn); }
よってサンプルコードでは、割り込みの初期化は
Nvic::initialise(); // デフォルトで<グループ優先度:サブ優先度>を<4:0>にする
_usart.enableInterrupts();
で行われていたということになります。
実はSTM32 Standard Peripheral Librariesの知識が必要だった
Lチカしたい程度のことならサンプルコードをそのまま使えば考えなしに動きます。しかし、自分でいろいろと設定しようとするならばSTM32 Standard Peripheral Librariesの知識が必要であると感じました。さらにテンプレートメタプログラミングも使われているので学習コストは高いとも感じました。なるほど、「stm32plusはSTM32の入門には適さない」とはこのことだったのですね。
次はAD変換をしてみたいと思います。