0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

FRDM-MCXA153(その2:FreeRTOS)

0
Posted at

FRDM-MCXA153でFreeRTOSを扱う

久しぶりにRTOSを実感することも兼ねて、NXP製FRDM-MCXA153でFreeRTOSを使ってみる。適宜、AIさん利用。

MCUXpresso

当初、スクラッチからProjectを作成をしていた。

CreateNewのコピー.png

FreeRTOSコンポーネントの選択をしても、Buildできずじまい。おそらく、Makefileの記述やらIncludeやライブラリやらの不足なのであろう。原因を追いかけるのが本質ではなかったため、サンプルFreeRTOSプロジェクトをCopy&Pasteすることにした。

ProjectCopy.png

既存のfreertos_helloをCopy&Paste。

ProjectPaste1.png

プロジェクト名称を入力。

ProjectPaste3.png

一覧にそのプロジェクトが作られる。

ProjectPasteDoneのコピー.png

任意ではあるが、ソースコードの名称も変更する。

RenameSrc1.png
RenameSrc2.png

変更完了。

RenameSrcDoneのコピー.png

題材:RTOSのストーリー

  • タスク2つ
    • タスクA:一定周期でメッセージ出力
      • キューが空:固定文字列およびインクリメントされるカウンタを表示
      • キューが空でない:キューの内容を表示
    • タスクB:シリアル入力をキューにためる
  • タスク優先度により、出力の状況を確認

検証&ソースコード

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()でブロックしてしまうからである(タスクスイッチが発生しない)。上記コード時の実行の様子を下記する。

1st_OK(SerialTaskLower).png

行の途中から見えるHelloやReceivedで始まるメッセージが、タスクAによる表示であり、行頭の表示が入力した文字列で、タスクBに取り込まれるものである。なお、ここでは、キュー数が3であり、4番目の入力(444)がタスクAに渡っていないこともわかる。

Case2:タスクAとタスクBの優先度が同じ

1st_NG(samePriority)_2.png

タスクAによる表示は1度だけ発生し、タスクBに制御が移った後、タスクAに戻らない。

Case3:タスクBの優先度が高い

1st_NG(SerialTaskHigher)_2.png

タスク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(タスクスイッチ)が行われる。よって、両タスクの優先度が同じでも、正しく動作する。その時の様子が下記。

2nd_OK(SamePriority).png

おわりに

AIさんの利用

Arduino環境と比べて、NXPボードでFreeRTOSを扱っているサイトはあまり見かけない。なので、AIさんにベッタリとなる。とても今風である。

あるべき姿

その2のようなポーリングではなく、割り込みを用いた方が美しい。別途、調べてみる(AIさんに依頼)。

たわごと

過去にArduino環境でFreeRTOSを扱った。setup()およびloop()による構文であるが、ジジイにとっては、main()+for()による無限ループの方がわかりやすく感じる。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?