こんにちは、ナナです。
A/D変換のAはAnalog、DはDigitalであり、アナログデータをデジタルデータに変換する機能です。
本記事では次の疑問点を解消する内容となっています。
では、AD変換の仕組みを学んでいきましょう。
AD変換の概要
アナログデータとは皆さんが肌で感じている温度や湿度といったデータですね。
このようなアナログデータはコンピュータではそのままでは扱えないため、具体的な数値情報に変換して扱います。この際に使われる変換機能のことをA/D変換と呼びます。
デジタル体温計や湿度計などは、マイコンがA/D変換した結果を皆さんは見ているのです。A/D変換は実は身近なところによく使われているということですね。
デジタル情報をアナログ情報に変換する機能はD/A変換と呼ぶ!
本章ではA/D変換機能を使った光センサの扱い方を学びます。
A/D変換を使ったアナログ情報の取得方法
ビュートローバーには赤外線発光素子と光センサが2セット付属しています。
光センサとは光量を検出するためのセンサです。光量は電圧というアナログデータとして入力されるため、A/D変換を行いマイコン側に取り込みます。
光センサを使うことでビュートローバーは白や黒の色を識別できるようになり、ライン上を走ることができるのです。これがライントレースの仕組みです。
アナログ入力可能なピン構成
LEDなどと同様に光センサといったセンサ関連の周辺機器もマイコンのピンに接続します。ただし、どこでもよいというわけではなくアナログ電圧の入力が可能なピンに接続せねばなりません。アナログ入力可能なピンとはデータシート上で次のように記載されています。
PB0といった表記はLED制御などで使用したHigh/Lowによる2値の電圧制御ですが、AN0という表記もありますね。ANはANalogのANであり、このポートはアナログ電圧を入力してAD変換を実施できるということを示しています。このようにポートというのは複数の使い方ができる場合にPB0/AN0のように並列表記がされます。
光センサの回路図
ビュートローバーの光センサはCH6とCH7という場所につながっています。回路図上では省略されていますが、このCHの先に光センサがつながっています。
つまりAN0とAN1を制御することができれば光センサの情報をプログラムで取得できるということです。
A/D変換が必要なセンサ機器は必ず、アナログポートに接続されている!
AD変換機能の使い方
A/D変換もレジスタを利用して制御を行います。データシート「17章 A/D変換器」がその章であり、全10ページの機能になります。これくらいのページ数はハードウェア制御においては序の口なレベルの機能ですので自然と読み込めるように少しずつ慣れていきましょう。
特徴
「17.1章 特徴」には本マイコンのAD変換器の特徴が記載してあります。特殊な用語が出てきますので解説しておきましょう。
分解能
アナログの入力電圧を識別できる粒度。
10bitということは0~1023の合計1024レベルとして変換ができる。
チャネル
A/D変換が実施可能なアナログ入力の最大個数。
AN0~AN7までサポートしているため8個分のチャネルがある。
動作モード
単一モードとスキャンモードがある。変換方法が選択できる。 本章では単一モードを利用する。
高機能マイコンでは分解能が12ビットあるものも珍しくない。12ビットの場合は4096レベルの変換が可能である!
レジスタ構成
A/D変換機能に所属するレジスタは下記とされていることがデータシートからわかります。
- A/DデータレジスタA(ADDRA)
- A/DデータレジスタB(ADDRB)
- A/DデータレジスタC(ADDRC)
- A/DデータレジスタD(ADDRD)
- A/Dコントロール/ステータスレジスタ(ADCSR)
- A/Dコントロールレジスタ(ADCR)
制御の中心となるレジスタがADCSRレジスタになります。このレジスタでA/D変換器に対してAD変換を要求します。その結果、AD変換が完了するとADDRA~ADDRDのレジスタにチャネル別に変換結果が格納されます。
データシート表17.2に記載されていますが、光センサはAN0とAN1に接続されており、それぞれADDRAとADDRBレジスタにAD変換結果が格納されることになります。
A/Dコントロール/ステータスレジスタ(ADCSR)とは
ADCSRレジスタはA/D変換の動作を制御するためのレジスタです。皆さんはA/D変換器というハードウェアに対してこのレジスタを利用して変換要求を依頼することになります。
光センサモジュールでは次の設定でこのレジスタを制御しましょう。
- スキャンモードとして単一モードを設定
- クロックセレクトは134ステートを設定
- チャネルセレクトはAN0とAN1を利用するためB’000とB001を切り替えて設定
- A/Dインタラプトイネーブルは割り込みを利用しないため0を設定
これらの設定をした後でA/Dスタートのビットを1にすればA/D変換が開始されます。
A/DデータレジスタA(ADDRA)、A/DデータレジスタB(ADDRB)とは
これら2つのレジスタはAN0とAN1のAD変換を行った変換結果の値が格納されるレジスタです。
大事な部分を太字にしてあります。このレジスタはまず読み取り専用のレジスタで2Byteのサイズとなっています。
そして2Byteのうち6~15ビットの系10ビットにAD変換の結果が格納されるとされています。これは見落としてはいけない重要なルールです。このルールを踏まえた上でプログラムを作る必要があります。
このようにAD変換結果が「7」だった場合は、ビット6~15にその値が格納されることになります。そのためビットシフト等の演算を活用して目的の値を読み出す必要があります。
AD変換を使ってセンサ値を制御するLsenモジュールの作成
A/D変換の制御方法がわかりましたのでプログラムの作成を行います。いつもの手順でlsen.cとlsen.hをプロジェクトに登録してください。lsenとは光センサ:Light Sensorの略です。
Lsenモジュールの役割検討
光センサモジュールでは次の機能を実装したいと思います。
- 管理すべき光センサは2つある。
- 他モジュールからの要求によりAD変換を行い、変換結果を返却する。
- AD変換はシンプルに扱える単一モードにて動作させる。
- 初期化処理ではAD変換に必要な基本設定を行う。
- ADの変換完了割り込みは利用しない。
では、これらを満たすインターフェース仕様を検討します。
Lsenモジュールのインターフェース仕様
それではタイマモジュールのインターフェース仕様を決定します。皆さんはこの仕様を元にプログラムを作成してください。
1.提供ヘッダファイル名
#include “lsen.h”
2.定数定義
なし
3.インターフェース定義
3.1 初期化
3.2 光センサ値の取得
課題:AD変換を使ってセンサ制御をしてみよう
課題1
課題内容
インターフェース仕様に従いタイマモジュールを作成せよ。lsen.cとlsen.hは次のプログラムをベースにして修正を加えよ。
lsen.h
#ifndef LSEN_H
#define LSEN_H
//------------------------------------------------
//------------------------------------------------
// マクロ定義(Macro definition)
//------------------------------------------------
//------------------------------------------------
// 型定義(Type definition)
//------------------------------------------------
//------------------------------------------------
// プロトタイプ宣言(Prototype declaration)
//------------------------------------------------
//------------------------------------------------
#endif // LSEN_H
lsen.hに追記すべきこと
- インターフェースのプロトタイプ宣言を追加せよ
lsen.c
#include <stdio.h>
#include "iodefine.h"
#include "lsen.h"
//------------------------------------------------
// 概 要:初期化
// 引 数:なし
// 戻り値:なし
//------------------------------------------------
void Lsen_init(void)
{
}
//------------------------------------------------
// 概 要:光センサーのAD値取得
// 引 数:sen1 センサー1に関するAD値(0-1023)
// sen2 センサー2に関するAD値(0-1023)
// 戻り値:0 正常
// -1 異常
//------------------------------------------------
int Lsen_getSensor(unsigned short * sen1, unsigned short * sen2)
{
return 0;
}
lsen.cに追記すべきこと
- Lsen_init関数ではADCSRレジスタの初期化処理を実施せよ。設定値は単一モード、134ステート、割り込み禁止として設定せよ。
- Lsen_getSensor関数ではセンサ1とセンサ2に対してそれぞれAD変換を実施し、引数のsen1とsen2にAD変換結果を格納せよ。
- AD変換は変換結果が出力されるまで時間が少しかかるのでADCSRレジスタのADFフラグを監視して変換完了を待つこと。
- AD変換結果は0~1023の値になるようにすること。
lsen.h
#ifndef LSEN_H
#define LSEN_H
//------------------------------------------------
//------------------------------------------------
// マクロ定義(Macro definition)
//------------------------------------------------
//------------------------------------------------
// 型定義(Type definition)
//------------------------------------------------
//------------------------------------------------
// プロトタイプ宣言(Prototype declaration)
//------------------------------------------------
void Lsen_init(void);
int Lsen_getSensor(unsigned short * sen1, unsigned short * sen2);
//------------------------------------------------
#endif // LSEN_H
- インターフェース仕様書に従いプロトタイプ宣言を追加する。
lsen.c
#include <stdio.h>
#include "iodefine.h"
#include "lsen.h"
//------------------------------------------------
// 概 要:初期化
// 引 数:なし
// 戻り値:なし
//------------------------------------------------
void Lsen_init(void)
{
// A制御レジスタ初期化
// ADIE:0 割り込み禁止
// ADST:0 AD変換停止
// SCAN:0 単一モード
// CKS :0 137ステート
AD.ADCSR.BYTE = 0x00;
}
//------------------------------------------------
// 概 要:光センサーのAD値取得
// 引 数:sen1 センサー1に関するAD値(0-1023)
// sen2 センサー2に関するAD値(0-1023)
// 戻り値:0 正常
// -1 異常
//------------------------------------------------
int Lsen_getSensor(unsigned short * sen1, unsigned short * sen2)
{
if (sen1 == NULL || sen2 == NULL)
{
return -1;
}
//---------------------------
// 光センサー1のAD変換処理
//---------------------------
AD.ADCSR.BIT.CH = 0; // 変換チャネル指定
AD.ADCSR.BIT.ADST = 1; // AD変換開始
// AD変換完了待ち
while (AD.ADCSR.BIT.ADF == 0);
AD.ADCSR.BIT.ADF = 0; // 完了フラグクリア
*sen1 = AD.ADDRA >> 6; // AD値取得
//---------------------------
// 光センサー1のAD変換処理
//---------------------------
AD.ADCSR.BIT.CH = 1; // 変換チャネル指定
AD.ADCSR.BIT.ADST = 1; // AD変換開始
// AD変換完了待ち
while (AD.ADCSR.BIT.ADF == 0);
AD.ADCSR.BIT.ADF = 0; // 完了フラグクリア
*sen2= AD.ADDRB >> 6; // AD値取得
return 0;
}
- 変換すべきチャネルを指定してセンサ1とセンサ2を順番に変換する。
- 変換開始はADSTを1にすることで可能。
- ADFが1になるのをwhile文で監視し変換が完了したら、変換結果を6ビット右シフトして取得する。
課題2
課題内容
MAINモジュールで光センサモジュールを使えるようにせよ。
また、main関数を変更し取得した光センサ値に伴いLEDが点灯/消灯するプログラムを作成せよ。
- センサ1が512以上ならオレンジLEDを点灯する
- センサ1が512未満ならオレンジLEDを消灯する
- センサ2が512以上ならグリーンLEDを点灯する
- センサ2が512未満ならグリーンLEDを消灯する
課題が完成したらビルドを行いビュートローバー上で動作させ、期待動作通りに動くことを確認せよ。
main.c
//------------------------------------------------
// 概 要:エントリーポイント
//------------------------------------------------
void main(void)
{
// システム初期化処理
main_init();
while(1)
{
}
}
期待動作
- センサ1の下に黒色がある場合はオレンジLEDが点灯
- センサ1の下に白色がある場合はオレンジLEDが消灯
- センサ2の下に黒色がある場合はグリーンLEDが点灯
- センサ2の下に白色がある場合はグリーンLEDが消灯
main.c
#include "iodefine.h"
#include "led.h"
#include "sw.h"
#include "timer.h"
// 光センサモジュールを使用するためインクルード
#include "lsen.h"
//------------------------------------------------
// 内部プロトタイプ宣言
//------------------------------------------------
void main_init(void);
//------------------------------------------------
// 概 要:エントリーポイント
//------------------------------------------------
void main(void)
{
// システム初期化処理
main_init();
while(1)
{
unsigned short sen1;
unsigned short sen2;
// 光センサー値取得
Lsen_getSensor(&sen1, &sen2);
if (sen1 >= 512)
{
// オレンジLED点灯
Led_setLight(D_LED_KIND_ORANGE, D_LED_LIGHT_ON);
}
else
{
// オレンジLED消灯
Led_setLight(D_LED_KIND_ORANGE, D_LED_LIGHT_OFF);
}
if (sen2 >= 512)
{
// グリーンLED点灯
Led_setLight(D_LED_KIND_GREEN, D_LED_LIGHT_ON);
}
else
{
// グリーンLED消灯
Led_setLight(D_LED_KIND_GREEN, D_LED_LIGHT_OFF);
}
}
}
//------------------------------------------------
// 概 要:システム初期化
//------------------------------------------------
void main_init(void)
{
//---------------------------------------------------
// ウォッチドッグタイマの停止(消さないこと)
//---------------------------------------------------
WDT.TCSRWD.BYTE = 0x92;
WDT.TCSRWD.BYTE = 0x92;
//---------------------------------------------------
// PCRの設定(PCRは書き込み専用レジスタ)
//---------------------------------------------------
IO.PCR6 = 0x11; // P60,P64を出力
IO.PCR7 = 0x00; // P74を入力
//---------------------------------------------------
// モジュールの初期化(各モジュールの初期化を実施)
//---------------------------------------------------
Led_init();
Sw_init();
Timer_init();
Lsen_init();
}
- lsen.hをインクルードする
- main関数ではLsen_getSensor関数を呼び出しセンサ値を取得。取得結果によりLEDのオレンジとグリーンを点灯/消灯する。
- main_init関数ではLsen_init関数の呼び出しを追加する。
Q&A:AD変換に関するよくある質問
本課題では単一モードというモードでA/D変換を動かしました。このモードはアナログポート1つに対して「AD変換を実施して」とお願いするモードです。ですので、2つのアナログポートに対してA/D変換を行いたい場合は2回お願いする必要があります。
それに対してスキャンモードというのは複数のアナログポートに対して「順番にA/D変換を実施しておいて」とお願いするモードです。
本課題では扱いがわかりやすい単一モードにて変換を行っています。
これはデータシートのポートBレジスタの説明を見るとわかります。
A/D変換器のADCSRでアナログ入力チャネルに指定されていると0が読みだされると記載されています。
つまり、ADSCRのチャネル設定こそがPBとANを選択するレジスタであるということです。