実験・調査

【PIC】PICマイコンのAD変換の使い方を紹介する

今回はPICマイコンでのAD変換の使い方を紹介したいと思います。

今回の処理はこれまでに紹介した下記のタイムベース処理を使っていますので、定期タスクの作り方やクロック設定は下記の記事を参照してください。

PIC使い方
【PICマイコン】PICマイコンの 各種機能の使い方を紹介する

PICマイコン勉強中。 ぼちぼち更新していく予定。 PICマイコンの使い方をまとめてみる 割とネットにサンプル集がない & 自分で新規処理作ろうとしたときに設計背景思い出したくなることが多々あ ...

続きを見る

AD変換とは

Analog Digital Conversion の略です。

マイコンのポートに入力されるアナログ電圧を、ソフトウェアで制御可能なデジタル値に変換することです。

マイコン内にはアナログ・デジタル変換のためのADCユニットがあり、これを使用して電圧を数値に変換します。

センサの出力を受け取る際などに使用されます。

今回の回路図

今回の実験で使った回路図を示します。

今回作成したプログラム

今回作成したプログラムを下記に添付します。

今回作成した処理の内容は下記になります。

・AN2とAN3に可変抵抗で同じ電圧を印可し、それぞれのポートでのAD変換結果を取得。
・それぞれの値を2つの電圧レベルと比較し、上下関係に応じてRB0、RB1ポートの出力を切り替える。

少し長いですが、ほぼコメントです。

/* PIC16F1827 Configuration Bit Settings */

/* 'C' source line config statements */

/* CONFIG1 */
#pragma config FOSC = INTOSC    // Oscillator Selection (INTOSC oscillator: I/O function on CLKIN pin)
#pragma config WDTE = OFF       // Watchdog Timer Enable (WDT disabled)
#pragma config PWRTE = OFF      // Power-up Timer Enable (PWRT disabled)
#pragma config MCLRE = ON       // MCLR Pin Function Select (MCLR/VPP pin function is MCLR)
#pragma config CP = OFF         // Flash Program Memory Code Protection (Program memory code protection is disabled)
#pragma config CPD = OFF        // Data Memory Code Protection (Data memory code protection is disabled)
#pragma config BOREN = ON       // Brown-out Reset Enable (Brown-out Reset enabled)
#pragma config CLKOUTEN = OFF   // Clock Out Enable (CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin)
#pragma config IESO = ON        // Internal/External Switchover (Internal/External Switchover mode is enabled)
#pragma config FCMEN = ON       // Fail-Safe Clock Monitor Enable (Fail-Safe Clock Monitor is enabled)

/* CONFIG2 */
#pragma config WRT = OFF        // Flash Memory Self-Write Protection (Write protection off)
#pragma config PLLEN = ON       // PLL Enable (4x PLL enabled)
#pragma config STVREN = OFF      // Stack Overflow/Underflow Reset Enable (Stack Overflow or Underflow will cause a Reset)
#pragma config BORV = LO        // Brown-out Reset Voltage Selection (Brown-out Reset Voltage (Vbor), low trip point selected.)
#pragma config LVP = OFF         // Low-Voltage Programming Enable (Low-voltage programming enabled)

/* #pragma config statements should precede project file includes. */
/* Use project enums instead of #define for ON and OFF. */

/*==========================================================================================================================================*/



/*
 * File:   main.c
 * Author: ICE_MEGANE
 *
 * Created on 2020/03/5, 22:26
 */

#include <xc.h>


/* 共通マクロ設定 */
#define     CLEAR   0U
#define     SET     1U


/* 共通型名称 */
typedef  unsigned char   u8;
typedef  unsigned short  u16;
typedef  unsigned long   u32;


/* AD変換チャネル定義 */
#define ADC_AN0     ((u8)0)
#define ADC_AN1     ((u8)1)
#define ADC_AN2     ((u8)2)
#define ADC_AN3     ((u8)3)
#define ADC_AN4     ((u8)4)
#define ADC_AN5     ((u8)5)
#define ADC_AN6     ((u8)6)
#define ADC_AN7     ((u8)7)
#define ADC_AN8     ((u8)8)
#define ADC_AN9     ((u8)9)
#define ADC_AN10    ((u8)10)
#define ADC_AN11    ((u8)11)
#define ADC_CH_NUM  (ADC_AN11+(u8)1)

#define ADC_REQ     ((u8)0)
#define ADC_RESULT  ((u8)1)
#define ADC_DATA_SELECT (ADC_RESULT + (u8)1)


/* 変換時間 */
#define ADC_ACQUISION_TIME      ((u8)20)


/* 関数プロトタイプ宣言 */
static void func_main_s_variable_init( void );
static void func_main_s_register_setup( void );
static void func_main_s_program_start( void );
static void func_main_s_loop( void );
static void func_main_s_timer4_match_int_task( void );      /* メインループ時間管理用割り込みタスク */
static void func_main_s_adc_start(u8 adc_ch);               /* AD変換 開始処理 */
static void func_main_s_adc_data_get( void );               /* AD変換結果 取得処理 */

/* 変数宣言 */
static u8 u8_main_s_loop_go;
static u8 u8_main_s_20ms_task_flag;
static u8 u8_main_s_dacout_step;

/* AD変換要求 保持用配列 */
static u8 u8_main_s_adc_req_status[ ADC_CH_NUM ] =
{ /* AD変換チャネルが数値と1対1なので、配列の要素指定にそのまま使える */
    (u8)0,
    (u8)0,
    (u8)0,
    (u8)0,

    (u8)0,
    (u8)0,
    (u8)0,
    (u8)0,

    (u8)0,
    (u8)0,
    (u8)0,
    (u8)0,
};

