PICマイコン(PIC16F1827)でAD変換を実装する

組み込みエンジニア

こんにちは、ENGかぴです。

PIC16F1827はADC機能を持っておりセンサーの電圧をアナログ情報として取り込むことができます。MCCでADCの実装例として温度センサーと照度センサーの値をアナログデータとして取り込んで動作確認を行いました。

温度センサーはMCP9700A(マイクロチップ)、照度センサーはNJL7302L-F5(西日本無線)を使用しています。

ADCを実装する

PIC16F1827のADCの設定はMCCを使って行っています。MCCの使い方については下記記事にまとめています。

MPLAB Code Configurator(MCC)の追加と使い方

今回はMCCを使用してADC及びTMR0の設定を行っています。

System Moduleの設定

最初に共通事項であるSystem Moduleの設定を行います。MPLAB X IDEを起動しMCCのアイコンをクリックしてMCCを有効にします。

System Moduleの設定(MCC)
System Moduleの設定(MCC)

内部クロックを使用するためINTOSCを選択しています。クロック周波数は任意でもよいですが4MHz_Hzを選択しています。クロックを高速にするほど消費電流が増えてしまいます。他にも設定項目はありますがクロックの設定のみとしています。

ADCを設定する

ADCをDevice Resources内のPeripherals欄のADCを選択して追加します。

ADCの設定(MCC)
ADCの設定(MCC)

ADCを選択しADC ClockをFOSC/4を選択しResult Alignmentをright(右揃え)にします。左揃えでも問題ありませんが右揃えの方が扱いやすいため右揃えにしています。

Positive Referenceは外部の電源を使用する場合はexternalを選択しますが、今回は電源電圧にするためVDDを選択し、Negative ReferenceにVSSを選択しています。

ADCが変換完了したタイミングでデータを取得するためにEnable ADC Interruptを有効にしています。

AD変換が完了したタイミングでデータ取得することでADCのサンプリングタイミングがずれにくくなります。サンプリングタイミングを気にしない場合はメイン処理でAD変換が完了したこと判断しても問題ありません。

TMR0を設定する

TMR0はDevice Resources内のPeripherals欄のTimerを選択しTMR0をクリックして追加します。

TMR0の設定(MCC)
TMR0の設定(MCC)

Timer Clock内でクロックに関する設定を行います。Clock Sourceは内部クロックを使用しているためFOSC(FOSC/4)を指定します。PrescalerはClock Sourceに対してTMR0のカウントアップする分周比を指定します。

Timer PeriodはTMR0がオーバーフローするタイミングを設定します。1ms毎にオーバーフロー割り込みが発生しますがCallback Function Rateを0x0A(10)を指定しているため10ms毎にコールバック関数が呼び出されます。

void mainTimer(void); //タイマー0割り込みのコールバック関数
//コールバック関数の使用例
TMR0_SetInterruptHandler(mainTimer); //コールバック関数を指定

上記例ではmainTimer()が10ms毎にコールされます。

MCCが生成した関数の使用例

ADC_SetInterruptHandler(AdcItr); //AD変換完了割り込み時にコールする関数

ADC_SelectChannel(channel_Temp); //内部の温度インジケーターを測定
//ADC_SelectChannel(channel_AN0); //AN0を測定
ADC_StartConversion(); //変換開始

/* AD変換割り込み処理 */
void AdcItr(void){
    tempValue = ADC_GetConversionResult(); //変換結果を変数に格納
}

AD変換完了時にAdcIrt()関数をコールバックするためにADC_SetInterruptHandler()でAdcIrt()を指定しています。

AD変換は登録したチャンネル番号に対して処理されるためAD変換開始の前に対象のチャンネル番号を指定する必要があります。上記例では温度インジケーターをAD変換する例を示しています。チャンネル番号はadc.h内にadc_channel_tの型としてenumで登録されています。

