割り込み
「その2」に記載した「あるべき姿」を求め(ポーリングではなく割り込み)、AIさんにたずねて作成した内容。今回も備忘録レベルの内容。
CPU消費を減らすために割り込みを利用(いまさらだけど)
ソフトウェア開発、特にRTOSでは、CPUリソースの関連から、ポーリングは嫌われるため、最大限、割り込みを用いるのが王道である。調べたところ(AIさんなど)、当然、FreeRTOSにも用意されている。後は、MCUXpresso環境での使い方(Call方法)の話。
ストーリー
- タスク2つ
- タスクA:一定周期でメッセージ出力
- キューが空:固定文字列およびインクリメントされるカウンタを表示
- キューが空でない:キューの内容を表示
- タスクB:シリアル入力をキューにためる
- タスク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"
#include "fsl_lpuart.h"
/*******************************************************************************
* Definitions
******************************************************************************/
/* タスクプライオリティ */
#define hello_task_PRIORITY (configMAX_PRIORITIES - 1)
#define serial_task_PRIORITY (configMAX_PRIORITIES - 1)
/* 入力バッファ及びキューのサイズ */
#define BUFFER_SIZE 32
/* キュー内のメッセージの数 */
#define NUM_QUEUE 3
/* hello_taskでSleepする時間(ms) */
#define HELLO_SLEEP 5000
ここは説明略。
/*******************************************************************************
* Globals
******************************************************************************/
/* シリアルタスクからハロータスクへのメッセージ用Queue */
static QueueHandle_t msg_queue = NULL;
/* UART受信バッファ用Queue */
static QueueHandle_t char_queue = NULL;
シリアル入力を割り込みハンドラで検出し、シリアルタスクに渡すQueueが必要となる。
/*******************************************************************************
* Prototypes
******************************************************************************/
static void init_lpuart(void);
void LPUART0_IRQHandler(void);
static void hello_task(void *pvParameters);
static void serial_task(void *pvParameters);
ここは自明。
/*******************************************************************************
* Code
******************************************************************************/
int main(void)
{
/* ボード初期化 */
BOARD_InitHardware();
/* UART初期化 */
init_lpuart();
/* タスク間メッセージQueue作成 */
msg_queue = xQueueCreate(NUM_QUEUE, BUFFER_SIZE);
/* UART受信バッファ用Queue作成(ISRから1文字受信ISR) */
char_queue = xQueueCreate(BUFFER_SIZE, sizeof(char));
/* タスク生成 */
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 (;;)
;
}
ここはコメント参照。
/* 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);
/* configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITYよりも大きな数値(低い優先度) */
NVIC_SetPriority(LPUART0_IRQn, 2); // 1:NG
/* 受信割り込みを有効 */
LPUART_EnableInterrupts(LPUART0, kLPUART_RxDataRegFullInterruptEnable);
/* NVIC側でLPUART割り込みを有効 */
EnableIRQ(LPUART0_IRQn);
}
ここでのミソは下記。
- 本プログラムのNVIC(Nested Vectored Interrupt Controller:ARM Cortex-M内)の割り込み優先度を適宜設定(OS自体より低く)
- 割り込み有効化
/* 割り込みハンドラ */
void LPUART0_IRQHandler(void)
{
uint8_t data;
/* 受信フラグが立っているか確認 */
if ((kLPUART_RxDataRegFullFlag) & LPUART_GetStatusFlags(LPUART0))
/* その2の (LPUART0->STAT & LPUART_STAT_RDRF_MASK) != 0 と同じだがこちらの方がベター */
{
data = LPUART_ReadByte(LPUART0);
/* 明示的にフラグをクリアする(チップ依存、今回は不要であった) */
/* LPUART_ClearStatusFlags(LPUART0, kLPUART_RxDataRegFullFlag); */
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
/* シリアルタスクに1文字送る */
xQueueSendFromISR(char_queue, &data, &xHigherPriorityTaskWoken);
/* 割り込み後に即座にタスクを切り替える必要があるか確認 */
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
}
ここでのミソは下記。
- LPUART0_IRQHandler():この関数名を割り込みハンドラとして用いるのがルール(らしい)
- 確実に受信しているかの確認(受信フラグ)
- データ(入力文字)を取得
- データをシリアルタスクへ送信(xQueueSendFromISR())
- シリアルタスクをWakeUp(portYIELD_FROM_ISR)
/* タスク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
{
PRINTF("\tHello %d\r\n", ++count);
}
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文字来るまで待機(割り込み発生待ち)、タスクスイッチ */
if (xQueueReceive(char_queue, &c, portMAX_DELAY) == pdPASS)
{
/* 取得した文字 c の処理 */
if (c == '\r' || c == '\n')
{
msg[idx] = '\0';
/* タスクAへ入力文字列送信 */
xQueueSend(msg_queue, msg, 0);
idx = 0;
PRINTF("\r\n");
}
else if (idx < (BUFFER_SIZE - 1))
{
msg[idx++] = c;
PUTCHAR(c); // エコーバック
}
}
}
}
下記が大事。
- xQueueReceive()のportMAX_DELAYにて無限待ち
- タスクスイッチあり(割り込みや他のタスクが動作可能)
- 改行コードが入力されたら、それまでに入力された文字列をタスクAへ送信
結果
ターミナルでの出力状況は、「その2」と同じのため、省略。
終わりに
FreeRTOSを使うというより、NXP提供プラットフォーム(MCUXpresso)での、APIなどの使い方を学ぶのがメインとなってしまった気がする。