/* AD変換結果 保持用配列 */
static u16 u16_main_s_adc_result[ ADC_CH_NUM ] =
{
    (u16)0,
    (u16)0,
    (u16)0,
    (u16)0,

    (u16)0,
    (u16)0,
    (u16)0,
    (u16)0,

    (u16)0,
    (u16)0,
    (u16)0,
    (u16)0,
};

/**************************************************************/
/*  Function:                                                 */
/*  main task                                                 */
/*                                                            */
/**************************************************************/
void main(void)
{   
    func_main_s_register_setup();       /* レジスタ初期化 */
    func_main_s_variable_init();        /* 変数初期化 */
    func_main_s_program_start();        /* プログラム開始設定 */

    while(1)
    { /* 20ms 周期タスク */
        if( u8_main_s_loop_go == SET )
        {
            func_main_s_loop();
            u8_main_s_loop_go = CLEAR;
        }
    }
}



/**************************************************************/
/*  Function:                                                 */
/*  割り込み処理                                               */
/*  割り込み発生時にここに来る                                  */
/**************************************************************/
void __interrupt() isr( void )
{
    if( ( PIR3 & 0x02U ) != 0U )
    { /* TMR4 一致割り込み発生 */
        func_main_s_timer4_match_int_task();        /* TMR4 一致割り込み処理 */
        PIR3 &= (u8)~0x02U;                         /* 割り込みフラグクリア */
    }

    if( ( PIR1 & 0x40U ) != 0U )
    { /* AD変換 完了割り込み発生 */
        func_main_s_adc_data_get();                 /* AD変換結果格納 & 残りの変換要求を処理する */
        PIR1 &= (u8)~0x40U;                         /* 割り込みフラグクリア */
    }
}





/*******************/
/* static function */
/*******************/
/**************************************************************/
/*  Function:                                                 */
/*  メインループ処理                                           */
/*                                                            */
/**************************************************************/
static void func_main_s_loop( void )
{
    u8 u8_loop_cnt;

    u16 u16_adc_result_an2;
    u16 u16_adc_result_an3;

    /* テスト出力 */
    /* 10ms感覚で出力反転 */
    if( u8_main_s_20ms_task_flag == CLEAR )
    {
        u8_main_s_20ms_task_flag = SET;
        LATA |= 0x02U;          /* RA1 : HI */
    }
    else
    {
        u8_main_s_20ms_task_flag = CLEAR;
        LATA &= (u8)~0x02U;     /* RA1 : LOW */
    }


    /* 処理中にAD変換結果が変わらないように、次の変換前に値を取得する */
    u16_adc_result_an2 = u16_main_s_adc_result[ ADC_AN2 ];
    u16_adc_result_an3 = u16_main_s_adc_result[ ADC_AN3 ];

    if( u16_adc_result_an2 > (u16)255)
    {
        LATB |= (u8)0x01;
    }
    else
    {
        LATB &= (u8)~0x01;
    }

    if( u16_adc_result_an3 > (u16)767 )
    {
        LATB |= (u8)0x02;
    }
    else
    {
        LATB &= (u8)~0x02;
    }

    /* 次のAD変換開始処理(変換結果は次のタスクで使用する) */
    func_main_s_adc_start( ADC_AN2 );
    func_main_s_adc_start( ADC_AN3 );
}


/**************************************************************/
/*  Function:                                                 */
/*  変数初期設定                                               */
/*                                                            */
/**************************************************************/
static void func_main_s_variable_init( void )
{
    u8_main_s_loop_go = CLEAR;
    u8_main_s_20ms_task_flag = CLEAR;
    u8_main_s_dacout_step = CLEAR;
}


