Menu Close

STM32 USARTを使う。通信速度の設定と送信

前回USARTの配線まで済ませましたので、今回はプログラムを書いてみましょう。

目標とするプログラムは

起動したら「a」と一文字送り、逆に「z」という文字を受け取ったら「b」という文字を送り返す

にしましょう。

文字を送信するには

送信は受信より簡単です。受信はいつ起こるか分かりませんが、送信は自分からするものなので簡単なんです。

送信するにはUSARTのDRレジスタにデータを書き込んでむだけでOKです。すると、USARTの別の送信用レジスタにデータがセットされて、決められた通信速度に合わせて順番にTXピンからデータが1ビットずつ出てきます。

通信速度を決める

UARTペリフェラルはタイマーの時みたいにペリフェラルをONにしちゃえばすぐ使えます。
ただ、通信速度を決めておかないといけません。
もちろん他にも設定はあるのですが(パリティチェックやストップビットの長さなど)
デフォルトで、とりあえずでよく使う設定

  • 8bit
  • パリティチェックなし
  • ストップビットは長さ1
  • クロック端子なし

になってますから、パソコン側をこれに合わせるとして速度だけ気にしましょう。

今回は115200bpsにしてみます。
速度を決めるのはUSARTのBRRレジスタの値です。ここの値を大きくすると遅くなります。
じゃあ実際にどの値にすれば115200になるかというと、

stm_usart_function

こういう関係になっています。
左側のTx/Rx baudは通信速度で、fckはクロックです。
当たり前ですが、CPUのクロックと関係があります。
ただし、USART2はAPB1のクロックで動くので今は48Mhzとなります。
下にあるOVER8というのはオーバーサンプリングというもので、16bit(デフォルト)なら0で、8bitにしたら1となります。
そしてUSARTDIVというのがBRRレジスタの値です。
なので、115200にするにはUSARTDIVは
USARTDIV=42000000/(8×(2-0)×115200)
により22.796458,,,,
となります。

ちなみにこんな計算しなくてもよく使いそうな通信速度似合わせるためのUSARTDIVの値はリファレンスマニュアルに載っています。

stm_usart_brr

これによると42Mhzの時に1115200bpsにするには22.8125 というのが載っています。
僕らが計算した値と若干違いますね。何ででしょうか。

まず、BRRレジスタは固定少数のレジスタす。

stm_usart_brrr

下4bitが小数点以下。15-4bitの12bitが実数部となります。
固定少数というのは意外と簡単で、
まず XX.YY という少数より大きな実数部と、少数以下の仮数部に別れます。
XXの方はBRRレジスタの15-4bitの値がそのまま使われます。
問題は少数の方ですが、これはBRRレジスタには4bitあります。つまり0~15の値が入るわけです。
これでどうやって少数を表すかというと、1を16で割った値を基礎として、それが何個なのかで表現します。難しいですがこういうことです

XX + (4bitの仮数部の値)/16

仮数部が0のときは少数以下は0となります。
つぎに、1の時には1/16の値となり、2の時には2/16の値となるわけです。
そして一番大きな15の時には15/16の値となるわけです。

では、22.8125の0.8125は何なのかというと13/16がちょうど0.8125となります。
つまりさっきの表は「本当の値とはちょっとずれるけど、BRRレジスタにセットできる最も近い値」を書いてくれているわけです。

これで115200に通信速度をセットするにはBRRに22.8125をセットすれば良くて、そのためには

USART2-> BRR = (22 << 4) + 13;

このようにセットすれば良いということがわかります。

誤差

ちなみに、本当なら22.796458,,,,をセットしなきゃいけないのに22.8125をセットしています。つまり通信速度が若干違うわけです。
どれぐらいズレてるかはさっきの表でもError%として載っています。
BRRを使えば好きな通信速度に出来るわけですが、欲しい通信速度ぴったりにはなかなか合わないわけです。
このズレは1バイト2バイト送るぐらいでは問題になりません。通信のはじまりと終わりごろでは若干ずれますが、ズレは小さいので受け取る側が間違うほどではないからです。
ただし、連続でデータを送るときには注意が必要です。だんだんズレていくのがなんとなくわかると思います。

実際のプログラム

それでは、起動したら「a」という文字を送るプログラムを書いてみましょう。

#include "stm32f4xx.h"

static void SetSysClock(void);

void GPIO_init(void)
{
    RCC-> AHB1ENR      |= RCC_AHB1ENR_GPIOAEN;

    GPIOA-> MODER      = 0x00000000;
    GPIOA-> PUPDR      = 0x00000000;            // no pullup down

     // PA2のAF7がUSART2_TX
    GPIOA-> AFR[0]     = 0;
    GPIOA-> AFR[0]     |= GPIO_AF7_USART2 << 8;
    GPIOA-> MODER      |= 0b10 << 4;            // PA2=AF

     // PA3のAF7がUSART2_RX
    GPIOA-> AFR[0]     |= GPIO_AF7_USART2 << 12;
    GPIOA-> MODER      |= 0b10 << 6;            // PA3=AF
}

