◆ テスト・プログラム02
このプログラムを実行すると、入力信号(直流電圧)を A-D変換した値が 7セグメントLED によって表示されます。 動作中に HC08スタータ・ボード上のプッシュ・スイッチを押したときのみ、前回と同様にバンド・ギャップ電圧を A-D変換した値が表示されます。
さっそくプログラムを見てみましょう。 内容が増えてきたので、入力処理と出力処理を関数分けしています。
マクロの定義では、A-D変換する入力信号のチャネルを指定しています。 プッシュ・スイッチに関する定義も追加しました。
#define ADCH_INPUT ( 4 ) // AD4(PTB0)
#define PORT_SW ( PTA_PTA3 )
#define SW_ON ( 0 )
#define SW_OFF ( 1 )
データの定義では、入力処理と出力処理のための変数を新しく用意しました。
// 入力処理
U2 ad_bandgap;
U2 ad_input;// 出力処理
U2 outdata;
初期化処理は前回とまったく同じです。
入力処理は、前回のプログラムから入力処理にあたる部分を抜き出して、入力信号とバンド・ギャップ電圧それぞれについて A-D変換および平均化を行っています。
//*************************
// 入力処理
//*************************
void input( void ){
U1 cnt;
ad_input = 0;
for ( cnt = 0 ; cnt < AVERAGE_NUM ; cnt++ ){
ADSCR_ADCH = ADCH_INPUT;
while ( !ADSCR_COCO );
ad_input += ADR;
}
ad_input /= AVERAGE_NUM;
ad_bandgap = 0;
for ( cnt = 0 ; cnt < AVERAGE_NUM ; cnt++ ){
ADSCR_ADCH = ADCH_BG;
while ( !ADSCR_COCO );
ad_bandgap += ADR;
}
ad_bandgap /= AVERAGE_NUM;
}
出力処理は前回のプログラムから出力処理にあたる部分を抜き出しただけなので、ソース・リストの掲載は省略します。
ここまで、ちっとも難しくなかったと思います。 メイン関数を見てみましょう。
//*************************
// メイン処理
//*************************void main( void ){
init_hardware( );
init_PORT( );
init_ADC( );
for ( ; ; ){
input( ); // 入力処理
if ( PORT_SW == SW_ON ){
outdata = ad_bandgap;
} else {
outdata = ad_input;
}
output( ); // 出力処理
}
}
いかがですか? もう説明は不要ですね。 スイッチが押されているときは outdata に ad_bandgap が代入されます。 スイッチが押されていないときは outdata に ad_input が代入されます。 出力処理では、いつも outdata を 7セグLED に表示します。
プロジェクトごと圧縮したものを qy4a_volt_meter02.zip に置いておきますのでご利用ください。
試しに 9V の電池を A-D変換してみます。 なお HC08スタータ・ボードの電池は前回と同じではなく、新しいものに入れ替えたのでご了承ください。 上の回路図の半固定抵抗を真ん中に合わせておいて、プログラムを動かしてみました。 7セグLEDの表示結果は 16進数で 「 1 3 2 」 10進数で 306 となっています。 プッシュ・スイッチを押したときは 「 1 8 9 」 10進数で 393 です。 変な値が出ていないかどうか確認してみましょう。
DMMディジタル・マルチ・メータ(ディジタル・テスタ)を使って測定してみると、電池電圧は 3.22V、入力信号(9V電池)の電圧は 9.53V でした。 そして、入力回路を良く見ると 100kΩと 11kΩで分圧されていますから、入力信号は 11 ÷ ( 100 + 11 ) = 0.0991 ≒ 0.1 倍 になっていることも忘れてはいけません(これにより 30V までの入力信号が測定可能になっている)。
それでは、実測した電池電圧 3.22V を使って入力電圧 9.53V の 0.1 倍を A-D変換するとどういう計算になるか確認してみます。 A-D変換結果なので小数点以下切り捨てとします。
9.53 × 0.1 × 1024 ÷ 3.22 = 303 (10進数) ・・・・・ (1)
プログラムの表示結果を 10進数に直すと 306 でしたから、ほぼ合っています。 10ビット精度で誤差が -3 というのは優秀ですね。 半固定抵抗もまだ調整していないことですし、この結果は満足できるものでした。
バンド・ギャップ電圧の方は、DMM を使って直接測定することができません。 前回 示した VBG 標準値 1.245V を使って A-D値を計算してみます。 表示された結果が見当はずれの値になっていないか確認してみます。
1.245 × 1024 ÷ 3.22 = 395 (10進数) ・・・・・ (2)
プログラムの表示結果を 10進数に直すと 393 でしたから、誤差は +2 となり、こちらもかなり近い値ですね。 よかったです。 ちなみに、プログラムの表示結果を使って逆向きに計算すればこのマイコンの VBG が求められます。
393 × 3.22 ÷ 1024 = 1.236 [V]
このあたりの電圧になっているはずですが、A-D変換器自体の誤差もありますので、そのまま信用するわけにはいきません。 また、この値は電池電圧 3.22V を使って出した値ですし、そもそもマイコンの個体差によって VBG にはバラつきがあるわけですから、プログラムの定数にはこの 1.236 [V] という値は利用できません。
◆ 7セグメントLED簡易電圧計プログラム
ここまでのテスト・プログラムの成果を利用して、7セグメントLED表示の簡易電圧計プログラムを作ります。 ポイントをまとめると、次のようになります。
(a) 電源電圧によらない測定結果を表示する。
(b) 表示電圧の分解能は 0.01V とする。
(c) 0.20V 未満(AD入力ピン 0.02V 未満)は残留ノイズなどの影響により
信用せず 0.00V とする。
(d) 16進数ではなく 10進数で表示を行う。
(e) 整数部(自動的に) 1桁または 2桁表示、小数点表示、小数部 2桁表示とする。
(a) 電源電圧によらない測定結果を表示する。
まず (a) ですが、先ほどの 式(1) と 式(2) から計算方法を考えてみます。 式(1),式(2) を書き直してみると次のようになります。
0.953 × 1024 ÷ 電池電圧 = 入力電圧AD値 ・・・・・ (1’)
1.245 × 1024 ÷ 電池電圧 = バンド・ギャップ電圧AD値 ・・・・・ (2’)
これでだいぶ見えてきました。 それぞれ変形して、
入力電圧AD値 × ( 電池電圧 ÷ 1024 ) = 0.953 ・・・・・ (1”)
1.245 ÷ バンド・ギャップ電圧AD値 = 電池電圧 ÷ 1024 ・・・・・ (2”)
式(2”) を 式(1”) に代入すれば、次のようになります。
入力電圧AD値 × 1.245 ÷ バンド・ギャップ電圧AD値 = 0.953 ・・・・・ (3)
みごとに電池電圧が計算式から消去できました! このようにして計算すれば、電池電圧が 3V だろうが 3.2V だろうが影響を受けずに済むというわけです。
ここで一つ問題があります。 小数が出てきましたね・・・。 実は、パソコンなどと異なり、非力なマイコンで小数の計算をするのは荷が重いのです。 できるだけ整数の計算に置き換えたいところです。 この場合、最終的に 0.953 (または 0.1 倍した分を元に戻した 9.53 ) という小数の値が必要なのではなくて、9.53 という表示を出したいだけなのです。 ということは、この式の両辺を 1000 倍してしまえば、整数の計算だけで済むことになります。
入力電圧AD値 × 1245 ÷ バンド・ギャップ電圧AD値 = 953 ・・・・・ (3’)
この結果を 9.53 [V] と表示すれば問題解決というわけです。 念のためにプログラムで表示された実際の数字を入れて計算してみましょう。
306 × 1245 ÷ 393 = 969 (小数点以下切り捨て) ・・・・・ (3”)
結果は 9.69V となりました。 実測値は 9.53V なので、+1.7% くらいのズレがあります。 バンド・ギャップ電圧のバラつきが最大で ±6% ほどあるため、それほど変な値ではありません。 とはいえ、絶対的に正しいものが何もないわけですから、この方法で良かったのかどうか心配になってきました。 理屈を確認するために、先ほど計算で出した値を使って式(3’) の方法で入力電圧を求めると、
303 × 1245 ÷ 395 = 955 (小数点以下切り捨て)
ホッ。 9.55V になりました。 今度はかなり期待した値 9.53V に近いですね。 式(3’) の方法で大丈夫ということが確認できました。 ・・・とはいっても、実測値ではなく計算値を用いたにもかかわらず、誤差が 0.02V も出ているのはどういうことでしょうか? これは結局 A-D変換値を求めるときに小数点以下が切り捨てられてしまい、それを使って掛け算・割り算を行っているので誤差の影響が大きく出てしまっているのです。 ためしにバンド・ギャップ電圧A-D値はそのままにしておいて、入力信号A-D値を 1 だけ増減させてようすを見てみます。
302 × 1245 ÷ 395 = 951 (小数点以下切り捨て)
304 × 1245 ÷ 395 = 958 (小数点以下切り捨て)
なるほど、分解能が 0.04V くらいしかないですね。 精度が出ていない問題は (b) の項目がクリアできれば同時に解決しますので、次へ進みましょう。
(b) 表示電圧の分解能は 0.01V とする。
式(3’) を実用的にするためには、精度をあと 4倍くらい良くする必要があることがわかりました。 ところで、先ほどから計算に使っている A-D値は、64個のA-D値を合計して、それを 64で割った値です。 せっかく貴重なデータを集めてきたのですから、そんなに桁を落として下の方の桁をバッサリ捨ててしまうのはもったいないことです。 64で割るのではなく、16で割るようにしてはどうでしょうか。 そうすることによって精度は 4倍になり、10ビットではなく実質的に 12ビット相当の A-D変換値が得られることになります(ピンと来ない場合はプログラムを見てください)。 この考え方を使って、式(1) 式(2) を計算し直してみます。
9.53 × 0.1 × 1024 ÷ 3.22 × 4 = 1212 (小数点以下切り捨て)
1.245 × 1024 ÷ 3.22 × 4 = 1583 (小数点以下切り捨て)
これらの精度の良いA-D値を使って 式(3’) の左辺を計算すると、
1212 × 1245 ÷ 1583 = 953
となり、結果は 9.53V です。 これで欲しかった答えが出せるようになり、式(3’) の方法が使えるということが確認できました。
確認のため、精度の良いバンド・ギャップ電圧A-D値はそのままにしておいて、精度の良い入力信号A-D値を 1 だけ増減させてようすを見てみます。
1211 × 1245 ÷ 1583 = 952
1213 × 1245 ÷ 1583 = 954
というわけで、表示電圧の分解能が 0.01V に改善されています。
(c) (d) (e) に関しては、プログラムを見ながら説明していきます。
型名の定義では、符号なし 4バイトを追加しました。
typedef unsigned long int U4;
マクロの定義では、バンド・ギャップ電圧の標準値を追加しました。 小数の計算をしているようにも見えますが、これはマイコンが計算するのではなく、コンパイラ(プリプロセッサ)が前もって計算してくれて 1245 という値になります。 時間待ちのための WAIT_SHORT と WAIT_LONG は、7セグメントLEDで表示をするときに桁と桁の間を一瞬だけ消灯させるのに利用します。
#define VOLTAGE_BG ( 1.245 * 1000 )
#define WAIT_SHORT ( 5000 )
#define WAIT_LONG ( 50000 )
データの定義では、精度のよくなったデータと測定結果の 1000 倍が入ったデータという意味が伝わりやすいように、変数名を変えました。
// 入力処理
U2 ad_bandgap_fine;
U2 ad_input_fine;// 出力処理
U2 out1000;
メイン関数はこんなにシンプルです。 初期設定は前と同じです。
//*************************
// メイン処理
//*************************void main( void ){
init_hardware( );
init_PORT( );
init_ADC( );
for ( ; ; ){
input( ); // 入力処理
volt_meter( ); // 電圧計制御
output( ); // 出力処理
}
}
入力処理は、AVERAGE_NUM で割っていたところを ( AVERAGE_NUM / 4 ) で割るように変更しています。 これによって 12ビット相当の A-D変換値を得ることができます。
//*************************
// 入力処理
//*************************
void input( void ){
U1 cnt;
ad_input_fine = 0;
for ( cnt = 0 ; cnt < AVERAGE_NUM ; cnt++ ){
ADSCR_ADCH = ADCH_INPUT;
while ( !ADSCR_COCO );
ad_input_fine += ADR;
}
ad_input_fine /= ( AVERAGE_NUM / 4 );
ad_bandgap_fine = 0;
for ( cnt = 0 ; cnt < AVERAGE_NUM ; cnt++ ){
ADSCR_ADCH = ADCH_BG;
while ( !ADSCR_COCO );
ad_bandgap_fine += ADR;
}
ad_bandgap_fine /= ( AVERAGE_NUM / 4 );
}
新しく設けた電圧計制御は、式(3’) の左辺をそのまま計算する関数です。 変数や定数の前にカッコ付きで ( U4 ) と書いてあるのは、今だけ符号なし 4バイトのサイズとして扱いますよ、という意味です。 専門用語で キャスト と言います。
work には測定結果の 1000倍の値が入っているので、(c) によって 0.20V 未満は 0 にしています。
最後に work を out1000 に入れるところで、( U2 ) と書いてあります。 これもキャストで、今だけ符号なし 2バイトのサイズとして扱いますよ、という意味です。 キャストをしなかった場合、 CodeWarrior のコンパイラがよく見ていて 「大きいサイズのデータから小さいサイズのデータへ代入するとデータの欠落があるかもしれません」 という意味の警告を出してきます。 警告はありがたいのですが、できれば警告の少ない書き方を身に付けたほうが、バグを作りにくいコーディングができるようになります。
//*************************
// 電圧計制御
//*************************
void volt_meter( void ){
U4 work;
work = ( U4 )VOLTAGE_BG * ad_input_fine;
work /= ad_bandgap_fine;
if ( work < 20 ){ // 0.20[V] 未満は 0[V] とみなす
work = 0;
}
out1000 = ( U2 )work;
}
出力処理の前半部分は (d) 「16進数ではなく 10進数で表示を行う」 ということをやっています。 今回はビット・シフトをしていないので、2進数とか 16進数を意識する必要はありません。 out1000 に適当な数、たとえば 1895(10進数) を入れてみると次のようになります。
work に 1895 を入れる
digit10 = 1895 ÷ 1000 = 1 (小数点以下切り捨て)
1895 を 1000 で割ったときの余り 895 を work に入れる
digit1 = 895 ÷ 100 = 8 (小数点以下切り捨て)
895 を 100 で割ったときの余り 95 を work に入れる
digit0_1 = 95 ÷ 10 = 9 (小数点以下切り捨て)
95 を 10 で割ったときの余り 5 を digit0_01 に入れる
というわけで 18.95 [V] という表示を行う準備が整いました。
後半部分は (e) 「整数部(自動的に) 1桁または 2桁表示、小数点表示、小数部 2桁表示」 ということをやっています。 ここはどちらかというと力技なので、ただ順番に見ていけばわかると思います。
//*************************
// 出力処理
//*************************
void output( void ){
U1 digit10, digit1, digit0_1, digit0_01;
U2 cnt, work;
work = out1000;
digit10 = ( U1 )( work / 1000 );
work %= 1000;
digit1 = ( U1 )( work / 100 );
work %= 100;
digit0_1 = ( U1 )( work / 10 );
digit0_01 = work % 10;
if ( digit10 != 0 ){
PORT_LED7SEG = led_7seg_tbl[ digit10 ];
for ( cnt = 0 ; cnt < ( WAIT_LONG - WAIT_SHORT ) ; cnt++ );
PORT_LED7SEG = led_7seg_tbl[ BLANK_7SEG ];
for ( cnt = 0 ; cnt < WAIT_SHORT ; cnt++ );
}
PORT_LED7SEG = led_7seg_tbl[ digit1 ];
for ( cnt = 0 ; cnt < WAIT_LONG ; cnt++ );
PORT_LED7SEG = led_7seg_tbl[ BLANK_7SEG ];
PORT_LED_DP = LED_ON;
for ( cnt = 0 ; cnt < WAIT_LONG ; cnt++ );
PORT_LED_DP = LED_OFF;
PORT_LED7SEG = led_7seg_tbl[ digit0_1 ];
for ( cnt = 0 ; cnt < ( WAIT_LONG - WAIT_SHORT ) ; cnt++ );
PORT_LED7SEG = led_7seg_tbl[ BLANK_7SEG ];
for ( cnt = 0 ; cnt < WAIT_SHORT ; cnt++ );
PORT_LED7SEG = led_7seg_tbl[ digit0_01 ];
for ( cnt = 0 ; cnt < WAIT_LONG ; cnt++ );
PORT_LED7SEG = led_7seg_tbl[ BLANK_7SEG ];
for ( cnt = 0 ; cnt < WAIT_LONG ; cnt++ );
}
プロジェクトごと圧縮したものを qy4a_volt_meter03.zip に置いておきます。
実際にこのプログラムを動かしてみたところ、表示は 9 → .→ 6 → 7 つまり 9.67V と出ました。 精度を良くする前の A-D値を用いた計算では、式(3”) のところで 9.69V と出ていたので、プログラムには問題なさそうです。 0.02V 差があるのは精度を上げたために、より正確な結果が出るようになったからだと考えられます。
最後に、半固定抵抗の調整を行います。 システム全体を見てみると、いろいろな誤差の要因が考えられます。 A-D変換器そのものの誤差、バンド・ギャップ電圧の標準値からのズレ、そして入力部分の分圧比の誤差。 これらを全部ひっくるめて、半固定抵抗で調整する仕組みです。
調整方法は簡単です。 基準とするDMM を使って入力信号の電圧を測定しながらプログラムを動かし、表示される電圧と DMM の表示が同じになるように半固定抵抗を調整してください。 これで完成です。
HC08スタータ・ボードの JP5 を外した人は、実験が済んだらまた JP5 を付けておいてください。
次回は 【書籍】 サポート記事の予定です。 HC08ミニマイコン扇風機を使い、入出力ポートとA-D変換器だけを利用するシンプルなプログラムを紹介します。
『参考文献』
「試しながら学ぶHC08マイコン入門」 (CQ出版)
第1章 マイコン電子工作を始めよう
第10章 統合開発環境CodeWarriorを使ってみる
筆者のホームページ 『マイコン工作の実験室』
組み込みエンジニア KAWANO

