割り込み処理

2024年4月12日 (金)

I2C液晶のアクセス、割り込みで処理しないようにすると

I2C液晶のアクセス、割り込み処理で遅れる原因らしきもの

I2C液晶の処理と高レートのタイマー割り込みとの競合、
その原因がI2C液晶アクセスでの割り込み処理だという
ことがわかりました。

I2Cアクセスでの最後、I2Cバスを待機状態に戻す
「ストップコンディション」の処理に時間待ちが
入れられていました。
  割り込み処理の中での時間待ちはちょっとなぁ。
そのせいで、こんな具合に抜けや遅れが出たのです。
Aa004

Aa000_20240408135301

そこで、I2C液晶の表示ルーチンを割り込みを使わない方法で
書いてみました。
   詳細(スケッチ例)は、また報告します。

普通は液晶アクセスでの遅れなんて問題にしないでしょうが、
特殊な場合はアカンでぇ!ということで、その解決方法の
一例と見てください。

10μ秒割り込みルーチンの抜けや遅れが無くなりました。
Ff000
Ff001
Ff002

割り込みを使うことで処理が早くなったり便利(次の処理に
早く移れる)になるんなら良いのですが、現状の液晶表示
手順の場合は、割り込み処理にするメリットがありません。

I2Cでの割り込み処理、I2Cアクセスでのエラーが生じた時に
対応しやすいという面は否定しません。
でも、液晶表示の場合はエラーが生じても、一連の表示ルーチン
さえ抜ければ次に進めるという処理でかまわないでしょう。
  ロックせずに「STOP」まで進めれば、なんとかなるかと。

I2Cでのエラー解除、本来はSDA=Hの状態(マスター側がHにする
つもりで)でダミークロックを流し込むという手法を使います。
  ※昔のI2C解説本ではよく見たけれど
現状、そういった処理はしていませんので、せっかくの割り込みが
生かされていません。

I2C簡単そうだけどトラブったときの対策、けっこうやっかいです。
  ノイズが多い環境でスカタンしたら
  そのリカバリーがたいへんなんです。
昔々、マイコンに搭載されたI2C機能を使うのが面倒くさくって、
H/Lパルスを自分で出してI2Cを制御したこともありました。
エラー発生時の手順が自由になりますので。

※参考
ROHMのBR24Lxxx EEPROMデータシートより
https://fscdn.rohm.com/jp/products/databook/datasheet/ic/memory/eeprom/br24lxxx-w-j.pdf

・基本のタイミング
Ep11
・リセット方法
Ep12
・コマンドのキャンセル
   SCLをHにしてSDAをL・H
Ep13

※テストプログラムをアップしておきます。
  ・ダウンロード - test_i2c_lcd_acm0802_2b.zip

ちなみに、超低速2相パルス発生回路 のI2C液晶表示を
wireライブラリを使わないようにしたら(wireの中からtwi
を呼んでいる)、プログラムのサイズが1.8kバイトほど減少。
RAMエリアも200バイトほど減りました。
単一の液晶表示のように、単純に「書くだけ」だとずいぶん
処理が簡単になるというのが理由かと。



| | コメント (0)

2024年4月 7日 (日)

I2C液晶のアクセス、割り込み処理で遅れる原因らしきもの

超低速2相パルス発生回路・ケース入れでの処理遅れ、
I2Cの処理をしているソース「twi.c」をざっと追いか
けてみると、

・TWI(I2C)の割り込み処理
  ISR(TWI_vect)
 この中にtwi_stop()というルーチンが出てくる。

・twi_stop()の処理を見ると、
  _delay_us(10); という時間待ちがある。
     ※実際にこれが動いているのかどうかは不明

・_delay_us()がどこにあるのか探してみると
  util\delay.hの中
・関数のヘッダは void _delay_us(double __us)
  「double __us」って何よ。

・もし、_delay_us(10)で10us遅らせているとしたら、
 オシロでの観察結果と合っている気がする。

割り込み処理の中では時間待ちはするなぁ~
  っと叫びたくなるゾ!

※追記
波形観察できるよう、簡単にテスト。
・タイマー0を停止
   じゃまされないよう
・タイマー1で10us割り込みを作る
   割り込み期間、Hパルスを出力