/**************************************************************/
/*  Function:                                                 */
/*  レジスタ初期設定                                            */
/*                                                            */
/**************************************************************/
static void func_main_s_register_setup( void )
{
    /* クロック周波数設定 */
    /*=================================================================*/
                            /*  +------------------------- 7 : SPLLEN  */ /* PLL 無効化 ※#pragmaセクションで設定したので、実際は常時許可状態(datasheet P.67) */
                            /*  | +----------------------- 6 : IRCF<3> */ /* 1110 : 32MHz HFINTOSC に設定 */
                            /*  | | +--------------------- 5 : IRCF<2> */ 
                            /*  | | | +------------------- 4 : IRCF<1> */
                            /*  | | | |                                */
                            /*  | | | |     +------------- 3 : IRCF<0> */
                            /*  | | | |     | +----------- 2 : -       */
                            /*  | | | |     | | +--------- 1 : SCS<1>  */ /* 00 : FOSC ※#pragmaセクションで設定している */
                            /*  | | | |     | | | + ------ 0 : SCS<0>  */
    OSCCON =  0x70U;        /*  0 1 1 1  -  0 # 0 0                    */
    /*-----------------------------------------------------------------*/
    /* memo                                                            */
    /*                                                                 */
    /*=================================================================*/


    /* クロック周波数微調整 */
    /*=================================================================*/

                            /*  +------------------------- 7 : -       */
                            /*  | +----------------------- 6 : -       */
                            /*  | | +--------------------- 5 : TUN5    */
                            /*  | | | +------------------- 4 : TUN4    */
                            /*  | | | |                                */
                            /*  | | | |     +------------- 3 : TUN3    */
                            /*  | | | |     | +----------- 2 : TUN2    */
                            /*  | | | |     | | +--------- 1 : TUN1    */
                            /*  | | | |     | | | + ------ 0 : TUN0    */
    OSCTUNE =  0x00U;        /*  0 0 0 0  -  0 0 1 1                    */
    /*-----------------------------------------------------------------*/
    /* memo                                                            */
    /* 特に調整するつもりはないので初期設定                               */
    /*=================================================================*/


    /* Option register setup */
    /* 今回は#pragmaセクションで設定したので、設定しなくてもOK。 */


    /* 割り込み設定 */
    /*=================================================================*/
                            /*  +------------------------- 7 : GIE     */   /* 全体割り込み 許可 */
                            /*  | +----------------------- 6 : PEIE    */   /* ペリフェラル割り込み 許可 */
                            /*  | | +--------------------- 5 : TMR0IE  */   /* タイマ0割り込み 禁止 */
                            /*  | | | +------------------- 4 : INTE    */   /* 外部割込み 禁止 */
                            /*  | | | |                                */
                            /*  | | | |     +------------- 3 : IOCIE   */   /* 外部ピン変化割り込み 禁止 */
                            /*  | | | |     | +----------- 2 : TMR0IF  */   /* タイマ0 オーバーフロー割り込み禁止 */
                            /*  | | | |     | | +--------- 1 : INTF    */   /* 外部割込みフラグ クリア */
                            /*  | | | |     | | | + ------ 0 : IOCIF   */   /* 外部ピン変化割り込みフラグ クリア */
    INTCON =  0x40U;        /*  0 1 0 0  -  0 0 0 0                    */
    /*-----------------------------------------------------------------*/
    /* memo                                                            */
    /* 初期化時点では全体割り込みは無効化しておく                          */
    /*=================================================================*/


    /* 割り込み設定 */
    /*=================================================================*/

                            /*  +------------------------- 7 : TMR1GIE */
                            /*  | +----------------------- 6 : ADIE    */   /* AD変換完了割り込み 有効 */
                            /*  | | +--------------------- 5 : RCIE    */
                            /*  | | | +------------------- 4 : TXIE    */
                            /*  | | | |                                */
                            /*  | | | |     +------------- 3 : SSP1IE  */
                            /*  | | | |     | +----------- 2 : CCP1IE  */
                            /*  | | | |     | | +--------- 1 : TMR2IE  */
                            /*  | | | |     | | | + ------ 0 : TMR1IE  */
    PIE1 = 0x40U;           /*  0 0 0 0  -  0 0 1 0                    */
    /*-----------------------------------------------------------------*/
    /* memo                                                            */
    /*                                                                 */
    /*=================================================================*/


    /* 割り込み設定 */
    /*=================================================================*/

                            /*  +------------------------- 7 : -       */
                            /*  | +----------------------- 6 : -       */
                            /*  | | +--------------------- 5 : CCP4IE  */   /* CCP4割り込み 禁止 */
                            /*  | | | +------------------- 4 : CCP3IE  */   /* CCP3割り込み 禁止 */
                            /*  | | | |                                */
                            /*  | | | |     +------------- 3 : TMR6IE  */   /* タイマ6 一致割り込み 禁止 */
                            /*  | | | |     | +----------- 2 : -       */
                            /*  | | | |     | | +--------- 1 : TMR4IE  */   /* タイマ4 一致割り込み 許可 */
                            /*  | | | |     | | | + ------ 0 : -       */
    PIE3 = 0x02U;           /*  0 0 0 0  -  0 0 1 0                    */
    /*-----------------------------------------------------------------*/
    /* memo                                                            */
    /*                                                                 */
    /*=================================================================*/


    /* 割り込み設定 */
    /*=================================================================*/

                            /*  +------------------------- 7 : TMR1GIF */
                            /*  | +----------------------- 6 : ADIF    */
                            /*  | | +--------------------- 5 : RCIF    */
                            /*  | | | +------------------- 4 : TCIF    */
                            /*  | | | |                                */
                            /*  | | | |     +------------- 3 : SSP1IF  */
                            /*  | | | |     | +----------- 2 : CCP1IF  */
                            /*  | | | |     | | +--------- 1 : TMR2IF  */
                            /*  | | | |     | | | + ------ 0 : TMR1IF  */
    PIR1 = 0x00U;           /*  0 0 0 0  -  0 0 0 0                   */
    /*-----------------------------------------------------------------*/
    /* memo                                                            */
    /*                                                                 */
    /*=================================================================*/

    /* 割り込み設定 */
    /*=================================================================*/

                            /*  +------------------------- 7 : -       */
                            /*  | +----------------------- 6 : -       */
                            /*  | | +--------------------- 5 : CCP4IF  */   /* CCP4割り込みフラグ クリア */
                            /*  | | | +------------------- 4 : CCP3IF  */   /* CCP3割り込みフラグ クリア */
                            /*  | | | |                                */
                            /*  | | | |     +------------- 3 : TMR6IF  */   /* タイマ6 一致割り込みフラグ クリア */
                            /*  | | | |     | +----------- 2 : -       */
                            /*  | | | |     | | +--------- 1 : TMR4IF  */   /* タイマ4 一致割り込みフラグ クリア */
                            /*  | | | |     | | | + ------ 0 : -       */
    PIR3 = 0x00U;            /*  0 0 0 0  -  0 0 0 0                    */
    /*-----------------------------------------------------------------*/
    /* memo                                                            */
    /*                                                                 */
    /*=================================================================*/

    /* ポート入出力方向設定:A */
    /*=================================================================*/

                            /*  +------------------------- 7 : TRISA7  */
                            /*  | +----------------------- 6 : TRISA6  */
                            /*  | | +--------------------- 5 : TRISA5  */
                            /*  | | | +------------------- 4 : TRISA4  */
                            /*  | | | |                                */
                            /*  | | | |     +------------- 3 : TRISA3  */ /* RA3:入力 */
                            /*  | | | |     | +----------- 2 : TRISA2  */ /* RA2:入力 */
                            /*  | | | |     | | +--------- 1 : TRISA1  */
                            /*  | | | |     | | | + ------ 0 : TRISA0  */
    TRISA = 0x0CU;          /*  0 0 0 0  -  1 1 0 0                    */
    /*-----------------------------------------------------------------*/
    /* memo                                                            */
    /* 0:出力、1:入力                                                   */
    /* RA4は入力専用ポートなので注意                                     */
    /*=================================================================*/

    /* ポート入出力方向設定:B */
    /*=================================================================*/

                            /*  +------------------------- 7 : TRISB7  */
                            /*  | +----------------------- 6 : TRISB6  */
                            /*  | | +--------------------- 5 : TRISB5  */
                            /*  | | | +------------------- 4 : TRISB4  */
                            /*  | | | |                                */
                            /*  | | | |     +------------- 3 : TRISB3  */
                            /*  | | | |     | +----------- 2 : TRISB2  */
                            /*  | | | |     | | +--------- 1 : TRISB1  */
                            /*  | | | |     | | | + ------ 0 : TRISB0  */
    TRISB = 0x00U;          /*  0 0 0 0  -  0 0 0 0                    */
    /*-----------------------------------------------------------------*/
    /* memo                                                            */
    /* 0:出力、1:入力                                                   */
    /* RA4は入力専用ポートなので注意                                     */
    /*=================================================================*/


    /* ポート読み取りデータ初期化:A */
    /*=================================================================*/

                            /*  +------------------------- 7 : RA7     */
                            /*  | +----------------------- 6 : RA6     */
                            /*  | | +--------------------- 5 : RA5     */
                            /*  | | | +------------------- 4 : RA4     */
                            /*  | | | |                                */
                            /*  | | | |     +------------- 3 : RA3     */
                            /*  | | | |     | +----------- 2 : RA2     */
                            /*  | | | |     | | +--------- 1 : RA1     */
                            /*  | | | |     | | | + ------ 0 : RA0     */
    PORTA = 0x00U;           /*  0 0 0 0  -  0 0 0 0                    */
    /*-----------------------------------------------------------------*/
    /* memo                                                            */
    /*                                                                 */
    /*=================================================================*/


    /* ポート読み取りデータ初期化:B */
    /*=================================================================*/

                            /*  +------------------------- 7 : RB7     */
                            /*  | +----------------------- 6 : RB6     */
                            /*  | | +--------------------- 5 : RB5     */
                            /*  | | | +------------------- 4 : RB4     */
                            /*  | | | |                                */
                            /*  | | | |     +------------- 3 : RB3     */
                            /*  | | | |     | +----------- 2 : RB2     */
                            /*  | | | |     | | +--------- 1 : RB1     */
                            /*  | | | |     | | | + ------ 0 : RB0     */
    PORTB = 0x00U;           /*  0 0 0 0  -  0 0 0 0                    */
    /*-----------------------------------------------------------------*/
    /* memo                                                            */
    /*                                                                 */
    /*=================================================================*/


    /* GPIO or AD変換選択:A */
    /*=================================================================*/
                            /*  +------------------------- 7 : -       */
                            /*  | +----------------------- 6 : -       */
                            /*  | | +--------------------- 5 : -       */
                            /*  | | | +------------------- 4 : ANSA4   */
                            /*  | | | |                                */
                            /*  | | | |     +------------- 3 : ANSA3   */   /* RA3 アナログ入力 */
                            /*  | | | |     | +----------- 2 : ANSA2   */   /* RA2 アナログ入力 */
                            /*  | | | |     | | +--------- 1 : ANSA1   */
                            /*  | | | |     | | | + ------ 0 : ANSA0   */
    ANSELA = 0x0CU;         /*  0 0 0 0  -  1 1 0 0                    */
    /*-----------------------------------------------------------------*/
    /* memo                                                            */
    /*                                                                 */
    /*=================================================================*/

    /* GPIO or AD変換選択:B */
    /*=================================================================*/
                            /*  +------------------------- 7 : ANSB7   */
                            /*  | +----------------------- 6 : ANSB6   */
                            /*  | | +--------------------- 5 : ANSB5   */
                            /*  | | | +------------------- 4 : ANSB4   */
                            /*  | | | |                                */
                            /*  | | | |     +------------- 3 : ANSB3   */
                            /*  | | | |     | +----------- 2 : ANSB2   */
                            /*  | | | |     | | +--------- 1 : ANSB1   */
                            /*  | | | |     | | | + ------ 0 : -       */
    ANSELB = 0x00U;          /*  0 0 0 0  -  0 0 0 0                    */
    /*-----------------------------------------------------------------*/
    /* memo                                                            */
    /*                                                                 */
    /*=================================================================*/


    /* AD変換設定1 */
    /*==================================================================*/
                            /*  +------------------------- 7 : -        */
                            /*  | +----------------------- 6 : CHS4     */
                            /*  | | +--------------------- 5 : CHS3     */
                            /*  | | | +------------------- 4 : CHS2     */
                            /*  | | | |                                 */
                            /*  | | | |     +------------- 3 : CHS1     */
                            /*  | | | |     | +----------- 2 : CHS0     */  /* 初期化時はいったん AN0指定 */
                            /*  | | | |     | | +--------- 1 : GO/nDONE */  /* 初期化時は実行しない */
                            /*  | | | |     | | | + ------ 0 : ADON     */  /* 初期化時にAD変換ユニット 有効化 */
    ADCON0 = 0x01U;         /*  0 0 0 0  -  0 0 0 1                     */
    /*------------------------------------------------------------------*/
    /* memo                                                             */
    /*                                                                  */
    /*==================================================================*/

    /* AD変換設定2 */
    /*==================================================================*/
                            /*  +------------------------- 7 : ADFM     */  /* 右詰め LSBから埋めてく */
                            /*  | +----------------------- 6 : ADCS2    */
                            /*  | | +--------------------- 5 : ADCS1    */
                            /*  | | | +------------------- 4 : ADCS0    */  /* 110:クロック32MHzのため、Fosc/64に設定 */
                            /*  | | | |                                 */
                            /*  | | | |     +------------- 3 : -        */
                            /*  | | | |     | +----------- 2 : ADNREF   */  /* 負側の電位基準:VSS(GND) */
                            /*  | | | |     | | +--------- 1 : ADPREF1  */
                            /*  | | | |     | | | + ------ 0 : ADPREF0  */  /* 正側の電位基準:AVDD */
    ADCON1 = 0xA0U;         /*  1 0 1 0  -  0 0 0 0                     */
    /*------------------------------------------------------------------*/
    /* memo                                                             */
    /*                                                                  */
    /*==================================================================*/

    /* AD変換設定3 */
    /*==================================================================*/
                            /*  +------------------------- 7 : -        */
                            /*  | +----------------------- 6 : -        */
                            /*  | | +--------------------- 5 : -        */
                            /*  | | | +------------------- 4 : -        */
                            /*  | | | |                                 */
                            /*  | | | |     +------------- 3 : -        */
                            /*  | | | |     | +----------- 2 : -        */
                            /*  | | | |     | | +--------- 1 : ADRES9   */
                            /*  | | | |     | | | + ------ 0 : ADRES8   */
    ADRESH = 0x00U;         /*  0 0 0 0  -  0 0 0 0                     */
    /*------------------------------------------------------------------*/
    /* memo                                                             */
    /* ADFMの設定内容によって右詰め、左詰め変わるので注意                   */
    /*==================================================================*/

    /* AD変換設定3 */
    /*==================================================================*/
                            /*  +------------------------- 7 : ADRES7   */
                            /*  | +----------------------- 6 : ADRES6   */
                            /*  | | +--------------------- 5 : ADRES5   */
                            /*  | | | +------------------- 4 : ADRES4   */
                            /*  | | | |                                 */
                            /*  | | | |     +------------- 3 : ADRES3   */
                            /*  | | | |     | +----------- 2 : ADRES2   */
                            /*  | | | |     | | +--------- 1 : ADRES1   */
                            /*  | | | |     | | | + ------ 0 : ADRES0   */
    ADRESL = 0x00U;         /*  0 0 0 0  -  0 0 0 0                     */
    /*------------------------------------------------------------------*/
    /* memo                                                             */
    /* ADFMの設定内容によって右詰め、左詰め変わるので注意                   */
    /*==================================================================*/

    /* タイマ4の設定 */
    /* ※一致割り込みを使用するだけなら、CCPの設定はいらない */
    /* タイマレジスタ初期化 */
    /*=================================================================*/

                            /*  +------------------------- 7 :         */
                            /*  | +----------------------- 6 :         */
                            /*  | | +--------------------- 5 :         */
                            /*  | | | +------------------- 4 :         */
                            /*  | | | |                                */
                            /*  | | | |     +------------- 3 :         */
                            /*  | | | |     | +----------- 2 :         */
                            /*  | | | |     | | +--------- 1 :         */
                            /*  | | | |     | | | + ------ 0 :         */
    TMR4 = 0xFFU;            /*  0 0 0 0  -  0 0 1 1                    */
    /*-----------------------------------------------------------------*/
    /* memo                                                            */
    /*                                                                 */
    /*=================================================================*/
    

    /* タイマ4初期設定 */
    /* 今回は100msの定期割り込みを発生させたい */
    /*====================================================================*/
                            /*  +------------------------- 7 : -          */
                            /*  | +----------------------- 6 : TxOUTPS<3> */    /* タイマ4 ポストスケーラ:1001 -> 1/10 */
                            /*  | | +--------------------- 5 : TxOUTPS<2> */
                            /*  | | | +------------------- 4 : TxOUTPS<1> */
                            /*  | | | |                                   */
                            /*  | | | |     +------------- 3 : TxOUTPS<0> */
                            /*  | | | |     | +----------- 2 : TMRxON     */    /* タイマ4動作 停止 */
                            /*  | | | |     | | +--------- 1 : TxCKPS<1>  */    /* タイマ4 プリスケーラ:11 -> 1/64 */
                            /*  | | | |     | | | + ------ 0 : TxCKPS<0>  */
    T4CON = 0x4BU;          /*  0 1 0 0  -  1 0 1 1            */
    /*--------------------------------------------------------------------*/
    /* memo                                                               */
    /* クロック:32MHz                                                      */
    /* ペリフェラルクロック:Fosc/4 = 8MHz                                  */
    /* 割り込みポストスケーラ:1:10 -> 800kHz                               */
    /* プリスケーラ:1/64 -> 12,500Hz                                      */
    /* 一致カウント数設定:250 -> 12,500/250 = 50Hz -> 20ms                 */
    /*====================================================================*/


    /* タイマ4 一致割り込み発生数値 */
    /*=================================================================*/
                            /*  +------------------------- 7 :         */
                            /*  | +----------------------- 6 :         */
                            /*  | | +--------------------- 5 :         */
                            /*  | | | +------------------- 4 :         */
                            /*  | | | |                                */
                            /*  | | | |     +------------- 3 :         */
                            /*  | | | |     | +----------- 2 :         */
                            /*  | | | |     | | +--------- 1 :         */
                            /*  | | | |     | | | + ------ 0 :         */
    PR4 = 0x7DU;            /*  1 1 1 1  -  1 0 1 0                    */
    /*-----------------------------------------------------------------*/
    /* memo                                                            */
    /* 125:10ms相当に設定                                               */
    /*=================================================================*/

    /* テンプレート */
    /*=========================================================*/

                            /*  +------------------------- 7 : */
                            /*  | +----------------------- 6 : */
                            /*  | | +--------------------- 5 : */
                            /*  | | | +------------------- 4 : */
                            /*  | | | |                        */
                            /*  | | | |     +------------- 3 : */
                            /*  | | | |     | +----------- 2 : */
                            /*  | | | |     | | +--------- 1 : */
                            /*  | | | |     | | | + ------ 0 : */
        /*  0 0 0 0  -  0 0 1 1            */
    /*-----------------------------------------------------------------*/
    /* memo                                                            */
    /*                                                                 */
    /*=================================================================*/
}


