Arduino

2024年10月 6日 (日)

おっと。map関数の計算桁に注意

Arduinoのmap関数に関して恨みを持っている
がごとくウダウダ言っております。
 ・2020年5月17日:Arduino なんとかして誤用を正したい:A/Dの1/1023とmap関数

トラ技にも載りました。
 ・トランジスタ技術2024年3月号『実はワナだらけ…確実に動かすArduino Uno R3

で、今回の
 ・2024年10月5日:DDS方式の2相パルス発生回路、周波数スキャン機能を付ける

ここで、「おっと危ない」だったのが「map」関数の数値範囲。

周波数sweepでは、
  周波数の値は0.01Hz単位で9999.99Hzが最大。
  BCDで8桁。999999=0xF423F 20bit

  経過時間が10ms単位で600.00秒が最大。
  BCDで5桁。60000=0xEA60 16bit

sweep処理では、こんな値を使います。
  Lo側周波数 1~9999Hz   f1
  Hi側周波数 1~9999Hz   f2
  Rise,fall時間 0.0~600.0秒 t1

sweep周波数の設定は1Hz単位ですが、内部では0.01Hz
単位に直します。
sweep時間も0.1秒を0.01秒にして60000が最大。

そして、「map関数」はこのように定義されています。

long map(long x,         入力
  long in_min, long in_max,  X1,X2 変換元
  long out_min, long out_max) Y1,Y2 変換先
{
 return (x - in_min) * (out_max - out_min) ★1
   /  (in_max - in_min)
   + out_min;
}

sweep処理で経過時間tmからsweep周波数fmを計算するとき、
map関数をこのように使いました。

  fm = map (tm , 0, t1, f1, f2);

このとき、周波数と経過時間が設定の最大値に近づくと、
★1の乗算に失敗するのです。

周波数(20bit)と時間(16bit)を掛け算すると、
値によっては「36bit」の数字が出てきます。

map関数の入力と出力、単独で見るとそれぞれは
32bitの数値範囲に入っていますが、内部計算を考えると
32bitではアウトなのです。

内部は「int64_t」で計算しなくちゃなりません。

 return ((int64_t)(x - in_min) *
     (int64_t)(out_max - out_min))
     / ((int64_t)(in_max - in_min))
     + out_min;

乗算部、除算部をint64_tにキャストして64bitで
計算するようにしました。

LOG sweepではfloatにしたmapも使っています。

float mapf(float x,
  float in_min, float in_max,
  float out_min, float out_max)

2022年11月6日:Arduino map関数をfloatに

もうひとつ。
t1がゼロだとゼロ除算してしまうので、mapに
食わす前に判定して除外しておかなくちゃなりません。
t1がゼロなら fm=f1でリターンてな処理です。

| | コメント (0)

2024年10月 5日 (土)

DDS方式の2相パルス発生回路、周波数スキャン機能を付ける

あれこれ悩みながらこしらえてきたDDS方式の2パルス発生回路
ざっと完成形に持ち込めました。
割り込み処理の時間はギリギリで動いていますが、
メインループはヒマ
ということで、周波数スキャン(sweep)機能を付加しました。
設定するのは、
  Lo側周波数 1~9999Hz   f1
  Hi側周波数 1~9999Hz   f2
  Rise時間  0.0~600.0秒  t1
  Fall時間  0.0~600.0秒  t2
  Hi待ち時間 0.0~600.0秒  w1
  Lo待ち時間 0.0~600.0秒  w2

スイープは
 (1) f1周波数で2相パルス出力
 (2) t1時間をかけてf2までパルス周波数を上昇
 (3) f2周波数をw1時間保持
 (4) f2周波数からf1まで周波数を下降
 (5) f1周波数をw2時間保持
 (6) (1)に戻る
を繰り返します。

「100Hz~400Hz」をスイープすると、こんな波形が出てきます。

Bb002_20241005115101
   変化点を拡大
Bb000_20241005115101

一番下の波形は「F-Vコンバータ」 で見た、
A相パルスの周波数変化。