void UART_init( void)
{
    RCC-> APB1ENR |= RCC_APB1ENR_USART2EN;

    USART2-> BRR = (22 << 4) + 13;
    USART2-> CR1 = USART_CR1_UE | USART_CR1_TE;
}

int main(void)
{
    SetSysClock();
    GPIO_init();
    UART_init();

    USART2-> DR = 'a';

    while(1);
}


#define PLL_M      8
#define PLL_N      336
#define PLL_P      2
#define PLL_Q      7
#define PLLI2S_N   258
#define PLLI2S_R   3

static void SetSysClock(void)
{
  __IO uint32_t StartUpCounter = 0, HSEStatus = 0;

  RCC->CR |= ((uint32_t)RCC_CR_HSEON);

  do {
    HSEStatus = RCC->CR & RCC_CR_HSERDY;
    StartUpCounter++;
  } while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));

  if ((RCC->CR & RCC_CR_HSERDY) != RESET) {
    HSEStatus = (uint32_t)0x01;
  } else {
    HSEStatus = (uint32_t)0x00;
  }

  if (HSEStatus == (uint32_t)0x01) {
    RCC->APB1ENR |= RCC_APB1ENR_PWREN;
    PWR->CR |= PWR_CR_VOS;

    RCC->CFGR |= RCC_CFGR_HPRE_DIV1;

    RCC->CFGR |= RCC_CFGR_PPRE2_DIV2; // APB2は84Mhz

    RCC->CFGR |= RCC_CFGR_PPRE1_DIV4;	// APB1は 42Mhz

    RCC->PLLCFGR = PLL_M | (PLL_N << 6) | (((PLL_P >> 1) -1) << 16) | (RCC_PLLCFGR_PLLSRC_HSE) | (PLL_Q << 24);

    RCC->CR |= RCC_CR_PLLON;

    while((RCC->CR & RCC_CR_PLLRDY) == 0);

    FLASH->ACR = FLASH_ACR_ICEN |FLASH_ACR_DCEN |FLASH_ACR_LATENCY_5WS;

    RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
    RCC->CFGR |= RCC_CFGR_SW_PLL;

    while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS ) != RCC_CFGR_SWS_PLL);

  } else {
  	GPIOD->ODR |= 1<<14;
  }

  RCC->CFGR &= ~RCC_CFGR_I2SSRC;
  RCC->PLLI2SCFGR = (PLLI2S_N << 6) | (PLLI2S_R << 28);
  RCC->CR |= ((uint32_t)RCC_CR_PLLI2SON);
  while((RCC->CR & RCC_CR_PLLI2SRDY) == 0);
}

このプログラムを書き込んでFTDIをつないだパソコンで115200bpsでUSARTを受信できるようにするとSTMマイコンをリセットするごとにaという文字を受信できると思います。

stm_usart_mac

プログラムを見てみましょう。

GPIO_init();

の中でやっているのは前回やったAlternativeFunctionの設定です。
次にUART_initですが、これがUART2ペリフェラルの有効化と速度の設定をしています。

void UART_init( void)
{
    RCC-> APB1ENR |= RCC_APB1ENR_USART2EN;

    USART2-> BRR = (22 << 4) + 13;
    USART2-> CR1 = USART_CR1_UE | USART_CR1_TE;
}

まずは、恒例のクロックの有効化です。
そしてBRRレジスタに値を入れて通信速度の設定をしています。
そしてコントロールレジスタCR1のUEビットとTEビットを1にしています。
UEがUARTの有効化ビットで、これを1にするとUSARTが使えるようになります。
また、TEビットが送信の有効化で、1にすると送信できるようになります。

そして、実際の送信は

    USART2-> DR = 'a';

ここで行われています。
DRレジスタに送信したいデータをセットします。
セットするだけで自動的に送信用のシフトレジスタにデータが送られて、1bitずつ決められた速度にあわせて送信されていきます。

送信中

本当なら他のデータの送信中はDRレジスタにデータを入れてTEビットを1にしても送信されません。さっきのは起動直後で明らかにデータは送信中じゃないからできることです。

DRレジスタにデータをセットできるかどうかはSRレジスタのTXEビットを見ればわかります。
TXEビットは前回DRレジスタにセットしたデータが送信用のシフトレジスタに移動して、DRレジスタに新しいデータをセットできることを示します。
連続送信できるようにしたプログラムを書いてみます。

