PICマイコン(PIC16F1827)でCO2濃度を判定する

組み込みエンジニア
本記事はプロモーションが含まれています。

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

PIC16F1827はADC機能を持っておりセンサーの出力電圧をAD変換して取り込むことができます。CO2ガスセンサーであるMG812の出力電圧をAD変換してCO2濃度の状態変化を検出して動作確認を行いました。

CO2ガスセンサアンプキットAE-MG-812(秋月電子)を使用しています。PIC16F1827で動作確認したことについてリンクをまとめています。

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

ADCを実装する

PIC16F1827の設定は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_HF選択しています。他にも設定項目はありますがクロックの設定のみとしています。

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が生成した関数の使用例

void AdcItr(void);
//割り込み関数の指定例
ADC_SetInterruptHandler(AdcItr); //AD変換完了でコールする関数
//AD変換使用例
ADC_SelectChannel(channel_AN0); //AN0を測定
ADC_StartConversion(); //変換開始

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

ADC_SetInterruptHandler()関数はAD変換が完了したときにコールする関数を指定する場合に使用します。例では自作の関数のAdcIrt()を指定しています。

AD変換は登録したチャンネル番号に対して処理されるためAD変換開始の前に対象のチャンネル番号を指定する必要があります。例ではAN0を選択してAD変換を開始しています。チャンネル番号はadc.h内にadc_channel_tの型としてenumで登録されています。

ADC_StartConversion()関数でAD変換が開始します。AD変換が完了するとAD変換完了割り込みが発生しAdcItr()関数がコールされます。AdcItr()関数ではADC_GetConversionResult()関数でAD変換の結果を格納しています。

スポンサーリンク

CO2ガスセンサーの情報を取得する

MG812はWinsen社のCO2センサーです。温度や湿度の影響が受けにくい化学式のガスセンサーであり固定電解質セルの原理によって二酸化炭素の検出ができます。

センサー内蔵のヒータに電圧を印加している状態でセンサーがCO2にさらされると正と負の電力により起電力が発生するため測定雰囲気との起電力の差によってCO2濃度が検出できます。

MG812は内部抵抗が大きいため出力電流が微小となります。そのためMG812の出力をマイコンのAD変換に直接接続することができません。

AE-MG-812はMG812の出力電圧をオペアンプで増幅しているためPICマイコンに直接接続することができるようになっています。以下ではセンサー単品の説明はMG812とし、AE-MG-812を指す場合はMG812モジュールとします。

広告

動作検証(エージング)

MG812のエージングの様子

MG812モジュールは初めて使用する場合や長く通電していなかった場合CO2濃度に関わらず出力電圧が低下します。そのため数時間から数日程度電源を入れた状態にしてセンサーを安定させる(エージング)必要があります。

MG812のデータシートによると以下の条件でエージングを行う必要があります。

引用:MG812のデータシート(エージング時間)
引用:MG812のデータシート(エージング時間の目安)

購入して電源を一度も入れていない場合はMore than six monthsの条件に当てはまるため少なくても168時間(1週間)はエージングが必要です。MG812のエージングの結果は下記記事を参考にしてください。

ArduinoライブラリでCO2センサー電圧を取得する

エージング後でも数日電源を入れていない場合はエージング終了時の電圧にすぐに戻らないことがあるので初期化時の出力電圧の判定に注意が必要です。

センサー情報取得の例

uint8_t i;
uint16_t sum;
//タイマで取得タイミングを管理
if( timAdc == TIME_UP){
  timAdc = TIME_ADC_WAIT;

  switch(adcmode){
    case AD_AN0:
      ADC_SelectChannel(channel_AN0);
      ADC_StartConversion();
      adcmode = AD_AN0_WAIT;  
      break;
    case AD_AN0_WAIT:
      //ADC変換待ち
      break;
    case AD_AN0_CHK:    
      adcmode = AD_AN0;
      sum = 0;
      for(i = 0;i < CHK_CNT; i++){
        sum += mg812.data[i];
      }
      mg812.avr = sum >> 3;
      break;
  }
}
/* AD変換割り込み処理 */
void AdcItr(void){

  mg812.data[mg812.wp] = ADC_GetConversionResult(); //AD変換値を取得
  adcmode = AD_AN0_CHK; //モードを進める

  if( ++mg812.wp >= CHK_CNT ){
    mg812.wp = 0;
  }
}

AD変換は以下のモードに従って処理を分岐します。

  1. AD_AN0
  2. AD_AN0_WAIT
  3. AD_AN0_CHK

1. AD_AN0モードはAD変換するチャンネルの設定とAD変換の開始を行います。AD変換を開始して次のモードに遷移します。

2. AD_AN0_WAITはAD変換が完了するまで待機します。AD変換が完了するとAD変換完了の割り込み内(AdcItr()関数)で次のモードに遷移させます。

