HelloWorld
メッセージ
Cortex-M4
Cortex-M0
PSoC6
PSoCDay 16

PSoC 6 のデュアルコアでHello World (2)

これは、PSoC Advent Calendar 2017の16日目に突っ込まれた記事です。

前回のあらすじ

前回の記事では、メッセージ機能を使ってCortex-M4からCortex-M0+に文字列を送り、UARTから出力を得ていました。
今回は、Cortex-M4側に複数のタスクを持たせて、タスク間でUARTの使用権を奪い合う、マルチタスクのような仕組みを取り入れてみました。

マルチタスクとは

ひとつのCPUに複数の仕事をさせる事は、ひとつのCPUが実行できるのはひとつのプログラムだけですので、普通は不可能な課題です。しかし、複数の仕事を細切れにして一つのCPUですこしずつ実行することができれば、外からCPUの振る舞いを見た場合に複数の仕事が同時に実行されているように見えます。これが単一のCPUによるマルチタスクの考え方です。

本プロジェクトで目指すゴール

本プロジェクトでは、Cortex-M4側にマルチタスクのような仕組みを取り入れて、それぞれのタスクからCortex-M0+側に対してメッセージを送りUARTからの出力を要求します。

Cortex-M0+側のプログラム

Cortex-M0+側のプログラムは、前回の記事のものが、そのまま使えます。変更箇所はゼロです。送信側のタスクの数がどんなに増えても、受信側は届いたデータをコツコツとUARTから流すだけなのです。

Cortex-M4側のプログラム

Cortex-M4側のプログラムでは、まず、文字列を送信するためのタスクをステートマシンに分解して初期化ルーチンとディスパッチルーチンに分離しました。

main_cm4.c
#include <stdio.h>
#include "project.h"

#define IPC_CHANNEL_UARTTX  (8)  /* メッセージ送受信用の IPC チャネル番号 */

/* IPC チャネルのハンドル */
IPC_STRUCT_Type *ipcHandle;

/* Task1の定義 */
enum Task1State {
    ST_CREATE,
    ST_SEND
};

struct Task1Context {
    enum Task1State state;  /* ステートマシンの状態 */
    uint32_t count;  /* メッセージ番号 */
    uint32_t side;  /* 使用中バッファ面 */
    const char_t *name;  /* タスクの名前 */
    char_t buffer[2][128]; /* ダブルバッファ */
};

void task1_init(struct Task1Context *context, const char_t *name) {
    /* タスクの初期化 */
    context->name = name;
    context->count = 0;
    context->side = 0;
    context->state = ST_CREATE;
}

void task1_dispatch(struct Task1Context *context) {
    switch (context->state) {
        case ST_CREATE:
            /* 送信するメッセージを作成する */
            (void)sprintf(
                context->buffer[context->side],
                "%s: HELLO WORLD %lu\r\n",
                context->name, context->count++
            );
            context->state = ST_SEND;
            break;
        case ST_SEND:        
            /* メッセージを送る */
            if (
                Cy_IPC_Drv_SendMsgPtr(
                    ipcHandle,
                    CY_IPC_NO_NOTIFICATION,
                    context->buffer[context->side]
                ) == CY_IPC_DRV_SUCCESS
            ) {
                /* バッファを切り替える */
                context->side = (context->side)?(0):(1);
                context->state = ST_CREATE;
            }
            break;
        default:
            CY_ASSERT(false);
    }
}

Task1は、自分の名前と通し番号の入った文字列をCortex-M0+側に送ります。ステートマシンには二つの状態があります。ST_CREATE状態では、次に送信するための文字列を準備し、ST_SEND状態に遷移します。ST_SEND状態では、Cortex-M0+にメッセージを送りますが成功した場合に限りST_CREATE状態に戻ります。こうすると、作成した文字列が送信されるまでST_SEND状態にとどまるので、すべての文字列を抜けなく送り出す事ができます。

Task1で使用されるタスクに固有の記憶域は、struct Task1Contextという構造体に入れられています。この構造体へのポインタをtask1_init()関数やtask1_dispatch()関数の第一引数で引き渡すことで独立したタスクの処理を行うことができます。

main_cm4.c
int main(void) {
    struct Task1Context context1_0;
    struct Task1Context context1_1;
    struct Task1Context context1_2;

    __enable_irq(); /* 全体の割り込みを許可する */

    /* メッセージ交換のためのハンドルを獲得する */
    ipcHandle = Cy_IPC_Drv_GetIpcBaseAddress(IPC_CHANNEL_UARTTX);

    /* タスクの初期化 */
    task1_init(&context1_0, "M4-1-0");
    task1_init(&context1_1, "M4-1-1");
    task1_init(&context1_2, "M4-1-2");

    for (;;) {
        /* Task1 のディスパッチャ */
        task1_dispatch(&context1_0);
        task1_dispatch(&context1_1);        
        task1_dispatch(&context1_2);        
    }
}

Task1を外にくくりだされたmain()関数には、初期化とディスパッチャの呼び出しだけが残りました。ここでは、三つのタスクを定義しており、無限ループ内で単純に順番通りにディスパッチャを呼び出しています。

実行してみたら

実行を始めると、三つのタスクが処理されていく様子がわかります。

GS003389.png

実行の順番や実行の頻度は、タスクによりまちまちです。これは、タスクのスケジュールはおろかメッセージのキューなども取り入れていないためです。このため、しばらく動かしていると、

GS003391.png

このようにタイミングによっては、ひとつのタスクだけが延々と実行されていく様子も観測されました。一番の原因は、Cortex-M4が手加減せず要求を連発していることです。要求側のタイミングを作ってやらないといけません。

関連文献

AN215656 – PSoC 6 MCU Dual-Core CPU System Design
AN217666 - PSoC 6 MCU Interrupts
CE216795 - PSoC(R) 6 MCU Dual-Core Basics
PSoC 6 MCU: PSoC 63 with BLE Architecture Technical Reference Manual

関連記事

PSoC 6 のデュアルコアでLチカ (1)
PSoC 6 のデュアルコアでLチカ (2)
PSoC 6 のデュアルコアでLチカ (3)
PSoC 6 のデュアルコアでLチカ (4)
PSoC 6 のデュアルコアでLチカ (5)
PSoC 6 のデュアルコアでLチカ (6)
PSoC 6 のデュアルコアでLチカ (7)
PSoC 6 のデュアルコアでHello World (1)