エビデイエビナイ電子工作

やったことの記録

STM32のレジスタを叩く。 #2 ~UART編~

f:id:Rappy:20191114111502j:plain 今回はUARTを使ってみます。

STM32F446は、4つのUSARTと2つのUARTを持っています。このうちUSART2は、NucleoのUSB-シリアル変換機能によって、基板上のUSBポートからアクセスすることが出来ます。

まずはGPIOと同様、ペリフェラルにクロックを供給することから始めます。

RCC->APB1ENR |= RCC_APB1ENR_USART2EN;


ボーレートの設定

続いてボーレートの設定を行います。 ユーザマニュアルによると、ボーレートは次のような式によって決められます。 f:id:Rappy:20191205183232p:plain

この式を使って、ボーレートが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

f:id:Rappy:20191205225835p:plain
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として使えるよう設定を行います。

f:id:Rappy:20191207023209p:plain
Alternate function
データシートの表を見ると、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

f:id:Rappy:20191207153217p:plain
GPIOx_AFRL
GPIOx_AFRは、それぞれのポートの各ピンごとに、どのオルタネート機能を使うかを設定するレジスタです。このレジスタGPIOx_AFRLGPIOx_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]);
  }
}


f:id:Rappy:20191207234118p:plain 正しく受信することが出来ました。


twitter.com