« ダイソー SHOOTING LIGHT:撮影用ライト LEDの輝度変化を探る #2 | トップページ | ありがたや、DigiKeyからのお知らせ »

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()」を。
あれこれ、探ってみましたがこんなところかと。

|

« ダイソー SHOOTING LIGHT:撮影用ライト LEDの輝度変化を探る #2 | トップページ | ありがたや、DigiKeyからのお知らせ »

Arduino」カテゴリの記事

AVRマイコン」カテゴリの記事

コメント

コメントを書く



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




« ダイソー SHOOTING LIGHT:撮影用ライト LEDの輝度変化を探る #2 | トップページ | ありがたや、DigiKeyからのお知らせ »