自作タイマ関数で時々起動しない時がある

他のスレッドで「H8/36094:IRQ0の処理が起動しないことがある」というのを見かけましたが

SH7670で下記のようなソースで自作関数を作成しています

ソースここから→

///////////////////////////////////////////////////////////////////////
//タイマを設定する
TIME_PROC tproc[]={
{0,0},
{0,0},
{0,0},
{0,0},
{0,0},
{0,0},
{0,0},
{0,0},
{0,0},
{0,0},
};
BOOL bWaitTimer=FALSE;
BOOL WaitTimer(int msec,void (*pc)())
{
int i;
CMT.CMSTR.BIT.STR1=0;//タイマ停止
for(i=0; i<10; i++){
if(tproc[i].msec ==0){
tproc[i].msec =msec;
tproc[i].pc =pc;
//1m Sec タイマ開始
if(!bWaitTimer){
bWaitTimer=TRUE;
cpu_ms1_start //タイマを開始させるマクロ
}
CMT.CMSTR.BIT.STR1=1;//タイマ開始
return TRUE;
}
}
//登録できない
return FALSE;
}
///////////////////////////////////////////////////////////////////////////////////
//1mSインターバルタイマ
void cmi1_(void)
{
BOOL flg;
int i;
void (*pc)();
CMT.CMCSR1.BIT.CMF &= 0;
CMT.CMSTR.BIT.STR1=0;
flg=FALSE;
for(i=0; i<10; i++){
if(tproc[i].msec > 0){
tproc[i].msec--;
if(tproc[i].msec == 0){
pc = tproc[i].pc;
tproc[i].pc=0;
pc();
}
}
if(tproc[i].msec > 0)flg=TRUE;
}
if(!flg){
bWaitTimer=FALSE;
return;
}
CMT.CMCSR1.WORD=0;
CMT.CMCNT1.WORD=0;
CMT.CMCOR1.WORD=1000;
CMT.CMCSR1.BIT.CKS=1;//11:Pφ/512
CMT.CMCSR1.BIT.CMIE=1;
CMT.CMSTR.BIT.STR1=1;
cpu_ms1 ++;
}
///////////////////////////////////////////////////////////////////////
//タイマ関数 end
///////////////////////////////////////////////////////////////////////

→ソースここまで、これを使用するには

void test(void)

{

}

に飛ばしたい場合に

WaitTimer(100, &test );

等とすると、100ミリ秒後にtest()が実行されるという仕組みですが

どうやらたまに実行されない場合があるということで、なにが原因なのか思案しています

アドバイスお願いできませんでしょうか?