・I2C液晶を使い1秒間隔で2文字を表示
   カーソル位置指定+2文字書込みの3データ
・10usパルスはいつも見えるはず
・I2Cの割り込みと重なった時にズレが出る

ところが・・・そのズレの量が大。
I2Cのストップコンディションと重なった部分の
割り込みパルスが1つ消失して、その後の1つ
が遅れて出現しました。

Aa000_20240408135301
終端部を拡大 (時間経過でSDA波形が↑のと異なります)
Aa004

I2C(TWI)の割り込みが悪さをしてるとしか考えられない。
割り込みを使わないでI2Cを処理するプログラムを
書かないといけないなぁ。
マスターだけならそんなに難しくないけどついつい
ライブラリを頼ってしまうんで。

もう一度言います!
  『割り込み処理の中では時間待ちはするなぁ~

Wire.cはI2C処理の手順を示すだけで実際の
ハードウェアアクセスはutility/twi.cの中でごそごそ。

※さらに追記
メインループの先頭でLEDポート(PB5)をトグル出力させると
いろんな処理時間が見えてきます。

今回の10usタイマー割り込みと液晶アクセスは
こんな具合。
A1000

一番下のch4の波形がトグル出力。
なにもせず(メインループがヒマ)グルグル回っていると
フルスピードでパルスが出ます。
しかし、何かの処理が長引くとトグル出力が一次停止
します。

液晶アクセス中にメインが回っていないということは、
何のための割り込み処理なの?
っと、思っちゃいます。
割り込み処理してるんだったら、ひとかたまりを
書き終わったら、メインに戻ってきて欲しいところ。
I2Cの送出完了でバッファから読み出し、無くなるまで
順次転送ということをしてくれたら良いのになぁっと。
  シリアル送信なんかはそうなっている。

10us割り込みのところを拡大すると。
A1001

割り込み処理そのものに加えて、その前準備と後始末に
けっこう時間が食われているのが見えます。
割り込み内で出しているパルスの前後に見えない
時間があるということが、メインループで出す
トグルパルスで見えてきます。

| | コメント (0)

2024年4月 3日 (水)

超低速2相パルス発生回路・ケース入れ

2024年3月25日:超低速2相パルス発生回路 の続き。
こんな回路に落ち着きました。
Cw_ccw1b
・電池3本運用。
・秋月の昇圧DC-DCモジュール
   AE-XCL102D503CR-G で5Vを発生。
   ただし、ちょい改造(EN端子のプルアップを外す)
・電源スイッチと操作スイッチを共用。1つだけ。
   長押しで電源のon/off操作。
   短押しでCW/CCW/stop選択。
   このためにあれこれややこしい回路に。(黄色部)
・最低周波数が0.1Hz。
 最高周波数を(無理やり)9990.0Hzに。
・周波数によって設定できる最小桁が変化。
 150Hzまでは0.1Hz。2800Hzまでが1Hz。それ以上が10Hz。
・1.5kHz程度までは、設定値から0.01%内の誤差。
 10Hzステップになる2.8kHzあたりだと0.016%。
 5kHzあたりまで上がると0.03%。
 最高周波数に近づくと分周比が小さくなり誤差が目立ちます。
 0.06%ほどの誤差が生じることがあります。
・例えば9810Hzの設定なら分周比が815で
 出てくる周波数が9815.95Hzとなって+0.061%の差。
 また、9970Hzも9980Hzも同じ分周比802になり
 出てくるのはどちらも9975.06Hz。
 でも、8000Hzの設定は誤差ゼロで出てきます。

バックアップがわりのスケッチ。
  ・ダウンロード - cw_ccw2k1b.txt
    ※inoではなくtxtにしてます。

箱はダイソーで買ったプラケース。
  (電池保管用のものだったか・・・)
Cc11_20240404142901

Cc12_20240404142901

LowBat警報表示
Cc14_20240404142901
Cc15
電池電圧が3Vを切ると、空電池マークと半分電池
マークを交互に点滅表示します。
  重要アラームは点滅させて欲しい
   なんて記事も書いてますし。

