「TOPPERS/ASPのタスク遷移表示システム」の紹介

##はじめに
2020年度の第10回TOOPERS活用アイデア・アプリケーション開発コンテストのアプリケーション開発部門で金賞を受賞した「TOPPERS/ASPのタスク遷移表示システム」の状態出力側のプログラム構成などを紹介します。
![1_con2020_2_015.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370759/4eb4a761-17f6-04e6-a782-aa7be1309b8f.jpeg)

作品全体については以下のページを参照ください。
・第10回TOOPERS活用アイデア・アプリケーション開発コンテスト
　https://www.toppers.jp/contest.html#2020
　アプリケーション開発部門 
　**「TOPPERS/ASPのタスク遷移表示システム」**

また、**TOPPERS** や **TOPPERS/ASP** については以下のページを参照ください。
・TOPPERSのページ
　https://www.toppers.jp/
・TOPPERSプロジェクトのページ
　https://www.toppers.jp/project.html
・TOPPERS/ASPカーネルのページ
　https://www.toppers.jp/asp-kernel.html


##作品の概要

###狙い
マルチタスクでプログラムを実行した場合に、各タスクが意図した順番で動作しているか、特定のタスクが動作していないなど確認したくなることがあります。
TOPPERS/ASP にはコンソールへのデバッグ情報の出力機能である syslog()を各タスク内に埋め込むことで大まかなタスクの流れを確認することもできますが、プログラムの修正やsyslog()そのもののオーバーヘッドが大きくなることから多用は難しいという問題があります。

また、RTOSやマルチタスクの参考資料などにはタスクの流れを説明するものもありますが、基本的に開発者の頭の中でイメージしながらになり、実際に流れを目視する機会はほとんどありません。
市販のツールやデバッガなどではタスクの動作状況を表示する機能のあるようですが、個人での利用を考えると高価で安易には利用できません。
このため、TOPPERS/ASPカーネルのディスパッチ処理からGPIOを直接操作して各タスクの状態を表示する機能を実現しようと考えました。
![2_clip_4.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370759/615b0d91-5f1d-568c-2223-6489e98523dc.jpeg)
![3_clip_6.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370759/21b2383a-766a-75f2-458a-ccae18447e2a.jpeg)

###作品概要
タスク遷移表示はTOPPERS/ASPを改造してGPIOへ状態を出力するプログラムを組み込んだターゲット装置と、出力された状態を受信して結果をグラフィックLCDへ表示する受信・表示装置の２つの装置で構成されています。

**ターゲット装置**はタスクIDごとに割り当てたGPIOの 0/1でタスクが動作中か否かを出力します。
今回の作品では STM32F401-Nucleoボードを用いて実現しました。
**受信・表示装置**はタスクIDごに割り当てられた信号をGPIOで受け、その結果をグラフィックLCDへグラフ状に表示を行います。
今回の作品では STM32F446-Nucleoボードと SPI接続の グラフィックLCDを用いて実現しました。
この記事では、タスク遷移の状態を出力する**ターゲット装置**側の紹介をします。

受信・表示装置側のタイマー割り込みに関する記事も書いていますので、そちらも参照ください。
　「TOPPERS/ASPとSTM32F446-Nucleoでタイマー割込みを使う」
　　https://qiita.com/Yukiya_Ishioka/items/ef136be8443c49dccb30

##装置紹介
STM32F401-Nucleoボードの PORT-Cのbit00からbit12計13個のGPIOを状態出力に利用します。作品では受信・表示装置と接続を容易にするため信号出力コネクタにまとめてあります。
また、ボード上のユーザボタンでボード上で動作するタスクの状態に変化を加えられるようにしてあります。
![4_clip_2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370759/f9f4fb9d-2594-42bb-0769-1df1b75540a4.jpeg)

##プログラム紹介

###概要
TOPPERS/ASPがデフォルトで提供している sample1.cを改造してメインタスクの他にタスク１０個を生成し、それぞれが２ミリ秒程度のビジーループとslp_tsk()を繰り返すプログラムを作成しました。さらにタスクの状態変化を見られるようユーザボタンの押下で タスク１０個うち TASK02、TASK04の２つのタスクでビジーループの時間を２０ミリ秒程度へ変化する処理を追加してあります。
PORT-Cの bit00 から bit12 の GPIOの信号を取り出すと以下のようになります。
![5_clip_4.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370759/78208e6f-0f54-338d-5c1b-45e12caf494d.jpeg)
各タスクごとに実行状態だと **H**レベル（**1**）、非実行状態だと **L**レベル（**0**）となります。全てのタスクが非実行状態になると TOPPERS/ASP内のアイドル処理が実行されるようになります。
アイドル処理はいずれかのタスクが実行状態になるまで繰り返されます。
今回の作品では、GPIOの **bit00** がアイドル状態を表します。