まだ「main loopはヒマ」だからと、LOG SWEEPの処理も
書き加えてみました。
A相周波数がこんな具合に変化します。
Bb001_20241005115401

Arduino UNO R3のATmega328P(16MHzクロック)、
8bitマイコンなのに、意外と浮動小数点処理を頑張って
くれます。
10msごとに経過時間から周波数を計算しています。

※関連
2024年9月24日:秋月のI2C接続液晶 AQM1602XAを基板に直付け
2024年9月27日:1クロックでも速くしたい DDS方式の2相パルス発生器
2024年9月30日:1クロックでも速くしたい 割込を「ISR_NAKED」で

※回路図
Cw_ccw2

元のでは無しにしていたSW4にsweepの操作を割り当てています。


| | コメント (0)

2024年9月30日 (月)

1クロックでも速くしたい 割込を「ISR_NAKED」で

2024年9月27日:1クロックでも速くしたい DDS方式の2パルス発生器

この続き。 タイマー1のコンペアマッチA割り込みを
インライン・アセンブラで書いてみました。

/***************************/
/* DDSでパルス出力 */
/***************************/
// A相B相出力データ COM1A,COM1Bで出力
volatile uint8_t com1_ab; // 3bitでCW,CCW判断
// tbl_ab[]をアクセス
volatile uint8_t tbl_ab[]={
0b11110000, // 000 H H CW
0b10110000, // 001 L H
0b10100000, // 010 L L
0b11100000, // 011 H L
0b11100000, // 100 H L CCW
0b10100000, // 101 L L
0b10110000, // 110 L H
0b11110000, // 111 H H
}; // |||| ++---- CTC OCR1A モード
// ||++-------- COM1B PB2 (00:off,01トグル,10:L,11:H)
// ++---------- COM1A PB1

/***** タイマー1 : コンペアマッチA割り込み *****/
// DDS処理
// CW/CCW出力 A:PB1(OC1A) B:PB2(OC1B)
// COM1(A,B) 00:off,01トグル,10:L,11:H
// PB4_H; // (!!!)
// dds_cnt += dds_step; // DDSカウンタをカウントアップ
// if(pls_on){ // パルス出力中
// com1_ab = ([dds_cnt.cnt1+3] / 4) & 0x03; // 1/(65536*4096)
// com1_ab |= pls_dir; // bit2でCW,CCWを区分
// }
// TCCR1A = tbl_ab[com1_ab]; // COM1A,COM1B制御 offなら前のb値で
// PB4_L; // (!!!)
ISR(TIMER1_COMPA_vect, ISR_NAKED)
{
__asm__ volatile(
" sbi %0, 4 \n" // PB4 H
" push r0 \n"
" in r0, __SREG__ \n"
" push r28 \n"
" push r29 \n"
" push r30 \n"
" push r31 \n"
// dds_cnt += dds_step;
" lds r28, (dds_step) \n" // LSB
" lds r30, (dds_cnt) \n"
" add r30, r28 \n"
" sts (dds_cnt), r30 \n"
" lds r28, (dds_step+1) \n" // +1
" lds r30, (dds_cnt+1) \n"
" adc r30, r28 \n"
" sts (dds_cnt+1), r30 \n"
" lds r28, (dds_step+2) \n" // +2
" lds r30, (dds_cnt+2) \n"
" adc r30, r28 \n"
" sts (dds_cnt+2), r30 \n"
" lds r28, (dds_step+3) \n" // +3
" lds r30, (dds_cnt+3) \n"
" adc r30, r28 \n"
" sts (dds_cnt+3), r30 \n"
// if(pls_on){
" lds r28, (pls_on) \n"
" and r28, r28 \n"
" breq j1 \n"
// com1_ab = ([dds_cnt+3] / 4) & 0x03;
" lsr r30 \n" // 1/2
" lsr r30 \n" // 1/4
" andi r30, 0x03 \n" // & 0x03
// com1_ab |= pls_dir; // bit2でCW,CCWを区分
" lds r28, (pls_dir) \n"
" or r30, r28 \n"
" sts (com1_ab), r30 \n"
" j1: \n"
// TCCR1A = tbl_ab[com1_ab];
" lds r30, (com1_ab) \n" // 0~7
" ldi r31, 0 \n"
" ldi r28, lo8(tbl_ab) \n" // tbl_ab[]
" ldi r29, hi8(tbl_ab) \n"
" add r30, r28 \n"
" adc r31, r29 \n"
" ld r28, z \n"
" sts %1, r28 \n" // TCCR1A
// pop reg
" pop r31 \n"
" pop r30 \n"
" pop r29 \n"
" pop r28 \n"
" out __SREG__, r0 \n"
" pop r0 \n"
" cbi %0, 4 \n" // PB4 L
" reti \n"
:
: "n" (_SFR_IO_ADDR(PORTB)), // %0
"n" (_SFR_MEM_ADDR(TCCR1A)) // %1
:
);
}

