はじめに
これは以下記事の内容の一部なります。
設計
基本的な処理が決まったため、次は詳細な設計をしていきたいと思います。
この設計パートでは初期化処理のみ説明します。
その他の処理について大したことないため実装パートにて説明します。
まず重要になるのが下のシステムブロック図になります。
これは後で何度も確認することになるため、まずはここに載せておきます。
[DS10693 P16]
用語
説明する前にこれから色々用語が出てきますのでその説明を先にしておきますね。
・RCC:STM32のクロック/リセットを制御するためのペリフェラルのこと。
・GPIO:STM32のデジタル入力/出力を制御するための機能のこと。
・EXTI:STM32の外部信号(GPIOピンなど)をトリガとして割り込みやイベントを生成する機能のこと。
・SYSCFG:STM32のシステム機能や設定を制御するためのペリフェラルのこと。
・NVIC:ARM Cortex-Mコアに内蔵されている割り込みコントローラーのこと。
GPIOレジスタ制御モジュール
GPIOを制御するためのモジュールになります。
今回の用途としては、LEDとボタンの制御になります。
以下がGPIOデバイスドライバの初期化制御フローになります。
何をしているか順に説明していきます。
1. RCCの設定
まずは使用するGPIO機能のバスのクロックを有効にしてあげる必要があります。
そもそもなぜバスのクロックを有効にする必要があるのか?
ですが、クロックを有効にしないと動作しないからです。
電子機器は全てクロックに同期して動作します(しています)。
人間で言うところの心臓ですね。人間も心臓がドクンドクン動いているタイミングで血液を全身に流しているじゃなないですか。それと全く同じ原理です。
(人間の場合止めたら終わりですけどね)
STM32にはGPIOのグループが8つ割り振ってあります。GPIOA〜Hになります。
で、今回GPIOで制御したいのがLEDとボタンになりますのでそれがどこにつながっているかを確認します。
今回使用するLEDはLD2(Green LED)というやつで、ボタンはB1 USER(青のボタン)になります。
[UM1724 P13]
ではこれらがどこのピンにつながっているかですが、回路図をみてみましょう!
LEDはPA5で、ボタンはPC13ということがわかりましたね。
これがGPIOのどのグループに属しているかですが、Pの後のアルファベットがグループ名になります。
なので、GPIOAとGPIOCになります。
GPIOA/Cがどのバスに接続されているかというと、AHB1というバスですね。
RCC_ENRレジスタがバスのクロック供給を制御しているものになります。
[RM0390 P144]
0bit目がGPIOA、2bit目がGPIOCになりますね。
どのように制御するかと言うと、この図の少し下に以下の様に書いてありますね。
0が無効で、1が有効。
なので、このレジスタの0bit目と2bit目を1にすればGPIOA/Cにクロック供給ができるようになりますね!
ちなみにこのレジスタの初期値は全て0に設定されています。上記図のReset Valueを確認すること
ということで、これにてRCC設定は完了!この調子で次に行きましょう!
2. GPIOの設定
次はGPIOの端子設定をしていきます。
ここでは使いたい端子が入力で使いたいのか出力で使いたいのかを設定します。
では、レジスタを見ていきましょう!
GPIO_MODERレジスタというのが入出力モードの設定をしているものになります。
[RM0390 P187]
MODERが0〜15までありますが、これはピンの番号になります。
今回はLEDが5番で、ボタンが13番でしたよね。
なので、0-1bitと26-27bitが対象になります。
次に何の値を設定するかですが、レジスタ図の下に値の組み合わせが記載されていますね。
"10"と"11"は一旦無視していいです。
LEDが出力なので"01"を設定し、ボタンが入力なので"00"を設定すればOKです。
最後に大事なことがあるのですが、図の上部を見ると表題にGPIOx_MODERと記載ありますね。
なぜ途中にxが記載されているかと言うと、このxはグループ名を指しています。
つまり、このレジスタはグループごとに管理されています。
使いたいピンが属しているグループのレジスタを設定する必要があります。
3. SYSCFGの設定
次はSYSCFGの設定になります。これをどこに使用するかは次の章で説明しますね。
一旦このように設定するんだぐらいで留めといてください。
SYSCFG_EXTICRレジスタを設定します。
[RM0390 P199]
1〜4まであると思いますが、中身を見てみるとEXTIの後に数字が記載されていますね。
これがGPIOのピン番号のことを指しています。今回のボタンは13ピンになるためCR4が対象になります。
4bit〜7bitが13ピンの設定になりますので、これに"0010"をセットすればOKになります。
4. EXTIの設定
次は外部割り込みの設定をしていきます。
ここではGPIOの割り込みを設定します。
デバッグ機能として、ボタンから割り込みを発生させてPCへデータ転送する機能があるためその設定になります。
EXTI_IMRレジスタというのが割り込みを有効/無効設定をしているものになります。
[RM0390 P247]
レジスタの見方ですが、MRの後に数字が割り振ってあると思います。この数字はGPIOのピン番号になります。
今回はボタンのピン番号が13番なので、MR13(13bit目)が対象となります。
"1"を設定すれば割り込みが有効になります。
あれ?このレジスタにはxが付いていないけどなんで?と疑問が湧くと思いますが安心してください。説明します。
このレジスタは別のレジスタと組み合わせて使用します。
以下を見てみると、セレクタにSYSCFG_EXTICRxと記載ありますよね。
この設定にてどの入力を使用するかを決める仕組みとなっています。
これは先ほど「3 SYSCFGの設定」で設定したものになります。
[RM0390 P246]
次の設定は、EXTI FTSRレジスタになります。
これは割り込みの立ち下がりエッジ検出を有効にするかを設定をしているものになります。
[RM0390 P248]
そもそも割り込みはどのように検出しているかですが、エッジとレベルの2通りがあります。
今回はエッジを採用するためこの説明をします。
立ち上がりエッジ:信号が0→1に変化するタイミングで検出
立ち下がりエッジ:信号が1→0に変化するタイミングで検出
今回なぜ立ち下がりエッジを使用するかですが、ボタンの回路図を見てもらうとわかります。
プルアップされていますね。つまりボタンが押されていない時は"1"固定で、押されると"0"に変化する仕組みとなっています。そのため立ち下がりエッジ検出を採用しました。
なのでEXTI FTSRレジスタの13bit目に"1"を設定すれば期待する設定になります。
まだEXTIレジスタの設定はあります。さっきの全体ブロック図を見るとEXTIはGPIOと同じバスではないことがわかりますね。
繋がっているバスはAPB2ですね。ではこれを有効にする必要があります。
RCC_APB2ENRレジスタというのがAPB2バスのクロックを設定をしているものになります。
[RM0390 P149]
あれ?APB2のレジスタを見てもEXTIがないぞ、、、と思いましたよね。
そうなんです、EXTI自体に専用のクロックはないんです。
ではどうやって動かすのかですが、実はこのEXTIはGPIO端子に依存する形で動作します。
なので、使用するGPIOのクロックを有効にすることでEXTIの機能が有効になります。
また、もう一つクロックを有効にしないといけない機能があります。
SYSCFGのクロックを有効にする必要があります。なぜSYSCFGなのかと言うと、
EXTIラインを使用したいGPIO端子をマッピングするためにSYSCFGが必要になります。
要は先ほど出てきたセレクタにSYSCFGの設定が必要と言いましたがそれを使用するためにSYSCFGのクロック設定が必要ということです。
なのでここで設定するのは、APB2_ENRレジスタのSYSCFG(14bit目)を設定してあげます。
5. NVICの設定
最後はNVICの設定をしてあげます。
これはレジスタ設定がないためここでは特にないです。実装の時にちょこっと出てくるぐらいです。
補足
NVICとEXTIの関係とは?
めちゃくちゃ簡単に説明すると、
まずEXTIで割り込みイベントを発生させる。その後その割り込みイベントをNVICが受け取り処理する。
実装
先ほどの設計を踏まえて実装していきます。
init_gpio:初期化処理
void init_gpio(void)
{
// 1. レジスタのアドレスを定義
// GPIOC13ピン(青のボタン)の設定
GPIO_TypeDef *pGPIOC;
pGPIOC = BASE_ADDR_GPIOC_PERI;
// GPIOA5ピン(LED)の設定
GPIO_TypeDef *pGPIOA;
pGPIOA = BASE_ADDR_GPIOA_PERI;
RCC_TypeDef *pRCC;
pRCC = RCC;
EXTI_TypeDef *pEXTI;
pEXTI = EXTI;
SYSCFG_TypeDef *pSYSCFG;
pSYSCFG = SYSCFG;
// 2. LEDの初期化
g_led_data.current_value = 0;
g_led_data.previous_value = 0;
// 3. レジスタの設定
// GPIOCのクロックを有効化
pRCC->AHB1ENR |= (1 << 2);
// GPIOAのクロックを有効化
pRCC->AHB1ENR |= (1 << 0);
// GPIOC13ピンを入力モードに設定
pGPIOC->MODER &= ~(0x3 << 26);
// GPIOA5ピンを出力モードに設定
pGPIOA->MODER |= (0x1 << 10);
// SYSCFGのクロックを有効化
pRCC->APB2ENR |= (1 << 14);
// GPIOC13ピンをEXTI13に接続
pSYSCFG->EXTICR[3] &= ~(0xF << 4); // clear
pSYSCFG->EXTICR[3] |= (0x2 << 4); // set
// GPIOC13ピンの割り込みを有効化
pEXTI->IMR |= (1 << 13);
// GPIOC13ピンをエッジ検出に設定
// 先にEXTI13をGPIOC13に接続してから設定する
// でなければデフォルトの設定が使われる
pEXTI->FTSR |= (1 << 13);
// EXTI15_10_IRQnの割り込みを有効化
NVIC_EnableIRQ(EXTI15_10_IRQn);
}
少し補足しておきます。
1. レジスタのアドレス定義
まずは、先ほどデータシートで見ていたレジスタを定義してあげます。
xxx_TypeDefと言うのが、レジスタの構造体になります。
なので、この構造体の定義を見ると、MODERやOTYPERなどのレジスタを持っています。
構造体でポインタ変数を作成した後に、レジスタのアドレスを入れてあげます。
このアドレスは「stm32f446xx.h」というファイルに定義されています。
2. LEDの初期化
今回LEDの状態を構造体で持ちたいと思います。
構造体 | メンバ | 説明 |
---|---|---|
led_t | current_value | 現在の値を保持 |
previous_value | 前回の値を保持 |
起動時は消灯していたいので0初期化としています。
3. レジスタ設定
この設定は先ほどの設計で説明したものを書いています。
あまり説明する箇所はないですが、2点だけ追加で説明します。
・レジスタのビット操作
これはデバイスドライバの話というより組み込みの基本的な話ですが。
レジスタのビット操作をする場合は、論理和と論理積を使用して設定します。
これはビット操作する場合の鉄板のやり方なので覚えておいてください。
あるビットに1を設定したい場合は論理和を使用してください。
例) 10bit目を1にしたい場合
xxx |= (1 << 10)
あるビットに0を設定したい場合は論理積を使用してください。
例) 10bit目を0にしたい場合
xxx &= ~(1 << 10)
・NVICの設定
割り込みの設定をする場合は、割り込みコントローラーにどの割り込みを使うのかを
教えてあげる必要があります。
それが、NVIC_EnableIRQ()という関数になります。
引数は「stm32f446xx.h」というファイルにあるIRQn_Typeというenumを使用します。
今回の割り込みはEXTI13を使用するためEXTI15_10_IRQnを引数に与えます。
set_led_data:LED設定処理
void set_led_data(uint32_t data)
{
uint8_t numeric_value = ascii_to_numeric(data);
// 0か1以外のデータは無視
if (numeric_value != 0 && numeric_value != 1) {
return;
}
g_led_data.current_value = numeric_value;
}
static uint8_t ascii_to_numeric(uint32_t data)
{
// ASCIIコードの0は0x30のため、0x30を引くことで数値に変換
uint8_t numeric_value = data - '0';
return numeric_value;
}
概要:UARTからのデータを受け取りLEDデータを設定する。
処理内容:
以下のようなことをやっています。
1. UARTからデータを受信する。
2. データを数値へ変換する。
3. 数値が0 or 1を判定する。
0 or 1の場合、LEDデータに今回の数値を設定。
0 or 1以外の場合、処理終了。
compare_led_status:LEDの状態監視処理
void compare_led_status(void)
{
// 前回の値と異なる場合のみLEDの状態を変更
if (g_led_data.current_value != g_led_data.previous_value) {
GPIO_TypeDef *pGPIOA;
pGPIOA = BASE_ADDR_GPIOA_PERI;
if (g_led_data.current_value == 0) {
pGPIOA->ODR &= ~(1 << 5);
} else {
pGPIOA->ODR |= (1 << 5);
}
g_led_data.previous_value = g_led_data.current_value;
// DMAの送信を開始
request_dma_start();
}
}
概要:LEDの状態を監視する。
処理内容:
以下のようなことをやっています。
1. LEDの状態を比較する。
異なる場合、次の処理へ進む。
同じ場合、処理終了。
2. 今回値が0かどうかを判定する。
0の場合、GPIOのアウトプットデータレジスタ(ODR)に0を設定する。
1の場合、GPIOのアウトプットデータレジスタ(ODR)に1を設定する。
3. LEDの前回データに今回値を設定する。
4. DMAの開始処理を呼び出す。 ※こちらの処理についてはUARTデバイスドライバ編にて解説します。
EXTI15_10_IRQHandler:割り込みハンドラ
割り込みハンドラも記載しておきます。
※こちらは「stm32f4xx_it.c」ファイルに記載しています。
void EXTI15_10_IRQHandler(void)
{
// DMAの送信を開始
request_dma_start();
clear_exti_pending_bit();
}
void clear_exti_pending_bit(void)
{
EXTI_TypeDef *pEXIT;
pEXIT = EXTI;
if (pEXIT->PR & (1 << 13)) {
// 割り込みフラグをクリア
pEXIT->PR |= (1 << 13);
}
}
「EXTI15_10_IRQHandler」と言うのが割り込みハンドラになります。
こちらの関数名は任意で付けることができないです。ではどこからこの名前を持って来たかと言うと、以下の表に割り込みベクタテーブルが記載してあります。
[RM0390 P239]
このテーブルのAcronym欄の名前をコピーし、「startup_stm32f446retx.s」というファイルで検索するとヒットします。
終わりに
これにてGPIOのレジスタ制御モジュール作成は完了となります。