#STM32 Nucleo Boardでベアメタルなhello world
これまで開発環境を構築し、実行ファイル作成までできるようになった。
しかし、私はなぜかCUI開発環境なので、GDBデバッグだけでは効率的でないと思っていた。
ここはひとつ「printf」を作るしかない。
今回はSTM32 Nucleo Boardでベアメタルな「hello world」を実現するしかない!
##開発ターゲット
STM32 Nucleo Board STM32F303K8
##STM32 Nucleo Boardでhello worldするには
STM32 Nucleo Boardには外部ディスプレイがない。しかし、USARTが使用可能。
ということで今回はシリアル出力で「hello world」という文字列を表示させる。
###完成イメージ
printfをたたくとUSART Driver経由してシリアルに「Hello World」が出力されるイメージ。
##USART Driverを作る
今回は学習のために自作することにした。
###USARTを使用するには
BoardにはST-Linkが搭載されており、USB端子でUSART通信ができるためそれを使用することにした。
SB2とSB3は購入時でONだったので PA2 → USART TX(送信部) PA15 → USART RX(受信部)となる。
USB端子なので、はんだ付けしなくていいなとは思ったけど、そこから先はよく分からず。
yuki-sato.comを参考にさせてもらった。
実はUSART専用のピンはなくて、GPIOとの共有ピンになっていてGPIOの設定をすることでUSARTピンとして切り替えて使います。
切り替えると当然ながらGPIOとしては入力も出力も使えなくなるのですが、USARTのピンとして使えるようになります。
ただ、「じゃあPortAの0をTx(送信)にする!」みたいにどのピンでもUSARTのピンに出来るわけではありません。
まず、ピン1つに16種類までの機能が割り振られていて、その16種類のどれかに切り替えるという感じになります。
なるほど。さっきのPA2ピンとPA15ピンをUSARTに切り替える必要があるのか。
データシートをみるとGPIO PortAは次のようになっている。
これにより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は次にようになっている。
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は次のようになっている。
PA2/PA15をAlternativeFunction7に切り替えるにはGPIOA_AFRLのAFR2に0111/GPIOA_AFRHのAFR15に0111を設定する。
####USARTピンを切り替えるコード
ここまでをコードにしてみた。
レジスタの定義などはstm32f303x8.hに詳細に定義されているため、
それを使用している。
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をよく読んでみたら、制御手順が載っていた。
送受信制御は以下の流れになる。
####送信制御
- USART_CR1レジスタのM bitからワード長を設定する。
- USART_BRRレジスタを使用して、ボーレートを選択する。
- USART_CR2レジスタでstop bitを設定する。
- USART_CR1レジスタのUEビットに1を書き込んで、USARTを有効にする。
- USART_CR1のTEビットをセットして、最初の送信とアイドルフレームを送信する。
- 送信するデータをUSART_TDRレジスタに書き込む(これによりTXEビットがクリアされる)。
- USART_TDRレジスタにデータを書き込んだ後、TXE=1になるまで待つ。これは、TXがEmptyであること(送信完了)を示す。
####受信制御
- USART_CR1レジスタのM bitからワード長を設定する。
- USART_BRRレジスタを使用して、ボーレートを選択する。
- USART_CR2レジスタでstop bitを設定する。
- USART_CR1 レジスタの UE ビットに 1 を書き込んで、USART を有効にする。
- 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レジスタにUSARTDIVを設定する。USARTDIVは次の式で求められる。
Tx/Rx baudは通信速度で、fckはクロック。
スタートアップルーチンでシステムクロックにHSIを選択しており、USART2はのクロックは8MHzとなる。
115200にするには
USARTDIV=8000000/115200
として設定する。
####USARTのデータ送受信を制御するコード
上記の設定処理と送信/受信制御をコード化してみた。
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を使用する。
###USART Driverと接続
printfの初期化関数内でUSART Driver経由で文字が出力されるように関数を登録する。
次のinit_printf
に出力関数が登録できる。
void init_printf(void (*putf)( char))
{
stdout_putf = putf;
}
ここに作成したWriteUSART
をセットすればprintfでの出力がUSARTになる。
がしかし、minicomでは改行がうまくいかない問題が発生したので、USART Driverに以下の処理を追加。
このPutcUSART
をprintfにセットすることにした。
void PutcUSART(char c)
{
if(c == '\n'){
WriteUSART('\r');
}
WriteUSART(c);
}
##テスト
printfでhello worldを出力するプログラムを書く。
#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;
}
printfでのhello world出力に成功した!!
##コード
ここまでのコードはGithubにおいた。
stm32/v02
##最後に
hello worldまでは長く険しい道のりだった。
しかし、今やprintfができる環境になった。
これで開発がしやすくなる。
作っている間に、クロックの設定やら割り込みやらを使用していないため、
性能を全然活かせていないことに気付いた。
##参考サイト
ST公式
yuki-sato.com