「ISR_NAKED」で、使うレジスタを我が手に。
その結果、
  ・pushとpopするレジスタの数を吟味して減らせる
  ・タイミングを見るためのポート出力を先頭と最後に
   持ってこれるので、正確に見える。
問題はインライン・アセンブラの書式。
AVRマイコン特有の問題がまだ解決できていません。

「Z」レジスタにA相B相出力テーブルの先頭を持ってきているところ、
「c」だと「マイナスの値を引いて足し算」する処理が行われています。

   820:	e0 91 62 01   lds    r30, 0x0162
824: f0 e0 ldi r31, 0x00
826: ed 5f subi r30, 0xFD
828: fe 4f sbci r31, 0xFE
82a: 80 81 ld r24, Z  テーブル値を読み出し

これ、AVRマイコンの命令体系で、即値加算の命令が無いのです。
  即値加算:addiとかadci
そこで、即値減算命令 subi、sbci命令を使い、
  マイナスの値を引いて足し算に
ということで対処するのです。
  AVRマイコン・アセンブラの定石

ところが、インライン・アセンブラの書式で、テーブルの先頭アドレスを
マイナスにする方法が分からないのです。
  (- tbl_ab)とか(0 - tbl_ab)とかあれこれ
  試しましたが、エラーが出ちゃいます。
そこで仕方なく、レジスタ同士の加算命令を使って、アドレスを
得ています。
  これで、2命令を損

 " lds r30, (com1_ab) \n" // 0~7
 " ldi r31, 0 \n"
 " ldi r28, lo8(tbl_ab) \n" // tbl_ab[]
 " ldi r29, hi8(tbl_ab) \n"
 " add r30, r28 \n"
 " adc r31, r29 \n"
 " ld r28, z \n"  テーブル値の読み出し

これ、なんとかできれば良いのですが。

push、popするレジスタを5つまで減らせました。
   元のは13個。
これでダイブとスピードアップ。

「union」を使った32bit値と8bit値のまぜこぜ処理も
無くなったし、ある意味、すっきりと。

※実行時間 割り込み処理およそ5μ秒に。
Scope_1126_memo1
  前は8μ秒ほど

| | コメント (0)

2024年9月27日 (金)

1クロックでも速くしたい DDS方式の2相パルス発生器

DDS方式の2相パルス発生回路、現在は80kHz(12.5μs)の
タイマー割り込み内で処理しています。
こんな感じ。

