haphysics blog - 幸福の物理ブログ

みんなに物理と工作と幸福をお届けするのだァ~!

STM32F4Discovery + stm32plusでUSARTしてみた

月が綺麗ですね

中秋の名月に続いてスーパームーン、月を見る機会が連続して訪れた面白い2日間でした。

f:id:shitaro2012:20150929130106j:plain
コンデジでも月の表面がよく写りました。

f:id:shitaro2012:20150929130138j:plain
本館も月明かりで照らされていました。

長らく放置していた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 という選択肢もあったのですが、

  1. Eclipseの設定がよくわからない
  2. Eclipseが重い
  3. stm32plusのexamplesが優秀でそのまま動く
  4. 研究室の事情により、最近テンプレートに手を出し始めた
  5. C++で開発したい

などの理由によりstm32plusを選択しました。

STM32を開発するにあたって必要となるものは以下の3つです。

  1. GCC ARM Embedded in Launchpad
  2. andysworkshop/stm32plus · GitHub
  3. texane/stlink · GitHub

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をそれぞれつなげるだけ。
f:id:shitaro2012:20150929125730j:plain

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へ転送されました。
サンプルコードほぼそのままで動いてしまうのですから頭を使わなくていいですね!
f:id:shitaro2012:20150929005740p:plain

なんで動くの?

さて、ここから頭を使います。どのようにして動くのか。自分が理解できた範囲で。

USARTの初期化手順

USARTを使う場合、以下の手順で初期化します。

  1. USARTにクロックを供給する
  2. GPIOをUSARTに初期化する
  3. USARTの各種値を設定する
  4. 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割り込みを使用するためには以下の手順で初期化します。

  1. NVICを設定する
  2. 割り込みハンドラを作成する
  3. 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::enable()で呼ばれています。
また、関数UsartInterruptFeatureEnabler::enable()は"usart/features/f4/UsartInterruptFeature.h"のUsartInterruptFeature::enableInterrupts()で呼ばれています。

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変換をしてみたいと思います。