« Arduino-UNO 割り込み処理のミスあれこれ:計時処理 | トップページ | 「スーパー玉出」の親会社が・・・ »

2022年3月11日 (金)

Arduino-UNO 割り込み処理のミスあれこれ:ロータリーエンコーダー

Arduino-UNO 割り込み処理のミスあれこれ:計時処理に続いて、
今度はロータリーエンコーダーの処理をあれこれと。

「Arduinoのライブラリを使ってカウントしよう」という場合は
問題ないでしょう。
割り込みを使ってのカウント、そのデータ読み出しのルーチンを
見るとちゃんと割り込み禁止状態で4バイトのカウントデータを
取り出しています。
   (端折って)
inline int32_t read(){  読み出し処理
 noInterrupts();   割り込み禁止で 
 int32_t ret = encoder.position; カウント値
 interrupts();     割り込み有効に戻して
 return ret;  カウント値4バイトを持ってリターン
}

ところが、エンコーダの処理を自前でされているスケッチが
気になるのです。

割り込みでカウント値を増減するのはかまいません。
問題はその読み出しです。
割り込みで処理させるwordデータの扱いに引っかかるのです。

Arduino-UNOのマイコンATmega328Pは、RAM領域にある
データを1バイト単位でしか読み書きできません。
   (8bitマイコンですから)
wordデータなら2回、longデータなら4回に分けて読み書きが
行われます。
多バイトデータ読み出しの間に割り込みが入ると、読み出し途中
の値が変わってしまうことがあります。

例えば、単純なカウントアップだと、
「0x12FF」の下位バイト「FF」を読み出した次のタイミングで
割り込みが入ると、データが+1されて「0x1300」になります。
次のステップの上位バイトの読み出しでは「13」が現れます。
0x12FF」あるいは「0x1300」が出て欲しいのに
0x13FF」という間違った値になってしまいます。

連続して読み出しているはずですが、1バイト単位でしか読み出し
できなので、こんなことが起こるのです。
割り込みで多バイトデータを扱う時は、読み書きのシーケンスが
分断されないよう割り込み禁止にしなければなりません。
  ※この場合↑、割り込みがない状態での次の読み出しでは
   「0x1300」と正しい値が出てきます。
   しかし、一瞬だけ出現する誤った「0x13FF」が
   命取りになる制御もあるのです。

書き込む場合も同じです。  (カウントダウンにして書き直し)
現カウント値が「0x1234」だったのを「ゼロクリアー」してみます。
先に下位バイトを「00」にした瞬間、カウントダウン割り込みが入ると、
いったん「0x1200」になったカウント値が「0x11FF」になります。
その直後、上位バイトに「00」を書くので、結果は「0x00FF」と
なり、思っていたプリセット値(0x0000)になりません。
この場合も割り込み禁止の手順は省けません。

ロータリーエンコーダのカウント値、いろんな例では
intあるいはlongにしている場合がほとんどなので、
この注意が必要です。

ただ、
カウント値をシリアル出力しているだけ」や
液晶に表示しているだけ」なら、計数割り込みと輻輳しても
次のタイミングで正しい値が出力、表示されるので、
異常があっても気付かないわけです。

しかし、カウント値を位置情報として制御に使った時は
たまにおかしくなる」というバグの原因になります。

位置制御だと「ここで止まるはずが・・・あれれ?」でしょうか?

わずかなことですが、最初からこれを頭に入れてプログラムを
組まなければ、見つけにくいバグを混入させてしまいます。

  ※プリセットの場合は、割り込みよるカウント操作を
   いったん止める処理でしょうか。

  ※また、プリセット処理の場合、割り込みとの競合で生じた
   結果はずっと残ってしまいます。
   読み出しだと、次のサイクルには正常値が出てくるんで、
   気付きにくいかもしれませんが、書き込む場合はミスした
   結果が残るので「あれれ?」っとミスを発見できる可能性
   が出てきます。
   しかし、このタイミングが合ってミスが生じるのは
   ほんとに「まれ」。
   「まれ」でも起きる可能性があるなら、いつかはアタリま
   すんで。   (マーフィーの法則っぽいお話し)
   気付きにくいから、最初からちゃんと考えておかないと、
   という次第です。   


