How to use STM32 to design an alcohol tester
Source: InternetPublisher:两手空空 Keywords: STM32 alcohol tester Updated: 2024/05/24
background
This project was born out of the observation that in most car-sharing services people can drive even if they are drunk, because their condition is not checked. In fact, to drive the car you just need to open it using a mobile app and get the keys inside. To solve this problem, I created a cloud-based IoT breathalyzer connected to a box containing the car keys; if the test returns a negative value, the box opens, otherwise it remains closed. Here is a more detailed analysis: IoT device architecture, cloud layer, and RIOT-OS code for IoT devices.
IoT devices
The image above shows how the sensors and actuators are connected to the SMT NUCLEO-f401re board.
The sensors used are an ultrasonic sensor and an MQ-3 alcohol sensor; the actuators used are a servo motor, three LEDs (mini traffic lights), a push button, and a buzzer.
Ultrasonic sensor (HC SR04):
It is used to allow the alcohol sensor to calculate the correct measurement. In fact, it is located near the MQ 3 sensor and only when the distance between the sensor and the person is less than 5 cm, the MQ 3 module starts measuring the blood alcohol level of the person when he exhales. The distance is estimated by sending a trigger signal and receiving an echo signal; the calculated time (in us) divided by 58 is the distance of the object in front of the ultrasonic sensor (in centimeters). Distances in the range of 2-400 cm can be measured with an accuracy of up to 3 mm. As soon as the car is opened through the mobile application (by powering the system), the ultrasonic sensor performs periodic sensing (a new measurement is performed every 5 seconds). When the box with the key is opened, the sensor stops taking measures.
MQ 3 sensor:
It measures the concentration of alcohol in the air. Its detection range is from 0.04 to 4 mg/l alcohol. It is a metal oxide semiconductor that detects the presence of alcohol vapor in the surroundings by changing its resistance. In fact, when the alcohol concentration becomes higher, the conductivity of the sensor also rises. This change in conductivity is converted into an output value that indicates the alcohol content. In particular, when the value returned minus 100 is greater than 450, the alcohol content is considered too high and the box key will remain closed. This sensor has both analog and digital outputs, but for this project, the analog output is used. The MQ 3 sensor only measures when the distance calculated by the ultrasonic sensor is less than 5 cm, so the correct measurement value can be calculated.
servo motor:
The servo motor is used to open or close the box that contains the car key. If the alcohol sensor returns a value less than or equal to 450, the box will open so the key can be taken away. If the measured value is greater than 450, the box key will remain closed.
Mini traffic light:
It has three LEDs: red, yellow and green. They are used to provide feedback for the distance measured by the ultrasonic sensor. The red LED is on when the distance is greater than 15 cm; the yellow light is on when the distance is between 5 cm and 15 cm; and the green light is on when the distance is less than 5 cm. When the green LED is on, it means that the person is close enough to the sensor for a breathalyzer test, so the MQ 3 sensor is activated and can measure the alcohol level.
Buttons:
It is used to close the frame key. When pressed, the servo motor is activated and the frame key will close. To connect the button to the circuit board, it uses a 10K ohm resistor.
buzzer:
It is used to provide feedback when the value returned by the breathalyzer is out of limits. When the value measured by the MQ 3 sensor is greater than 450, the buzzer turns on for 1 second. To connect the buzzer to the board, it uses a 1 ohm resistor.
Cloud Level
The cloud level is fully developed using the AWS ecosystem. In the following figure, there is an architecture that illustrates how the AWS services used are connected in the entire system.
The IoT device layer and the cloud exchange messages via a communication protocol based on a publish/subscribe mechanism. The board sends the actions taken by the alcohol sensor to the Mosquitto broker using the MQTT-SN protocol. These messages are published under the topic “alcool_level”. In addition, the board subscribes to the topic “topic_in” to receive messages sent from the outside, which are used to close or open the box containing the key. Mosquitto uses MQTT to exchange messages with the AWS ecosystem through a transparent bridge, a Python script that serves as a bridge between Mosquitto and AWS IoT Core. In fact, it publishes messages of “alcool_level” from the board to IoT Core and takes as input messages published by IoT Core under the topic “topic_in”, which are directed to the board. Then, by setting appropriate rules, the messages coming from the board to IoT Core are stored directly to DynamoDB. They are then displayed on the web dashboard by calling the REST API, which triggers a lambda function (“get_data_from_db.py”) that gets data from the database. From the web dashboard, the box key can be closed or opened by publishing a message "close" or a message "open" under the topic "topic_in". The message is published to IoT Core by calling a REST API that uses another lambda function ("publish_to_iotcore.py") to do this.
AWS Amplify is used to host all the static web content for the web dashboard. This triggers a lambda function (“get_data_from_db.py”) that gets the data from the database. From the web dashboard, the box key can be closed or opened by publishing a message “closed” or a message “opened” under the topic “topic_in”. The message is published to IoT Core by calling a REST API that does this with another lambda function (“publish_to_iotcore.py”). AWS Amplify is used to host all the static web content for the web dashboard. This triggers a lambda function (“get_data_from_db.py”) that gets the data from the database. From the web dashboard, the box key can be closed or opened by publishing a message “closed” or a message “opened” under the topic “topic_in”. The message is published to IoT Core by calling a REST API that does this with another lambda function (“publish_to_iotcore.py”). AWS Amplify is used to host all the static web content for the web dashboard. Messages are published to IoT Core by calling a REST API that does this using another lambda function (“publish_to_iotcore.py”). AWS Amplify is used to host all the static web content for the web dashboard. Messages are published to IoT Core by calling a REST API that does this using another lambda function (“publish_to_iotcore.py”). AWS Amplify is used to host all the static web content for the web dashboard.
On the web dashboard there are:
Two graphs are used to display: the number of box key openings (values measured by the MQ-3 sensor less than or equal to 450) in a day over the past 7 days and the number of alcohol tests that returned positive values for a day over the past 7 days;
A table showing all actions taken by the MQ-3 sensor on that day;
Two buttons for opening or closing the box key;
Some statistics about the tests calculated in the last 7 days: the maximum period of time in which the test result was positive (values between 8-12, 12-17, 17-20, 20-24 and 00-8); the number of times the box with the key was opened; the number of times the breathalyzer detected a value above the limit; the percentage of positive tests in total tests.
The main functions of the RIOT code logic
are as follows:
int main(void){
int result;
sensor_init();
mqtts_init();
while(true){
if(box_keys==0){
dist=distance_ultrasonic();
if(dist<5){
set_led("verde");
check_alcool();
}
else if(dist>=5 && dist<15){
set_led("giallo");
}
else{
set_led("rosso");
}
}
else{
while(box_keys==1){
result = gpio_read(box_pin);
if(result>0){
box_keys=0;
/*close box keys*/
servo_set(&servo, SERVO_MAX);
}
xtimer_sleep(0.5);
}
}
xtimer_sleep(5);
}
return 0;
}
If the global variable box_keys is equal to 0, it means that the box containing the keys is closed, so we can continue with the measurement. The function distance_ultrasonic returns the distance calculated from the ultrasonic sensor in centimeters.
If the distance is less than 5 cm: the green LED of the mini traffic light is turned on via the set_led("verde") function and the user can proceed with the alcohol test. The check_alcool function manages all parts related to the test (more details are explained below).
If the distance is between 5 cm and 15 cm, the yellow LED lights up, indicating that the calculated test distance is about the same, but the user must get closer
If the distance is greater than 15 cm, the red LED will light up, indicating that the distance is too far and the user must move closer to the sensor to perform the alcohol test.
If the global variable box_keys is not equal to 0, it means that the box containing the keys is open, so we enter the "else" block. The pin connected to the button is read every 0.5 seconds until its value is equal to 1. If it returns a value greater than zero (it returns the value 256 when it is pressed), the box is closed by locking it with the servo motor, and the variable box_keys is set to 0 to allow entry to the previous "if" block for the next round of the while loop.
If box_keys equals 0, the ultrasonic sensor will sense every 5 seconds due to the timer set outside the "if-else" block in the main while.
All the functions mentioned previously in the main function are explained in more detail below.
sensor_init function: Used at the beginning of the main function to initialize all GPIO pins of sensors and actuators, as well as servo motors.
void sensor_init(void){
/*ultrasonic*/
gpio_init(trigger_pin, GPIO_OUT);
gpio_init_int(echo_pin, GPIO_IN, GPIO_BOTH, &call_back, NULL);
distance_ultrasonic(); /*first read returns always 0*/
/*mq3*/
adc_init(ADC_LINE(0));
/*traffic light*/
gpio_init(red_pin, GPIO_OUT);
gpio_init(yellow_pin, GPIO_OUT);
gpio_init(green_pin, GPIO_OUT);
/*button box keys*/
gpio_init(box_pin,GPIO_IN);
/*buzzer*/
gpio_init(buzzer_pin,GPIO_OUT);
/*servo init*/
servo_init(&servo, DEV, CHANNEL, SERVO_MIN, SERVO_MAX);
servo_set(&servo, SERVO_MAX);
}
All variables for pins and servo variables are global, so they are defined outside the function (you can find more information about them in the code in the GitHub repository of the project). For the MQ 3 sensor, it is initialized as an analog line where the board receives the value. The constants DEV, CHANNEL, SERVO_MIN, SERVO_MAX used to initialize the servo motors are defined outside the function.
check_alcool function: It checks the alcohol level in the user's breath and acts accordingly.
void check_alcool(void){
int sample = 0;
char msg[4];
sample=read_mq3();
sprintf(msg, "%d", sample);
if (sample > 450) {
gpio_set(buzzer_pin);
xtimer_sleep(1);
gpio_clear(buzzer_pin);
} else {
/*open box keys*/
servo_set(&servo, SERVO_MIN);
box_keys=1;
}
pub(TOPIC_OUT1,msg);
}
Function read_mq3 returns the value calculated by the MQ 3 sensor, if greater than 450 it means that the legal limit is exceeded and therefore the car cannot be driven. The box containing the keys remains closed and the buzzer is activated for 1 second (the buzzer is used to provide feedback to the user of a positive result of the alcohol test). If the value returned by the sensor is less than or equal to 450, the box is opened (unlocked by the servo motor) and the global variable box_keys is set to 1. In both cases, it is published by Breathalyzer with the function pub under the topic "alcool_level" (this is the value of the constant TOPIC_OUT1).
read_mq3 function: returns the value measured by the MQ 3 sensor.
int read_mq3(void){
int sample = 0;
int min = 100;
sample = adc_sample(ADC_LINE(0), RES);
sample = (sample > min) ? sample - min : 0;
return sample;
}
If the value measured by the sensor is greater than 100, it returns the value minus 100, otherwise it returns 0.
distance_ultrasonic function: Returns the value measured by the ultrasonic sensor.
int distance_ultrasonic(void){
uint32_t dist;
dist=0;
echo_time = 0;
gpio_clear(trigger_pin);
xtimer_usleep(20);
gpio_set(trigger_pin);
xtimer_msleep(100);
if(echo_time > 0){
dist = echo_time/58;
}
return dist;
}
It sends a pulse to the sensor and waits 100 milliseconds to read the value of the global variable echo_time. If the value is greater than 0, it is divided by 58 to calculate the distance in centimeters of the object in front of the sensor.
call_back function: It is used along with the distance_ultrasonic function to calculate the values measured by the ultrasonic sensor.
void call_back(void* arg){
int val = gpio_read(echo_pin);
uint32_t echo_time_stop;
(void) arg;
if(val){
echo_time_start = xtimer_now_usec();
}
else{
echo_time_stop = xtimer_now_usec();
echo_time = echo_time_stop - echo_time_start;
}
}
This function is activated when a change on the echo pin is detected. It measures the time difference from sending an ultrasonic pulse to receiving it back. It stores the value in the global variable echo_time, which is used by the distance_ultrasonic function to calculate the distance in centimeters of an object in front of the sensor. echo_time_stop is also a global variable.
set_led function: This is used to set the correct LED of the mini traffic light based on the parameters passed to the function.
void set_led(char *str){
if(strcmp(str,"verde")==0){
gpio_clear(red_pin);
gpio_clear(yellow_pin);
gpio_set(green_pin);
}
else if(strcmp(str,"rosso")==0){
gpio_clear(yellow_pin);
gpio_clear(green_pin);
gpio_set(red_pin);
}
else if(strcmp(str,"giallo")==0){
gpio_clear(red_pin);
gpio_clear(green_pin);
gpio_set(yellow_pin);
}
}
If str is "verde", the green LED is on and the others are off. If str is "giallo", the yellow one is on and the others are off. If str is "rosso", the red one is on and the others are off.
mqtts_init function: It initializes the connection with the MQTT-SN broker and subscribes to the topic "topic_in" (the value of the constant TOPIC_IN) using the function sub.
static char stack[THREAD_STACKSIZE_DEFAULT];
static msg_t queue[8];
static emcute_sub_t subscriptions[NUMOFSUBS];
static char topics[NUMOFSUBS][TOPIC_MAXLEN];
void mqtts_init(void){
/* the main thread needs a msg queue to be able to run `ping`*/
msg_init_queue(queue, ARRAY_SIZE(queue));
/* initialize our subscription buffers */
memset(subscriptions, 0, (NUMOFSUBS * sizeof(emcute_sub_t)));
/* start the emcute thread */
thread_create(stack, sizeof(stack), EMCUTE_PRIO, 0, emcute_thread, NULL, "emcute");
char * addr1 = "fec0:affe::99";
add_address(addr1);
con();
sub(TOPIC_IN);
}
The following function is used to initialize the part:
static void *emcute_thread(void *arg){
(void)arg;
emcute_run(BROKER_PORT, "board");
return NULL;
}
static int add_address(char* addr){
char * arg[] = {"ifconfig", "4", "add", addr};
return _gnrc_netif_config(4, arg);
}
static int con(void){
sock_udp_ep_t gw = { .family = AF_INET6, .port = BROKER_PORT };
char *topic = NULL;
char *message = NULL;
size_t len = 0;
ipv6_addr_from_str((ipv6_addr_t *)&gw.addr.ipv6, BROKER_ADDRESS);
if (emcute_con(&gw, true, topic, message, len, 0) != EMCUTE_OK) {
printf("error: unable to connect to [%s]:%i
", BROKER_ADDRESS, (int)g w.port);
return 1;
}
printf("Successfully connected to gateway at [%s]:%i
", BROKER_ADDRESS, (int)gw.port);
return 0;
}
The function sub is used to subscribe to the topic passed as an argument.
static int sub(char* topic){
unsigned flags = EMCUTE_QOS_0;
if (strlen(topic) > TOPIC_MAXLEN) {
puts("error: topic name exceeds maximum possible size");
return 1;
}
/* find empty subscription slot */
unsigned i = 0;
for (; (i < NUMOFSUBS) && (subscriptions[i].topic.id != 0); i++) {}
if (i == NUMOFSUBS) {
puts("error: no memory to store new subscriptions");
return 1;
}
subscriptions[i].cb = on_pub;
strcpy(topics[i], topic);
subscriptions[i].topic.name = topics[i];
if (emcute_sub(&subscriptions[i], flags) != EMCUTE_OK) {
printf("error: unable to subscribe to %s
", topic);
return 1;
}
printf("Now subscribed to %s
", topic);
return 0;
}
When a message is received under a subscribed topic (in this case, the topic "topic_in"), the function on_pub manages it:
static void on_pub(const emcute_topic_t *topic, void *data, size_t len){
(void)topic;
char *in = (char *)data;
printf("### got publication for topic ’%s’ [%i] ###
", topic->name, (int)topic->id);
for (size_t i = 0; i < len; i++) {
printf("%c", in[i]);
}
puts("");
char msg[len+1];
strncpy(msg, in, len);
msg[len] = ’�’;
if (strcmp(msg, "open") == 0){
if(box_keys==0){
/*open box keys*/
servo_set(&servo, SERVO_MIN);
box_keys=1;
}
}
else if (strcmp(msg, "close") == 0){
if(box_keys==1){
/*close box keys*/
servo_set(&servo, SERVO_MAX);
box_keys=0;
}
}
}
If the message received is "open", the box containing the keys is unlocked via a servo motor and the global variable box_keys is set to 1. If the message is "close", the box is locked using a servo motor and the global variable box_keys is set to 0. The first part of the function is used to get feedback by printing the received message and its associated subject on the terminal.
The pub function is used to publish messages.
static int pub(char* topic,char* msg){
emcute_topic_t t;
unsigned flags = EMCUTE_QOS_0;
printf("pub with topic: %s and name %s and flags 0x%02x
", topic, msg, (int)flags);
/* step 1: get topic id */
t.name = topic;
if (emcute_reg(&t) != EMCUTE_OK) {
puts("error: unable to obtain topic ID");
return 1;
}
/* step 2: publish data */
if (emcute_pub(&t, msg, strlen(msg), flags) != EMCUTE_OK) {
printf("error: unable to publish data to topic ’%s [%i]’
",t.name, (int)t.id);
return 1;
}
printf("Published %i bytes to topic ’%s [%i]’
", (int)strlen(msg), t.name, t.id);
return 0;
}
In particular, the second argument to the function is the message you want to publish, and the first argument is the name of the relevant topic.
- Convenient and fast power line break detection circuit
- Make a Economical and Practical Rectifier Voltmeter
- Homemade LED digital display photovoltaic meter
- Using a Remote Control to Make a Crystal Oscillator Frequency Tester
- Inductive Pointer Watch Battery Tester
- 1.5V and 9V battery tester circuit diagram
- direct coupled amplifier circuit
- Geiger counter circuit diagram
- High-speed gain programmable amplifier circuit
- A basic differential amplifier using only one OP amplifier
- STM32 system chip worth knowing about
- Downhole oxygen concentration detection circuit
- Woodworking hand planer safety device detection circuit
- Long-distance ringtone detection circuit using photoelectric coupler
- Transistor multiple "1" detection circuit
- Static electricity detection circuit composed of LM324
- Piezoelectric sensor detection circuit
- ADM690~ADM695 constitute a detection circuit (2)
- Light sensor particle size distribution detection circuit
- Carbon dioxide gas sensor detection circuit