ADC_StartConversion()を発行するとAD変換が開始します。AD変換が完了するとADC割り込みが発生しAdcItr()がコールバックされます。AdcItr()ではADC_GetConversionResult()を発行してAD変換値を変数に格納します。

AD変換の考え方

10ビットのAD変換値から電圧に換算する場合以下の式に従います。$$電圧換算値 = \frac{AD変換値}{1024}×V_{DD}・・(1)$$

AD変換は基準電圧に対してAD変換値が計算されるため基準電圧のVDDが不安定である場合AD変換値が安定しなくなります。このためAD変換に使用する電源を精度の良い外付けの電源を使用する場合があります。

アナログ電圧が一定の場合電源電圧が5Vから4.85Vまで低下したとするとAD変換の値が大きく出てしまいます。そのため換算する際は電源電圧を測定したを使用すると誤差が少なくなります。

温度センサーの考え方

温度センサーであるMPC9700Aは電源端子に電圧を加えることで温度に応じた電圧を出力します。MCP9700Aのデータシートを確認すると0℃の時500mVで1℃の変化で±10mVとなっています。例えば出力電圧が0.75Vであれば温度換算すると25℃になります。

照度センサーの考え方

照度センサーの使い方
照度センサーの使い方

照度センサは新日本無線のNJL7302-F5を使用しています。照度センサを飽和領域で使用するとVCEが0.6V程度になります。

照度センサのデータシートを確認するとコレクタの電源がVceと表記されています。エミッタコレクタ間の飽和電圧と同じなので間違えてしまいそうですが、コレクタに接続しているのはプラス電源なのでDC5.0Vになります。

Vceが5.0Vになる点から流したいIL(uA)の点までを線で結んだ時のEvとの交点が飽和電圧もしくはアナログ領域での値になります。Ev=20luxで飽和領域となるようにILが30uA程度とすることでほぼ2値で判定するように使用します。

電流を絞ると抵抗が大きくなり電圧変化が大きくなり照度を細かく判定できないためほぼONかOFFかの2値での判断になります。一方抵抗を小さくすると消費電流が増えますが電圧の変化量が少なくなるため照度を比較して処理を変えるような使い方ができます。

動作確認

PIC16F1827のアナログ入力ピンにMPC9700A及びNJL7302-F5を接続してAD変換を行います。

動作確認用の回路

PIC16F1827のADCの動作確認回路
PIC16F1827のADCの動作確認回路

温度センサー(SN2)で30℃を検出したときにLDE1を点灯させるようにします。照度センサー(SN1)で照度が低下し電圧が低下したときにLDE2を点灯させます。照度は測定しにくいため手をかざして暗くして点灯するかを確認します。ピン設定は以下の通りです。

PIC16F1827のピン設定(MCC)
PIC16F1827のピン設定(MCC)

動作結果

電源はDC5Vを想定していましたが、実際には4.88Vと低く出ているためAD変換値が高めに出ているため誤差が出てしまいました。

温度インジケーター、温度センサー、照度センサーの順にAD変換をしていますが電源電圧が安定しないため温度インジケーターの値が正確に測定できていませんが、温度センサー及び照度センサーについてはそれなりに測定できていることが分かりました。

PICkit4でデバックしてレジスタの値を確認したところ温度センサーにおいては0xA3(163)であったたあめ式(1)においてVDDを4.88V(電源の実測値です)を代入して電圧値に換算すると0.77Vになります。温度センサーの出力電圧を実測すると0.75Vであったため2℃程度の誤差となっています。

照度センサーについては照度が測定できないため手をかざして照度を落とすとLDE2が点灯することが確認できています。手を離すと照度があがるためLDE2が消灯することが確認できました。

ソースコード全体

以下のソースコードはコンパイルして動作確認をしております。チェックはしておりますがコメントなど細かな部分で間違っている可能性があります。参考としてお使いいただければと思います。

#include "mcc_generated_files/mcc.h"

