Arduino

2024年7月 2日 (火)

ひさしぶりのバージョンアップ:チャートレコーダ

プリンタシールドでチャートレコーダ、このとっ
かかりが2014年1月
  ※これがしたかった を実現したのです。
そして、トランジスタ技術に載せてもらったのが
  ・2014年6月号のハンディ・チャート・レコーダ

Pp4

その後、このブログ記事にもあれこれ登場していまして
オシロやテスターなみに重要なツールになっています。
入力電圧レンジを合わせ、紙送り速度を決めたら勝手に
記録してくれるお手軽さ。
停電しても、電源が復旧したら記録を続行という安心感。

で、かれこれ10年になるわけですが、ちょっとだけ
機能をアップしました。
これが旧回路。
Nada_prn2b
そして新回路。
Nada_prn3

・ロータリDipSWで紙送り速度を設定していたのを
 Fast、Slow SWで設定。
   設定はEEPROMに記録

・10段階の紙送り速度設定だったのを13段階に。

・2つのイベント・マーカー入力を追加。

手直しの主目的がこのイベント・マーカー機能。
2chのアナログ波形とともに、デジタル入力の
on/offをチャートの上部に記録したいという
わけです。
このように記録。
Pxx2


外部に置いたセンサーからのon/off信号。
その状態でアナログ値がどう変化するのかを
同時に観察したかったのです。

スケッチをアップしておきます。
  ※「.ino」ではなく「.txt」にしています。
    ダウンロード - prnsld03.txt

 

| | コメント (0)

2024年6月 6日 (木)

最適化処理のせいで悩んだぞ 呪文volatile再び

割り込みに絡む処理で、「C」が良かれと思って
やった最適化処理のせいで悩んでしまいました。

簡略化して説明するとこんな流れ。
  intr_aは割り込み内で処理されるデータ。
  2バイト値なんでメイン側では割込禁止で書き込む
  必要がある。
  hoge()という関数の中でそれを実行する。

~~~~~~~~~~~~~~~~~~~~~~~~~
// 割り込みで読み出し処理される2バイトデータ
volatile int intr_a;

// aをあれこれ計算してintr_aを更新
void hoge(int a)
{
int d;      ★1 一時変数
  d = a * なんたら (1)
  d += あれこれ   (2) そこそこ時間のかかる計算
  cli();       // 割込禁止にして
  intr_a = d;  (3)  // intr_aに計算結果を書いて
  sei();       // 割込有効に戻す
}            // リターン
~~~~~~~~~~~~~~~~~~~~~~~~~

走らせてみると、思いのほか割り込みが禁止されて
いる時間が長いのです。
割り込み禁止期間は計算結果を書く(3)の一瞬だけ
のはず。
しかし、禁止時間から考えて計算途中も割り込み
禁止になっている様子でなのです。

その原因は最適化処理。
別行に書いている(1)と(2)の処理を、(3)のところに
コンパイラがまとめてしまっていたのです。

Cコンパイラの気持ち:
  「一時変数なんか使わんでも処理できるぜい」と

そのため、計算が始まる直前に割込禁止にして
しまっていて、時間のかかる計算が割り込み
禁止状態で行われていたのです。

で、対策。
・その1
  一時変数int dの前に「volatile」を前置。
  これで(1)(2)(3)が記述どうりの順序で進む
  ので、(3)だけが割り込み禁止になります。

・その2
  hoge()を戻り値を持たせるようにして、
  その値を割り込み禁止でintr_aに書く。
  int hoge(int a) とし、hoge()の中で
  intr_aを書くのではなく、外の処理として
    b = hoge(a);
    cli();
    intr_a = b; (4)
    sei();
  とすると、(4)だけが割り込み禁止になって
  思惑どうりに処理が流れます。
    ひょっとしたらこのbにもvolatileが
    必要かもしれない。

※過去記事
2018年10月11日:魔法の言葉「volatile」

割り込みで直接操作されるintr_aはvolatileを付けなけりゃ
と注意します。
しかし、処理の流れが重要なら、計算途中で使う一時変数に
も付けておかないと、ということです。

「割り込み禁止時間が長いぞ」・・・この発見は
テストパルスをオシロで見てしかわかりません。
答えは合ってるし。


※スペルのミスを訂正 (恥ずかしいぞ)

| | コメント (0)

2024年5月 3日 (金)

数値をBCD出力(表示)するルーチン #3

