« 2022年2月 | トップページ | 2022年4月 »

2022年3月

2022年3月30日 (水)

なぜか同じ本が・・・「MSX2テクニカル・ハンドブック」発掘

書架の整理で発掘。 同じ本が2冊。
41_20220330085001

初版が1986年4月。 36年前に出版された本。
発掘した一冊、1986年4月の第3刷。
もう一つが第9刷で1989年7月。
MSX2機の発売が1985年6月ということです。

二冊のうち一冊は引き取り手が見つかって います。
残り一冊、どなたかいりますか?

※基本は無料ですが、「送料+お駄賃」をビール券で。
 B5サイズの本ですので、クリックポスト(198円)で
 お送りできます。
 ポストに入らなければ手渡しのレターパックプラスに
 なります。

※なぜ同じ本を2冊置いてあったのが不明。

3刷目のは定価3500円
9刷目になると定価3610円(本体3505円)と3%消費税に。

| | コメント (10)

2022年3月29日 (火)

電卓の機能 知らなかった:「÷=」で「1/X」に

女房が持っているいくつかの電卓(関数電卓じゃなくごく普通の)、
その中の1つ、シャープ製の電卓に説明書が付いていたので、
消費税の計算方法なんかの解説を読んでいました。
  ※税込み、税抜きの計算手順なんかですな。

その説明書でこんな記述を発見。

「×=」で2乗を計算。 
   ※これは知っていたし、よく使う機能。

「÷=」で逆数を計算
 これで「1/X」が出てくる!
   ※この機能、知りませんでしたよ。

試してみてちょっと感激。
電卓への要望、何度も書いてますが
  「1/X」をワンアクションで。
これができない関数電卓はキライ。

関数電卓なら、2nd-Funcに割り当てられていても
「1/X」の機能があるだけで逆数計算できるだけ
ましです。

普通の電卓しかない時に逆数を計算したいなら、
計算途中のX値をいったんメモリーに入れてから
「1÷MR=」として値を出すわけです。
  ※あるいは直接数字入力で「1÷X=」で。

それが「÷=」の2アクションで逆数が出現しちゃったの
です。
普通の電卓で、です。
ほんとに知りませんでしたよ。

例えば、抵抗の並列計算。
 CM      メモリークリア
 R1 ÷ = M+  R1の逆数をメモリーに加算
 R2 ÷ = M+  R2の逆数をメモリーに加算
 R3 ÷ = M+  R3の逆数をメモリーに加算
 :
 RM      メモリーリード
 ÷ =     逆数にして並列抵抗値算出

これが、関数電卓じゃなく普通の電卓でできちゃう。
 1 / (1/R1 + 1/R2 + 1/R3 +・・・)

「×=」で2乗は知られてると思うんです。
でもこの「÷=」で逆数、皆に聞いても
「知らんかった」と驚いてました。

※できる電卓とできない電卓があるようです。

2007年03月01日:ロケットガール第二話…今から5分で操作をたたき込む
2007年03月24日:現用中の関数電卓
2018年2月24日:愛用電卓、アウト!
2018年3月14日:TIの電卓、これはバグなのか?
2020年10月26日:シャープ製関数電卓 EL-566


| | コメント (5)

2022年3月24日 (木)

ダイソー「LOOPER」(1000mAh) 充放電実験 220サイクル目

ダイソー「LOOPER」(1000mAh) 充放電実験開始 が2月6日。

11_20220324124901

今朝、220サイクル目の放電が終わったので、途中経過を
抜き出しました。
  実験装置では400サイクルが一区切りなんですが、
  気になったんでどんなもんかと。

充放電時間と充電停止電圧の変化。
Cap040
   ※50cycごとのゆっくり充放電のデータは抜いています。

寒い季節なんで、室温変化の影響が大きいようです。
温度が上がると内部抵抗が小さくなる傾向があるんで
けっこう電圧が変化します。

0.5Cでの充放電電圧の変化がこのグラフ。
Cap039

まだまだ大丈夫そうです。

同じダイソーの「ReVOLTES」の様子。
ダイソーReVOLTES単3 JIS C8708:2019充放電試験(-ΔV検出有) サイクルごとの充放電時間
  500サイクルで終わりました。
その時の充放電時間の変化。
V517_20200512173401
100cycあたりまでは、定格の2時間を越えた放電時間を
記録しています。
この時の充電時間、-ΔV検出より先に目一杯の132分の
リミットで停止しています。
充電停止電圧の上昇が内部抵抗の増大を示していて、
徐々に悪い方に変化している様子が見えます。
放電時間「60%=72分」が200サイクルちょい越えあたり。
比べると、「LOOPER」、頑張っているようです。
   ※容量は違いますが

一昨日、近所のダイソーに寄ったのですが、「LOOPER」
ありませんでした。

同じ1000mAhの充電式エボルタお手軽モデルのデータ。
パナソニック 充電式エボルタお手軽モデル BK-3LLB 1000mAh 2000サイクル目で終了
Cap002_20211018105901
値段が違いますが・・・
このデータを記録するのに1年近くかかっています。

電池あれこれ

| | コメント (1)

2022年3月23日 (水)

「ペコ」、7年目

トイプーの「ペコ」、我が家にやってきたのが
  ・2015年03月22日:「ペコ」がやってきた
もう7年たちました。
元の飼い主さん、お元気なのがなによりです。

15_20220323101801

| | コメント (0)

2022年3月21日 (月)

Arduino-UNO 割り込み処理のミスあれこれ:割り込み禁止にして書かないとダメよ

何度も言ってるんですが・・・ もう一度言います。
「8ビットマイコンで割り込みで処理される多バイトデータを
 読み書きする時は割り込み禁止にしなくちゃダメ」

前記事、
Arduino-UNO 割り込み処理のミスあれこれ:割り込み禁止にして読まないとダメよ
これ↑は、読み出し時のお話しでしたが、今回は
書き込みで発生するミスを検証してみます。

Arduino-UNOのINT0を↓エッジ検出の割り込み入力にして
PD5のPWM出力(約980Hz)をつなぎます。
「読み出し」と同じ接続です。
Aa1_20220321093001
INT0割り込み処理では、long:4バイトのカウントデータを
デクリメント(-1)します。
メインループでこれを「ゼロ・クリアー」する処理を走ら
せます。
そして、ゼロにした直後、この値を読み取ってチェックし
ます。
こんな手順です。

  (1) 割り込み有効のままカウント値を
    ゼロクリア。
  (2) その直後、割り込み禁止にして
    カウント値を読み取る。
  (3) カウント値は、
      ゼロのままになっている。
        あるいは、
      直後に-1されてマイナスの値に
      なるかもしれない。
  (4) ゼロかマイナスなのでプラスには
    ならないはず。

問題はこの(4)。 ・・・ほんとでしょうか?
それを確かめます。

こんなスケッチです。

