Um Sistema olfativo artificial tem uma matriz de sensores que sofrem mudança de resistência quando algum composto volátil é detectado. A informação, traduzida pelos sensores como mudanças de resistência, é adquirida para depois ser processada e, então, a amostra é classificado. O processo de funcionamento do sistema olfativo é mostrado abaixo. Uma aplicação do sistema para reconhecimento de vinhos pode ser vista aqui.
Neste post, é apresentado um sistema de aquisição de dados para sistemas de olfato artificial usando o Arduino Uno para capturar sinais de um conjunto de seis sensores de gás que compõem a matriz de sensoreamento (mais detalhes sobre sensores de gás aqui), bem como a conexão pela porta serial com o NodeMcu ESP 8266 para publicação da informação coletada dos sensores na nuvem via MQTT.

Matriz de sensoreamento construída com sensores MQ-3, MQ-4 e MQ-6 (dois de cada)
Funcionamento
Cada um dos seis sensores de gás são conectados as portas analógicas do Arduino Uno. O Arduino foi programado para realizar o processo de coleta dos voltagens em três etapas como mostradas na figura abaixo.
1. Fase de concentração. Antes do início desta fase, a amostra é colocada na câmara de concentração. Ao iniciar o processo, que demora três minutos, a amostra fica na câmara fechada pelas válvulas solenóides controladas pelo Arduino. O objetivo da fase é conseguir que sejam liberados os voláteis que serão levados ate a câmara de sensoriamento na seguinte etapa 2.
2. Fase de medida. No início desta etapa o Arduino envia o valor lógico para as portas configuradas como saídas digitais encargadas de ativar os relés que governam as válvulas solenóides e a bomba de ar. Neste caso são ligadas a bomba de ar e abertas as válvulas solenoides que controlam o fluxo de voláteis na câmara de concentração e na câmara de medições. Simultaneamente, o processo de envio dos dados coletados dos seis sensores é feito pela porta serial comunicando o Arduino com o NodeMcu do qual são aproveitadas as vantagems e funcionalidades para publicar informação via MQTT. O tempo estimado nesta etapa é de dois minutos.
3. Fase de Limpeza. Precisa-se limpar as duas câmaras (concentração e medição) para o início de um novo ciclo de medição. Para isso, a bomba injeta ar limpo nas câmaras tendo as válvulas abertas com o objetivo de ter fluxo dos gases para fora das mesmas. O tempo nesta etapa é de dois minutos.
Finalmente, o NodeMCU envia os dados recebidos pela porta serial ao broker MQTT que, por sua vez, entrega esta informação às aplicações inscritas no tópico que realizarão as tarefas de processamento e classificação ou regressão. O esquema abaixo ilustra a ligação dos principais componentes do sistema olfativo.

