STM32 Nucleo Boardでベアメタルなhello world

#STM32 Nucleo Boardでベアメタルなhello world
これまで[開発環境を構築](https://qiita.com/mitazet/items/bfa944c0583b88cf486f)し、[実行ファイル作成](https://qiita.com/mitazet/items/59f953ecfaadd3092e08)までできるようになった。
しかし、私はなぜかCUI開発環境なので、GDBデバッグだけでは効率的でないと思っていた。
ここはひとつ「printf」を作るしかない。
今回はSTM32 Nucleo Boardでベアメタルな「hello world」を実現するしかない!
##開発ターゲット
STM32 Nucleo Board STM32F303K8
##STM32 Nucleo Boardでhello worldするには
STM32 Nucleo Boardには外部ディスプレイがない。しかし、USARTが使用可能。
ということで今回はシリアル出力で「hello world」という文字列を表示させる。
###完成イメージ
![printf.png](https://qiita-image-store.s3.amazonaws.com/0/140055/38604f06-f158-d6c6-db4b-2bb720cd3141.png)
printfをたたくとUSART Driver経由してシリアルに「Hello World」が出力されるイメージ。

##USART Driverを作る
今回は学習のために自作することにした。
###USARTを使用するには
BoardにはST-Linkが搭載されており、USB端子でUSART通信ができるためそれを使用することにした。
![vusart.png](https://qiita-image-store.s3.amazonaws.com/0/140055/c654209b-d87d-e154-8752-831db3e4c40a.png)
SB2とSB3は購入時でONだったので PA2 → USART TX(送信部) PA15 → USART RX(受信部)となる。
USB端子なので、はんだ付けしなくていいなとは思ったけど、そこから先はよく分からず。
[yuki-sato.com](http://yuki-sato.com/wordpress/2016/02/24/stm32-uart%e3%82%92%e4%bd%bf%e3%81%86%e3%80%82%e3%83%94%e3%83%b3%e3%81%aealternativefunction%e3%82%92%e8%a8%ad%e5%ae%9a%e3%81%99%e3%82%8b/)を参考にさせてもらった。
>実はUSART専用のピンはなくて、GPIOとの共有ピンになっていてGPIOの設定をすることでUSARTピンとして切り替えて使います。
>切り替えると当然ながらGPIOとしては入力も出力も使えなくなるのですが、USARTのピンとして使えるようになります。
>ただ、「じゃあPortAの0をTx(送信)にする！」みたいにどのピンでもUSARTのピンに出来るわけではありません。
>まず、ピン１つに16種類までの機能が割り振られていて、その16種類のどれかに切り替えるという感じになります。

なるほど。さっきのPA2ピンとPA15ピンをUSARTに切り替える必要があるのか。
データシートをみるとGPIO PortAは次のようになっている。
![gpio_af.png](https://qiita-image-store.s3.amazonaws.com/0/140055/164f1d3a-459b-daa7-a463-4366c7f8b4cd.png)
これによりGPIOAの2番ピンをAlternateFunction7、GPIOAの15番ピンをAlternateFunction7に設定すると、
それぞれUSART2_TX、USART2_RXとなることが分かる。

GPIOピンは次の2つの設定を行うことでUSARTピンとして使用できるようになる。
(1)GPIOモードの設定
(2)GPIO AlternateFunctionの設定
####GPIOモードの設定
GPIO port mode registerからGPIOモードを設定する。
ReferenceManualではGPIO port mode registerは次にようになっている。
![gpio_mode.png](https://qiita-image-store.s3.amazonaws.com/0/140055/3a281251-71ae-cf9a-23bc-c186d9ad766e.png)
PA2/PA15をAlternativeFunctionモードに切り替えるにはMODER2に10/MODER15に10を設定する。
####GPIO AlternateFunctionの設定
GPIO alternate function registerからAlternateFunctionを設定する。このレジスタは1ピンを4bitで設定する。
そのため32bitでは足りず、low registerとhigh registerに分かれている。
ReferenceManualではGPIO alternate function low registerは次のようになっている。
![gpio_afrl.png](https://qiita-image-store.s3.amazonaws.com/0/140055/19167189-66ae-de6f-bb06-e11201974dca.png)
PA2/PA15をAlternativeFunction7に切り替えるにはGPIOA_AFRLのAFR2に0111/GPIOA_AFRHのAFR15に0111を設定する。
####USARTピンを切り替えるコード
ここまでをコードにしてみた。
レジスタの定義などはstm32f303x8.hに詳細に定義されているため、
それを使用している。

```c
void EnablePinUSART(void)
{
	RCC->AHBENR |= RCC_AHBENR_GPIOAEN;

	GPIOA->MODER = 0;
	GPIOA->AFR[0] = 0;
	GPIOA->AFR[1] = 0;

	// PA2 and PA15 as AF
	GPIOA->MODER |= GPIO_MODER_MODER2_1;
	GPIOA->MODER |= GPIO_MODER_MODER15_1;

	// Select AF7 (USART2) for PA2 and PA15
	GPIOA->AFR[0] = 0x00000700;
	GPIOA->AFR[1] = 0x70000000;
}
```
最初の RCC->AHBENR |= RCC_AHBENR_GPIOAEN; はGPIOAのクロックを有効にしている。
これでピンをUSARTとして使えるようになった。

###USARTでのデータ送受信制御を実装する
ここから先はいよいよUSARTの制御になる。しかし何をどうしたらよいか分からない。
そこでReference Manualをよく読んでみたら、制御手順が載っていた。
送受信制御は以下の流れになる。
####送信制御
>1. USART_CR1レジスタのM bitからワード長を設定する。
>2. USART_BRRレジスタを使用して、ボーレートを選択する。
>3. USART_CR2レジスタでstop bitを設定する。
>4. USART_CR1レジスタのUEビットに1を書き込んで、USARTを有効にする。
>5. USART_CR1のTEビットをセットして、最初の送信とアイドルフレームを送信する。
>6. 送信するデータをUSART_TDRレジスタに書き込む(これによりTXEビットがクリアされる)。
>7. USART_TDRレジスタにデータを書き込んだ後、TXE=1になるまで待つ。これは、TXがEmptyであること(送信完了)を示す。

####受信制御
> 1. USART_CR1レジスタのM bitからワード長を設定する。
>2. USART_BRRレジスタを使用して、ボーレートを選択する。
>3. USART_CR2レジスタでstop bitを設定する。
>4. USART_CR1 レジスタの UE ビットに 1 を書き込んで、USART を有効にする。
>5. USART_CR1 レジスタの RE ビットをセットする。これによりレシーバが有効になり、Start bitの検索を開始する。
>
>データが受信されると、
>・RXNE ビットは、RDR レジスタにデータが転送されるとセットされる。読み出し可能ということ。
>・RXNE ビットのクリアは、ソフトウェアによる USART_RDR レジスタの読み出しによって行われる。

####実装方針
上記の送受信で1.～5.はほぼ共通のため、初期設定としてまとめることができそう。
TXEビットとRXNEビットはUSART_ISRレジスタにあり、監視を行いながら制御する。
割り込みを使った方法もあるみたいだが、まずは簡単な方法で実装する。
各設定値は、次のようにする。
ワード長:8bit (デフォルト)
stop bit:1bit (デフォルト)
ボーレート:115200bps (なんかよく使われてる気がするから)
ボーレートの選択方法はReference Manualに書いてあったので、次項で説明する。
####ボーレートの設定
送信/受信制御の「2.」であるように、USART_BRRでボーレートを設定する。
今回は非同期通信で、クロックによるデータの同期はない。
そのため、受信側と送信側で通信速度(ボーレート)を合わせる必要がある。
![usart_brr.png](https://qiita-image-store.s3.amazonaws.com/0/140055/15242e24-f91b-2195-fc3f-bd68d4800a4a.png)

USART_BRRレジスタにUSARTDIVを設定する。USARTDIVは次の式で求められる。

![calc_baudrate.png](https://qiita-image-store.s3.amazonaws.com/0/140055/15bbbd9f-c369-b7ac-4137-b7fc449ef107.png)

Tx/Rx baudは通信速度で、fckはクロック。
スタートアップルーチンでシステムクロックにHSIを選択しており、USART2はのクロックは8MHzとなる。
115200にするには
USARTDIV=8000000/115200
として設定する。
####USARTのデータ送受信を制御するコード
上記の設定処理と送信/受信制御をコード化してみた。

```c
static void ConfigureUSART(void)
{
	// Distribute clock to USART2
	RCC-> APB1ENR |= RCC_APB1ENR_USART2EN;

	// Configure baudrate
	USART2->BRR  = 8000000L/115200L;

	// Eable USART, TX and RX
	USART2->CR1 |= USART_CR1_UE | USART_CR1_TE | USART_CR1_RE;
}

char ReadUSART(void)
{
	// Wait for a char on the USART
	while (!(USART2->ISR & USART_ISR_RXNE));
	return USART2->RDR;
}

void WriteUSART(char c)
{
	// Wait for a char on the USART
	while (!(USART2->ISR & USART_ISR_TXE));
	USART2->TDR = c;
}
```

RCC->APB1ENR |= RCC_APB1ENR_USART2EN; はUSART2のクロックを有効にしている。
また、ワード長とstop bitはデフォルトのため設定していない。
これでUSARTでのデータ送受信が可能になった。
ここまでの処理をUSART Driverとする。

##printfを作る
ごめんなさい。printfは[A tiny printf](http://www.sparetimelabs.com/tinyprintf/tinyprintf.php)を使用する。
###USART Driverと接続
printfの初期化関数内でUSART Driver経由で文字が出力されるように関数を登録する。
次の`init_printf`に出力関数が登録できる。

```c
void init_printf(void (*putf)( char))
{
    stdout_putf = putf;
}
```
ここに作成した`WriteUSART`をセットすればprintfでの出力がUSARTになる。
がしかし、minicomでは改行がうまくいかない問題が発生したので、USART Driverに以下の処理を追加。
この`PutcUSART`をprintfにセットすることにした。

```c
void PutcUSART(char c)
{
    if(c == '\n'){
        WriteUSART('\r');
    }
    WriteUSART(c);
}
````

##テスト
printfでhello worldを出力するプログラムを書く。

```c
#include "stm32f303x8.h"
#include "usart_driver.h"
#include "printf.h"

// Main -----------------------------------------------------------------------
int main(void)
{
	InitUSART();
    
	init_printf(PutcUSART);

	printf("Hello World\n");
	printf("Hello World\n");
	printf("Hello World\n");

	return 0;
}
```

プログラムを実行する。
![hello_world.png](https://qiita-image-store.s3.amazonaws.com/0/140055/d40e5831-02b0-be02-ab40-c8fbbf92fc88.png)

printfでのhello world出力に成功した!!
##コード
ここまでのコードはGithubにおいた。
[stm32/v02](https://github.com/mitazet/stm32/tree/master/v02)
##最後に
hello worldまでは長く険しい道のりだった。
しかし、今やprintfができる環境になった。
これで開発がしやすくなる。
作っている間に、クロックの設定やら割り込みやらを使用していないため、
性能を全然活かせていないことに気付いた。
##参考サイト
[ST公式](http://www.st.com/en/microcontrollers/stm32f303k8.html)
[yuki-sato.com](http://yuki-sato.com/wordpress/%e9%9b%bb%e5%ad%90%e5%b7%a5%e4%bd%9c%ef%bc%9a%e3%83%9e%e3%82%a4%e3%82%b3%e3%83%b3/stm32/)