
久しぶりの電子工作。
今回は[TGS2450]というガスセンサーを使って、臭いモニターを作ってみました。

使用した材料
・マイコン:Arduino Pro Mini 互換品
・表示器:OLD(SSD1306)※I2cのタイプを使いました
電源は5V。回路は秋月電子の資料そのままです。
Arduinoのスケッチはラジオペンチさんの「0.96インチOLEDにトレンドグラフを表示 (Arduino)」を参考にさせていただきました。
オートスケールのグラフは見ていて楽しいです。
ラジオペンチさんこのようなソースコードを公開していただいてありがとうございます。
表示は中央上にリアルタイムの信号値、右上上段「D」はグラフが表示されている最中の最大値と最小値の差、右上下段「M」はグラフが表示している最大値を表示しています。
電源を入れてから安定するまで(数値が下がるまで)ちょっと時間がかかります。
臭いモニターのフルスケールは1000くらいです。
実際に色々反応を試してみたところ、揮発性の高い気体(サインペンやアルコール)は敏感に反応(900超え)します。

にんにくみそもしっかり反応(800くらい)しました。←この反応の数値では耐えられないくらいの臭いですorz...
気になる体臭や口臭ではどうだったかというと・・・
反応は低かったです。周囲の環境にもよるかもしれませんが、よほど他に反応する気体が存在しないところでないと相対的には変化量は見れないのかもしれません。
気が付かないだけで、室内ではセンサーに反応する気体が結構存在するように思います。(私や私の部屋が臭いわけではないですよ!たぶん・・・)
空気が澄んでいるときの庭で少し様子を見てセンサーの値が低くなったの確認してから「ハァ~」ってやると多少の値の変化が見られます。
楽しいけどちょっと傷つく(^^;)
まー、臭いの感覚は人それぞれなのであまり気にすることはないのですが...
ほんとに臭ったら自分でも気づきますよね!?
ちなみに歯磨き後はほとんど変化なしでした。
様々な臭いのするものや条件で数値を取って研究してみないと、臭いモニターとしては使いこなすのは難しいと思いました。
ーーーーー Arduino スケッチ -----
/* データートレンドモニタ(20190127_OLEDgraphTest.ino)
* 0.96インチOLEDにアナログ入力の変化グラフを書く
* 2019/01/27 ラジオペンチ http://radiopench.blog96.fc2.com/
*
*/
* 0.96インチOLEDにアナログ入力の変化グラフを書く
* 2019/01/27 ラジオペンチ http://radiopench.blog96.fc2.com/
*
*/
#include
#include // adafruitのライブラリを使用
#include
#include // 定周期割込みに使用
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define PIN_HEATER 14 // D14(A0)
#define PIN_SENSOR 15 // D15(A1)
#define PIN_OUTPUT 3 // A3
// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
int latestData;
int latestData_rev;
int latestData_Max;
int latestData_Min;
int latestData_Deff;
int dataBuff[110]; // データーバッファ
char chrBuff[20]; // 表示フォーマットバッファ
int dataMin;
int dataMax;
volatile boolean timeFlag = LOW;
void setup() {
// pinMode(13, OUTPUT);
// Serial.begin(9600);
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3C for 128x64
Serial.println(F("SSD1306 allocation failed"));
for (;;); // Don't proceed, loop forever
}
for (int i = 0; i <= 99; i++) {
dataBuff[i] = -1; // バッファを未定義フラグ(-1)で埋める
}
drawCommons(); // 共通部分を描画
MsTimer2::set(60, timeUp); // 60ms秒毎にタイマー割込み
MsTimer2::start();
pinMode(PIN_HEATER,OUTPUT);
pinMode(PIN_SENSOR,OUTPUT);
digitalWrite(PIN_HEATER,HIGH); // Heater Off
digitalWrite(PIN_SENSOR,LOW); // Sensor Pullup Off
}
void loop() {
while (timeFlag == LOW) { // MsTimer2割込み待ち
}
timeFlag = LOW;
int val=0;
delay(185);
digitalWrite(PIN_SENSOR,HIGH); // Sensor Pullup On
delay(3);
latestData = analogRead(PIN_OUTPUT); // Get Sensor Voltage
delay(2);
digitalWrite(PIN_SENSOR,LOW); // Sensor Pullup Off
digitalWrite(PIN_HEATER,LOW); // Heater On
delay(8);
digitalWrite(PIN_HEATER,HIGH); // Heater Off
latestData = 1024 - latestData;
//Serial.println(latestData);
// digitalWrite(13, HIGH);
// latestData = analogRead(0); // アナログポート0のデーターを読む(115us)
saveBuff(); // 表示バッファに書き込み(190us)
dispNewData(); // 最新値を表示(1.42ms)
dispPeekData();
plotData(); // グラフプロット(7.4ms)
scaleLine(); // 目盛り線表示(1.9ms)
dispVscale(); // 縦軸目盛り表示(3.9ms)
display.display(); // バッファの値を転送して表示(37ms)
// digitalWrite(13, LOW); // 処理時間合計=52ms
}
void drawCommons() { // 共通図形の作画
display.clearDisplay();
display.setTextSize(1); // Normal 1:1 pixel scale
display.setTextColor(WHITE); // Draw white text
display.setCursor(0, 0); // Start at top-left corner
display.println("Smell");
display.setCursor(0, 8);
display.println(" monitor");
display.drawRect(26, 16, 102, 48, WHITE); // グラフ領域枠の作画
display.drawFastHLine(24, 16, 2, WHITE); // Max値の補助マーク
display.drawFastHLine(24, 39, 2, WHITE); // center
display.drawFastHLine(24, 63, 2, WHITE); // Min
display.display(); // VRAM転送
}
void dispVscale() { // 縦軸目盛り表示
display.fillRect(0, 16, 24, 55, BLACK); // 前の表示をまとめて消す(24x55ドット)(460us)
display.setCursor(0, 16);
sprintf(chrBuff, "%4d", dataMax); display.print(chrBuff); // Max値表示
display.setCursor(0, 36);
sprintf(chrBuff, "%4d", (dataMax + dataMin) / 2); display.print(chrBuff); // 中心値表示
display.setCursor(0, 57);
sprintf(chrBuff, "%4d", dataMin); display.print(chrBuff); // Min値表示
}
void saveBuff() { // データバッファの更新と最大・最小値の決定
int d;
dataMin = 1023; // 最小
dataMax = 0; // 最大値記録変数を初期化
for (int i = 98; i >= 0; i--) { // 配列に値を保存しながら最大と最小値を求める
d = dataBuff[i];
dataBuff[i + 1] = d; // 配列のデーターを一つ後ろにずらし
if (d != -1) { // ずらしたデータが有効値だったら、
if (d
dataMin = d;
}
if (d > dataMax) { // 最大値を記録
dataMax = d;
}
}
}
latestData_Max = dataMax;
latestData_Min = dataMin;
dataBuff[0] = latestData; // 配列の先頭には最新データーを記録し、
if (latestData
dataMin = latestData;
}
if (latestData > dataMax) { // 最大値を再確認
dataMax = latestData;
}
dataMin = dataMin - 20; // 最小値を-20下に設定
dataMin = (dataMin / 10) * 10; // 10ステップに丸め
if (dataMin < 0) {
dataMin = 0; // 但し下限は0
}
dataMax = dataMax + 20; // 最大値を+20上に設定
dataMax = ((dataMax / 10) + 1) * 10; // 切り上げで10ステップに丸め
if (dataMax > 1020) {
dataMax = 1023; // 但し1020以上なら1023で抑える
}
// Serial.print(dataMin); Serial.print(", "); Serial.println(dataMax);
}
void dispNewData() { // 最新データーの値を画面の中央に表示
display.fillRect(48, 0, 46, 16, BLACK); // 前の表示値を消す(24x8ドット)(320us)
display.setCursor(48, 0);
display.setTextSize(2,2);
sprintf(chrBuff, "%4d", latestData); // 4桁右詰めで
display.print(chrBuff); // 最新測定値を表示
}
void dispPeekData() { // Maxデーターの値を画面の右上に表示
if (latestData_Max >= latestData_Min){
latestData_Deff = latestData_Max - latestData_Min;
}
display.fillRect(100, 0, 47, 16, BLACK); // 前の表示値を消す(24x8ドット)(320us)
display.setCursor(102, 0);
display.setTextSize(1);
display.println("D");
display.setCursor(103, 0);
sprintf(chrBuff, "%4d", latestData_Deff); // 4桁右詰めで
display.print(chrBuff); // 最新測定値を表示
display.setCursor(102, 8);
display.println("M");
display.setCursor(102, 8);
sprintf(chrBuff, "%4d", latestData_Max); // 4桁右詰めで
display.print(chrBuff);
}
void plotData() { // 配列の値に基づきデーターをプロット
long yPoint;
display.setTextSize(1);
display.fillRect(27, 17, 100, 46, BLACK); // グラフ表示領域をクリア(100x53ドット)(1.9ms)
for (int i = 0; i <= 98; i++) {
if (dataBuff[i] == -1) { // データーが未定(-1)なら
break; // プロット中止
}
yPoint = map(dataBuff[i], dataMin, dataMax, 63, 15); // プロット座標へ変換
display.drawPixel(125 - i, yPoint, WHITE); // データをプロット
}
}
void scaleLine() { // 目盛り線を作画
for (int x = 26; x <= 128; x += 4) {
display.drawFastHLine(x, 39, 1, WHITE); // 中心線を点線で描く
}
for (int x = (127 - 33); x > 30; x -= 33) {
for (int y = 16; y
display.drawFastVLine(x, y, 1, WHITE); // 縦線を点線で2本描く
}
}
display.setTextSize(1);
display.setCursor(96, 55);
display.print("< 8s>"); // 横軸スケール表示
}
void timeUp() { // MsTimer2割込み処理
timeFlag = HIGH;
}
#include // adafruitのライブラリを使用
#include
#include // 定周期割込みに使用
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define PIN_HEATER 14 // D14(A0)
#define PIN_SENSOR 15 // D15(A1)
#define PIN_OUTPUT 3 // A3
// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
int latestData;
int latestData_rev;
int latestData_Max;
int latestData_Min;
int latestData_Deff;
int dataBuff[110]; // データーバッファ
char chrBuff[20]; // 表示フォーマットバッファ
int dataMin;
int dataMax;
volatile boolean timeFlag = LOW;
void setup() {
// pinMode(13, OUTPUT);
// Serial.begin(9600);
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3C for 128x64
Serial.println(F("SSD1306 allocation failed"));
for (;;); // Don't proceed, loop forever
}
for (int i = 0; i <= 99; i++) {
dataBuff[i] = -1; // バッファを未定義フラグ(-1)で埋める
}
drawCommons(); // 共通部分を描画
MsTimer2::set(60, timeUp); // 60ms秒毎にタイマー割込み
MsTimer2::start();
pinMode(PIN_HEATER,OUTPUT);
pinMode(PIN_SENSOR,OUTPUT);
digitalWrite(PIN_HEATER,HIGH); // Heater Off
digitalWrite(PIN_SENSOR,LOW); // Sensor Pullup Off
}
void loop() {
while (timeFlag == LOW) { // MsTimer2割込み待ち
}
timeFlag = LOW;
int val=0;
delay(185);
digitalWrite(PIN_SENSOR,HIGH); // Sensor Pullup On
delay(3);
latestData = analogRead(PIN_OUTPUT); // Get Sensor Voltage
delay(2);
digitalWrite(PIN_SENSOR,LOW); // Sensor Pullup Off
digitalWrite(PIN_HEATER,LOW); // Heater On
delay(8);
digitalWrite(PIN_HEATER,HIGH); // Heater Off
latestData = 1024 - latestData;
//Serial.println(latestData);
// digitalWrite(13, HIGH);
// latestData = analogRead(0); // アナログポート0のデーターを読む(115us)
saveBuff(); // 表示バッファに書き込み(190us)
dispNewData(); // 最新値を表示(1.42ms)
dispPeekData();
plotData(); // グラフプロット(7.4ms)
scaleLine(); // 目盛り線表示(1.9ms)
dispVscale(); // 縦軸目盛り表示(3.9ms)
display.display(); // バッファの値を転送して表示(37ms)
// digitalWrite(13, LOW); // 処理時間合計=52ms
}
void drawCommons() { // 共通図形の作画
display.clearDisplay();
display.setTextSize(1); // Normal 1:1 pixel scale
display.setTextColor(WHITE); // Draw white text
display.setCursor(0, 0); // Start at top-left corner
display.println("Smell");
display.setCursor(0, 8);
display.println(" monitor");
display.drawRect(26, 16, 102, 48, WHITE); // グラフ領域枠の作画
display.drawFastHLine(24, 16, 2, WHITE); // Max値の補助マーク
display.drawFastHLine(24, 39, 2, WHITE); // center
display.drawFastHLine(24, 63, 2, WHITE); // Min
display.display(); // VRAM転送
}
void dispVscale() { // 縦軸目盛り表示
display.fillRect(0, 16, 24, 55, BLACK); // 前の表示をまとめて消す(24x55ドット)(460us)
display.setCursor(0, 16);
sprintf(chrBuff, "%4d", dataMax); display.print(chrBuff); // Max値表示
display.setCursor(0, 36);
sprintf(chrBuff, "%4d", (dataMax + dataMin) / 2); display.print(chrBuff); // 中心値表示
display.setCursor(0, 57);
sprintf(chrBuff, "%4d", dataMin); display.print(chrBuff); // Min値表示
}
void saveBuff() { // データバッファの更新と最大・最小値の決定
int d;
dataMin = 1023; // 最小
dataMax = 0; // 最大値記録変数を初期化
for (int i = 98; i >= 0; i--) { // 配列に値を保存しながら最大と最小値を求める
d = dataBuff[i];
dataBuff[i + 1] = d; // 配列のデーターを一つ後ろにずらし
if (d != -1) { // ずらしたデータが有効値だったら、
if (d
dataMin = d;
}
if (d > dataMax) { // 最大値を記録
dataMax = d;
}
}
}
latestData_Max = dataMax;
latestData_Min = dataMin;
dataBuff[0] = latestData; // 配列の先頭には最新データーを記録し、
if (latestData
dataMin = latestData;
}
if (latestData > dataMax) { // 最大値を再確認
dataMax = latestData;
}
dataMin = dataMin - 20; // 最小値を-20下に設定
dataMin = (dataMin / 10) * 10; // 10ステップに丸め
if (dataMin < 0) {
dataMin = 0; // 但し下限は0
}
dataMax = dataMax + 20; // 最大値を+20上に設定
dataMax = ((dataMax / 10) + 1) * 10; // 切り上げで10ステップに丸め
if (dataMax > 1020) {
dataMax = 1023; // 但し1020以上なら1023で抑える
}
// Serial.print(dataMin); Serial.print(", "); Serial.println(dataMax);
}
void dispNewData() { // 最新データーの値を画面の中央に表示
display.fillRect(48, 0, 46, 16, BLACK); // 前の表示値を消す(24x8ドット)(320us)
display.setCursor(48, 0);
display.setTextSize(2,2);
sprintf(chrBuff, "%4d", latestData); // 4桁右詰めで
display.print(chrBuff); // 最新測定値を表示
}
void dispPeekData() { // Maxデーターの値を画面の右上に表示
if (latestData_Max >= latestData_Min){
latestData_Deff = latestData_Max - latestData_Min;
}
display.fillRect(100, 0, 47, 16, BLACK); // 前の表示値を消す(24x8ドット)(320us)
display.setCursor(102, 0);
display.setTextSize(1);
display.println("D");
display.setCursor(103, 0);
sprintf(chrBuff, "%4d", latestData_Deff); // 4桁右詰めで
display.print(chrBuff); // 最新測定値を表示
display.setCursor(102, 8);
display.println("M");
display.setCursor(102, 8);
sprintf(chrBuff, "%4d", latestData_Max); // 4桁右詰めで
display.print(chrBuff);
}
void plotData() { // 配列の値に基づきデーターをプロット
long yPoint;
display.setTextSize(1);
display.fillRect(27, 17, 100, 46, BLACK); // グラフ表示領域をクリア(100x53ドット)(1.9ms)
for (int i = 0; i <= 98; i++) {
if (dataBuff[i] == -1) { // データーが未定(-1)なら
break; // プロット中止
}
yPoint = map(dataBuff[i], dataMin, dataMax, 63, 15); // プロット座標へ変換
display.drawPixel(125 - i, yPoint, WHITE); // データをプロット
}
}
void scaleLine() { // 目盛り線を作画
for (int x = 26; x <= 128; x += 4) {
display.drawFastHLine(x, 39, 1, WHITE); // 中心線を点線で描く
}
for (int x = (127 - 33); x > 30; x -= 33) {
for (int y = 16; y
display.drawFastVLine(x, y, 1, WHITE); // 縦線を点線で2本描く
}
}
display.setTextSize(1);
display.setCursor(96, 55);
display.print("< 8s>"); // 横軸スケール表示
}
void timeUp() { // MsTimer2割込み処理
timeFlag = HIGH;
}