/*****  割り込み処理ミスの検証     *****/
// "intr_miss_dncnt1.ino" 2022-03-20 / JH3DBO
// 割り込みでカウントダウンされるデータをゼロクリアするとき
// 割り込み禁止にしていないとミスすることがあるのを検証
// PD2 <D2> パルス入力 ↓エッジ
// PD5 <D5> テスト用パルス analogWriteで980Hzを出力
// 1秒サイクルでループした回数を出力
// ミスを検出した時に16進で値を出力
// CR入力でクリア
/***** I/O MACRO *****/
#define PB0_H (PORTB |= (1 << PB0)) // (!!!)PB0 H/L
#define PB0_L (PORTB &= ~(1 << PB0))
#define PB4_H (PORTB |= (1 << PB4)) // (!!!)PB4 H/L
#define PB4_L (PORTB &= ~(1 << PB4))
#define PB5_H (PORTB |= (1 << PB5)) // (!!!)PB5 H/L
#define PB5_L (PORTB &= ~(1 << PB5))
/***** データ *****/
// カウントデータ
volatile long cnt_down; // 割込み内でカウントダウン
long loop_cnt; // メイン処理をloopするごとに+1
// シリアル出力バッファ
char tx_bff[64]; // sprintfで出力文字を設定
/***** INT0(PD2) ↓エッジパルス割り込み *****/
void countdn(void){
PB0_H; // (!!!)
cnt_down--; // カウント値 -1
PB0_L; // (!!!)
}
/***** セットアップ *****/
void setup(){
analogWrite(5, 128); // D5に980Hz出力
pinMode(2, INPUT_PULLUP); // D2は入力
attachInterrupt(0, countdn, FALLING); // D2をINT0割り込みに
pinMode(8, OUTPUT); // PB0 テストパルス
pinMode(12, OUTPUT); // PB4
pinMode(13, OUTPUT); // PB5
Serial.begin(9600); // シリアル出力
}
/***** LOOP *****/
void loop(){
char c;
long d0; // 割り込み禁止でcnt_downをコピー
uint32_t tm_1ms; // 1msタイマー
Serial.println(F("test(ZeroClr + DnCnt)")); // タイトル
tm_1ms = millis() - 1000; // タイマーすぐ表示で
// 実行loop
while(1){
loop_cnt++; // ループカウンタを+1
if(loop_cnt > 99999999) loop_cnt = 0; // max8桁
delayMicroseconds(random(100)); // 0~99us停止
// 一定周期でループカウンタを出力
if((millis() - tm_1ms) >= 1000){ // 1秒経過
tm_1ms = millis(); // 現在値保存
sprintf_P(tx_bff, PSTR("%8ld"), // ループカウンタを出力
loop_cnt);
Serial.println(tx_bff); // シリアル出力
}
// CR受信でカウント値をクリア
if(Serial.available()){ // 受信データあり
c = Serial.read(); // 1文字読み出し
if(c == '\r'){ // CR ?
noInterrupts(); // いったん割込禁止に
cnt_down = 0; // カウント値クリア
interrupts(); // 割込再開
loop_cnt = 0; // ループカウンタもゼロに
tm_1ms = millis() - 1000; // タイマーすぐ表示で
}
}
// カウント値をゼロクリアしたあと読み出してチェック
PB4_H; // (!!!) データコピータイミング
cnt_down = 0; // ★1 割り込み禁止にしないでゼロクリア
PB4_L; // (!!!)
noInterrupts(); // いったん割込禁止に
d0 = cnt_down; // カウント値longデータ
interrupts(); // 割込再開
if(d0 > 0){ // ゼロかマイナスのはず
PB5_H; // (!!!)
sprintf_P(tx_bff, PSTR("0x%08lX"), d0); // 8桁16進で
Serial.println(tx_bff); // シリアル出力
PB5_L; // (!!!)
}
}
}

・setupで、PWM出力とIN0を↓エッジ割り込みに。
  他、テストポートを出力に。
・PWMはD5ポート。 約980Hzを出力。
  これをINT0入力につなぐ。
・1秒周期でループカウンタ(何回loopを回ったか)
 を出力。
・ゼロクリア後のカウント値を割り込み禁止で
 読み取り、「プラス」ならその値を16進で出力。
・ゼロクリア+読み取りの処理が高速で続くので、
 loopを回るとき、
   delayMicroseconds(random(100))
 で、ちょっとだけ(max99us)時間待ちを入れる。

その結果です。

test(ZeroClr + DnCnt)
1
0x000000FF
0x0000FFFF
0x0000FFFF
6808
0x00FFFFFF
0x000000FF
0x0000FFFF
0x00FFFFFF
13592
0x0000FFFF
0x000000FF
0x0000FFFF
0x000000FF
20388
0x000000FF
0x000000FF
0x0000FFFF
27190
0x000000FF
0x000000FF
0x000000FF
33980
0x0000FFFF
0x000000FF
0x00FFFFFF
40792
0x0000FFFF
0x0000FFFF
0x0000FFFF
0x000000FF
47590
  :

  ※時間待ちを入れないと「ミス」の出現が
   多過ぎて見にくいので、割り込みと重なる
   タイミングを少なくしています。

割り込みを有効にしたままでのゼロクリア、
多バイトのゼロ書き込み途中に入り込んだ割り込み
(カウントダウン処理)のタイミング(どの桁の時に)
によって、結果が変わります。

ミスしたデータとして出てくるのはこの3種類。
  0x000000FF
  0x0000FFFF
  0x00FFFFFF
いずれもプラスの値です。
ダウンカウントしているはずなのに、です。

本来は「0x00000000」か、-1された「0xFFFFFFFF」。
もう少し進むと-2されて「0xFFFFFFFE」となるはず
です。
ゼロクリア中に割り込みが入ると、上位にだけ
「00」が残ってしまい(最後に書かれる)「プラス」
の値となってしまうのです。

この書き込みミスは、
 ・8ビットマイコン特有の問題
 ・ゼロクリアした途中のカウントダウン
  だけでなく、数値のプリセットでも
  桁上がり・桁下がりが生じるとミスが
  発生する。
 ・一度生じたミスは残ってしまい、正常には
  戻らない。
    ※これが読み出しと違って怖いところです。
     読み出しは、次の読み出しでは正常に戻
     りますんで。
対策は、
 ・割り込み禁止にしてから、ゼロクリアー
  あるいはカウント値のプリセット処理を
  行って、その後割り込み有効に戻す。
です。

うまく条件が合わないことにはこのミスは出ません。
しかし、読み出しと違って、ミスが出たときは
致命的な影響が残ってしまいます。

何度も言います。
起きる可能性があるなら、いつかはアタリます。

それがプログラムの「バグ」というものです。

| | コメント (1)

Arduino-UNO 割り込み処理のミスあれこれ:割り込み禁止にして読まないとダメよ

何度も言ってるんですが・・・
「8ビットマイコンで割り込みで処理される多バイトデータを
 読み書きする時は割り込み禁止にしなくちゃダメ」

今回は読み出し時のミスを検証してみます。

Arduino-UNOのINT0を↓エッジ検出の割り込み入力にして
PD5のPWM出力(約980Hz)をつなぎます。
こんな接続です。
Aa1_20220321093001

テストしたスケッチを示します。
ハードウェアの直叩きはポートのH/L操作だけにして
Arduinoの作法でsetup()などを記しています。
cli()、sei()も noInterrupts()とinterrupts()で
記述してます。
  ※アセンブラからAVRマイコンに入った人は、
   3文字のCLI、SEIでエエやんっと。
   8080なら2文字でDIとEIだ。

/*****  割り込み処理ミスの検証     *****/
// "intr_miss_upcnt1.ino" 2022-03-20 / JH3DBO
// 割り込みでカウントアップされるデータをprintするとき
// 割り込み禁止にしていないとミスすることがあるのを検証
// PD2 <D2> パルス入力 ↓エッジ
// PD5 <D5> テスト用パルス analogWriteで980Hzを出力
// 1秒サイクルでループした回数を出力
// ミスを検出した時に16進で値を出力
// CR入力でクリア
/***** I/O MACRO *****/
#define PB0_H (PORTB |= (1 << PB0)) // (!!!)PB0 H/L
#define PB0_L (PORTB &= ~(1 << PB0))
#define PB4_H (PORTB |= (1 << PB4)) // (!!!)PB4 H/L
#define PB4_L (PORTB &= ~(1 << PB4))
#define PB5_H (PORTB |= (1 << PB5)) // (!!!)PB5 H/L
#define PB5_L (PORTB &= ~(1 << PB5))
/***** データ *****/
// カウントデータ
volatile long cnt_up; // 割込み内でカウントアップ
long loop_cnt; // メイン処理をloopするごとに+1
// シリアル出力バッファ
char tx_bff[64]; // sprintfで出力文字を設定
/***** INT0(PD2) ↓エッジパルス割り込み *****/
void countup(void){
PB0_H; // (!!!)
cnt_up++; // カウント値 +1
PB0_L; // (!!!)
}
/***** セットアップ *****/
void setup(){
analogWrite(5, 128); // D5に980Hz出力
pinMode(2, INPUT_PULLUP); // D2は入力
attachInterrupt(0, countup, FALLING); // D2をINT0割り込みに
pinMode(8, OUTPUT); // PB0 テストパルス
pinMode(12, OUTPUT); // PB4
pinMode(13, OUTPUT); // PB5
Serial.begin(9600); // シリアル出力
}
/***** LOOP *****/
void loop(){
char c;
long d1; // 割り込み有効のままcnt_upをコピー
long d2; // 割り込み禁止でcnt_upをコピー
uint32_t tm_1ms; // 1msタイマー
Serial.println(F(" (EI) (DI) dif")); // タイトル
tm_1ms = millis() - 1000; // タイマー1秒前に
noInterrupts(); // いったん割込禁止に
cnt_up = 0; // カウント値クリア
interrupts(); // 割込再開
// 実行loop
while(1){
loop_cnt++; // ループカウンタを+1
if(loop_cnt > 99999999) loop_cnt = 0; // max8桁
// 一定周期でループカウンタを出力
if((millis() - tm_1ms) >= 1000){ // 1秒経過
tm_1ms = millis(); // 現在値保存
sprintf_P(tx_bff, PSTR("%8ld"), // ループカウンタを出力
loop_cnt);
Serial.println(tx_bff); // シリアル出力
}
// CR受信でカウント値をクリア
if(Serial.available()){ // 受信データあり
c = Serial.read(); // 1文字読み出し
if(c == '\r'){ // CR ?
noInterrupts(); // いったん割込禁止に
cnt_up = 0; // カウント値クリア
interrupts(); // 割込再開
loop_cnt = 0; // ループカウンタもゼロに
tm_1ms = millis() - 1000; // タイマーすぐ表示で
}
}
// カウント値を読み出してチェック
PB4_H; // (!!!) データコピータイミング
d1 = cnt_up; // 【1】割り込み状態でコピー
PB4_L; // (!!!)
noInterrupts(); // ★いったん割込禁止に
d2 = cnt_up; // 【2】カウント値longデータ
interrupts(); // ★割込再開
if(abs(d2 - d1) > 100){ // 差が大きければ
PB5_H; // (!!!)
sprintf_P(tx_bff, PSTR("0x%08lX 0x%08lX 0x%04lX"), // 8桁16進で
d1, d2, abs(d2 - d1));
Serial.println(tx_bff); // シリアル出力
PB5_L; // (!!!)
}
}
}