/******************************/
/* DDS制御 */
/******************************/
// DDS制御データ
// dsiz(1024,2048,4096)の上位2bitでA,B相出力を決定
// タイマー1割り込みで処理 80kHz=12.5us(=TM1_FRQ)
int32_t dds_frq; // 0.01Hz単位の周波数設定値 ★1
// 0.10Hz~9999.99Hz
volatile uint32_t dds_step; // frqから計算したdds制御1step ★2
// frq * dsiz * 65536 / TM1_FRQ
// DDS設定カウンタ (uint32_t dds_cnt)
// タイマー割り込みでdds_stepを加算 上位ビットで波形出力
union u_dc{
volatile uint32_t cnt4; // 4byte DDS設定カウンタ ★3
volatile uint8_t cnt1[4]; // 1byt DDS設定カウンタ ★4
};
u_dc udds_cnt; // カウントデータ共用体
// cnt4に加算 cnt1で読み出し
// --- 3--- --- 2--- --- 1--- --- 0--- 4バイト
// 10987654 32109876 54321098 76543210
// xxxxHHbb bbbbbbbb aaaaaaaa aaaaaaaa
// || 下位26bitのところにstepを加算
// ++--- CW,CCW判定bit これを読み出したい
// xxxx----- 上位は無視してok

/***************************/
/* DDSでパルス出力 */
/***************************/
/***** タイマー1 : コンペアA割り込み *****/
// DDS処理
// CW/CCW出力 A:PB1(OC1A) B:PB2(OC1B)
// COM1(A,B) 00:off,01トグル,10:L,11:H
ISR(TIMER1_COMPA_vect)
{
static uint8_t b; // 3bitでCW,CCW判断
static uint8_t com1ab[]={
0b11110000, // 000 H H CW
0b10110000, // 001 L H
0b10100000, // 010 L L
0b11100000, // 011 H L
0b11100000, // 100 H L CCW
0b10100000, // 101 L L
0b10110000, // 110 L H
0b11110000, // 111 H H
}; // |||| ++---- CTC OCR1A モード
// ||++-------- COM1B PB2 (00:off,01トグル,10:L,11:H)
// ++---------- COM1A PB1
PB4_H; // (!!!)
udds_cnt.cnt4 += dds_step; // DDSカウンタをカウントアップ ★3+★2
if(pls_on){ // パルス出力中
b = (udds_cnt.cnt1[3] >> 2) & 0x03; // 1/(65536*4096)
b |= pls_dir; // bit2でCW,CCWを区分
}
TCCR1A = com1ab[b]; // COM1A,COM1B制御 offなら前のb値で
PB4_L; // (!!!)
}


アセンブル結果がこれ。

000007a6 <__vector_11>:
7a6: 1f 92 push r1
7a8: 0f 92 push r0
7aa: 0f b6 in r0, 0x3f ; 63
7ac: 0f 92 push r0
7ae: 11 24 eor r1, r1
7b0: 4f 93 push r20
7b2: 5f 93 push r21
7b4: 6f 93 push r22
7b6: 7f 93 push r23
7b8: 8f 93 push r24
7ba: 9f 93 push r25
7bc: af 93 push r26
7be: bf 93 push r27
7c0: ef 93 push r30
7c2: ff 93 push r31
7c4: 2c 9a sbi 0x05, 4 ; 5
7c6: 40 91 74 01 lds r20, 0x0174
7ca: 50 91 75 01 lds r21, 0x0175
7ce: 60 91 76 01 lds r22, 0x0176
7d2: 70 91 77 01 lds r23, 0x0177
7d6: 80 91 63 01 lds r24, 0x0163
7da: 90 91 64 01 lds r25, 0x0164
7de: a0 91 65 01 lds r26, 0x0165
7e2: b0 91 66 01 lds r27, 0x0166
7e6: 84 0f add r24, r20
7e8: 95 1f adc r25, r21
7ea: a6 1f adc r26, r22
7ec: b7 1f adc r27, r23
7ee: 80 93 63 01 sts 0x0163, r24
7f2: 90 93 64 01 sts 0x0164, r25
7f6: a0 93 65 01 sts 0x0165, r26
7fa: b0 93 66 01 sts 0x0166, r27
7fe: 80 91 7e 01 lds r24, 0x017E
802: 88 23 and r24, r24
804: 69 f0 breq .+26 ; 0x820 <__vector_11+0x7a>
806: 80 91 66 01 lds r24, 0x0166   ★A
80a: 90 e0 ldi r25, 0x00 ;★B
80c: 95 95 asr r25  ★B
80e: 87 95 ror r24  ★A
810: 95 95 asr r25  ★B
812: 87 95 ror r24  ★A
814: 83 70 andi r24, 0x03 ; 3
816: 90 91 7c 01 lds r25, 0x017C
81a: 89 2b or r24, r25
81c: 80 93 62 01 sts 0x0162, r24
820: e0 91 62 01 lds r30, 0x0162
824: f0 e0 ldi r31, 0x00 ; 0
826: ed 5f subi r30, 0xFD ; 253
828: fe 4f sbci r31, 0xFE ; 254
82a: 80 81 ld r24, Z
82c: 80 93 80 00 sts 0x0080, r24
830: 2c 98 cbi 0x05, 4 ; 5
832: ff 91 pop r31
834: ef 91 pop r30
836: bf 91 pop r27
838: af 91 pop r26
83a: 9f 91 pop r25
83c: 8f 91 pop r24
83e: 7f 91 pop r23
840: 6f 91 pop r22
842: 5f 91 pop r21
844: 4f 91 pop r20
846: 0f 90 pop r0
848: 0f be out 0x3f, r0 ; 63
84a: 0f 90 pop r0
84c: 1f 90 pop r1
84e: 18 95 reti

