MasaのITC Life

夢は起業家!全てにおいて凡人だけど頑張ることだけはいっちょ前!

電子工作

【ゼロ作】STM32F411REとArduinoをUARTで通信させる!(HALなし)

投稿日:2021年10月7日 更新日:



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の初期化



以下が初期化のためのコード例です。



変数名はマクロ定義していますので、

もしかしたら逆に分かりにくいかもしれません。



その場合は最後に全コードのリンクがありますので、

その中のヘッダーファイルを見ていただけると幸いです。



2つのパートに分かれており、

前半がGPIOをUART仕様にするためで、

後半がUARTのセットアップになります。




まず前半のGPIOまわりについてです。

GPIOをUARTにセットアップする


GPIOのレジスタを以下で定義しておきます。



この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レジスタを以下で定義しておきます。


ドキュメントはこのようになっています。



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 は何ぞやということですが、

クロック周波数とボーレートと以下の関係があります。


Tx/Rx baud = f_ck 8×(2-OVER8)×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を初期値のまま計算するなら、

USARTDIV = f_ck 8×2×baud


のようになます。



STM32F411REの場合は、

APB1のクロック周波数は16MHzです。

同値ですがコア周波数の意ではないので注意です。




例えばボーレートを115200で設定すると、

USARTDIVの値は、約8.681 になります。

そしてドキュメントにあるように、

以下でFractionとMantissaを導出します。





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による送受信を行う関数を実装します。



まずは送信です。



続いて受信です。



以上が送受信コードになります。



「受信」に関する補足:

当記事のコードはポーリングのための実装であり、

一度に複数のバイトを受信してしまうと、

受信バッファ(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つですが、

ドキュメントに記載されているように

TDRRDRの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 で実装しています。



systick部に関する処理の抜粋で、

記事最後にコード全部のリンクが載せましたので、

そちらも併せて参照いただくと幸いです。




配線は以下になります。


STM32F411RE Arduino Uno
PA9 D10
PA10 D11
GND GND



GND線もお忘れなく!




Arduino側のコードです。

STM32F411REの受信も確かめる場合は、

それに対応する(Arduinoの送信)コードも

各自でお好みに実装して下さい!



容易に想像できますがシリアルモニタを見ると、

データを毎秒受信しているのが確認出来ます!






記事中でご紹介したコードは一部であり、

全てのコードはこちら(github)にあります。




めでたしめでたし!

最後まで読んでいただきありがとうございます。



-電子工作

Copyright© MasaのITC Life , 2023 All Rights Reserved Powered by STINGER.