内部処理でキツいのが周波数を上げたとき。
最高が9990Hzですので、その2倍の周波数
(トグルさせるので2倍に)でタイマー1の
コンペエマッチ割り込みが発生します。
周期が50μ秒ほどとなって、16MHzのATmega328P
にはなかなかキツい。
この割り込みの他に、システムタイマーであるタイマー0の
オーバーフロー割り込みを止めて、別の周期、
3.90625kHz=0.256msのタイマー割り込みを
使っています。
これの主目的はロータリーエンコーダのチャタリング除去。

2つの割り込み処理のタイミングを見てみます。
   オシロの無限残光モードがありがたい
Cs000

Cs001

B相出力が変化する前にタイマー1のコンペエマッチ
割り込みを終えなければ、2つのパルスを正しい
タイミングで出力できません。

タイマー割り込みの処理時間がけっこう長いので、
これが問題。
シリアル出力すると、この割り込み処理による
遅延も入ってきます。
  ※I2C液晶制御でも割り込み絡むのか?
最高周波数になると、タイミングはなかなか
キビシイです。

ロータリーエンコーダの読み取りタイミング。
Cs003
I2C液晶での文字表示(xxxx.xHzの8文字)に
いがいと時間がかかります。

| | コメント (2)

2024年3月25日 (月)

超低速2相パルス発生回路

実験中の回路の動作検証用のジグで、
ジッタの無い2相パルスが欲しいという
のに答えた回路とスケッチです。

過去、あれこれ2相パルス発生器を作っていますが、
その原発振に使っているのはVCO。
手軽に周波数を可変できますが、安定度は
もう一つ。
周波数カウンタで発振周波数を読むのも面倒だし。
ということで、Arduino UNO R3を使って作って
みました。

タイマー1をCTCモードで使います。
OCR1AレジスタでA相周期を決めて、OC1Aを
トグル出力。
これがA相。

B相は、OCR1Aの半分の値をOCR1Bに設定して
(90度位相のため)OC1Bの出力極性をOC1A割り込み
のたびにトグル(CW、CCWに合わせて)します。

こんな回路です。
タイマー1の「ハード」でパルスを作っているので
ジッタはありませんし、安定した周波数が得られます。

Cw_ccw0
タイマー1のクロック源は、設定周波数が150Hz以上
だと内蔵の16MHzクロック。
周波数が低くなると、外部クロックに切り替えて
タイマー2で作った方形波をクロック源にします。

ケースへの組み込みはまだ。
バラックでの試運転。
C21_20240325151801

設定できる最低周波数が「0.1Hz」。
つまり周期が10秒。
150.0Hzまでは0.1Hzステップで可変できます。
越えると1Hzステップに。

設定できる最高周波数は5kHzまでにしていますが、
2.8kHzを越えたあたりから1Hzステップの可変では
分周器での誤差が目立ってきます。

こんなスケッチです。
  ・ダウンロード - cw_ccw2k1.zip
I2C液晶のライブラリを同梱しています。

ソースファイルだけ。 (.inoを.txtにして)
  ・ダウンロード - cw_ccw2k1.txt

パルスジェネレータ では周期を設定していましたが、
今回のは周波数
  (入力周波数に反応する回路の検証用です)
DDS機能なんかは無いので、
 クロック周波数 ÷ 設定周波数
で、タイマー1の分周比を得ています。
そのため、設定Hzと実際のHzに少々の狂いが
生じます。 (Arduino 発振子の誤差とは別に)
実験中のArduino UNO R3では1000.0Hzに設定
したら1000.074Hzが出てきます。
この末尾の74は発振子の誤差。
999.9Hzにすると999.0753となって、分周比が
整数になることによる誤差が積まれます。
1000.0Hzなら8MHz÷1000で8000.0000が分周比。
999.0Hzなら8008.008008に。
目標周波数で少数以下が変わります。
そして周波数が上がると飛んでしまう設定も
出てきます。
3007Hzも3008Hzも3007.742Hzが出て、
3009Hzになると3008.873Hzになります。
  (実物の試験回路で)
周波数が高くなると分周比が小さくなって、
微妙なところが出せなくなるのです。

