This is an old revision of the document!
Frecventele la care functioneaza semnalele cerebrale:
Mai jos am inclus un exemplu grafic care ilustreaza cum arata undele cerebrale, fiecare cu frecventele si amplitudinile specifice gamei din care face parte. Fiecare linie reprezinta o componenta a semnalului compus, iar pe baza acestor forme de unda este posibila identificarea si clasificarea activitatii cerebrale in gamele mentionate:
Sistemul EEG propus capteaza semnalul cerebral prin electrozi conectati la un amplificator si apoi la convertorul ADS1115. Semnalul este transmis catre ESP32, care il proceseaza in timp real. Datele apoi sunt afisate pe un ecran OLED si vor fi salvate pe un card microSD. Zgomotul de 50/60 Hz si altele sunt filtrate digital pentru a obtine o clasificare precisa in benzi cerebrale.
#include <Wire.h> #include <Adafruit_ADS1X15.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 #define GRAPH_WIDTH 128 #define GRAPH_HEIGHT 40 #define GRAPH_Y_OFFSET 20 Adafruit_ADS1115 ads; Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1); float dataPoints[GRAPH_WIDTH]; int currentX = 0; float visualGain = 10.0; unsigned long lastSample = 0; unsigned long sampleCount = 0; void setup() { Serial.begin(115200); Wire.begin(); if (!ads.begin()) { Serial.println("ADS1115 error!"); while (1); } if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { Serial.println("Display error!"); while (1); } ads.setGain(GAIN_ONE); ads.setDataRate(RATE_ADS1115_860SPS); display.clearDisplay(); display.setTextSize(1); display.setTextColor(SSD1306_WHITE); display.setCursor(0, 0); display.println("EEG ready"); display.display(); Serial.println("timestamp,raw,voltage"); } void loop() { unsigned long now = micros(); if (now - lastSample >= 4000) { lastSample = now; sampleCount++; unsigned long timestamp = millis(); int16_t raw = ads.readADC_SingleEnded(0); float voltage = raw * 4.096 / 32767.0; Serial.print(timestamp); Serial.print(","); Serial.print(raw); Serial.print(","); Serial.println(voltage, 6); float amplified = voltage * visualGain; dataPoints[currentX] = amplified; currentX = (currentX + 1) % GRAPH_WIDTH; drawWaveform(); } } void drawWaveform() { display.clearDisplay(); float minVal = dataPoints[0]; float maxVal = dataPoints[0]; for (int i = 1; i < GRAPH_WIDTH; i++) { if (dataPoints[i] < minVal) minVal = dataPoints[i]; if (dataPoints[i] > maxVal) maxVal = dataPoints[i]; } float range = maxVal - minVal; if (range < 0.001) range = 0.001; display.setTextSize(1); display.setCursor(0, 0); display.print("V:"); display.print(dataPoints[(currentX - 1 + GRAPH_WIDTH) % GRAPH_WIDTH], 2); display.setCursor(65, 0); display.print("R:"); display.print(range, 2); int baselineY = GRAPH_Y_OFFSET + GRAPH_HEIGHT / 2; for (int x = 0; x < GRAPH_WIDTH; x += 10) { display.drawPixel(x, baselineY, SSD1306_WHITE); } for (int i = 0; i < GRAPH_WIDTH - 1; i++) { int x1 = i; int x2 = i + 1; int y1 = GRAPH_Y_OFFSET + GRAPH_HEIGHT - ((dataPoints[i] - minVal) / range * GRAPH_HEIGHT); int y2 = GRAPH_Y_OFFSET + GRAPH_HEIGHT - ((dataPoints[x2 % GRAPH_WIDTH] - minVal) / range * GRAPH_HEIGHT); y1 = constrain(y1, GRAPH_Y_OFFSET, GRAPH_Y_OFFSET + GRAPH_HEIGHT); y2 = constrain(y2, GRAPH_Y_OFFSET, GRAPH_Y_OFFSET + GRAPH_HEIGHT); display.drawLine(x1, y1, x2, y2, SSD1306_WHITE); } int markerY = GRAPH_Y_OFFSET + GRAPH_HEIGHT - ((dataPoints[currentX] - minVal) / range * GRAPH_HEIGHT); markerY = constrain(markerY, GRAPH_Y_OFFSET, GRAPH_Y_OFFSET + GRAPH_HEIGHT); display.fillCircle(currentX, markerY, 1, SSD1306_WHITE); display.display(); }
import serial import time import csv def get_serial_port(serial_port, baud_rate, timeout=1): ser = serial.Serial(serial_port, baud_rate, timeout=timeout) time.sleep(2) return ser def write_to_csv(serial_conn, csv_file_path): with open(csv_file_path, mode='w', newline='') as file: writer = csv.writer(file) writer.writerow(["timestamp", "raw", "voltage"]) while True: line = serial_conn.readline().decode(errors='ignore').strip() if line.count(',') == 2: timestamp, raw, voltage = line.split(",") writer.writerow([timestamp.strip(), raw.strip(), voltage.strip()]) print(timestamp, raw, voltage) def main(): serial_port = "COM5" baud_rate = 115200 csv_file = "eeg_data.csv" ser = get_serial_port(serial_port, baud_rate) write_to_csv(ser, csv_file) if __name__ == "__main__": main()
Fişierele se încarcă pe wiki folosind facilitatea Add Images or other files. Namespace-ul în care se încarcă fişierele este de tipul :pm:prj20??:c? sau :pm:prj20??:c?:nume_student (dacă este cazul). Exemplu: Dumitru Alin, 331CC → :pm:prj2009:cc:dumitru_alin.
In aceasta sectiune voi evidentia cateva aspecte suplimentare. Saptamana aceasta a fost dedicata componentei hardware, am realizat mai multe simulari in LTSpice pentru a anticipa comportamentul circuitului odata ce va fi implementat fizic. Am testat semnale care acopera majoritatea gamelor EEG ce pot fi observate realist, pentru a evalua atat amplificarea cat si filtrarea acestora. Mai jos am pus codurile celor doua etape prin care obtin un semnal EEG pregatit pentru a fi citit de ADC:
* Test INA333 EEG Amplifier ; Delta (1Hz, amplitudine 5μV) ; V1 INP 0 SIN(2.5 5u 1) ; V2 INN 0 SIN(2.5 -5u 1) ; Theta (6Hz, amplitudine 2μV) ; V1 INP 0 SIN(2.5 2u 6) ; V2 INN 0 SIN(2.5 -2u 6) ; Alpha (10Hz, amplitudine 2.3μV) V1 INP 0 SIN(2.5 2.3u 10) V2 INN 0 SIN(2.5 -2.3u 10) ; Beta (20Hz, amplitudine 1.8μV) ; V1 INP 0 SIN(2.5 1.8u 20) ; V2 INN 0 SIN(2.5 -1.8u 20) VCC VCC 0 DC 5 VREF REF 0 DC 2.5 CDECOUPLE VCC 0 0.1u RGAIN RG+ RG- 1k XU1 INP INN VCC 0 OUT REF RG+ RG- INA333 .lib INA333.LIB .options abstol=1n reltol=1u .tran 0 1000ms 0 10us .backanno .end
* Test Sallen-Key MCP6002 - Low-Pass Filter (~40 Hz cutoff) * Delta - 1Hz, 5mV * V1 IN 0 SIN(2.5 5m 1) * Theta - 6Hz, 3mV * V1 IN 0 SIN(2.5 3m 6) * Alpha - 10Hz, 2.5mV V1 IN 0 SIN(2.5 2.5m 10) * Beta - 20Hz, 2mV * V1 IN 0 SIN(2.5 2m 20) * Zgomot 50Hz - 5mV * V1 IN 0 SIN(2.5 5m 50) VDD VDD 0 DC 5 R1 IN N1 18k R2 N1 N2 18k C1 N2 0 220n C2 N1 OUT 220n XU1 N2 OUT VDD 0 OUT MCP6002 Rload OUT 0 100k .lib MCP6002.LIB .tran 0 1s 0 1m .options reltol=1e-3 abstol=1e-6 .backanno .end