PIC MPLABX harmony SYS_TMRでコールバックが呼ばれない!

こんばんは、ファルコン 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 の作りで以下の処理を行っている。

  1. Test2 タスク : CASE2 でTest2 のタイマーハンドルを無効化する。
  2. Test1 タスク : CASE1 で先ほど無効化したタイマーハンドルを有効化する。
  3. 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_CallbackPeriodicSYS_TMR_CallbackSingleSYS_TMR_DelayMS 関数は使用しない

 

これらの関数では、内部でSYS_TMR_ObjectCreate 関数を呼び出している。そしてその関数で、SYS_TMR_FLAG_AUTO_DELETE を指定している。

1つ前で書いたことと同じことをすることになる。

これらの関数を呼び出してはいけない。

 

4. SYS_TMR_ObjectDelete 関数は使用しない

 

一度取得したハンドルを、SYS_TMR_ObjectDelete 関数で無効化してはいけない。

プログラム実行中はそのハンドルをずっと保持しておこう。

 

まとめ

 

SYS_TMR を使うと、コールバックが呼ばれない現象が発生した。

その原因は、他のモジュールでタイマーハンドルを無効化していたから。

harmony で生成したsdカードや、自分のモジュールでもタイマーの無効化には気をつけよう。

 

 

スポンサードリンク



コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です