#include "stm32f4xx.h"

static void SetSysClock(void);

void GPIO_init(void)
{
    RCC-> AHB1ENR      |= RCC_AHB1ENR_GPIOAEN;

    GPIOA-> MODER      = 0x00000000;
    GPIOA-> PUPDR      = 0x00000000;            // no pullup down

     // PA2のAF7がUSART2_TX
    GPIOA-> AFR[0]     = 0;
    GPIOA-> AFR[0]     |= GPIO_AF7_USART2 << 8;
    GPIOA-> MODER      |= 0b10 << 4;            // PA2=AF

     // PA3のAF7がUSART2_RX
    GPIOA-> AFR[0]     |= GPIO_AF7_USART2 << 12;
    GPIOA-> MODER      |= 0b10 << 6;            // PA3=AF
}

void UART_init( void)
{
    RCC-> APB1ENR |= RCC_APB1ENR_USART2EN;

    USART2-> BRR = (22 << 4) + 13;
    USART2-> CR1 = USART_CR1_UE | USART_CR1_TE;
}

void UART_send(uint32_t character)
{
    USART2-> DR = character;
    while ((USART2->SR & USART_SR_TXE) == 0);
}

int main(void)
{
	SetSysClock();
	GPIO_init();
	UART_init();

	UART_send('a');
	UART_send('a');
	UART_send('a');


    while(1);
}




#define PLL_M      8
#define PLL_N      336
#define PLL_P      2
#define PLL_Q      7
#define PLLI2S_N   258
#define PLLI2S_R   3

static void SetSysClock(void)
{
  __IO uint32_t StartUpCounter = 0, HSEStatus = 0;

  RCC->CR |= ((uint32_t)RCC_CR_HSEON);

  do {
    HSEStatus = RCC->CR & RCC_CR_HSERDY;
    StartUpCounter++;
  } while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));

  if ((RCC->CR & RCC_CR_HSERDY) != RESET) {
    HSEStatus = (uint32_t)0x01;
  } else {
    HSEStatus = (uint32_t)0x00;
  }

  if (HSEStatus == (uint32_t)0x01) {
    RCC->APB1ENR |= RCC_APB1ENR_PWREN;
    PWR->CR |= PWR_CR_VOS;

    RCC->CFGR |= RCC_CFGR_HPRE_DIV1;

    RCC->CFGR |= RCC_CFGR_PPRE2_DIV2; // APB2は84Mhz

    RCC->CFGR |= RCC_CFGR_PPRE1_DIV4;	// APB1は 42Mhz

    RCC->PLLCFGR = PLL_M | (PLL_N << 6) | (((PLL_P >> 1) -1) << 16) | (RCC_PLLCFGR_PLLSRC_HSE) | (PLL_Q << 24);

    RCC->CR |= RCC_CR_PLLON;

    while((RCC->CR & RCC_CR_PLLRDY) == 0);

    FLASH->ACR = FLASH_ACR_ICEN |FLASH_ACR_DCEN |FLASH_ACR_LATENCY_5WS;

    RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
    RCC->CFGR |= RCC_CFGR_SW_PLL;

    while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS ) != RCC_CFGR_SWS_PLL);

  } else {
  	GPIOD->ODR |= 1<<14;
  }

  RCC->CFGR &= ~RCC_CFGR_I2SSRC;
  RCC->PLLI2SCFGR = (PLLI2S_N << 6) | (PLLI2S_R << 28);
  RCC->CR |= ((uint32_t)RCC_CR_PLLI2SON);
  while((RCC->CR & RCC_CR_PLLI2SRDY) == 0);
}

この中で前回と違うのは送信後にTXEビットのチェックをしているところです。

    USART2-> DR = character;
    while ((USART2->SR & USART_SR_TXE) == 0);

DRレジスタに新しいデータを入れるとTXEビットは0となります。
送信用シフトレジスタにデータを転送中というのが0の意味です。
そして転送が完了してDRにデータを入れれるようになると1になりますので、1になるまでwhile文で待機するわけです。
ちなみにこのTXEは新しいデータをDRレジスタに入れるとまた0に戻ります。

無駄にwhile文で待つことになりますが、簡易的にはこれで大丈夫です。
本当ならDMAを使ったり、TXEビットが1になるきっかけで割りこませることも出来ますので、それを使うと無駄にCPUで待機するなんてことはしなくても良くなります

まとめ

  • DRレジスタに送りたいデータを入れるだけで送信が始まる
  • 通信速度は固定少数のBRRレジスタで決まり、リファレンスによく使う値が載ってる
  • CR1のUEビットを1にすると送信できるようになる
  • DRレジスタに新しいデータを入れれるかはTXEビットをチェックする

Related Posts

コメントを残す