« あー、 | トップページ | USB »

2021年5月16日 (日)

高分解能かつ長周期インターバルタイマ

16ビットを超える処理は32ビットタイマーを使え、って
ことでもあるけれど。実際stm32あたりでも、32ビットまで
実装されているタイマーは限られている。

まあ、lpcみたいにどれもこれも32ビットタイマー、
なんて実装のもあるし。
チップの設計思想なんだろけどね.....


stm32に関しては、沢山タイマーがあっても、そのうち
32ビット実装は1~2個なので、st的にはタイマーの
物理カウンタは16ビットあれば十分でしょ?と言いたいのか。
それ以上の事は、ソフトウエアでどうにでもなるよね?って
メッセージの気もする。。


まあ....まあ、そうなんだけど。


-------


ざっくり、だいたい動く(ときどきおかしい値が出る)んなら
簡単にコーディングできるけど、
ちゃんと動かすには、かなり考えないと出来ないよね....

適当な実装で、ときどきズッコケるタイマー処理コード
に付き合うことになると、流石にメンタル削れたりもするけどね。

 


現実的に、普通は16ビットの範囲で処理できるように、
プリスケーラーで動作クロックを落として、
タイマーレジスタを素直に設定できる範囲に
持ってきて処理をする。

原理的に時間分解能は下がってしまうが、その辺はさじ加減。

 

例えば、連続的に1Hz~20KHzとかの矩形波を作ろうとすると、
ソフトウエア処理は十分できても、タイマーの
分解能の限界が先に来て、
1Hzを設定値0xffffまででカバーできるように設定すると、
20KHz付近では設定できる周波数が、非常にとびとびになる。
このあたりで考えると、実質28ビットくらいは欲しい処。

 

では、と、プリスケーラ―の値を動的に書き換えると、
その瞬間に、一発だけ変な周期の割込みが発生したりとか、
これまた 上記の”まあ、だいたい動く” 一例となる。

 

これでもいいですよね?とねじ込む技術(キレたり、ゴネたりとか)
の方がお手軽ではあるのだが....

諸々コストを考えると、ねじ込む技術も大いにアリだけど、
ねじ込むばかりだと、段々行き詰っていくのも確か。

 

------


16ビットタイマーがオーバーフローするのを
ソフトウエアでカウントして、目的のタイミングだけで、
処理を回すようにするとよいのだが。

 


#define TIMERCONST 200000;

static unsigned long buff = TIMERCONST;


void TIM2_IRQHandler(void) {

  if (TIM2->SR & (1 << 1)) {
    TIM2->SR = ~(1 << 1);

    if (buff == 0){
      buff = TIMERCONST;
      // ここでインターバル処理など
    }

    if (buff > 0x10000){
      buff -= 0x10000;
      // ここでタイマー出力禁止(コンペアマッチで出力をする場合)
    }
    else {
      TIM2->CCR1 += buff;
      buff = 0;
      // ここでタイマー出力許可(コンペアマッチで出力をする場合)
    }
  }
}

 

とまあ、こんな感じで記述すると、16ビットを超える
タイマー定数でのインターバル処理を実現できる。

 

 

この記述にも問題があって、buff値が
非常に小さくなると、CCR1の更新が間に合わなくなり、
カウンタ1ループ分、処理が遅れるタイミングが
できてくる。
(例えば初期値0x20001とか)

これはハードウエアの割込み処理応答に起因するので、
このままでは回避できず、処理間隔として上手く動かない
値が存在しているということになる。

簡単にソフトウエアで処理するとすれば、
この付近の値が設定されないように
事前にチェックし、近くて安全な値に変更する、
などが考えられる。

割込み応答や、優先順位の高い処理がどの程度あるか
によって、うまく動かない定数の範囲が変わっていくのだけど
DMAなんかも動かしているようなシステムだと、
どの程度まで余裕を見ればいいかの判断が難しい。


絶対安全、だからと言って、ハードウエアで
動かしているタイマーが数msecもズレる
ようだと、流石に意味をなさない。

長周期で動かしているインターバル処理を
成立させるために、
許容できる応答時間が非常に短いとか、
システムとしてはかなりダメな気がする。


------


なので、もう一工夫して、

TIM2->CCR1 += buff; 

のbuffの値が小さくならないような処理を追加する。


void TIM2_IRQHandler(void) {

  if (TIM2->SR & (1 << 1)) {
    TIM2->SR = ~(1 << 1);

    if (buff == 0){
      buff = TIMERCONST;
      // ここでインターバル処理など
    }

    if (buff > 0x10000){
      if (buff < 0x20000){
        buff -= (buff / 2);
        TIM2->CCR1 += buff;
      }
      else {
        buff -= 0x10000;
      }
      // ここでタイマー出力禁止(コンペアマッチで出力をする場合)
    }
    else {
      TIM2->CCR1 += buff;
      buff = 0;
      // ここでタイマー出力許可(コンペアマッチで出力をする場合)
    }
  }
}

 

こんな感じにして、最後の引き算の結果が
小さくなりすぎないようにしてやる。

これで、タイマーが誤作動するような危険なタイミングを回避しつつ
必要な割り込み処理の応答も、シビアではなくなる。
(割込み処理の間隔が、0x10000以上から始めた場合、
0x7fff以下にはならない)


この記述で、めでたく16ビットタイマーで、
16ビット超のイベント間隔と時間分解能を両立できる。


つづく

| |

« あー、 | トップページ | USB »

コメント

コメントを書く



(ウェブ上には掲載しません)




« あー、 | トップページ | USB »