INT0の割り込み処理では、long:4バイトのカウントデータを
インクリメント(+1)します。
メインループでこれを読み取って、シリアル出力します。
この時、
  (1)割り込み有効のままカウント値を
    読み出した値:d1。
  (2)その直後、割り込み禁止にして
    読み出した値:d2。
という2つのデータを得ます。
d2のほうが後なので、途中でカウントアップされたら、
d2のほうが大きくなります。

ところが・・・
(1)の読み出し処理の途中でカウントアップ割り込みが
入って、カウント値が桁上がり直前だと、こんなミス
が生じるかもしれないのです。

データをシリアル出力してミスが生じる様子を観察できるように
しています。

  (EI)   (DI)   dif
    1
 137345           ←loopカウンタ
  :
2197306           ←カウントが進む
0x00003EFF 0x00003E00 0x00FF ←異常発生時 16進で出力
  |     |    +--差分 (差の絶対値)
  |     +-------割り込み禁止で読んだ値
  +-------------割り込み有効のままで読み出し

解説:(1)「3DFF」の時、下位がFFの時に読み出し。
   (2)そのタイミングでカウントアップ割り込みが入る。
   (3)「3E00」になる。
   (4)カウントアップされた2桁目の「3E」を読み出し。
   (5)結果、「3DFF」あるいは「3E00」なのに
    「3EFF」になる。 ※読み出しミス発生。
   (6)割り込み禁止にしておくと、途中のカウントアップが
     待たされて正しい値が読める。
  :
0x00006DFF 0x00006D00 0x00FF
3982559
  :
4806511
0x000088FF 0x00008800 0x00FF
4943813
5081068
0x00008FFF 0x00008F00 0x00FF
  :
0x0000AAFF 0x0000AA00 0x00FF
  :
0x0000CCFF 0x0000CC00 0x00FF
  :
0x0000DAFF 0x0000DA00 0x00FF
  :
0x0000E4FF 0x0000E400 0x00FF
  :
0x0000FFFF 0x0000FF00 0x00FF
9200547
0x0001FFFF 0x00010000 0xFFFF ★1:2桁目でミス 珍しい
9337847
  :
9887149
0x000114FF 0x00011400 0x00FF
10024449
  :

ミスが生じたときの下位桁は「FF」か「00」ですので、
正しいカウント値との差は「FF」になります。

また、4バイトの下位2桁が「FFFF」のときにうまく(!)
割り込みが入ると、正しい値との差が「FFFF」という
大きなもの生じてしまい、これはもう大事件です。
★1のように「たまたま」出現しました。

カウント値の下位が「FF」になるのは、割り込みの
256回に1回です。
異常値が出るのはこのときにたまたま読み出してと
いうタイミングです。
うまく条件が合わないとこのミスは出ません。

でも・・・  何度も言います。
  起きる可能性があるなら、いつかはアタリます。

この読み出しミスは、
 ・8ビットマイコン特有の問題
 ・特定の値の時に割り込みと競合した
  場合だけミスが発生。
 ・次の読み出しでは正常に戻る
という特徴があります。

解決方法は、
 ・割り込み禁止にして読み出して、
  その後、すぐ割り込み有効に戻す。
です。

  ※割り込みの応答速度への影響はほとんどありません。
   割り込み禁止にするのは、数値を読み出す一瞬だけ
   です。
   バックグランドで動いているタイマー割り込み処理
   の影響やトロくさいdigitalWriteなんかのほうが問題
   でしょう。

  ※割り込みで処理される値を扱っている関数全体を
   割り込み禁止で動かすのではなく、割り込み禁止に
   してから数値をローカル変数にコピーした後、割り込み
   有効に戻し、関数ではそのコピーした値を使うという
   ふうにして、割り込み禁止している時間を短くする
   ということで速度低下(ちょっとだけだ)に対処します。

表示だけだと実害は無いかもしれません。
しかし、カウント値を何かの制御に使っていたら
「たまにおかしくなる」が発生して、
見つけにくいバグになってしまいます。
気付きにくいから、最初からちゃんと考えておか
ないと、という次第です。

※関連
割り込みで処理させるwordデータの扱い
Arduino-UNO 割り込み処理のミスあれこれ:ロータリーエンコーダー
Arduino-UNO 割り込み処理のミスあれこれ:計時処理
『アトミック操作』・・・8bitマイコンに限り何か別の言い方なかったか?

タイマー割り込みを使ったLEDのダイナミック点灯処理
Arduinoのタイマー処理
toneパルスの異常 もうちょっと掘り下げて

※続き
Arduino-UNO 割り込み処理のミスあれこれ:割り込み禁止にして書かないとダメよ

| | コメント (0)

2022年3月18日 (金)

Arduino-UNO 割り込み処理のミスあれこれ:パルス計数の抜けを確かめる その2

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

こんな回路にして、ボリュームで試験用パルス出力の
L時間/H時間を変えられるようにして「パルス抜け」を
Arduino自身で確認できるようにしました。

Bb01

Arduino-UNO基板に10kΩのボリュームを3つ
付けたサブ基板を装着します。
Bb11
Bb10
小基板に乗せて、直接挿入。

そして、テストプログラムのINT0パルス割り込み
↓↑両エッジに変更します。
L側の時間を短くしたり長くすると、入力パルスに
対する割り込み応答の様子が見えてきます。

まず、最短クロック幅での割込応答。
Bb21
両エッジモードのままでも、ちゃんと割り込みがかかりました。
  (両エッジそれぞれに対する応答は無理ですが)

両エッジを捉えてくれる最小パルス幅がこんな感じ。
Bb22

「割り込み禁止にしてない」のが理由で、2発目のパルスを
落としている様子です。

(1)まず、正常な場合。
Bb23

(2)スカタンすると・・・
Bb24

(3)忘れたころに「抜け」が出る様子です。
Bb25
この「メイン処理でパルス数を積算する」方式では、
読み出し時に「抜け」が発生すると、それがどんどん
積もっていきます。
  ※基本、1発でも抜けがあるとアウト!!。
   パルスに対する割り込み応答はハードウェアの
   制限ですが、このパルス抜けはソフトウェアの
   ミス(スカタン)ですんで・・・

新しいスケッチです。 ・・・ずいぶん長いけど