3. AD_AN0_CHKは複数回取得したAD変換の結果の平均値をとってMG812の出力電圧として採用しています。平均値の計算は除算の効率をあげるためシフト演算を使用しています。

シフト演算を使用するため平均化するデータの個数は2の乗数になるように指定する必要があります。

平均値の算出して出力電圧値を更新したら1. AD_AN0にモードを戻して次のAD変換をスタートします。

例ではAD変換のタイミングをタイマで管理しています。CO2濃度の状態変化のみを把握目的ですがアナログ入力であることを考慮して複数回取得したデータを平均化してノイズなどの外部要因による影響を減らしています。

PR:スキマ時間を有効に!スマホで学べる人気のオンライン資格講座【スタディング】まずは気になる講座を無料で体験しよう!

出力電圧からをCO2濃度を判定する

引用:MG812のデータシート(Picture 3 sensitivity)
引用:MG812のデータシート(Picture 3 sensitivity)

MG812の出力電圧はCO2濃度が高くなるほど小さくなります。データシートを確認すると出力電圧は400ppmを超えたあたりからCO2濃度の対数に比例しています。外気の雰囲気と同等の環境で放置した場合の出力電圧を初期値(グラフの平坦な部分)とします。

初期のCO2濃度に対する出力電圧と検出するCO2濃度時の出力電圧の差分に相当するAD変換値を求めます。10ビットのAD変換値から電圧値を求める方法は以下の式に従います。$$電圧換算値 = \frac{AD変換値}{1024}×V_{DD}・・(1)$$

(1)の式においてVDDは5Vとし、CO2濃度の判定の目安とするP0の1000ppmとP1の2000ppmの2通りの目安についてAD変換値を算出します。

データシートでは初期時の出力電圧は約325mVになっており1000ppm相当の出力電圧を確認すると約305mVになっています。この差分は約20mVとなりますが、MG812モジュールはゲインを10にしていることから200mVになります。(1)の式からAD変換値を逆算すると40.96になるので41を初期値からの差分のAD変換値とします。

同様にして2000ppm相当の出力電圧を確認すると約295mVになっています。初期時の出力電圧との差分は30mVになりますがMG812モジュールでは300mVになります。(1)の式からAD変換値を逆算すると61.44になるので61を初期値からの差分のAD変換値とします。

初期時の出力電圧と現状の出力電圧の差分と上記で算出した基準の差分を比較してCO2濃度の状態変化を検出します。本記事では1000ppm相当のCO2濃度の差分を判定値として使用しています。

例)初期時の出力電圧を2.2Vで現状の出力電圧が1.9Vの時
出力電圧が2.2V時のAD変換値は(1)から450になり、出力電圧が1.9V時のAD変換値は389になります。差分は450-389=61になるので1000ppmの検出の目安となる41以上になるのでCO2濃度が1000ppm以上になったと判断します。

厚生労働省推奨基準によると基準ガスが400ppmがしっかりと換気ができている状態、1000ppmを超えると換気の目安となっています。学校環境衛生基準では1500ppmが換気の目安になっています。2500~5000ppmになると長期間さらされると健康被害が出るCO2濃度となります。

初期値の更新

起電力が上昇したときの初期値の更新
起電力が上昇したときの初期値の更新

MG812の出力電圧はCO2濃度の高くなるほど低くなるため基本的に設置環境が安定していると外気の雰囲気で調整した初期値の出力電圧以上になる可能性は低くなります。

エージング時間の不足や外部要因によって出力電圧が上昇することがあります。初期時の出力電圧よりも出力電圧が高くなることがあるため初期値を更新する再調整を行います。

#define EMF_CHK_RANGE 4
update_chk = initvcc + EMF_CHK_RANGE;
                            
if( mg812.avr >= update_chk ){
  initvcc = mg812.avr; //初期値の更新
}

MG812モジュールの出力電圧はノイズなどの外部要因によって一時的に上昇しないように複数回取得したデータの平均値を使用しています。平均化に加えて初期値の更新を頻繁に行わないようにするため初期値よりも0.02V(AD変換値は4)以上になっていることを確認して初期値の更新を行います。

スポンサーリンク

動作確認

PIC16F1827のADCとMG812モジュールの動作確認回路
PIC16F1827とMG812モジュールの動作確認回路

PIC16F1827のAN0とMG812モジュールを接続します。LED1は初期化時とMG812モジュールの出力電圧が2.0V未満の時に点灯するようにしています。出力電圧が2.0V以上になるとLED1を消灯し通常の動作に遷移します。

通常の動作ではLED2(緑)を点灯/消灯させます。CO2濃度が1000ppm以上になるとLED1(赤)を点灯して換気を促す通知を行います。

