2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

アルコール検知機を作ってみた

Posted at

はじめのはじめ

この記事は趣味で工作を楽しんでいる人が、

  • 勉強のために自分のやったことをまとめる
  • やったことを共有することで、誰かの役に立といいな

という想いと目的で書いています。

目次

1. はじめに
2. 構成検討
3. 設計
4. 作製
5. おわりに

1. はじめに

概要

昨今運転中のアルコール規制は厳しくなっていますが、
そういえば呼気中のアルコール濃度を測定する方法が家に無いな~と思ったので、
とりあえず深く考えずに作ってみることにしました。
あとMOSFETでハイサイドスイッチも作ってみたかったのでそれも盛り込むことにしました。

コンセプト

  • なるべく安く作る
  • 1ボタンで電源ON/OFFと計測開始を兼ねる回路構成
  • 充電式の電池駆動
  • Fusion360で3D設計
  • PCB基板を作製してみる

2. 構成検討

アルコール検出センサー

呼気中アルコール検知の方法として、いくつか検出方式がありますが、
2023年現在、市販のセンサでは半導体式が圧倒的に安いので、半導体式を用いることにします。
原理は下記が詳しい。
赤外吸光式が精度、安定性ともに良さそうですね。
最近の市販品はこの方式なんですかね。

使ったセンサはMQ-3です。
SnO2の酸化/還元状態が周辺ガスより変化し、抵抗値が変わる現象を用いたセンサです。

データシートに記載ありますが、周辺温度により強く影響を受けるので、
暖気時間が24時間以上が推奨とあります。
どの程度で安定するのか、センサ単品でアナログ値を読み取って確認してみました。
image.png
記載の通り安定までの暖気は結構かかりますね。(横軸は稼働後の経過sec、縦軸はアナログ値 1024max)
アルコールウェットティッシュで検出できることも確認できました。
しらふの呼気でも多少増加する傾向ついでに確認。
検出後の安定には多少時間かかりますが、安定前に再度呼気をかけてもそれなりにピーク値は再現してます。
よって、安定時間はそこまでやらなくても、ピーク値検出はできると判断できそう。
今回はそこまでの精度を求めないので、使用毎に電源をON/OFFする仕様にします。
暖気時間は使いながら考えればヨシ。
ただ当然ですが精度を追った使用方法の際は、常時稼働状態にすべきですね。

校正をするため、データシートの校正曲線をフィッティングして関数化し、
検出値からガス濃度へ計算できるようにしました。
image.png

  • Air状態のRs/R0が60データシートより規定されている
  • Air状態の入力アナログ値を電圧に変換
  • ロード抵抗はデータシートより200kΩとあるので、ここからAir状態のRsを計算
  • Rs/R0=60からR0値を計算
  • 先のフィッティングで導出したRs/R0とガス濃度の関係式に代入してガス濃度を計算

という型で事前にR0計算をして、のちのコードに入れ込む形にしました。
温度や湿度依存があるので、温湿度センサとセットにして、
補正係数をかけるようにするとより精度があがりますが、今回はやらない。

電源回路

電源には充電式の角型9V電池を用います。
Micro USBで充電ができるタイプです。
image.png

Arduinoは内部レギュレータで9V入力のままでOKですが、
MQ-3はデータシートより5±0.1Vとあるので、別途レギュレータをかませることにします。

タクトスイッチをボタンにして、MOSFETで電源ON/OFFを制御しつつ、
そのボタンで測定開始のトリガとするようにします。
その部分だけ抜き出した回路図は下記です。
image.png
pMOSでハイサイドを止めに行く回路にしてます。
ArduinoのGND側をローサイドで止める方法でも構成できそうですが、
カソードコモンのLEDをGPIOに接続した際、OFF状態でもGPIOから電流が漏れてしまうケースがあったため、
ハイサイド側で止める構成が必要と考えました。

ハイサイドで止める回路構成は下記を参考にさせていただきました。

表示器

7segLEDで測定値の表示、3色LEDで測定値が規格通りかどうかの一目可視化を行います。
7segの表示には74HC545のシフトレジスタを使いました。
余っていたので。

全体構成