あれこれ紹介してきましたBCD出力ルーチン。
 ・2022年10月8日:数値をBCD出力(表示)するルーチン
 ・2024年4月28日:数値をBCD出力(表示)するルーチン #2

除算・剰余方式ではどうしても「遅れ」が目立ちます。
そこで、float値を文字変換する「dtostrf()」と処理時間を
比べてみました。

まずこれが「strbcd02.ino」:除算・剰余方式の速度。
 ch1が自作ルーチンstrbcd()の速度
 ch2がdtostrf()の速度
 ch3はstrbcdの中で使っている除算の時間です。
   (商と剰余の両方を得ている時間)
 ch4は液晶表示のタイミング。

「0」を表示したところ。
01_0

上位はゼロサプレスされるんで、
7文字のスペース+'0'という表示です。

「9999」の変換。
01_9999

上位4文字がゼロサプレス。
4回の除算が実行されていて、除算に時間が
かかっていることが見えます。

さらに「99.99999」の変換。
01_99r99999
  ※上の二つと時間軸が異なります。
ゼロサプレスはありません。
全桁で0~9に数字を得るために除算が7回実行
されています。
こうなるとdtostrf()が圧倒的に有利。

そこで、除算と剰余の計算でなく、上位桁から
10進数値を減算できるかどうかで、剰余を得る
方法を試してみました。

たとえば、「1234 = 0x04D2」を変換する手順。
・千桁 1234から1000=0x03E8 を何回引けるか → 1回 余り234
・百桁 234から100=0x0064 を何回引けるか → 2回 余り34
・十桁 34から10=0x000A を何回引けるか → 3回 余り4
・一桁 余り=4
これで「1234」とBCD値が得られます。
比較と加減算ですので除算より高速に処理できますが、
数値が「9」だと9回の比較・加減算が生じます。
ループ回数が増えると、これも時間がかかります。

strbcd03.ino

/********************************/
/* BCD文字出力処理 */
/********************************/
/***** BCD出力用文字バッファ *****/
char bcd_bff[16]; // 16文字 終端のnull含めて
// longは10桁
// BCD変換4bitデータ
byte bcd_4bit[10]; // 10桁の4bitバッファ
// 下位が10^0桁
// 先頭が10^9桁
// 減算データ
const int32_t bcd_chks[] PROGMEM ={ // 32bitは10桁
1000000000, // 0 10桁 元データからいくつ
100000000, // 1 9桁 引けるかを調べて
10000000, // 2 8桁 余りを次の桁へ
1000000, // 3 7桁
100000, // 4 6桁
10000, // 5 5桁
1000, // 6 4桁
100, // 7 3桁
10, // 8 2桁
}; // 1の桁は引かない 最後の余り

/***** 桁指定BCD文字出力 *****/
// d:変換データ long値
// n:整数部文字数(-を含めて)
// p:小数部文字数(.は含まない)
// 数値は整数を入力 浮動小数点ではない
// 小数部は整数の基数が0.01などの時に用いる
// 12345が123.56を意味する時 (d,3,2)と指定
// -1234を-123.4と表示するときは(d,4,1)と-を含めた文字数に
// bcd_bffに入った文字の先頭アドレスを持ってリターン
char *strbcd(int32_t d, byte n, byte p)
{
byte i, j;
byte sgn; // 符号フラグ
byte mins = 0; // マイナス符号処理済みフラグ
uint32_t e; // 減算値
char *s; // 0~9文字書込みバッファのアドレス
byte *t; // bcd_4bit変換データアドレス
if(d < 0){ // マイナス?
d = -d; // +の値にして
sgn = 1; // -フラグをオン
}
else{
sgn = 0;
}
// 引き算できるかどうかで余りを計算
for(i = 0; i < DIMSIZ(bcd_chks); i++){ // 桁数loop
e = pgm_read_dword(&bcd_chks[i]); // 減算値
j = 0; // 引ける数をカウント
while(1){
if((uint32_t)d < e) break; // 引けなければおわり
j++;
e += pgm_read_dword(&bcd_chks[i]); // 比較値を加算
}
bcd_4bit[i] = j; // 引けた数が余り
d -= (e - pgm_read_dword(&bcd_chks[i])); // 元値を減少
}
bcd_4bit[sizeof(bcd_4bit) - 1] = d; // 最下位 1の桁
// 変換した桁数をチェック ゼロサプレスのため
t = bcd_4bit; // 変換データの先頭
j = sizeof(bcd_4bit); // 変換データ数
while(j){
if(*t++ != 0) break; // 0じゃない
j--; // 0なら-1
} // jは0以外の数字の数
if(j == 0) j = 1; // 0なら1に
// 文字バッファ終端 nullをセット
i = n + p; // 全体の文字数
if(p) i++;
s = bcd_bff + i; // 文字バッファの最後尾
*s = '\0'; // nullをセット
// 4bitの変換データを文字にしながらコピー
t = bcd_4bit + sizeof(bcd_4bit); // 変換データの最後
if(p){ // 小数部あり
for(i = 0; i < p; i++){
*--s = *--t + '0'; // 0~9を書込み
if(j) j--;
}
*--s = '.'; // 小数点
}
for(i = 0; i < n; i++){ // 整数部
if(j){ // 0以外の数が残っている
*--s = *--t + '0'; // 0~9を書込み
j--;
}
else{ // 必要数字を全部書き込んだ
if((p != 0) && // 小数部あり
(i == 0)){ // .の左隣に
*--s = '0'; // ゼロを付加
}
else{
if((sgn != 0) && // マイナス
(mins == 0)){ // -符号処理まだ
*--s = '-'; // マイナスを付加
mins = 1;
}
else{ // 2桁目以降でゼロ
*--s = ' '; // ゼロサプレス
}
}
}
}
return s;
}

