HAL等のライブラリを使用せずに、
自力でレジスタを操作して、
UART通信を実現させていきます。
実機はNeucleo STM32F411REを使用します。
ArduinoはUnoを使用しました。
記事中で特に断りがない限り、
ドキュメントと表現した場合は、
STM32F411xC/Eの Reference manual を挿します。
いくつか初期化に必要な箇所を抽出していきます。
以下に沿って話を進めていきます。
まずはUARTの初期化を実装して、
sendとrecvを実装します。
最後に実機による実行テストの紹介です。
以下に赤枠で囲ったように、
STM32F411REの場合は例えば、
PA10をRxとして、PA9をTxとして使用できます。

参考元:https://os.mbed.com/platforms/ST-Nucleo-F411RE/
USARTレジスタはいくつかありますが、
今回はUSART1を使用してきます!
まだ最小限でUARTを始めるための設定なので、
今回はDMAの使用や割り込みの設定などは行いません。
※記事中ではUSARTとUARTが混在しています。
共通する事項という意味で、
基本的にはUSARTと表記しています。
一方でUARTと表記している箇所は、
USARTではない、という意味が
特に込められている箇所です。
それではレッツラゴー!
UARTの初期化
以下が初期化のためのコード例です。
変数名はマクロ定義していますので、
もしかしたら逆に分かりにくいかもしれません。
その場合は最後に全コードのリンクがありますので、
その中のヘッダーファイルを見ていただけると幸いです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
void usart1_init(void){ //GPIO register (*REG_RCC_AHB1ENR) |= RCC_AHB1ENR_GPIOAEN; REG_GPIOA->MODER |= (GPIO_MODER_AF << (UART_TX_PIN*2)) | (GPIO_MODER_AF << (UART_RX_PIN*2)); REG_GPIOA->OSPEEDR |= (GPIO_OSPEEDR_HIGH << (UART_TX_PIN*2)) | (GPIO_OSPEEDR_HIGH << (UART_RX_PIN*2)); REG_GPIOA->AFRH |= (GPIO_AF_USART1_TX << (4*(UART_TX_PIN-8))) | (GPIO_AF_USART1_RX << (4*(UART_RX_PIN-8))); //USART register (*REG_RCC_APB2ENR) |= RCC_APB2ENR_USART1EN; REG_USART1->CR1 = 0x0UL; //clear all REG_USART1->CR1 |= USART_CR1_UE; //enable the USART REG_USART1->CR1 &= ~USART_CR1_M; //1 start_bit, 8 data_bit, n stop_bit REG_USART1->CR2 |= USART_CR2_STOP_1; //n = 1 REG_USART1->CR3 &= ~USART_CR3_DMAT; //DMA for transmission is disabled REG_USART1->CR3 &= ~USART_CR3_DMAR; //DMA for reception is disabled REG_USART1->BRR |= (11<<0)|(8<<4); //_baud_rate(115200) REG_USART1->CR1 |= USART_CR1_TE; //Transmitter is enable REG_USART1->CR1 |= USART_CR1_RE; //Receiver is enable } |
2つのパートに分かれており、
前半がGPIOをUART仕様にするためで、
後半がUARTのセットアップになります。
まず前半のGPIOまわりについてです。
GPIOをUARTにセットアップする
GPIOのレジスタを以下で定義しておきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
typedef struct { uint32_t MODER; uint32_t OTYPER; uint32_t OSPEEDR; uint32_t PUPDR; uint32_t IDR; uint32_t ODR; uint32_t BSRR; uint32_t LCKR; uint32_t AFRL; uint32_t AFRH; } gpio_t; |
この2ピンをUSARTとして使うには、
GPIOのMODERでAFをセットする必要がります。
AF : Alternate Function

各ピンがに2ビットずつ割り当てられており、
共通して以下の値から選択することになります。
00 | Input (reset state) |
01 | General purpose output mode |
10 | Alternate function mode |
11 | Analog mode |
10 のAF mode を選ぶ必要があります。
オルタネートと言ったからには、
何を使用するのかを教えてあげる必要があります。
それがGPIOのAFRです。
AFRにはHighとLowがあり、
ピン番号によって分かれています。
ピン番号9と10を使用するので、AFRHに該当します。

ピン9は[7:4]の4ビットにAFxの数字をセットします。
ピン10は[11:8]の4ビットにAFxの数字をセットします。
このAFxのxは使用した機能によって変わります。
今回はUSART1なので、AF7に該当します。
赤枠と青枠がちょうどクロスするところです。

ドキュメント: STM32F411 DatasheetのP47
後はRCCでGPIOAのクロックを有効にするのをお忘れなく!
上述のusart1_init()の一行目のことです。
これでGPIO周りは完了です。
続いては本題のUSARTレジスタです。
USARTレジスタのセットアップ
USARTレジスタを以下で定義しておきます。
1 2 3 4 5 6 7 8 9 10 11 |
typedef struct { uint32_t SR; uint32_t DR; uint32_t BRR; uint32_t CR1; uint32_t CR2; uint32_t CR3; uint32_t GRPR; } usart_t; |
ドキュメントはこのようになっています。

UARTに関する初期化はドキュメントのP511に記載されています。

これに沿ってコードを記述していきます。
1. USARTのCR1の[13]に1にセットする
ドキュメントよりCR1レジスタは以下になっています。

上の赤枠[13]にはUEの名前が付いており、
ビットの意味は以下になっています。
Bit 13 UE: USART enable | |
---|---|
When this bit is cleared, the USART prescalers and outputs are stopped and the end of the current byte transfer in order to reduce power consumption. This bit is set and cleared by software. | |
0 | USART prescaler and outputs disabled |
1 | USART enabled |
つまり[13]UEに1をセットすることでUSARTを有効化します。
続いて、、、
2. USARTのCR1の[12]でワード長を設定する
UEフィールドの隣にMフィールドがあります。
Bit 12 M: Word length | |
---|---|
This bit determines the word length. It is set or cleared by software. | |
0 | 1 Start bit, 8 Data bits, n Stop bit |
1 | 1 Start bit, 9 Data bits, n Stop bit |
スタンダードな設定は、
スタートビットが1ビットで、
データが8ビットのものなので、
0ビットのままで大丈夫です。
上述コードにある、[12]Mのビット演算は、
1ビットの否定(つまり0)をセットする、
という意味で0をセットしています。
3. USARTのCR2の[13:12]でstopビット数の設定をする
ドキュメントよりCR2は以下になっています。

先ほどワード長を設定した際に、
stopビットだけ n bit でしたので、
それの n の値を決めてあげます。
Bits 13:12 STOP: STOP bits | |
---|---|
These bits are used for programming the stop bits. | |
00 | 1 Stop bit |
01 | 0.5 Stop bit |
10 | 2 Stop bit |
11 | 1.5 Stop bit |
[13:12]STOPには0をセット、
つまりstopビット数は1にしています。
4. USARTのCR3でDMAの使用否かを設定する
ドキュメントよりCR3は以下になっています。

[7]DMATで送信におけるDMAを
[6]DMARで受信におけるDMAを設定します。
Bit 7 DMAT: DMA enable transmitter | |
---|---|
This bit is set/reset by software | |
0 | DMA mode is disabled for transmission |
1 | DMA mode is enabled for transmission |
Bit 6 DMAR: DMA enable receiver | |
---|---|
This bit is set/reset by software | |
0 | DMA mode is disabled for reception |
1 | DMA mode is enabled for reception |
今回は使用せずCPUと直接やり取りさせるので、
0ビットつまり無効のままにしておきます。
#define USART_CR3_DMAT (0x1U << 7) として、
その否定とCR3レジスタの値を論理積の形で0セットしてます。
DMARも同様にして0をセットしています。
0ビットをセットする処理は正直、
なくても動作しますがコードを振り替えった際に、
「あ、DMAはここでセットすれば良いのか」
って思えるのでメンテナンス性は上がるかなと。
ちなみにこれに関しては、
上述の M ワード長 についても同様です。
5. USARTのBRRにボーレートを設定する
ドキュメントよりBRRは以下になっています。

ボーレートは選択肢が与えらえているわけではなく、
単に値そのまま入れる、というわけでもありません。
ボーレートを格納するBRRには、
MantissaとFractionの2つのフィールドがあります。
Bits 15:4 DIV_Mantissa[11:0]: mantissa of USARTDIV | |
---|---|
These 12 bits define the mantissa of the USART Divider (USARTDIV) |
Bits 3:0 DIV_Fraction[3:0]: fraction of USARTDIV |
---|
These 4 bits define the fraction of the USART Divider (USARTDIV). When OVER8=1, the DIV_Fraction3 bit is not considered and must be kept cleared. |
この2つは USARTDIV という値が導出します。
んじゃ、このUSARTDIV は何ぞやということですが、
クロック周波数とボーレートと以下の関係があります。
ボーレートには希望する値を当てはめ、
クロック周波数はUSARTのクロックソースである
APB1のクロック周波数を代入します。
OVER8 はUSARTをサンプリングするときに
8倍でサンプリングするか16倍でするか、
どちらでするかのフラグです。
このOVER8というフィールドは、
CR1の[15]にあり、初期値は0です。
Bit 15 OVER8: Oversampling mode | |
---|---|
0 | oversampling by 16 |
1 | oversampling by 8 |
ちなみにオーバサンプリングは、
16倍の方が最大通信速度は低くなりますが、
相手機器とのクロックのズレによる信頼性は上がります。
8倍は最大通信速度が16倍のときより早いですが、
その分クロックのズレによる信頼性は下がります。
そしてOVER8を初期値のまま計算するなら、
のようになます。
STM32F411REの場合は、
APB1のクロック周波数は16MHzです。
同値ですがコア周波数の意ではないので注意です。
例えばボーレートを115200で設定すると、
USARTDIVの値は、約8.681 になります。
そしてドキュメントにあるように、
以下でFractionとMantissaを導出します。

1 2 3 4 5 6 7 8 9 |
//Baud Rate is 115200 //APB1 clock is 16MHz //Then USARTDIV = 8.68055... float div_fraction = roundf(16 * 0.6806);//11 float div_mantissa = roundf(8); //8 //REG_USART1->BRR |= (11<<0)|(8<<4); |
DIV_Fractionが[3:0]なので (11<<0)
DIV_Mantissaが[15:4]なので (8<<4)
になる、というわけです。
6. USARTのCR1で送受信を有効にする。
CR1の画像中の青枠に1をセットします。
送信用の[3]TEと受信用の[2]REです。
Bit 3 TE: Transmitter enable | |
---|---|
This bit enables the transmitter. It is set and cleared by software. | |
0 | Transmitter is disabled |
1 | Transmitter is enabled |
Bit 2 RE: Receiver enable | |
---|---|
This bit enables the receiver. It is set and cleared by software. | |
0 | Receiver is disabled |
1 | Receiver is enabled and begins searching for a start bit |
このビットフィールドに1が立つことで、
実際の通信が開始されます。
GPIO同様にRCCでUSARTのクロック供給を有効にして
これでやっと、UARTの初期化処理は完了です。
7番以降は送受信に関する事項になります。
UARTの送受信を実装する
次はデータの送受信のお話です。
UARTによる送受信を行う関数を実装します。
まずは送信です。
1 2 3 4 5 6 7 8 9 10 11 12 |
void _usart1_send_char(uint8_t c) { REG_USART1->DR = c; while(!(REG_USART1->SR & USRAT_SR_TC)); } void usart1_send(uint8_t *str) { while(*str) _usart1_send_char(*str++); } |
続いて受信です。
1 2 3 4 5 6 7 |
uint8_t usart1_recv(void) { while(!(REG_USART1->SR & USART_SR_RXNE)); return REG_USART1->DR; } |
以上が送受信コードになります。
「受信」に関する補足:
当記事のコードはポーリングのための実装であり、
一度に複数のバイトを受信してしまうと、
受信バッファ(RDR)のオーバーランエラーが
発生する可能性があります。
オーバーランとは受信バッファに空きがなく、
既に格納済みのデータが新しいデータによって
上書きされ、損失してしまうことです。
オーバーランが発生するケースとして、
RDRの読み込みが完了していないのに、
次々とデータが送信されると起こり得ます。
しかし複数バイト送信しようと、
ポーリングが間に合っていれば、
オーバ―ランエラーは起きないです。
もし受信インターバル間に何かの処理があり、
ポーリングが間に合わない場合は、
オーバーランエラーを回避するために、
割り込みもしくはDMAを使う必要があります。
少し話しがそれましたが、
それでは7番以降を見ていきましょう!
ちなみに送信と受信とでは、
次の7、8番の順番が逆になります。
送信が7番が先で8番が最後です、
受信は8番が先で7番が最後です。
7. USARTのDRにアクセスする
ドキュメントよりDRは以下になっています。
DR : Data Register

データ部が9ビットだけあって、
他は全てReserved領域になっています。
Bits 8:0 DR[8:0]: Data value | |
---|---|
Contains the Received or Transmitted data character, depending on whether it is read from
or written to. The Data register performs a double function (read and write) since it is composed of two registers, one for transmission (TDR) and one for reception (RDR) The TDR register provides the parallel interface between the internal bus and the output shift register (see Figure 1). The RDR register provides the parallel interface between the input shift register and the internal bus. When transmitting with the parity enabled (PCE bit set to 1 in the USART_CR1 register), the value written in the MSB (bit 7 or bit 8 depending on the data length) has no effect because it is replaced by the parity. When receiving with the parity enabled, the value read in the MSB bit is the received parity bit. |
|
このDRは受信データを取り出すときも
送信データを設定するときも、
どちらの場合もDRの[8:0]を使用します。
DRは表面上1つですが、
ドキュメントに記載されているように
TDRとRDRの2つに分かれています。

イメージとしてuint8_t c = DR であれば、
ロード系の命令が実行されるわけですので、
RDRの方にアクセスします。
逆にDR = c であればストア系の命令が
DRレジスタに作用しますので、
TDRの方にアクセスします。
8. USARTのSRに1が立つのを待つ
ドキュメントよりSRは以下になっています。
SR : Status Register

SRは完了フラグのためのフィールドがあります。
完了フラグという表記は、
送信完了ないし読み込み準備完了、
のどちらかの意味を持たせています。
送信完了の場合は [6] TC で、
読み込み準備完了は [5] RXNE です。
Bit 6 TC: Transmission complete | |
---|---|
This bit is set by hardware if the transmission of a frame containing data is complete and if TXE is set. An interrupt is generated if TCIE=1 in the USART_CR1 register. It is cleared by a software sequence (a read from the USART_SR register followed by a write to the USART_DR register). The TC bit can also be cleared by writing a '0' to it. This clearing sequence is recommended only for multibuffer communication. | |
0 | Transmission is not complete |
1 | Transmission is complete |
Bit 5 RXNE: Read data register not empty | |
---|---|
This bit is set by hardware when the content of the RDR shift register has been transferred to the USART_DR register. An interrupt is generated if RXNEIE=1 in the USART_CR1 register. It is cleared by a read to the USART_DR register. The RXNE flag can also be cleared by writing a zero to it. This clearing sequence is recommended only for multibuffer communication. | |
0 | Data is not received |
1 | Received data is ready to be read. |
つまり送信した場合は、 [6]TC に1が
受信する場合は、読み込む前に [5]RXNE に1が
それぞれセットされるの待つ必要があります。
それがコード中の while() 文です。
これで送受信の実装は完了です。
あとは好きなタイミングで、
それぞれの関数を呼び出すだけです。
最後に、実際に通信させてみます。
STM32F411REとArduinoをUARTで通信させる
今回はSTM32F411REの送信を
実機を用いたテストとして簡単に紹介します。
Arduino Unoを受信側にしています。
送信側は1秒おきに送信しています。
「1秒毎」というのは systick で実装しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
#define TICK_MS (1000) #define LOAD_VAL (16000 * TICK_MS) //TICK_MS*(f_ck/1000), core frequency is 16MHz void systick_init(void) { REG_SYSTICK->CSR = 0x0UL; REG_SYSTICK->VAL = 0x0UL; REG_SYSTICK->LOAD = LOAD_VAL - 1U; REG_SYSTICK->CSR = 0x7UL; } void systick_handler(void) { led_toggle(); usart1_send("Send Data\n"); } |
systick部に関する処理の抜粋で、
記事最後にコード全部のリンクが載せましたので、
そちらも併せて参照いただくと幸いです。
配線は以下になります。
STM32F411RE | Arduino Uno |
---|---|
PA9 | D10 |
PA10 | D11 |
GND | GND |
GND線もお忘れなく!
Arduino側のコードです。
STM32F411REの受信も確かめる場合は、
それに対応する(Arduinoの送信)コードも
各自でお好みに実装して下さい!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
#include <SoftwareSerial.h> SoftwareSerial Serial2(10, 11); //Rx, Tx int speed = 115200; void setup() { Serial.begin(115200); Serial2.begin(115200); Serial.println("Let's get started!"); } void loop() { if(Serial2.available()){ int c = Serial2.read(); Serial.print(char(c)); } } |
容易に想像できますがシリアルモニタを見ると、
データを毎秒受信しているのが確認出来ます!
記事中でご紹介したコードは一部であり、
全てのコードはこちら(github)にあります。
めでたしめでたし!
最後まで読んでいただきありがとうございます。