STM32 nucleoを使う (4) UART 送信

#はじめに
UARTとはマイコンと外部との通信に良く使われます。
nucleo STM32F303REではuart2がUSBに変換されています。ターミナルソフトで通信が可能です。
STM32からUART送信をしてみます。

#送信の仕方
STM32のUARTはFIFOがありませんので連続で送信することができません。
UARTの通信時間は遅いので、送信完了を待って次のデータを設定する必要があります。
連続して送信するには以下の方法があります。

- ポーリング
- 割り込み
- DMA

#ポーリングによる送信
UARTの送信データレジスタにデータを設定後、送信完了フラグを監視し送信可能に変化したら次のデータを設定します。
HAL_UART_Transmit()がポーリングで送信する関数になります。

```C++
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
```

QubeMXの設定
nucleo STM32F303REではuart2がデフォルトで使用する設定になっています。
設定はそのまま使います。
![001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68712/3c7816b7-932d-acb3-f45a-6fb26e9aa158.png)

main.cに以下のコードを追加しました。
HAL_GetTick()を使い送信時間を測ります。

```C++
  /* USER CODE BEGIN Includes */
  #include <stdio.h>
  #include <String.h>
  /* USER CODE END Includes */

  /* USER CODE BEGIN 1 */
  const uint8_t txBuff[] = "Hello, World\n";
  uint32_t startTime;
  uint32_t sdTime;
  uint8_t sdBuff[100];
  /* USER CODE END 1 */

  /* USER CODE BEGIN 2 */
  startTime = HAL_GetTick();

  HAL_UART_Transmit(&huart2, txBuff, sizeof(txBuff), 0xFFFF);

  sdTime =  HAL_GetTick() - startTime;
  sprintf(sdBuff, "time: %dms\n", sdTime);
  HAL_UART_Transmit(&huart2, sdBuff, strlen(sdBuff), 0xFFFF);
  /* USER CODE END 2 */
```

Tera Termなどのターミナルソフトで受信させます。
通信設定を合わせるのを忘れずに。
実行結果は以下のようになりました。
![002.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68712/92c8d777-431e-c842-1399-38a55db5e5a8.png)

ボーレートが38400bps、送信データ数が13byte、1byteのデータを送信するのに、スタートビットとストップビットが1bit付加されるので10bitとなるので送信時間は

```math
\frac{13 \times 10}{38400} 
 = 3.385 (ms)
```

となります。
1ms単位なのでほぼ計算通りの結果です。
HAL_UART_Transmit()を使うと、その間他の処理ができないので時間に余裕がある処理で使うのが無難です。

#割り込みを使った送信
送信割り込みは送信完了時に発生します。なので最初のデータを送信した後のデータを割り込みで送信するようになります。
HAL_UART_Transmit_IT()が割り込み送信の関数です。

```C++
HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
```

QubeMXの設定
割り込みを使う場合は、NVIC Settingsを開き、Enabledにチェックを入れます。
![003.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68712/5c10b42b-93c0-41f1-e29c-a59c7c8f2379.png)

main.cに以下のコードを追加しました。

```C++
  /* USER CODE BEGIN 1 */
  uint8_t txBuff[] = "Hello, World\n";
  /* USER CODE END 1 */

  /* USER CODE BEGIN 2 */
  HAL_UART_Transmit_IT(&huart2, txBuff, sizeof(txBuff));
  /* USER CODE END 2 */
```
HAL_UART_Transmit_IT()で送信を開始し、1byte終了するたびに割り込みハンドラーで送信を行います。
送信中も他の処理は行えますが、送信が完了したわけではないので、続けて送信する場合は、送信中かどうか判定するか、ダブルバッファやリングバッファのようなバッファリングの処理を行います。

#DMAを使った送信
割り込みを使うと送信完了を待つ必要がないため時間的に効率が良くなります。DMAを使うともっと効率が良くなります。
DMAはDirect Memory Accessのことで、CPUを使わずにメモリの内容を他のメモリやペリフェラルに転送することができます。

QubeMXの設定
DMAを使う場合は、DMA Settingを開き、Addボタンを押しUSART2_TXを追加します。
![004.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68712/334dfccb-46fc-5015-195a-da1132d13b82.png)
開き、Enabledにチェックを入れます。
転送完了の割り込みを使いたいので、NVIC SettingのUSART2 global interruptnにチェックを入れます。
![005.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68712/e3b6c8dc-de01-b4bf-dc21-5fcb2e9ffdef.png)

DMAで送信するにはHAL_UART_Transmit_DMA()関数を使います。この関数を呼ぶだけで送信はできますが、DMAによる送信が完了した後もhuart2.gStateの状態が変化しないため、割り込みハンドラでREADYに戻す処理にします。
HAL_UART_Transmit_DMA()の実行時間と転送時間を計測します。
転送終了はhuart2.gStateがHAL_UART_STATE_READYになるまでの時間です。

```C++
/* USER CODE BEGIN Includes */
#include <stdio.h>
#include <String.h>
/* USER CODE END Includes */

/* USER CODE BEGIN 0 */
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
  huart2.gState = HAL_UART_STATE_READY;
}
/* USER CODE END 0 */

/* USER CODE BEGIN 1 */
uint8_t txBuff[] = "Hello, World\n";
uint32_t startTime;
uint32_t setTime;
uint32_t sdTime;
uint8_t sdBuff[100];
/* USER CODE END 1 */


/* USER CODE BEGIN 2 */
startTime = HAL_GetTick();
HAL_UART_Transmit_DMA(&huart2, txBuff, sizeof(txBuff));
setTime =  HAL_GetTick() - startTime;

while(huart2.gState != HAL_UART_STATE_READY)
{
  ;
}
sdTime =  HAL_GetTick() - startTime;

sprintf((char *)sdBuff, "setting time: %ldms\n", setTime);
HAL_UART_Transmit(&huart2, sdBuff, strlen((char *)sdBuff), 0xFFFF);

sprintf((char *)sdBuff, "DMA time: %ldms\n", sdTime);
HAL_UART_Transmit(&huart2, sdBuff, strlen((char *)sdBuff), 0xFFFF);
/* USER CODE END 2 */
```

実行結果は以下のようになりました。
![006.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68712/0c5180cb-bd21-3dca-c093-866d843c20d3.png)
HAL_UART_Transmit_DMA()はすぐに終了していますが、送信完了までは3msかかっています。
割り込みを使う場合と同様にバッファリングの処理などが必要です。

次はUARTの受信を行います。
STM32の使い方を学習した内容をまとめています。内容に不備等ありましたら連絡お願いします。