構成図と部品類はざっくりこんな感じです。
image.png
動作フローもざっくりこんな感じで動かしてみることにします。
あとは作りながら微修正していく作戦。
image.png

3. 設計

動作仕様

前述の通り、今回は1ボタンで電源のON/OFF制御と測定開始のトリガを兼ねさせる設計をします。

Arduino側は

  • ボタン押している間、電源供給でArduinoが起動
  • Arduino起動したら、GPIOで保持するnMOSをON
  • ボタン離しても、GPIOで電源ONを保持
  • ボタン長押しでGPIOで長押し検知、電源ONを保持するnMOSをOFFにして電源遮断
  • ボタン短押しの場合は測定開始
    というシーケンスArduinoで作ります。

回路図

いつも通りFusion360で回路~筐体設計まで一括でやりました。
回路図は下記です。
image.png

配線図

配線図は下記です。
後述しますが、手持ちの素子と同じCADモデルがなかったので、
適当なMOSFET,、レギュレータをライブラリからモデル選択して配置しましたが、
SDGのピン配置などが違ってて、一部で現物合わせが発生してしまいました。
3ピン以上はちゃんとピン配置を確認しないと駄目ですね。今後注意。
image.png

筐体&3D配置図

こんな感じで配置しました。
筐体は3Dプリンタで作りました。材質はPLAです。
image.png
フタも嵌合で作成しようと思いましたが、寸法や形状がうまくできなかったので
とりあえず全面むき出しで。

4. 作製

まずブレッドボードでの動作確認から。
配線ごちゃごちゃしますが、意図どおりの動作シていることを確認。

ついでに稼働中のセンサ部温度を確認してみました。
SnO2を活性にするため、抵抗加熱しているのでピンポイントで80℃くらいにはなってます。
指が入らないようにはなってますが、お子さんがいる場合はちょっと注意。
image.png

筐体作製して、各部品実装をしました。
image.png

一部素子にジャンプ線かましてますが、CADモデルと実物で異なるものを配置したため、
ピン配置を誤って基板を作っちゃいました。
そのために足元にジャンパ線をかましてごまかしてます。
MOSFETとか3端子レギュレータとか、意外とメーカーによってピン配置違うんですね。
image.png

最終的な動作確認です。
ティッシュを近づけて0.3mg/Lと、若干低めになっているように感じます。
もう少し校正をイジったほうがよいかもしれないけど、それはおいおい実施するとして、
とりあえず完成。
https://www.youtube.com/watch?v=ETKMnI8H-C4

ソースコードは下記です。

╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╮
╏    ソースコードを表示(折りたたみ)   ╏
╰╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╯
//Arduino IDEボードが「Arduino Pro or Pro mini」を選択
#include "Arduino.h"

//ピンアサイン
int bot = 2; //入力ボタンのピン
int pow_keep =3; //電源ONキープのピン
int dig1 = 4;       // DIG1割り当て
int dig2 = 5;       // DIG2割り当て
int dig3 = 6;       // DIG3割り当て
int dsPin = 7;     // (14) DS [SER] on 74HC595
int srclkPin = 8;   // (11)  SH_CP [SRCLK] on 74HC595
int rclkPin = 9;   // (12) ST_CP [RCLK] on 74HC595
int LED_Red=10; //赤LEDのピン
int LED_Green=11; //緑LEDのピン
int LED_Blue=12; //青LEDのピン
int MQ_analog= A0; //MQ-3のアナログ出力ピン

//変数定義
unsigned long pre_time = 0; //前回の時間
unsigned long curr_time = 0; //現在の時間
unsigned long off_time= 30000; //自動オフまでの時間
unsigned long warm_up_time= 10000; //校正時間
float raw_val = 0.0; //MQ-3のアナログ生値

//MQ用の値
float RL=200000;//センサの可変抵抗値
float R0=0.0;//air状態でのセンサ抵抗 起動時に決めるが、だいたい6.4kΩ
float V0=0.0;//air状態でのセンサ電圧 起動時に決めるが、だいたい1.7V
float v0_defoult=1.2;//事前測定したデフォルト値
float max_vol=5.0;//Aruinoの電圧max値
float Vs=0.0;//計測したセンサの電圧値
float air_val=60;//air状態でのセンサ値
float alc_val=0.0;//alcohol状態でのセンサ値

