2024年に秋月電子にてSTM32C011F4P6というマイコンが発売されました.
GPIO18ピン使えて80円と大変お得です.
STMマイコンの開発ではmbedやHALなんかが使えますが,宗教上の理由でレジスタ叩いて遊んでみましたので,知見を共有します.
準備
- Cube IDEをインストール
- STM32C011F4P6でプロジェクトを生成(Cube MX経由が良い)
- 自動生成されたコードを全て削除する
- hexファイルを吐き出せるようにしておく
プロジェクト名を右クリック->Properties->C/C++ Build->Settings->MCU/MPU Post build outputs -> Convert to Interl Hex file
データシートの準備
また,英語ではありますがSTM32C011のデータシートとリファレンスマニュアルを手元に置いておきましょう.
レジスタ叩いて開発する場合,極論を言えばこの2つの資料に全てが載っています.閻魔帳です.
C言語の基本的な書き方を除けば,マイコンの動作は全てこれらの資料に基づいています.
レジスタ叩きの良いところは,全て自分の指示した通りに動くことです.
悪く言えば何もしてくれないのですが,思いどおりに動かない場合はこちらの指示ミスが原因であり,なんかよく知らない関数が勝手に何かしてた…ということはまずありえません.
GPIOを動かすにもタイマを使うにも,全てこちらで設定してあげる必要があります.
上手く動いたときにはマイコンの事を分かった気になれて楽しいのがレジスタ叩きだと思います(宗教).
基本的なGPIO設定(Lチカ)
まっさらなmain.cにコードを書いていきます.
最小限のLチカのコードを以下に示します.(PA0にLEDついていると仮定しています.)
#include <stm32c0xx.h>
int main (void){
RCC->IOPENR |= RCC_IOPENR_GPIOAEN; // GPIOAにクロックを供給
GPIOA->MODER &= ~GPIO_MODER_MODE0_Msk;
GPIOA->MODER |= (GPIO_MODE_OUTPUT_PP << GPIO_MODER_MODE0_Pos); // A0をOutputに
while (1)
{
GPIOA->BSRR |= GPIO_BSRR_BS0;
for(int i = 0; i<20000; i++);
GPIOA->BRR |= GPIO_BRR_BR0;
for(int i = 0; i<20000; i++);
}
}
1. stm32c0xx.hのインクルード
まず冒頭にてstm32c0xx.hをインクルードしています.
このファイルからstm32c011.hが引用されています.
stm32c011.hを少し見てみると,大量のマクロが定義されていることがわかります.
このマクロを使うことで,レジスタのアドレスやレジスタ内のビット位置を具体的に調べなくても,視覚的に分かりやすくレジスタ操作を行うことができるようになります.
2. クロックの供給(RCC_IOPENRレジスタ)
メイン関数の冒頭では,GPIOAポートにクロックを供給する設定を行っています.
最近の小型STMマイコンでは,省電力化のため(?)にデフォルトでは各機能にクロックが供給されていません.
つまり初期状態ではなにも動かない状態になっています.
GPIOポートへのクロック供給を司っているのが,このRCC_IOPENRレジスタです.
RCCはReset and clock controlの略で,システムの起動とクロックという根幹部分を担っています.クロック自体の設定は一度飛ばして,ひとまずRCC_IOPENRレジスタを見ていきましょう.
このレジスタについてはリファレンスマニュアルの147ページに載っています.
Bit 0にGPIOAEN,つまりport Aへのクロック供給を設定する項目があります.
このレジスタの初期値すなわちReset Valueは全部0となっていますので,やはりport Aには初期状態でクロック供給されていません.
Bit 0に1を書き込んで(セットして)クロック供給を有効化してあげます.
特定のビットをセットしたいときには,or演算子|を使うと便利です.
クリアしたいときにはand演算子&を使います.
今回はBit 0のみセットしたいので,0x0000 0001とor演算を取ります.
ここで便利なのが,先ほどにも触れた stm32c011.h内のマクロです.
#define RCC_IOPENR_GPIOAEN 0x1UL
の記載があります.これを使えば,
RCC->IOPENR |= RCC_IOPENR_GPIOAEN; // GPIOAにクロックを供給
としてport Aへのクロック供給を設定できます.
今回はやりませんが,port Bにも供給したければ RCC->IOPENR |= (RCC_IOPENR_GPIOAEN | RCC_IOPENR_GPIOBEN);
と書いてあげればOKです.
3. ピンモードの設定(GPIOX_MODERレジスタ)
続いて,GPIOピンの入出力設定を行います.Arduino IDEでもpinMode関数がありますね.
担当はGPIOX_MODERレジスタです.リファレンスマニュアルの187ページを見てみましょう.
Xがport名,yがピン番号を示しています.
今回操作したいのはA0ピンなので,GPIOAのMODE0を見ればOKです.
上位ビットからyが大きい順に並んでいますので,いじるA0ピンは最下位2ビットです.
説明を読むと,00が入力,01が出力となっています.10のAlternate functionはGPIOピンを何らかの機能(SPIやタイマ出力等)として使う場合に設定します.11はアナログピンですね.
ではA0を出力に,すなわち01に設定していきます.1ビットのみをセットしたい時にはIOPENRレジスタのようにOR演算を直接行えばいいのですが,01と設定したい場合には,一度クリアして00にしてから,or演算で01にします.
GPIOA->MODER &= ~GPIO_MODER_MODE0_Msk;
GPIOA->MODER |= (GPIO_MODE_OUTPUT_PP << GPIO_MODER_MODE0_Pos); // A0をOutputに
GPIO_MODER_MODE0_MskはMODE0の部分のみがセットされた値が入っています.つまり0x0000 0003です.
この値の否定(0xFFFF FFFC)と&を取ることでGPIOA_MODERレジスタの最下位2ビットを一度クリアします.
つぎに,設定値01はマクロGPIO_MODE_OUTPUT_PPによって置き換えることができ,これを指定の位置までずらします.
何ビットずらすか指定するときには,GPIO_MODER_MODE0_Posマクロを使います.
ちょっとわかりにくいかもしれないので,マクロを全て書き下すと
GPIOA->MODER &= ~0x3UL;
GPIOA->MODER |= (0x1UL << 0); // A0をOutputに
となっています.確かにMODERレジスタの下位2bitが01に書き換わります.
難しいようであれば紙とペンで確認してみてください.これらのマクロが定義されている部分を見るのも勉強になります.
今回はA0ピンを操作しましたが,マクロのMODEyの部分を変えれば,別のピンについて設定することができます.
さて,ここで一つ注目したいのは,このレジスタのReset Valueがほぼ1になっていることです.
port A以外は全て11になっていて,port Aのみ一部のピンが10に設定されているようです.
これは A13, A14ピンが書き込みピンに対応しているためです.
従って,これらのピンの入出力設定を書き換えてしまうと,二度と書き込みができなくなります.
なのでGPIOA->MODER = 0
みたいに初期化してはいけません.面倒でも当該のビットをマスクを使ってクリアしてから書き換えた方がよいと思います.
なお,この場合書き込めなくなったら,リセットボタンを押しながら書き込みすると復帰できます(詳細).
レジスタ操作ではあらゆる設定をいじることができるのでこのような事故も起こりえます.
4. ピン状態の操作
いよいよピンの状態を変えていきます.
使うレジスタについてはいくつか選択肢があります.
- GPIOA_ODRレジスタを操作する.
- GPIOA_BSRRレジスタの下位16ビットでセットし,上位16ビットでリセットする.
- GPIOA_BSRRレジスタの下位16ビットでセットし,GPIOA_BRRレジスタの下位16ビットでリセットする.
今回はシフトなしのOR演算だけで完結する3つ目の方法を用います.
まずGPIOX_BSRRレジスタを見てみると
となっており,先述したように上位16ビットでピンをリセット,下位16ビットでピンをセットできます.
今回はこの内下位16ビットのセット機能を使います.従ってピンをセットしたい時には,
GPIOA->BSRR |= GPIO_BSRR_BS0;
とします.続いてGPIOX_BRRレジスタは
であり,下位16ビットでこちらはリセットの機能があります.
従ってピンをリセットするときには以下のようにします.
GPIOA->BRR |= GPIO_BRR_BR0;
5. 待ち時間の設定
まだ詳細なクロック設定をしていないので,待ち時間は適当に無駄な処理を行って作っています.
for(int i = 0; i<20000; i++);
まとめ
Lチカをやるだけでもいくつも設定を行う必要があります.楽しいですね.
PWMやタイマも書いていく予定です.