HALライブラリによるSTM32マイコンのUART機能使用方法

# 記事の概要
STM32マイコンのUART機能をHALライブラリを用いて使用する方法を解説します。

一般的にはSTM32CubeMXコード生成ツールを使うことが多いと思いますが、ここでは直接HALライブラリから関数を呼び出して設定を行います。
また、通常のモードについてのみ解説し、DMA機能については解説していません。

# USART
STM32ではUART機能をUSARTとも呼んでいます。
正確にはUSARTは非同期なUART通信、SPIマスター通信、LIN（ローカル内部ネットワーク）などの複数のシリアル通信機能を兼ね備えたモジュールです。

https://www.st.com/content/ccc/resource/sales_and_marketing/presentation/product_presentation/group0/70/92/62/c3/7b/16/45/9f/28.Universal%20synchronous/asynchronous%20receiver%20transmitter%20(USART)/files/28.STM32L4-Peripheral-USART%20interface%20(USART)%20Final_JP.pdf/jcr:content/translations/en.28.STM32L4-Peripheral-USART%20interface%20(USART)%20Final_JP.pdf

例えばSTM32F407には4つのUSARTモジュールと2つのUARTモジュールがあります。
UARTモジュールにはSPIマスター機能に対応していないことが分かります。

https://www.st.com/resource/en/datasheet/dm00037051.pdf

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150620/81fde11d-df34-23be-f072-cacd81b45d3b.png)

# サンプルコード

例えばUSART3モジュールを使用する場合、以下のように設定します。
UART受信は割り込み処理しています。

```C
#include "stm32f4xx.h"
#include "stm32f4_discovery.h"

uint8_t byte;
UART_HandleTypeDef huart3;

void USART3_IRQHandler(void)
{
  HAL_UART_IRQHandler(&huart3);
}

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  if (huart->Instance == USART3)
  {	  
    HAL_UART_Transmit(&huart3, &byte, 1, 100);
	  
    HAL_UART_Receive_IT(&huart3, &byte, 1);
  }
}

void uart_init(void)
{				
    __HAL_RCC_GPIOD_CLK_ENABLE();
    __HAL_RCC_USART3_CLK_ENABLE();

    GPIO_InitTypeDef GPIO_InitStruct;
    GPIO_InitStruct.Pin = GPIO_PIN_8 | GPIO_PIN_9;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_LOW;
    GPIO_InitStruct.Alternate = GPIO_AF7_USART3;
    HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);

    huart3.Instance = USART3;
    huart3.Init.BaudRate = 115200;
    huart3.Init.WordLength = UART_WORDLENGTH_8B;
    huart3.Init.StopBits = UART_STOPBITS_1;
    huart3.Init.Parity = UART_PARITY_NONE;
    huart3.Init.Mode = UART_MODE_TX_RX;
    huart3.Init.HwFlowCtl = UART_HWCONTROL_NONE;
    huart3.Init.OverSampling = UART_OVERSAMPLING_16;
    HAL_UART_Init(&huart3);

    HAL_NVIC_SetPriority(USART3_IRQn, 1, 0);
    HAL_NVIC_EnableIRQ(USART3_IRQn);
	
    HAL_UART_Receive_IT(&huart3, &byte, 1);
}
```

# ポート端子設定
UART機能に使用するポート端子の設定を行います。

USART3の場合は、TX端子はPD8、RX端子はPD9です。

マイコンのモジュールは、初期状態では消費電力を抑えるために停止しています。
GPIODにクロック供給することで起動してやります。

```C
__HAL_RCC_GPIOD_CLK_ENABLE();
```

GPIO_InitStructの設定を行います。
ポート8番と9番を指定します。

```C
GPIO_InitStruct.Pin = GPIO_PIN_8 | GPIO_PIN_9;
```

プルアップ設定にしているのは、初期からポート出力をにしておき、ハイインピーダンス状態による不安定さを回避するためと私は解釈しています。

```C
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
```

速度は低速にしていますが、高速にしても特に動作に支障はありませんでした。

```C
GPIO_InitStruct.Speed = GPIO_SPEED_LOW;
```

1つの端子は複数の機能に使い分けることができます。
マイコン内部に選択回路があり、AFレジスタを設定することで、ポート端子と様々なモジュールとの接続を切り替えています。
その選択をするのが`Alternate`です。

マニュアルのPD8とPD9を見ると、UART機能を選択する設定はAF7です。
（もしPD8とPD9をFSMCのD13とD14として使用したい場合はAF12に設定します。）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150620/85b0df56-b7b0-8aea-d6f2-6fb41b90bc16.png)

