STM32のレジスタを叩く。 #2 ~UART編~
今回はUARTを使ってみます。
STM32F446は、4つのUSARTと2つのUARTを持っています。このうちUSART2は、NucleoのUSB-シリアル変換機能によって、基板上のUSBポートからアクセスすることが出来ます。
まずはGPIOと同様、ペリフェラルにクロックを供給することから始めます。
RCC->APB1ENR |= RCC_APB1ENR_USART2EN;
ボーレートの設定
続いてボーレートの設定を行います。 ユーザマニュアルによると、ボーレートは次のような式によって決められます。
この式を使って、ボーレートが115200となるようなUSARTDIVの値を求めます。
ここで、fckはクロックの周波数です。まだPLLやプリスケーラの設定をしていないので内蔵16MHzが使われます。 また、OVER8はオーバーサンプリングというもので、ここはデフォルト値(0)のまま使うこととします。
これらの値を代入すると、
$$ USARTDIV = \frac{16 \times 10^{6} }{8 \times (2 - 0) \times 115200} = 8.6805... $$
USARTDIVの値が、8.6805... に求まりました。
USART_BRR
この値を実際に設定するのはUSART_BRRレジスタです。実部をDIV_Mantissaに、小数部をDIV_Fractionに入れます。 つまりこのレジスタには、USARTDIVを4bit左シフトした値を代入すればよいということになります。
...ところで、先ほどの式は次のように表すこともできます。
$$ USARTDIV \times 16 = \frac{16 \times 10^{6}}{115200} $$
USARTDIVに16を掛けることは、USARTDIVを4bit左シフトすることと同義なので、OVER8が0の場合は、単にfckをボーレートで割った値を代入すればよいということになります。(ただし整数同士で除算する関係で、1bit分の誤差が生じる可能性がある。)
USART2->BRR |= 16000000 / baudrate;
ピンの設定
次に、ピンをUSART2のTX/RXとして使えるよう設定を行います。
データシートの表を見ると、USART2のTX/RXはそれぞれ PA2, PA3で使用できることがわかります。
ピンをGPIO以外のペリフェラルで使いたいときは、該当するピンをGPIOx_MODERレジスタで"10 (Alternate function mode)"に設定します。
GPIOA->MODER |= (GPIO_MODE_AF_PP << GPIO_MODER_MODER2_Pos); GPIOA->MODER |= (GPIO_MODE_AF_PP << GPIO_MODER_MODER3_Pos);
GPIOx_AFR
GPIOx_AFRは、それぞれのポートの各ピンごとに、どのオルタネート機能を使うかを設定するレジスタです。このレジスタはGPIOx_AFRLとGPIOx_AFRHの二つに分かれています。
PA2, PA3をUSART2(AF7)で使うよう設定します。
GPIOA->AFR[0] |= (GPIO_AF7_USART2 << GPIO_AFRL_AFSEL2_Pos); GPIOA->AFR[0] |= (GPIO_AF7_USART2 << GPIO_AFRL_AFSEL3_Pos);
最後に USART_CR1レジスタでTX/RX、そしてUSART2を有効にすれば、初期化は完了です。
USART2->CR1 |= USART_CR1_TE | USART_CR1_RE; USART2->CR1 |= USART_CR1_UE;
文字の送受信
1文字を送信/受信する関数を、それぞれ簡単に実装してみます。 基本的にはどちらも、USART_SRレジスタでフラグが立つのを待ち、USART_DRレジスタでデータの受け渡しを行うといった流れになります。 今回は簡単のため、whileで待ちをつくります。
送信
void usart_send_char(char c){ while(!(USART2->SR & USART_SR_TXE_Msk)); USART2->DR = c; while(!(USART2->SR & USART_SR_TC_Msk)); }
受信
char usart_get_char(void){ while(!(USART2->SR & USART_SR_RXNE_Msk)); char buf = USART2->DR; return buf; }
Hello, World! してみる
STM32から一定間隔で "Hello, World!" を送信するプログラムを実行してみます。
#include "stm32f4xx.h" void usart_init(uint32_t baudrate); void usart_send_char(char c); void usart_send_str(const char*); int main(void){ usart_init(115200); while(1){ for(volatile uint32_t i=0;i<1000000;i++); usart_send_str("Hello, World!\r\n"); } return 0; } void usart_init(uint32_t baudrate){ RCC->APB1ENR |= RCC_APB1ENR_USART2EN; USART2->BRR |= 16000000 / baudrate; RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN; /* USART2 TX (PA2) */ GPIOA->MODER |= (GPIO_MODE_AF_PP << GPIO_MODER_MODER2_Pos); GPIOA->AFR[0] |= (GPIO_AF7_USART2 << GPIO_AFRL_AFSEL2_Pos); GPIOA->PUPDR |= (GPIO_PULLUP << GPIO_PUPDR_PUPD2_Pos); /* USART2 RX (PA3) */ GPIOA->MODER |= (GPIO_MODE_AF_PP << GPIO_MODER_MODER3_Pos); GPIOA->AFR[0] |= (GPIO_AF7_USART2 << GPIO_AFRL_AFSEL3_Pos); GPIOA->PUPDR |= (GPIO_PULLUP << GPIO_PUPDR_PUPD3_Pos); USART2->CR1 |= USART_CR1_TE | USART_CR1_RE; USART2->CR1 |= USART_CR1_UE; } void usart_send_char(char c){ while(!(USART2->SR & USART_SR_TXE_Msk)); USART2->DR = c; while(!(USART2->SR & USART_SR_TC_Msk)); } void usart_send_str(const char *str){ for(uint8_t p=0;str[p]!='\0';p++){ usart_send_char(str[p]); } }
正しく受信することが出来ました。