Parents
  • cmi1_(1msインターバルの割り込み関数)が常時動作しているとしたら、
    WaitTimer関数での処理中に割り込まれると、状態がおかしくなる可能性があります。
    WaitTimer関数での処理中は割り込み禁止にした方が良いと思います。
  • Higetakaさん、アドバイスありがとうございます
    遅延関数登録後にはWaitTimer関数が常時動作いたします、これを止めますとミリ秒カウントしなくなるのではと思いますが?というのがその中でtproc[i].msec--;if(tproc[i].msec == 0){をやっていまして割り込み中にマイナスしていき0になった時に遅延関数実行して登録削除のようにしています。
  • 現状のコードでは
    「合計で1msを超えれば、数回の割り込みがこの後で発生するかもしれません。」
    というのは、ないかもしれないと思えてきました。
    (割込み側でタイマを停止してから関数呼び出しをしているので)

    今のコードの延長でも、資源管理をしっかりすれば、なんとかなると思いますよ。
    管理するべき資源はタイマ(CMT)とテーブル(tproc)です。
    WaitTimerは急に割り込まれても大丈夫なように割り込み禁止でガードをかけるのがポイントです。
    (但し、禁止期間はなるべく短くしたいものです。他の緊急な割り込みが遅れたりする事があるので)

    今の延長で私がデザインするとしたら、以下のような疑似コードにします。

    // IKUZOさんのコードとほぼ同じ
    // 但し、CMTの制御はStartだけ行う。
    WaitTimer()
    {
      ★ タイマ停止は行わない
      for (i) {
        if (tproc[i]が未使用) {
          ★ テーブル&CMT制御は割り込みに邪魔されないようにする。
          disable_irq
          テーブル登録
          if (CMT停止中) {
            CMT Start
          }
          enable_irq
        }
      }
      ★ タイマ再開は行わない
    }

    // IKUZOさんのコードとほぼ同じ
    // 但し、CMTの制御はStopだけ行う。
    cmi1_()
    {
      ★ タイマ停止は行わない
      waiting = false;
      for (i) {
        if (tproc[i].msec > 0) {
          tproc[i].msec--;
          if(tproc[i].msec == 0){
            func呼び出し
          }
        }
        if (tproc[i].msec > 0)
          waiting = true;
      }
      ★ 有効な登録がなければ停止する
      if (!waiting) {
        CMT Stop
      }
      ★ タイマ再開は行わない
    }

  • わわいさん
    なにか別のいい方法て、ご存知ないでしょうか?
  • Higetakaさん
    いろいろ考えていただき、感謝します、ご提案の内容を早速組み込んで評価してみたいと思います。
  • わわいです
    8bitCPUの時代から使っているタイマルーチン群です
    1msのタイマ割り込みルーチンの中では32bit変数をインクリメントさせているだけです
    timeout_set関数で、タイムアウト時間を設定し、timeout_chk関数で、timeout_setの戻り値を与えてやれば、タイムアウト時間経過していればtrueを返します。
    まあ、32bitCPUではこんなシンプルな形となりますが、16bitCPUや8bitCPUでは、tickcnt 変数の読み込みのところで一工夫必要となります。
    これで、tickcnt インクリメントでオーバーフローする場合、timeout_setでオーバーフローする場合、など、いろいろどうなるか考えてみてください。


    #include <stdint.h>
    // 32bit整数
    typedef uint32_t TIMER_T;

    volatile TIMER_T tickcnt;

    // 1ms タイマ割り込みルーチン
    void INT_TIMER1MS(void)
    {
    tickcnt++;
    // その他の処理
    }


    // タイマ設定
    // time_ms :タイマ設定値(*ms)
    // 戻り値:タイマ値
    TIMER_T timeout_set(int time_ms)
    {
    TIMER_T res;
     res=tickcnt;
     res+=time_ms;
     if(res==0) res++;
     return res;
    }

    // タイムアウト判定
    // timer : タイマ値
    // 戻り値:タイム・アウトしていれば true
    int timeout_chk( TIMER_T timer)
    {
     if(timer==0) return 0;

     timer -= timeout_set(0);
     return ((int32_t)timer)<=0;
    }
  • わわいさん
    フリーランを使用したタイマ処理ですね、簡単そうで理解するのが大変でした、1ミリ秒ですから32ビットであれば0 から 4294967295ですから4,294,967,295は4,294,967秒71582.8分1193.05時間ですから、32ビットで普通の使用条件ならあまり問題にならないと思いますが、かりにこれを64ビットとすればほとんどオーバフローしないですね、タイマ確認関数timeout_chkで時間確認するというのが、これなら安心して使用できますね。
  • わわいです
    このタイムアウト関数は、制限事項が以下の2つです
    ・タイマ設定の上限値は32bitの半分の値
     2の31乗*ms=約24日間 というのがタイマ時間の上限値となります
     また、タイムアウト後約24日間経過するとtimeout_chk関数がfalseを返します
    ・タイマの時間精度は最大+2ms
     タイマ割り込み自体の時間精度は計算に入れない、という条件で
    まー、ふつーに使うぶんには、これらの制限事項は気にする必要はないかなとおもいます

    >かりにこれを64ビットとすればほとんどオーバフローしないですね
    そーすると、タイマの上限値は、(おそらく)人類の生存期間を超えますので、上記の制限事項の一つは消えますね。
    しかし、提示したソースで、単に uint64_t と int64_t に置き換えるだけではダメなので注意しましょう。

    #なにがどーダメかは、あえて書かないでおきますね
  • わわいさん
    32ビットでも「24日」ですから、連続して毎日使用しない分には使えそうですね、また「タイマの時間精度は最大+2ms」もあれば、時間計測でもない限りは十分使用できますね、毎日使用する分には単純に割り込みカウントでは停電でメモリがなくなる可能性があるのでRTCかに持たせた方が良いですよね。
  • LEONです。
    私も以下例のように、超々シンプルなルーチンを使っています。
    分周やインタバルを変えたり、uint型にしたりで、実用用途に合わせます。

    typedef unsigned short int ushort; // 符号無し2Byte
    #define PCLOCK 50000000 // PCLOCK=50MHz
    #define TM10 10 // 10ms インタバルタイマ周期時間
    #define TM_CMCOR (((PCLOCK/8)/1000)*TM10)-1 // =62499 CMT0.CMCORの設定値
    #define TM_1000MS (1000/TM10) // 1000ms
    #define TMC_100US (((TM_CMCOR+1)*100)/10000) // 100us

    volatile static ushort Tm1; // TMカウンタ 10ms精度. ushort型

    //-- 10ms精度のタイマ監視用 --//
    void Int_CMT0(void) // CMT0 インタバルタイマ割込み
    {
    Tm1++; // TMカウンタ更新(ushort型)
    // 0x0000~0xFFFFを永遠に繰り返し
    }

    void Tm1_Start(ushort *ptm1) // 時間計測開始
    {
    *ptm1 = Tm1; // 開始時のTm1値
    }

    ushort Tm1_Check(ushort tm1) // 時間経過チェック
    {
    return((ushort)(Tm1 - tm1)); // 経過時間演算
    }

    <使用例>
     static ushort TmChk;
     Tm1_Start(&TmChk); // 時間計測開始
    :
     if(Tm1_Check(TmChk) < TM_1000MS){ // 1000ms未経過?
    return 等 // 未経過時の処理.他タスク実施等
    }
    経過の処理

    ・CMT0は10msインタバルの設定で初期化時に開始。以降、動作しっぱなし。
    ・メインループ中で、時間計測開始と経過時間チェックを行う。
    ・ushort型なので最大655350ms(約655秒)
    ・開始時のTm1値が 0xFFFE、チェック時のTm1値が 0x0001 の場合、
    0x0001 - 0xFFFE = 0x0003 => 30ms経過
    ・±10ms誤差。1msインタバルなら±1ms誤差。


    実用上、用途によっては上記で十分ではないでしょうか。
    更に精度を上げたい時、以下を追加。10ms未満限定でμsレベルの精度
    が得られます。(メインループが追いつかないけどね)
    //-- 10ms未満の計測用 --//
    void TmC_Start(ushort *ptmC)
    {
    *ptmC = CMT0.CMCNT; // タイマ開始時の CMT0.CMCNT値
    }

    ushort TmC_Check(ushort tmC)
    {
    ushort tmCnt = CMT0.CMCNT;
    if(tmCnt < tmC)
    tmCnt += TM_CMCOR+1;
    return((ushort)(tmCnt - tmC)); // 経過時間演算
    }

    <使用例>
    static ushort TmC_Chk;
    TmC_Start(&TmC_Chk); // 時間計測開始
    :
    if(TmC_Check(TmC_Chk) < TMC_100US) // 100μs未経過?

  • LEONです。
    あは、ちょっと間違えてました。(汗) ごめんなさい。
    ushort tmCnt = CMT0.CMCNT;
    if(tmCnt < tmC)
    tmCnt += TM_CMCOR+1;

    訂正は、あえて書かないでおきますね。
  • LEONさん
    なるほど、これは汎用的なタイマーなのですね、こういうような標準的なものであれば間違いが少なくなるかもしれませんね、是非使用したいと思います。
Reply
  • LEONさん
    なるほど、これは汎用的なタイマーなのですね、こういうような標準的なものであれば間違いが少なくなるかもしれませんね、是非使用したいと思います。
Children
  • いろいろ教えていただきまして、ありがたく思っています、よくよく考えるに、今時の高速CPUの場合1ミリ秒の割り込みメモリーのインクリメントというのがどれほどのCPU負荷になるのか、「それは微々たるものだよ」ということなのかもしれませんが、新たなアイデアが浮かびまして、2つの方法どう思われますでしょうか?
    ●1.CPUのタイマーはたいてい2つの閾値を設定できるので10個の登録をやめて2個にして割り込ませる
     こうすればCPU時間はタイマ設定のみにすることができる
    難しい点:Aがすでに動作中で途中でBを設定できるのか?
    ●2.CPUのタイマーで変数と組み合わせてスケジュールする
    タイマ予約
    ------------------------------------------>
    --------------------->
    のような設定の場合
    タイマセット--------->割り込み/タイマセット------------>割り込み
    このようにすれば------->の期間はハードウェアでカウントされるのでCPU時間を消費せず、変数の数も制限がない、
    難しい点:タイマ予約が頻発する場合に、タイマの再設定(停止/全体の再スケジュール/再設定)を行うことが必要で停止させて設定している時に時間の誤差が発生しないか?同じタイミングで関数コールが発生した場合はどちらかが優先されるので片方は遅れることにならないか?
    ●end
    ご意見等を教えてください。
  • わわいです
    とにかく実際にやってみましょう。
    実際にやってみれば、なにがダメでなにが優れているのかわかってくるかと思います
  • わわいさん
    おはようございます、「実際にやってみましょう」ですね、これの考えがSH2Aで使用していて、作りこんだものの例のごとく信頼性がなくてそのままにしてありました(作成したが使用せず)いま取り立てて必要ということではないので、今やっているRX64Nのファーム開発後にやってみます、ただこれは意外と便利だと思うので利用範囲としては、なにか処理しないといけない関数で長い時間戻って来ない場合があることは誰でも経験しますよね、そのような場合でも特にライブラリーとかドライバーとかタイム制限はどのようにするのですか?と考えた時にこういうタイマーがあるとタイマー制限で処理をキャンセルさせたりできるので、極端な場合はリセットをかけたりして、復旧を図ることができるようになりますので、これは便利じゃないかと。
  • 平行して複数の経過時間の監視をしたいのであれば、タイマ値記憶用の変数
    (TmChk0, TmChk1...)をいくらでも増やせます。
    タイマルーチンはこの変数を参照しているだけですから、タイマ利用側で
    管理すれば良いです。変数が増えてもタイマルーチンの負荷は増えていません。
    使い終わった変数は使い回しもできます。

    <例>
     static ushort  TmChk0;
     static ushort  TmChk1;
     Tm1_Start(&TmChk0);   // Time0計測開始
     Tm1_Start(&TmChk1);   // Time1計測開始
      :
     while(Tm1_Check(TmChk0) < TM_1000MS){  // Time0は1000ms未経過?
      処理A                // yes; 他タスク等
      if(Tm1_Check(TmChk1) >= TM_100MS){  // Time1は100ms経過?
       Tm1_Start(&TmChk1);         // yes; 再度Time1計測開始
       処理B               // 100ms毎の処理
      }
     }
     Tm1_Start(&TmChk0);           // Time0計測開始の再利用
      :
     if(Tm1_Check(TmChk0) < TM_2000MS)
      :
  • LEONさん
    貴重なソースを見せてくださり、ありがとうございます、「変数が増えてもタイマルーチンの負荷は増えていません」なるほどこのようにすればよいのですね、勉強になります、これを一つのタイマーシステムとか構成できるようにしたいと思うのですが、登録部/実行部/削除部とか意外と大規模になりそうな予感がします。
  • またアイデアが一つ浮かびましたので
    忘れないうちに、
    LEONさんが提案してくださった内容をそのまま可能な限り小さなマイクロプロセッサにプログラムしておいて、マルチプロセッサー通信でそのサブマイコンにタイミングを設定して、サブマイコンから元マイコンに割り込みを掛ける、割り込みを受け取ったらマルチプロセッサー通信で設定された分岐先を受け取り、そこへ分岐する、というような、これならCPU負担ゼロですよね。
  • > なにか処理しないといけない関数で長い時間戻って来ない場合があることは.....
    > マルチプロセッサー通信でそのサブマイコンにタイミングを設定して.....

    なかなか戻ってこない処理なので、タイマでキャンセルさせたい。で解決させようの策と推測します。
    長い処理時間がかかることが本質的な問題ですから、私だったらこの関数の処理時間を短くすることを考えます。
    長い処理時間の原因は、たいていループ中に変化条件を待っていることが大半でしょうから、この原因を無くします。
    変化条件で無ければ即リターンさせるとか。再度実施時は、この変化条件の監視から実施できるようにします。(変化条件で無ければ即リターン。その繰り返し)

    で、この遅い問題関数を解決すれば、「タイマでキャンセルさせる処理」が不要になるのでは。
    たとえタイマでキャンセルできたとしても、次なる新たな問題が発覚し、負のスパイラルに突入するのが容易に推測できます。

    遅い問題の関数。このような関数は、処理中は他のタスクを処理させません! 的なシングルタスク思考な関数ですから、マルチタスク性が必然なソフトでは、次々と周りに問題を生じさせる迷惑な関数といえます。
    遅い問題の関数を途中でキャンセルさせることに知恵を絞るよりも、遅い問題の関数を速くするように知恵を絞った方が賢明です。

    ちょっと辛口でしたね。すみません。
  • LEONさん
    辛口のアドバイスありがとうございます、先輩からのアドバイスはためになります、「遅い問題の関数。このような関数は、処理中は他のタスクを処理させません!」全く同感です、「遅い問題の関数を速くするように」そうですね、それができればです、作興自分で作成したもので全て構成できれば言うことはないのですが、ライブラリとハードウェアをあてがわれ作成するような場合、作成先にクレームをつけられない場合等あるのではないんじゃないかと、また割り込み処理中で何とか直ぐに制御を返さないと大変なことになるような場合、ちょっと一発予約を入れて、割り込み終了、後で目的の関数が実行されるというような、意外と便利かと思うのですが、LEONさんはきっとマルチタスクOSで考えておられるので、こんなことは最初から必要ないのかもしれませんね、ただWindows等(linuxでも)自由にいかなるタイミングでも関数を実行させる機能があるため、日ごろ使い慣れているこんなタイマ等が使用できないかと思った次第です。
  • ついでに
    割り込み中で処理するのは、きっと他の割り込みを阻害するので良くありませんよね。
  • > ライブラリとハードウェアをあてがわれ作成するような場合....

    状況は悲惨...なんとか工夫して乗り越えたいところですね。
    タイマを使用しての工夫、シンプルに実現できることを期待します。

    > ちょっと一発予約を入れて、割り込み終了、後で目的の関数が実行....

    後で目的の関数を実行できるくらいなら、無理に割り込み内で実行しなくともメインループ側で実行できるのでは。
    割り込みがトリガならそこで旗立てて、メインで旗チェック、旗下ろして(例の遅い処理を速く改造した)関数を実行。とかね。
    ほんの思いつきなので、適切かどうかわかりません。

    > マルチタスクOS...

    ここ十数年間、OSは使ってないですし、ほぼ忘れました。メインループを速く回す疑似マルチタスク手法がメインです。
    各種割り込みが稼働し、CPUが暇な状態のメインループのループ時間は数μs~数十μs、忙しい処理状態でも数十μs~数百μs(100側に近い)程度です。
    CPUクロック、ソフト規模にもよりますが、メインループが1ms(近く)超えは、私の作成物の中ではアウトのつもりです。

    > 割り込み中で処理するのは、きっと他の割り込みを阻害するので良くありませんよね。

    割り込み処理は極力短く。が原則です。
    処理が長かったり、他の割り込みを阻害....ぐちゃぐちゃになるのは嫌いですよね。

    近くの龍潭寺に竜宮小僧はいるかな