通常の動作時にMG812モジュールに息を数秒間吹きかけるとLED1が点灯しました。しばらく放置するとMG812モジュール周りのCO2濃度が低下しLED1が消灯しました。

ピン設定は以下の通りです。

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

エージングが長くなることや電源ON時に基準ガスの調整のため外気の雰囲気でスタートする必要があるため若干使いにくい印象がありました。数日のエージングから1,2日間電源を入れずに放置しただけでも初期時の出力電圧が低くなることも使いにくい要因だと感じました。

ソースコード全体

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

#include "mcc_generated_files/mcc.h"

#define TIME_OFF -1     //タイマーを使用しない場合
#define TIME_UP 0       //タイムアップ
#define TIME_ADC_WAIT 100
#define TIME_ADC_ERR 500
#define LED_FL 50
#define CHK_CNT 8
#define EMF_BASE 0x19A //2.0V相当
#define EMF_PPM1000 41 //初期値より低くなる基準1
#define EMF_PPM2000 61 //初期値より低くなる基準2
#define EMF_CHK_RANGE 4

typedef struct{
  uint8_t wp;
  uint16_t data[CHK_CNT];
  uint16_t avr;
}AI_FILT;

typedef enum{
  AD_AN0 = 0,
  AD_AN0_WAIT,
  AD_AN0_CHK,
}ADC_MODE_NO;

typedef enum{
  MD_IDLE = 0,
  MD_INIT,
  MD_NORMAL,        
}MODE_TYP;

int16_t timAdc;
int16_t timAdcErr;
int16_t timinitLed;

ADC_MODE_NO adcmode;
MODE_TYP mode;
bool initokflg;
uint16_t initvcc;
AI_FILT mg812;
int16_t emf_chk;
int16_t update_chk;

/* プロトタイプ宣言*/
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();
  IO_RB4_SetHigh();
    
  while (1)
  {
    mainApp();
  }
}
/* タイマ管理関数 */
void mainTimer(void){
    
  if( timAdc > TIME_UP ){
    --timAdc;
  }
  if( timAdcErr > TIME_UP ){
    --timAdcErr;
  }
  if( timinitLed > TIME_UP ){
    --timinitLed;
  }
}
/* メイン処理 */
void mainApp(void){
  uint8_t i;
  uint16_t sum;

  if( timAdc == TIME_UP){
    timAdc = TIME_ADC_WAIT;
        
    switch(adcmode){
      case AD_AN0:
        ADC_SelectChannel(channel_AN0);
        ADC_StartConversion();
        adcmode = AD_AN0_WAIT;  
        timAdcErr = TIME_ADC_ERR;
        break;
      case AD_AN0_WAIT:
        //ADC変換待ち
        break;
      case AD_AN0_CHK:    
        adcmode = AD_AN0;

        if( initokflg ){
          sum = 0;
          for(i = 0;i < CHK_CNT; i++){
            sum += mg812.data[i];
          }
          mg812.avr = sum >> 3;
          if( mode == MD_IDLE ){
            mode = MD_INIT;
          }
        }
        break;
    }

    switch(mode){
      case MD_IDLE:
        //処理待機
        break;
      case MD_INIT:  
        if(initokflg){
          if( mg812.avr > EMF_BASE ){
            initvcc = mg812.avr;
            IO_RB4_SetLow();
            IO_RB3_SetHigh();
            mode = MD_NORMAL;
          }
        }
        break;
      case MD_NORMAL:
        emf_chk = initvcc - mg812.avr; 
        if( emf_chk >= 0){ //CO2濃度上昇
          if(emf_chk >= EMF_PPM1000 ){    
            IO_RB4_SetHigh();
          }
          else{
            IO_RB4_SetLow();
          }
        }
        else{
          update_chk = initvcc + EMF_CHK_RANGE;
 
          if( mg812.avr >= update_chk ){
            initvcc = mg812.avr;
          }
        }
        break;
    }       
  }
    
  if(timinitLed == TIME_UP && mode == MD_NORMAL ){
    timinitLed = LED_FL;
    IO_RB3_Toggle(); 
  }    

}
/* AD変換割り込み処理 */
void AdcItr(void){
    
  mg812.data[mg812.wp] = ADC_GetConversionResult(); //AD変換値を取得
  adcmode = AD_AN0_CHK; //モードを進める
  timAdcErr = TIME_OFF;
  if( ++mg812.wp >= CHK_CNT ){
    mg812.wp = 0;
    initokflg = true; //初期データが貯まったかを判定
  }
}
/* End of File */

本ソースコードはMPLAB X IDEにMCCのプラグインをインストールしていることが前提となります。MCCをインストールする方法は下記記事を参考にしてください。

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

関連リンク

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

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

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

PR:技術系の通信教育講座ならJTEX

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

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