/**************************************************************/
/*  Function:                                                 */
/*  プログラム開始直前の設定                                    */
/*                                                            */
/**************************************************************/
static void func_main_s_program_start( void )
{
    T4CON  |= 0x04U;     /* タイマ4 動作開始 */
    INTCON |= 0x80U;     /* 全体割り込み許可 */
}


/**************************************************************/
/*  Function:                                                 */
/*  タイマ4 一致割り込み処理                                    */
/*                                                            */
/**************************************************************/
static void  func_main_s_timer4_match_int_task( void )
{
    u8_main_s_loop_go = SET;
}


/**************************************************************/
/*  Function:                                                 */
/*  AD変換開始要求処理                                         */
/*                                                            */
/**************************************************************/
static void func_main_s_adc_start(u8 adc_ch)
{
    u8 u8_adc_wait;
    u8 u8_setting_buff;
    u8 u8_adc_now_process;

    u8_main_s_adc_req_status[ adc_ch ] = SET;       /* AD変換要求を格納する */

    u8_adc_now_process = ADCON0;
    
    if( ( u8_adc_now_process & (u8)0x02 ) == (u8)0 )
    { /* 現在実行中のAD変換がない = 最初の1回目の変換待ち状態 */
        u8_setting_buff = (u8)( adc_ch << 2U );     /* CHSは6~2bit目なので、下2bit分シフトする */
        ADCON0 &= ((u8)~0x7C);              /* CHSビットをいったんすべてクリア */
        ADCON0 |= u8_setting_buff;          /* 今回変換したい */

        /* AD変換の対象チャネル切り替え後はアクイジョン・タイムを設ける */
        /* 短すぎるとサンプルホールド用コンデンサの充電が足りず、AD変換結果が期待値より下がるので注意 */
        u8_adc_wait = ADC_ACQUISION_TIME;
        while( u8_adc_wait > (u8)0 )
        {
            u8_adc_wait--;
        }

        ADCON0 |= (u8)0x02;     /* GO_nDONE = SET:ADC開始 (完了後は自動クリアされる) */
    }

    /* ここで変換要求を出して以降、割り込み内でこの関数を再度呼び出し、自動で連続処理する */
}

