Autor: Atomei Alexandru-Constantin
Grupa: 334CD
Proiectul consta intr-un dispozitiv portabil care integreaza un senzor de pulsoximetrie și un senzor de alcool. Cu ajutorul pulsoximetrului se masoara în timp real ritmul cardiac (BPM) și nivelul de saturație a oxigenului din sange (SpO₂), iar cu ajutorul senzorului de alcool se determina concentrația de alcool din aerul expirat. Datele sunt afișate pe un ecran LCD TFT, iar utilizatorul poate schimba modul de functionare al dispozitivului printr-un buton dedicat. În cazul depășirii unui prag de alcoolemie prestabilit, un buzzer emite un semnal de avertizare.
Scopul proiectului este de a oferi o soluție practică de auto-monitorizare a starii de sanatate și a nivelului de alcool, ajutand astfel la prevenirea incidentelor cauzate de lipsa de informare. Dispozitivul poate fi util atat pentru persoanele preocupate de sanatatea lor, cat si pentru șoferi care vor să evite sancțiunile legale.
Inspirat de dorinta de a avea control total asupra propriei stari, mi-am imaginat un dispozitiv care să imbine două functii: monitorizarea continua a semnelor vitale şi testarea alcoolemiei, pentru evitarea deciziilor riscante. Am realizat ca, de multe ori, nu avem la indemana un instrument rapid care sa ne spuna ca avem o problema – fie că e vorba de un atac de panica/efort mult prea ridicat, fie ca ne intoarcem acasa dupa o seara în oras in care am baut.
Astfel, am decis sa proiectez un sistem compact, user-friendly, care sa puna la dispozitie date importante despre corpul nostru, combinand utilitatea medicala cu responsabilitatea sociala.
Proiectul are la baza un microcontroller ESP32, care face legatura intre tot hardware-ul si gestioneaza atat achizitia de date, cat si interactiunea cu utilizatorul.
Conectate la el sunt urmatoarele componente:
Nume componenta | Model | Protocol | Link achizitie | Datasheet |
---|---|---|---|---|
Microcontroller | ESP32-WROOM-32 | - | Optimus Digital | Datasheet ESP32 |
Senzor pulsoximetrie | MAX30102 | I2C | eMAG | Datasheet MAX30102 |
Senzor gaz | MQ-3 | ADC | Optimus Digital | Datasheet MQ-3 |
Display LCD TFT | 128x128px 1.44 inch ST7735S | SPI | eMAG | - |
Buzzer | Buzzer pasiv 5V | PWM | - | - |
Buton | Push Button | - | - | - |
Rezistori | - | - | - | - |
Din punct de vedere al legaturilor, componentele sunt conectate astfel:
Descrierea codului aplicaţiei (firmware):
void IRAM_ATTR isr_button(void* arg) { button_time = esp_timer_get_time() / 1000; if (button_time - last_button_time > 250) { buttonPressed = true; last_button_time = button_time; } }
Intreruperea e folosita pentru a detecta corect apasarea butonului, care determina schimbarea starii device-ului intre pulsoximetru si alcooltest, iar interogarea timer-ului intern al ESP32-ului pentru a scapa de debouncing.
// Setup buzzer PWM ledc_timer_config_t ledc_timer = { .speed_mode = LEDC_LOW_SPEED_MODE, .duty_resolution = LEDC_TIMER_8_BIT, .timer_num = LEDC_TIMER_0, .freq_hz = 2000, .clk_cfg = LEDC_AUTO_CLK }; ledc_timer_config(&ledc_timer); ledc_channel_config_t ledc_channel = { .gpio_num = buzzerPin, .speed_mode = LEDC_LOW_SPEED_MODE, .channel = buzzerChannel, .timer_sel = LEDC_TIMER_0, .duty = 0, .hpoint = 0 }; ledc_channel_config(&ledc_channel); // Set GPIO19 as input GPIO.enable_w1tc = (1 << 19); // Clear output enable (input mode) // Enable pull-up GPIO.pin[19].pad_driver = 0; // Normal output (not open-drain) REG_SET_BIT(GPIO_PIN_MUX_REG[19], FUN_PU); // Enable pull-up (register macro) // Set interrupt type: negative edge GPIO.pin[19].int_type = 1; // Enable GPIO interrupt for GPIO19 GPIO.pin[19].int_ena = 1; // Enable interrupt for CPU 0 // Register the ISR esp_intr_alloc(ETS_GPIO_INTR_SOURCE, 0, isr_button, NULL, NULL); // Enable global GPIO interrupts ets_intr_unlock();
Initializarea butonului si al PWM-ului pentru buzzer sunt realizate utilizand ESP-IDF + lucru direct pe registrii ESP-ului(lucru care mi-a dat batai de cap foarte mari, pentru restul implementarilor am decis sa raman la ESP-IDF si framework-ul de Arduino). Pinul la care e legat butonul este initializat ca input, intreruperea e setata pe falling edge si e activata rezistenta de pull-up interna corespunzatoare. Pentru PWM, este folosit timer-ul LEDC pe 8 biti.
void buzzer_task(void *param) { int freq; while (1) { if (xQueueReceive(buzzerQueue, &freq, portMAX_DELAY)) { ledc_set_freq(LEDC_LOW_SPEED_MODE, LEDC_TIMER_0, freq); ledc_set_duty(LEDC_LOW_SPEED_MODE, buzzerChannel, 128); ledc_update_duty(LEDC_LOW_SPEED_MODE, buzzerChannel); vTaskDelay(pdMS_TO_TICKS(100)); ledc_set_duty(LEDC_LOW_SPEED_MODE, buzzerChannel, 0); ledc_update_duty(LEDC_LOW_SPEED_MODE, buzzerChannel); } } }
if (buttonPressed) { int note = NOTE_D4; xQueueSend(buzzerQueue, ¬e, 0); change_state(); }
La fiecare apasare a butonului este actionat si buzzer-ul prin modificarea duty cycle-ului pe o perioada scurta. Am decis sa creez un task separat pentru buzzer, deoarece aparea un delay sesizabil la schimbarea afisajului din cauza delay-ului.
SPI-ul e initializat prin libraria Adafruit_ST7735 a display-ului. La schimbarea valorilor de pe ecran, acesta nu se redeseneaza in totalitate, ci doar partea care necesita modificari.
I2C-ul e initializat prin libraria Wire.h din Arduino. Transmiterea si primirea de informatii este realizata prin libraria MAX3010x a senzorului de pulsoximetrie. Codul pentru prelucrarea datelor este preluat dintr-un exemplu pus la dispozitie in aceasta librarie.Referinta este la finalul paginii.
double measure_alcohol() { const int measurementDuration = 500; const int sampleInterval = 50; const int numSamples = measurementDuration / sampleInterval; int samples[numSamples]; unsigned long startTime = millis(); int sampleCount = 0; while (millis() - startTime < measurementDuration && sampleCount < numSamples) { samples[sampleCount++] = analogRead(MQ3_PIN); delay(sampleInterval); } float mean = 0.0; for (int i = 0; i < sampleCount; ++i) { mean += samples[i]; } mean /= sampleCount; double Vout = static_cast<double>(mean) * (5 / 4096.0); double Rs = RL * (5 - Vout) / Vout; double ratio = Rs / R0; // Apply the log-log relationship to get ppm double ppm = pow(10, -1.76 * log10(ratio) + 2.3); // Convert to mg/L (ethanol molecular weight = 46.07 g/mol) double mgL = (ppm * 46.07 / 24.45) / 1000.0; return mgL; }
Pentru citirea senzorului de alcool prin ADC m-am folosit de functia de analogRead() din Arduino(din nou, am incercat sa folosesc varianta din ESP-IDF, dar obtineam rezultate diferite si nu mai erau calibrate..). Calculele pentru transformari au fost gasite prin diferite articole pe care le voi referentia la finalul paginii.
Aici gasiti demo-ul proiectului: https://www.youtube.com/watch?v=yMKbu_0QTNY
Resurse Software
Resurse Hardware