こんにちは。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です。UART受信にチョコさんのリングバッファのソースコードを使用する版をデバッグしていて、不具合が存在していたことに気付きました。以下のg_rx_dtnoという変数は割り込み処理で値が更新される変数なのですが、そのことは以下の4箇所で読み出された値が同じ値とは限らないということになります。このg_rx_dtnoという変数はリングバッファ内の受信済みデータ量を示す変数ですが、青文字のところで、要求データ数 number >= リングバッファ内データ量 g_rx_dtno が成立していても、赤文字のところに来るまでに、もしデータを受信してしまっていると number < g_rx_dtno となってしまっている場合があります。その場合には、要求データ数よりも多くのデータを 読み出しバッファ buff へ書き込んでしまいますので、(読み出しバッファを余分に確保していなかったのであれば)、バッファオーバーランが起きてしまいます。実際、以下の画面コピーの通り、起きてしまっているようでした。(表現が微妙なのは、アクセスブレークポイント機能やトレース機能を使おうとすると再現しなくなってしまうのみならず、読み出しバッファの値をチェックするようなif文を追加することでも再現しなくなってしまうという、トホホな状況に陥ってしまっていたからです。(ですので、この部分を対処したことで現象は起きなくなったのですが、デバッグしているプログラムには本当の原因が別にある、という可能性は残っています。))なお、先日投稿したRL78のコード生成機能で生成されたUART受信処理のコードと同様に、この区間でタスク切り替えが発生してしまった場合には他タスクへミリ秒オーダーで行ってしまうかもしれず、そうなると、ずっと先までバッファオーバーランしてしまうこともあるかもしれないと思いました。異常時(時々発生)の画面コピー正常時(期待値)の画面コピーRL78コード生成へのリングバッファ追加japan.renesasrulz.com/cafe_rene/m/sample_program/306RL78のUARTへのリングバッファ追加/UART_3/UART0.c対処前:・ g_rx_dtnoは割り込み処理で値が更新される変数なので、以下の4箇所(太字箇所(黒/青/赤/黒))で読み出された値が同じ値とは限らない・ 青文字で、要求データ数 number >= リングバッファ内データ量 g_rx_dtno でも、赤文字で、number < g_rx_dtno の場合が有り得る・ その場合、要求データ数よりも多くのデータを 読み出しバッファ buff へ書き込んでしまうのでバッファオーバーランが起きてしまう
uint8_t get_blk(uint8_t * const buff, uint8_t number){ uint8_t work; uint8_t cnt; /* 転送用カウンタ */ uint8_t * gp_buff; /* 転送用ポインタ */ work = number; /* 要求データ数をセット */ if ( ( 0 != number ) && ( 0 != g_rx_dtno ) ) { /* 転送データがある場合 */ gp_buff = buff; /* 転送先ポインタを設定 *//* --------------------------------------------------------------------------- 転送データ数を算出----------------------------------------------------------------------------*/ if ( number < g_rx_dtno ) { /* 要求数がデータ量以下の場合 */ cnt = number; /* 転送データ数をセット */ work = 0; /* 残りデータ数は0にする */ } else { /* 要求数がデータ量以上の場合 */ cnt = g_rx_dtno; /* 転送数はバッファ内の全て */ work = number - g_rx_dtno; /* 残りデータ数を算出 */ }/* --------------------------------------------------------------------------- データを転送----------------------------------------------------------------------------*/ for ( ; cnt > 0 ; cnt-- ) { *gp_buff = g_rx_buff[g_rx_rdpt]; /* リングバッファから転送 */ SRMK0 = 1; /* INTSR0との排他制御 */ g_rx_rdpt++; /* 読み出しポインタを更新 */ g_rx_rdpt &= 0x0F; g_rx_dtno--; ←ここは問題は無いです /* データ数を-1 */ SRMK0 = 0; /* INTSR0との排他制御終了 */ gp_buff++; /* 転送ポインタを更新 */ } } return( work );}
対処案: (追加箇所/変更箇所を赤字にしています)
uint8_t get_blk(uint8_t * const buff, uint8_t number){ uint8_t work; uint8_t dtno; /* リングバッファ内データ量 */ uint8_t cnt; /* 転送用カウンタ */ uint8_t * gp_buff; /* 転送用ポインタ */ work = number; /* 要求データ数をセット */ dtno = g_rx_dtno; /* 変数がvolatileなので保持 */ if ( ( 0 != number ) && ( 0 != dtno ) ) { /* 転送データがある場合 */ gp_buff = buff; /* 転送先ポインタを設定 *//* --------------------------------------------------------------------------- 転送データ数を算出----------------------------------------------------------------------------*/ if ( number < dtno ) { /* 要求数がデータ量以下の場合 */ cnt = number; /* 転送データ数をセット */ work = 0; /* 残りデータ数は0にする */ } else { /* 要求数がデータ量以上の場合 */ cnt = dtno; /* 転送数はバッファ内の全て */ work = number - dtno; /* 残りデータ数を算出 */ }/* --------------------------------------------------------------------------- データを転送----------------------------------------------------------------------------*/ for ( ; cnt > 0 ; cnt-- ) { *gp_buff = g_rx_buff[g_rx_rdpt]; /* リングバッファから転送 */ SRMK0 = 1; /* INTSR0との排他制御 */ g_rx_rdpt++; /* 読み出しポインタを更新 */ g_rx_rdpt &= RX_BUFF_SIZE_BITS; g_rx_dtno--; ←ここはそのままです /* データ数を-1 */ SRMK0 = 0; /* INTSR0との排他制御終了 */ gp_buff++; /* 転送ポインタを更新 */ } } return( work );}
以下、デバッグ時の画面コピーです。(いつものようにRL78シミュレータでデバッグしていました。) なお、初めの方で書いた「読み出しバッファの値をチェックするようなif文」とは、以下のことです。(ちなみに、ワーニングが出ますが、今回は放置しました。(CC-RL V1.02ですが。))
if ((*(volatile __near uint8_t *)0xf500e) != 0){ nop();}
CS+シミュレータGUIアクセスブレークポイント機能を使おうとしたら再現しなくなってしまいました(以下は受信オーバーランエラーになった画面)トレース機能を使おうとしたら再現しなくなってしまいました(以下は正常動作している画面)