タスクID は、**0** から始まり、ユーザが作成するタスク数により最大IDは変化します。
**ID 0** はシステムにより **syslogタスク**に割り当てられています。また最大タスクはメインタスクに割り当てられ今回の作品では **ID 11** がメインタスクの状態になります。


###TOPPERS/ASPの改造
タスクの状態はTOPPERS/ASP内の**ディスパッチ処理**で設定されます。
ディスパッチ処理では次に実行状態にする TCB（タスク・コントロール・ブロック）を決めて切り替えることでタスクの切り換えを行います。このため TCBを切り替える処理部分へ GPIOを設定する処理を追加することでタスクの状態を出力することができます。
実行すべきタスク（TCB）が存在しない場合（全てのタスクが非実行状態）、アイドル状態として GPIOを制御します。

TOPPERS/ASPに対して改造・追加する処理は以下になります。
・TCBの改造
・TCBの初期化処理の改造
・GPIO初期化の追加
・指定タスクIDの GPIOを Hレベルにする
・アイドル状態のGPIOを Hレベルにする

####TCBの改造
TCBからタスクIDの取り出しを容易にするため **TCB構造体**に新たなメンバ **tskid**を追加します。

``` asp/kernel/task.h
@@ -254,6 +254,7 @@ typedef struct task_control_block {
    TEXPTN          texptn;         /* 保留例外要因 */
    WINFO           *p_winfo;       /* 待ち情報ブロックへのポインタ */
    TSKCTXB         tskctxb;        /* タスクコンテキストブロック */
+   int             tskid;
 } TCB;
```

####TCBの初期化処理の改造
TCB初期化時に追加したメンバ tskidへタスクIDを代入する処理を追加します。

``` asp/kernel/task.c
 /*
  *  タスク管理モジュールの初期化
  */
@@ -131,7 +133,9 @@ initialize_task(void)
                if ((p_tcb->p_tinib->tskatr & TA_ACT) != 0U) {
                        (void) make_active(p_tcb);
                }
+               p_tcb->tskid = j;
        }
```

####GPIO初期化の追加
PORT-Cの bit00～bit12を利用するため、GPIOを初期化する処理を新たに作成し、追加します。
今回の装置ではGPIOを初期化する関数を新たに作成し、TCBを初期化する処理内で初期化処理を呼び出すようにしました。処理コードはTOPPERSコンテストのページにリンクされているソースファイルを参照してください。


####指定タスクIDの GPIOを Hレベルにする
実行状態となったTCBのタスクに該当する GPIOの bitを **H**レベルへ設定します。
なお、それまで実行状態であったタスクに該当する GPIOの bitを **L**レベルへ落とすため、いったん全ての bitを **L**レベルへ落としてから指定タスクIDの bitを **H**レベルへ設定します。
今回は以下のようなファイル、関数を新たに作成し、それをディスパッチ処理から呼び出しています。

``` asp/kernel/user_monitor.c
void  user_monitor_onoff( void )
{
    int  num = p_runtsk->tskid;

    GPIOC->BSRR = 0x1fff << 16 ;     /* off 0-12 */
    GPIOC->BSRR = (0x1<<(num+1)) ;   /* on  num+1 */

    //GPIOC->BSRR = (0x1<<12) ;        /* on  12 */
}
```

ディスパッチ処理内のタスクへ復帰する処理の途中に user_monitor_onoff()関数の呼び出しを追加しました。

``` asp/arch/arm_m_gcc/common/core_support.S
@@ -657,6 +657,12 @@ ALABEL(dispatcher_0)
        str   r1, [r2]        
        cbz   r1, dispatcher_1  /* p_runtskがNULLならdispatcher_1へ */
        ldr   sp, [r1,#TCB_sp]  /* タスクスタックを復帰 */
+#if 1
+       push  {r1,r2,r3,lr}
+       bl    user_monitor_onoff
+       nop
+       pop   {r1,r2,r3,lr}
+#endif
 #ifdef LOG_DSP_LEAVE
        mov   r0, r1            /* p_runtskをパラメータに */
        mov   r4, r1            /* r1はスクラッチレジスタなので保存 */
```


####アイドル状態のGPIOを Hレベルにする
実行すべきタスク（TCB）が存在しない場合、TOPPERS/ASPはアイドル状態になります。
アイドル状態となった場合には、アイドル状態を表す GPIOの PORT-C bit00を **H**レベルへ設定します。この時、全てのタスクは非実行状態となるので、PORT-C bit01からbit12までを **L**レベルへ落とします。
今回は以下のようなファイル、関数を新たに作成し、それをディスパッチ処理から呼び出しています。

``` asp/kernel/user_monitor.c
void  user_monitor_notask( void )
{
    GPIOC->BSRR = 0x1ffe << 16 ;     /* off 1-12 */
    GPIOC->BSRR = (0x1) ;            /* on  0 */
}
```