/*****  割り込み処理ミスの検証     *****/
// "intr_miss2.ino" 2022-03-18 / JH3DBO
// cnt + n; n = 0; 処理間での割り込みとの競合をチェック
// 【1】に割り込みが入ると「n2」がミスする
// PD2 パルス入力 ↓↑両エッジ
// PB1 PWM周期方形波出力
// PB2 PWM出力
// PB3 125Hz方形波出力
// 1秒サイクルでカウント値を出力
// CR入力でクリア
// cnt_up, cnt_add1は割り込み禁止で処理
// cnt_add2を割り込み有効のままで計数
// VR1,VR2でL幅可変 VR3でH幅可変
// VR1は1clk単位で微調
/***** I/O MACRO *****/
#define PB0_H (PORTB |= (1 << PB0)) // (!!!)PB0 H/L
#define PB0_L (PORTB &= ~(1 << PB0))
#define PB4_H (PORTB |= (1 << PB4)) // (!!!)PB4 H/L
#define PB4_L (PORTB &= ~(1 << PB4))
#define PB5_H (PORTB |= (1 << PB5)) // (!!!)PB5 H/L
#define PB5_L (PORTB &= ~(1 << PB5))
/***** データ *****/
// 加算データ
volatile long cnt_up; // 割込み内でカウントアップ
volatile int8_t cnt_n1; // 同じだが1バイトでカウントアップ
volatile int8_t cnt_n2; // 割り込み禁止なし用
long cnt_add1; // loop内でcnt_n1,n2を加算
long cnt_add2; // n1は割り込み禁止で
// n2は割り込み有効のままで処理
// タイマーデータ
volatile byte tm_4ms; // 1msタイマー割り込みでダウンカウント
// 250で1秒
// シリアル出力バッファ
char tx_bff[64]; // sprintfで出力文字を設定
/***** INT0(PD2) ↓↑エッジパルス割り込み *****/
// cnt_upはlongデータ n1,n2は1バイト
// メイン処理でn1,n2をadd_n1,n2に加算する時に違いが出る
ISR(INT0_vect)
{
PB0_H; // (!!!)
if(cnt_up < 99999999) cnt_up++; // 8桁maxで+1
cnt_n1++; // loop内でcnt_add1に加算
cnt_n2++; // cnt_add2に加算
PB0_L; // (!!!)
}
/***** タイマー2コンペアマッチA割込み *****/
// 1kHz 1msサイクル
ISR(TIMER2_COMPA_vect)
{
static byte cnt4 = 0; // 4msカウント
cnt4++;
if(cnt4 >= 4){
cnt4 = 0;
if(tm_4ms) tm_4ms--; // 4msタイマーダウンカウント
}
ADCSRA |= (1 << ADSC); // A/D変換開始
}
/**** A/D データ *****/
// A/D変換チャンネル (ADMUX)
const byte ad_mpx[] PROGMEM ={
0b01000000, // ch 0 VR1
0b01000001, // ch 1 VR2
0b01000010, // ch 2 VR3
}; // ||| ++++---- MPX 0,1
// ||+--------- ADLAR 右そろえ
// ++---------- REFS1,0 AVCC接続
#define AD_SU sizeof(ad_mpx) // A/D ch数
// AD ch0,1,2 : VRデータ
volatile word ad_avr[AD_SU]; // A/D変換データ 0~1023
// 平均処理された結果
volatile word ad_add[AD_SU]; // A/D平均処理用加算データ
volatile byte f_adok; // A/D変換完了フラグ
// 192msごと
volatile byte adok_cnt; // A/D変換完了回数
// max255で+1
/***** A/D割り込み処理 *****/
// タイマー割り込みでA/D変換開始
// 1msに1回, 3msで3ch, 64回(192ms)で平均値算出
// 10bitを64回なら16bit内に入る
ISR(ADC_vect)
{
word d;
static byte ch = 0; // A/D変換チャンネル
static byte cnt = 0; // A/D変換平均回数
d = (word)ADCL; // A/Dデータ
d |= (ADCH & 0x03) << 8; // 符号なしで 0~3FF
// 測定値
ad_add[ch] += d; // 平均用に加算
// 次変換チャンネル
ch++; // ch0,1,2繰り返し
if(ch >= AD_SU) ch = 0;
ADMUX = pgm_read_byte(&ad_mpx[ch]); // MPX切り替え
// 平均加算回数
if(ch == 0){
cnt++; // 変換回数+1
if(cnt >= 64){ // 64回?
cnt = 0;
ad_avr[0] = ad_add[0] / 64; // ch0 平均値 10bit
ad_avr[1] = ad_add[1] / 64; // ch1
ad_avr[2] = ad_add[2] / 64; // ch2
ad_add[0] = 0; // 次加算データクリア
ad_add[1] = 0;
ad_add[2] = 0;
f_adok = 1; // 変換完了フラグon
if(adok_cnt < 255) adok_cnt++; // 変換回数+1
}
}
}
/***** 2つのshort値の差を求める *****/
short sdiff(short a, short b)
{
short d;
d = a - b; // ±差
if(d < 0) d = -d; // 絶対値で
return d;
}
/***** PWM出力設定 *****/
// A/D変換タイミングでタイマー1のPWMを設定
// 変動するので8bitのA/D値で設定
// VR1 L幅微調 16MHzクロックで 1~256clk : 62.5ns~16us
// VR2 L幅 5usクロック 0~1275us
// VR3 H幅 8usクロック 0~2040us + 50us (VR1~3足して16bit越えないよう)
void pwm1set(void)
{
static byte f1 = 0; // 初めてフラグ
static word d[AD_SU]; // A/D変化チェック用 前回値
word a[AD_SU]; // A/D値一時データ
if(f_adok){ // 変換完了?
f_adok = 0; // 次を待つ
cli(); // 割込禁止でコピー
a[0] = ad_avr[0]; // 10bit A/D値を保存
a[1] = ad_avr[1];
a[2] = ad_avr[2];
sei(); // 割込許可
if(f1 == 0){ // はじめて?
d[0] = a[0]; // チェックデータを保存
d[1] = a[1];
d[2] = a[2];
}
// 変化があればPWM値を設定 2以上の変動で変化ありと判断
if((sdiff(d[0], a[0]) >= 2) || // どちらかA/D値変化あり?
(sdiff(d[1], a[1]) >= 2) || // 3つのうちどれか
(sdiff(d[2], a[2]) >= 2) ||
(f1 == 0)){ // はじめてか?
f1 = 1; // はじめて処理済みに
d[0] = a[0]; // 新値をコピー
d[1] = a[1]; // 有効桁は10bit
d[2] = a[2];
// A/D値は8bit(0~255)で設定
a[0] = (d[0] / 4); // VR1 62.5ns
a[1] = (d[1] / 4) * 80; // VR2 5us
a[2] = ((d[2] / 4) * 128) + 799; // VR3 8us + 50us
OCR1B = a[0] + a[1]; // PWM L区間
OCR1A = a[0] + a[1] + a[2]; // PWM周期
}
}
}
/***** カウント値シリアル出力 *****/
// cnt_up, add1, add2 ,差 の4データを出力
// 割り込み禁止でカウント値をコピー
void txcnt(void)
{
long d0, d1, d2;
word a0, a1;
PB5_H; // (!!!)
cli(); // ★いったん割込禁止に
d0 = cnt_up; // カウント値longデータ
cnt_add1 += (long)cnt_n1; // n1の残値をadd1に加算
cnt_n1 = 0; // 次に備えてゼロクリア
cnt_add2 += (long)cnt_n2; // n2をadd2に加算
cnt_n2 = 0; // ゼロクリア
d1 = cnt_add1; // 割込禁止でのカウントアップ
d2 = cnt_add2; // 割込禁止しないで
sei(); // ★割込再開
sprintf_P(tx_bff, PSTR("%8ld %8ld %8ld %8ld"), // 8桁で表示
d0, d1, d2, d1 - d2);
Serial.print(tx_bff); // シリアル出力
sprintf_P(tx_bff, PSTR(" %4dus %dus"), // L,Hパルス幅
(OCR1B + 1) / 16, // PWM L区間
(OCR1A - OCR1B + 1) / 16); // PWM H区間
Serial.println(tx_bff); // シリアル出力
PB5_L; // (!!!)
}
/***** カウント値クリア *****/
void clrcnt(void)
{
cli(); // いったん割込禁止に
cnt_up = 0; // カウント値クリア
cnt_n1 = 0;
cnt_n2 = 0;
cnt_add1 = 0;
cnt_add2 = 0;
sei(); // 割込再開
}
/***** セットアップ *****/
// ATmega328Pのレジスタを直接制御
void setup()
{
cli(); // 割込禁止
// I/Oイニシャル
PORTB = 0b00000000; // data/pull up
DDRB = 0b00111111; // portI/O
// |||||+---- PB0 IO8 out TEST 割り込みでパルス
// ||||+----- PB1 IO9 out OC1A出力 PWM周期でトグルする方形波
// |||+------ PB2 IO10 out OC1B出力 PWM出力
// ||+------- PB3 IO11 out OC2A出力 500Hz方形波
// |+-------- PB4 IO12 out TEST カウントでパルス
// +--------- PB5 IO13 out (LED) シリアル送信タイミング
PORTC = 0b00000000; // data/pull up
DDRC = 0b00111000; // portI/O
// |||||+---- PC0 AD0 in VR1入力
// ||||+----- PC1 AD1 in VR2入力
// |||+------ PC2 AD2 out VR3入力
// ||+------- PC3 AD3 out
// |+-------- PC4 AD4 out
// +--------- PC5 AD5 out
PORTD = 0b00000111; // data/pull up
DDRD = 0b11111010; // portI/O
// |||||||+---- PD0 RXD in
// ||||||+----- PD1 TXD out
// |||||+------ PD2 IO2 in INT0↓↑両エッジをカウント
// ||||+------- PD3 IO3 out
// |||+-------- PD4 IO4 out
// ||+--------- PD5 IO5 out
// |+---------- PD6 IO6 out
// +----------- PD7 IO7 out
// INT0 ↓↑両エッジ割り込み (PD2)
EICRA = 0b00000001; // 外部割り込み
// ||++---- ISC0 INT0 ↓↑両エッジで
// ++------ ISC1 INT1 Lレベル
EIMSK = 0b00000001;
// |+---- INT0 割り込み有効:アップカウント実行
// +----- INT1 割り込み未使用
// タイマー1 : PWM出力
TCCR1A = 0b01110011;
// |||| ++---- WGM11,10 PWM TOP=OCR1A
// ||++-------- COM1B PB2 PWM反転出力
// ++---------- COM1A PB1トグル出力
TCCR1B = 0b00011001;
// || ||+++---- CS 1/1=16MHz
// || ++------- WGM13,12 PWM
// |+---------- ICES1
// +----------- ICNC1
TIMSK1 = 0b00000000; // 割り込み 使わない
// | ||+--- TOIE1
// | |+---- OCIE1A
// | +----- OCIE1B
// +-------- ICIE1
OCR1A = 1023 + 4000; // PWM周期
OCR1B = 1023; // L区間
// タイマー2 : 1ms割り込み
TCCR2A = 0b01000010; // モード設定
// |||| ++--- WGM:CTC OCR2AがTOP値
// ||++------- COM2B 出力しない
// ++--------- COM2A PB3 トグル出力 500Hz
TCCR2B = 0b00000101;
// |+++--- CS:16MHz/128 8us
// +------ WGM02
TIMSK2 = 0b00000010;
// ||+---- TOIE2 オーバーフロー割り込み
// |+----- OCIE2A コンペアマッチA割り込み 有効
// +------ OCIE2B コンペアマッチB割り込み
OCR2A = 125 - 1; // 8us * 125 = 1ms
// A/D変換 割り込みでデータを受ける
ADMUX = 0b01000000; // 内蔵A/D
// ||| ++++---- ch0 VR1
// ||+--------- ADLAR
// ++---------- AVCC接続
ADCSRA = 0b10001111;
// |||||+++--- ADPS 16MHz/128=125kHz
// ||||+------ ADIE A/D割込有効
// |||+------- ADIF 変換完了フラグ
// ||+-------- ADATE A/D自動起動
// |+--------- ADSC 変換開始
// +---------- ADEN A/D有効
DIDR0 = 0b00000111; // デジタル入力禁止
// |||+++--- A/D ch2,1,0 A/Dに
// +++------ A/D ch5~3 I/Oポートに
// シリアル
Serial.begin(9600);
sei(); // 割込許可
}
/***** LOOP *****/
// 【1】の間に割り込が入るとカウント読み出し値に抜けが発生
void loop()
{
char c;
sprintf_P(tx_bff, PSTR("%8s %8s %8s %8s %6s %6s"), // データ区分
"cnt_up", "add1", "add2", "1-2", "PWM-L", "PWM-H");
Serial.println(tx_bff); // 1行シリアル出力
adok_cnt = 0; // A/D変換回数
while(adok_cnt < 5){ // 5回 0.96秒
pwm1set(); // A/D値でPWM出力
}
clrcnt(); // カウント値クリア
// 実行loop n1,n2カウントアップ処理
while(1){
if(cnt_n1){ // n1割り込みあった
cli(); // ★割り込み禁止して
cnt_add1 += (long)cnt_n1; // n1をadd1に加算
cnt_n1 = 0; // 次に備えてゼロクリア
sei(); // ★割り込み有効に戻す
if(cnt_add1 > 99999999) cnt_add1 = 99999999; // max規制
}
if(cnt_n2){ // n2割り込みあった
PB4_H; // (!!!)
cnt_add2 += (long)cnt_n2; // ★1割り込み許可のまま加算
// 【1】この間に割り込みがあると
cnt_n2 = 0; // このゼロクリアで抜けが発生
PB4_L; // (!!!)
if(cnt_add2 > 99999999) cnt_add2 = 99999999; // max規制
}
// 1秒ごとにカウント値をシリアル出力
if(tm_4ms == 0){ // 1秒経過
tm_4ms = 250; // 1秒プリセット
txcnt(); // カウント値シリアル出力
}
// A/D値でPWM出力を設定
pwm1set(); // 256msサイクルで
// CR受信でカウント値をクリア
if(Serial.available()){ // 受信データあり
c = Serial.read(); // 1文字読み出し
if(c == '\r'){ // CR ?
clrcnt(); // カウント値クリア(割込禁止で)
tm_4ms = 0; // タイマークリア
}
}
}
}

