FRDM-MCXA153でFreeRTOSを扱う
久しぶりにRTOSを実感することも兼ねて、NXP製FRDM-MCXA153でFreeRTOSを使ってみる。適宜、AIさん利用。
MCUXpresso
当初、スクラッチからProjectを作成をしていた。
FreeRTOSコンポーネントの選択をしても、Buildできずじまい。おそらく、Makefileの記述やらIncludeやライブラリやらの不足なのであろう。原因を追いかけるのが本質ではなかったため、サンプルFreeRTOSプロジェクトをCopy&Pasteすることにした。
既存のfreertos_helloをCopy&Paste。
プロジェクト名称を入力。
一覧にそのプロジェクトが作られる。
任意ではあるが、ソースコードの名称も変更する。
変更完了。
題材:RTOSのストーリー
- タスク2つ
- タスクA:一定周期でメッセージ出力
- キューが空:固定文字列およびインクリメントされるカウンタを表示
- キューが空でない:キューの内容を表示
- タスクB:シリアル入力をキューにためる
- タスクA:一定周期でメッセージ出力
- タスク優先度により、出力の状況を確認
検証&ソースコード
FreeRTOSで用意されているフレームワーク(下記など)を適宜利用。
- xTaskCreate()
- vTaskStartScheduler()
- vTaskDelay()
- xQueueCreate()
- xQueueSend()
- xQueueReceive()
その1
Case1:下記コードそのもの(タスクAの優先度が高い)
/*System includes.*/
#include <stdio.h>
/* FreeRTOS kernel includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "timers.h"
/* Freescale includes. */
#include "fsl_device_registers.h"
#include "fsl_debug_console.h"
#include "board.h"
#include "app.h"
/*******************************************************************************
* Definitions
******************************************************************************/
/* タスクプライオリティ */
#define hello_task_PRIORITY (configMAX_PRIORITIES - 1)
#define serial_task_PRIORITY (configMAX_PRIORITIES - 2)
/* 入力バッファ及びキューのサイズ */
#define BUFFER_SIZE 32
/* キュー内のメッセージ数 */
#define NUM_QUEUE 3
/* hello_taskでSleepする時間(ms) */
#define HELLO_SLEEP 5000
/*******************************************************************************
* Globals
******************************************************************************/
/* Queue handle */
static QueueHandle_t msg_queue = NULL;
/*******************************************************************************
* Prototypes
******************************************************************************/
static void hello_task(void *pvParameters);
static void serial_task(void *pvParameters);
/*******************************************************************************
* Code
******************************************************************************/
int main(void)
{
/* ボード初期化 */
BOARD_InitHardware();
/* キュー初期化 */
msg_queue = xQueueCreate(NUM_QUEUE, BUFFER_SIZE);
/* タスク生成 */
if (xTaskCreate(hello_task, "Hello_task", configMINIMAL_STACK_SIZE + 100, NULL, hello_task_PRIORITY, NULL) !=
pdPASS ||
xTaskCreate(serial_task, "Serial_task", configMINIMAL_STACK_SIZE + 100, NULL, serial_task_PRIORITY, NULL) !=
pdPASS)
{
PRINTF("Task creation failed!.\r\n");
return 1;
}
vTaskStartScheduler();
for (;;)
;
}
/* タスクA */
static void hello_task(void *pvParameters)
{
int count = 0;
char buf[BUFFER_SIZE];
for (;;)
{
/* キューが空でなければ、キューの内容を表示 */
if (xQueueReceive(msg_queue, buf, 0) == pdTRUE)
{
PRINTF("\tReceived Message: %s\r\n", buf);
} else
/* 空なら、Hello+カウンタを表示 */
{
PRINTF("\tHello %d\r\n", ++count);
}
/* Sleep(タスクスイッチ) */
vTaskDelay(pdMS_TO_TICKS(HELLO_SLEEP));
}
}
/* タスクB */
static void serial_task(void *pvParameters)
{
char msg[BUFFER_SIZE];
int idx = 0;
char c;
while (1)
{
/* 1文字取得(入力があるまでここでタスクがブロック) */
c = GETCHAR();
/* 改行コード(CRまたはLF)が来たら終了 */
if (c == '\r' || c == '\n')
{
msg[idx] = '\0';
/* Helloタスクへ入力文字列送信 */
xQueueSend(msg_queue, msg, 0);
idx = 0;
PRINTF("\r\n");
}
/* バッファがいっぱいになる前なら文字を保存 */
else if (idx < (BUFFER_SIZE - 1))
{
msg[idx++] = c;
PUTCHAR(c); /* エコーバック */
}
/* vTaskDelay(1); */
}
}
このコードでは、タスクAの優先度をタスクBより、高くする必要がある。というのは、タスクBのGETCHAR()でブロックしてしまうからである(タスクスイッチが発生しない)。上記コード時の実行の様子を下記する。
行の途中から見えるHelloやReceivedで始まるメッセージが、タスクAによる表示であり、行頭の表示が入力した文字列で、タスクBに取り込まれるものである。なお、ここでは、キュー数が3であり、4番目の入力(444)がタスクAに渡っていないこともわかる。
Case2:タスクAとタスクBの優先度が同じ
タスクAによる表示は1度だけ発生し、タスクBに制御が移った後、タスクAに戻らない。
Case3:タスクBの優先度が高い
タスクAの表示が一度も存在しない。
Case2とCase3の補足
タスクBの最終行にある「/* vTaskDelay(1); */」をコメントアウトすれば、タスクAへのスイッチが発生するため、結果は異なる。
その2
その1では、タスクBでブロックするところが存在した(入力待ち)。RTOSでは、基本的にブロックは避けるべきである。ブロックしないように改良したコードが下記となる。ただし、その1との差がある部分+αのみ記載。
/* 略 */
#include "fsl_lpuart.h"
/* 略 */
#define hello_task_PRIORITY (configMAX_PRIORITIES - 1)
#define serial_task_PRIORITY (configMAX_PRIORITIES - 1)
/* 略 */
/* シリアルタスクで入力待ちする時間(ms) */
#define WAIT_TIME 100
/* シリアルタスクで入力待ちタイムアウト時間(ms) */
#define TIMEOUT 1000
/* シリアルタスクで入力待ち時の最大待ち回数 */
#define MAX_WAIT_NUM (TIMEOUT / WAIT_TIME)
/* 略 */
/*******************************************************************************
* Prototypes
******************************************************************************/
static void init_lpuart(void);
/* 略 */
int main(void)
{
/* ボード初期化 */
BOARD_InitHardware();
/* UART初期化 */
init_lpuart();
/* 略 */
/* UART初期化 */
void init_lpuart(void) {
lpuart_config_t config;
/* シリアルパラメータ設定 */
LPUART_GetDefaultConfig(&config);
config.baudRate_Bps = 115200U;
config.enableRx = true;
config.enableTx = true;
/* UART初期化 */
LPUART_Init(LPUART0, &config, BOARD_DEBUG_UART_CLK_FREQ);
}
/* 略 */
/* タスクB */
static void serial_task(void *pvParameters)
{
char msg[BUFFER_SIZE];
int idx = 0;
char c;
int wait_num = MAX_WAIT_NUM;
while (1)
{
/* 1文字取得(1秒待機ループ) */
bool received = false;
while (wait_num > 0) {
/* 受信データがあるかハードウェアレジスタを確認
LPUART_STAT_RDRF_MASKは受信データありを示すビット */
if ((LPUART0->STAT & LPUART_STAT_RDRF_MASK) != 0) {
c = LPUART_ReadByte(LPUART0);
received = true;
break;
}
/* Sleep(タスクスイッチ) */
vTaskDelay(pdMS_TO_TICKS(WAIT_TIME));
wait_num--;
}
/* タイムアウト */
if (!received) {
wait_num = MAX_WAIT_NUM;
continue; /* 次の文字待ち */
}
/* 改行コード(CRまたはLF)が来たら終了 */
if (c == '\r' || c == '\n')
{
msg[idx] = '\0';
/* Helloタスクへ入力文字列送信 */
xQueueSend(msg_queue, msg, 0);
idx = 0;
PRINTF("\r\n");
}
/* バッファがいっぱいになる前なら文字を保存 */
else if (idx < (BUFFER_SIZE - 1))
{
msg[idx++] = c;
PUTCHAR(c); /* エコーバック */
}
wait_num = MAX_WAIT_NUM; /* カウントリセット */
}
}
直接UARTをハンドリングするところがミソ。受信レジスタにデータがあるかないかを定期的にチェックし、その度にvTaskDelayによるSleep(タスクスイッチ)が行われる。よって、両タスクの優先度が同じでも、正しく動作する。その時の様子が下記。
おわりに
AIさんの利用
Arduino環境と比べて、NXPボードでFreeRTOSを扱っているサイトはあまり見かけない。なので、AIさんにベッタリとなる。とても今風である。
あるべき姿
その2のようなポーリングではなく、割り込みを用いた方が美しい。別途、調べてみる(AIさんに依頼)。
たわごと
過去にArduino環境でFreeRTOSを扱った。setup()およびloop()による構文であるが、ジジイにとっては、main()+for()による無限ループの方がわかりやすく感じる。