それと・・・
周波数を低くした(周期を長く)ときの応答。
0.1Hz→0.2Hzに変えた時、実際の波形が変わるのは
最大で5秒後。
A相波形をトグルするOC1A割り込みの中で次の周期を
設定しているのでこんな動作になります。

トラ技2005年10月号:2相パルス発生器
29年前に製作したツール 2相パルスカウンタ
アナログ入力で2相パルスの周波数と正転逆転を制御する発振器
高速2相パルス発生回路


※アナログコンパレータ入力
Low Batのチェックをしてますんで
  (液晶右下に電池マーク点滅表示)
AIN1/PD7がオープンだとチラチラするかもです。

※なんでこんなめんどくさいことを
タイマーで計時してポートをH/Lさせれば、どんなマイコンでも
2相パルスを作れます。
今回の要望は2相パルスの安定性。
ジッターが出て欲しくなかったのです。
ATmega328の場合、タイマー1(16biit)でのハード的な
波形出力が使えました。

割り込みの中でコンペアマッチの値を書き換えているのは、
出力波形を崩さないため。
OCR1Aをメイン側で書き換えたとき、タイミングとその値で
TCNT > OCR1Aになってしまうと、コンペアマッチの発生
が1周(65536パルス)遅れてしまう可能性があるからです。
低周波ではその影響が目立ちます。
コンペアマッチの直後だと、TCNTはゼロからちょいと進んだ
値に留まっているはずなので、それより大きな値だと
1周抜けは避けられます。

これ、データシートでは「2重緩衝」という表現が使われています。
CTCモードでのOCR1Aはその機能が無効になるのです。
PWMモードでのOCR1Aは2重緩衝されますので、割り込み処理
しなくても自由に周期を変えることができます。
  ただし、ICR1でのTOP値設定では2重緩衝されないでの、
  単純な操作ではコンパエマッチのミスが発生します。
タイマー周期を自由に変えたいときは、データシートよく読ま
ないとスカタンします。

※続き
2024年4月3日:超低速2相パルス発生回路・ケース入れ

| | コメント (0)

2024年3月14日 (木)

割り込みの中でSerial.printを使うと・・・

割り込み処理の中でのdelayやSerila.print,lcd.print
  ↑この続き

割り込み処理の中でSerial.printを使ったら
どうなるのか、
具体的に見てもらいましょう。

こんなスケッチで確認

//   割り込みの中でSerial.printを使うと・・・
//  割込元はINT1 (D3)の↓エッジ
// テストパルス
#define D12_H (PORTB |= (1 << PB4)) // PB4 D12 H/L
#define D12_L (PORTB &= ~(1 << PB4)) // テストパルス
#define D13_X (PINB |= (1 << PB5)) // PB5(LED)トグル出力

/***** INT1↓エッジ *****/
void ISRint1(void)
{
char s[80]; // sprintf用文字バッファ
static uint32_t t, t0, t1, cnt;
D12_H;
t1 = micros(); // us時間
t = t1 - t0; // 経過時間
t0 = t1;
cnt++; // 割り込み回数
if(cnt > 99999999) cnt = 0L; // 8桁max
sprintf(s, "%08ld %8ldus", // 20文字
cnt, t);
Serial.println(s); // CR,LF付加で22文字(約23ms)
D12_L;
}

/***** SETUP *****/
void setup()
{
pinMode(3, INPUT_PULLUP); // INT1
pinMode(12, OUTPUT); // テストパルス
pinMode(13, OUTPUT); // LED
Serial.begin(9600); // シリアル
delay(100);
Serial.println("Test Serial Print");
attachInterrupt(1, ISRint1, FALLING); // INT1 ↓エッジ
}

/***** LOOP *****/
void loop()
{
while(1){
delay(5); // 5msでH/L
D13_X; // LEDをトグル
}
}

・INT1入力の↓エッジで割り込み。
・割り込み内では、
  割り込み回数をカウント。
  micros()を使って、パルス間隔を取得。
  回数と間隔をシリアル出力。
  CRとLFを入れて22文字。 22.92ms
  D12ポートをH/L。 割り込み処理時間が分かる。
・loopでは
  delayで5ms待ち。
  LEDポート(D13)をトグル出力。
  割り込み処理が長引くとこのパルスが止まる。
  正常なら100Hzのパルス。