・前のと同じで、★1のところ、【1】に割り込みが入ったら
 パルス抜けが生じます。
 だから、ちゃんと割り込み禁止にして処理しなくちゃいけません。

※参:パルス抜けが生じるかも
ピンチェンジ割込みを使ってロータリーエンコーダーを読む (Arduino)
ロータリーエンコーダテスト 割り込みを使う場合

・タイマー1でPWMパルスを出力。
 TOP値(全体の周期)はOCR1Aで。
 OCR1Aを使うと、周期を変えた時にギクシャクしません。
 TOP値をICR1でセットできるモードもあるのですが、
 これは固定周期用になります。
 OCR1Aが2重バッファになっているので、更新時、
 TCNT1より小さくなっても、一週回りの0xFFFFを待つ
 ということがありません。
 ICR1を使うと、この一周回りが起こってしまい、ギクシャク
 するのです。

・このPWM波のL区間とH区間のパルス幅をボリュームで設定します。
 VR1でL区間のパルス幅を微調するようにして、1clk単位で設定
 できます。

・3つのVRの読み込みは1msタイマー割り込みでA/Dを起動。
 呼ぶたびに待たされるanalogRead()はキライです。
 64回加算して平均値を算出。
 これも割り込み内で処理しちゃうんで、メイン側は勝手に
 出てくる平均値を読み出すだけ。
   ただし、割り込み禁止にして。


・シリアル出力の様子

■徐々にLパルス幅を短く

 cnt_up   add1   add2   1-2 PWM-L PWM-H
    0    0    0    0 300us 2090us
   836   836   836    0 300us 2090us
  1674   1674   1674    0 285us 2090us
  2524   2524   2524    0 235us 2090us
  3398   3398   3398    0 175us 2090us
  4286   4286   4286    0 135us 2090us
  5196   5196   5196    0  80us 2090us
  6124   6124   6124    0  65us 2090us
  7054   7054   7054    0  50us 2090us
  7990   7990   7990    0  45us 2090us
  8930   8930   8930    0  30us 2090us
  9876   9876   9876    0  20us 2090us
  10824  10824  10821    3  15us 2090us
  11774  11774  11767    7  15us 2090us
  12724  12724  12709    15  15us 2090us
  13674  13674  13653    21  15us 2090us
  14624  14624  14599    25  15us 2090us
  15574  15574  15538    36  14us 2090us
  16526  16526  16475    51  13us 2090us
  17476  17476  17353   123  12us 2090us
  18428  18428  18056   372  11us 2090us
  19380  19380  18755   625  10us 2090us
  20332  20332  19453   879  10us 2090us
  ↑    ↑   ↑    ↑
  +-同じ-+ パルス抜け  差

