STMのGPIOはどうなってるのか(LEDをつける)

前々回CoIDEを使ってボタンに合わせてLEDを点灯させるプログラムを書いて実行しました。
プログラムはこれでしたね。

#include "stm32f4xx.h"

int main(void)
{
  RCC->AHB1ENR |= RCC_AHB1ENR_GPIODEN;

  GPIOD->MODER = 0x40000000;
  GPIOD->ODR = 1 << 15;
  while(1);
}

このプログラムでなぜLEDが点灯するのでしょうか。
では、今回はこれがどうなっているのか順番に見ていきたいと思います。
ただし、C言語についてはある程度分かるものとして話を進めますね。

GPIOとは

STMマイコンには外部とシリアル通信するためのUARTモジュール、アナログ信号をデジタルに変換するA/D変換モジュールなどがありプログラムから使えます。
こういうプログラムから(CPUから)利用できるモジュールのことをペリフェラル(Peripheral)といいCPUと一緒にあの黒いチップの中に入っています。
これでさっきのを言い直すとUARTペリフェラルだし、A/Dペリフェラルとなります。
LEDをつけたり消したりといった、マイコンのピンの電圧を単純に上げたり下げたりするのもペリフェラルでGPIO(General Purpose IO)ペリフェラルといいます。
つまりプログラムからピンの電圧を上げたり下げたりするというのは、プログラムからそのマイコンのGPIOペリフェラルを制御するということになります。
ちなみに世の中にはいろんなマイコンがありますが、CPUの速さの違いなどもありますが、多くはペリフェラルの違いです。
下にあるのはSTM32F40xxxシリーズのブロックダイアグラムというチップの内部構成です。

Screen Shot 2015-12-29 at 12.20.34出典: STM DataSheetDM00037051

左上にある「ARM Cortex-M4 168Mhz FPU」というのがいわゆるCPUで、左中央にある 「GPIO PORT A」とかがGPIOペリフェラルです。
もちろん初めから見て「なるほど分かった」と分からなくても大丈夫。
ただ、CPUとペリフェラルが沢山一つのチップに入っているんだなというのがイメージしてもらえればOKです。

で?どうやってLEDをつけるのさ

ペリフェラルの使い方はメーカーであるSTMが出しているリファレンスマニュアルに全て書いてあります。一度ダウンロードしておいてください。残念ながら全部英語です。

http://www.st.com/web/en/resource/technical/document/reference_manual/DM00031020.pdf

さて、GPIOを使ってLEDをONにしましょう。読める人はリファレンスマニュアルのGPIOのページを開きながら。

レジスタ ODR がその人

まず、LEDはどこにつながってるのでしょうか。Disocoveryボードのユーザーマニュアルによると

Screen Shot 2016-01-05 at 21.28.47

PD12〜PD15に緑、オレンジ、赤、青のLEDがつながっています。
青のLEDを使うことにします。
これによると青のLEDは62番ピンにつながっていることがわかります。
ただ、これだけ分かってもプログラムからLEDをONにはできません。
GPIO-62 = 1;
みたいなのはできません。プログラムから見るのはPDってやつです。
PDっていうのはさっきのGPIOの話で、このチップには沢山のピンがあって管理が大変なので、ピンをポートと言う単位で分けています。PDとはポートDの略です。
つまり実際の62版とかでなく青いLEDを点灯させるためにはGPIOで管理されてるポートDの15というのをいじればいいわけです。
プログラムからいじるのはODRというレジスタです。
62番ピンのLEDをONOFFするためにはODRレジスタの対応するビットを1にしたり0にしたりすればOKです。1にするとON(3.3V)になり0にするとOFF(0V)になります。
ちょっとReferenceManualのODRのページを見てみましょう。

Screen Shot 2016-01-05 at 21.19.17出典: ReferenceManual

ここにあることを読むとこういうことになります。
「ポートDの15をHighにしたければポートDのODRの下から15bit目を1にしろ」
ということです。こんな感じ。

(PORTDのODR) = 1 << 15;

さて、この(PORTDのODR)はどうしたらいいんでしょう。
先に正解をお知らせすると

*(0x40020C14) = 1 << 15;

こうなります。
レジスタというのはROMやRAMといっしょにアドレス上に存在します。
PORTDのODRというのは32bitのレジスタで、0x40020C14番地にあります。つまりポートDのODRの32bitを全部0にしたければ0x40020C14に0を書き込めばいいですし、全部1にしたければ0x40020C14に0xFFFFFFFFを書き込めばいいことになります。
今回1にしたい(ONにしたい)のはポートDの15ビット目なので上にあるようなコードになるわけです。

便利なstm32f407xx.h

ただ、毎回「じゃあ今度はポートAのODRをいじりたいけどアドレスは、、、」と調べるのは大変です。
ここで使えるのがCMSISで入れたstm32f407xx.hです。includeしているstm32f4xx.hのなかでincludeされています。
この中にはほぼ全部のアドレスがdefine文で分かりやすい文字になっています。
なので、さっきの文はこのように書けます。