出力文字数22文字で9600BPSだから約23ms。
この周期以上のINT1パルス入力なら、Serial.printでの
待ちは発生しません。

INT1の周期が23msを切ると、徐々にFIFOが詰まり出し、
割り込みの応答がおかしくなってきます。
割り込み処理にかかる時間が長くなると、他の割り込み、
(今回は計時用のタイマー0のオーバーフロー割り込み)
も影響を受け、正しい計時ができなくなってしまいます。

INT1の周期を22.8msにすると、パルス間隔はこんな具合に
変化します。

~~~~~~~~~~~~~~~~~~~~~
◆22.8ms周期のINT1パルス
 22文字なので文字列は約23msの長さ
Test Serial Print
割り込み回数 間隔
00000001 100172us (初回は不定)
00000002 22788us
00000003 22800us 最初は正常
00000004 22800us
 :
00000335 22800us
00000336 21776us FIFOが詰まり出して
00000337 22804us microsが正常に
00000338 22800us カウントできなくなる
00000339 21776us
00000340 21780us
 :
00000609 1296us
00000610 1324us
00000611 352us 割り込みが間に合わず
00000612 -672us 完璧にアウト
00000613 352us
00000614 348us
00000615 -668us
~~~~~~~~~~~~~~~~~~~~~

最初は22.8msを計測していますが、それが徐々に
詰まってきて(タイマー0が動かないので)、
612回目を過ぎるとアウトになっている様子
が捉えられました。

とうぜん、メインループ側も動かないのでD13パルス
(正常なら100Hz)も出てきません。

オシロでの観察波形です。
 上から(ch1→ch4)
  ch1:INT1入力 ↓エッジで割り込み
  ch2:LEDポート D13出力
  ch3:テスト用D12出力 割り込みでパルス
  ch4:TXD出力 アイドル中はHレベル

・シリアル出力が間に合って正常な時 
I000

・INT1にチャタリングを与えると・・・
I003

・その拡大
I004

Serial.printの処理がエライというのか、FIFOがちゃんと
働いて、文字の抜けはありません。
しかし、メインの処理が止まるのはやはり致命的。

割り込みの中でのSerial.print、いかがでしょうか。
「間に合えばエエやん」というのもご意見。
しかし、基本は書いたらアカんプログラムです。

※チャタリングの発生はこの回路で
「チャタリング除去回路」じゃなくって「チャタリング発生回路」をどうぞ

| | コメント (0)

2024年3月12日 (火)

割り込み処理の中でのdelayやSerila.print,lcd.print

(1)割り込みの中でのdelay()

そもそも割り込みの中で時間待ちするという発想が
おかしいが、delay()の動きの解説を。
   delay()のソースはwiring.cの中。
   millis()やmicros()もこの中。
   delayMicroseconds()もそばにある。
   delay()内では割込許可フラグを
   操作していない。

Arduino UNOのdelayはタイマー0のオーバーフロー割り
込みでカウントされる値を使って処理されている。
その周波数は16MHz÷64÷256で約976Hzで周期1.024ms。

delayの単位は1msだが、micros()でμ秒単位のスタート
時間を得て、そこからの経過ミリ秒をチェックして
時間待ちを行っている。

そのmicros()もタイマー0のオーバーフロー割り込みで
カウントされるtimer0_overflow_countの値をベースと
して、8bitのタイマー0カウンタの値(1カウント4usで
255が最大)を加算して経過時間を得ている。

割り込み禁止状態(割り込み処理の中)ではmicros()の
カウントが進まないため、delay(10)としても、実際に
遅延するのは1~2msとなる。
  ※delay()は割込禁止でも抜けるように
   なっているのがエライ。
delay(1)だとは1msほどは待ってくれるが、時間設定を
長くしても1~2msしかならない。

割り込み内での時間待ちは、割り込みの有効・禁止に
左右されないdelayMicroseconds()が使える。
ただし設定できる時間の最大は16383μ秒まで。
65535は設定できない。


(2)割り込みの中でのSerila.print()

Arduino UNO R3でのシリアル出力は割り込みで
処理される。
送出文字列は64文字のFIFOバッファに入れられて、
送信完了割り込みで順次出力される。

