STM32のFreeRTOSでメールキューの使い方を、サンプルコードと合わせてわかりやすく解説します。
メールキューとは
メールキューを知らない方のために軽く解説しておきたいと思います。すでにご存知の方は読み飛ばしてください。
メールキューとはキューの一種で、大きなデータも効率的に送受信するための仕組みを備えています。動作イメージを図にしましたので、下記をご覧になってください。
メールキューはメモリブロックを保持しており、これは送受信するデータを保持するための領域です。データの実態はメールキューの中に置き、送信側と受信側はポインタ経由でデータを操作します。データコピーを行う必要がないので、処理効率が良く、メモリの節約にもなります。
使い方としては、送信時はメモリブロックにallocでメモリ領域を確保し、その領域を使ってデータを加工し、putします。受信側はgetして処理が終わったらfreeでメモリ領域を解放します。
メモリブロックの操作が増える分、コード的にはちょっと面倒ではありますが、allocとfreeの2行分だけなので気にするほどのことではありません。
作成するプログラム
それでは今回のゴールを確認しましょう。今回はメールキューを使って、構造体データの送受信を行います。構造体はLEDの点灯設定(点灯間隔と点灯回数)を表現します。ボタンを押したらLEDの点灯設定を作成して、メールキューに送信します。スレッド側では、メールキューからLEDの点灯設定を取得し、その指示に従ってLEDの点滅を行います。
前提条件
前提条件は目次の記事を参照してください。
プログラムの作成
割込みハンドラの設定
今回はボタンを押したときにデータ送信を行うので、ボタンに対応した割込みハンドラを用意します。
STM32CubeIDEでプロジェクト作成時に、使用するボードを指定してデフォルト設定で初期化するようにすれば、ボタン用の割込み設定まで行ってくれます。ここでは念のため、割込み設定が正しく行われていることを確認しておきます。
- Pinout & Configurationsタブ→Pinout View
- ボタンのピンの設定が割込みになっていることを確認してください
(NUCLEO-F401REの場合、PB13がGPIO_EXTI13)
- Pinout & Configurationsタブ→System Core→NVIC→NVICタブ
- EXTI line[XX] interruptの、EnabledとUses FreeRTOS Functionsにチェックが入っていることを確認してください
(NUCLEO-F401REの場合、EXTI line[15:10] interrupts)
- Pinout & Configurationsタブ→System Core→NVIC→Code generationタブ
- EXTI line[XX] interruptsの、Generate IRQ handlerとCall Had handlerにチェックが入っていることを確認してください
(NUCLEO-F401REの場合、EXTI line[15:10] interrupts)
スレッドの作成
スレッドはデフォルトで用意されているdefaultTaskをそのまま使用します。
メールキューの作成
メールキューの作成ですが、STM32CubeIDE ver.1.00ではユーザインタフェースからのコード生成は対応していません。ちょっと残念ですがマニュアルで作成できるので我慢しましょう。
ユーザインタフェースの設定は終わっているので、コード生成を実行してから、下記の手順を行ってください。
まずはメールキューに入れる構造体と、メールキューのIDを定義しましょう。これらの情報はメールキューの生成時に必要になります。
main.c:メールキューに入れる構造体とID
/* USER CODE BEGIN PV */ typedef struct LEDConfig { uint32_t interval; uint32_t count; } LEDConfig; osMailQId LEDMailHandle; /* USER CODE END PV */
これらを使ってメールキューを生成します。
main.c:メールキューの生成
/* USER CODE BEGIN RTOS_QUEUES */ /* add queues, ... */ osMailQDef(LEDMail, 16, LEDConfig); LEDMailHandle = osMailCreate(osMailQ(LEDMail), NULL); /* USER CODE END RTOS_QUEUES */
osMailQDef()でメールキューの宣言をしています。第1引数にメールキューのIDを指定します。第2引数はメールキューのキューサイズです。第3引数でメールキューに入れるデータを指定します。
osMailCreate()で実際にメールキューを生成しています。第1引数で先ほど宣言したメールキューの定義を渡します。第2引数はCMSIS RTOSの仕様上はスレッドIDを指定するのですが、内部的には使ってないのでNULLを指定しておけば大丈夫です。
データ送信処理を実装
ボタン押下時、メールキューにLEDの点滅設定を送信します。
main.c:ボタン押下時の割込みハンドラ
/* USER CODE BEGIN 4 */ uint32_t index = 1; void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { LEDConfig *conf = osMailAlloc(LEDMailHandle, osWaitForever); conf->interval = 500 * index; conf->count = 20 / index; osMailPut(LEDMailHandle, conf); index++; if (index > 2) { index = 1; } } /* USER CODE END 4 */
HAL_GPIO_EXTI_Callback()関数はweak宣言されたデフォルトの関数が存在するため、ここで上書きしてあげます。
osMailAlloc()でメモリブロック内の領域を確保します。第1引数はメールキューのIDです。第2引数が生成待ち時間で、osWaitForeverを指定することで生成するまでずっと待ちます。
大域変数のindexを使って、LEDの点滅設定を毎回変えていますが、ここは本質的ではないので説明は省略します。
osMailPut()でメールキューにデータ送信を行っています。第1引数はメールキューのIDで、第2引数が送信データのポインタです。
データ受信処理の実装
後はデータ受信処理部を実装すれば完成です。defaultTaskスレッドでLEDの点滅設定を受信し、その設定に従ってLEDを点滅させます。
main.c:defaultTaskスレッドのデータ受信処理
void StartDefaultTask(void const * argument) { /* USER CODE BEGIN 5 */ /* Infinite loop */ for(;;) { osEvent event = osMailGet(LEDMailHandle, osWaitForever); if (event.status == osEventMail) { LEDConfig *conf = (LEDConfig*)event.value.p; for (uint16_t i = 0; i < conf->count; i++) { HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin); osDelay(conf->interval); } osMailFree(LEDMailHandle, conf); } } /* USER CODE END 5 */ }
APIの使い方はメッセージキューとほとんど同じですが、処理が終わった後にosMailFree()で領域を解放することを忘れないようにしましょう。
プログラムの作成は以上です。実際に動かしてLEDが点滅することを確認してみてください。
まとめ
STM32のFreeRTOSを使ったメールキューの使い方は以上です。allocとfreeの手間がありますが、そんなに気にならないと思います。
STM32CubeIDEがユーザインタフェースからのメールキューの自動生成ができない点は残念ですが、将来に期待ですね。当分は本記事を参考にマニュアルでの作成で我慢しましょう。