/**************************************************************/
/*  Function:                                                 */
/*  AD変換 値取得処理                                          */
/*                                                            */
/**************************************************************/
static void func_main_s_adc_data_get( void )
{
    u8 u8_adc_ch;
    u8 u8_loopcnt;
    u16 u16_adc_result;

    /* 現在のAD変換対象の取得(この処理自体が割り込みで呼ばれる=初回の変換は完了している地点からスタート) */
    u8_adc_ch = ADCON0;
    u8_adc_ch = u8_adc_ch & (u8)0x7C;       /* ビットマスク:0b0111-1100 */
    u8_adc_ch = u8_adc_ch >> 2U;            /* CHSのデータを2bit下位シフトして数値として使えるようにする */

    /* AD変換結果の取得 */
    u16_adc_result = (u16)ADRESH;
    u16_adc_result = u16_adc_result << 8U;              /* 上位データを8ビット上へ移動 ※データはLSB詰めで取得している */
    u16_adc_result += (u16)ADRESL;                           /* 下位データはそのまま加算する */

    u16_main_s_adc_result[ u8_adc_ch ] = u16_adc_result;    /* AD変換結果取得 */
    u8_main_s_adc_req_status[ u8_adc_ch ] = CLEAR;          /* 変換要求クリア */

    /* AD変換未完了CHの検索 */
    for( u8_loopcnt = (u8)0; u8_loopcnt < ADC_CH_NUM; u8_loopcnt++ )
    { /* AD変換要求が連続して発生 = 渋滞している場合への対応 */
        if( u8_main_s_adc_req_status[ u8_loopcnt ] == SET )
        { /* AD変換要求があるチャネルを発見 */
            break;
        }
    }

    /* 次のAD変換実行 */
    if( u8_loopcnt < ADC_CH_NUM )
    { /* 少なくとも1chの未完了-変換要求が残っている。 */
        func_main_s_adc_start( u8_loopcnt );
    }
}

