FreeRTOSをPSoC5LPで使う。既存のプロジェクトに適用する

カテゴリー: PSoC,ソフトウェア  タグ:

HOS V4以来のRTOSです。RTOSといっても、キャラクタLCDにリアルタイム表示させる、バックグラウンドで何かやらせる、程度で使うのみでした。

今回はメモリに多少余裕のあるPSoC5LP(CY8C5868LTI-038)で、アクチュエータ動作中にもUP/DOWNキーによるスピード設定をしたいので、既存のベアメタルファームに後からFreeRTOSを導入し、メインと設定タスクを分けてみました。FreeRTOSはPSoC5LP用のデモを公開しているので、OSのコンフィギュレーションはそれをコピーするだけでも導入はできます。

アクチュエータ駆動中にもスピードを変えたい

このボードには表示器とUP/DOWNキーが3組ある。

動作は単純なステートマシンで、ほぼシーケンシャルな動作です。数値設定中は動作開始できないし、動作中に設定変更できません。いままで仕様なので問題ありませんでした。

今回のアプリでは、動作中に設定変更する仕様です。数値設定は、押しているとどんどん早くなる仕様で、複数あるUP/DOWNキーのどれを押してもストレスなくカウントUP/DOWNしなければなりません。アクチュエータに即座に反映するには、ISRで時間分割するなど方法はいくらでもありますが、とにかく面倒。そこでRTOSの出番です。

このステートマシンでは、運転中に設定変更できない。State2を別タスク化したい

PSoC Createrで、既存Projectに後からFreeRTOS10.4.1を導入する。

すでにFreeRTOS10.4.2になっているようです。DEMOから移植する方法と、カーネルのソースファイルをプロジェクトに関連づけ、コンフィギュレーションだけDEMOからコピーする方法があります。

後者を導入します

ソースファイルとヘッダファイルをプロジェクトに関連付ける

ダウンロードしたFreeRTOSを適当な場所に解凍します。このとき関連付け元のフォルダが固定するので、プロジェクトに近いフォルダか、共通フォルダを作成するのがBetter です。

プロジェクトごとに完結するなら、プロジェクトフォルダの中にRTOSソースを入れてしまうの一番がシンプルです。ソースの容量は増えますが・・・。ちなみにPCoC Creater 4.2です。

以下、ネット情報をたどって自分のプロジェクトにRTOSを組み込んだ経緯です。

  1. プロジェクトのSourceFilesとHeaderFilesに適当な名前のフォルダを作る。(EX. SRCとHEADER。作らないで同一フォルダにすることも可能)
  2. FreeRTOS\Source\内のファイル7個をプロジェクトのSourceFiles\SRCにドラック&ドロップ。(croutine.cは使わないので除く。あると.hファイルが無い、と怒られる)
  3. FreeRTOS\include\内のファイルをSourceFiles\HEADERにドラック&ドロップ
  4. FreeRTOS¥Source¥Potable\GCC\ARM_CM3のPort.cとPortmacro.hをSourceFiles\SRCとHeaderFiles\HEADERにそれぞれドラック&ドロップ
  5. FreeRTOS\portable\MemMang\内のheap_n.c(後で説明)をSourceFiles\SRCにドラック&ドロップ
  6. PSoC Createrの「Project」「Build Setting」「ARM GCC・・・」[Compiler]「Additional Include Directries」で、上記のフォルダを関連付ける。「MemMang」を忘れずに。
  7. FreeRTOS\Demo\CORTEX_CY8C5588_PSoC_Creator_GCC」「FreeRTOS_Demo.cydsn」フォルダの「device.h」と「FreeRTOSConfig.h」をプロジェクトフォルダにコピー。これはファイル自体をコピーしてから関連付けする。

以上を行うとPSoC Createrのプロジェクトは下記のようなフォルダ構成になります

 

xprintはあとから導入

HeapやFreeRTOSConfig.hについては、AWSサイトまたは「 FreeRTOSユーザーズガイド」(日本語版はここから)

Heapの適用

  • heap_1 最も簡単な実装です。メモリを解放することはできません。
  • heap_2 メモリを解放することはできますが、フリーブロックに隣接するメモリを結合することはできません。
  • heap_3 スレッドの安全性のために標準の malloc()free() をラップします。
  • heap_4 断片化を避けるために、隣接するフリーブロックを結合します。絶対アドレス配置オプションを含みます。
  • heap_5 これは heap_4 に似ています。ヒープは複数の隣接していないメモリ領域にまたがることができます。

Heap_1.cにすることにしました

 

FreeRTOSConfig.hを編集

クロックはPSoC Createrから継承されています。将来使う可能性のある「mutex」と「スタックオーバーフローチェック」そして使用予定の「vTaskDelay()」関数以外はチェックを外してみました。以下抜粋です。

※変更11/17 タスクブロック用のvTaskSuspendにもチェック


必要になったらチェックいれるとよいでしょう

#define configUSE_PREEMPTION 1
#define configUSE_IDLE_HOOK 0
#define configMAX_PRIORITIES ( 5 )
#define configUSE_TICK_HOOK 0
#define configCPU_CLOCK_HZ ( ( unsigned long ) BCLK__BUS_CLK__HZ )
#define configTICK_RATE_HZ ( ( TickType_t ) 1000 )
#define configMINIMAL_STACK_SIZE ( ( unsigned short ) 100 ) #define configTOTAL_HEAP_SIZE ( ( size_t ) ( 32 * 1024 ) )
#define configMAX_TASK_NAME_LEN ( 12 )
#define configUSE_TRACE_FACILITY 0
#define configUSE_16_BIT_TICKS 0
#define configIDLE_SHOULD_YIELD 0
#define configUSE_CO_ROUTINES 0
#define configUSE_MUTEXES 1
#define configMAX_CO_ROUTINE_PRIORITIES ( 2 )
#define configUSE_COUNTING_SEMAPHORES 0
#define configUSE_ALTERNATIVE_API 0
#define configCHECK_FOR_STACK_OVERFLOW 1
#define configUSE_RECURSIVE_MUTEXES 1
#define configQUEUE_REGISTRY_SIZE 0
#define configGENERATE_RUN_TIME_STATS 0
#define configUSE_MALLOC_FAILED_HOOK 0 /* Set the following definitions to 1 to include the API function, or zero to exclude the API function. */