cnt_up : longで加算
add1  : n1を割り込み禁止で加算
        cnt_upとadd1は同じ値
add2  : n2を割り込み許可のまま加算
      競合するとパルス抜けが生じる


※続き 「読み」と「書き」に分けて検証
Arduino-UNO 割り込み処理のミスあれこれ:割り込み禁止にして読まないとダメよ
Arduino-UNO 割り込み処理のミスあれこれ:割り込み禁止にして書かないとダメよ
   ↑
  この2つは8bitマイコン特有の注意点

| | コメント (2)

2022年3月16日 (水)

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

Arduino-UNO 割り込み処理のミスあれこれ:ロータリーエンコーダー
の中で示した、重大なバグにつながる落とし穴の手順
~~~~~~~~~~~~~~~~~~~~~~~~~~
 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マイコンでも
  発生します。
~~~~~~~~~~~~~~~~~~~~~~~~~~  

この「パルス抜け」が発生する様子を確かめるスケッチを
書いてみました。
  ・・・ちょっと長いですが

/*****  割り込み処理ミスの検証     *****/
// cnt + n; n = 0; 処理間での割り込みとの競合をチェック
// 【1】に割り込みが入ると「n2」がミスする
// PD2 パルス入力 ↓エッジ
// PB1 10kHz方形波出力
// PB3 125Hz方形波出力
// 1秒サイクルでカウント値を出力
// CR入力でクリア
// cnt_up, cnt_add1は割り込み禁止で処理
// cnt_add2を割り込み有効のままで計数
/***** I/O MACRO *****/
#define PB0_H (PORTB |= (1 << PB0)) // (!!!)PB0 H/L
#define PB0_L (PORTB &= ~(1 << PB0))
#define PB2_H (PORTB |= (1 << PB2)) // (!!!)PB2 H/L
#define PB2_L (PORTB &= ~(1 << PB2))
#define PB5_H (PORTB |= (1 << PB5)) // (!!!)PB5 H/L
#define PB5_L (PORTB &= ~(1 << PB5))
/***** データ *****/
// 加算データ
volatile long cnt_up; // 割込み内でカウントアップ
volatile int8_t cnt_n1; // 同じだが1バイトでカウントアップ
volatile int8_t cnt_n2; // 割り込み禁止なし用
long cnt_add1; // loop内でcnt_n1,n2を加算
long cnt_add2; // n1は割り込み禁止で
// n2は割り込み有効のままで処理
// タイマーデータ
volatile byte tm_4ms; // 4msタイマー割り込みでダウンカウント
// 250で1秒
// シリアル出力バッファ
char tx_bff[64]; // sprintfで出力文字を設定
/***** INT0(PD2) ↓エッジパルス割り込み *****/
ISR(INT0_vect){
PB0_H; // (!!!)
if(cnt_up < 99999999) cnt_up++; // 8桁maxで+1
cnt_n1++; // loop内でcnt_add1に加算
cnt_n2++; // cnt_add2に加算
PB0_L; // (!!!)
}
/***** タイマー2コンペアマッチA割込み *****/
// 250Hz 4mサイクル
ISR(TIMER2_COMPA_vect){
if(tm_4ms) tm_4ms--; // 4msタイマーダウンカウント
}
/***** カウント値シリアル出力 *****/
// cnt_up, add1, add2 ,差 の4データを出力
// 割り込み禁止でカウント値をコピー
void txcnt(void){
long d0, d1, d2;
PB5_H; // (!!!)
cli(); // ★いったん割込禁止に
d0 = cnt_up; // カウント値longデータ
cnt_add1 += (long)cnt_n1; // n1の残値をadd1に加算
cnt_n1 = 0; // 次に備えてゼロクリア
cnt_add2 += (long)cnt_n2; // n2をadd2に加算
cnt_n2 = 0; // ゼロクリア
d1 = cnt_add1; // 割込禁止でのカウントアップ
d2 = cnt_add2; // 割込禁止しないで
sei(); // ★割込再開
sprintf_P(tx_bff, PSTR("%8ld %8ld %8ld %8ld"), // 8桁で表示
d0, d1, d2, d1 - d2);
Serial.println(tx_bff); // 1行シリアル出力
PB5_L; // (!!!)
}
/***** セットアップ *****/
// ATmega328Pのレジスタを直接制御
void setup(){
cli(); // 割込禁止
// I/Oイニシャル
PORTB = 0b00000000; // data/pull up
DDRB = 0b00111111; // port指定
// |||||+---- PB0 IO8 out TEST 割り込みでパルス
// ||||+----- PB1 IO9 out OC1A出力 10kHz
// |||+------ PB2 IO10 out TEST カウントでパルス
// ||+------- PB3 IO11 out OC2A出力 125Hz
// |+-------- PB4 IO12 out
// +--------- PB5 IO13 out (LED) シリアル送信タイミング
PORTD = 0b00000111;
DDRD = 0b11111010;
// |||||||+---- PD0 RXD in
// ||||||+----- PD1 TXD out
// |||||+------ PD2 IO2 in INT0↓エッジをカウント
// ||||+------- PD3 IO3 out
// |||+-------- PD4 IO4 out
// ||+--------- PD5 IO5 out
// |+---------- PD6 IO6 out
// +----------- PD7 IO7 out
// INT0 ↓エッジ割り込み (PD2)
EICRA = 0b00000010; // 外部割り込み
// ||++---- ISC0 INT0 立ち下がりエッジ
// ++------ ISC1 INT1 Lレベル
EIMSK = 0b00000001;
// |+---- INT0 割り込み有効
// +----- INT1 割り込み未使用
// タイマー1 PB1に10kHz方形波出力
TCCR1A = 0b01000000;
// |||| ++---- WGM11,10 CTCモード
// ||++-------- COM1B
// ++---------- COM1A (PB1トグル出力)
TCCR1B = 0b00001001;
// || ||+++---- CS 1/1=16MHz
// || ++------- WGM13,12 CTC
// |+---------- ICES1
// +----------- ICNC1
TIMSK1 = 0b00000000; // 割り込み
// | ||+--- TOIE1
// | |+---- OCIE1A
// | +----- OCIE1B
// +-------- ICIE1
OCR1A = 800 - 1; // 20kHz PB1は1/2で10kHz
// タイマー2:4ms割り込みとPB3に125Hz方形波出力
TCCR2A = 0b01000010; // モード設定
// |||| ++--- WGM:CTC OCR2AがTOP値
// ||++------- COM2B (出力しない)
// ++--------- COM2A (PB3 トグル出力)
TCCR2B = 0b00000110;
// |+++--- CS:16MHz/256 16us
// +------ WGM02
TIMSK2 = 0b00000010;
// ||+---- TOIE2 オーバーフロー割り込み
// |+----- OCIE2A コンペアマッチA割り込み 有効
// +------ OCIE2B コンペアマッチB割り込み
OCR2A = 250 - 1; // 16us * 250 = 4ms
// シリアル
Serial.begin(9600);
sei(); // 割込許可
}
/***** LOOP *****/
// 【1】の間に割り込が入るとカウント読み出し値に抜けが発生
void loop()
{
char c;
sprintf_P(tx_bff, PSTR("%8s %8s %8s %8s"), // 8桁で表示
"cnt_up", "add1", "add2", "add1-2");
Serial.println(tx_bff); // 1行シリアル出力
while(1){
if(cnt_n1){ // n1割り込みあった
cli(); // ★割り込み禁止して
cnt_add1 += (long)cnt_n1; // n1をadd1に加算
cnt_n1 = 0; // 次に備えてゼロクリア
sei(); // ★割り込み有効に戻す
if(cnt_add1 > 99999999) cnt_add1 = 99999999; // max規制
}
if(cnt_n2){ // n2割り込みあった
PB2_H; // (!!!)
cnt_add2 += (long)cnt_n2; // ★1割り込み許可のまま加算
// 【1】この間に割り込みがあると
cnt_n2 = 0; // このゼロクリアで抜けが発生
PB2_L; // (!!!)
if(cnt_add2 > 99999999) cnt_add2 = 99999999; // max規制
}
// 1秒ごとにカウント値をシリアル出力
if(tm_4ms == 0){ // 1秒経過
tm_4ms = 250; // 1秒プリセット
txcnt(); // カウント値シリアル出力
}
// CR受信でカウント値をクリア
if(Serial.available()){ // 受信データあり
c = Serial.read(); // 1文字読み出し
if(c == '\r'){ // CR ?
cli(); // ★いったん割込禁止に
cnt_up = 0; // カウント値クリア
cnt_n1 = 0;
cnt_n2 = 0;
cnt_add1 = 0;
cnt_add2 = 0;
tm_4ms = 0; // タイマークリア
sei(); // ★割込再開
}
}
}
}

