SNSへはこちら

STM32いじってみた(3) - 設定の記法・GPIO編

前回の投稿から間が空きましたが、引き続いて STM32 でレジスタを使ったプログラミングをご紹介していきます。
今時はやはり設定が煩わしくて HAL 等の抽象化レイヤーを用いることが多いとは思いますが、その需要を無視して、一からレジスタを設定して使い方を理解していこうじゃないかのコンセプトでやっています。

何はともあれ、まずは ユーザーマニュアルとデータシートをダウンロードしてください。ユーザーマニュアルの番号は RM0316 です。

Lチカプログラムで練習

前回のLチカのプログラムを使います。まずは GPIO の設定をしましょう。まずはピン番号と機能を確認しましょうね。以下にそのコードを再掲します。

それでは PB7 を探しましょう。データシートの p.30 によると、30番ピンであることがわかります。では書いていきましょう。

GPIOレジスタに値を書き込む前に

早速 GPIO レジスタに...と言いたいところですが、実際の所これだけでは動きません。STM32 はデフォルトで省電力機能により、ペリフェラルのレジスタ自体にクロック供給をしていないのです。僕は当初これに気づかず、「なんだこのマイコン!?全然動かないじゃないか」どキレていました。まぁ、最近のマイコンはみんなこんな感じですよ。ペリフェラルのコードを書き始める前に、有効化をする項目はないか必ずチェックしましょう

まずは GPIO レジスタ自体にクロックを供給してやる設定をしなくてはなりません。さぁ、ユーザーマニュアルの RCC の欄を見ましょう。

というわけで、RCC_AHBENR という名前のレジスタを設定すれば、GPIO レジスタにクロックが行くのでは、とわかります。このレジスタには、まず

#include "stm32f3xx.h"

としてから(プロジェクト作成時から存在するファイルでは既にインクルードされていると思います)、

RCC->AHBENR

と記述することでアクセスできます。このように、一般に HOGE_HAGE という名前のレジスタへは HOGE->HAGE と記述してアクセスする、と覚えておいてください。
さて、実際に設定をするのですが、GPIOB のビット位置を先程のマニュアルの下を見ることで調べていきましょう。

...ということで、18bit 目がそれですね。ですので

RCC->AHBENR |= (1 << 18);

とします。でもこれではコードとしてどういう機能が有効化されているのかイマイチわかりにくいですね。ですので、HAL ライブラリに標準で #define されているマクロを使いたいと思います。

マクロでビットを設定

マクロを使った記述は次のようになります。

RCC->AHBENR |= RCC_AHBENR_GPIOBEN;

すなわち、マクロA_B_C は、「A_B レジスタの、 C という名前がついているビットを立てるための数」を表しています。今回の C に該当する部分は違うので例外ですが、基本的にビットの名前と同じになっています。また、あるペリフェラルにおいて例えば連番で ADC1, ADC2, ... とあった場合、マクロにおいてはこれらは全て ADC_xx_xx と記述します。

例ですが、ADC1_CR レジスタの ADCAL ビット(列)を設定したい場合、

ADC1->CR |= ADC_CR_ADCAL

と記述します。

また、複数桁で1つ設定項目をなしているものもあります。ADC1_CFGR レジスタの RES という部分(2ビットで1つの設定項目)を 2 にしたい時はどうするか。
やり方は、マクロに接尾辞 _Pos を付けたものを用いれば良いです。その名の通り、設定ビットのビットシフト位置をそのまま表しています。ですから、

ADC1->CFGR |= (2 << ADC_CFGR_RES_Pos);

とすれば良いのです。この書き方が面倒くさいなら、トークン連結やらビットシフトやらを扱うマクロ関数を定義しても良いでしょう。僕は作っていないですが、何かこの記事書いていて作りたくなってきたなぁ。

さて、上の RCC->AHBENR |= RCC_AHBENR_GPIOBEN; に戻りましょうか。実際このマクロは名前が変です。今見たように、ピンの名前は IOPBEN という名前なんですね。普通だったら、先程のルールに従うと RCC_AHBENR_IOPBEN というマクロ名になるのでしょうが、なんか違うんですね。詳しくは知りません。このようにルールにちょっと違う物があるかもしれませんが、そこは柔軟に見てください。その他のレジスタは概ねルールに則っています。SW4STM32 は Eclipse ベースなので、困ったらタイプを止めてちょっと待つとか、control-Space を押すとかして察してあげましょう。

マクロでビットをクリア

と、今述べた内容はあくまでビットごとに論理和(or) を取っているだけですので、 Res |= 0 なんかは全く意味がなくなってしまうわけです。すなわち、ビットを 0 にクリアしたい場合は not と and を利用します。その際ですが、設定するビットの中には 2桁で1つ、3桁で1つ というようにまとまっているものがあります。例えば ADC1_CFGR レジスタの RES という部分を設定したければ(ユーザーマニュアル p.383 参照)、