アセンブラのソースになった「C」のコードを見ると、
  ここ、どうにかでけへんのか?
というところが見えてきます。

32bitのDDSカウンタ、その上位バイトを取り出して
A相B相出力をアウトプット・コンペアレジスタにセット
するためのテーブルを読み出しているところ、
  1バイトでのシフトで良いのに2バイトで処理してる
  ★Aだけでエエのに。 ★Bはイラんねん。
アセンブラだと、好きに書けるんですが・・・
Cコンパイラの内部処理まで手が出せません。

dds_cntにdds_stepを加算しているところも、使用レジスタを
ケチれば、push/popも減らせます。

dds_cntにunionを使い、32bitのところを8ビットでアクセス
できるようにしたのも高速化のため。
32bitのまま 「dds_cnt >> 26」としたら、えらく時間
がかかってしまいます。

現在の処理時間。
Scope_1123_memo1

パルスだけを見ると「3.5μs」使って処理して
いるように見えますが、ch4のトグルパルスを
見ると、「8μs」ほどかかっています。
割り込みサイクルが12.5μsなので割り込みの
処理時間は65%ほど。
push/popの数を減らせればだいぶと時間稼ぎ
できるんですが。


※追記 : 試してみるものです。

2ビット右シフトのところ、
   b = (udds_cnt.cnt1[3] >> 2) & 0x03; 
>> 2」を「 /4 」にしたら、
   b = (udds_cnt.cnt1[3] / 4) & 0x03; 
1バイトで処理してくれました。

806: 80 91 66 01 lds r24, 0x0166
80a: 86 95 lsr r24
80c: 86 95 lsr r24
80e: 83 70 andi r24, 0x03 ; 3
810: 90 91 7c 01 lds r25, 0x017C
814: 89 2b or r24, r25
816: 80 93 62 01 sts 0x0162, r24

3命令=3クロックだけですが、Cのままで高速化成功!


※補足 今回のDDS処理

・目標周波数 fo (Hz)
・基準周波数 fr = 80kHz
・DDSカウンタ 上位12bit + 下位16bitの28bit
       4096 × 65536
・DDSステップカウンタ step 26bit(max)

step = (fo × (4096×65536)) ÷ fr

1.00Hz出力だと
 step = (1 * (4096×65536)) ÷ 80000
    = 3355
この値を80kHzでDDSカウンタに加算する。

1秒後には80000回加算されて0.999868…Hzの
パルスが出る。

目標周波数が最大の9999.99Hzだと
 step = (9999.99 * (4096×65536)) ÷ 80000
    = 33554398 = 0x01FF FFDE : 25bit
実周波数は、9999.9899…Hzとなる。

A相・B相パルスを4バイトあるDDSカウンタの
上位バイトのbit3とbit2を使って発生させている。
  28bitの上位2ビット

