STM32 + LL でGPIO 機能を使用する

2020年6月11日

STM32マイコンのペリフェラル関連記事を一覧にまとめました。

stm32従来のライブラリであったSPLライブラリ1と同様の機能であり、STM32CubeMXから提供されているLLライブラリ2でのGPIOでInput/Outpu機能の使い方を書いていきたいと思います。

HALライブラリとの大きな違いは、抽象度が低く直接レジスタをたたくことを考えないといけないことや、コードサイズが小さいということがあります。

LLライブラリは、レジスタについてしらないと実装することが厳しいため、データシートを見ながら話を進めていきたいと思います。

今回使用するマイコンはSTM32F405RGTを使用していくため、データシートもF4のリファレンスマニュアルを見ていきます。各自で使用しているマイコンタシートを確認しながら進めていただければと思います。

また、私が必要だったと思った点を部分的に上げていくので、取り上げていないことは各自で確認をお願いします。

 




GPIOの入出力のやり方を知る

I/Oポートのレジスタについて調べる

STM32F4シリーズのリファレンスマニュアルを読んでいきます。GPIOのI/Oポートの設定について書いてあるところを一部抜粋します。

RM0090 リファレンスマニュアルより引用

 

これから読み取れることをいくつかピックアップしてみました。

  • GPIOのポート設定は使用する前に方向や速度、出力タイプ、プルアップ/プルダウンを設定する必要がある
  • 入力、読み出しレジスタは別にあり、読み出しレジスタは読み込み専用
  • ビット単位の不可分操作が可能、複数ビットを変更することもできる
  • セット、リセット操作の両方を実行しようとした場合はセットが優先
  • BSRR(i)にビットを立てるとセット、 BSRR(i+SIZE)にビットを立てるとリセット。

他にも、オルタネート機能やアナログモード等あるそうです。今回は使用しないためスルーします。

 

レジスタのチェック

動作の際に必要なレジスタの名前がわかっても、レジスタのどのビットを設定してあげればいいのかわからないので、レジスタの確認をしていきたいと思います。

RM0090 リファレンスマニュアルより引用

入出力データレジスタのページをとりあげました。ビット単位に設定をするためにはBSRRに書き込む必要があるといことがここにも書いてありますね。

せっかくなので、BSRRレジスタがどのようになっているかどうかの確認をしてみるとついのようになっていました。

RM0090 リファレンスマニュアルより引用

書き込み専用になっています。初期設定のレジスタや、ビットごとの機能の説明も書いてあるので、文章による説明で何をいっているのかわからないときは、レジスタの各ビットの説明を見ながら読むと理解をできるときがあったりします。

 

STM32CubeMXの設定

STM32CubeMXを起動、使用するMCU/MPUをセレクトして編集画面を開きましょう。

今回は、Lチカ・プッシュスイッチの入力のプログラムを書いていこうと考えているのでLEDとプッシュスイッチのポートにそれぞれGPIO _output,GPIO_Inputを設定し、ラベル名をled,pushswとしました。

私が使用している基板では、回路上にセラロックを配置して、外部クロックを発振させているのでRCCのHSEをONにしました。

各自、使用している実験用基板の回路に即して、RCCの設定をしていただければと思います。

同様に、Clock Configurationも各自で設定をしていただければと思います。私の場合は、HSEクロックを20Mhz(外部発振素子のクロック),HCLKを160MHzに設定しました。特にこだわりのない方は初期設定のままで大丈夫です。

続いて、Project ManagerでProject Name,Project Location, Tool chain/IDEの設定を行いましょう。

私は、それぞれstudyLLAPI, Document/STM32/(ドキュメント内のSTM32ディレクトリ直下),Makefileにしました。

CodeGeneratorの設定

Advanced Setting

マウスをHALと書いてある近くまで近づけるとプルダウンメニューが出ると思います。プルダウンメニューからLL,HALの変更ができます。HALとなっているところをすべてLLに変更しましょう。

ここまで設定をしたらコード生成をしてもらいましょう。

 

実装をしてみる

回路を確認する

LEDを付けたつもりが消しているということや、スイッチを押していないのに入力されたと判断されないようにするために、出力をどのように行えばLEDがつくのか、スイッチはプルアップ/プルダウンのどちらかを確認をしておきましょう。

私の場合は周辺回路が次のようになっていました。

LED:GPIOがHighの時に消灯、Lowのときに点灯

PUSH SW: プルアップされているので、押されていないときにHigh,押されたときにLow

 

生成されたプログラムを確認する

先ほど生成したプロジェクト(ディレクトリ)をIDEやエディタで開き、gpio.cを確認しましょう。

すると、void MX_GPIO_Init(void)という関数が定義されていると思います。

設定をしてくれていることは、

  • クロックを有効化
  • ピンの初期化
  • モード、速度、プルアップ/プルダウンの設定、初期状態といった先程データシートを読んで確認したこと

