STM32でポーリングIOを実装
ポーリングIOとは
ポーリングIOとはポーリングによりIOを操作する方式です。
一定周期でIOポートをリード、もしくはライトします。この方式は産業用装置のIOボードなどでよく使われる方式です。
前回はArduinoでのやり方を紹介しましたが、今回はSTM32でのやり方を紹介します。Arduinoは製品には使わないですからね。
制御の流れ
制御の流れを簡単に説明します。
組込み基板ではメイン関数に無限ループを作り、その中でIO更新などの関数を実施するのが一般的です。ポーリングIOに必要なものは以下です。
- ソフトウェアタイマー
- タイマーのタイムアップ確認関数
- IO更新関数
- タイマーセット関数
無限ループの中で、毎回タイマーのタイムアップ確認を行い、タイムアップしている場合はIO更新を行い、IO操作後に再度タイマーをセットするという流れになります。
Timer割り込みハードウェア設定
まずはソフトウェアタイマーを用意しますが、ソフトウェアタイマーを作成するには、STM32でタイマー割り込みを設定する必要があります。
今回は100us毎に割り込みが発生するようにします。
TIM2はAPB1のバスクロックがクロックソースです。

84MHzのクロックから100us毎に割り込みを発生させるときの設定は以下の通りです。


ソフトウェアタイマー
この割り込みを使ってソフトウェアタイマーを駆動します。
最大で何秒数えたいかによりますが、私は深く考えずに32bitの変数を用意しました。
タイマーを初期化する関数と、タイマー用の変数をデクリメントする関数を準備します。
#define USER_TIMER_NUM 1
uint32_t u_timer[USER_TIMER_NUM] = {0};
/**
* @brief Timer tick handler for polling-based user timers
* @param none
* @retval none
* @note Decrements all user timers and increments the cycle timer
* This function is called every time when TIM2 interrupt occurs
*/
void user_timer()
{
uint8_t i;
for(i = 0; i < USER_TIMER_NUM; i++){
if(u_timer[i] > 0){
u_timer[i] = u_timer[i] - 1;
}
}
}
タイマーがタイムアップしてないかを確認する関数と、タイマーに時間を設定する関数も必要ですね。
以下のように準備します。他のファイルから参照できるようにtim.hに以下のように記載します。
先ほどの関数も割り込みサービスに登録するので忘れずにtim.hに書きましょう。
#define TIME_UP 0
#define TIMER_NO0 0
#define TIME_1MS 10
extern void user_timer();
extern uint32_t user_timer_check(uint8_t Timer_No);
extern void user_timer_set(uint8_t Timer_No, uint32_t time);
タイマーチェックとセット関数は以下のように記述します。
/**
* @brief Get remaining time of the specified user timer
* @param timer_no Index of the timer (0 to USER_TIMER_NUM-1)
* @retval uint32_t Remaining time in units of 100 µs
* @note Returns remaining time of specified timer
*/
uint32_t user_timer_check(uint8_t timer_no)
{
return u_timer[timer_no];
}
/**
* @brief Set a specific software timer
* @param timer_no Index of the timer (0 to USER_TIMER_NUM-1)
* @param time Countdown time to set (unit: 100 µs)
* @retval none
*/
void user_timer_set(uint8_t timer_no, uint32_t time)
{
u_timer[timer_no] = time;
}
最後にTIM2の割り込みサービスにタイマーデクリメント関数を登録します。
割り込みフラグのクリアも忘れずに記載しましょう。
/**
* @brief This function handles TIM2 global interrupt.
*/
void TIM2_IRQHandler(void)
{
/* USER CODE BEGIN TIM2_IRQn 0 */
if(LL_TIM_IsActiveFlag_UPDATE(TIM2)){
LL_TIM_ClearFlag_UPDATE(TIM2);
user_timer();
}
/* USER CODE END TIM2_IRQn 0 */
/* USER CODE BEGIN TIM2_IRQn 1 */
/* USER CODE END TIM2_IRQn 1 */
}
IO更新関数
IOを更新する関数を記載します。
私はソフトウェアの作りやすさを重視して、MCUのGPIOポートのビット割り付けと上位インターフェイスから見たビットの並びが一致するように回路設計します。
ですので、HALではなくLLライブラリを使ってます。この辺りは個人の好みがあると思います。
回路はLowアクティブで設計したものとしてます。そのため、ビットの反転処理も入れてます。
タイマーNO.0がタイムアップしたらGPIOのGPIOAポートを読んでます。
得られたデータを保存するために適当な変数を用意しておきましょう。
#define DI_REG_NUM 1
uint16_t di_reg[DI_REG_NUM] = {0};
/**
* @brief IO update
* @param none
* @retval none
* @note Update IO data and calculate actual cycle time of this period.
* This function is executed by every 1ms
*/
void io_update()
{
if(user_timer_check(TIMER_NO0) == TIME_UP){
read_di();
user_timer_set(TIMER_NO0, TIME_1MS);
}
}
/**
* @brief Read DI from MCU
* @param none
* @retval none
* @note chattering eliminator period 1ms,
* If previous data and current data is different,
* it is defined as chattering.
* In that case, do not update DI data.
*/
void read_di()
{
uint8_t i;
// Read di port. All di ports are low-active, so bit inversion is necessary
di_reg[0] = ~(uint8_t)(LL_GPIO_ReadInputPort(GPIOA));
}
io_update()はmain.cファイル内の無限ループで呼び出す必要があるので、gpio.hに忘れずに書いておきましょう。
extern void io_update();
Main関数
最後にメイン関数の無限ループにio_update()を追加すれば完成です。
Main関数内の処理
while(1){
io_update();
}
これで1msの更新周期でIOステータスを更新するプログラムができました。
ポーリングIOの実際の使われ方
産業用装置のIOボードのようにms単位の更新頻度で事足りる場合、ポーリングIOがよく使われます。
理由として以下が挙げられます。
- 実装難易度が低い
- 低コストで性能の低いマイコンでも問題なく動く
- センサー、バルブなど接続機器の動きはマイコンから見ると圧倒的に遅いため、
- 高速でIO更新する必要がない
割り込みによる低遅延の恩恵をあまり受けないので、それよりも実装難易度を優先しています。
組込み機器のエンジニアになりたい人や、または高専、大学でロボコンなどに関わる学生はポーリングIOの実装をぜひできるようになってください。
|
STマイクロエレクトロニクス STM Nucleo-F401RE 【NUCLEO-F401RE】 価格:3,119円(税込、送料別) |
|---|
Discussion