さっきと同じようにdtostrf()との速度比較。
「1」を表示したところ。
03_1

「9999」の変換。
03_9999

さらに「99.99999」の変換。
03_99r99999

なんとかdtostrf()に追いついている感じです。

この方式で有利なのが、プログラムサイズ。
それぞれのルーチンを使ったときのROMサイズを
見てみましょう。

メモリ比較 (strbcd03.ino)

 strbcd() dtostrf()  ROM RAM
-----------------
(1)  ○   ○   5900 359
(2)  ○   -   3738 335
(3)  -   ○   5348 333
   --------------
    ROM (3)-(2) = 1610

(2)dtostrfなどfloatを使わないようにした。

dtostrf()やfloatを除いてstrbcd()だけで処理すると
1600バイトほどプログラムの必要ROM容量が減りました。

strbcd()ルーチンそのもの実行ルーチンROMバイト数は、
  除算・剰余方式  :278バイト
  比較・加減算方式 :374バイト
でした。
比較・加減算方式でほ実行ルーチンの他に10桁~2桁の
加算データテーブルが36バイト必要です。

遅くても良いのでプログラムを短くしておきたいなら
「除算・剰余方式」。
ちょっとでも速くというのなら「比較・加減算方式」という
ところでしょう。
「ルーチンをまとめるのが面倒」「ROMに余裕はあるので
既設の関数で」というのなら「float」で「dtostrf()」を。
あれこれ、探ってみましたがこんなところかと。

| | コメント (0)

ダイソー SHOOTING LIGHT:撮影用ライト LEDの輝度変化を探る #2

スタートしたのが2024年1月24日。
  ・ダイソー SHOOTING LIGHTLED:撮影用ライト LEDの輝度変化を探る
9パラされたLEDのうち端っこの一つを取り外して点灯。
100日間点灯しっぱなしでした。
  100日を経過しましたが、劣化のきざしがないので
  (面白くないなぁ)これで、実験を終わります。

LEDの駆動電流は定電流回路で30mA。
輝度はローム照度センサーBH1603で検出。
Arduino UNO R3(のチップ)でA/D変換。
毎日1回、A/D値を内蔵EEPROMに記憶してきました。

結果、ほぼ変化無し
Cap002_20240503090001
8週目あたりに段差がありますが、置いていた場所が
資料棚の上で、ゴソゴソしてたときにセンサー部
(塩ビ管)を落っことしたのが原因。

100日間、点灯しっぱなしで劣化はなし、という評価かと。

「もっと電流を大きくしたらどうなるか?」、
興味はありますが、スペックの不明なLEDを
これ以上いじめても、面白くないでしょう。
回路資料などは↑のリンクを。

  ・LEDの劣化・・・まとめ

| | コメント (0)

2024年4月28日 (日)

数値をBCD出力(表示)するルーチン #2

2022年10月8日:数値をBCD出力(表示)するルーチン
の改良版ということで、文字出力・表示ではなく
BCDの文字列に変換するようにしました。

