HOS V4以来のRTOSです。RTOSといっても、キャラクタLCDにリアルタイム表示させる、バックグラウンドで何かやらせる、程度で使うのみでした。
今回はメモリに多少余裕のあるPSoC5LP(CY8C5868LTI-038)で、アクチュエータ動作中にもUP/DOWNキーによるスピード設定をしたいので、既存のベアメタルファームに後からFreeRTOSを導入し、メインと設定タスクを分けてみました。FreeRTOSはPSoC5LP用のデモを公開しているので、OSのコンフィギュレーションはそれをコピーするだけでも導入はできます。
アクチュエータ駆動中にもスピードを変えたい
動作は単純なステートマシンで、ほぼシーケンシャルな動作です。数値設定中は動作開始できないし、動作中に設定変更できません。いままで仕様なので問題ありませんでした。
今回のアプリでは、動作中に設定変更する仕様です。数値設定は、押しているとどんどん早くなる仕様で、複数あるUP/DOWNキーのどれを押してもストレスなくカウントUP/DOWNしなければなりません。アクチュエータに即座に反映するには、ISRで時間分割するなど方法はいくらでもありますが、とにかく面倒。そこでRTOSの出番です。
PSoC Createrで、既存Projectに後からFreeRTOS10.4.1を導入する。
すでにFreeRTOS10.4.2になっているようです。DEMOから移植する方法と、カーネルのソースファイルをプロジェクトに関連づけ、コンフィギュレーションだけDEMOからコピーする方法があります。
後者を導入します
ソースファイルとヘッダファイルをプロジェクトに関連付ける
ダウンロードしたFreeRTOSを適当な場所に解凍します。このとき関連付け元のフォルダが固定するので、プロジェクトに近いフォルダか、共通フォルダを作成するのがBetter です。
プロジェクトごとに完結するなら、プロジェクトフォルダの中にRTOSソースを入れてしまうの一番がシンプルです。ソースの容量は増えますが・・・。ちなみにPCoC Creater 4.2です。
以下、ネット情報をたどって自分のプロジェクトにRTOSを組み込んだ経緯です。
- プロジェクトのSourceFilesとHeaderFilesに適当な名前のフォルダを作る。(EX. SRCとHEADER。作らないで同一フォルダにすることも可能)
- FreeRTOS\Source\内のファイル7個をプロジェクトのSourceFiles\SRCにドラック&ドロップ。(croutine.cは使わないので除く。あると.hファイルが無い、と怒られる)
- FreeRTOS\include\内のファイルをSourceFiles\HEADERにドラック&ドロップ
- FreeRTOS¥Source¥Potable\GCC\ARM_CM3のPort.cとPortmacro.hをSourceFiles\SRCとHeaderFiles\HEADERにそれぞれドラック&ドロップ
- FreeRTOS\portable\MemMang\内のheap_n.c(後で説明)をSourceFiles\SRCにドラック&ドロップ
- PSoC Createrの「Project」「Build Setting」「ARM GCC・・・」[Compiler]「Additional Include Directries」で、上記のフォルダを関連付ける。「MemMang」を忘れずに。
- FreeRTOS\Demo\CORTEX_CY8C5588_PSoC_Creator_GCC」「FreeRTOS_Demo.cydsn」フォルダの「device.h」と「FreeRTOSConfig.h」をプロジェクトフォルダにコピー。これはファイル自体をコピーしてから関連付けする。
以上を行うとPSoC Createrのプロジェクトは下記のようなフォルダ構成になります
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のため(?)にシステム用のカウンタを持っています。これが使われるようです。タイマで心配しなくてよいということで納得!