[c]
GPIOD->ODR = 1 << 15;
[/c]

これはただのdefineと構造体を使っているだけなので、コンパイル時にただのアドレスに変換されますので、速度的な問題はありません。

出力か入力か

では、さっそくLEDをつけてみましょう

[c]

#include “stm32f4xx.h”

int main(void)
{
GPIOD->ODR = 1 << 15;
while(1);
}

[/c]

ただ、これだとLEDはつきません。
何故かと言うとGPIOは起動直後は入力モードになっているからです。
GPIOは出力だけでなく入力にも出来て、それは同時には使えないようになっています。
入力か出力かはピン1つ1つ設定できます。ポートDの15は出力で14は入力ということもできます。
そのモード設定はMODERレジスタでできます。

Screen Shot 2016-01-05 at 22.16.17
出典: ReferenceManual

1つのピンの設定に2bitつかうようです。
ポートDの15はMODERの30,31bitで設定します。組み合わせは4パターンで
00 入力
01 出力
10 外部機能
11 アナログ入力
となっています。
11はともかく10は何かというとGPIOでない例えばSPIとかUSBとかをつなぐときに使います。別の回でまた紹介します。
で、今回は出力なので01にしましょう。

[c]

#include “stm32f4xx.h”

int main(void)
{

GPIOD->MODER = 0x40000000;
GPIOD->ODR = 1 << 15;
while(1);
}

[/c]

これでいけるはず!
ただ、これでも点灯はしません。

ペリフェラルのクロック

点灯しない理由は「GPIOの電源が入っていないから」です。
実はSTMマイコンではペリフェラルごとに電源をONOFF出来るようになっています。これにより使っていないペリフェラルの電源を切っておけば少ない電流でも動かせるわけです。
そして、起動直後は基本的に全てのペリフェラルはOFFになっています。
なのでレジスタに何を書き込んでも何の反応もないわけです。
ちなみに”電源”と僕は言いましたが、正確に言うと電源自体は来ているのですが、クロックが供給されない状態になります。
これがSTM32F4系のクロック図です。今回の話に関係あるところだけ抜き取っています。

Screen_Shot_2016-01-05_at_23_08_20出典: ReferenceManual

マイコンのクロック源はマイコンの外側についている8Mhzのクリスタルです。
それを内部で100倍したりして使っています。ただ全部の場所で同じクロックというわけじゃなくて、場所によってクロックを分周して使っています。CPUのクロックは矢印で示した上の方です、で、今回関係あるGPIOは下の矢印で示したAPBx ペリフェラルクロックというやつです。
APBxはCPUのクロックと同じか一番遅くて1/16となることがわかります。
そして、APBxにつながったペリフェラルごとそれぞれON/OFFができます。
APBはAPB1とAPB2があるのですが、GPIODはAPB1につながっています。ONにするにはRCC_AHB1ENRレジスタを使います。

Screen Shot 2016-01-05 at 23.13.58出典: ReferenceManual

APB1においてGPIODを有効にするかどうかはODRのように対応するビットを1にすることで決められます。この図によると GPIODはAHB1ENRの3bit目です。
このbitは最初0でこれを1にすればGPIODにクロックを供給できます。
クロックが供給できればレジスタの書き込みん対してちゃんと動作するようになります。(同時に何もしてなくてもそれなりの(本当にわずかな)消費電流を食うことになります)
これをレジスタに書き込む前に行えば、完成です。

[c]

#include “stm32f4xx.h”

int main(void)
{
RCC->AHB1ENR |= RCC_AHB1ENR_GPIODEN;

GPIOD->MODER = 0x40000000;
GPIOD->ODR = 1 << 15;
while(1);
}

[/c]

このRCC_AHB1NERGPIODNENというのは((uint32_t)0x00000008)のことです。ちょうど3bit目だけ1の数字です。この方が分かりやすいですよね、これもstm32f407xx.hで定義されています。
これをor演算でAHB1ENRにセットしています。(or演算なのは、他の既に1になっているものに影響を与えずにここだけ追加で1にするため)
どうでしょう、これで青色LEDが点灯するようになったと思います。

まとめ

  • マイコンの中にはペリフェラルというのがあり、いろいろな機能を持っている
  • GPIOはピンの電源ON,OFFなどを管理しているピンのペリフェラル
  • GPIOではピンはポートで管理されている
  • ピンがhighかlowかはそのポートのODRレジスタでセットできる
  • ピンが出力か入力かなどはそのポートのMODERレジスタでセットする
  • ペリフェラルごとにクロックの供給はON,OFF出来て、基本はOFFなので最初にONにしないといけない

どうでしょうか、ただLEDを点灯するだけでもしらないといけないことが多くて大変ですよね。
ただ、進んでいくうちに段々と楽になっていきます。
次回は入力にしてボタンを押したらLEDをつける。といったことをやってみましょう。

“STMのGPIOはどうなってるのか(LEDをつける)” への1件のコメント

  1. のアバター
    匿名

    分かりやすい解説で助かりました!ほかのページも拝読します

匿名 へ返信する コメントをキャンセル