[STM32メモ]NUCLEO基板搭載のST-LINKからシリアル通信する

NUCLEO基板には、ライタ・デバッガとして使えるST-LINKが搭載されており、ST-LINKを介してVirtual COMポートでシリアル通信ができるようになっている。ここでは、Keil MDKに同梱されるフリーのRTOS(RTX5)とCMSIS-Driverを使って1秒ごとにメッセージをPCに送るためのサンプルプログラムを作る手順をまとめる。

何かぱっと作るときに必要な設定は一通りまとまっているはず。

#プロジェクト作成
STM32F0ではKeil MDKが無制限で使えるので、ここではuVision(統合可発環境)で開発を行う。まずは、必要なソフトウェアパッケージ(Keil RTX5とUSART Async)を選択・依存関係を解決後、STM32CubeMXでHAL(Hardware Abstraction Layer)の設定を行う。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/296170/112879c6-0a40-3963-de31-b28d9b8a4fe8.png)

#HAL設定
ST-LINKにつながっているUSARTのピンと割り込み・DMAの設定を行う。
##回路図を調べる
[NUCLEO基板の回路図](https://www.stmcu.jp/design/hwdevelop/other/52242/)から、使用している基板の回路図を調べる。以下はNUCLEO-F072RB基板の回路図の抜粋。ST-LINKにつながっている信号は、`USART_TX`と`USART_RX`と書かれている。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/296170/8ed88dda-5301-5d50-5643-176637bfefda.png)

`USART_TX`と`USART_RX`はそれぞれ`PA2`と`PA3`というピンであることが分かった。これを、STM32CubeMXで設定する。

##ピン設定
Pinout viewから、`PA2`と`PA3`に割り当てられている`USART2`の信号`USART2_TX`と`USART2_RX`を選択する。この段階では、`USART2`の設定をしていないので、ピンはオレンジで表示される。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/296170/5d18445c-4480-67b2-d739-8038c0471a4d.png)

次にConnectivityから`USART2`の機能を選択し、モードを`Disabled`(無効)から`Asynchronous`(非同期…いわゆる普通のシリアル通信)に変更する。これにより、`USART2`が利用できるようになる。同時に、`PA2`と`PA3`の表示がオレンジから緑になる。(有効な機能が割り当てられたということ)

##CMSIS-Driverのための設定
USARTはSTM32CubeMXが生成するHALを直接叩くことで通信できるが、もう少し抽象化されたCMSIS-Driverというドライバを使えば、簡単にDMAを用いた通信ができるようになっている。[CMSIS-DriverはテンプレートはARMのリポジトリから入手できて、色々なマイコンに移植できるらしい](https://github.com/ARM-software/CMSIS_5)。Keil MDKにはCAN, I2C, SPI, USART, USBなどが同梱されているｊ。抽象化している分、効率は若干劣るらしいものの、ぱっと使えるので便利。

CMSIS-Driverは`Send`や`Receive`といった、バッファの送受信を行えるようになっているので、割り込みの設定が必要となる。(しないとTX_RX_BUSYという状態で止まってしまう)

###DMAを使わない場合
`USART2`の`NVIC`(Nested Vector Interrupt Controller)で、USART2の割り込みを有効化する。この場合、CMSIS-Driverは1文字送信/受信完了するたびに、割り込みハンドラでバッファからデータを読み出す/書きこむ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/296170/e6e974b8-b08a-ad41-f968-252ca47bb24f.png)

###DMAを使う場合
DMAの設定をしてから、USART2の割り込みを有効化する。この場合、CMSIS-Driverはバッファ全部をDMAで送信/受信完了してくれる。(割り込みハンドラで後処理する)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/296170/92de71e7-895f-662f-2a36-c4bd32f92977.png)

##RTOSの設定
動作確認用の周期生成に、贅沢にもRTOSを使う。

RTOSには[RTX v5という、GitHubで公開されていてApacheライセンスのもとフリーで利用できるものを使う](https://github.com/ARM-software/CMSIS_5)。Keil MDKにも同梱されているので、STMF0シリーズ向けならuVisionからパッケージを追加するだけで使えるので利用しやすい。

### 割り込み優先度の設定
RTX5のマニュアル(Create an RTX5 Project)を参考に、割り込み優先度を設定する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/296170/f4c1de64-ec82-e572-ea8f-0285ae697da3.png)

STM32F0では、優先度が高い順に0～3の4つが選べるので、以下のように設定する。(SVCは最低+1だが、これは優先度が+1であって優先度の数字が+1ではないので注意)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/296170/14a79d1e-359e-e738-42d5-eb375c06793e.png)

なお、それぞれの役割はTheory of Operationの項に書かれている。ざっくり以下のような使われ方をする。