動作確認結果

今回のサンプルプログラムの動作確認結果です。

黄色がAN2とAN3ポートに入力したアナログ電圧になります。

電圧を上げるに従い、RB0(水色)、RB1(ピンク色)のGPIO出力が切り替わっていることが確認できます。

今回AN2の変換結果には閾値255との比較を、
AN3の変換結果には閾値767との比較を行っています。

電源電圧は5Vを使用しているため、5Vを1023分割したうちの割合で考えると、それぞれ

となります。

カーソルでポートのレベルが変化した地点の電圧レベルを確認してみると、大体あっていそうです。

今回電圧を連続的に上げていったのに対し、サンプリング間隔を10msにしているため、若干誤差が出てしまいましたが、動作としては問題なしです。(もう少し黄色の電圧をゆっくり変化させれば一致します。)

詳細設定

詳細な設定について、軽く触れていきたいと思います。

機能概要の把握

今回使用するPIC16F1827のAD変換機能は、下記のような概要です。

スリープ中もAD変換可能なようです。

続いてブロック図を確認していきます。

PIC16F1827のAD変換ブロック図は下記のようになっています。

このブロック図から下記のことがわかります。

・AD変換チャネル数は0~11の計12コ
・AD変換の基準電圧は正側、負側で個別に選択可能。
・AD変換ユニットのON/OFF切り替えが可能
・AD変換実行のための入力あり
・AD変換結果はADRESH、ADRESLレジスタへ保存される。
・AD変換結果は、8+8の16ビットのうち、右詰め、左詰めを選択できる。