txbcd()では変換結果の文字を、関数の中でいきなり
シリアル出力していたんで、同じデータを液晶に
表示したいときは、別の処理をしなくちゃなりま
せんでした。
変換結果を文字列にすると、同じ値をシリアル出力と
液晶表示に送り込みたいとき、変換操作が一度ですみます。

BCDへの文字変換ということで「strbcd()」という関数名
にしました。
使い方はほぼ同じです。
変換結果が入った文字列の先頭アドレスを持ってリターンす
るのでそのままSerial.printやLCD.printに渡せます。

/********************************/
/* BCD文字出力処理 */
/********************************/
/***** BCD出力用文字バッファ *****/
static char bcd_bff[16]; // 16文字 終端のnull含めて
// longは10桁
/***** 桁指定BCD文字出力 *****/
// d:変換データ long値
// n:整数部文字数(-を含めて)
// p:小数部文字数(.は含まない)
// 数値は整数を入力 浮動小数点ではない
// 小数部は整数の基数が0.01などの時に用いる
// 12345が123.56を意味する時 (d,3,2)と指定
// -1234を-123.4と表示するときは(d,4,1)と-を含めた文字数に
// bcd_bffに入った文字の先頭アドレスを持ってリターン
char *strbcd(int32_t d, byte n, byte p)
{
byte i;
byte sgn = 0; // 符号フラグ
byte zr = 0; // ゼロデータ処理済みフラグ
char *s; // 0~9書込みバッファのアドレス
s = bcd_bff+sizeof(bcd_bff) - 1; // バッファの最後尾
*s-- = '\0'; // 最後にnullをセット
if(d < 0){ // マイナス?
d = -d; // +の値にして
sgn = 1; // -フラグをオン
}
// 小数部 '.'まで
if(p){ // 小数部あり
for(i = 0; i < p; i++){ // loop
*s-- = (char)((d % 10) + '0'); // 下位桁から0~9に
d = d / 10; // 1/10して上位桁の処理
}
*s-- = '.'; // 小数点
}
// 整数部 上位ゼロサプレス
for(i = 0; i < n; i++){ // loop
if((i == 0) || // 1桁目あるいは
(d != 0)){ // ゼロ以外
*s-- = (char)((d % 10) + '0'); // 下位桁から0~9に
d = d / 10; // 1/10して上位桁の処理
}
else if((sgn != 0) && // 2桁目以降でゼロ、そしてマイナス
(zr == 0)){ // -符号処理まだ
*s-- = '-'; // マイナスを付加
zr = 1; // ゼロでマイナスを処理した
}
else{ // 2桁目以降でゼロ
*s-- = ' '; // ゼロサプレス
}
}
return (s + 1); // 文字列の先頭アドレスを持ってリターン
}

char *s;   // 文字列のアドレスを置いておくポインタ
  s = strbcd(d, 4, 2);   // -999.99~9999.99の範囲をBCDに
  Serial.print(s);   // シリアルとLCDに同じ値を
  LCD.print(s);

こんな格好で使います。
  ※次にstrbcdを使うまでは変化しません

食わせる変換元データdの指定サイズで関数の容量が
変わります。
  例のように4バイトデータ、int32_tなら264バイト
  2バイトのint16_tなら178バイト
でした。
±32768の範囲で処理できるなら、shortにしておけば
だいぶと小さくできます。

以前のtxbcdは前から変換結果を置いていましたが、
今回はバッファの最後から前に向かって詰めて行っ
てます。
そして、分かりやすくするため小数部と指数部に分けて
変換処理したので商計算と剰余計算がそれぞれ2回出て
きます。
このあたりが原因でプログラム(機械語レベルでの)の
サイズが大きくなったのかと思います。

数字の桁数を固定しての表示や出力、たいていの場合
「printf系」の処理が必要です。
確かに便利なんですが、printfを使うとプログラムが
膨れます。

それと、マイナスの数値を出したいときにちょいと不便。
  printf("%4d.%02d", d/100, abs(d%100))
とした時、-123は-1.23と出てくれますが、2桁までの
マイナス値、例えば-12だと 0.12 となり整数部の0、
この頭にマイナス記号が付いてくれません。
「-0.xx」を出すのに悩んでしまいます。