割り込みの中でSerial.printを使った時、割り込みの
頻度が大きくなって(周期が短い割り込み要因が入力
されるなどして)FIFOがいっぱいになったら、送信が
終わるまで割り込みの中で待たされることになって
しまい、割り込み処理から抜けるのが遅れてしまう。
そのため、割り込みだけでなく、メイン側の流れも
滞ってしまう。
  ※9600BPSなら1文字の送出に約1msかかる。
   FIFOに空きがあれば一瞬で抜けてくるが、
   いっぱいだと空くのを待つ。
シリアル受信を処理していたら、割り込みが遅れると
受信文字を落とすかもしれない。
タイマーへも影響する。


(3)割り込みの中でのlcd.print() 液晶表示

カーソル位置を指定して文字を表示するだけですぐに
数ミリ秒かかってしまうので、Serila.print()より
たちが悪い。
  ※Serila.print()はFIFOに空きがあればすぐの
   処理が終わる。

液晶モジュール制御のEパルスを出すだけで0.1msほど。
画面クリアで2ms待ち。
  ※busy読みしたら早くなるかとR/W線をつない
   でも、液晶ライブラリLiquidCrystaはlR/W=L
   にして無視してしまっているので、つなぐのは
   無意味。

割り込みで液晶をアクセスしてしまったら、メイン側での
表示と衝突してうまく表示できなくなってしまうし。

間違っても割り込みの中で使う関数じゃない。


(4)float計算

16MHzで走っているArduinoのATmega328、floatの加減算
だとそんなに重くならない。
float(4バイト)とlong(4バイト)で実測してみると、単純な
加減算だと10倍も違わない。
乗算も思いのほか早い。(乗算命令が活躍)
しかし、整数でも除算が遅い。
割り込み内での除算はしたくない。

 

割り込みの中では、時間がかかる(かもしれない)処理は
避けるのが基本。
   もたもたしない!
どうしてもというときは、他に影響が無いことを
確認して。


※実際に検証
2024年3月14日:割り込みの中でSerial.printを使うと・・・


| | コメント (0)

2023年5月25日 (木)

初めて買ったArduino UNO・・・今は

Arduino初体験 が2012年12月。 (10年以上前だわ)

その基板の現在がこれ。
 ・どこかから拾ったプラケースをベースに。
 ・16MHzセラミック発振子を水晶発振子に交換。
 ・ブートローダ書き込み済みのATmega328Pと
  通信(スケッチアップロード)するための
  ケーブルを付加。
 ・乗っているチップは何度も交換。

Aa21_20230525110601

ちょっとした実験や試運転で便利なのが
アナログ入力ポートにつなぐ4つのボリューム。
スイッチも2つ付けてます。
こんな回路です。
Aa1_20230525110701

ボリュームのスライダーに0.1uFのコンデンサを
入れておくと安心。

発端はこんな3つのボリューム。
Aa22_20230525110801

+5VとGNDを取れるようにピンヘッダをハンダして
います。

ケチって片面のユニバーサル基板なんで、配線は
ちょっと面倒。

AnalogReadで読んだA/D値(0~1023)を使って
Delay値を設定したり、動作パラメータを変えたりと
リアルタイムでできますんで、なかなか便利です。

使ったボリュームは秋月電子通商のつまみ付半固定抵抗
  (足の1・2・3の順に注意:刻印あり)
  似てるけど3386K-EY5-103TRじゃない!


※応用のためのスケルトン・スケッチ
4つのボリュームと2つのスイッチを入力するための
応用基本スケッチを示しておきます。
 ・ボリューム値の入力は1ms割り込みで処理。
 ・64回平均。
 ・4つあるので256msごとにデータが確定。
 ・勝手にスキャンするのでAnalogReadのように
  100us待たされるということがない。
 ・割込禁止にしなくてもいつでも読めるよう
  VR値は0~255の8bitで。
    ad_avr[ch]を読めばok。

起動すると256msごとに値をシリアル出力します。
  A/D 8bit:VR1,2,3,4 SW1,2
  105 178 123 249 0 0
  105 178 123 255 0 0
   :
  0 64 128 255 0 0
  0 64 128 255 0 0
  0 64 128 255 1 0 SWはオンで1
  0 64 128 255 1 1
  0 64 128 255 0 1
  0 64 128 255 0 0