INT0入力で↓エッジのパルス割り込み
  long値cnt_upと1バイト値cnt_n1とcnt_n2を+1。
・1秒ごとに、4つの数値をシリアル出力します。

~~~~~~~~~~~~~~~~~~~~~~
【125Hz】
 cnt_up   add1   add2  add1-2
    0    0    0    0
   125   125   125    0
   250   250   250    0
   375   375   375    0
   500   500   500    0
   :

【10kHz】
 cnt_up   add1   add2  add1-2
    5    5    5    0
  10000  10000  10000    0
  20000  20000  20000    0
  30000  30000  30000    0
  40000  40000  40000    0
   :
~~~~~~~~~~~~~~~~~~~~~~

「cnt_up」はINT0割り込みでカウントアップするlong値。
「add1」と「add2」は、INT0割り込みでカウントアップする
1バイト値を、メインループ内で加算して得られるlong値。
加算処理時、add1は割り込み禁止で加算
add2は割り込み有効のまま加算
このため、add2ではパルス抜けが生じるかも、となります。
「add1-2」はadd1とadd2の差です。

ちゃんと処理してるcnt_upとadd1は同じ値になりますが、
割り込み禁止にしていないadd2はパルス抜けが生じて、
その差「add1-2」の値がどんどん大きくなっていきます。

リストの【1】部分がそれ。
ここに割り込みが入るとパルス抜けが発生します。
  ★の所で割り込み禁止と再許可

ハード的には、Arduino-UNOをこんな具合に。
A1_20220316112801
PB1に10kHzの方形波、PB3に125Hzの方形波を出しているので、
これを使ってパルス入力します。

125Hzも10kHzも、ふつうにつないだだけでは割り込み処理が
十分に追いついているので安定してカウントが進みます。
カウント値のシリアル出力やシリアル出力割り込み、タイマー
割り込みなどで、メインループでのカウント処理が遅れること
はありますが、カウント値のミスは出てきません。

ところが・・・
INT0入力クリップを離したり付けたりして
チャタリングを発生させると、一発でアウトに。

チャタリングがあってもcnt_upとadd1は同じ値を維持
していますが、add2がパルス抜けを起こして、
差の値「add1-2」がどんどん大きくなってきます。

~~~~~~~~~~~~~~~~~~~~~~
【チャタリングあり】
 cnt_up   add1   add2  add1-2
    0    0    0    0
  1397   1397   1384    13
  2805   2805   2781    24
  4209   4209   4169    40
  5616   5616   5562    54
   :
~~~~~~~~~~~~~~~~~~~~~~

オシロで見ると、パルス抜けのタイミングはこんなふうになっ
ています。
   ランダムなチャタリングでなく、見やすいような
   パルスにしています。
A2_20220316121301

2発目のパルスエッジ、割込は応答してちゃんとカウント
していますがメインループでの加算処理と重なってしまい、
「cnt_n2」がゼロクリアされ、結果的にパルス抜けが
生じています。

cnt_upとadd1が一致しているということで、
割り込みを禁止してのあれこれ処理、重要なのがお分かり
いただけたかと。。。


※チャタリングへの応答
ランダムなチャタリングパルスへの応答、パルス全部を捉えられる
わけではありません。
そこは割り込み処理の応答速度です。


※補足
Arduinoでの割り込み禁止と割り込み許可は、
noInterrupts()」と「interrupts()」を使えという
のが標準です。 が・・・
「Arduino.h」を見ると、こんな記述が
  #define interrupts()  sei()
  #define noInterrupts() cli()
「sei」と「cli」はAVRマイコンでのアセンブラ・ニーモニック。
Arduino-UNOのATmega328Pを制御するんであれば・・・
使ってヨシ!。 ということで。

※補足
テスト用パルスを出している
  #define PB0_H (PORTB |= (1 << PB0))
  #define PB0_L (PORTB &= ~(1 << PB0))
この記述で、ポートに対するビット操作命令を生んでくれます。
ポートをダイレクトにH/LするSBI」「CBI」命令に置き換わるので、
最高速です。
割り込み処理確認に出しているテストパルスに「digitalWrite()」
なんて、まどろっこしくて使えません。
Arduino-UNOでtone()の挙動を調べる
toneパルスの異常 もうちょっと掘り下げて
digitalWriteの秘密をもうちょっと

※補足  これ↓、何度も言っておきます。
Arduinoのタイマー OCRレジスタは「n」じゃなく「n - 1」の値を設定せよ

※関連
ロータリーエンコーダーの2相パルスをピン変化割り込みで取り込む
Arduinoでロータリーエンコーダーを使う:ラジオペンチ
ピンチェンジ割込みを使ってロータリーエンコーダーを読む (Arduino):ラジオペンチ

※続き
Arduino-UNO 割り込み処理のミスあれこれ:パルス計数の抜けを確かめる その2

| | コメント (0)

2022年3月14日 (月)

Amazonベーシック単3(2000mAh) 60%(72分)放電 1200回で終了

2021年9月24日:東芝TNH-3ME(1900mAh) 新JISでの充放電実験終了:突然死で
これで実験回路が空いたので、次の実験として、
  「Amazonベーシック単3(2000mAh)の60%(72分)放電」
を試していました。
「放電深度を60%から100%にしたら寿命が10~15%に減る」
これ↑の検証です。

スタートしてから半年。
1200サイクル目で終了します。
11_20220314095001

まず、50サイクルごとの0.2C(400mA)放電の様子。
Cap028
JISでは、この放電時間が「180分」を切ったら「寿命」と
判断します。

毎サイクルの充放電時間と充電停止電圧をグラフにすると、
劣化が進行する様子がよくわかります。
Cap029
0.5Cでの充放電ですんで電流は「1A」。
充電停止電圧の変化から、徐々に内部抵抗が上昇する様子が
浮かんできます。
内部抵抗は189mΩまで上昇していました。


Amazonベーシック、通常のJIS C8708:2019によるサイクル充放電
での結果はこれ↓
 ・Amazonベーシック 2000mAh単3ニッケル水素電池 800サイクル目で終了
Amz_vt_20220314095001
この時は、800サイクルまで続けましたが、こんな評価かと。
 ・200サイクルまでは劣化を感じない。
   でも、300サイクルを越えると、ちょっと・・・
 ・エネループ・プロや東芝インパルス、富士通・黒のように
  突然死はしなかった。

今回の72分放電では、891サイクル目の放電で72分を切りました。
冬場なので、仕事場の暖房など、温度変化が大きな試験環境でした。
後半になると、温度の影響でグラフの変動が大きくなっています。
「突然死」せずに寿命を全うした感じです。

| | コメント (0)

2022年3月12日 (土)

「スーパー玉出」の親会社が・・・

ガレージの冷蔵庫・食料庫であるご近所の「スーパー玉出」、
ここが「イセ食品」に買収されたのが2018年7月。
  ・大阪名物の激安スーパー「玉出」買収 鶏卵大手「イセ食品」会長出資の系列会社

そのイセ食品が『会社更生手続き』とのニュースが入ってきました。
  ・イセ食品が会社更生手続き 鶏卵大手「森のたまご」

さて、スーパー玉出、どうなるんでしょう?


| | コメント (3)

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; 」での抜けを確かめる


| | コメント (1)

2022年3月10日 (木)

Arduino-UNO 割り込み処理のミスあれこれ:計時処理