ADC1->CFGR &= ~(0x3 << 3);

というようにやらなくてはいけません。3 は先程同様に ADC_CFGR_RES_Pos とすれば良いのですが、この 0x3 というのもまああまり意味の感じられるものではないですよね。そこで出て来る便利マクロが _Msk という接尾辞を付けたものです。これも名前の通り、丁度指定のビット範囲が 1 でマスクされているマクロです。ですので実はこれは単純に

ADC1->CFGR &= ~ADC_CFGR_RES_Msk;

とすれば良いとわかります。これら一連のマクロはあら便利って感じですね。

GPIO 設定でのマクロ

GPIOB レジスタに戻りましょう。やることは同じなのですがこの設定では特別なマクロがあります。というのも先程のプログラムを見ていただければ分かりますが...

GPIOB->MODER |= (GPIO_MODE_OUTPUT_PP << GPIO_MODER_MODER7_Pos);

という記述ですね。MODER を設定しているにも関わらず GPIO_MODE_OUTPUT_PP という奇妙なマクロが存在しています。これは文字通り「GPIOのモードを出力にせよ」という意味です。これは一般のピンについての値であるので、然るべき位置にこの値をビットシフトしてやる必要がありますね。ということで例の _Pos を使っているわけです。
PP ってなんだよ!」って思われる方がいると思います。えーっとですねぇ、これは無視してください。とりあえず黙って PP 使っとれば良いんじゃ!!!ということです。以下は細かく知りたい人向けの説明です。

=== 分かりづらい説明開始 ===

知ってる方は「いや、PPは『プッシュプル』、ODは『オープンドレイン』だろ常識的に考えて」と言われるかもしれません。プッシュプルとは所謂通常の端子モードです。一方オープンドレインは出力にある内部の MOSFET のゲート部分にピンが接続され、コレクタは接地。ドレインがオープンになっている、電流吸い込み型のピンにするということです。う〜ん、うまい説明ができない。。。要は普通は使わないよーって思ってください。僕もそんな認識ですから。
いずれにせよ、その説明はあっているのですが、では実際に

GPIOB->MODER |= (GPIO_MODE_OUTPUT_OD << GPIO_MODER_MODER7_Pos);

としたらオープンドレインになるのか?というと、答はノーです。ナゼかというのは実際ユーザーマニュアルを読んでいただければわかりますね。そりゃオープンドレイン設定レジスタとポートモード設定レジスタは別物なんですから。
では何故このような区別があるのか?というと、結論はSTM32F439xx HAL User Manual: stm32f4xx_hal_gpio.c Source Fileを見ていただくとわかります。これは HAL のGPIO設定初期化関数です。ちょっと別の型の STM32 になってしまうのですが、コードを読むと下4bitは MODER に使っていて、上4bitはオープンドレインモード設定に使っています。ということで今回のようなレジスタ弄りに _OD を使うのは適さないということなのですね。また当然ですが or は 0 に対してスルーしますので、「どっちを使うべきか?」となったらそりゃ _PP の方が望み通りに設定できるよね、ということになります。以上。
=== 分かりづらい説明終了 ===

それで、述べるの忘れていましたが、MODER というのはピンのモードを選択するレジスタです。つまり汎用入出力端子として使うのか、アナログ用端子として使うのか、周辺機能用に使うのか、等を選択するものです。どんな値にした時にどう振る舞うかはユーザーマニュアル読んでくださいね。

続いて ODR です。出力データを指定するレジスタになります。High にするには 1、Low にするなら 0 です。今回は exclusive or で PB7のデータを反転しているという感じになります。

途中にあるのは時間稼ぎの無駄ループ。点滅が目に見えるようにするためです。volatile は指定された変数の処理に関して「コンパイラのお節介はやめてくれ」という威圧です。これがないとコンパイラが「そのループ、プログラム実行の邪魔になるだけで意味なくね?w」という提案をします。それでは時間稼ぎという目的では困るので、volatile を使ってその提案を黙殺するというわけです。組み込みとか並列プログラミングではよく利用する手法らしい。

はい、そんなこんなで長々とLチカとその周辺事項について述べて行きました。ネットに情報がないデバイス、チップではなんて言いますか、時間的な初期投資が大きく掛かるんですねぇ。僕自身、「たかがLチカ」ですが、できるようになるまでに1ヶ月は使いました。お陰でデータシート読む体力がついたので結果としては良かったんですけどね。
ということで、今回は単なるGPIOだけでなく定義されているマクロ、レジスタ構造体の記法についてまとめました。