こんにちは、ENGかぴです。
PIC16F1827の出力とアナログ入力を使用するとにおいセンサーであるTGS8100(フィガロ技研)から電圧値を取得することができます。電圧値からセンサ抵抗に換算し除菌シートのエタノールを検出して動作確認を行いました。
においセンサモジュールキットAE-TGS8100(秋月電子)を使用しています。MPLAB X IDEのプラグインであるMCCでAD変換とTMR0割り込みを実装します。PIC16F1827で動作確認したことについてリンクをまとめています。
PICマイコン(PIC16F1827)で実現できる機能と解説リンクまとめ
AE-TGS8100を使ってArduino UNOでも動作確認を行っています。
Arduino UNOをPIC16F1827に置き換えて動作確認を行っています。MCCのClassicはサポート対象外になりました。新規プロジェクトでMCCを起動するとMelodyが標準で動作するようになりました。Melodyを使ったMCCの設定と動作確認も兼ねてまとめています。
以下ではPIC16F1827をPICとします。
においセンサーの情報を取得する

TGS8100はフィガロ技研のにおいセンサーです。酸化物半導体式の検知の原理によって空気の汚れ(水素やアルコール)を検知します。
ヒータ電圧と回路電圧の2つの印加電圧が必要です。ヒータ電圧のDC1.8Vが印加されると検出対象のガスを検出するのに最適な動作温度に感ガス素子が加熱されます。
回路電圧は、センサと直列に接続される負荷抵抗(RL)の電圧(Vout)を測定するために印加されます。負荷抵抗は対象ガスの検知濃度域における分解能が最適になるように抵抗値(10kΩ以上)を選定します。AE-TGS8100モジュールは10kΩが実装されています。
回路電圧はPICからパルス電圧で印加します。パルスの印加時間は2msでありパルスの印加間隔は1sec以上必要です。1秒周期のパルスに対してデューティー比が2:1000になるようなパルスを生成する必要があります。
AE-TGS8100はヒータ電圧と回路電圧用が実装されているため便利です。(以下ではセンサー単品の説明はTGS8100とし、AE-TGS8100を指す場合はセンサモジュールとします。)
PR:わからないを放置せず、あなたにあったスキルを身に着けるコツを教える テックジム 「書けるが先で、理解が後」を体験しよう!
動作検証(エージング)
TGS8100は初めて使用する場合や長く通電していなかった場合、周囲の空気の状況に関わらず出力電圧が低下します。数分間電源を入れたままにして周囲の空気環境に対してセンサの出力電圧を安定させる(エージング)必要があります。エージングの条件や方法は下記記事と同様とします。
エージングの結果、センサ抵抗31.5kΩを基準の雰囲気として採用します。
出力電圧を抵抗値に換算する

センサ抵抗は製品情報に換算式の記載があります。この式は分圧の法則からVout(VRL)の式を作りRsの式にすると得られるものです。
VRLはPICのアナログ入力で取得した電圧値、Vcは回路電圧のDC3.0V、RLは10kΩです。取得したアナログ値を電圧に換算すると10ビットのAD変換なので
$$V_{RL} = \frac{A1}{1024}×5V$$
になります。A1はデジット値、DC5VはPICの電源電圧です。
for( i=0; i<AI_MAX; i++ )
{ //出力電圧の合計
ave += sensor.buf[i];
}
sensor.ana = ave / AI_MAX; //平均値
sensor.sen_f = 6144.0 / sensor.ana - 10;
1秒毎にアナログ値の取得を行いますが、センサ抵抗への換算はアナログ値を移動平均した値を使用して行います。例のように10回分のアナログ値を合計し、10で割って平均値を算出します。7行目の換算式でセンサ抵抗に換算します。
PR:(即戦力のスキルを身に着ける:DMM WEBCAMP 学習コース(はじめてのプログラミングコース))
ガス特性から検出抵抗を決定する

ガス特性によると清浄大気中(基準の雰囲気)でのセンサ抵抗値に対するガス中のセンサ抵抗値の比によって検出するガス濃度が決まります。
除菌シート(エタノール)を10ppm程度検出したと判断する場合のセンサ抵抗の閾値を求めます。ガス特性から10ppmとなる比を確認すると約0.3になります。また100ppsの場合は約0.08になります。
基準の雰囲気でのセンサ抵抗値を31.5kΩにしているので比が0.3になるセンサ抵抗値は9.45kΩになりますが、誤差等を考慮して10kΩを検出の閾値とします。0.08の場合は2.52kΩですが3kΩを検出の閾値とします。
#define SEN_JUDGE (10.0)
#define SEN_JUDGE2 (3.0)
if( sensor.sen_f < SEN_JUDGE){
IO_RB6_SetHigh();
if( sensor.sen_f < SEN_JUDGE2 ){
IO_RB4_SetHigh();
}
else{
IO_RB4_SetLow();
}
}
else
{
IO_RB6_SetLow();
}
センサ抵抗値が検出の閾値の10.0kΩを下回るとRB6をHIGHを出力してLED2を点灯させます。さらにガス濃度が上がり3.0kΩを下回るとRB4をHIGHにしてLED1を点灯します。
MCCで各種機能を実装する
PIC16F1827の各種設定はMCCを使っています。MCCの使い方については下記記事にまとめています。
MPLAB Code Configurator(MCC)の追加と使い方
今回はAD変換で使用するADC、タイマ管理とパルス波形の生成に使用するTMR0の設定を行います。
System Moduleの設定
最初に共通事項であるSystem Moduleの設定を行います。MPLAB X IDEを起動しMCCのアイコンをクリックしてMCCを有効にします。