* SysTick: 周期的にタスクを起床するために使われる割り込み。基本的に他の割り込みより低くしたいので最低優先度にする。
* PendSV: ハンドラモード(多分ISR:割り込みサービスルーチン)から要求のあった処理を行うための割り込み。(タスクのスケジュールとかリソースのロックが)SysTickと干渉しないよう、SysTickと同じ優先度にする。(下図のSVに該当…ISRから要求があったRTOSへの要求が、SysTickの処理が終わった後で実行されるようにするために使われる)
* SVC: タスク上でOSの特殊な機能(タスクのロック解除とか)を使うためのソフト的な割り込み。他のタスクに割り込まれないようにしたいため、前記2つより優先度が高い。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/296170/d9ca8ec5-afe0-be52-5a09-58e9ee66e5a1.png)

### コード生成の設定
RTOSがSysTick、PendSV、SVCの割り込みハンドラを定義するので、STM32CubeMXではこれらの割り込みハンドラの生成を行わないようにする必要がある。(生成してしまうと、同名の関数が二重に定義されているとリンカに怒られる)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/296170/b14205fd-73ae-5ee4-6871-31cb62254d85.png)

#お試しコード
実はこれはCMSIS-DriverのUSARTのサンプルをほぼそのままコピペしたもの。

```c:main.c抜粋
1. インクルード
/* USER CODE BEGIN Includes */
#include "RTE_Components.h"
#include "stm32f0xx.h"
#include "cmsis_os2.h"
#include "Driver_USART.h"
/* USER CODE END Includes */
...

2. USARTのアクセス構造体のExtern宣言(実体はstm32_hal_uart.c)
/* USER CODE BEGIN PV */
extern ARM_DRIVER_USART Driver_USART2;
/* USER CODE END PV */
...

3. 1秒ごとに(送信完了しているかにかかわらず)"hello"と送信する
/* USER CODE BEGIN 0 */
__NO_RETURN void thread_a( void * arg ) {
    while( 1 ) {
        osDelay( 1000 );
        Driver_USART2.Send( "hello\n\r", 7 );
    }
}
/* USER CODE END 0 */
…

int main(void)
{
...
  /* USER CODE BEGIN 2 */
4. USART2のドライバ設定(ボーレートやフロー制御など)
  Driver_USART2.Initialize( NULL ); /* CMSIS-Driverからのコールバックは無視 */
  Driver_USART2.PowerControl( ARM_POWER_FULL );

  /*Configure the USART to 38400Bits/sec */
  Driver_USART2.Control(ARM_USART_MODE_ASYNCHRONOUS |
                    ARM_USART_DATA_BITS_8 |
                    ARM_USART_PARITY_NONE |
                    ARM_USART_STOP_BITS_1 |
                    ARM_USART_FLOW_CONTROL_NONE, 38400);
   
  /* Enable Receiver and Transmitter lines */
  Driver_USART2.Control (ARM_USART_CONTROL_TX, 1);
  Driver_USART2.Control (ARM_USART_CONTROL_RX, 1);

5. RTOS起動(3.のタスクを登録)
  osKernelInitialize();                            // initialize RTX
  osThreadNew(thread_a, NULL, NULL);               // create some threads
  osKernelStart ();                                // start RTX kernel
  /* USER CODE END 2 */
…略…
}
```

これだけで1秒ごとに"hello"と遅れてしまうのだから、良い時代になってしまった。ちなみに、ST-LINKは自動でボーレートを調整してくれるみたいなので、コードと、ターミナルソフトのボーレートさえ合わせれば良いようになっている。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/296170/2549c55f-b3d4-74aa-9872-866753c5e6b1.png)


#この後の発展
上記のサンプルは送信完了も確認せず、1秒ごとに送信するだけだが、受信したり、送信完了をチェックしたいという場合があると思う。

その時は、アクセス構造体の初期化時に登録するコールバック関数(上記ではNULL)内で、抽象化されたイベント(受信完了、送信完了、エラー発生など)を判定し、その場で応答するか、OSのサービスコールでタスクを起床するなどして、イベントに応じた処理を行うと良いらしい。

この辺のことはCMSIS-DriverのTheory of Operationを読むと載っているのでそちらを参照のこと。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/296170/cfdee1b6-a221-c78b-5edc-c1c8ee9b3d51.png)

#各ツールの連携について
* CMSIS-Driver: HALが決まった命名で
* STM32CubeMX: HAL

#参考
* [Keil MDK(STMF0,L0,G0向け無制限版)](https://www2.keil.com/stmicroelectronics-stm32/mdk)
* [GitHub: CMSIS-Driver](https://github.com/ARM-software/CMSIS_5/tree/develop/CMSIS/Driver)
* [GitHub: Keil RTX5](https://github.com/ARM-software/CMSIS_5)
* [NUCLEO基板の回路図](https://www.stmcu.jp/design/hwdevelop/other/52242/)