```C
GPIO_InitStruct.Alternate = GPIO_AF7_USART3;
```

# UART通信設定
UARTの実体`UART_HandleTypeDef`を作成しておきます。

http://www.disca.upv.es/aperles/arm_cortex_m3/llibre/st/STM32F439xx_User_Manual/structuart__handletypedef.html

```C
UART_HandleTypeDef huart3;
```

GPIODと同様にUSART3モジュールにクロックを供給します。

```C
__HAL_RCC_USART3_CLK_ENABLE();
```

通信速度、データ長、ストップビット、パリティビットを設定します。
ここでは通信速度を115200bps、データ長8bit、ストップビット1bit、パリティビットなしに設定しています。

```C
huart3.Instance = USART3;
huart3.Init.BaudRate = 115200;
huart3.Init.WordLength = UART_WORDLENGTH_8B;
huart3.Init.StopBits = UART_STOPBITS_1;
huart3.Init.Parity = UART_PARITY_NONE;
```

モードはRXモード、TXモード、TX_RXモードがあります。
送受信の機能を使用するのでTX_RXモードにします。

https://www.disca.upv.es/aperles/arm_cortex_m3/llibre/st/STM32F439xx_User_Manual/group__uart__mode.html#gab47c162935901e89322e2ce6700b6744

```C
huart3.Init.Mode = UART_MODE_TX_RX;
```


ハードウェアコントロール（RTS端子とCTS端子）を使用するかを選択します。
ハードウェアコントロールを使用しないので、設定を`UART_HWCONTROL_NONE`にします。

https://www.disca.upv.es/aperles/arm_cortex_m3/llibre/st/STM32F439xx_User_Manual/group__uart__hardware__flow__control.html#gae0569001c06b7760cd38c481f84116cf


```C
huart3.Init.HwFlowCtl = UART_HWCONTROL_NONE;
```

オーバーサンプリングは8倍と16倍があります。
クロックずれによる最大受信耐性を増加させたいので、16倍を設定します。

https://www.disca.upv.es/aperles/arm_cortex_m3/llibre/st/STM32F439xx_User_Manual/group__uart__over__sampling.html#gaa6a320ec65d248d76f21de818db1a2f0

```C
huart3.Init.OverSampling = UART_OVERSAMPLING_16;
```

以上の設定を有効化します。

```C
HAL_UART_Init(&huart3);
```


# UART送信

以下の送信関数によりデータ送信できます。

```C
uint8_t data;
byte = 'A';

HAL_UART_Transmit(&huart3, &data, 1, 100);
```

第1引数にはUSARTモジュールの実体である`UART_HandleTypeDef`を代入します。
第2引数には送信データの先頭アドレスを代入し、第3引数には送信データサイズを代入します。
第4引数はタイムアウトの時間です。この時間を過ぎても通信できない場合、エラー終了します。

例えば、10文字のデータを送信したい場合は以下のようにします。

```C
HAL_UART_Transmit(&huart3, (uint8_t *)"FreeRTOS\r\n", 10, 100);
```

# UART受信

データをUART受信すると割り込み処理が発生するように設定します。

```C
HAL_NVIC_SetPriority(USART3_IRQn, 1, 0);
HAL_NVIC_EnableIRQ(USART3_IRQn);
```

受信待機処理`HAL_UART_Receive_IT`を実行して、データ受信できるようにします。
ここではUART通信開始時に、1バイトデータを受信待機するように命令しておきます。
`HAL_UART_Receive_IT`を実行しないと、データ受信できないのでご注意ください。

```C
HAL_UART_Receive_IT(&huart3, &byte, 1);
```

UART受信により以下のハンドラーが呼び出されます。

```C
void USART3_IRQHandler(void)
{
  HAL_UART_IRQHandler(&huart3);
}
```

ハンドラーからコールバック関数`HAL_UART_RxCpltCallback`が呼び出されます。
コールバック関数内で、USART3の実体`Instance`であるかの確認をします。
そして、ユーザーのしたい処理を行います。

ここでは受信したデータをそのまま送信し返してから、再度1バイト受信待機処理を実行して、次のデータに備えます。
1バイト受信したことで最初に設定した`HAL_UART_Receive_IT`は無効になっているので、再度`HAL_UART_Receive_IT`を実行しないとデータ受信できなくなっているのでご注意ください。

```C
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
   if (huart->Instance == USART3)
   {	  
       HAL_UART_Transmit(&huart3, &byte, 1, 100);
	  
       HAL_UART_Receive_IT(&huart3, &byte, 1);
   }
}
```
	

# 参考
ここでは解説しなかったDMA通信については、以下をご参照ください。

https://code-examples.net/ja/q/17b9361