数値を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」。
| 固定リンク
「AVRマイコン」カテゴリの記事
- ATmega4809のシリアルポート(2023.01.18)
- PWMでD/A変換:アナログマルチプレクサの応用で(2023.01.02)
- ガレージのLED表示デジタル時計がダウン(2022.12.20)
- FLUKE 87IV デジタルテスターのデータをIrDAで吸い上げる(2022.11.15)
- 数値をBCD出力(表示)するルーチン(2022.10.08)
コメント
いつも楽しく読ませて頂いております。
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分