におい検知器
2008/05/28 Nishimura Hiromi
これは 2008/03/15 のお話
安いし面白そうだなと思い 購入しておいた「においセンサーTGS2450」。週末(2008/03/15頃のお話)に作ってみようかと。インターネットで検索すると「SVX日記 - オペアンプ実装」や「電子工作etc」が ヒット「SVX日記 - オペアンプ実装」は非常に独創的なページで面白く読める。「電子工作etc」 さんは回路図やタイミングチャート がありありがたいページ。でも何か違和感が。そもそも匂いって何? このTGS2450は匂いの元となる物質の濃度が 高くな ると抵抗が小さくなるとの事。特性グラフには清浄大気時の抵抗をRo、各種ガス濃度雰囲気中の抵抗値をRsとしたとき、その比のRs/Roで書かれてい る。 また使うにはヒーターで加熱しないといけないそうな。そのタイミングも250msec毎に130mA,8msecの加熱が必要らしい。検出も5msec以 内にする 必要があるみたい。まあタイミングはPICを使えば良いが。ヒーターの抵抗が10Ω位で130mA流すには電源を1.3V程度と中途 半端な値にしなけりゃならない。「電子工作etc」の ように抵抗を直列に接続し制御する方法もあるだろうが、何かすっきりしない。それにセンサーの出力をそのままA/D変換するというのも後がたいへん。でき ればデジタルボルトメータ(DVM)に出力できたほうが持ち運びも便利だ。
そんな訳で、結局は自分で 回路を考える事に。まず電源は5Vに決定。出力はPICのPWM機能を利用し結果をDVMで表示することに決定。006P型乾電池を三端子レギュレータを 利用し5Vに、センサーのヒーターの電流だが、これはオペアンプを利用し定電流回路で130mA流すことに。センサーの抵抗値を計るのではなくセンサーの 抵抗がオペアンプの帰還回路の一部にする。これだと後の計算が非常に楽になるはず。

上記回路はオペアンプを 使った普通の非反転増幅回路である。この回路のRo,Rsの部分をセンサーに置き換える。ここでRoは清浄空気時のセンサー抵抗、Rsは匂い物質雰囲気中 の センサー抵抗。また入力をVxに固定する。これは分圧抵抗で作る。
まずセンサーを清浄大気雰 囲気に置いたときのセンサーの抵抗をRoとし、そのときの出力をVoとする。同様に各種ガス濃度雰囲気中の抵抗値をRsとし、そのときの出力をVsとす る。ここでRs/Roは
Rs/Ro = (Vo-Vx)/(Vs-Vx)
となる。この式には帰還抵 抗Rの項が消えるので出力電圧を計測するだけでOK。このRが消えるというのは調整の項目が一つ消えるに等しい(感覚的にだが!)。この手のセンサーは比 例・反比例で何とかなるのでオペアンプを使った方が簡単。安易にログアンプとか考えると泥沼にはまり込んでしまいますよ。

これが最初に考えた匂い検 知器の回路図(珍しく少々複雑だが)。2SK30Aを使い計測時間だけオペアンプに接続。この部分はあっても無くても変化なしであった(TGS2450に は5msecだけ通電と 書いているので付けたまでの事、たぶん不要だろう?)。CPUにはPIC12F683を使用。オペアンプにはLMC662を使った(最近使う機会が多い。 レールtoレールって楽だね!)。配線図を下記に示 す。

抵抗やコンデンサーには値 が書かれていない。また余計な部品も付いている。回路図と見比べれば判るはず。水色は錫メッキ線で、それ以外はホルマル線で配線すると簡単ですよ。

ケースに入れたところ。 配線図そのままであるところが芸が無い。