▲割り込み処理のカウント値をそのまま読み出している例
 <カウントをミスるかも>
ArduinoUNOでロータリエンコーダを読む 立ち上がりエッジ読み取り(2021-03-22)
  (コメント書き込み)
Arduinoでロータリーエンコーダを使う(つなぎ方&スケッチ)(2019.12.05)
  (コメント書き込み)
arduinoでロータリーエンコーダ値の読み取り(2016/09/28)
第二十一項 ロータリーエンコーダとノイズ対策・割り込み(電子工作創作表現 2019/12/04)
モーター制御にも通じる!Arduinoとエンコーダーを使ったオモシロIoT工作&プログラムまとめ(2020年10月6日)
  (コメント書き込み、返信頂戴しました)
Arduinoでロータリーエンコーダー (じわじわ進む 2016年12月28日)
  (コメント書き込み、返信頂戴しました)

▲カウント抜けが生じる処理
ロータリーエンコーダテスト 割り込みを使う場合
   ※↑カウント抜けの可能性。
    「m_nValue = 0;」の直前に入ったカウントパルスは
     捨てられてしまいます。
 ※解説
  int R_count0;  ←エンコーダカウント値
  volatile int m_nValue; ←割り込みでup/down
               パルスが無ければ0
  m_nValueに値が入ったらR_countを+/-という処理
  ~~~~~~~~~~~~~~~~~~~~~~~
   if(m_nValue != 0){      ←(1) up/downパルスあり?
    R_count = R_count + m_nValue; ←(2)カウント値を+/-
    m_nValue = 0;      ←(3)up/downパルスをゼロに
   }
  ~~~~~~~~~~~~~~~~~~~~~~~  
  もし、(2)と(3)の間にエンコーダカウント割り込みが入って
  m_nValueの値が+/-されたとすると・・・
  (3)でクリアされてしまって、パルス抜けが発生します。
  (2)と(3)は割り込み禁止状態で実行しなければなりません。
  short,、longデータを扱える16bit、32bitマイコンでも
  発生します。

  (1)と(2)の間に割り込みが入って、m_nValueの値が
  変わっても問題なしです。ゼロになってもOK。
  (2)(3)間が割り込み禁止状態なら、その間のカウントは
  (3)の次でm_nValueが確定して抜けることはありません。


▲割り込み禁止処理を入れて正しく解説
ロータリーエンコーダ(PIC AVR 工作室)


▲補足「カウント抜けが生じる処理」に関して
これ、カウンタup/down計数だけでなく、「割り込みでのデータ転送」
にも絡んできます。 (例えばシリアル通信)

(1)は「新たなデータが来たか?」のチェック。
 割り込みで受けたデータ数をチェックして、データが来てたら、
 その数だけ別のバッファにデータをコピーするような処理を
 想像してください。

(2)は受けたデータの数だけ、受信データをコピーする操作。
  割り込み用のバッファから、受けた数だけ処理用のバッファへ
  転送します。
  
(3)は次のデータを待つためにデータ数をゼロにという処理。

もし、(2)と(3)の間に割り込みが入って新たなデータが来てたら・・・
割り込み禁止にしていないと(3)でデータ数がクリアされ、せっかく
受けたデータを喪失してしまいます。
これは、8bitマイコン特有の問題ではありません。

割り込みとの競合、ちゃんと考えておかないと、見つけにくいバグが
生じてしまいます。

※次は
Arduino-UNO 割り込み処理のミスあれこれ:「cnt+n; n=0; 」での抜けを確かめる


|

« Arduino-UNO 割り込み処理のミスあれこれ:計時処理 | トップページ | 「スーパー玉出」の親会社が・・・ »

Arduino」カテゴリの記事

重箱の隅」カテゴリの記事

コメント

ロータリーエンコーダの記事、ご紹介ありがとうございます。

投稿: nekosan | 2022年3月23日 (水) 17時20分

コメントを書く



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




« Arduino-UNO 割り込み処理のミスあれこれ:計時処理 | トップページ | 「スーパー玉出」の親会社が・・・ »