「fr ÷ fo」が「2のn乗」になると、(4096×65536)が
割り切れて加算時の端数が出なくなる。
  例えば 5kHz → 16
     2.5kHz → 32
     1250Hz → 64
     625Hz → 128
     312.5Hz → 256
     156.25Hz → 512
これ以外の時は、特定の周期で出力パルスに
ジッターが生じる。

※ジッターの例
Jj1_20240928090201
0.1秒間とか1秒間の平均なら一定周波数が
カウントされるが、瞬間を見ると主クロック
(今回は80kHz)とのズレが生じている。

上に記した「fr ÷ foが2のn乗」の時だけ、
このズレはなくなる。

※過去記事
  割り込みの高速応答、こんな手法を使ってました。
2020年8月18日:Arduino デューティー計測のためのインプットキャプチャータイミング
2020年8月19日:Arduino インプットキャプチャー割り込みを「ISR_NAKED」で (デューティー比測定)


| | コメント (0)

2024年9月24日 (火)

秋月のI2C接続液晶 AQM1602XAを基板に直付け

SDAとSCLの2線でつなげる液晶モージュールが
なかなか便利です。
先日来使っているのが16文字x2行のこれ。
  AQM1602XA-RN-GBW
液晶の足は1.27mmピッチで一直線。
  AE-AQM1602A(KIT)
これになると2.54mmピッチへの変換基板が付属しています。
内部昇圧回路のコンデンサが付いているので、
主回路への接続は、+電源・GND・SDA・SCLの4本だけ。

ところが、この基板を使うと、液晶を基板に貼り付けるのに
  上に出っぱる
  厚くなる(基板そのものは1.2mm厚)
と、使い勝手がよくありません。
Ll10
Ll13

そこで、こんなつなぎをしています。
足を曲げて外に膨らませ、2.54mmピッチの
ユニバーサル基板にハンダしちゃうのです。

Ll11
ちょい拡大
Ll12

コンデンサの取り付けは基板の裏(パターン側)で。
L14_20240924163601

液晶板と基板の間にはスポンジタイプの両面テープ。
全面に貼ると剥がすとき大ごとになりそうなんで、
両端にちょっとだけ。

この液晶を使い、Arduino UNO R3(のチップで)こんなツールを
作っています。
Ll14

二つともデジタルで周波数を設定できる2相パルス発生回路
です。
90度位相差のあるA相B相パルスを出力します。
CW/CCWとカウント方向を変えられます。

下のは 「fclk / n16」でATmega328の16bitタイマー、
アウトプットコンペアで分周する方式。
周波数を設定できる範囲は0.1Hz~9990.0Hz。
   1/nですんで周波数が高くなると
   ちょいと誤差が出ます。

割り込みの時間に余裕があったんで、パルス数を
指定して「何発出せ」をできるようにしてみました。
  ロータリーエンコーダを回して周波数とパルス数を指定。
  CWあるいはCCWスイッチを押すとパルスを出力。
  設定数出したらSTOP。
2相パルスとして使わなくて、
  「何発かのパルスが欲しい」
ときに便利。
  お手軽ツールで、これってなかなか無いんじゃ
  なかろうか。

上のはDDS方式で、0.01Hz単位で周波数を設定できます。
0.10Hz~9999.99Hz。
こっちは STOP・CW・CCWの操作だけ。

基準カウントクロックが80kHzで、16MHzクロックの
ATmega328にはなかなか重い仕事です。

80kHz=12.5μ秒で割り込みをかけてDDS処理。
タイマー1のコンペアマッチで2相パルスを出力します。
ポートを叩いてH/Lを出力するんじゃないので、ジッターは
ありません。  (DDSに起因するジッタはある)
  80kHzで割り込み。
  DDSのための32bit加算
  次のコンペマッチで出力するA相B相を決定。
これを繰り返します。