まだ出力電圧を表示してい るだけ。Rs/Roを如何にして変換し表示するかこれから考える予定(たぶんこのままである可能性が高い)
電源を入れてから計測値が 安定するまで4〜5分かかる。ただ応答は思った以上に早く瞬時に変化、1〜2秒で安定(マ イコン電子工作あれこれには1〜2分の遅れとあるが何かの間違いだろう)。感度も非常に高い。外気でゼロ点調整。室内でちょっとでも タバコの匂いがすると 出力電圧は0.2V程度まで上昇。タバコの煙があると1Vを軽く突破。匂い検知器というよりタバコの煙検知に使えそう。病院では消毒用のアルコールを頻繁 に利 用。そのため病棟ではアルコールの匂いがしなくても1.5Vまで上昇。病理研究部や検査科では1.7〜1.9V。実際の濃度は不明だが明らかにアルコール 濃度が高いようだ。これなら換気扇を回すタイミングを決めるセンサーに使えそう。
いつも思うのだが、セン サーを購入し試験回路を作り使ってみて「あれに使える、これに使える」までは行くのだが、実際に完成までたどり着く事は殆ど無い。結局は......まあ 楽しければ..........
● [2008/04/06] 新しい匂いセンサー
匂いセンサーの出力をデジ タルボルトメータ(DVM)に表示させるのは悪くは無い。でも数字だけで何を計測しているのか不明。そこで桁数の多い液晶ディスプレイに変更してみた。 PIC12F683だとIOピンの数が足りないのでPIC16F88に変更。PWMはデータロガー用出力として使う事に。新しい回路図は、
である。回路の変更点はセン サーアンプの帰還抵抗を1KΩから5.1KΩに変更しオペアンプの5番ピンにかかる電圧を約1Vの低い電圧でも 計測可能にした。回路図に大きな変更は無し。
電源を006P型乾電池と ACアダプタ併用とした。ダイオードでカップリングしているためACアダプタの電圧が006P型乾電池より低くなると乾電池の方が消費される。よってAC アダプタの電源は12V以上が適当。
対応するガス毎に濃度 (ppm)を表示するようにしているが簡易近似である。Rs/Roの逆数から1を引いた値にガス毎に係数を乗算している。係数はセンサーマニュアルのガス 感度特性グラフのガス最大濃度になるよう決定した。例えばエタノールではRs/Roが0.008のとき約300ppmになるので 300*0.008=2.4 とした。
5.1KΩ の帰還抵抗を大きくすると低濃度での感度は上昇するがダイナミックレンジが狭くなる。
計測値の安定度は期待以上 である。またセンサーの反応も瞬時、安定も数秒、結構速いと思う。問題は匂いという抽象的なものを対象にしているだけにタバコの煙でもアルコールでも結構 何にでも反応し てしまう事か。
下記に配線図を示す。

右側のLCD周りが少々汚い が基板 の上にLCDをのせるので我慢。

ケースに入れた状態。上に見 えるのが匂いセンサー

