A-D変換器の活用例として、簡易電圧計を作ります。 基準電圧には内蔵バンド・ギャップ電圧を利用しました。 この作品は表示方法が少し変わっていて、たとえば 9.58V のときは次のように表示します。
9 → .→ 5 → 8 →(消灯)→ 9 → .→ 5 → 8 →(消灯)→ 繰り返し
0.00V , 9.58V , 18.95V を順に表示する動画
まずは上の動画をクリックして、表示のしかたを見てください。 7セグメントLEDが1個しかない簡単な回路にもかかわらず、もっと多くの桁を表示しているのが確認できます。
いきなりこのプログラムを作るのは大変なので、段階を追って作ることにしましょう(回路は最初から最後まで同じ)。 回路図を下記に示します。 HC08スタータ・ボードとブレッド・ボードを組み合わせて実現しています。
HC08スタータ・ボードの回路図は 第18回 に掲載しています。
【 最終的な簡易型電圧計の特徴 】
入力電圧範囲 0~30V
入力インピーダンス 約110kΩ
表示能力 整数部 2桁、小数部 2桁
電源 単3アルカリ乾電池 2本
7セグメント LED について簡単に説明しておきます。 アノード・コモン型とカソード・コモン型があり、内部には 7個の LED が数字を表示できる形に配置してある部品です。 小数点を表す LED を含めて、合計 8個の LED が入っているものが多いです。
メーカや製品ごとにピン配置は異なりますが、データシート上でセグメントの位置を表す記号 a ~ g は各社共通のようです。 ピン配置の例として 松下電器 LN516 の場合を示します(LN516RA は赤色アノード・コモン)。
下の 3枚の写真 を参考にして、回路図をよく見ながらブレッド・ボード上に回路を組み立ててください。 クリックで拡大できます。
HC08スタータ・ボードの CN2、CN3 にはL字型(アングル)のピン・ヘッダを実装しています。 これについては 第19回 で説明しました。 マイコン工作ファンSHOP でも購入できます。
7セグメントLED の小数点にあたる LED は CN2 経由で PTA1 に接続されています。 これはスタータ・ボード上の LED(D4) にもつながっているので、チラチラ光ってしまって 7セグメント LED の表示が読みにくいかもしれません。 気になる場合はスタータ・ボード上の JP5 を外しておくと LED(D4) が点灯しなくなるので、7セグメント LED の表示が読みやすくなります。
スタータ・ボードを挿したままだと細かいところが良く見えないので、一時的に取り外した状態をお見せします。
前方から見たところ(クリックで拡大)
後方から見たところ(クリックで拡大)
写真ではところどころ抵抗の足が接触しているように見えますが、実際には適当に浮かせてあって接触しないようになっています。 組み立ての際はご注意ください。
◆ テスト・プログラム00
最初のプログラムの目的は、7セグメントLED の表示を確認することです。 少し長くなりますが、全体を示します。
// qy4a_volt_meter00
// 7セグメントLED の表示テスト#include <hidef.h>
#include "derivative.h"typedef unsigned char U1;
typedef unsigned int U2;//*************************
// マクロの定義
//*************************#define PORT_LED_DP ( PTA_PTA1 )
#define PORT_LED7SEG ( PTB )
#define LED_ON ( 0 )
#define LED_OFF ( 1 )//*************************
// データの定義
//*************************const U1 led_7seg_tbl[ 17 ] = {
0x80, // 0 Gfedcba-
0xF2, // 1 GFEDcbA-
0x48, // 2 gFedCba-
0x60, // 3 gFEdcba-
0x32, // 4 gfEDcbA-
0x24, // 5 gfEdcBa-
0x04, // 6 gfedcBa-
0xF0, // 7 GFEDcba-
0x00, // 8 gfedcba-
0x20, // 9 gfEdcba-
0x10, // A gfeDcba-
0x06, // B gfedcBA-
0x8C, // C GfedCBa-
0x42, // D gFedcbA-
0x0C, // E gfedCBa-
0x1C, // F gfeDCBa-
0xFE // GFEDCBA-
};//*************************
// 関数プロトタイプ宣言
//*************************void init_hardware( void );
void init_PORT( void );//*************************
// メイン処理
//*************************void main( void ){
U2 cnt;
U1 num = 0;
init_hardware( );
init_PORT( );
for ( ; ; ){
PORT_LED7SEG = led_7seg_tbl[ num ];
// 時間待ち
for ( cnt = 0 ; cnt < 60000 ; cnt++ );
// 0から Fの表示を繰り返す
num++;
if ( num > 15 ){
num = 0;
}
}
}//*************************
// リセット後の初期化処理
//*************************void init_hardware( void ) {
CONFIG2 = 0x00; // IRQピン無効、RSTピン無効
CONFIG1 = 0x01; // LVI(3V)有効、STOP無効、COP無効
}void init_PORT( void ) {
PTAPUE = 0x00;
PTA = 0x02;
DDRA = 0x02;
PTBPUE = 0x00;
PTB = 0xFE;
DDRB = 0xFE;
}// End Of File
上から順に、説明が必要と思われるところを見ていきましょう。
typedef unsigned char U1;
typedef というのは、ANSI C に準拠した正統派のコンパイラで使うことができるキーワードで、新しく型名を定義することができます。 この例では、U1 と書いたときに unsigned char と同じ意味の 型名 として扱われます。
#define PORT_LED7SEG ( PTB )
回路図と合わせて見ていただくとわかるのですが、ポートB の bit7~bit1 を 7セグメントLED にあてています。 bit0 は A-D入力なので入力方向であり、そのビットを気にしないで 1バイトまとめて出力することができます。
const U1 led_7seg_tbl[ 17 ] = {
7セグメントLED のための出力データ・パターンを配列の形で定義しました。 0番目から16番目まで、全部で 17個のデータが入っています。 0~15番目にはそれぞれ 16進数に対応する 7セグメントLEDの表示パターンが入っており、16番目は消灯させるためのパターンが入っています。 const を付けることにより、この配列を RAM ではなく ROM(フラッシュ・メモリ)に配置しています。 なお、配列のコメントにあるアルファベットはただのメモ書きですが、大文字のところを1、小文字のところを0として組み立てるとパターン・データができあがるようになっています。
プロジェクトごと圧縮したものを qy4a_volt_meter00.zip に置いておきます。 HC08スタータ・ボードで書き込んだあとデバッガ画面を閉じないで、ジャンパの設定を変えずに Start させてみてください。 ポートA をほとんど使っていないのでそのまま動かすことが可能です(細かい話をするとこのときマイコンはモニタ・モードで動いており、BUSCLKX4クロックが 9.8304MHz なので内蔵12.8MHzに比べて少し遅い)。 ジャンパの差し替えが面倒なのでこういう説明をしましたが、もちろんジャンパを差し替えて、スタンド・アロンで動かすこともできます。
テスト・プログラム00 を動かすと、0 から F の数字が順番に次々と表示されます。 動きのあるプログラムなので、これだけでもう半分くらいできたような気分になります。
◆ テスト・プログラム01
このプログラムの目的は、マイコンに内蔵された バンド・ギャップ(Band Gap)電圧を A-D変換して 7セグメントLED に 16進数で表示させることです。
バンド・ギャップ電圧というのは、(難しい話は抜きにして)電源電圧に関わらずほぼ一定の電圧(変換値ではなく電圧)が得られるもの、と覚えておいてください。 要するに、今回の最終目的のように電源電圧が乾電池だったりすると、それを基準にして入力電圧を評価することはできないため、基準となる電圧としてバンド・ギャップ電圧を利用しよう、というわけなのです。
バンド・ギャップ電圧については、実は今回が初登場ではなく 第44回 の ADCH4~ADCH0 チャネル選択ビット の表に記述されています。 ADSCR_ADCH に 11010b を書き込んだとき 「バンドギャップ基準」 が選択されます。 なお、表の中に注意書きがあるので確認しておくと、「 LVI への電力供給が必要です(CONFIG1 の LVIPWRD = 0) 」 と書かれています。 今回は LVI の電源を有効にしているので、この点は問題ありません。
プログラムは、変更したところを中心に紹介しておきます。
//*************************
// マクロの定義
//*************************#define ADCH_BG ( 0x1A ) // BANDGAP REF
#define AVERAGE_NUM ( 64 ) // 2, 4, 8,… 64以下
#define PORT_LED_DP ( PTA_PTA1 )
#define PORT_LED7SEG ( PTB )
#define LED_ON ( 0 )
#define LED_OFF ( 1 )
#define BLANK_7SEG ( 16 )
#define WAIT_TIME ( 50000 )
#define AVERAGE_NUM ( 64 ) // 2, 4, 8,… 64以下
このテスト・プログラムでは A-D変換を 64回行い、合計値を 64分の1 にした値を平均値として扱っています。 プログラム中に何度も 64 と書くよりも、一か所で定義して意味のわかる名前を付けたほうがスマートであり、仕様変更によるバグを作りにくくなります。
平均する数は、何でもよいというわけではありません。 2, 4, 8, ・・・ 64 といった 2のべき乗 が好ましいです。 その理由は、2のN乗で割るということは、Nビットだけ右シフトすることと同じなので、計算が速いのです。 ピンと来なかった方は、こう考えてみてください。
「10進数の場合、10のN乗で割るということは、N個だけ桁を減らすことと同じ(小数点以下は切り捨て)。」
ちなみに 64 ではなく 63 で試してみると、平均の結果は差が出ないのですが計算を行うために演算ライブラリを呼び出してくるので時間はかかるし、コード・サイズは増えるし、何も良いことはありませんでした。
平均する数を 64 にした場合、10ビット A-D変換の最大値が連続すると 1023 × 64 = 65472 となります。 符号なし 2バイト・サイズの変数を用意した場合、扱うことのできる数の最大値が 65535 ですから、ちょうどよい大きさだということが理解いただけたと思います。 平均する数を 64 より大きくしてしまうと、合計をしているときに 2バイト・サイズの変数に収まらなくなる可能性があるので避けてください。
A-D変換器に関する初期設定は、次のようにしました。 前回 の qy4a_adc00 と同じです。
ADCLK = 0x36; // 3.2÷2=1.6MHz、10ビット、長期
ADSCR = 0x1F; // 単独変換、停止中
長期サンプリングと短期サンプリング どちらを選ぶべきかということを考えたとき、やはりある程度は回路のことも意識しなくてはなりません。 次のようなことを考慮に入れて、どちらにするか決めてください。 なお、これは筆者の判断であり、半導体メーカが保証する判定法というわけではないのでご了承ください。
ADCLK レジスタの ADLSMP ビットについて
・ A-D変換を行う入力の外部回路のインピーダンスは 10kΩ以下にすること
(英文データシート P.167 Analog source impedance RAS の項)。
・ 大まかな目安として、外部回路のインピーダンスが 1kΩより大きいときは
長期サンプリングを推奨する。
・ 大まかな目安として、外部回路のインピーダンスが 1kΩ以下のときは
短期サンプリング、長期サンプリングどちらでもよい。 速さを優先する場合は短期
を選択し、精度を優先する場合は長期を選択すればよい。 変換時間については
データシートを参照すること。
長期サンプリングと短期サンプリング、厳密に判定法を決めようとするとこれだけで決まるものではなく、実際にどのくらいの精度が必要なのか、ということも含めて検討しなくてはならないでしょう。 あるいは逆に、求められる精度が非常に大雑把でよい場合、どちらでもかまわない、ということも考えられます。 データシートには精度に関わる項目として変換誤差の説明が詳しく書かれています。 必要に応じてそちらをご覧ください。
テスト・プログラム01 のメイン関数を示します。
//*************************
// メイン処理
//*************************void main( void ){
U2 work, cnt;
U1 hex1, hex10, hex100;
init_hardware( );
init_PORT( );
init_ADC( );
for ( ; ; ){
work = 0;
for ( cnt = 0 ; cnt < AVERAGE_NUM ; cnt++ ){
ADSCR_ADCH = ADCH_BG; // A-D開始
while ( !ADSCR_COCO ); // A-D完了待ち
work += ADR; // 取り込み
}
work /= AVERAGE_NUM;
hex1 = work & 0x0F;
work >>= 4;
hex10 = work & 0x0F;
work >>= 4;
hex100 = work & 0x0F;
PORT_LED7SEG = led_7seg_tbl[ hex100 ];
for ( cnt = 0 ; cnt < WAIT_TIME ; cnt++ );
PORT_LED7SEG = led_7seg_tbl[ hex10 ];
for ( cnt = 0 ; cnt < WAIT_TIME ; cnt++ );
PORT_LED7SEG = led_7seg_tbl[ hex1 ];
for ( cnt = 0 ; cnt < WAIT_TIME ; cnt++ );
PORT_LED7SEG = led_7seg_tbl[ BLANK_7SEG ];
for ( cnt = 0 ; cnt < WAIT_TIME ; cnt++ );
}
}
必要な情報がそろったところで、上から見ていきましょう。
↓ここは入力処理にあたる部分です。 work に平均化されたデータが入ります。
work = 0;
for ( cnt = 0 ; cnt < AVERAGE_NUM ; cnt++ ){
ADSCR_ADCH = ADCH_BG; // A-D開始
while ( !ADSCR_COCO ); // A-D完了待ち
work += ADR; // 取り込み
}
work /= AVERAGE_NUM;
↓ work に入ったデータを表示するために、16進数の 三つの桁に分解しています。 4ビット右にシフトするということは、2の4乗(16進数の1桁分のサイズ)で割るのと同じことです。
hex1 = work & 0x0F;
work >>= 4;
hex10 = work & 0x0F;
work >>= 4;
hex100 = work & 0x0F;
その後は、各桁を順番に表示する処理を行っています。
駆け足でしたが、プログラムは理解できましたか? 丁寧に見ていけば必ず理解できるはずです。 メイン関数だけでなく、ソース・ファイル全体を見たほうがイメージしやすいでしょう。 プロジェクトごと圧縮したものを qy4a_volt_meter01.zip に置いておきますので使ってみてください。 このプログラムと次回示すプログラムは、HC08スタータ・ボードで書き込んだあとでジャンパの設定をスタンド・アロンの設定(JP1 のジャンパ・リンクはショート。JP2 はジャンパ・リンクなし。JP3 は 1-2間をショート。JP4 は 1-2間をショート。VR1 は自由)に変えてから動かしてください。 先ほどと同じ手抜きのやり方では、A-D変換の値が不正確に出てしまうのでお勧めできません。
ちなみに筆者の場合は、実際に動かしてみると 16進数で 「 1 9 4 」 と表示されました。 10進数に直すと 404 です。 電池電圧がピッタリ 3V ではありませんし、皆さんの実測値がこれと同じじゃないからといってバンド・ギャップ電圧の異常を疑う必要はありません。 だいたい似たような値が表示されれば大丈夫です。
念のために計算してみると、仮に電池電圧が3.1V だった場合、バンド・ギャップ電圧は次のように求められます。
404 × 3.1 ÷ 1024 ≒ 1.22 [V]
英文データシート P.168 によるとバンド・ギャップ電圧VBG は 最小値 1.17V,標準値 1.245V,最大値 1.32V なので、納得の行く値でした。
次回は入力信号とバンド・ギャップ電圧を A-D変換して表示するテスト・プログラムを作った後、最終目標である 7セグLED電圧計のプログラムを作ります。
『参考文献』
「試しながら学ぶHC08マイコン入門」 (CQ出版)
第1章 マイコン電子工作を始めよう
第10章 統合開発環境CodeWarriorを使ってみる
筆者のホームページ 『マイコン工作の実験室』
組み込みエンジニア KAWANO