※関連記事
2024年6月2日:秋月電子通商のI2C接続液晶表示器
2021年7月2日:秋月の液晶表示器 ACM0802C-NLW-BBW-IIC、I2Cのプルアップ抵抗
2022年7月18日:秋月電子16文字x2行のI2Cインターフェース液晶AQM1602Y
2024年9月12日:液晶表示:CG-RAMの表示がちょいと違う

※重要 液晶へのI2CアクセスとTWI割り込みを切り離せたから
    高速割り込みが安定してできるようになったのです。
2024年4月3日:超低速2相パルス発生回路・ケース入れ
2024年4月7日:I2C液晶のアクセス、割り込み処理で遅れる原因らしきもの
2024年4月12日:I2C液晶のアクセス、割り込みで処理しないようにすると


※2相パルスあれこれ
2024年4月26日:治具作りは面白い:2相パルスの発生
2014年07月01日:高速2相パルス発生回路
2018年5月25日:アナログ入力で2相パルスの周波数と正転逆転を制御する発振器

※2相パルス発生回路 回路図
Cw_ccw2

2022年8月24日:パルスジェネレータを作ってみた:箱に入れた
これの出力はデューティ可変できますが
「パルスを何発欲しい」には対応していません。
設定も「周期」で、「周波数」として合わせたい時は
ちょいと不便。
「全部入り」を作ろうとすると、Ardino UNO-R3ベースでは
だいぶとチカラ不足。

いずれも、電源や表示器以外は、
  ATmega328Pの中に入っている機能だけ
で実現していますんで、
「こんだけできるんやで」と褒めてやりたいところ
です。

※案
DDS方式の2相パルス発生器、割り込みの中では
処理時間の制限でもうなにもできませんが、
「外(main loop)」ではあれこれできそうです。
例えば、周波数を振らす。
  何Hzから何Hzまで、何秒かけて周波数をup/down。
てな処理です。
DDS加算している値を変えると周波数が変わります。
  割り込みでの処理時間は同じ。
設定メニューを出してあれこれでしょう。
  SW4を付けとけば良かった。


| | コメント (0)

2024年9月12日 (木)

液晶表示:CG-RAMの表示がちょいと違う

昔人間ですんで、「これで行こう」と決めたことは
いつまでも引きずっています。
今回のトラブルは液晶表示器の外字登録。

使ったのは秋月で買ったI2C液晶 AQM1602XA
  I2Cで安定して使えるようになって、6本線で
  つなぐ液晶表示器はちょいと縁遠くなりました。

キャラクタ表示の液晶表示器、8文字分のCG-RAMに
外字を登録できます。
文字コードとして0x00~0x07に対応するパターンを書き
込むと、登録した文字が出てくるという仕掛けです。

しかし、その先頭文字が「0x00」となり、C言語の
ヌル終端文字列:Wikipedia に引っかかってしまうので、
文字列の中に入れられません。
実質、7つの外字しか利用できないのです。
そこで、この表にあるよう、
L11_20240912102801

CG-RAMの文字コード0x00~0x07を0x08~0x0Fと
して定義しておくという手法を使ってきました。
  文字列の中に入れずに単独文字として書き込む
  ぶんには0x00で問題なしなんですが

ところが今回のAQM1602XAでこれをすると、定義した
CG-RAMではなく、内蔵された文字が出てきちゃったのです。
L12_20240912102901
16文字×2行の液晶、その下段に0x00~0x0Fの文字を
表示してみました。
L13_20240912103001

左の8つがCG-RAMに入れた登録外字。
昔の液晶なら同じ字が右に8つ並ぶのですが、
この液晶だと、内蔵文字になってしまい、
今までの手法が使えません。

ということで、登録した8つのCG-RAM文字は
0x00~0x07で表示するという、まっとうな処理に
変更しなくちゃなりませんでした。

※関連
PIC AVR 工作室別館 arduinoの館->キャラクターLCD
よく使うパラレル接続液晶(LCDモジュール)のコネクタ信号
液晶表示モジュールを4ビットモードで使ったときの空きピン処理


| | コメント (0)

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)

より以前の記事一覧