皆さんのスケッチを見ていて、どうしても一言いたくって・・・
「割り込み禁止での排他制御」、重要です。

■誤表示の可能性
 タイマー割り込みで時計処理していて、読み出し時に
割り込み禁止していないので、桁上がりタイミングと
表示処理が重なると誤表示の可能性があります。
 ・Arduinoでタイマー割り込み:Lightning Brains

▲スケッチ
// 経過時刻表示
void loop() {
 char buf[18]; // 表示バッファ
 sprintf(buf, "%02d:%02d:%02d.%03d", hh, mm, ss, ms);
 setCursor(2, 1);
 sendText(buf, strlen(buf));
}
 ※時刻データ「hh, mm, ss, ms」は1msのタイマー割り込みで
  カウントアップ。

▲状況
sprintfで4つの時刻データ(int値)を文字列に変換しています。
  ※「hh, mm, ss, ms」の読み出しはどのタイミングで
   行われるか、どの順番でされるのか分かりません。

「hh, mm, ss, ms」の順だと仮定して、
23:59:59.999」の時に「時と分」まで読み出したとすると、
文字列bufには「23:59」が入ります。
この直後にタイマー割り込みが入って1ms桁がカウントアップ
したら時刻データは「00:00:00.000」になります。
すると、「時分」に続く時刻データ「ss,ms」の読み出しは
00.000」となってしまい、文字列bufは
23:59:00.000」となり、ミス表示が出現します。

しかし、loop()処理だからすぐに次の表示が行われるので、
このミスには気が付きにくいでしょう。
表示が「チラ」と変化するだけですから。

▲対策
割り込み禁止状態「cli()実行」にして「hh, mm, ss, ms」を
別の変数にコピーします。
その後、「sei()」で割り込み有効状態に戻します。
そして、sprintfにはコピーした変数を食わせます。
コピーした値は割り込みが入っても変化しません。

面倒でもこうしないと、いつミスが生じるかわかりません。
表示だと実害は無いかもしれませんが、タイマー値を何かの制御
や計算に使っていたら「たまにおかしくなる」が発生します。
見つけにくいバグになってしまいます。

▲補足
これは8bitマイコンの1バイトでしか読み書きできない問題
ではなく、16bit、32bitマイコンでも発生するミスです。

▲補足2
もう一つ気になるのが、アウトプット・コンペアレジスタの
設定値です。
  OCR1A = 62500;
  OCR1A = 250;
のように、分周値をそのまま入れられてます。
「分周値 - 1」つまり「TOP値」でないと、1クロック余計に
カウントしてしまい、正しい周波数(周期)が得られません。

▲参考に
『アトミック操作』・・・8bitマイコンに限り何か別の言い方なかったか?
割り込みで処理させるwordデータの扱い
Arduinoのタイマー OCRレジスタは「n」じゃなく「n - 1」の値を設定せよ
Arduinoのタイマー処理
Arduinoから「タイマー0」を取り上げる(ユーザーが使う)



※体言止めを書き直しました。

| | コメント (0)

温湿度センサーDHT11、ライブラリを使うと氷点下の温度がおかしくなるぞ!

出窓の結露対策回路、春めいてきたのでそろそろお片付け
ということで、製作時の記事へのリンクを示してしておきます。

2021年12月7日:湿度センサー「DHT11」がやってきた・・・けど!?
   ↑
氷点下での温度計算異常の発生を確かめています。

これに気が付かない原因・・・
  ・昔からあるライブラリにミスは無いだろう。
     それが、甘い!
  ・みんな、使っているし。
     氷点下まで温度を下げたことあるのかな?
  ・動いているし。
     暴走せずにそれなりの値は出てくるけど。
  ・欲しいのは湿度。 温度はおまけ。
     でもみなさん、温度を使ってるし。
  ・DHT11で氷点下なんて使わないぜ。
     屋外のデータを得ようとしたら、大阪でも
     氷点下になりますぜ。
  ・1℃桁が合ってればヨシ!
     みんな、1℃で満足してないでしょ。

みなさんが使っておられるライブラリに関する言及記事をピックアップ。
Arduino(アルドゥイーノ)始めます:ライブラリのインストールと温湿度センサDHT11をつかう
Arduinoで温度湿度センサDHT11の使い方
温度・湿度センサーDHT11を使ってArduinoで温度と湿度を計測する
などなど・・・

| | コメント (1)

2022年3月 9日 (水)

『最悪の予感 パンデミックとの戦い』

マイケル・ルイス 著 中山宥 訳
  ・『最悪の予感 パンデミックとの戦い』

11_20220309085801

ありがたや・・・図書館。
 ノンフィクションです。
   一気読みしました。

「パンデミック」に備えていたはずが・・・
  トランプ政権がぐちゃぐちゃにしてしまった。

そんなアメリカでのお話しです。

日本での「客船ダイヤモンド・プリンセス」クラスターの
話も出てきます。

たまたま大統領が悪かった。
「いて欲しかったのはチェンバレンではなくチャーチルだ」っと。

| | コメント (0)

2022年3月 8日 (火)

DHT11を使った出窓の結露対策用ヒータ制御回路 この1週間

3月になって、日差しも長くなり、そろそろ出窓の「結露」も終わりかと。
この1週間の様子です。
H31
「温度15℃以下」の時に「湿度70%以上」でヒーターをオン。
という制御にしています。

一番下の青線がヒーター駆動。
ヒーターonのとき、温度と湿度の波形幅が
広がっている(ヒステリシスでハンチング)様子が
見えています。


| | コメント (0)

2022年3月 5日 (土)

プリント基板用リレーのピン番号

真空管のピン配置は1番から右回りに2,3,4・・・
それがDIP ICだと、左下の足が1番で左回りに2,3,4・・
重要なのが、真空管は「BOTTOM VIEW」で下から足を見ます。。
DIP ICになると「TOP VIEW」となって上から見たところ。
昔話になりますが、プリント基板用リレーのピン番号で混乱した
ことありませんか?

例えばオムロンのG5V-1リレー。
カタログから外観をピックアップ。
Aa1_20220305123801
その端子配列がこれ。
Aa2_20220305123801
BOTTOM VIEWで記されています。
ICのTOP VIEWに慣れてきた頃に、BOTTOM VIEWの絵が出てくると
ちょいと混乱します。 (・・・混乱しました)

G5V-1のコイルには極性がないので間違って反対にしてもリレーは
駆動します。
しかし、A接点とB接点が逆になって「あれれ?」な状態に。
   手組みの試作だと修正できるので良いのですが、
   ピン番を勘違いしてプリント基板を起こしちゃ
   うとアウト!

サーフェスマウント端子のリレーを使ったときに気が付いたのが
端子配置の表現。
   ※使ったのはオムロンG6Kという2回路の小型リレー。

足が下に出たプリント基板用端子のリレーはBOTTOM VIEWで示されて
います。
ところがサーフェスマウント端子になると、ICと同じようにTOP VIEW
なっています。
どちらも同じなのですが、きちんとピン番号を確認せず「ぱっと見」で
組んでしまうと「あらら」。
みなさん、勘違いしたことありませんか?

これがプリント基板用端子のG6Kリレー。

Aa3_20220305123901

ピン番号はこれまでどおりにBOTTOM VIEWで描いてあります。

Aa5

サーフェスマウント端子になると・・・

Aa4_20220305124002

ピン番号の表記の方法がTOP VIEWに。

Aa6

同じ品種のリレーでも、実装方法の区別でピン番表記の向きが
異なります。
  ※表記の上下方向が変わるだけで、同じものですよ。

G6Kのコイルには極性があるの、誤って上下を逆に見てしまうと
リレーが動きません。
  ※でも、このリレーの場合、A接、B接は大丈夫。


※似たような話
GP-IBコントローラ TMS9914のピン名称


| | コメント (2)

2022年3月 4日 (金)

ありゃま「多治見無線」の丸形コネクタ、納期が「1年」

仕事で使う多治見の丸形コネクタ、ネットで探しても「在庫ゼロ」ばかり・・・
お付き合いしてるパーツ屋さんから聞いてもらったら・・・
「納期約12ヶ月」と・・・
Aa11_20220304085401
「ありゃま」っす。

| | コメント (0)

« 2022年2月 | トップページ | 2022年4月 »