« 5chサーミスタ温度計のA/D入力、map関数を使って補正 | トップページ | 何度も言うぞ! Arduino(8bitマイコン)の割り込みには気をつけろ! »

2022年10月 8日 (土)

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

ROM、RAM容量に余裕のあるマイコンなら
「printf」系の「sprintf」で文字列に
変換してシリアル出力、あるいは液晶表示
という手法が使えます。

しかし・・・
  ROM、RAMに余裕がないなぁ
  BCDにして出力(表示)するだけなのに
てな時に役立つルーチンを紹介しておきます。

・longデータ ということは整数だけ。
・ゼロサプレス付で数値に変換。
・マイナスは最も左側の数字の頭に「-」を付加。
・数値「-12345」の最小桁が0.001を意味する時の
 ため(例えば-12.345Vと表示したい)、整数部桁数
 と小数部桁数を指定できるようにした。
・値が指定桁数より大きくなっても、左にはみ
 出さない。
   液晶表示などでは表示がズレないけれど
   越えた部分の数字は見えない。
・1文字シリアル出力ルーチンの名をtx()として
 記している。
   液晶表示なら1文字書き込みに変える。
・「 -12.345」と出力したいのなら
   (先頭にスペース1コ付けている)
      txbcd(d, 4, 3);
 これで、-999.999 ~ 9999.999 の範囲を
 出力できる。
 -32768 ~ 32767のint値なら(longにキャストして)
   「 -32.678」~ 「  32.767」
  頭にスペース1つ  頭にスペース2つ
 wordなら
   「   0.000」 ~ 「  65.535」
  頭にスペース3つ  頭にスペース2つ

/*****  桁指定BCD出力       *****/
// n:整数部文字数 p:小数部文字数
// 数値は整数を入力 浮動小数点ではない
// 小数部は整数の基数が0.01などの時に用いる
// 12345が123.56を意味する時 (d,3,2)と指定
void txbcd(int32_t d, byte n, byte p)
{
char bff[20]; // 20文字(null入れず)
byte i, m;
byte sgn = 0; // 符号フラグ
memset(bff, ' ', sizeof(bff)); // スペースで埋める
if(d < 0){ // マイナス?
d = -d;
sgn = 1;
}
m = n + p; // 整数+小数 文字数
for(i = 0; i < (sizeof(bff) - 2); i++){ // longで12文字とマイナス
bff[i] = (d % 10) + '0'; // 下位桁から0~9に
d = d / 10; // 1/10して上位桁の処理
if((d == 0) && // 0になったら終わり
(i >= p)) break; // 小数桁ok?
}
if(sgn){ // 元値がマイナス?
bff[i + 1] = '-';
}
for(i = 0; i < m; i++){
tx(bff[m - i - 1]); // バッファを逆順で
if(p){
if(i == (m - p - 1)) tx('.'); // 小数点
}
}
}

例に記した「12345が123.56を意味する時 (d,3,2)と指定」
だと、整数部が3桁だけですのでマイナスを考えると正常に
出力できる(落ち無く)数値の範囲は「-99.99 ~ 999.99」
となります。

プログラムそのものは30行もありませんが、実プログラム
ではこれでも300バイトを越えてしまいます。
もっと小さくしたいときは、
 ・long:int32_t → int16_tに
 ・bffをローカル変数(スタックの処理が入る)で
  はなく関数の外に持ってくる。
てなところでしょうか。

sprintfだと、数値の単位や区切り文字を含めて1行
に表記できて便利ですが、これは数字だけ。

longの剰余と除算を使っています。
アセンブラで書くとなると、ここらから始めなければ
なりません。

Arduinoの「print」は数値の桁数を指定できない
ので不便。
数値とゼロサプレスの数を合わせて桁を揃えて
出力したい時は面倒でも「sprintf」。

 

|

« 5chサーミスタ温度計のA/D入力、map関数を使って補正 | トップページ | 何度も言うぞ! Arduino(8bitマイコン)の割り込みには気をつけろ! »

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

コメント

いつも楽しく読ませて頂いております。

printf欲しいですよね。
普通に導入すると浮動小数点ライブラリが
ついてきて容量的に大変なことになるので
私も整数系のみサポートのprintfを自作し
使っていました。

インターフェースが整理されていないので
ひとに紹介するにはchan氏の
xprintfが良いのではないか思います。
新しく 2021年版がでていました。

http://elm-chan.org/fsw/strf/xprintf_j.html

あと Arduinoの Serialクラスですが
printだけではなくprintfが使えるものも
割りとあったりします。

特に ESP8266, ESP32 の Serial.printfはよく使います。
ESP32だと log_d(), log_i()なども使えデバッグに便利です。

投稿: nari | 2022年10月 8日 (土) 12時14分

今回のtxbcd()はROMが8kバイトのATtiny841で使いました。
この用途↓
http://igarage.cocolog-nifty.com/blog/2022/10/post-50b00a.html
4kバイトのATtiny841にも入るかとのチャレンジです。

そうそう。
「bff」を外に持ってくると、ローカル変数のためのスタックのゴソゴソが無くなり、同じことをさせるのに42バイトも減りました。

投稿: 居酒屋ガレージ店主(JH3DBO) | 2022年10月 8日 (土) 15時32分

Arduino UNOやNanoなどATmega328P、AVRマイコンを使ったsprintfは、書式を文字列で指定するとき、RAMじゃなくROMに置けるよう「sprintf_P」を用います。
このあたりも、ちょっと数値を出力するだけなのに「大げさやん」と感じてしまうわけです。

紹介しましたtxbcdはお手軽さが第一ということで。

パルスジェネレータのスケッチでは、
http://igarage.cocolog-nifty.com/blog/2022/08/post-c07c42.html

数値表示用にこんな書式指定文字列を使っています。

const char cnt_pf0[] PROGMEM = "%4u.%04u"; // 4095.9375us ※16MHzclkの時
const char cnt_pf1[] PROGMEM = "%2u.%03ums "; // 65.535ms
const char cnt_pf2[] PROGMEM = "%3u.%02ums "; // 655.35ms
const char cnt_pf3[] PROGMEM = "%1u.%04us "; // 6.5535s
const char cnt_pf4[] PROGMEM = "%2u.%03us "; // 65.535s

数値の後に「単位」の文字をくっつけれるので、やはり便利です。

投稿: 居酒屋ガレージ店主(JH3DBO) | 2022年10月 8日 (土) 16時12分

bffを外に出して数値を「int」(16bit)にしたら、txbcdルーチンは186バイトまで小さくなりました。

投稿: 居酒屋ガレージ店主(JH3DBO) | 2022年10月10日 (月) 15時25分

コメントを書く



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




« 5chサーミスタ温度計のA/D入力、map関数を使って補正 | トップページ | 何度も言うぞ! Arduino(8bitマイコン)の割り込みには気をつけろ! »