こんなところでしょうか。

大体概要が把握できたので続いて詳細設定をしていきます。

ポートのアナログ入力設定

ポートの設定を行います。

PICにはピンで扱う電圧レベルがH/Lの2種類のGPIOモード、アナログ入力に対応できるようにするアナログ入力モードがあります。

ピンへの入力をアナログ電圧レベルで受け取れるようにする場合、ANSELxレジスタを設定します。

今回はRA2、RA3の2つのポートをAD変換で使用するアナログ入力ポートにしたいため、この2ビットのみ1を設定しています。

ポートの入出力方向設定

先ほどはポートを入力として使う場合に、アナログ入力とするか、デジタル入力とするかを設定ました。

そもそもポートを入力/出力のどちらで使うかについては、また別のレジスタ設定が必要です。

ポートの入出力方向は、TRISxレジスタで設定します。

今回はRA2、RA3のみ入力として使用します。

ポートレジスタの初期化

正直ポートの出力データレジスタ、はマイコンリセット時に初期化されるので、最初にクリアするかは好みだと思いますが、一応。

変換対象チャネルの選択/AD変換開始/AD変換ユニット動作許可

ADCON0レジスタではAD変換対象のチャネル、AD変換の動作許可、AD変換開始フラグの設定を行います。

CHS<4:0>:00000
 初期化時はとりあえずAN0指定しておく。(特に意味はない)

GO/nDONE:0
 AD変換開始フラグ。1セットすると変換開始、変換完了すると自動的に0になる。
 とりあえず初回は0で変換はしない。

ADON:1
 AD変換ユニットはマイコン初期化時に起動させておく。

AD変換結果のフォーマット/AD変換クロック/AD変換基準電圧の選択

AD変換関連の設定について、2つ目のレジスタです。

ADFM:1
 AD変換結果はLSB、右詰め。AD変換分解能は10bitあるため、2つの8bitレジスタに上位と下位で分けて保存される。今回は下位ビットに詰めて使用。

ADCS<2:0>:110
 AD変換のクロックスピードを指定。今回はFosc=32MHzの64分の1を使用。

変換時間はクロック周波数ごとに指定があります。守らないとAD変換結果が適切なものになら居ない可能性があるので注意です。

AD変換結果レジスタ

AD変換結果を格納するレジスタについて、PICは10bitAD変換が可能なため、8bitのレジスタ2つに結果が格納されます。

ADFM=1としてデータを右詰めにした場合、下記のようなフォーマットで変換結果が保存されます。

一応初期化時は0クリアしておきました。

ソフトウェアについて

以上でAD変換結果に関する設定は完了です。

ソフトウェアに関してもざっくり説明してみます。

処理の流れ

メインループ部分では、割り込みで取得したAD変換結果の取得、その値をもとにPORTBの0番と1番のポートの点灯切り替えを行っています。

PIC16F1827のADCは10bit分解能のため、最大で1023まで取得できます。

AD変換開始の処理ですが、ループの一番最後で実施しています。

このためこの処理内で実施したAD変換結果は次のメインループで使用されます。

ループ内で使用しているu16_adc_resultは、1つ前のループで取得した値を使用しているので、途中で値が変わってしまうことはありません。