#define TIME_OFF -1     //タイマーを使用しない場合
#define TIME_UP 0       //タイムアップ
#define TIME_ADC_WAIT 10
#define TIME_ADC_ERR 50
#define LIGHT_JUDGE 0x100
#define TMEP_OVER 0xAA //30℃程度のADC値

typedef enum{
    AD_TEMP = 0,
    AD_TEMP_WAIT,
    AD_AN0,
    AD_AN0_WAIT,
    AD_AN1,
    AD_AN1_WAIT        
}ADC_MODE_NO;

/* 変数宣言*/
uint16_t tempValue;
uint16_t an0Value;
uint16_t an1Value;
int16_t timAdc;
int16_t timAdcErr;
ADC_MODE_NO adcmode;

/* プロトタイプ宣言*/
void mainTimer(void);
void AdcItr(void);
void mainApp(void);

/* Main application */
void main(void)
{
    SYSTEM_Initialize();
    TMR0_SetInterruptHandler(mainTimer);
    ADC_SetInterruptHandler(AdcItr);
    INTERRUPT_GlobalInterruptEnable();
    INTERRUPT_PeripheralInterruptEnable();

    while (1)
    {
        mainApp();
    }
}
/* タイマ管理関数 */
void mainTimer(void){
    
    if( timAdc > TIME_UP ){
        --timAdc;
    }
    
    if( timAdcErr > TIME_UP ){
        --timAdcErr;
    }
}
/* メイン処理 */
void mainApp(void){
 
    if( timAdc == TIME_UP){
        timAdc = TIME_ADC_WAIT;
        
        switch(adcmode){
            case AD_TEMP:
                ADC_SelectChannel(channel_Temp);
                ADC_StartConversion();
                adcmode = AD_TEMP_WAIT;
                break;
            case AD_TEMP_WAIT:
                //ADC変換待ち
                break;
            case AD_AN0:
                ADC_SelectChannel(channel_AN0);
                ADC_StartConversion();
                adcmode = AD_AN0_WAIT;                
                break;
            case AD_AN0_WAIT:
                //ADC変換待ち
                break;
            case AD_AN1:
                ADC_SelectChannel(channel_AN1);
                ADC_StartConversion();
                adcmode = AD_AN1_WAIT;                
                break;
            case AD_AN1_WAIT:
                //ADC変換待ち
                break;    
        }
        
        timAdcErr = TIME_ADC_ERR;
    }
    
    if( timAdcErr == TIME_UP){
        timAdcErr = TIME_OFF;
        adcmode = AD_TEMP;
    }
    
    if( an0Value >= TMEP_OVER ){
        IO_RB4_SetHigh();
    }
    else{
        IO_RB4_SetLow();
    }
    
    if( an1Value < LIGHT_JUDGE){
        IO_RB3_SetHigh();
    }
    else{
        IO_RB3_SetLow();
    }
}
/* AD変換割り込み処理 */
void AdcItr(void){
    
    switch(adcmode){
        case AD_TEMP_WAIT:
            adcmode = AD_AN0;
            tempValue = ADC_GetConversionResult(); //温度インジケーターの値
            break;
        case AD_AN0_WAIT:
            adcmode = AD_AN1;
            an0Value = ADC_GetConversionResult(); //温度センサーの値
            break;
        case AD_AN1_WAIT:
            adcmode = AD_TEMP;
            an1Value = ADC_GetConversionResult(); //照度センサーの値
            break;  
    }
    timAdcErr = TIME_OFF;
}
/**
 End of File
*/

本ソースコードはMPLAB X IDEにMCCのプラグインをインストールしていないと使用できません。

関連リンク

PICマイコンを使ってマイコンのレジスタの設定やMPLAB X IDEのプラグインであるMCCを使用して動作確認したことについてまとめています。

PICマイコン(PIC12F675)で実現できる機能と解説リンクまとめ

PICマイコン(PIC16F1827)で実現できる機能と解説リンクまとめ

GEEKJOB-未経験からITエンジニアに【オンライン無料体験】

最後まで、読んでいただきありがとうございました。

タイトルとURLをコピーしました