もう一つが、表示幅。
printfだと、桁数指定より大きな数が来た時でも
ちゃんと正しく変換してくれます。
でも液晶表示のように、想定しているより表示桁数が
増えると、表示がずれちゃいます。
本来は、ズレないように数値に規制をかけなくちゃい
けないのですが、「とりあえずのプログラム」だと
めんどうなものです。
今回のstrbcd()で、範囲外の値はマイナス記号も含めて
捨ててしまうので、表示幅は確定します。
  ※異常値出現が分からないというのは
   デメリットでしょうけど。


※2024-05-01追記
リターン値が「nとp」(整数部、小数部)の値で変わるというのも
けったいなので、いつも文字バッファの先頭を持ってリターン
するようにしてみました。
バッファへの文字書込みも、プリデクリメント「*--s」の記号を
使うようにしました。
AVRマイコンのメモリーへの間接アクセス命令は
  ST X,Rr のアドレスそそまま(X以外にY,Z)
そして
  ST X+,Rr のポストインクリメント
あるいは
  ST --X,Rr のプリデクリメント
の3種。
で、「*--s」を使っていみたわけです。
   ※コンパイル結果は残念ながら「ST --X,Rr」には
    展開されてませんでした。
    「ST X,Rr」も「ST --X,Rr」もクロック数は同じ
    なんですが・・・

/*****  桁指定BCD文字出力   *****/
char *strbcd(int32_t d, byte n, byte p)
{
byte i;
byte sgn = 0; // 符号フラグ
byte zr = 0; // ゼロデータ処理済みフラグ
char *s; // 0~9書込みバッファのアドレス
if(d < 0){ // マイナス?
d = -d; // +の値にして
sgn = 1; // -フラグをオン
}
i = n + p; // 文字数
if(p) i++; // 小数点あれば+1
s = bcd_bff + i; // バッファの最後尾
*s = '\0'; // 最後にnullをセット
// 小数部 '.'まで
if(p){ // 小数部あり
for(i = 0; i < p; i++){ // loop
if(d){ // 0でないときは計算
// PB2_H; // (!!!)
*--s = (byte)(d % 10) + '0'; // 下位桁から0~9に
d = d / 10; // 1/10して上位桁の処理
// PB2_L; // (!!!)
}
else{ // 0なら'0'を
*--s = '0';
}
}
*--s = '.'; // 小数点
}
// 整数部 上位ゼロサプレス
for(i = 0; i < n; i++){ // loop
if((i == 0) || // 1桁目あるいは
(d != 0)){ // ゼロ以外
// PB2_H; // (!!!)
*--s = (byte)(d % 10) + '0'; // 下位桁から0~9に
d = d / 10; // 1/10して上位桁の処理
// PB2_L; // (!!!)
}
else if((sgn != 0) && // ゼロでマイナス
(zr == 0)){ // -符号処理まだ
*--s = '-'; // マイナスを付加
zr = 1; // 始めてのゼロを処理した
}
else{ // 2桁目以降でゼロ
*--s = ' '; // ゼロサプレス
}
}
return s; // 文字列の先頭アドレスを持ってリターン
}

もう一つ問題。
printf()やdtostrf()を使った場合より、うんと全体のROM
容量は減らせるんですが、速度がでません。
ループの中にあるlongの割り算に時間がかかります。
   ※ちょいとでも速くとゼロのときをスキップする
    ようにしても、変換桁数が多いと同じ。
longの割り算、商と剰余を使っています。
コンパイル結果は一度の割り算で商と剰余が出る「ldiv()」関数
を使っているようなのですが、割り算の時間はおよそ「40μs」。
変換桁数ぶんだけ時間がかかります。

ところが、浮動小数点を文字列に変換する「dtostrf()」は桁数が
多くなってもそんなに遅くならず、70μs~120μs(8桁)程度。
   ※8桁だとstrbcd()の時間は2倍を超えてしまいます。

binデータの数字文字変換、10で割ってその余りを使う以外に
高速な手法あるのかな。
↓のZ80で使ってるMSBからのcarryを見て10進加算という手法。
32bitなら10文字なんで、あっという間か。
   ※機械語ならあれこれ思いつくんだけど。

=============================
※ちょっと昔話・・・
Z-80でのBCD変換、こんなルーチンを使っていました。

; 16 bit binary カラ BCD ヘ ヘンカン
; HL --->AHL
; FFFF--->65535
BCD:
LD B,16
LD DE,0
LD C,D
BCD01:
ADD HL,HL
LD A,E
ADC A,A
DAA
LD E,A
LD A,D
ADC A,A
DAA
LD D,A
LD A,C
ADC A,A
DAA
LD C,A
DJNZ BCD01
EX DE,HL
RET