ダウンロード -  ad_vr4.txt
   .inoではなくUTF8Nのテキストです。


※タイマー割り込みとADC変換完了割り込み

ソースを見てもらえれば、その手順がわかるかと。
さまざまなライブラリ、確かに便利です。
でも、Arduino UNOのATmega328Pマイコン
あたりなら、レジスタの直接操作はそんなに
難しくはありません。
マイコンに備わっているさまざまな機能を引き
出すには、データーシートをにらみながらの
プログラミングをしなければなりません。

| | コメント (0)

2023年3月 1日 (水)

8ビットマイコンの割り込み処理・・・1バイトに収まるなら1バイトに

2022年9月1日:8ビットマイコンの割り込み処理・・・言い足りないぞ
ここ↑では、トランジスタ技術2018年5月号での記述を
「ちょっと違うけどなぁ」っと話題にしました。

この内容↓です。
 「割り込み処理関数とメイン関数の両方から
  アクセスされる変数はchar型にしておく」

Pp1_20220901103201

確かに、1バイトにしておくと安全側にはなるのですが、
間違いなく割り込みを動かそうとすると、アトミック処理
(いったんn割り込み禁止にしてごそごそ)が必須です。

で、またまた何気なく古いトラ技を見ていたらの話
になります。
今度は2019年4月号
Tr1904  

この連載:宇宙ロケットMOMO 開発深堀り体験<2>

ブロック図を記したp.129の図1を見ると、ジャイロや
サーボの制御に8bitマイコンATmega328が乗った
Arduino Pro Miniが使われています。

Tr1904a

p.135に制御ソフトが載っていたんで、ちょっと
追いかけてみました。
T11a_20230301162201

まず、目に入ったのが割り込みで処理されるであろう
変数です。
Volatileが前置されたint値(2バイト値)です。

T11b  

これを使うのが0.1ms周期のタイマー割り込みの中

T11c

pwm_cntがアップカウンタで、200になったら
出力ピンをHにしてゼロクリア。
そして、pwm_cntがpwm_h_periodeになったら
出力ピンをLにという制御。
200がPWM周期でpwm_h_periodでPWMの
パルス幅を決めています。
pwm_cntはこの割り込みの中だけで使われて
いるようなので、割り込みとの競合は問題なし。

比較するデータpwm_h_periodはどうかと追いかけ
ますと、gimbal_agl_to_pwmという関数で値を
出していました。
これはメイン側の処理です。

T11d

gimbal_agl_to_pwmがint値を算出して、それを
pwm_h_periodeに書く時に割り込みが入ると
どうなるか・・・

2バイト値ですので、2回に分けて値が書き込まれます。
もしその中間で割り込みが入り、なおかつ、
0x00FF → 0x01000xFFFF(-1) → 0x0001(+1)の
ような2バイトにまたがる数値の変化が発生したら、
割り込み処理での数値判定をミスするかもしれません。

しかし・・・このプログラムでは
  ・pwm_cntは200が最大値なんで、int値(2バイト)の
   下位側しか変化しない。
  ・関数gimbal_abl_to_pwmが返す値は
   +8~+20の範囲で1バイトの範囲内。
   負にもならない。

ということで、2バイトデータと割り込み処理との競合は
大丈夫でした。
でも、もし上位バイト変化するようなint値なら
割り込み禁止にしてデータを更新して割り込み処理に
知らせるというアトミック処理が必須です。

このプログラムでは、変数が1バイトに収まるならデータの
宣言は1バイトでということにしておくと、処理も速くなるし
でエエんじゃないでしょか。

ただ、昔人間からすると、
  char符号有り符号無しかどっちやねん?
     コンパイラはどう処理するんや?
  Arduino環境なら符号無しは  byte やろ。
  今ふうにちゃんと書くなら uint8_tint8_t で宣言か。
こんなところが気になります。

| | コメント (0)

2022年10月14日 (金)

8bitマイコンにも16bitのメモリ読み書き命令があった

8080のニーモニックでは
  LHLD nn と SHLD nn

Z-80なら
  LD HL,(nn)  LD (nn),HL