int warm_up_cnt=500;//airで校正する暖気時間


int disp_seg_delay = 5; //7segの点滅時間
int num_1 = 0; //1桁目の位の数字
int num_2 = 0; //2桁目の位の数字
int num_3 = 0; //3桁目の位の数字


//シフトレジスタ用定義
// seven_ledsをbyte型として定義
// 配列にDisplay表示用のデータ0~9と全消灯を設定
// 1 = LED OFF, 0 = LED ON

//小数点OFF
byte seven_leds[12] =       { B11111100,  // 0
                              B01100000,  // 1
                              B11011010,  // 2
                              B11110010,  // 3
                              B01100110,  // 4
                              B10110110,  // 5
                              B10111110,  // 6
                              B11100100,  // 7
                              B11111110,  // 8
                              B11110110,  // 9
                              B00000001,  // .
                              B00000000,  // OFF
                             };
//小数点ON
byte seven_leds_dp[12] =    { B11111101,  // 0
                              B01100001,  // 1
                              B11011011,  // 2
                              B11110011,  // 3
                              B01100111,  // 4
                              B10110111,  // 5
                              B10111111,  // 6
                              B11100101,  // 7
                              B11111111,  // 8
                              B11110111,  // 9
                              B00000001,  // .
                              B00000000,  // OFF
                             };



//シフトレジスタ部分を関数化
void funcShiftReg(int x)
{
  digitalWrite(rclkPin, LOW);                     //送信中のRCLKをLowにする
  shiftOut(dsPin, srclkPin, LSBFIRST, seven_leds[x]); //シフト演算を使って点灯するLEDを選択
  digitalWrite(rclkPin, HIGH);                        //送信終了後RCLKをHighにする
}

void funcShiftReg_dp(int x)
{
  digitalWrite(rclkPin, LOW);                     //送信中のRCLKをLowにする
  shiftOut(dsPin, srclkPin, LSBFIRST, seven_leds_dp[x]); //シフト演算を使って点灯するLEDを選択
  digitalWrite(rclkPin, HIGH);                        //送信終了後RCLKをHighにする
}

//センサ初期化
void initial_MQ(){
    //センサの初期化
    //センサの電圧を計測して平均化
    Serial.println("start initializing sensor");
    for(int i=0;i<warm_up_cnt;i++){
        raw_val += analogRead(MQ_analog);
        delay(10);
        if(i%10==0){
            Serial.println("raw_val:"+String(raw_val/(i+1)));
        }
    }
    raw_val /= warm_up_cnt;
    V0=raw_val*max_vol/1024.0;
    R0 = RL*(max_vol/V0-1);

    Serial.println("raw_val = "+String(raw_val)+" R0 = "+String(R0)+" V0 = "+String(V0));
}

//ボタン入力のチェック 通常は1、長押しは2を返す
int push_bottun_check(){
    unsigned long cnt_0 = millis();
    unsigned long cnt_1 = millis();
    delay(200);
    if(digitalRead(bot) == HIGH){
        // delay(300);//チャタリング防止
        // if(digitalRead(bot) == HIGH){//0.3sec後も押されてるか
            while(digitalRead(bot) == HIGH){
                cnt_1 = millis();
                if(cnt_1 - cnt_0 > 1500){//1sec以上押されているか確認
                    return 2;
                }
            }
            return 1;//1sec未満の入力は1を返す
        // }
    }
    Serial.println("return 0");
    return 0;
}

int push_bottun_check2(){
    unsigned long cnt_0 = millis();
    unsigned long cnt_1 = millis();
    delay(200);
    if(digitalRead(bot) == HIGH){
        // delay(300);//チャタリング防止
        // if(digitalRead(bot) == HIGH){//0.3sec後も押されてるか
            while(digitalRead(bot) == HIGH){
                cnt_1 = millis();
                if(cnt_1 - cnt_0 > 1500){//1sec以上押されているか確認
                    return 2;
                }
            }
    }
    return 1;
}