ケースの中の基板。ほとんど がLCDで隠れている(映す意味が無い)
● 感想
正直、これができた時は匂 いセンサーって面白いもんだな〜と感動した。でも2〜3時間遊んだら興味が薄れてしまう。最近、硫化水素を使った自殺が流行っているので常時ロギングし てい れば近所で何かあったら直ぐに判るな〜と思っている(嫌な性格だな〜)。
このセンサーを使ってみて 感じた印象。それは何を測っているのか判らないいかげんなセンサーだという印象。タバコを吸っても反応するし、酒を飲んでも反応(匂いとはいったい 何?)。少なくてもセンサー一つでは何 を計測しているのかが不明。もう一つ別の、例えばアルコールセンサーを併用すると引き算で検知しているのがアルコールなのかそれ以外の物質なの かが判る。要は人間のように複数のセンサーが無ければ匂いセンサーとして意味が無いと思う。
病棟で使っている消毒用の アルコール。病棟の前を歩くと匂いがしないのに何故かセンサーが強く反応(変な機械を持って歩く姿は不気味かも!)。病理や検査科に行っても強く反応。病 院って思った以上に消毒用のアルコー ル濃度が高い(100〜800ppm:たぶん)。そこで職場環境におけるアルコールの基準濃度というか規制があるのか調べてみる。何故か日本ではアルコー ルの環境基準 が 無い。でも米国では1000ppm という基準があるらしい。日本で基準を作ったら飲んべえばかりで大変だろうな〜(ん、これは秋田限定か!) でも勤務中にエチルアルコールの雰囲気に いると通 勤時に飲酒運転で捕まってしまうのでは。計算では呼気中のアルコール 濃度が0.15mg/ℓだと70〜80ppmになるのでは(計算違いかな?)。これは結構大きな問題。なに せ秋田県では県職員が飲酒運転で捕まると一発で懲戒免職。大変だぞ〜 ..... と考えてみたら私はバス通勤、まあどうでも良いか!
インターネットでの検索 で、あまり「においセンサーTGS2450」がヒットしないのでセンサー周りの回路の参考になればと思っている。本回路はセンサーを帰還回路の一部として 使っているので簡単な計算でRs/Roが得られる点が強み。そのため調整が殆ど不要な点が使えるのではと思う。
● PIC プログラム
これも参考程度に見て下さ い。
//--------------------------------------------------------------
// においセンサーTGS2450用
// 本プログラムは2008/03/23の回路図用のもので ある
// 2008/04/03 by Nishimura Hiromi
//
// 2008/04/03 Ver 1.0.0 プログラミング開始
// 2008/04/05 Ver 1.0.1 LCD動作確認
// 2008/04/06 Ver 1.0.2
//
//--------------------------------------------------------------
#include <16F88.h>
#device adc=10
#FUSES NOWDT //No Watch Dog Timer
#FUSES INTRC_IO //Internal RC Osc, no CLKOUT
#FUSES NOPUT //No Power Up Timer
#FUSES NOMCLR //Master Clear pin used for I/O
#FUSES BROWNOUT //Reset when brownout detected
#FUSES NOLVP //No low voltage prgming
#FUSES NOCPD //No EE protection
#FUSES NOWRT //Program memory not write protected
#FUSES NODEBUG //No Debug mode for ICD
#FUSES NOPROTECT //Code not protected from reading
#FUSES FCMEN //Fail-safe clock monitor enabled
#FUSES IESO //Internal External Switch Over mode enabled
#FUSES CCPB3
#use delay(clock=8000000)
#include <float.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//--------------------------------------------------------------
//
// #use fast_io(a)
// #use fast_io(b)
#define Amode 0xBB //port A initial mode
#define Bmode 0x00 //port B initial mode
#byte db
= 6
//port
B
//
// #define Co 0.0048828125
//--------------------------------------------------------------
// Port define and link LCD library
//
#define rs PIN_B2 //LCD chip select
#define rw PIN_B1 //LCD read/write
#define stb PIN_B0 //LCD strobe
//
#include "lcd_lib.c"
//
//--------------------------------------------------------------
#define dMode PIN_A3 // 表示モードボタン
#define zeroReset PIN_A4 // ゼロ点補正入力ボタ ン
#define maxReset PIN_A5 // 最大値をクリア入力 ボタン
#define heater PIN_A2 // ヒーター。highでOFF、lowでON
#define gate PIN_A7 // 計測用ゲート。highでON、lowでOFF
#define ledLamp PIN_A6 // 状態表示ランプ
//--------------------------------------------------------------
// VoはADチャンネル0
// VsはADチャンネル1
//
//--------------------------------------------------------------
//
float vRef; // 基準電圧
float Vo; // ゼロ点電圧
float Vs; // 計測時電圧
float maxVs; // 最大値
// float RsRo; // 計測値計算結果
int doneMS; // 計測結果あり
int dispMode; //
//--------------------------------------------------------------
// Timer1オーバーフローで計 測を行う
// 本来は250msec周期で実行すべきだ が262msecの近い周期の割り込 みがあったのでこれを利用
//
#int_TIMER1
void TIMER1_isr()
{
doneMS = 1;
}
//--------------------------------------------------------------
//
void init_disp()
{
lcd_init(); // initialize LCD
lcd_clear(); // clear display
lcd_cmd(0x0c); // no cursor
lcd_data("Nioi Ver 1.0.2"); // version
lcd_cmd(0xC0);
lcd_data(" 2008/04/05 NiS");
output_low(ledLamp);
delay_ms(500);
}
//
//--------------------------------------------------------------
//
void initPIC()
{
setup_adc_ports(sAN0|sAN1|VSS_VDD);
setup_adc(ADC_CLOCK_INTERNAL);
setup_spi(FALSE);
setup_timer_0(RTCC_INTERNAL|RTCC_DIV_256);
setup_timer_1(T1_INTERNAL|T1_DIV_BY_8);
setup_timer_2(T2_DIV_BY_4,66,1);
setup_ccp1(CCP_PWM);
set_pwm1_duty(512);
setup_comparator(NC_NC_NC_NC);
setup_vref(FALSE);
enable_interrupts(INT_TIMER1);
enable_interrupts(GLOBAL);
setup_oscillator(OSC_8MHZ|OSC_TIMER1);
//
// set_tris_a(Amode);
set_tris_b(Bmode);
}
//
//--------------------------------------------------------------
// 何故かlcd_data関数で文字列変数を 指定できない。そのための処理
//
void disp_string(char *str)
{
int i,n;
n = strlen(str);
for(i=0;i<n;i++) lcd_data(str[i]);
}
//
//--------------------------------------------------------------
// Calibration データをROMから読み込む
//
void read_Vo()
{
int i;
char *a;
a = &Vo;
for(i=0;i<4;i++) *(a++) = read_eeprom(i);
}
//
//--------------------------------------------------------------
// Calibration データをROMに書き込む
//
void write_Vo()
{
int i;
char *a;
a = &Vo;
for(i=0;i<4;i++) write_eeprom(i,*a++);
}
//
//--------------------------------------------------------------
//
void disp_org()
{
char str[20]; // LCD表示用バッファー
lcd_cmd(0x80);
sprintf(str,"Vr=%4.0f Vs=%4.0f",vRef,Vs);
disp_string(str);
lcd_cmd(0xC0);
sprintf(str,"Vo=%4.0f",Vo);
disp_string(str);
}
//
//--------------------------------------------------------------
// 計測値の計算
float calc_RsRo()
{
return (Vo-vRef)/(Vs-vRef);
}
//
float calc_MaxRsRo()
{
return (Vo-vRef)/(maxVs-vRef);
}
//
//--------------------------------------------------------------
//
void disp_Result()
{
char str[20]; // LCD表示用バッファー
if(Vs>maxVs) maxVs = Vs;
lcd_cmd(0x80);
sprintf(str,"Now Rs/Ro=%5.3f ",calc_RsRo());
disp_string(str);
lcd_cmd(0xC0);
sprintf(str,"Max Rs/Ro=%5.3f ",calc_MaxRsRo());
disp_string(str);
}
//
//--------------------------------------------------------------
//
void disp_Ethanol()
{
char str[20]; // LCD表示用バッファー
if(Vs>maxVs) maxVs = Vs;
lcd_cmd(0x80);
sprintf(str,"Ethano=%5.1fppm",2.4/calc_RsRo()-2.4);
disp_string(str);
lcd_cmd(0xC0);
sprintf(str,"MaxAlc=%5.1fppm",2.4/calc_MaxRsRo()-2.4);
disp_string(str);
}
//
//--------------------------------------------------------------
//
void disp_NH4()
{
char str[20]; // LCD表示用バッファー
if(Vs>maxVs) maxVs = Vs;
lcd_cmd(0x80);
sprintf(str,"NH4 =%5.2fppm",0.9/calc_RsRo()-0.9);
disp_string(str);
lcd_cmd(0xC0);
sprintf(str,"MaxNH4=%5.2fppm",0.9/calc_MaxRsRo()-0.9);
disp_string(str);
}
//
//--------------------------------------------------------------
//
void disp_H2S()
{
char str[20]; // LCD表示用バッファー
if(Vs>maxVs) maxVs = Vs;
lcd_cmd(0x80);
sprintf(str,"H2S =%5.3fppm",0.12/calc_RsRo()-0.12);
disp_string(str);
lcd_cmd(0xC0);
sprintf(str,"MaxH2S=%5.3fppm",0.12/calc_MaxRsRo()-0.12);
disp_string(str);
}
//
//--------------------------------------------------------------
//
//--------------------------------------------------------------
void main()
{
//
long i;
signed long pwm;
//
initPIC(); // PIC16F88 の初期設定
init_disp(); // 初期表示
// 変数の初期化
read_Vo();
if(Vo>500.0||Vo<100.0) Vo = 235.0;
Vs = vRef = maxVs = 0.0;
dispMode = doneMS = 0;
// 状態表示LEDを赤に
output_low(ledLamp);
// LCD画面クリア
lcd_clear(); lcd_cmd(0x0c);
while(true){
// 表示モード切替
if(input(dMode)==0){
delay_ms(10);
while(input(dMode)==0);
dispMode++;
if(dispMode>4) dispMode=0;
lcd_clear();
lcd_cmd(0x0c);
}
// 最大値リセットボタ ンが現在値を最大値に変更
if(input(maxReset)==0) maxVs = Vs;
// ゼロ点補正ボタンが1秒以上押されたら押 されたら現在値を基準に変更
if(input(zeroReset)==0){
output_low(ledLamp);
delay_ms(10);
for(i=0;i<990;i++){
delay_ms(1);
if(input(zeroReset)>0) break;
}
if(i>=990){
Vo = Vs;
write_Vo();
}
output_high(ledLamp);
while(input(zeroReset)==0);
}
//
// 割り込み計測が行わ れたら
if(doneMS==1){
//
// 計測ルーチン
//
output_high(gate); // 計測用ゲートを開く
delay_ms(5); //
// リファレンス電圧計 測
set_adc_channel(0);
delay_us(70);
vRef = (float)read_adc();
// センサー出力電圧計 測
set_adc_channel(1);
delay_us(70);
Vs = (float)read_adc();
output_low(gate); // 計測用ゲートを閉じ る
//
// ADCの結果が1000を超えたらオーバー フロー
if(Vs>1000) output_low(ledLamp);
else output_high(ledLamp);
//
// センサー加熱関連
output_low(heater); // 加熱開始
delay_ms(10);
output_high(heater); // 加熱終了
//
// 計測結果の表示
if(dispMode==0) disp_org();
else if(dispMode==1) disp_Result();
else if(dispMode==2) disp_Ethanol();
else if(dispMode==3) disp_NH4();
else if(dispMode==4) disp_H2S();
//
pwm = 10.24/calc_RsRo();
if(pwm<0) pwm = 0;
if(pwm>=1024) pwm = 1023;
set_pwm1_duty(pwm);
//
doneMS = 0;
}
}
}
lcd_lib.c
///////////////////////////////////////////////
// LCD control Library
// functions are below
// lcd_init()-------- initialize
// lcd_ready()------- busy check
// lcd_cmd(cmd)------ send command
// lcd_data(string)-- display string
// lcd_clear() ------ clear display
//////////////////////////////////////////////
/////////// lcd ready check function
int lcd_ready(){
int high,low;
set_tris_b(Bmode | 0xF0); //upper is input
output_low(rs);
output_high(rw); //read mode
output_high(stb);
high=db & 0xF0; //input upper
output_low(stb);
output_high(stb);
low=db & 0xF0; //input lower
output_low(stb);
set_tris_b(Bmode);
return(high | (low>>4)); //end check
}
////////// lcd display data function
void lcd_data(int asci){
int tp1,tp2;
tp1= asci & 0xF0;
tp2= db & 0x0F;
db = tp2 | tp1; //set upper data
output_low(rw); //set write
output_high(rs); //set rs high
output_high(stb); //strobe
output_low(stb);
asci=asci<<4;
tp1= asci & 0xF0;
tp2= db & 0x0F;
db = tp2 | tp1; //set lower data
output_high(stb); //strobe
output_low(stb);
while(bit_test(lcd_ready(),7));
}
////////// lcd command out function
void cmdout(int cmd){
int tp1,tp2;
tp1= cmd & 0xF0;
tp2= db & 0x0F;
db = tp2 | tp1; //set upper data
output_low(rw); //set write
output_low(rs); //set rs low
output_high(stb); //strobe
output_low(stb);
cmd=cmd<<4;
tp1= cmd & 0xF0;
tp2= db & 0x0F;
db = tp2 | tp1; //set lower data
output_high(stb); //strobe
output_low(stb);
}
void lcd_cmd(int cmd){
cmdout(cmd);
while(bit_test(lcd_ready(),7)); //end check
}
////////// lcd display clear function
void lcd_clear(){
lcd_cmd(1); //initialize command
}
///////// lcd initialize function
void lcd_incmd(int cmd){
int tp1,tp2;
tp1= cmd & 0xF0;
tp2= db & 0x0F;
db = tp2 | tp1; //mode command
output_low(rw); //set write
output_low(rs); //set rs low
output_high(stb); //strobe
output_low(stb);
delay_us(100);
}
void lcd_init(){
delay_ms(30);
lcd_incmd(0x30); //8bit mode set
lcd_incmd(0x30); //8bit mode set
lcd_incmd(0x30); //8bit mode set
lcd_incmd(0x20); //4bit mode set
lcd_cmd(0x2E); //DL=0 4bit mode
lcd_cmd(0x08); //disolay off C=D=B=0
lcd_cmd(0x0D); //display on C=D=1 B=0
lcd_cmd(0x06); //entry I/D=1 S=0
}