16bitのペアレジスタ 、HLに入った0x0000~
0xFFFFの16進数をBCDに変換して「AHL」に
入れてリターン。
  00・00・00 ~ 06・55・35
の数値が得られます。
「DAA」命令が役立っています。
  ※このアセンブラ・ニーモニックを見て動きを
   理解していただける人がどれだけいるか・・・

このようにHEXをBCDに変換した後、次の
16進文字変換ルーチンでASCII文字に直して
「TX」でシリアル出力します。

TXHEXH:         ;上位4ビットの変換 0~9,A~F
RLCA ;hex data (H nibble)
RLCA
RLCA
RLCA
TXHEXL: ;下位4ビットの変換
AND 0FH ;hex data (L nibble)
ADD A,90H
DAA
ADC A,40H
DAA
JP TX ;0~9,A~Fを1文字出力

TXHEX: ;00~FF 2文字16進数出力
PUSH AF ;hex data (byte) (2 chr)
CALL TXHEXH
POP AF
JR TXHEXL
TXHL: ;0000~FFFFF 4文字16進数出力
LD A,H ;H,L (4 chr)
CALL TXHEX
LD A,L
JR TXHEX

ここでも「DAA」が活躍。
0x90を加算してDAA。
その後、carryを含めて0x40を加算してDAA。
これで、「0~9」の10進数字と「A~F」の
16進文字が得られるのです。
この謎の手法を知ったときはほんと驚きました。
条件分岐しないで16進文字が生まれるのですから。

※テストプログラム
ロータリーエンコーダーとI2C液晶(8文字×2行)を
つないでます。
   ・ダウンロード - strbcd02.zip

strbcd()とdtostrf()の速度差をオシロで
見れるようにしました。

| | コメント (0)

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月19日 (火)

Arduino UNO R3(のATmega328P)を使った電流計各種

実験用の電流計、あれこれ作ってきました。
ハイサイド側に電流検出抵抗を入れることで、
供給電源とデバッグ側回路のGNDを共通にして
るというところが特徴かと。

現用中のはこの3つ。
Cc11_20240319130701

液晶を使えば電流と電圧を同時に表示できるのですが、
あえて7seg LEDを使っています。
  ※手持ち部品の活用
  ※文字が大きいのがありがたい

発端はこれ。
シンプルにATmega328P内蔵の10bit ADCで
電流を計りました。
Cur10bit
2022年11月29日:Arduino UNO(のチップ)を使ったUSB電流計
2022年12月02日:Arduino UNOを使ったUSB電流計 箱に入れて完成

最大表示が3桁で電流測定範囲は「999mA」まで。
1mAが最小桁というのが不満でした。

そこで、外付けの12bit ADCを使ったのがこれ。
Cur12bit

2022年12月14日:Arduino UNOを使ったUSB電流計 4桁表示も
4桁で最小桁が0.1mA。
でも最大が300.0mAまで。
電流検出抵抗の値もちょっと大きいし。(75mΩ)
出力に付けたCRDは電流検出アンプのゼロ付近にある
不感帯対策。
ちょっと電流を流しておくことで、ゼロ付近の非直線性を
避けて校正操作できるようにしました。

そして、トランジスタ技術2023年6月号のトラ技Jr.コーナ
載せてもらった回路がこれ。
Cur16bit

16bit ADCで、999.9mAが最大。
  ※999.9を越えると1.xxxと表示

備忘録としてスケッチをアップしておきます。
  ※.inoではなく.txtにしています。

  ・10bit版 ダウンロード - usb_cur3a.txt
  ・12bit版 ダウンロード - usb_cur_12bit1a.txt
  ・16bit版 ダウンロード - cur_16bit1a.txt

タイマー割り込みと内蔵ADC変換完了割り込みを使った
7セグLEDの駆動方法や、校正データのEEPROMへの保存、
浮動小数点化したmap関数による直線補間の方法(電流,電圧計算)
などが参考になるかと。

10bitと12bitの回路で使った電流検出アンプ「INA180」
(差動入力タイプ)が60円ほど。
  ※オペアンプより安価
16bitの回路で使った「INA281」(FETソース出力型)が
ちょい高くて400円ほど。

| | コメント (0)

より以前の記事一覧