void LED_Green_ON(){
    digitalWrite(LED_Blue, HIGH);
    digitalWrite(LED_Green, LOW);
    digitalWrite(LED_Red, HIGH);
}

void LED_Red_ON(){
    digitalWrite(LED_Blue, HIGH);
    digitalWrite(LED_Green, HIGH);
    digitalWrite(LED_Red, LOW);
}

void LED_Blue_ON(){
    digitalWrite(LED_Blue, LOW);
    digitalWrite(LED_Green, HIGH);
    digitalWrite(LED_Red, HIGH);
}

void LED_Yellow_ON(){
    digitalWrite(LED_Blue, HIGH);
    digitalWrite(LED_Green, LOW);
    digitalWrite(LED_Red, LOW);
}

void LED_OFF(){
    digitalWrite(LED_Blue, HIGH);
    digitalWrite(LED_Green, HIGH);
    digitalWrite(LED_Red, HIGH);
}

// void intertupt_bot(){
//     Serial.println("interrupt");
//     disp_test();
// }

void disp_num(){
    Serial.println("start disp_num");
    if(alc_val<0.05){
        LED_Green_ON();
    }
    else if(alc_val<=0.05 && alc_val<0.1){
        LED_Yellow_ON();
    }
    else{
        LED_Red_ON();
    }

    pre_time = millis();
    while(millis()-pre_time<10000){
        //1桁目表示
        digitalWrite(dig1, LOW); 
        digitalWrite(dig2, HIGH);
        digitalWrite(dig3, HIGH);     
        funcShiftReg(num_3);
        delay(disp_seg_delay);
        //2桁目表示
        digitalWrite(dig1, HIGH);
        digitalWrite(dig2, LOW);
        digitalWrite(dig3, HIGH);
        funcShiftReg(num_2);
        delay(disp_seg_delay);
        //3桁目表示
        digitalWrite(dig1,HIGH);
        digitalWrite(dig2, HIGH);
        digitalWrite(dig3, LOW);
        funcShiftReg_dp(num_1);
        delay(disp_seg_delay);
        if(digitalRead(bot) == HIGH){
            Serial.println("break");
            break;
        }
    }
    digitalWrite(dig1,HIGH);
    digitalWrite(dig2, HIGH);
    digitalWrite(dig3, HIGH);     
}


void get_val(){
    Serial.println("start get_val");
    V0=v0_defoult;//初期設定使わず固定値にする。無効時はコメントアウト
    float sum = 0.0;
    float val = 0.0;
    float tmp_val = 0.0;
    alc_val = 0.0;
    pre_time = millis();
    Serial.println("millis-pretime:"+String(millis()-pre_time));
    while(millis()-pre_time<10000){
        if(digitalRead(bot)==HIGH){
            Serial.println("break");
            break;
        }
        sum=0.0;
        val=0.0;
        tmp_val=0.0;
        int t=millis()-pre_time;
        if((t/1000)%2==0){//0-999sec, 2000-2999secまで
            LED_Blue_ON();
        }
        else{//1000-1999sec, 3000-3999secまで
            LED_OFF();
        }
        for(int i = 0; i < 10; i++){
            val = analogRead(A0);
            sum = sum+val;
            delay(10);
        }
        raw_val = sum/10;
        Vs = (raw_val/1024)*5;
        tmp_val = pow(air_val*(V0/(max_vol - V0))*(max_vol-Vs)/Vs,-1.5);
        alc_val=max(alc_val,tmp_val);
    }
    Serial.println("Rs/R0:"+String(air_val*(V0/(max_vol - V0))*(max_vol-Vs)/Vs));
    Serial.println("raw_val:"+String(raw_val)+" Vs:"+String(Vs)+" alc_val:"+String(alc_val));    
    //表示の文字を計算
    if(alc_val<0.01){
        num_1 = 0;
        num_2 = 0;
        num_3 = 0;
    }
    else if(0.01<=alc_val && alc_val<1){
        num_1 = 0;
        num_2 = int(alc_val*100)/10;
        num_3 = int(alc_val*100)%10;

    }
    else{
        num_1 = 1;
        num_2 = 0;
        num_3 = 0;
    }
    Serial.println("num_1:"+String(num_1)+" num_2:"+String(num_2)+" num_3:"+String(num_3));
}