#define INCLUDE_vTaskPrioritySet 0
#define INCLUDE_uxTaskPriorityGet 0
#define INCLUDE_vTaskDelete 0
#define INCLUDE_vTaskCleanUpResources 0
#define INCLUDE_vTaskSuspend 1
#define INCLUDE_vTaskDelayUntil 0
#define INCLUDE_vTaskDelay 1
#define INCLUDE_uxTaskGetStackHighWaterMark 0
#define INCLUDE_eTaskGetState 0

ソースを改変する

とりあえず、旧ソースのステート部分です。すこし端折りますがご容赦を。

もともと100msと1msの周期割り込みハンドラを使ってますので、ここは変えることなく移行します。タイムベース部分は手直しします(概要は後述)。

 
	for(;;) {
		check_USB_active();
		if(stat==0) {	// 待機状態	
			if(KYUP1) numset(1);
			if(KYDN1) numset(2);
			if(KYUP2) numset(3);
			if(KYDN2) numset(4);
			if(KYST) {
				stat = 1;
				wait_timec(10);
				while(KYST);
			}
		}
		if(stat==1) {	// フタが開いてなけれはモータースタート
			if(!(KYOP1)) stat=5;		
                        else if(!(KYOP2)) stat=6;
			else stat = 2;
			stat = 2;
		}

この4行目からの四行をそっくりタスクに移行し、mainループ(mloop)と設定タスクを分けます。お決まりのコードを追加し以下になります。

※追加11/17 タスクブロック用にタスクハンドルを追加

 

この4行目からの四行をそっくりタスクに移行し、mainループ(mloop)と設定タスクを分けます。お決まりのコードを追加し以下になります。

※追加11/17 タスクブロック用にタスクハンドルを追加

xTaskHandle xTask1;    // タスクハンドル1 xTaskHandle xTask2;    // タスクハンドル2
int main() {
    CyGlobalIntEnable;
    init();
    flag.b.blink = 0; //FreeRTOSの初期設定
    extern void xPortPendSVHandler( void );
    extern void xPortSysTickHandler( void );
    extern void vPortSVCHandler( void );
    extern cyisraddress CyRamVectors[];
    CyRamVectors[ 11 ] = ( cyisraddress ) vPortSVCHandler; CyRamVectors[ 14 ] = ( cyisraddress ) xPortPendSVHandler; CyRamVectors[ 15 ] = ( cyisraddress ) xPortSysTickHandler; //タスクを生成 
xTaskCreate(vTestTask1,"task_main",100,NULL,3,&xTask1); xTaskCreate(vTestTask2,"task_setting",100,NULL,3,&xTask2);
//スケジューラを起動 
    vTaskStartScheduler();
    for(;;);
}

//メインタスク 
void vTestTask1(){ 
// vTaskDelay(10);
    if((KYUP1) && (KYDN2)) { while((KYUP1) || (KYDN2));    vTaskDelay(20);
        test2();
    }
    for(;;) mloop();
}

//設定値アップダウンタスク
void vTestTask2(){ //
    vTaskDelay(10);
    for(;;){
        if(KYUP1) numset(1);
        if(KYDN1) numset(2);
        if(KYUP2) numset(3);
        if(KYDN2) numset(4);
    }
}

私は慣例的に1ms×n待ちを「wait_timec()」という関数にしています。単純なms待ちは、これをvTaskDelay()に置き換えるだけでOKのはずです。ウォッチドッグエサやりは各自工夫していただきたいです。すいません。

タイムベース同期は、周期割り込み内のタイミング変数を拾ってvTaskDelay()を追従させます。

USBやEEPROM資産の同期は?

今のところ、EEPROM読出しは初期化タスク、書き込みは設定タスクのみなので、このままでもよいかしら?

USBは、メインタスクでしかアクセスしませんが、将来的にEEPROMに書き込むのでMutexにするか検討。

最終的にリソース調整

例にならい、各タスクのスタック使用量を100にしたのでSRAM使用量は5%前後から55%近くに上がりました。たぶんこの程度のアプリで100は不要です。まだあと45%あるのでひとまずこのままで・・・

メモリのチューニングはあまり知識がないので、今後の課題です。2タスクしか使わないので優先度もこのままで良いでしょう。メインの動作によっては今後ブロック処理が必要になるでしょう。

以上、RTOSの簡単な導入手順の覚書です。以前HOSでH8/3664などでRTOSのメリットを書いたことがあります(HP更新で削除)が、いまのMCUはリソースが豊富なので応用はさらに容易です。

デバッグは・・・

デバッガはMiniProg4ですが、タスク間のタイミングはデバッグしにくいです。慣れるしかありませんが、ポート出力モニタでも、タスクごとに変えないといけません。今後学習します

ちょっと疑問。スケジューラは何のタイマを使ってるの?

以前のMCUではハードウェアタイマを必ず一つ占有していました。ARMではSyisTickだそうです。ARMはRTOSのため(?)にシステム用のカウンタを持っています。これが使われるようです。タイマで心配しなくてよいということで納得!

お気軽にコメントをどうぞ。

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)