##前回のあらすじ
前回の記事では、Cortex-M4側のデータをIPC Channelの仕組みを使って**Cortex-M0+**側に送信しました。
今回は、Pipeという仕組みを使ってメッセージを送信します。
##ハードウェア
本プロジェクトでは、前回の記事と同様にタイマとしてMCWDTを使用します。
##PIPE
前回の記事では、メッセージを送信するためにIPC Channelを一つ消費していました。IPC Channelの数は限りがあるため、アプリケーションが大きくなると使い切ってしまう恐れがあります。このPCoC 6の場合には、16個のIPC Channelのうちユーザが使えるのは8個だけです。
この状況を解決するために用意されたのがPIPEという仕組みです。
ふたつのCPUの間をふたつのIPC Channelがつないでいて、互いにメッセージを送りあう仕組みがあらかじめ用意されています。そこにメッセージを流すのですが、メッセージの先頭にCLIENTと呼ばれる宛先番号を付加することにより適切な宛先にメッセージを送り届けることができます。
##**Cortex-M0+**側のプログラム
メッセージ交換の仕組みをIPC ChannelからPIPEに変更したので、今回は**Cortex-M0+**側のプログラムも変更されます。
#include <string.h>
#include "project.h"
#define PIPE_CLIENT_UARTTX (0) /* UART送信用の CLIENT 番号 */
/* 送信バッファの定義 */
char_t txBuffer[128] = ""; /* バッファ本体 */
uint32_t txColumn = 0; /* 次に送るべき文字へのインデックス */
uint32_t txCharsLeft = 0; /* 送信すべき残り文字数 */
PIPEでは、あらかじめ決められたCLIENT番号で宛先を識別しています。マクロ定義部分で、このCLIENT番号が宣言されます。他に使う予定もないので、0番を割り当てました。
このPIPEという仕組みは、システム向けのCYPIPEと呼ばれるものとユーザ向けのUSRPIPE呼ばれるものの二種類が存在しているようです。しかしながら、USRPIPEの方はデフォルトの状態では実装が不完全なため、使うにはかなり手間がかかります。ここでは、CYPIPEの方を利用します。
送信バッファの宣言部分は、これまで同様です。
void pipeUartTxCallback(uint32_t *message) {
CY_ASSERT(
txCharsLeft == 0
);
/* 受信成功:急いでデータをバッファにコピー */
strcpy(txBuffer, (char_t *)(&message[1]));
txCharsLeft = strlen(txBuffer);
txColumn = 0;
/* 処理が終わるまで受け付け停止 */
if (txCharsLeft > 0) {
Cy_IPC_Pipe_EndpointPause(CY_IPC_EP_CYPIPE_CM0_ADDR);
}
}
PIPEの仕組みを使うと、受信されたメッセージを引き渡すためにCallback関数が呼ばれます。ここには、これまでのプログラムで受信に成功した場合の処理が入っています。
PIPEの仕組みでは、送られてきたメッセージは必ず受信されて、Callbackが呼び出されます。受信側の都合にはお構いなしです。したがって、このプログラム例のように一文字ずつUARTに出力するような処理を行っていると、処理しきれないほどCallbackが発生してしまいす。
このような事態を防ぐために、このプログラムでは、文字列を受信したらCy_IPC_Pipe_EndpointPause()
関数を呼び出して、受信した文字列の処理が終わるまで次のメッセージを受け付けないようにします。ただし、この関数はPIPE上のすべてのCLIENTに対する受信も停止してしまうため、他のCallbackも停止してしまい、悪影響が出ます。修正は、次回考えます。
int main(void) {
__enable_irq(); /* 全体の割り込みを許可する */
/* メッセージ受信後に呼び出されるCallbackを登録する */
CY_ASSERT(
Cy_IPC_Pipe_RegisterCallback(
CY_IPC_EP_CYPIPE_CM0_ADDR,
pipeUartTxCallback,
PIPE_CLIENT_UARTTX
) == CY_IPC_PIPE_SUCCESS
);
/* Cortex-M4 を叩き起こして CY_CORTEX_M4_APPL_ADDR から実行させる。 */
Cy_SysEnableCM4(CY_CORTEX_M4_APPL_ADDR);
/* UART を初期化する */
UART_Start();
for (;;) {
/* UART制御ブロック */
if (txCharsLeft > 0) {
/* バッファのデータを一文字ずつちんたら送り出す。 */
UART_Put(txBuffer[txColumn]);
txColumn++;
txCharsLeft--;
if (txCharsLeft <= 0) {
/* すべて送り出したら受付再開 */
Cy_IPC_Pipe_EndpointResume(CY_IPC_EP_CYPIPE_CM0_ADDR);
}
}
/* 一文字当たり10msの低速処理 */
CyDelay(10);
}
}
main()
関数では、IPC Channelを生成していた部分がCallbackの登録に変更されました。登録する際には、受信側のENDPOINT番号とCLIENT番号が必要です。
メッセージの受信はCallbackで行われるため、メインループでは、バッファ内のデータをUARTに送信する処理だけが残されています。
バッファのデータをすべて送り出したら、Cy_IPC_Pipe_EndpointResume()
関数で次のメッセージの受け付けを再開します。
##Cortex-M4側のプログラム
メッセージ交換の方法が変更されたので、Cortex-M4側のプログラムも変更されます。
#include <stdio.h>
#include "project.h"
#define PIPE_CLIENT_UARTTX (0) /* UART送信用の CLIENT 番号 */
/* WDT を使った周期割り込み */
bool Int_WDT_flag0 = false;
bool Int_WDT_flag1 = false;
bool Int_WDT_flag2 = false;
void Int_WDT_isr(void) {
uint32_t status = Cy_MCWDT_GetInterruptStatus(MCWDT_HW);
if (status & CY_MCWDT_CTR0) {
Cy_MCWDT_ClearInterrupt(MCWDT_HW, CY_MCWDT_CTR0);
Int_WDT_flag0 = true;
}
if (status & CY_MCWDT_CTR1) {
Cy_MCWDT_ClearInterrupt(MCWDT_HW, CY_MCWDT_CTR1);
Int_WDT_flag1 = true;
}
if (status & CY_MCWDT_CTR2) {
Cy_MCWDT_ClearInterrupt(MCWDT_HW, CY_MCWDT_CTR2);
Int_WDT_flag2 = true;
}
}
void Int_WDT_Start(void) {
/* 割り込みコンポーネントを初期化しイネーブル */
Cy_SysInt_Init(&Int_WDT_cfg, Int_WDT_isr);
NVIC_ClearPendingIRQ((IRQn_Type) Int_WDT_cfg.intrSrc);
NVIC_EnableIRQ((IRQn_Type) Int_WDT_cfg.intrSrc);
/* MCWDT コンポーネントを起動する */
Cy_MCWDT_Init(MCWDT_HW, &MCWDT_config);
Cy_MCWDT_Enable(MCWDT_HW, MCWDT_ENABLED_CTRS_MASK, 0u);
/* MCWDT コンポーネントの割り込みマスクを設定する */
Cy_MCWDT_SetInterruptMask(MCWDT_HW,
CY_MCWDT_CTR0 | CY_MCWDT_CTR1 | CY_MCWDT_CTR2
);
}
メッセージ交換に使用されるCLIENT番号がマクロで宣言されています。それ以外の割り込み処理とフラグの取り扱いは、これまで通りです。
/* Task1の定義 */
enum Task1State {
ST_CREATE,
ST_SEND
};
struct Task1Context {
enum Task1State state; /* ステートマシンの状態 */
bool *flag; /* 割り込みフラグ */
uint32_t count; /* メッセージ番号 */
uint32_t side; /* 使用中バッファ面 */
const char_t *name; /* タスクの名前 */
struct {
uint32_t header;
char_t message[128];
} message[2]; /* ダブルバッファ */
};
void task1_init(struct Task1Context *context, const char_t *name, bool *flag) {
/* タスクの初期化 */
context->name = name;
context->count = 0;
context->side = 0;
context->flag = flag;
context->state = ST_CREATE;
}
Task1Context
のメッセージバッファ部分が変更されています。PIPEを使うときには、メッセージの最初の32ビットにCLIENT番号などの宛先情報が格納されます。このため、文字列本体の前にheader
が追加されています。
void task1_dispatch(struct Task1Context *context) {
switch (context->state) {
case ST_CREATE:
/* 送信するメッセージを作成する */
context->message[context->side].header =
_VAL2FLD(CY_IPC_PIPE_MSG_CLIENT, PIPE_CLIENT_UARTTX) |
_VAL2FLD(CY_IPC_PIPE_MSG_USR, 0u) |
_VAL2FLD(CY_IPC_PIPE_MSG_RELEASE, CY_IPC_CYPIPE_INTR_MASK );
(void)sprintf(
context->message[context->side].message,
"%s: HELLO WORLD %lu\r\n",
context->name, context->count++
);
context->state = ST_SEND;
break;
case ST_SEND:
/* フラグが立っている時のみ実行 */
if (*(context->flag)) {
/* メッセージを送る */
if (
Cy_IPC_Pipe_SendMessage(
CY_IPC_EP_CYPIPE_CM0_ADDR,
CY_IPC_EP_CYPIPE_CM4_ADDR,
(uint32_t *)(&context->message[context->side]),
0
) == CY_IPC_PIPE_SUCCESS
) {
/* バッファを切り替える */
context->side = (context->side)?(0):(1);
context->state = ST_CREATE;
/* 割り込みフラグを折る */
*(context->flag) = false;
}
}
break;
default:
CY_ASSERT(false);
}
}
送信メッセージを作成するST_CREATE
では、header
部分の操作が追加されています。
ST_SEND
では、Cy_IPC_Pipe_SendMessage()
関数でCortex-M0+からCortex-M4に向けてメッセージが発信されます。
main()
関数は、これまでと同じです。
##実行してみたら
実行を始めると、三つのタスクが処理されていく様子がわかります。
##関連文献
AN215656 - PSoC 6 MCU Dual-Core CPU System Design
AN217666 - PSoC 6 MCU Interrupts
CE216795 - PSoC(R) 6 MCU Dual-Core Basics
CE219339 - PSoC 6 MCU - MCWDT and RTC Interrupts (Dual Core)
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)
PSoC 6 のデュアルコアでHello World (2)
PSoC 6 のデュアルコアでHello World (3)