static void func_main_s_loop( void )
{
    ・・・・

    /* 処理中にAD変換結果が変わらないように、すでに変換完了している値を取得、関数内で利用する */
    u16_adc_result_an2 = u16_main_s_adc_result[ ADC_AN2 ];
    u16_adc_result_an3 = u16_main_s_adc_result[ ADC_AN3 ];

    if( u16_adc_result_an2 > (u16)255)
    {
  ・・・・


    /* 次のAD変換開始処理(変換結果は次のタスクで使用する) */
    func_main_s_adc_start( ADC_AN2 );
    func_main_s_adc_start( ADC_AN3 );
}

割り込みによる値の連続取得

今回の処理では割り込みにより値を連続して取得できるようにしています。

一度 func_main_s_adc_start() を実行した場合、u8_main_s_adc_req_status[]に割り込み要求がセットされます。

static void func_main_s_adc_start(u8 adc_ch)
{
    u8 u8_adc_wait;
    u8 u8_setting_buff;
    u8 u8_adc_now_process;

    u8_main_s_adc_req_status[ adc_ch ] = SET;       /* AD変換要求を格納する */
  ・・・・
}

以降は割り込みが実行中でないか、ADCON0の1bit目を確認し、実行中なら割り込み要求だけセットして終了します。

以降、AD変換が完了してADIFビットが立った場合、割り込み処理内でデータ取得処理を実施します。

ビット指定で操作するとたまに動作がおかしくなることがあるので、レジスタ全体で書き換えをしています。
その都合上、多少ややこしく見えますが、基本的にはレジスタを設定しているだけです。

static void func_main_s_adc_data_get( void )
{
    u8 u8_adc_ch;
    u8 u8_loopcnt;
    u16 u16_adc_result;

    /* 現在のAD変換対象の取得(この処理自体が割り込みで呼ばれる=初回の変換は完了している地点からスタート) */
    u8_adc_ch = ADCON0;
    u8_adc_ch = u8_adc_ch & (u8)0x7C;       /* ビットマスク:0b0111-1100 */
    u8_adc_ch = u8_adc_ch >> 2U;            /* CHSのデータを2bit下位シフトして数値として使えるようにする */

    /* AD変換結果の取得 */
    u16_adc_result = (u16)ADRESH;
    u16_adc_result = u16_adc_result << 8U;              /* 上位データを8ビット上へ移動 ※データはLSB詰めで取得している */
    u16_adc_result += (u16)ADRESL;                           /* 下位データはそのまま加算する */

    u16_main_s_adc_result[ u8_adc_ch ] = u16_adc_result;    /* AD変換結果取得 */
    u8_main_s_adc_req_status[ u8_adc_ch ] = CLEAR;          /* 変換要求クリア */

    /* AD変換未完了CHの検索 */
    for( u8_loopcnt = (u8)0; u8_loopcnt < ADC_CH_NUM; u8_loopcnt++ )
    { /* AD変換要求が連続して発生 = 渋滞している場合への対応 */
        if( u8_main_s_adc_req_status[ u8_loopcnt ] == SET )
        { /* AD変換要求があるチャネルを発見 */
            break;
        }
    }

    /* 次のAD変換実行 */
    if( u8_loopcnt < ADC_CH_NUM )
    { /* 少なくとも1chの未完了-変換要求が残っている。 */
        func_main_s_adc_start( u8_loopcnt );
    }
}

値の取得では、ADCON0のCHSビットを確認し、今変換完了したのが何番のチャネルなのかをチェックし、配列u16_main_s_adc_result[]に保存してます。

この割り込み処理内で、再度割り込み要求が残っているかチェックし、残っていた場合は再度AD変換開始処理を呼び出します。

以降、メインループで設定した割り込み要求が消えるまで、割り込み発生→次の要求チェック&変換開始・・・

を繰り返し、最終的にすべてのチャネルのAD変換を完了させます。

最後に重要なところを説明します。

今回、AD変換の処理内で、チャネルを切り替えてからwhile分で20回ほどループを回して待機しています。

        /* AD変換の対象チャネル切り替え後はアクイジョン・タイムを設ける */
        /* 短すぎるとサンプルホールド用コンデンサの充電が足りず、AD変換結果が期待値より下がるので注意 */
        u8_adc_wait = ADC_ACQUISION_TIME;
        while( u8_adc_wait > (u8)0 )
        {
            u8_adc_wait--;
        }

直前の処理でADCON0を操作して変換対象のチャネルを切り替えているのですが、その直後にADCON0のGOビットに1をセットしてAD変換開始してしまった場合、十分なサンプル・ホールド時間を得られず、AD変換結果が変な値になってしまいます。(期待値より下がる。)

AD変換の仕組みですが、PIC16F1827の場合は、サンプル・ホールド方式で、ポートに加えられた電圧をマイコン内部のコンデンサ充電に使い、その充電電圧を読む、といった方式で実施しています。

このため、サンプル・ホールドで充電する時間が短すぎる(チャネル切り替え後、AD変換開始するまでの間が短すぎる)と、十分に充電できず、ポートの電圧より幾分か低いAD変換結果を示すようになってしまいます。

今回は特に厳密な計算はせず、値が適切な値になればOK・・・ぐらいの間隔でデバッガでチェックして進めましたが、処理時間を気にする場合はデータシートの計算式に倣って調整するのもいいかも。

温度によって電解コンデンサの特性が変わると、適切な待ち時間も変わってきてしまうため、処理時間に余裕があるのであれば長いほうが無難かな~と思います。

まとめ

今回はPICのAD変換のサンプルプログラムを作成してみました。

ADC Acquision timeのところが一番罠っぽいかな~と思います。
(ソフトは動くのに値が一向に一致しないため。)

それでは、また。

関連記事
【PICマイコン】PICマイコンの 各種機能の使い方を紹介する

PICマイコン勉強中。 ぼちぼち更新していく予定。 PICマイコンの使い方をまとめてみる 割とネットにサンプル集がない & 自分で新規処理作ろうとしたときに設計背景思い出したくなることが多々あ ...

続きを見る

-実験・調査
-, ,