マイコン入門 AD変換を使ったセンサ制御【ADコンバータ機能】

マイコン
この記事は約19分で読めます。

こんにちは、ナナです。

A/D変換のAはAnalog、DはDigitalであり、アナログデータをデジタルデータに変換する機能です。

本記事では次の疑問点を解消する内容となっています。

本記事で学習できること
  • AD変換って何?
  • AD変換の特徴と仕組みとは?
  • AD変換ってプログラムからどうやって使うの?

では、AD変換の仕組みを学んでいきましょう。

スポンサーリンク

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のレジスタにチャネル別に変換結果が格納されます。

AD変換のレジスタ制御図

データシート表17.2に記載されていますが、光センサはAN0とAN1に接続されており、それぞれADDRAとADDRBレジスタにAD変換結果が格納されることになります。

表17.2 アナログ入力チャネルとA/D データレジスタの対応
変換結果の格納場所

A/Dコントロール/ステータスレジスタ(ADCSR)とは

ADCSRレジスタはA/D変換の動作を制御するためのレジスタです。皆さんはA/D変換器というハードウェアに対してこのレジスタを利用して変換要求を依頼することになります。

17.3.2章 A/D コントロール/ステータスレジスタ(ADCSR)
ADCSRレジスタ仕様

光センサモジュールでは次の設定でこのレジスタを制御しましょう。

  • スキャンモードとして単一モードを設定
  • クロックセレクトは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変換を行った変換結果の値が格納されるレジスタです。

17.3.1章 A/DデータレジスタA~D(ADDRA~D)

A/D データレジスタはA/D 変換結果を格納するための16 ビットのリード専用レジスタで、ADDRA~ADDRD の4本あります。各アナログ入力チャネルの変換結果が格納されるA/D データレジスタは表17.2 のとおりです。

10 ビットの変換データはA/D データレジスタのビット15からビット6 に格納されます。下位6 ビットの読み出し値は常に0 です。CPU との間のデータバスは8 ビット幅で、上位バイトはCPU から直接リードできますが、下位バイトは上位バイトリード時にテンポラリレジスタに転送されたデータが読み出されます。

このためA/D データレジスタをリードする場合は、ワードアクセスするか、バイトアクセス時は上位バイト、下位バイトの順でリードしてください。ADDR の初期値はH’0000 です。

大事な部分を太字にしてあります。このレジスタはまず読み取り専用のレジスタで2Byteのサイズとなっています。

そして2Byteのうち6~15ビットの系10ビットにAD変換の結果が格納されるとされています。これは見落としてはいけない重要なルールです。このルールを踏まえた上でプログラムを作る必要があります。

ADDRAの格納イメージ

このように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 初期化

Lsen_init仕様

3.2 光センサ値の取得

Lsen_getSensor仕様
スポンサーリンク

課題: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変換の動作モードにスキャンモードというモードがありますが、これはどのような機能なのでしょうか?

本課題では単一モードというモードでA/D変換を動かしました。このモードはアナログポート1つに対して「AD変換を実施して」とお願いするモードです。ですので、2つのアナログポートに対してA/D変換を行いたい場合は2回お願いする必要があります。

それに対してスキャンモードというのは複数のアナログポートに対して「順番にA/D変換を実施しておいて」とお願いするモードです。

本課題では扱いがわかりやすい単一モードにて変換を行っています。

ポートBに対してPB0/AN0と並列表記されていますが、どちらの機能として使用するかはどのように決めるのでしょうか?

これはデータシートのポートBレジスタの説明を見るとわかります。

9.8.1章 ポートデータレジスタB(PDRB)
ポートBレジスタ仕様

A/D変換器のADCSRでアナログ入力チャネルに指定されていると0が読みだされると記載されています。

つまり、ADSCRのチャネル設定こそがPBとANを選択するレジスタであるということです。