PSoC 6 のデュアルコアでLチカ (2)


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

前回のあらすじ

前回の記事では、ふたつのCPUでかわりばんこにLow/Highを出力させることで、Lチカを実現しました。しかし、これらのCPUは互いに同期しているわけではないので、タイミングがずれるときれいなLチカになりません。
今回は、セマフォという仕組みを使ってふたつのCPUを同期させます。

セマフォって、何じゃろうか

セマフォについて、ここでは詳しく解説しません。セマフォという「もの」が一つだけ存在し、一方のCPUが獲得したら他方は獲得できなくなるという仕組みで働きます。

ここで実現したいこと

このプロジェクトでは、「500m秒のあいだセマフォを獲得し、その間だけLEDを点灯する。」というプログラムをふたつのCPUに実行させます。すると、

  1. CPU1がセマフォを獲得しLED1を点灯させる。
  2. CPU1が500m秒の間、セマフォを保持し続ける。
  3. CPU1がLED1を消灯しセマフォを解放する。
  4. CPU2が解放されたセマフォを獲得しLED2を点灯させる。
  5. CPU2が500m秒の間、セマフォを保持し続ける。
  6. CPU2がLED2を消灯しセマフォを解放する。
  7. 1に戻る

以上のように、ふたつのCPUがかわりばんこにセマフォを獲得し、LED1/LED2が点滅をするという計画です。

Cortex-M0+のプログラム

main_cm0p.c
#include "project.h"

#define SEMAPHORE_LED  (8)  /* セマフォの識別番号 */

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

    /* Cortex-M4 を叩き起こして CY_CORTEX_M4_APPL_ADDR から実行させる。 */
    Cy_SysEnableCM4(CY_CORTEX_M4_APPL_ADDR); 

    for(;;)
    {
        /* セマフォを獲得する */
        while (
            Cy_IPC_Sema_Set(SEMAPHORE_LED, false) != CY_IPC_SEMA_SUCCESS
        ) ;
        /* GPIO 出力をクリア (Low) する。 */
        Cy_GPIO_Clr(Pin_LEDR_PORT, Pin_LEDR_NUM);
        /* 500m秒待つ */
        CyDelay(500);
        /* GPIO 出力をセット (High) する。 */
        Cy_GPIO_Set(Pin_LEDR_PORT, Pin_LEDR_NUM);
        /* セマフォを開放する */
        while (
            Cy_IPC_Sema_Clear(SEMAPHORE_LED, false) != CY_IPC_SEMA_SUCCESS
        ) ;
    }
}

セマフォは、main() 関数に制御が渡る前に SystemInit()関数によって初期化されて、全部で128個のセマフォが作成されます。このうち、0番から7番まではシステムで予約されています。このプログラムでは、8番だけを使用します。

無限ループでは、先のステップ通りにセマフォの獲得(Cy_IPC_Sema_Set)および解放(Cy_IPC_Sema_Clear)とLEDのON/OFF制御を行っています。

main_m4.c
#include "project.h"

#define SEMAPHORE_LED  (8)  /* セマフォの識別番号 */

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

    for(;;)
    {
        /* セマフォを獲得する */
        while (
            Cy_IPC_Sema_Set(SEMAPHORE_LED, false) != CY_IPC_SEMA_SUCCESS
        ) ;
        /* GPIO 出力をクリア (Low) する。 */
        Cy_GPIO_Clr(Pin_LEDG_PORT, Pin_LEDG_NUM);
        /* 500m秒待つ */
        CyDelay(500);
        /* GPIO 出力をセット (High) する。 */
        Cy_GPIO_Set(Pin_LEDG_PORT, Pin_LEDG_NUM);
        /* セマフォを開放する */
        while (
            Cy_IPC_Sema_Clear(SEMAPHORE_LED, false) != CY_IPC_SEMA_SUCCESS
        ) ;
    }
}

Cortex-M4側でも main() に制御が渡る前に初期化されます。ふたつのCPUが同じセマフォにアクセスするために識別番号 SEMAPHORE_LED が定義されています。

無限ループでの振る舞いは、操作するLEDが異なるのを除いて、Cortex-M0+と同じです。

うまくいかなかった

プロジェクトを作成してLチカさせたところ、ふたつのLEDが交互に光るのを期待したのに、点灯時間が異なっているようです。オシロスコープでみた所、このようになっていました。

上(CH1)がCortex-M0+側の波形で、下(CH2)がCortex-M4側の波形です。どうやらCortex-M4側がセマフォを獲ったまま解放していないように見えます。そこで、セマフォが解放された瞬間を詳しく見てみました。

先ほどの波形を見る限りでは、セマフォが解放されていないように見えました。しかし、解放の様子を詳しく見ると、Cortex-M4により解放されたセマフォが、ふたたびCortex-M4によって獲得されているのがわかります。再獲得するまでの時間は、約3μ秒です。

一方、Cortex-M0+がセマフォを獲得できた場合を見ると、約4μ秒かかっています。これだけの時間がかかっていたら、Cortex-M4に先を越されてしまいます。
つまり、Cortex-M0+がセマフォを獲得するスキが無かったのが原因のようです。

Cortex-M0+にゆずってあげる

色々な解決方法が考えられますが、手っ取り早いのが「Cortex-M0+がセマフォを獲得するスキを作る」ことです。main_cm4.cの無限ループの終わりの方にこのようにスキを作りました。

main_cm4.c
        /* セマフォを解放する */
        while (
            Cy_IPC_Sema_Clear(SEMAPHORE_LED, false) != CY_IPC_SEMA_SUCCESS
        ) ;
        /* Cortex-M0+ にチャンスを与える */
        CyDelayUs(3);
    }
}

これで、毎回Cortex-M0+がセマフォを獲得することができるようになります。変更後は、50%デューティーの波形が観測されました。

Cortex-M4がセマフォを開放した後の様子を見ると、Cortex-M0+がセマフォを獲得するまでに約5.6μ秒を要しています。この時間の長さがCortex-M4にセマフォを横取りされてしまった原因と考えられます。

こんな目分量でいいの?

とはいえ、この記事で「スキ」を作るために待ち合わせた3μ秒が適切かどうかはわかりません。もっと確実に動作させる方法は、無いのでしょうか?

関連文献

AN215656 – PSoC 6 MCU Dual-Core CPU System Design
CE216795 - PSoC(R) 6 MCU Dual-Core Basics

関連記事

PSoC 6 のデュアルコアでLチカ (1)