記事概要
STM32 CubeIDEが公開されたことを記念して,STM32の基本的な使い方を確認します.
本記事ではSTM32で回転を読み取るためのセンサ,エンコーダを読み取るプログラムを作ります.
本ブログを書くにあたって下記リンク先を大いに参考にさせていただいています.
環境
- STM32CubeIDE 1.0.0
- macOS 10.14.5
- NUCLEO-F401RE
STM32 CubeIDEについて(読まなくてもよい)
STM32 CubeIDEはSTM32用の統合開発環境です.2019年4月にリリースされたものですが,実態はこれまでも存在し,広く使われていた「STM32CubeMX」,「SW4STM32」の2つのアプリケーションを統合したものです.
CubeMXはマイコンの設定を作成するためのアプリケーションです.
SW4STM32はプログラムを書いて実行するためのアプリケーションです.
これまではCubeMXで作った設定データをSW4STM32に取り込んで作る必要がありましたが,Cube IDEで統合されたため,とてもわかりやすくなりました.まだネット上にはCubeIDEを使った記事は少ないため,CubeMXやSW4STM32を使った情報が多くあります.調べる際には上記のワードも覚えておくとよいでしょう.CubeMX,SW4STM32は今後アップデートされなくなるため,今から始める方はSTM32CubeIDEで練習するのがよいでしょう.
環境構築について
STM32 CubeIDEのインストール方法はこちらをご覧ください.
プロジェクトの作成
プロジェクト作成方法はUART通信のときと同じです.こちらの記事をご覧ください.
CubeMX
プロジェクト作成をするとこの画面にたどり着くと思いますので,そこで設定が必要になります.
既存のプロジェクトを活用する人は左メニューの「(プロジェクト名).ioc」(黒丸で囲ったところ)をダブルクリックするとこの画面が開くはずです.
STM32ではマイコンの周辺機能(Peripheral)を様々なピンに割り当てることができます(緑のピンは割り当て済み).
この画面で,タイマーなどマイコンの機能をピンに割り当てることで,設定や初期化の内容がプログラムに反映されます.
今後機能を増やしていく際にはこちらの画面でピンを設定して,プログラムを記述という作業を繰り返していくことになります.
エンコーダのピン設定
エンコーダのために必要な入力
エンコーダは2つのパルス状波形(A相,B相)を発生させ,そのタイミングで,回転方向と回転量を知ることができます.
マイコンは2つのデジタル入力が1か0か,常に監視する必要があります.
そのようなプログラムを書いてもいいのですが,他にも様々な処理をさせたい時には,エンコーダの処理を自動で行ってくれる機能を活用すべきです.
STM32ではTimer機能の中にあるEncoder読み取り機能を活用出来ます.
STM32F401ではタイマー1〜5(TIM1〜TIM5)のいずれかで利用可能であり,それぞれに備えられたCH1,CH2端子にA相,B相の入力を接続します.
今回はまず,TIM3をエンコーダ入力に設定していきます.
ピンへの機能割り当て
では実際にピンを選んで設定していきます.今回は
- PA6 → TIM3_CH1
- PA7 → TIM3_CH2
を割り当てます.
PA6をクリックすると,割り当てられる機能一覧が出てきますので,「TIM3_CH1」を選択します.
PA7をクリックすると,割り当てられる機能一覧が出てきますので,「TIM3_CH2」を選択します.
PA6とPA7が黄色になっているはずです.
これは,ピンに機能が割り当てられたにも関わらず,必要な設定がなされていないことを表しています.
これらのピンを使えるようにするために,TIM3の設定をしていきます.
ペリフェラルの設定ができるメニューを引っ張り出して設定を行います.
(最初から表示されていれば問題ありません)
TIM3の設定を開いて,「Combined Channels」をEncoder Modeにします.
すると,先ほど黄色で表示されていたピンが緑色に色付けされるはずです.
もう少し詳しい設定をします.
先ほどの設定の下側にさらに詳しい設定「Parameter Settings」があります.
このうち下記を変更します.
- Counter Period : 0 → 65535
- Encoder Mode : Encoder Mode TI1 → Encoder Mode TI1 and TI2
Counter Period
Counter Periodはカウンタの範囲を決定するためのもので,これを超えたときにプログラムへの割込みを設定することができます.
0〜65535まで設定できます.
初期設定の0ではカウントできないため必ず変更する必要があります(なぜ初期設定が0???).
Encoder Mode
Encoder Modeは エンコーダのどの入力が変化したときにカウントを変えるかを決定します.
初期設定ではA相だけでしか変化しない状態なので,A相,B相で変化させることで,より細かく値が変化します.これにより100[Pulse/rev]のエンコーダを用いて,400[Count/rev]を利用できます.
これにて設定は終了です.
ここまでの結果を保存してください(File→Save).
するとこのようなダイアログが出現しますので,Yesを押してください(mainプログラムが設定を反映して自動的に書き換えられます).
左の「Project Explorer」メニューから「main.c」を開きます.
作成完了
これがmainプログラムです.
すでに自動生成された設定などが記述されています.
エンコーダ読み取りプログラム
プログラムを書く上での注意
mainプログラムを下にスクロールすると,main()関数が見えてきます.
main関数の中もいろいろと記述済みですが,こちらを追記していきます.
その際に注意しなければならないのが「自分で書いたプログラムが勝手に削除される場合がある」ことです.
main.cはマイコンの設定に応じて自動で書き換えられますが,その際,「指定の場所」以外に書かれたプログラムは削除されていしまいます.
必ず,
/*USER CODE BEGIN xxx */ /*USER CODE END xxx */
の間に記述するようにしてください.
またこのコメントを消さないでください.
インクルード文
今回はC言語の標準入出力関数などをインクルードして
- sprintf()
- strlen()
を使用したいので,
- stdio.h
- string.h
をインクルードします.
以下のように,「USER CODE BEGIN Includes」の下に2行追加します.
/* Includes ------------------------------------------------------------------*/ #include "main.h" /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ #include "stdio.h" //追記 #include "string.h" //追記 /* USER CODE END Includes */
main関数
main関数には「USER CODE BEGIN〜END 2」の中に3行と「USERCODE BEGIN〜END WHILE」の中に4行追記します.
「HAL_TIM_Encoder_Start()」関数を使うことで,エンコーダのカウントをスタートできます. また,「TIM3->CNT」を参照することで今のカウントを随時読むことができます. 「HAL_UART_Transmit()」でUARTを経由してMacに値を送信しています.
/* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_TIM3_Init(); MX_USART2_UART_Init(); /* USER CODE BEGIN 2 */ //この中に追記 HAL_TIM_Encoder_Start(&htim3,TIM_CHANNEL_ALL); //読み取りスタート //変数 int cnt; //カウントした値を格納する変数, char scnt[100]; //カウントした結果を文字列で格納する変数 /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ //この中に追記 while (1) { cnt = TIM3 -> CNT; //TIM3の値を取り出してcntに格納 //UARTで値を送信 sprintf(scnt, "%d\r\n", cnt); HAL_UART_Transmit( &huart2, scnt, strlen(scnt) + 1, 0xFFFF); //ループ時間調整 HAL_Delay( 100 ); /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */
エンコーダの接続
ハードウェアを接続します.今回接続するエンコーダはAliexpressで購入したこちらのエンコーダです(Aliexpressはすぐリンク切れするのでリンク貼りません,Aliexpressで「rotary encoder 600」とかで検索すれば出ます).
エンコーダにもいくつか種類があって,今回購入したものはオープンコレクタタイプでした.
通常のエンコーダであれば電源と出力を直接つなぐだけで良いですが...
オープンコレクタタイプでは外部にプルアップ抵抗を接続する必要があります.
その代わり,エンコーダの電源電圧とI/Oの電源電圧を変えることができます(同じ電源でももちろんOK).
プルアップ抵抗は10kΩとかその辺の適当に大きな抵抗をA相⇔3.3V,B相⇔3.3Vに接続しておけば大丈夫です.
今回は下記のような感じで接続しました.
D11,D12という名前はArduino準拠にしたもので,STMの呼称ではありません.Arduino互換端子のすぐ隣にあるピンは同じ機能ですので,どちらを使っても構いません.
書き込み
UARTの時と手順は同じです.
macでUARTを見る
UARTの時と手順は同じです.
今回のプログラムを実行すると,エンコーダのカウント値が延々と出力されます.
オーバーフローに対処する
タイマーを用いることで,エンコーダの値の変化を捉えることができました.
しかしながら,このままではタイマーのカウント数の上下限0〜65535の間しかカウントできません.
65535を超える(オーバーフロー)と0に,0を下回る(アンダーフロー)と65535にそれぞれ値が飛んでしまいます.
そこで,オーバーフロー,アンダーフローに対処するように書き換えました.
なお,この処理は自分で実装できず,ふまるちゃんさん(失踪中)にやってもらいました.この場を借りて御礼申し上げます.
プロジェクトの再設定
まず,設定を一部変更する必要があるので,マイコン設定の画面(CubeMX)に戻ります.
設定(出ていなければ最初と同様に引っ張り出して表示させる)をTimer→TIM3→NVIC Settingsとたどり,「TIM3 global interrupt」をオンにします.
この操作により,TIM3がオーバーフロー,アンダーフローしたときに「HAL_TIM_PeriodElapsedCallback()」関数が呼ばれるようになります.
もう一度保存してください.
プログラムの再編集
左の「Project Explorer」メニューから「main.c」を開きます.
先ほどのプログラムに帰ってきました.
まず,先ほどの設定を活かして,オーバーフロー・アンダーフローを検出して数える関数を書きます.main文より上の「USER CODE BEGIN〜END 0」に以下を加えます.
/* USER CODE BEGIN 0 */ //オーバーフロー・アンダーフローの回数をカウントするグローバル変数 int overflowcnt = 0; //オーバーフローアンダーフローすると自動的に呼ばれる関数 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef* htim) { //デバッグ用の出力,消してよい. HAL_UART_Transmit( &huart2, "OVERFLOW!\r\n", 12, 0xFFFF); //この関数自体はTIM3で無くても呼ばれてしまう可能性があるので,TIM3の割り込みであることを確認する if(htim->Instance == TIM3){ //割り込みフラグクリア,これをしないとこの関数が複数回呼び出される __HAL_TIM_CLEAR_FLAG(&htim3, TIM_IT_UPDATE); if(__HAL_TIM_IS_TIM_COUNTING_DOWN(&htim3)) //0 → 65535 { overflowcnt --; } else //65535 → 0 { overflowcnt ++; } } } /* USER CODE END 0 */
以上です.次にmain()関数を書き換えます.
/* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_TIM3_Init(); MX_USART2_UART_Init(); /* USER CODE BEGIN 2 */ HAL_TIM_Encoder_Start(&htim3,TIM_CHANNEL_ALL); //エンコーダ読み取りスタート HAL_TIM_Base_Start_IT(&htim3); //【追記】割り込み有効化 uint32_t cnt; char scnt[100]; overflowcnt = 0; //【追記】なぜかわからないけど必要 /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { //【追記】オーバーフロー分を加算,(オーバーフロー回数-アンダーフロー回数) * 65536 cnt = (long)(overflowcnt) * 0x10000 + (long)TIM3 -> CNT; //UARTで値を送信 sprintf(scnt, "%d\r\n", cnt); HAL_UART_Transmit( &huart2, scnt, strlen(scnt) + 1, 0xFFFF); //ループ時間調整 HAL_Delay( 100 ); /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */
以上でOKです.
途中にある,
overflowcnt = 0; //【追記】なぜかわからないけど必要
については,これが無いとカウントが65536から始まってしまうという不都合があったので対症療法的に入れました.
再実行結果
きちんとオーバーフローを検出できました.
トラブルシューティング
- 「OVERFLOW!」が出ない
- .ioc(CubeMX)の設定で「TIM3 global interrupt」にチェックをつけましたか?
- main文ループ開始前に「HAL_TIM_Base_Start_IT(&htim3);」を追記しましたか?
参考文献
ふまるちゃんさんを書きたいのに更新されてないので書けない