前回の記事ではMCUXpressoというIDEを導入して、実際にLEDを点滅させました。
サンプルコードでは、LEDは、GPIO(General Purpose Input/Output)の出力をHigh/Lowで切り替えることによって、点滅させています。今回はその設定方法を簡単に説明したいと思います。
FRDM-K64Fで前回赤色で点滅させたLEDは、基板上に実装されているフルカラーLEDで、色を自由に変えられます。今回の目標はその色を青に変更することです。
この記事でわかること
- 回路図の見方
- リファレンスマニュアルの見方
- GPIOの使い方
回路図をみる
下が、FRDM-K64Fです。ボードに実装されてるLEDを制御するには、当たり前ですが、このLEDがマイコンのどこのピンにつながっているかを確認する必要があります。
下に回路図のリンクを貼っておきます。
回路図内でLEDを探します。
発見!
これを見ると3.3Vの電源がそれぞれRED、GREEN、BLUEに接続されていて、GND側(カソード側)は、PTB22, PTE26, PTB21につながってることがわかります。
PTB、PTEとかいうのは、マイコンの汎用ポートの名前で、K64Fの場合、PORTA~PORTEの合計4つのポートがあり、それぞれが32ピンまでのIOをコントロールすることになってます。
ちなみに実際のピン数はマイコンによって変わります。K64Fの場合、数えてみるとPORTAが20pin、PORTBが25pin、PORTCが19pin、PORTDが8pin、PORTEが10pinありました。
ここでは説明しないですが、Port毎にプルアップ/ダウンの設定、割り込み処理有効化/無効化、オープンドレインの設定する/しない、駆動電流の制限などが色々決められます。
LEDの回路図の上の方にMCUのピン配が載っていて、それが下図です。全部で100pinでてますね。各ピンはマルチプレクサといって、1つのpinで何役もこなせるようにできてます。ピンのラベルに色々書いているのは、そのpinは何と何ができますよってことが明記されている訳です。
例えば、赤色LEDを光らせていたPTB22(下のマーカ部分)をみてみます。PTB22/SPI2_SOUT/FB_AD29/CMP2_OUTとありますが、これはデジタル入出力(PTB22)の他にもSPI通信用とか…に使かえるぜ、ってことです。
LEDの3色は下のようにつながってました。
GPIOはPTA~PTEと同じようにGPIOA~GPIOEといった感じで、制御するグループがわけられてます。青色、赤色ともおなじPORTBになってるので、GPIOBをマイコンで制御することになります。
Pin Config Toolで使用するPINを定義する
ピンの設定には、Config Toolというアプリを使います。便利です。Defaultの画面レイアウトだと、右上のほうにマイコンのマークがあるので、それをクリックします。あるいはメニューバーからConfigTools->Pinsでもアプリを起動できます。
Configツールのメイン画面はこんな感じ。
実際にサンプルコードではどうなってるかみてみます。
①の画面、左の上にソート用のボタンがあるので、一番左のShow routed pinをクリックすれば、いま使っているピンの状態が見れます。サンプルでは2ピンしかつかってません。#68-PTB22の赤色LED用のGPIOとデバッグ用のラインです。
ついでに②と③もみてみます。
PTB22はDirectionが”Output”となっていて、デジタル出力用のピンにちゃんと設定されてます。GPIO initial stateは”Logical 0″となっていて、電源オン時の初期値は0Vが出力されるってことです。LEDは電源にぶらさがっていたので、初期化のタイミングで点灯することになります。
ほね、実際に青色を点滅させます。青色=PTB21でしたので、①の画面でPTB21を探します。pin67にあるので、チェックボックスをクリックすると、
こんなポップアップがでます。これは前述したこのピン何用ですか?の設定画面で、今回はGPIOとして使うので、PTB21をクリックします。
すると、もっかいShow routed pinで表示させると、PTB21が増えました。③の画面を確認します。
リストには追加されてますが、Direction がNot Specifiedになってるので、これをOutputにしときます。ついでにPTB22のinitial stateを”Logical 1″にしときます。初期値をHighにして、赤LEDを消すためです。
終わったら、コードに変換します。画面の上のほうにあるUpdate Codeをクリックします。
すると、コードの変更点がリストアップされます。色々変更されてるっぽいですが、実際に処理が追加されてるのは pin_mux.cとpin_mux.hの2ファイルです。
changeをクリックするとその差分がみれます。
pin_muxは使用するピンのアサインや設定をコントロールするコードが書かれてます。とりあえず内容はおいておいて、OKを押します。
サンプルプロジェクトfrdmk64f_led_blinkyにあるメインプログラムled_blinky.c内のdefine文を下のように変更します。
/*******************************************************
* Definitions
********************************************************/
//もとは BOARD_LED_RED_GPIO
#define BOARD_LED_GPIO BOARD_LED_BLUE_GPIO
//もとは BOARD_LED_BLUE_PIN
#define BOARD_LED_GPIO_PIN BOARD_LED_BLUE_PIN
実際のLチカを指示している箇所は、↓のとおり。これはなんも編集してないです。
int main(void)
{
/* Board pin init */
BOARD_InitPins();
BOARD_InitBootClocks();
/* Set systick reload value to generate 1ms interrupt */
if (SysTick_Config(SystemCoreClock / 1000U))
{
while (1)
{
}
}
while (1)
{
/* Delay 1000 ms */
SysTick_DelayTicks(1000U);
GPIO_PortToggle(BOARD_LED_GPIO, 1u << BOARD_LED_GPIO_PIN);
}
}
ビルドして実行すると、青になりました。めでたしめでたし。
で?となるので、もうちょっと踏み込んで中身をみます。
Lチカプログラムの中身
具体的にみていく前に理解するにはMemory Mapped IOの考え方をざっくり掴んどく必要があるとおもいます。
以前ちょっと、Arduino Uno関連の記事でMemory Mapped IOの説明をしてます。”メモリーマップを確認する”という所から読むと良いです。
要約すると、MMIOではGPIOとかの周辺機器は、とあるメモリーアドレス上にいちいち割り当てられていて、所望の動作に合ったレジスタを変更することで、色々な制御ができる。 ということです。
どこのアドレスにどんなレジスタがあるのか確認していく必要がありますが、リファレンスマニュアル(NXPのエンジニアはRMって呼んでます)にすべてが記載されてます。
で、実際にmainプログラムを見ると、実際にピカピカさせてる関数
GPIO_PortToggle(BOARD_LED_GPIO, 1u << BOARD_LED_GPIO_PIN);
があって、その中身はというと
static inline void GPIO_PortToggle(GPIO_Type *base, uint32_t mask)
{
base->PTOR = mask;
}
です。まず、第一引数になっているBOARD_LED_GPIOってなんでしょうか。F3キーを連打して、定義元にさかのぼっていくと
// pin_mux.hで定義されてる
#define BOARD_LED_RED_GPIO GPIOB
結局は実体は、GPIOBという値でした。そこからさらにF3キーで遡ると
//MK64F12.hで定義されている
#define GPIOB_BASE (0x400FF040u)
#define GPIOB ((GPIO_Type *)GPIOB_BASE)
GPIOBは特定のアドレス0x400F_F040を指してました。ただGPIO_Typeという構造体のポインタでキャストされてます。同じファイルの上のほうで、
/** GPIO - Register Layout Typedef */
//MK64F12.hで定義されている
typedef struct {
__IO uint32_t PDOR; /* offset: 0x0 */
__O uint32_t PSOR; /* offset: 0x4 */
__O uint32_t PCOR; /* offset: 0x8 */
__O uint32_t PTOR; /* offset: 0xC */
__I uint32_t PDIR; /* offset: 0x10 */
__IO uint32_t PDDR; /* offset: 0x14 */
} GPIO_Type;
と定義されています。 __IOとかありますが、これは今のところ、無視で。(実際はvolatile宣言がはいる)
これはマイコンのプログラムではよくあるプログラムの書き方です。制御するペリフェラルの名前(ここでは、GPIOB)の構造体を定義して、該当するレジスタの先頭アドレスを指すようにしとく。構造体内は、CPUのレジスターの定義(RMで書かれている)と同じ順番で、変数を定義しとけば、いちいちアドレスを即値で定義しなくてもよくなります。
GPIOのグループはA~Eまであるので、こうしとくことで使いまわしもできます。でもいちいちペリフェラル毎に定義してかないくてはいけないのか?というと、そうでもありません。
MK64F12.h
これがプロジェクトファイルにありますが、この子がすべてのアドレスを上のような形で定義してくれているので、ユーザがアドレスを直接打ち込むようなことはなくなっています。
で、実際にマニュアルででているGPIOのレジスタは下のような感じです。
今回。サンプルで使っていたレジスタに青いマーカをつけときました。アドレス0x400F_F040からGPIOBの制御レジスタが色々定義されているのに注目。名前はというとさっきの構造体の名前と順番がリンクしてますね。
こうすることで、GPIOB->PTORとすれば0x400F_F04Cのアドレスにアクセスが簡単に出来るって訳です。
それぞれのレジスタはマニュアルでその説明があります。今回のサンプルで使っているPDDRとPTORの内容をみてみます。
ArduinoUNOでも同じでしたが、GPIOのレジスタはビット毎に制御するピンがアサインされていて、GPIOBの場合、Bit0がPTB0, Bit1がPTB1,…,Bit22がPTB22といった感じになります。
これで、最初の関数の中身 base->PTOR = mask; が分かると思います。実際の値に則して書き直すと、GPIOB -> PTOR = 1U<<21; になるので、bit21のPTORに1を書いて、青色LEDを点滅させていたということになります。
GPIOの初期化について
今回のサンプルコードはこのほかにもGPIOのレジスタの設定をおこなってます。
序盤に呼ばれてるBOARD_InitPins関数内の処理GPIO_PinInitという関数です。(MCUExpressoは、関数にフォーカスをあてて、F2を押すと、↓のような感じで関数内の処理がポップアップWindowで表示されます)
gpio_pin_config_tという構造体が直前で定義されていて、構造体のメンバはpinDirectionとoutputLogicになります。それぞれ、ピンの入出力の設定と初期値をLOW/Highにするかの設定です。
そして、それをGPIOの初期化関数GPIO_Pin_Initにポインタで渡しています。
これもよくある方法です。ほとんどの場合、ペリフェラルを使う場合は、初期設定が必要ですが、初期化用の関数は、各設定値をメンバ変数を定義した構造体(サンプルコードだとconfigとか名前がついてる)を定義して、そのポインタを引数としてもらう仕様がほとんどです。
GPIO_Pin_Init関数中身は下のような感じです。
void GPIO_PinInit(GPIO_Type *base,
uint32_t pin,
const gpio_pin_config_t *config)
{
assert(NULL != config);
uint32_t u32flag = 1;
if (config->pinDirection == kGPIO_DigitalInput)
{
base->PDDR &= ~(u32flag << pin);
}
else
{
GPIO_PinWrite(base, pin, config->outputLogic);
base->PDDR |= (u32flag << pin);
}
}
説明したPDDRはここで使われてました。
PinDirectionが出力に設定されているときはGPIO_PinWriteという関数が呼ばれてますが、ここでは説明ははぶきます。実際、PCORとPSORというレジスタが書き込まれますが、確認してみてください。
PORT設定の仕方
内容的にはほぼ終わってますが、冒頭で説明したマルチプレクサの設定の話を少ししときます。MCUの各ピンは色々とやれるヤツなので、どれ使うか最初に宣言しとく必要があります。
/* PORTB21 (pin 67) is configured as PTB21 */
PORT_SetPinMux(BOARD_LED_BLUE_PORT,
BOARD_LED_BLUE_PIN,
kPORT_MuxAsGpio); //pin_mux.c内
その宣言が、この関数PORT_SetPinMuxです。
第一引数は、BOARD_LED_BLUE_PORTの元はPORTB、BOARD_LED_BLUE_PINにはピン番号21が入っています。つまり、PTB21ってこと。PTB21に対して設定をしますってことを関数に伝えてます。
え?何にする?というのが、kPORT_MuxAsGpioです。これは別の箇所でEnumで1という数字で定義されてます。なぜ1なのか、後でわかります。
とにかく、書き直すと PORT_SetPinMux( PORTB, 21, 1)ですね。その中身はこんな感じ。
static inline void PORT_SetPinMux(PORT_Type *base,
uint32_t pin,
port_mux_t mux)
{
base->PCR[pin] = (base->PCR[pin] & ~PORT_PCR_MUX_MASK) |
PORT_PCR_MUX(mux);
}
やっていることは、GPIOのレジスタを書き換えた時と全く一緒です。今回はGPIOBじゃなくて、PORTBになっただけです。PORTBも同じように構造体型のポインタで、PORTBを設定するためのレジスタがアレコレある一画のアドレスの先頭番地を指しています。
でPCRってなに?RMを確認すると、PORTA->PORTEの順番に各ピンに設けられたレジスタ名っぽいですね。
中身をみるとMUXというフィールドがありました。
説明をみると、bit10-8に001という数を打ち込めば、このピンをGPIOにしてあげると書いてあります。
base->PCR[pin] = (base->PCR[pin] & ~PORT_PCR_MUX_MASK) | PORT_PCR_MUX(mux)とか複雑にみえますが、やってることはbit10-8に001を書いて、PTB21をGPIOとして、使うと設定しているだけです。
以上です。
これまで、長々とサンプルの内容を解読してきましたが、基本的にはconfig toolを使えば、少なくともPORT設定とGPIOの初期化は勝手にやってくれるので、直接触れる必要はありません。なので、必要な作業をまとめると
GPIOに必要な4つのステップ
- 回路の確認:回路図をみて、MCUのPORTとピン番号を確認する
- ピンのアサインと設定:Config Toolを使って、1でわかったピンの動作設定をする
- プログラムに反映
- GPIO用のAPIをよぶ:今回はGPIO_PortToggleをつかった
になるでしょうか。簡単ですね。
それでわ、ばんざいGPIO🙌