こんにちは。NoMaYです。別スレッド『Amazon AWSのFreeRTOS Kernel Developer GuideのサンプルコードをRenesas RX SimulatorのDebug Consoleで試せるようにしてみた』で気付いたことですが、FreeRTOSでは、ポートレイヤーの実装に内蔵周辺のソフトウェア割り込み機能(これはCPUのソフトウェア割り込み命令のことでは無いです)を使用することで、素朴に簡単に割り込みルーチンからFreeRTOS APIを呼び出せるようになることに気付きました。そこで、FreeRTOS v10.2.1 RL78ポートレイヤーをそのように改変してみました。以下、プロジェクトのファイル一式です。(CC-RL版はCS+ V8.01/e2 stuiod v7.40+CC-RL V1.02でビルド(e2 studio用.project/.cproject等を同梱))(GNURL78版はe2 studio v7.40+GNURL78 2019q2(4.9.2.201902)でビルド)(共にzipファイルをe2 studioに直接インポート可能) プロジェクト構造は極力RX版と同じにしてあります。([追記] すみません。以下に含まれるMAPファイルが別のものでした。タスク側のクリティカルセクションが以前のままのものでした。この投稿の末尾に追加したMOTファイルのzipファイルには訂正版を入れてあります。)sim_rl78_freertos_full_demo_ccrl_c_csplus_20190705.zip 628KBsim_rl78_freertos_full_demo_gnurl78_c_e2v740_20190705.zip 582KBそのスレッドに投稿していたRL78ポートレイヤー(これもFreeRTOS v10.2.1 RL78ポートレイヤーを改変したものです)では、割り込みルーチンからFreeRTOS APIを呼び出すには以下のような特別なおまじないを記述する必要がありました。(以下はCC-RLの場合のものですがGNURL78でも同様です。)
#pragma interrupt r_intc3_interrupt(vect=INTP3)/* Start user code for pragma. Do not edit comment generated here */R_PRAGMA_FREERTOS_INTERRUPT(r_intc3_interrupt)#define r_intc3_interrupt _r_intc3_interrupt/* End user code. Do not edit comment generated here */
そのスレッドにはRXポートレイヤーの改変についても投稿していたのですが、その時、最初に書いたことに気付きました。RL78の内蔵周辺には専用のソフトウェア割り込み機能はありませんが、以下のハードウェアマニュアルに記載されている通り、プログラムで割り込み要求フラグをセットすると割り込みを発生させることが出来ますので、実質的に任意の空き割り込みをソフトウェア割り込み機能として使うことが出来ます。(今回は、とりあえず、ウォッチドッグタイマのオーバフロー時間の75%到達のインターバル割り込みを使用してみました。) RL78/G13 ユーザーズマニュアル ハードウェア編からの抜粋www.renesas.com/ja-jp/doc/products/mpumcu/doc/rl78/r01uh0146jj0340_rl78g13.pdfなお、今回のRL78ポートレイヤーの改変で、先ほどの特別なおまじないを記述する必要は無くなりますが、割り込みルーチンからFreeRTOS APIを呼び出してブロック解除待ちタスクをブロック解除する場合のタスク切り替えの遅延時間が以下のRenesas RL78 SimulatorのSimulator GUIの画面コピーのように数十クロックほど(32MHz動作では600nsほど)延びます。(以下はCC-RLの場合のものですがGNURL78でも同様(ただし数百nsほど長い)です。) ちなみに、タスク側のクリティカルセクション(その区間では割り込み禁止となる)への出入りによるタイミングへの影響を避ける為、今回はクリティカルセクションとして扱うことしていませんので、以下の画面コピーでは別スレッドの画面コピーよりもポート出力トグルの速さが速くなっています。今回の改変前:今回の改変後:以下、今回のRL78ポートレイヤの改変内容です。コメントの訂正や不要になったルーチンの削除は省略しています。(以下はCC-RLの場合のものですがGNURL78でも同様です。)src/FreeRTOS/Source/portable/Renesas/RL78/portmacro.h今回の改変前:
/* Task utilities. */#define portYIELD() __brk()#define portYIELD_FROM_ISR( xHigherPriorityTaskWoken ) if( xHigherPriorityTaskWoken ) vTaskSwitchContext()#define portNOP() __nop()
今回の改変後: 赤文字行を追加/変更
#include "iodefine.h"
/* Task utilities. */#define portYIELD() do{ WDTIIF = 1; } while (0)#define portYIELD_FROM_ISR( xHigherPriorityTaskWoken ) if( xHigherPriorityTaskWoken ) portYIELD()#define portNOP() __nop()
src/FreeRTOS/Source/portable/Renesas/RL78/port.c今回の改変後: 赤文字行を追加
BaseType_t xPortStartScheduler( void ){ /* Setup the hardware to generate the tick. Interrupts are disabled when this function is called. */ configSETUP_TICK_INTERRUPT(); /* Setup the hardware to generate the yield interrupt. */ WDTIPR1 = 1; WDTIPR0 = 1; WDTIMK = 0; /* Restore the context of the first task that is going to run. */ vPortStartFirstTask(); /* Execution should not reach here as the tasks are now running! */ return pdTRUE;}
src/FreeRTOS/Source/portable/Renesas/RL78/portasm.asm今回の改変前:
_vPortYield: portSAVE_CONTEXT ; Save the context of the current task. call@ _vTaskSwitchContext ; Call the scheduler to select the next task. portRESTORE_CONTEXT ; Restore the context of the next task to run. retb
_vPortYield .VECTOR 0x7E
今回の改変後: 赤文字行を変更
_vPortYield: portSAVE_CONTEXT ; Save the context of the current task. call@ _vTaskSwitchContext ; Call the scheduler to select the next task. portRESTORE_CONTEXT ; Restore the context of the next task to run. reti
_vPortYield .VECTOR 0x04
以下、今回のタスク側のプログラムです。先ほど書いた通り、今回は各タスクのポート出力をトグルさせる部分をクリティカルセクションとして扱うことしていません。(なお、src/frtos_config/FreeRTOSConfig.hの#define FREERTOS_USER_MAIN 0の行で0→1の変更を行うことで以下が実行されるようになります。)src/user_main.c
void main_task(void *pvParameters){ (void) pvParameters; while (1) { P1_bit.no6 = !P1_bit.no6; }}void second_task(void *pvParameters){ (void) pvParameters; while (1) { P1_bit.no5 = !P1_bit.no5; }}void third_task(void *pvParameters){ (void) pvParameters; while (1) { P1_bit.no4 = !P1_bit.no4; }}void intp3_task(void *pvParameters){ (void) pvParameters; R_INTC3_Start(); LED_INIT(); while (1) { xSemaphoreTake( xSemaphoreINTP3, portMAX_DELAY ); LED_BIT = !LED_BIT; }}
[追記]MOTファイルsim_rl78_freertos_full_demo_ccrl_c_csplus_20190705_mot.zip 2019/07/12追加sim_rl78_freertos_full_demo_gnurl78_c_e2v740_20190705_mot.zip 2019/07/24追加
こんにちは。NoMaYです。Call Walker (CC-RL+CS+)で見積もったスタック使用量の妥当性を検証してみました。なお、そもそも(RTOS未使用時においても)Call Walker (CC-RL+CS+)によるスタック使用量の見積もりは適切ではないですよね。あまりに当たり前過ぎて、皆さん不問に付しているのだとは思うのですが、(少なくとも多重割り込みを使用していない場合において、)必要スタックサイズの値は以下ではないかと思うのですが、その点が考慮されてませんよね、、、本来の必要スタックサイズの見積もり(多重割り込みを使用していない場合):必要サイズ = Cスタートアップルーチンから数えて通常処理で最も深くなるサイズ + 割り込み処理の内で最も深くなるサイズCall Walker (CC-RL+CS+)での必要スタックサイズの見積もり:必要サイズ = 通常処理も割り込み処理も区別することなく、単に最も深くなるサイズですので、私も、そうした点は手計算するものとして検証しました。ですが、検証といっても、大したことをする訳でもなく、デバッガ上で、ブレークポイントを設定してブレークさせたり、ステップ実行させたりして、最も深くなる位置でのSP(スタックポインタ)の値をウォッチウィンドウで確認してみただけです。また、しらみつぶしに確認した訳ではなく、事例を絞って確認しました。あと、ルネサスRL78シミュレータで確認しました。確認結果:(A) 見積もりと実動作は概ね一致ただし(B) 関数/サブルーチンの最後のCALL命令をBR命令に置き換える最適化が行われた箇所(および、CALL命令をBR命令で置き換えた後の分岐先が連続していたのでBR命令さえ削除された箇所)の影響により実動作の方がサイズが小さかった(SPの値としては大きかった)という不一致あり(実害は無い)また(C) 所望の実行経路を今回のサンプルプログラムですぐに実現することが出来なかったので断念したものありさらに(D) スタック渡し引数で使用されるスタックが、呼び出し元の関数に計上されるのか、呼び出し先の関数に計上されるのか、調べてみたところ、(少なくともCC-RL V1.02では)呼び出し元の関数に計上されていたので、このことは見積もりの方がサイズが大きくなる方向に作用すると思います確認事例:(1) main_task(レジスタ渡し引数有り+戻り番地+自動変数無し、関数/サブルーチン呼び出し無し)での確認 ⇒ 一致(2) r_intc0_interrupt(割り込み処理)から数えて最も深くなる場所での確認 ⇒ 一致(3) _start(Cスタートアップルーチンのアセンブラ)から数えて通常処理で最も深くなる場所での確認 ⇒ 不一致(実害無し)(4) _vPortYield(FreeRTOSポートレイヤのアセンブラの割り込み処理の1つ)から数えて最も深くなる場所での確認 ⇒ 一致(5) _vPortTickISR(FreeRTOSポートレイヤのアセンブラの割り込み処理の1つ)から数えて最も深くなる場所での確認 ⇒ 断念(6) スタック渡し引数がある関数を記述してmain_taskから呼び出してみた時の例また、今回、確認がやり易くなるかと考えて、各タスクの優先順位を以下の通りに変更してみました。(実質、main_task実行中にr_intc0_interruptが実行されるケースが多くなる、ようにしてみました。)src/frtos_startup/freertos_object_init.c (赤文字箇所を変更)
void Kernel_Object_init (void){ ...略... xTaskCreateStatic_R_Helper( main_task, "MAIN_TASK", pdBYTES_TO_STACK_DEPTH(1024), NULL, 1, NULL ); xTaskCreateStatic_R_Helper( task_LED0, "task_LED0", pdBYTES_TO_STACK_DEPTH(1024), NULL, 0/*2*/, NULL ); xTaskCreateStatic_R_Helper( task_LED1, "task_LED1", pdBYTES_TO_STACK_DEPTH(1024), NULL, 2, NULL ); xTaskCreateStatic_R_Helper( task_CONIO, "task_CONIO", pdBYTES_TO_STACK_DEPTH(1024), NULL, 0/*3*/, NULL); ...略...} /* End of function Kernel_Object_init()*/
ちなみに、mapファイルで調べたmain_taskのスタック領域は 0x0f3f5a ~ 0x0f435a となっていました。あと、_startのスタック領域は 0x0f510e ~ 0x0ffe20 となっていました。DefaultBuild/freertos_sampleprog2_ccrl_c.map
FILE=DefaultBuild\freertos_object_init.obj 000f3f5a 000f5011 10b8 _main_task_xStackBuffer@1@Kernel_Object_init 000f3f5a 400 data ,l 1 _main_task_xTCBBuffer@2@Kernel_Object_init 000f435a 2e data ,l 1
Absolute value symbolsFILE=rlink_generates_05... __STACK_ADDR_START 000ffe20 0 none ,g 1 __STACK_ADDR_END 000f510e 0 none ,g 1
以下、それぞれの場合のCS+のルネサスRL78シミュレータでの実行結果の画面コピーです。なお、今回、Call Walkerの表示設定は以下の通りに変更しています。あと、CS+のビルドツールのプロパティを変更して、アセンブラソースを出力させるようにしています。[View]→[Show Used Stack]をチェック[View]→[Show All Symbols]をチェック[アセンブリソースファイルにコメントを出力する]を[いいえ]に設定(デフォルト設定) (画面コピーの大きさを少し小さくする為)[アセンブリソースファイルを出力する]を[はい(-asm_path)]に設定(1) main_task(レジスタ渡し引数有り+戻り番地+自動変数無し、関数/サブルーチン呼び出し無し)での確認SP期待値: 0x0f4356 (0x0f435a - 4)SP確認値: 0x0f4356 ⇒ OK(一致)(2) r_intc0_interrupt(割り込み処理)から数えて最も深くなる場所での確認SP期待値: 0x0f432e (0x0f435a - 4 - 40) : 4 = main_taskでの使用量、40 = r_intc0_interruptでの使用量SP確認値: 0x0f432e ⇒ OK(一致)(3) _start(Cスタートアップルーチンのアセンブラ)から数えて通常処理で最も深くなる場所での確認SP期待値: 0x0ffe10 (0x0ffe20 - 16)SP確認値: 0x0ffe18 ⇒ △(不一致だが実害無し)(4) _vPortYield(FreeRTOSポートレイヤのアセンブラの割り込み処理の1つ)から数えて最も深くなる場所での確認SP期待値: 0x0f4344 (0x0f435a - 4 - 22) : 4 = main_taskでの使用量、22 = _vPortYield割り込みでの使用量SP確認値: 0x0f4340 ⇒ OK(一致)(5) _vPortTickISR(FreeRTOSポートレイヤのアセンブラの割り込み処理の1つ)から数えて最も深くなる場所での確認SP期待値: 0x0f4330 (0x0f435a - 4 - 38) : 4 = main_taskでの使用量、38 = _vPortTickISR割り込みでの使用量SP確認値: Unknown(所望の実行経路を今回のサンプルプログラムですぐに実現することが出来なかったので断念)(6) スタック渡し引数がある関数を記述してmain_taskから呼び出してみた時の例スタック渡し引数で使用されるスタックは呼び出し元の関数のスタックサイズに計上されていました。このことは見積もり値の方が大きくなる方向に作用すると思います。main_task.c
uint16_t foo3(uint16_t a, uint16_t b, uint16_t c);uint16_t foo4(uint16_t a, uint16_t b, uint16_t c, uint16_t d);uint16_t foo3(uint16_t a, uint16_t b, uint16_t c){ return a + b + c;}uint16_t foo4(uint16_t a, uint16_t b, uint16_t c, uint16_t d){ return a + b + c + d;}uint16_t a, b, c, d;volatile uint16_t r;void main_task(void *pvParameters){ INTERNAL_NOT_USED(pvParameters); while(1) { r = foo3( a, b, c ); r = foo4( a, b, c, d ); }}
main_task.asm
_foo3: .STACK _foo3 = 4 addw ax, bc addw ax, de ret_foo4: .STACK _foo4 = 4 movw hl, ax movw ax, [sp+0x04] addw ax, de movw de, ax movw ax, hl addw ax, bc addw ax, de ret_main_task: .STACK _main_task = 6.BB@LABEL@3_1: ; bb11 movw de, !LOWW(_c) movw bc, !LOWW(_b) movw ax, !LOWW(_a) call $!_foo3 ⇒ ここから先の呼び出しが深くなった時にスタック使用量に +2 のゲタが発生する movw !LOWW(_r), ax movw ax, !LOWW(_d) push ax movw de, !LOWW(_c) movw bc, !LOWW(_b) movw ax, !LOWW(_a) call $!_foo4 ⇒ ここから先の呼び出しではそのようなゲタは発生しない pop hl movw !LOWW(_r), ax br $.BB@LABEL@3_1