Captura de tela do aplicativo celular com os dados enviados por meio do protocolo MQTT para o aplicativo Android MQTT Dashboard para observação.
![]() |
![]() |
Códigos:
Os códigos para execução no Arduino e no NodeMcu estão disponibilizados a seguir.
Código usado no Node-MCU:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#code for Node MCU | |
#include <ESP8266WiFi.h> | |
#include <PubSubClient.h> | |
#include <ESP8266HTTPClient.h> | |
#include <string.h> | |
extern "C"{ | |
#include "user_interface.h" | |
} | |
const char* ssid = "Network-name"; //Wifi conection name | |
const char* password = "password"; //Wifi conection password | |
const char* mqtt_server = "mqtt(server).com"; //Url MQTT Server | |
const char* clientID = "nodemcu"; //ID MQTT Client | |
const char* topic0 = "configuration"; //Instance | |
const char* topic1 = "sensor1"; //Instance | |
const char* topic2 = "sensor2"; //Instance | |
const char* topic3 = "sensor3"; | |
const char* topic4 = "sensor4"; | |
const char* topic5 = "sensor5"; | |
const char* topic6 = "sensor6"; | |
const char* mqttuser = "USER"; //Instance user authorized | |
const char* mqttpass = "password"; //Instance password | |
const int port = 00000; //Number MQTT port (unencrypted) | |
WiFiClient TempClient; | |
PubSubClient mqttclient(TempClient); | |
const int LED = D4; | |
String data_received=""; | |
char cadena[4]; | |
void sendMQTT(char topic); | |
byte to_receive(void); | |
byte check_data; | |
void setup() | |
{ | |
Serial.begin(38400); | |
pinMode(LED, OUTPUT); // Pin digital 5 como salida | |
//Wifi configuration | |
WiFi.begin(ssid, password); | |
while (WiFi.status() != WL_CONNECTED) { | |
delay(500); | |
} | |
//MQTT configuration | |
mqttclient.setServer(mqtt_server, port); | |
mqttclient.setCallback(callback); | |
} | |
void loop() | |
{ | |
if (!mqttclient.connected()) { | |
mqtt_connection(); | |
} | |
if (Serial.available()) | |
{ | |
to_receive(); | |
if (data_received=="s1") | |
{ | |
check_data==LOW; | |
check_data=to_receive(); | |
if (check_data==HIGH) { | |
mqttclient.publish(topic1, cadena); | |
Serial.flush(); | |
if (check_data==HIGH) digitalWrite(LED, LED^1); | |
} | |
} | |
if (data_received=="s2") | |
{ | |
check_data==LOW; | |
check_data=to_receive(); | |
if (check_data==HIGH) { | |
mqttclient.publish(topic2, cadena); | |
Serial.flush(); | |
if (check_data==HIGH) digitalWrite(LED, LED^1); | |
} | |
} | |
if (data_received=="s3") | |
{ | |
check_data==LOW; | |
check_data=to_receive(); | |
if (check_data==HIGH) { | |
mqttclient.publish(topic3, cadena); | |
Serial.flush(); | |
if (check_data==HIGH) digitalWrite(LED, LED^1); | |
} | |
} | |
if (data_received=="s4") | |
{ | |
check_data==LOW; | |
check_data=to_receive(); | |
if (check_data==HIGH) { | |
mqttclient.publish(topic4, cadena); | |
Serial.flush(); | |
if (check_data==HIGH) digitalWrite(LED, LED^1); | |
} | |
} | |
if (data_received=="s5") | |
{ | |
check_data==LOW; | |
check_data=to_receive(); | |
if (check_data==HIGH) { | |
mqttclient.publish(topic5, cadena); | |
Serial.flush(); | |
if (check_data==HIGH) digitalWrite(LED, LED^1); | |
} | |
} | |
if (data_received=="s6") | |
{ | |
check_data==LOW; | |
check_data=to_receive(); | |
if (check_data==HIGH) { | |
mqttclient.publish(topic6, cadena); | |
Serial.flush(); | |
if (check_data==HIGH) digitalWrite(LED, LED^1); | |
} | |
} | |
} | |
} | |
byte to_receive(void) | |
{ | |
float var1; | |
Serial.flush(); | |
delay(5); | |
data_received=""; | |
data_received = Serial.readStringUntil('\n'); | |
data_received.toCharArray(cadena, 4); | |
var1 = strtod (cadena, NULL); | |
if (var1>=0.0 && var1<=5.0) return HIGH; | |
else return LOW; | |
} | |
void mqtt_connection() { | |
while (!mqttclient.connected()) { | |
if (mqttclient.connect(clientID,mqttuser,mqttpass)) { | |
bool subscribe(String topic1, uint8_t qos = 0); | |
} else { | |
delay(1000); | |
} | |
} | |
} | |
//Optional, not used here | |
void callback(char* topic, byte* payload, unsigned int length) { | |
char conf[33]; | |
for (int i=0;i<length;i++) { | |
conf[i]=(char)payload[i]; | |
} | |
} |
Código usado no Arduino:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//Serial communication between ARDUINO and/or node MCU – Program of Master | |
// RODRIGUEZ-GAMBOA, JUAN C. & ALBARRACIN-ESTRADA, E. SUSANA | |
// – E-MAIL: {juan.gamboa,eva.susana}@ufrpe.br | |
// Libraries | |
#include <string.h> | |
#include "TimerOne.h" | |
const byte button = 2; // Digital pin when is wired the button | |
const int led=13; | |
const int gas_S1=A0; | |
const int gas_S2=A1; | |
const int gas_S3=A2; | |
const int gas_S4=A3; | |
const int gas_S5=A4; | |
const int gas_S6=A5; | |
const int air_pump=12; | |
const int valve1=9; | |
const int valve2=10; | |
const int valve3=11; | |
String data_received=""; | |
char var[8]; | |
float time_conf=0; | |
float sensor1; | |
float sensor2; | |
float sensor3; | |
float sensor4; | |
float sensor5; | |
float sensor6; | |
int state=0; | |
int meter=0; | |
int time_state1=10; | |
int time_state2=20; | |
int time_state3=10; | |
bool button_status = false; | |
bool timeout=false; | |
bool flag=false; | |
void configuration(void); | |
bool send_data(float number); | |
void to_receive(void); | |
void tCallback (void) { timeout=true;} | |
void push_button(void) { | |
button_status=true; | |
delay(100); | |
if (flag==false) { | |
flag=true; | |
state=1; | |
meter=0; | |
} | |
} | |
void setup() | |
{ | |
Serial.begin(38400); //Open serial port communication | |
attachInterrupt(digitalPinToInterrupt(button),push_button,FALLING); | |
Timer1.initialize(333333); | |
Timer1.attachInterrupt(tCallback); | |
pinMode(led, OUTPUT); | |
pinMode(air_pump, OUTPUT); | |
pinMode(valve1, OUTPUT); | |
pinMode(valve2, OUTPUT); | |
pinMode(valve3, OUTPUT); | |
} | |
void loop() | |
{ | |
if (timeout) | |
{ | |
meter++; | |
if (state==2) | |
{ timeout=false; | |
read_sensor(); | |
} | |
timeout=false; | |
} | |
if (flag==true && state!=0 && meter==0) | |
{ | |
configuration(); | |
delay(1000); | |
} | |
if (state!=0 && meter!=0) | |
{ | |
switch (state) | |
{ | |
//gas concentration | |
case 1: | |
if ((meter/3)>=time_state1) | |
{ state=2; | |
meter=0; | |
} | |
break; | |
case 2: | |
if ((meter/3)>=time_state2) { | |
state=3; | |
meter=0; | |
} | |
break; | |
case 3: | |
if ((meter/3)>=time_state3) { | |
state=4; | |
flag=false; | |
meter=0; | |
} | |
break; | |
} | |
} | |
} | |
void read_sensor(void) | |
{ | |
sensor1=analogRead(gas_S1)*5.0/1024.0; | |
String fild="s1"; | |
send_data(sensor1,fild); | |
delay(40); | |
sensor2=analogRead(gas_S2)*5.0/1024.0; | |
fild="s2"; | |
send_data(sensor2,fild); | |
delay(40); | |
sensor3=analogRead(gas_S3)*5.0/1024.0; | |
fild="s3"; | |
send_data(sensor3,fild); | |
delay(40); | |
sensor4=analogRead(gas_S4)*5.0/1024.0; | |
fild="s4"; | |
send_data(sensor4,fild); | |
delay(40); | |
sensor5=analogRead(gas_S5)*5.0/1024.0; | |
fild="s5"; | |
send_data(sensor5,fild); | |
delay(40); | |
sensor6=analogRead(gas_S6)*5.0/1024.0; | |
fild="s6"; | |
send_data(sensor6,fild); | |
} | |
void configuration(void) | |
{ | |
switch (state) | |
{ | |
//gas concentration | |
case 1: | |
Serial.println("Gas concentration"); | |
digitalWrite(air_pump,LOW); | |
digitalWrite(valve1,LOW); | |
digitalWrite(valve2,LOW); | |
digitalWrite(valve3,HIGH); | |
break; | |
//measurement | |
case 2: | |
Serial.println("Measurement"); | |
digitalWrite(valve3,LOW); | |
digitalWrite(air_pump,HIGH); | |
digitalWrite(valve1,HIGH); | |
digitalWrite(valve2,HIGH); | |
break; | |
//purge | |
case 3: | |
Serial.println("Purge"); | |
digitalWrite(air_pump,HIGH); | |
digitalWrite(valve1,HIGH); | |
digitalWrite(valve2,HIGH); | |
digitalWrite(valve3,HIGH); | |
break; | |
case 4: | |
Serial.println("Measurement finished"); | |
digitalWrite(air_pump,LOW); | |
digitalWrite(valve1,LOW); | |
digitalWrite(valve2,LOW); | |
digitalWrite(valve3,LOW); | |
flag=false; | |
state=0; | |
break; | |
} | |
} | |
bool send_data(float number, String fild) | |
{ | |
dtostrf(number,2,2, var); | |
Serial.flush(); | |
char cadena[4]; | |
fild.toCharArray(cadena,4); | |
Serial.write(cadena); | |
Serial.write('\n'); | |
delay(1); | |
Serial.flush(); | |
Serial.write(var); | |
Serial.write('\n'); | |
delay(5); | |
Serial.flush(); | |
digitalWrite(led,HIGH); | |
return true; | |
} | |
void to_receive(void) | |
{ | |
Serial.flush(); | |
delay(5); | |
if (Serial.read()=="conf") | |
{ | |
data_received=""; | |
data_received=Serial.read(); | |
char cadena[4]; | |
data_received.toCharArray(cadena,4); | |
time_conf = strtod(cadena,NULL); | |
} | |
} |