こんばんは、ファルコン M です。
PIC MPLABX harmony でSystem Service のTimer を使用しているが、動作が安定しない、上手くできない、コールバックが呼ばれない?思っている人
harmony でSYS_TMR を使っていたが、タイムアウトのコールバックが呼ばれない現象が発生する。
SYS_TMR_ObjectCreate 関数で正しく設定しているはずなのだが、コールバック関数が呼ばれない。ときどき呼ばれないようだ。
全く分からず、
4、5時間くらいはまって、
harmony のソースコードを調査して、
やっと理解できた。
SYS_TMR_ObjectCreate 関数は、使い方工夫しなければならない。そうしないと、コールバックが呼ばれない現象が発生する。
ハンドルが意図しないところで無効化される
SYS_TMR_ObjectCreate 関数でタイマーハンドルを有効化したのだが、それが意図しないところで無効化されていることが分かった。
まず、以下のコードを見てほしい。
Test1 タスク
// メイン関数からの定期呼び出し処理 static void test1Tasks( void ) { switch( state ) { case CASE1: { test1Data.handle = SYS_TMR_ObjectCreate( 1000, 0, callbackTimer, SYS_TMR_FLAG_SINGLE ); break; } } } static void callbackTimer( uintptr_t context, uint32_t currTick ) { return; }
定期呼び出し処理で、SYS_TMR_ObjectCreate 関数でタイマーオブジェクトを有効化し、ハンドルを取得している。
1秒(1000ms)後、callbackTimer 関数が呼ばれる。
使い方としては問題なさそうに見える。
しかし、このやり方だと、callbackTimer 関数が呼ばれないときがある!
それは、別タスクでtestData.handle を無効化する可能性があるから!
次に以下のコードをみてほしい。
Test2 タスク
// メイン関数からの定期呼び出し処理 static void test2Tasks( void ) { switch( state ) { case CASE1: { test2Data.handle = SYS_TMR_ObjectCreate( 1000, 0, callbackTimer2, SYS_TMR_FLAG_SINGLE ); state = CASE2; break; } case CASE2: { SYS_TMR_ObjectDelete( test2Data.handle ); state = CASE3; break; } case CASE3: { SYS_TMR_ObjectDelete( test2Data.handle ); break; } } } static void callbackTimer2( uintptr_t context, uint32_t currTick ) { return; }
先ほどと別のタスクで、定期呼び出し処理でSYS_TMR_ObjectCreate 関数でタイマーオブジェクトを有効化し、ハンドルを取得している。
その後、CASE2 、CASE3 でSYS_TMR_ObjectDelete 関数を呼び出し、ハンドルを無効化している。
では、Test1タスクとTest2タスクを同時に実行することを考える。
このとき、Test2 のCASE2 が呼ばれた直後にTest1のCASE1 が呼ばれると、Test1 のコールバックが返らないことになる。SYS_TMR の作りで以下の処理を行っている。
- Test2 タスク : CASE2 でTest2 のタイマーハンドルを無効化する。
- Test1 タスク : CASE1 で先ほど無効化したタイマーハンドルを有効化する。
- Test2 タスク : CASE3 でTest1 のタイマーハンドルを無効化する。
このような作りになっている。図で表すと以下のようになる。
Test2 タスクでTest1 のタイマーハンドルを無効化していたので、コールバックが呼ばれないという現象が発生した。
そして、このようなことをやっているモジュールがいた!
drv_sdcard.c
ここの処理で、SYS_TMR_CallbackStop 関数を呼んでいる。つまり、SYS_TMR_ObjectDelete 関数を呼んでいる。ここのモジュールがSYS_TMR_ObjectCreate 関数とSYS_TMR_ObjectDelete 関数 を交互に呼んでいる。
そのため、偶然のタイミングで、上記ケースの現象が起こっていた。
SYS_TMR を上手に使う方法
さて、ではどうすればいいのか?
基本的な方針として、
- SYS_TMR_ObjectCreate 関数で取得したハンドルは、プログラム実行中は保持しておく。
- タイマーの再設定は、SYS_TMR_ObjectReload 関数を使用する。
と言ったところ。
以下にサンプルを書く。
// メイン関数からの定期呼び出し処理 static void test1Tasks( void ) { switch( state ) { case INIT: { // ハンドル取得用( タイムアウト処理は何もしない ) test1Data.handleSig = SYS_TMR_ObjectCreate( 10, 0, callbackTimer, SYS_TMR_FLAG_SINGLE ); test1Data.handlePer = SYS_TMR_ObjectCreate( 10, 0, callbackTimer, SYS_TMR_FLAG_PERIODIC ); break; } case CASE1: { // タイマーを再設定 SYS_TMR_ObjectReload( test1Data.handleSig, 1000, 0, callbackTimerSig ); SYS_TMR_ObjectReload( test1Data.handlePer, 2000, 0, callbackTimerPer ); break; } } }
ポイントは、以下の4つ。
1. SYS_TMR_ObjectCreate 関数はInit 時に実行する
そのタスクで使用するタイマーハンドルは、Init 時に取得する。
drv_sdcard.c などは、タイマーを使用するときにSYS_TMR_ObjectCreate 関数を実行をしている。
なので、その前に、SYS_TMR_ObjectCreate 関数を実行する。
その時ハンドルを取得してしまえば、drv_sdcard.c ではそのハンドルを取得できない。
2. SYS_TMR_ObjectCreate 関数を使うとき、SYS_TMR_FLAG_AUTO_DELETE を指定しない
SYS_TMR_ObjectCreateの第4引数にSYS_TMR_FLAG_AUTO_DELETE を指定してはいけない。
これを指定すると、タイムアウトした後、自動的にSYS_TMR_ObjectCreate 関数と同じ処理が実行されるから。
もう一度タイマーを使うときは、SYS_TMR_ObjectCreate 関数を呼び出さなければいけない。
3. SYS_TMR_CallbackPeriodic、SYS_TMR_CallbackSingle、SYS_TMR_DelayMS 関数は使用しない
これらの関数では、内部でSYS_TMR_ObjectCreate 関数を呼び出している。そしてその関数で、SYS_TMR_FLAG_AUTO_DELETE を指定している。
1つ前で書いたことと同じことをすることになる。
これらの関数を呼び出してはいけない。
4. SYS_TMR_ObjectDelete 関数は使用しない
一度取得したハンドルを、SYS_TMR_ObjectDelete 関数で無効化してはいけない。
プログラム実行中はそのハンドルをずっと保持しておこう。
まとめ
SYS_TMR を使うと、コールバックが呼ばれない現象が発生した。
その原因は、他のモジュールでタイマーハンドルを無効化していたから。
harmony で生成したsdカードや、自分のモジュールでもタイマーの無効化には気をつけよう。
コメントを残す