ディスパッチ処理内のアイドル処理の先頭に user_monitor_notask()関数の呼び出しを追加しました。

``` asp/arch/arm_m_gcc/common/core_support.S
@@ -719,6 +725,12 @@ ALABEL(dispatcher_2)
         *   r7      : lock_flgのアドレス
         *   sp      : 非タスクコンテキスト用のスタックの先頭アドレス(msp)
         */
+#if 1
+       push  {r1,r2,r3,lr}
+       bl    user_monitor_notask
+       nop
+       pop   {r1,r2,r3,lr}
+#endif
 #ifdef TOPPERS_CUSTOM_IDLE
        toppers_asm_custom_idle
 #else
```

ディスパッチ処理への今回の処理の追加は以下のようになります。
![6_clip_8.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370759/a0844465-c5fa-c903-fd16-daf76f97bb27.jpeg)

###サンプルアプリケーション
今回の作品では１２個のタスクの状態遷移を GPIOへ出力できるようになっています。
TOPPERS/ASPでは提供時の設定であれば**メインタスク**と**syslogタスク**が最小構成になると思います。このため、以下のようなタスク１０個を新たに作成し、状態遷移のデモプログラムとしました。

状態遷移が分かりやすいよう、各タスク内はミリ秒単位のビジーループの呼び出して２ミリ秒の処理を **tslp_tsk()** と交互に連続して行うようにしてあります。
ビジーループ実行中はタスクの実行状態を保持した状態になります。ただしタスクの優先度が高いタスクが実行権を持つと実行状態が優先度の高いタスクへ一時的に映ります。

``` obj/sample1.c
#define  DEF_TASK_NORM_ACT    2

void task03(intptr_t exinf)
{
  syslog(LOG_NOTICE, "%s(exinf = %d).", __FUNCTION__, (int_t) exinf);

  while( 1 ) {
    WaitMsec( DEF_TASK_NORM_ACT );
    tslp_tsk( DEF_TASK_WAIT );
  }
}
```

全てのタスクが同じままだと状態遷移の表示に変化がなくなるため、**TASK02**と**TASK04**は以下のように変数 **dev_btn_status** の状態によりビジーループの時間を２０ミリ秒へと伸ばすようにしてあります。
**dev_btn_status** は **TASK01**内でユーザボタンの状態をリードして押下されると「**1**」を設定するようにしてあります。

``` obj/sample1.c

void task02(intptr_t exinf)
{
  int  act_time;

  syslog(LOG_NOTICE, "%s(exinf = %d).", __FUNCTION__, (int_t) exinf);

  while( 1 ) {
    if( dev_btn_status ) {
      act_time = 20;
    } else {
      act_time = DEF_TASK_NORM_ACT;
    }
    WaitMsec( act_time );
    tslp_tsk( DEF_TASK_WAIT );
  }
}
```

``` obj/sample1.c
void task04(intptr_t exinf)
{
  int  act_time;

  syslog(LOG_NOTICE, "%s(exinf = %d).", __FUNCTION__, (int_t) exinf);

  while( 1 ) {
    if( dev_btn_status ) {
      act_time = 20;
    } else {
      act_time = DEF_TASK_NORM_ACT;
    }
    WaitMsec( act_time );
    tslp_tsk( DEF_TASK_WAIT );
  }
}
```

これにより以下のように２つの状態を出力できるようにしてあります。
●通常時
![7_con2020_2_005.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370759/9b8323b0-0796-f7e2-b32f-08e254ce763e.jpeg)
●ユーザーボタン押下時
![8_con2020_2_008.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370759/d279efb3-0513-7ac7-3f5f-aaa5bba6334b.jpeg)

##最後に
今回の作品は**STM32F401-Nucleoボード**用のコードになりますが、**NUCLEO-64ボード**であれば少ない修正で動作させることが可能だと思います。他の CPUであっても状態遷移を出力するためのコードの追加個所は**TOPPERS/ASP**の同じような処理になると思うので、利用されているCPUのコードを見てトライされてはいかがでしょうか。

また、今回はより多くのタスクの状態をみられるよう13本のGPIOを用いて１２タスクの状態を出力しましたが、利用可能なGPIOの本数が少なければ表示するタスクの数を制限することで対応可能と思います。
なお、今回１２タスクを想定してGPIO制御時にタスクIDによるチェックを省略してあります。**１２より多くのタスクを生成する場合**にはチェック処理などを追加して誤動作しないような工夫が必要となります。

【参考文献】
・日本OSS推進フォーラム「26. 組み込みアプリケーション開発に関する知識 I」
　http://ossforum.jp/book/export/html/751
・NCES「RTOSの基礎」
　https://dev.toppers.jp/trac_user/contrib/raw-attachment/wiki/contest-seminar/RTOSの基礎.pdf


 － 以上 －