デフォルトの設定からクロックと周波数を変更します。内部クロックをシステムクロックとして使用するためSystem Clock SelectからINTOSCを選択します。
システムクロックの周波数をInternal Osillator Frequency Selectから16MHz_HFを選択します。システムクロックはパルス出力の分解能にも影響しますが分解能を細かくするため変更しています。他にも設定項目はありますがクロックの設定のみとしています。
ADCを設定する
Device ResourcesのDriversからADCを選択して追加します。

Hardware SettingsのResult Alignmentをright(右揃え)にします。右揃えにするとAD変換値がLSBからセットされます。
Positive Referenceは外部の電源を使用する場合はexternalを選択しますが、今回は電源電圧にするためVDDを選択し、Negative ReferenceにVSSを選択しています。
Positive Input Channelは使用するアナログチャンネルを選択します。PinGrid Viewから使用するANxを選択しても設定できます。
ADC Clockはシステムクロックに対する分周比を選択します。システムクロックに対して分周比が低いとAD変換の精度に影響が出ることがあるため適切な分周比になるようにFOSC/16を選択しています。
今回は割り込みを使用しませんが、AD変換が変換完了したタイミングで割り込みを発生させる場合はInterrupt SettingsのADI Interrupt EnableのスライドスイッチをONして有効にします。
AD変換が完了したタイミングでデータ取得することでADCのサンプリングタイミングがずれにくくなります。サンプリングタイミングを気にしない場合はメイン処理でAD変換が完了したこと判断しても問題ありません。
PR:RUNTEQ(ランテック )- マイベスト3年連続1位を獲得した実績を持つWebエンジニア養成プログラミングスクール
TMR0を設定する
Device ResourcesのDriversからTMR0を選択して追加します。

Hardware SettingsのClockSourceは内部のシステムクロックを使用するためFOSC/4を選択します。選択するとClock Frequency欄にTMR0のカウント周期が表示されます。
Prescaler AssignmentはTMR0はClock Sourceに対する分周を行う場合はassignedを選択します。分周しない場合はnot assignedを選択します。
Clock Prescallerで分周比を選択すると分周比によってRequested Periodの範囲が更新されるため範囲以内の値を指定します。1ms毎に割り込みを発生させて出力パルスを生成するため1msを指定しています。
Interrupt SettingsのOverflow Interrupt EnableのスライドスイッチをONして割り込みを有効にします。Callback Function Rateで指定した回数に達するとコールバック関数に遷移します。1を指定すると1ms毎にコールバック関数に遷移します。
void TimerCnt(void); //タイマー0割り込みのコールバック関数
//コールバック関数の使用例
TMR0_OverflowCallbackRegister(TimerCnt); //コールバック関数を指定
上記例ではTimerCnt()が1ms毎にコールされます。
Pinの設定