void setup() {
    // put your setup code here, to run once:
    Serial.begin(9600);
    pinMode(bot, INPUT);
    pinMode(pow_keep, OUTPUT);
    pinMode(dig1, OUTPUT);
    pinMode(dig2, OUTPUT);
    pinMode(dig3, OUTPUT);
    pinMode(dsPin, OUTPUT);
    pinMode(srclkPin, OUTPUT);
    pinMode(rclkPin, OUTPUT);
    pinMode(LED_Blue, OUTPUT);
    pinMode(LED_Green, OUTPUT);
    pinMode(LED_Red, OUTPUT);
    //割り込みピンの設定
    // attachInterrupt(digitalPinToInterrupt(bot),intertupt_bot, CHANGE);

    digitalWrite(pow_keep, HIGH);//これで電源KEEP
    digitalWrite(dig1,HIGH);
    digitalWrite(dig2,HIGH);
    digitalWrite(dig3,HIGH); 
    LED_OFF();


    // //MQ-3の暖気待ち用 固定値使わないとき用
    // pre_time = millis();
    // while(millis() - pre_time < warm_up_time){
    //     LED_Blue_ON();
    //     digitalWrite(dig1, LOW); 
    //     digitalWrite(dig2, HIGH);
    //     digitalWrite(dig3, HIGH);          
    //     funcShiftReg(1);    
    //     delay(300);
    //     LED_Green_ON();
    //     digitalWrite(dig1, HIGH);
    //     digitalWrite(dig2, LOW);
    //     digitalWrite(dig3, HIGH);
    //     funcShiftReg(2);    
    //     delay(300);
    //     LED_Red_ON();
    //     digitalWrite(dig1,HIGH);
    //     digitalWrite(dig2, HIGH);
    //     digitalWrite(dig3, LOW);
    //     funcShiftReg_dp(3);    
    //     delay(300);
    //     LED_OFF();
    //     delay(300);
    // }
    // digitalWrite(dig1,HIGH);
    // digitalWrite(dig2, HIGH);
    // digitalWrite(dig3, HIGH);
    // //MQ-3の初期設定
    // LED_Red_ON();
    // initial_MQ();
    // LED_OFF();
    
    //ボタン押しっぱなしで起動時の自動OFFを止めるためのループ
    while(digitalRead(bot)==HIGH){
        LED_Yellow_ON();
    }

    V0=v0_defoult;//初期設定使わず固定値にする。無効時はコメントアウト
    Serial.println("setup finished");

}

void loop() {
    LED_Blue_ON();
    if(digitalRead(bot)==HIGH){
        Serial.println("ボタンが押されました。ボタン入力のチェック");
        int bot_state=push_bottun_check2();
        if(bot_state==1){
            Serial.println("ボタンが押されました。");
            // LED_Red_ON();
            // disp_test();
            get_val();
            disp_num();
            pre_time = millis();
        }
        else if (bot_state==2){
            Serial.println("ボタンが長押しされました。");
            // LED_Yellow_ON();
            delay(5000);
            digitalWrite(pow_keep, LOW);
        }
        else{
            Serial.println("入力検出ミス");
        }
    }
    curr_time = millis();
    if(curr_time - pre_time > off_time){
        Serial.println("5分経過しました。5sec後に自動オフします");
        pre_time=millis();
        while(millis()-pre_time<5000){
            int t=millis()-pre_time;
            if(t/500%2==0){
                LED_Yellow_ON();
            }
            else{
                LED_OFF();
            }
        }
        delay(5000);
        digitalWrite(pow_keep, LOW);
    }
 
}

5. 終わりに

アルコールチェッカーを作ってみようと思いたち、ひとまずそれっぽいものが完成できました。
また、ハイサイドスイッチも組み込むことができました。
ただ3Dプリンタでのはめあいはうまくできなかったので、おいおい何か別のクラフトでリベンジしようかな。

ここまで見ていただいてありがとうございました。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?