をしてくれているので、あとはGPIOレジスタのBSRRにビットをセット、IDRからGPIOのビットを読み取ることで入出力を行うことが可能であるということがわかりました。

 

レジスタをたたいてみる

Lチカをやってみたいと思います。先程、LEDの名前を付けたポートはmain.hに使用しているGPIOx,Pinが定義されていると思います。これを使ってレジスタをたたいてみます。

LL_mDelayでLチカの待ち時間を作ると、プログラムは次のようになりました。

while(1){ 
  led_GPIO_Port->BSRR = led_pin; 
  LL_mDelay(1000); 
  led_GPIO_Port->BSRR = (led_pin << 16); 
  LL_mDelay(1000); 
 }

 

これでLチカができました。スイッチの入力も書いてみると、

// 先に入力確認用の変数を準備しておくこと
uint8_t sw;

// レジスタから値を取得
sw = ( (pushsw_GPIO_Port->IDR & pushsw_Pin) == pushsw_Pin );

IDRレジスタにピンをアンド演算でビットだけを抽出し、ピンの値と同じ場合はHigh,違う場合はLowと値の取得をすることが可能です。

しかし、このように書いてしまった場合は、使用するマイコンを変えるたびにレジスタの構成が同じか確認して、プログラムを書き換えるという作業をしなければなりません。LLAPIは、同じ名前の関数で使用するマイコンそれぞれのレジスタの違いを考える必要がなくなるようにしてくれています。

例えば、STM32F303K8のデータシートを読むと、ビットセットするときのレジスタはBSRR、ビットリセットするときのレジスタはBRRと別れていますが、LLAPIを使用することであまり気にする必要がなく同じ関数を呼ぶことで、レジスタに値をセットしてくれます。

HALとは違い関数内でやっていることはレジスタのセットだけなのでレジスタを直接打つのとあまり変わらない速度で動作をすることが可能になっているようです。逆に、誤った値をレジスタに入れてしまうと予期せぬ動作をする可能性もありますが、データシートを読んで設定をすれば間違えることは少ないと思われるので問題はないと考えています。

 

LLAPIを使用したプログラムを書く

LLAPIを使用したLチカ、プッシュスイッチの入力は次のように書けます。

// ピンにHighを出力
LL_GPIO_SetOutputPin(led_GPIO_Port, led_Pin);
// ピンにLowを出力
LL_GPIO_ResetOutputPin(led_GPIO_Port, led_Pin);
// ピンの入力状態を取得
LL_GPIO_IsInputPinSet(pushsw_GPIO_Port, pushsw_Pin);

LLライブラリの関数の動作がわからないときは、stm32f4xx_ll_gpio.h/.cの関数を確認しに行って、内部でどのような処理をしているか見るといいと思います。

UM1725のHAL、LLAPIのドキュメントから探すのが大変なのでセットしたいレジスタをIDEやエディタの機能を使用して検索して探すと早く見つかるかもしれないです。

私の周辺回路に合わせたプログラムを関数にまとめて書くと次のようになりました。

void setLed(uint8_t enable)
{
  if(enable==1) LL_GPIO_ResetOutputPin(led_GPIO_Port, led_Pin);
  else LL_GPIO_SetOutputPin(led_GPIO_Port, led_Pin);
}

uint8_t getPushSW(void)
{
  return (~(LL_GPIO_IsInputPinSet(pushsw_GPIO_Port, pushsw_Pin)) & 0x01);
}

先程の、周辺回路を確認するで考えた通りに実装しました。

getPushSW内での処理は、押していないときは1,押されているときは0なので、ビットをすべて反転して最下位ビットのみを確認することで0/1を返すプログラムにしています。

if文で区切る方法もあります。

 

さいごに

LLライブラリを使用すれば、1行で終わるGPIOのInput,Outputについてしっかりとデータシートを読みながら実装までしてみました。

Lチカ、プッシュスイッチの入力だけでこれだけの長さになるとは思いませんでした。LLライブラリを使用する場合は、基本的にレジスタの設定がどのようになっているか、確認しながらやっていく必要があるということがわかったと思います。

情報も少ないので、ちらほら情報を上げていきたいと考えています。

 

今後の予定

LLライブラリで周辺機能の設定をする記事をいくつか書いていこうと考えています。

今現在、書く予定を立てているのは

  • TIM PWMの出力
  • TIM エンコーダの入力
  • SPI通信(多分、MPU-6500と通信)
  • USART通信
  • ADC

といった、マイクロマウスで使用している機能は、全て書いていこうかなと思っています。

リクエスト等あれば、connectからメールまたは、Twitterで教えていただければ、時間はかかる可能性が高いと思われますが書く予定に追加させていただきます。

 

参考文献

RM0090 リファレンスマニュアル

 

  1. Standard Peripheral Libraries
  2. Low Layer Application Program Interface