Pin Grid Viewの設定を行います。ADCを追加するとPin Grid ViewのModuleにADCが追加されます。AN0をアナログ入力に設定しますが、残りのピンはGPIOのDOとして使用するためoutputを選択します。
MCCが生成した関数の使用例
ADC_SelectChannel(channel_AN0); //AN0を測定
ADC_StartConversion(); //変換開始
if( ADC_IsConversionDone() )
{ //AD変換が完了するとTrueになる
sensor.buf[sensor.wp] = ADC_GetConversionResult(); //変換結果を変数に格納
}
IO_RB0_Toggle(); //RB0の出力を反転(トグル出力)
AD変換は登録したチャンネル番号に対して処理されるためAD変換開始の前に対象のチャンネル番号を指定する必要があります。例ではAN0をAD変換する例を示しています。チャンネル番号はadc.h内にadc_channel_tの型としてenumで登録されています。
ADC_StartConversion()を発行するとAD変換を開始します。AD変換が完了すると割り込みを有効にしている場合は割り込みフラグがセットされます。割り込みを使用しない場合はAD変換が完了しているかを確認して変換値を取得します。
ADC_IsConversionDone() 関数でADCON0レジスタのGO_nDONEビットが0になっていることを確認してAD変換値を取得します。AD変換開始から十分に時間が経過していると判断できる場合は確認せずに読み込んでも問題ありません。
PR:スキマ時間で自己啓発!スマホで学べる人気のオンライン資格講座【スタディング】まずは気になる講座を無料で体験しよう!
センサー情報取得の例
void TimerCnt(void){
if( sencnt == 0)
{
IO_RA1_SetHigh();
}
else if( sencnt == 1 )
{
ADC_SelectChannel(channel_AN0);
ADC_StartConversion();
}
else if( sencnt == 2 )
{
IO_RA1_SetLow();
}
else if( sencnt == 10 )
{
sensor.buf[sensor.wp] = ADC_GetConversionResult();
if( ++sensor.wp >= AI_MAX )
{
sensor.wp = 0;
sensor.flg = true;
}
}
if( ++sencnt >= TIM_SEN_FREQ )
{
sencnt = 0;
}
}
TimerCnt()関数はTMR0の割り込みで遷移する自作関数です。1ms毎に割り込みに遷移するようにします。パルス生成用の変数としてsencntを準備します。sencnt は割り込み毎にカウントを更新し処理を分岐させるのに使用します。
sencntが0の時はパルスを出力するためIO_RA1_SetHigh()関数でパルス出力ピンをHIGHにします。次の割り込み発生時はsencntが1に更新されているので9~10行目の処理を行います。
ADC_SelectChannel()関数の引数にAD変換するチャンネルを指定します。AN0を変換するのでchannel_AN0を指定しています。ADC_StartConversion()関数を発行するとAD変換を開始します。
AD変換を開始した後の割り込みではsencntが2に更新されているのでIO_RA1_SetLow()関数でパルス出力をLOWにします。
AD変換を開始して約11μsで完了しますが、パルス出力をLOWにしている区間が長いのでsencntが10(10ms)のときにAD変換値をADC_GetConversionResult()関数で取得します。
1秒毎に測定値が取得していますが、アナログ入力がノイズなどの外部要因による影響を受けて振れることがあるため移動平均を取っています。例ではAI_MAX 回(10回 20~24行目)データを取得してからセンサ抵抗値に換算する処理に移るようにしています。
27~30行目は割り込みに遷移した回数の更新を行っています。sencntが1000以上になった時1秒経過したことになるのでカウントをクリアします。
この処理を繰り返すことで1秒周期で2ms間HIGHとなるパルスを生成することができます。
PR:
わからないを放置せず、あなたにあったスキルを身に着けるコツを教える テックジムPython入門講座の申込
動作確認

PA1からセンサモジュールにパルス出力するとAN0にセンサモジュールの電圧が取得できます。AD変換の結果からセンサ抵抗値に換算してガス濃度の判定に使用します。
電源を入れるとPB0を500ms毎にトグル出力(出力を反転しながら動作)してLED3(緑)を点灯/消灯して動作中であることを通知します。電源を入れて間もなくはセンサ抵抗が低くなるため5分間はガスの検出の判定をしないようにしています。
エタノールの濃度を10ppm以上になるとLED2(黄)を点灯して通知します。更に濃度が高くなり100ppm以上になるとLED1(赤)を点灯して通知します。
エタノールを配合している除菌シートを近づけて動作確認を行いました。

除菌シートをセンサモジュールに近づけるとセンサ抵抗が低くなり10kΩを下回るとLED2が点灯してエタノールに反応したことが分かりました。PICkit4のシミュレーションでセンサ抵抗を確認すると約8.9kΩになっています。
除菌シートをセンサモジュールに近づけたままにするとさらにセンサ抵抗が低くなり1kΩを下回るとLED1が点灯して100ppm相当のエタノールを検出したことが分かりました。

PICkit4のシミュレーションでセンサ抵抗を確認すると約0.3kΩになっています。除菌シートを徐々に離していくとセンサ抵抗が上昇していきLED1が消灯し、除菌シートを基準の雰囲気に影響しないようにするとLED2が消灯して基準の雰囲気に戻りました。
ソースコード全体
ソースコードは記事作成時点において動作確認できていますが、使用しているライブラリの更新により動作が保証できなくなる可能性があります。また、ソースコードを使用したことによって生じた不利益などの一切の責任を負いかねます。参考資料としてお使いください。
リンクからZIPファイル形式のファイルをダウンロードし、任意の場所に展開していただくとテキストファイルが生成されます。
関連リンク
Arduinoのライブラリを使って動作確認を行ったことを下記リンクにまとめています。
Arduinoで学べるマイコンのソフト開発と標準ライブラリの使い方
Seeeduino XIAOで学べるソフト開発と標準ライブラリの使い方
ESP32-WROOM-32Eで学べるソフト開発と標準ライブラリの使い方
最後まで、読んでいただきありがとうございました。