と、HLレジスタ(8bitレジスタのHとLを合わせて
16bitレジスタとする)を使っての16bitのメモリ
読み書き命令がありました。

割り込みで操作されるワード値を、読むだけ
あるいは書くだけなら、割り込み禁止操作が
不要だったのです。
  ※読んで処理して書き戻すときは
   当然アトミック操作が必要。

Z-80ではHLレジスタだけでなくBC、DEレジスタ
とIX、IYにもこの操作ができました。

タイマーなどのダウンカウンタのゼロチェック
(タイムアップチェック)が割り込み禁止にしなくても
できたのです。

この16bit値の直接読み書き機能、8bitマイコンでは
なかなかありがたかった。

6301にも LDD STD LDX STX命令があって、便利に
使って(アセンブラで)いました。
  6301はA,Bレジスタをペアにして。
  ただ、インデックスレジスタが1個というのがねぇ。
  メモリーの転送のように、あっちをこっちへという
  操作が面倒。

| | コメント (0)

2022年10月11日 (火)

何度も言うぞ! Arduino(8bitマイコン)の割り込みには気をつけろ!

過去、何度も言ってます。
8bitマイコンの割り込み処理で多バイトデータを扱う時は
アトミック操作」を!

  ※1バイトのデータならokということではなく、
   マイコンがどんな動きをするのかを想像して
   (マイコンになりきって)手順を考えなくてはなり
   ません。
   割り込み処理なら別の動きを考えるためにもう
   一つ頭がいるかも。

今月のトランジスタ技術2022年11月号

この「倒立振子 立つ」(著者:藤森 嵩人さん) の記事でも
手を抜かれて(cli、seiしていない)ます。

p.188~p.189のリスト。
volatile」が頭に付いた変数に注目。

  割り込みで読み書き。
  メインループでも読み書き。
  メイン側で「読む」だけでなく、処理したデータを
  「書いて」それを割り込み内で使っています。

  元はintのエンコーダ入力値。
  それを加工してあれこれのfloatデータに。
  モータ制御はMstimer2で駆動される1ms周期の
  タイマー割り込み内。

面倒でも、メイン側 ←→ 割り込み処理 を受け渡しする
データは「割り込み禁止・割り込み許可」を挟まなくては
なりません。

処理全体を割り込み禁止にするのではなく、受け渡しの
データだけで良いのです。

  cli、sei命令の動作は最短の1クロック。
  やりとりのための余分なデータ領域が増えますが、
  実行時間が遅くなることはありません。

  計算には一時的なデータを使って、それを本番データに
  コピーするだけ。 その時、割り込み禁止で。
  読み出すときも、割り込み禁止で本番データを一時データ
  にコピー。
  ただ、それだけ。

何度も言います。
  起きる可能性があるなら、いつかはアタリます。
  それがバグというものです。

最初からちゃんと考えておかないと、
  「忘れた頃におかしくなる」が発生します。


※この件、トラ技編集部に伝えています。

※公開されているダウンロードサービスでは
本誌に掲載されたのとは異なったスケッチ
(ちょっと手直し)が出てきます。
しかし、まだ不完全です。
面倒がらずに「cli、sei」の手順をお願い
したいところです。
「とりあえず動いているから」とか
「ちょっとした実験だから」という性質のもの
ではありません。

ネットに上がっているミス含有スケッチ、
「初心者だから許される」は言語道断。


※新たに「割り込み処理」というカテゴリを設けました。
これで、過去記事を追跡してもらえるかと。

ターゲットに「8bitマイコンを選んでしまった・・・」と
いうのが、今回のスカタンの根本原因じゃないでしょか。
32bitマイコンなら、現プログラムのままでOKかと思うのです。
電源やらエンコーダ入力割り込み処理の手軽さから
Arduino nanoを選ばれたんじゃないかと推測します・・・
8bitマイコンを使いこなすにはそれなりの覚悟が必須という
ことで・・・

「どれだけ痛い目にあったか」の経験値がスカタン・バグの
予防になるのかと。

簡単なワクチンはありません。
自分で痛い目にあって「抗体」を作るしか、バグから逃れる術は
ありません。

| | コメント (6)

より以前の記事一覧