diff --git a/Interface/vtk_test.py b/Interface/vtk_test.py deleted file mode 100644 index fea3f30..0000000 --- a/Interface/vtk_test.py +++ /dev/null @@ -1,59 +0,0 @@ -import sys -from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget -from vtkmodules.qt.QVTKRenderWindowInteractor import QVTKRenderWindowInteractor -import vtkmodules.all as vtk - -class MainWindow(QMainWindow): - def __init__(self, parent=None): - super(MainWindow, self).__init__(parent) - self.setWindowTitle("VTK with PyQt Example") - self.frame = QWidget() - self.vl = QVBoxLayout() - - # VTK Renderer - self.vtkWidget = QVTKRenderWindowInteractor(self.frame) - self.vl.addWidget(self.vtkWidget) - - self.ren = vtk.vtkRenderer() - self.vtkWidget.GetRenderWindow().AddRenderer(self.ren) - self.iren = self.vtkWidget.GetRenderWindow().GetInteractor() - - # Load OBJ and MTL files - self.load_obj_file("resource/PCB.obj", "resource/PCB.mtl") - - # Add a light to the renderer - self.add_light() - - self.frame.setLayout(self.vl) - self.setCentralWidget(self.frame) - - self.show() - self.iren.Initialize() - - def load_obj_file(self, obj_file_path, mtl_file_path): - # Create an OBJ importer - importer = vtk.vtkOBJImporter() - importer.SetFileName(obj_file_path) - importer.SetFileNameMTL(mtl_file_path) - importer.SetTexturePath("resource") - importer.SetRenderWindow(self.vtkWidget.GetRenderWindow()) - importer.Update() - - self.ren.ResetCamera() - - def add_light(self): - # Create a light - light = vtk.vtkLight() - light.SetFocalPoint(0, 0, 0) - light.SetPosition(1, 1, 1) - light.SetIntensity(0.3) # Adjust intensity to make the scene darker - - self.ren.AddLight(light) - # Set a darker background color - colors = vtk.vtkNamedColors() - self.ren.SetBackground(colors.GetColor3d("DarkSlateGray")) - -if __name__ == "__main__": - app = QApplication(sys.argv) - window = MainWindow() - sys.exit(app.exec_()) diff --git a/README.md b/README.md index 8b4f762..c027330 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,8 @@ ### 电路设计图: -![电路设计图](path/to/circuit_diagram.png) +![Img](https://imgpool.protodrive.xyz/img/yank-note-picgo-img-20240623124910.png#pic_center%20=400x) + ## 软件开发 💻 @@ -40,7 +41,130 @@ - **数据读取**:从芯片读取EEG数据。 - **数据处理**:初步处理和滤波。 -## 上位机程序 📊 +### 引脚定义 + +| 引脚名称 | 描述 | +| --------- | ------------------------------- | +| CS_PIN | 片选引脚,低电平有效 | +| SCLK_PIN | SPI时钟引脚 | +| MOSI_PIN | SPI主设备输出从设备输入引脚 | +| MISO_PIN | SPI主设备输入从设备输出引脚 | +| DRDY_PIN | 数据就绪引脚,低电平表示数据可读 | +| CLKSEL_PIN| 时钟选择引脚 | +| START_PIN | 启动转换引脚 | +| RESET_PIN | 重置引脚 | +| PWDN_PIN | 省电模式引脚 | + +### 命令定义 + +| 命令 | 描述 | +| ------- | --------------------- | +| WAKEUP | 唤醒芯片 | +| STANDBY | 进入待机模式 | +| RESET | 重置芯片 | +| START | 开始数据转换 | +| STOP | 停止数据转换 | +| RDATAC | 连续读取数据 | +| SDATAC | 停止连续读取数据 | +| RDATA | 读取数据 | +| RREG | 读取寄存器 | +| WREG | 写寄存器 | + +### 定时器中断 + +代码中使用ESP32的定时器来定时读取数据和导联检测状态: + +- `onDataTimer`: 检查数据是否就绪,如果就绪则设置标志位`startRead`。 +- `onImpedanceTimer`: 读取导联检测状态。 + +### 初始化 + +在`setup`函数中: + +- 初始化串口通信。 +- 初始化引脚模式。 +- 初始化SPI通信参数。 +- 调用`initADS1299`函数初始化ADS1299。 +- 获取并打印设备ID。 +- 配置ESP32的定时器用于数据读取和导联检测。 + +### 主循环 + +在`loop`函数中: + +- 检查串口是否有数据输入,用于切换连续读取模式和导联检测模式。 +- 如果`startRead`标志位被设置,调用`readData`函数读取数据。 + +### 启动连续读取模式 + +`startContinuousReadMode`函数: + +- 设置必要的标志位。 +- 重置并初始化ADS1299的寄存器。 +- 启动连续数据读取模式。 +- 启动数据读取定时器,停止导联检测定时器。 + +### 启动导联检测模式 + +`startLeadOffDetectionMode`函数: + +- 设置必要的标志位。 +- 重置并初始化ADS1299的寄存器用于导联检测。 +- 启动导联检测定时器,停止数据读取定时器。 + +### ADS1299初始化 + +`initADS1299`函数: + +- 启动ADS1299的时序。 +- 重置芯片。 +- 停止连续数据读取模式。 +- 配置必要的寄存器。 + +### 读取导联检测状态 + +`readLeadOffStatus`函数: + +- 读取`LOFF_STATP`和`LOFF_STATN`寄存器。 +- 打印导联检测状态。 + +### 读取数据 + +`readData`函数: + +- 从ADS1299读取数据。 +- 将数据转换为电压并打印。 + +### 寄存器读写 + +包含`writeRegister`和`readRegister`函数,用于向ADS1299写入和读取寄存器。 + +### 数据转换 + +`convertData`函数: + +- 将读取的字节数据转换为电压值。 + +这个代码框架基本涵盖了ADS1299的初始化、数据读取、导联检测等主要功能。在实际应用中,可能需要根据具体需求调整寄存器配置和定时器的时间间隔。确保ADS1299手册中的寄存器配置正确无误,以实现所需的功能。 + +### REF和偏置电压(BIAS)的工作原理及电路连接 + +#### 1. 参考电极(REF) + +参考电极是所有测量电极电位的基准点。它的作用是提供一个稳定的参考电压,以确保各个测量电极的电位能够被正确地采集。ADS1299提供了灵活的配置选项,可以将任意电极设置为参考电极。 + +#### 2. 偏置电压(BIAS) + +偏置电压用于提供共模电压稳定性和干扰抑制。在EEG测量中,人体容易受到共模干扰,如电源线频率的干扰(50/60Hz)。偏置电压通过向病人施加一个控制的电压,帮助将测量电极的共模电压保持在ADS1299输入范围内。ADS1299的偏置放大器(BIAS amplifier)提供了一个内部的反馈回路,用于减少共模干扰。 + +#### 3. SRB引脚 + +SRB(Signal Reference Buffer)引脚在ADS1299中用于信号参考电极的配置。 + +- **SRB1**:当SRB1引脚用于参考时,所有通道的负输入可以连接到SRB1。设置`MISC1`寄存器的SRB1位可以实现这种配置。 +- **SRB2**:SRB2引脚可以选择单独的电极作为参考电极,并通过设置`CHnSET`寄存器的SRB2位,将该电极的电位作为其他通道的参考。 + +#### 上位机程序 📊 ### 功能: @@ -51,11 +175,15 @@ ### 使用技术: - **编程语言**:Python -- **图形库**:Matplotlib +- **图形库**:pyqt + fluent ### 界面截图: -![上位机界面](path/to/gui_screenshot.png) +![Img](https://imgpool.protodrive.xyz/img/yank-note-picgo-img-20240623125708.png#pic_center%20=400x) +![Img](https://imgpool.protodrive.xyz/img/yank-note-picgo-img-20240623125839.png#pic_center%20=400x) +![Img](https://imgpool.protodrive.xyz/img/yank-note-picgo-img-20240623125915.png#pic_center%20=400x) +![Img](https://imgpool.protodrive.xyz/img/yank-note-picgo-img-20240623125926.png#pic_center%20=400x) + ## 使用指南 📚 @@ -69,8 +197,9 @@ 1. 安装所需库: ```bash - pip install pyserial matplotlib pip install pyserial pyqt5 + pip install pyqtgraph PyQt-Fluent-Widgets + pip install vtk scikit-learn ``` ## 项目结构 🗂️ ``` @@ -78,7 +207,7 @@ │ ├── schematics │ └── pcb ├── firmware -│ └── ads1299_driver.c +│ └── ads1299_driver.ino ├── software │ └── bci_gui.py └── README.md diff --git a/Hardware/arduino/sketch_jun15a.ino b/firmware/ads1299_driver/ads1299_driver.ino similarity index 55% rename from Hardware/arduino/sketch_jun15a.ino rename to firmware/ads1299_driver/ads1299_driver.ino index 8498a5a..f4e9107 100644 --- a/Hardware/arduino/sketch_jun15a.ino +++ b/firmware/ads1299_driver/ads1299_driver.ino @@ -1,15 +1,34 @@ #include #include "esp_timer.h" + +// 函数声明 +void IRAM_ATTR onDataTimer(void* arg); +void IRAM_ATTR onImpedanceTimer(void* arg); +void setup(); +void drawSprite(); +void readButtons(); +void loop(); +void startContinuousReadMode(); +void startImpedanceMeasurementMode(); +void initADS1299(); +void measureImpedance(); +void getDeviceID(); +void sendCommand(byte cmd); +void writeRegister(byte reg, byte value); +byte readRegister(byte reg); +void readData(); +void convertData(byte *data, double *channelData); + // 定义引脚 -#define CS_PIN 10 -#define SCLK_PIN 12 -#define MOSI_PIN 11 -#define MISO_PIN 13 -#define DRDY_PIN 15 -#define CLKSEL_PIN 16 -#define START_PIN 18 -#define RESET_PIN 8 +#define CS_PIN 15 +#define SCLK_PIN 14 +#define MOSI_PIN 13 +#define MISO_PIN 12 +#define DRDY_PIN 10 +#define CLKSEL_PIN 11 +#define START_PIN 1 +#define RESET_PIN 2 #define PWDN_PIN 3 // 定义命令 @@ -25,12 +44,32 @@ #define WREG 0x40 volatile boolean startRead = false; -volatile boolean readLeadOff = false; +volatile boolean startImpedanceRead = false; +volatile boolean readImpedance = false; volatile boolean continuousReadMode = true; esp_timer_handle_t dataTimer; esp_timer_handle_t impedanceTimer; +// TFT_eSPI tft = TFT_eSPI(); +// TFT_eSprite sprite = TFT_eSprite(&tft); + +// float batteryVoltage; + +// // 颜色定义 +// #define gray 0x2A0A +// #define lines 0x8C71 +// unsigned short rings[4] = { 0x47DD, 0xFB9F, 0x86BF, 0xFFD0 }; + +// // 图表变量 +// int n = 0; +// int fromTop = 30; +// int fromLeft = 20; +// int w = 480; +// int h = 200; +// double channelData[9][20] = { 0 }; + +// 定义ESP32定时器回调函数 void IRAM_ATTR onDataTimer(void* arg) { if (digitalRead(DRDY_PIN) == LOW) { startRead = true; @@ -38,7 +77,9 @@ void IRAM_ATTR onDataTimer(void* arg) { } void IRAM_ATTR onImpedanceTimer(void* arg) { - readLeadOffStatus(); + if (digitalRead(DRDY_PIN) == LOW) { + startImpedanceRead = true; + } } void setup() { @@ -65,7 +106,7 @@ void setup() { // 初始化ADS1299 initADS1299(); getDeviceID(); - Serial.println("ADS1299 初始化完成"); + Serial.println("ADS1299 initialized"); // 配置ESP32定时器 const esp_timer_create_args_t dataTimer_args = { @@ -82,13 +123,15 @@ void setup() { esp_timer_create(&impedanceTimer_args, &impedanceTimer); } + void loop() { + // 处理串口输入,切换ADS1299功能 if (Serial.available()) { char cmd = Serial.read(); if (cmd == '1') { startContinuousReadMode(); } else if (cmd == '2') { - startLeadOffDetectionMode(); + startImpedanceMeasurementMode(); } } @@ -96,48 +139,50 @@ void loop() { startRead = false; readData(); } + + if (startImpedanceRead) { + startImpedanceRead = false; + measureImpedance(); + } + } void startContinuousReadMode() { continuousReadMode = true; - readLeadOff = false; - // 复位芯片 + readImpedance = false; sendCommand(RESET); delay(100); - sendCommand(SDATAC); // 停止数据连续读取 - // 配置寄存器 - writeRegister(0x01, 0x96); // CONFIG1 设置数据速率为1kSPS - writeRegister(0x02, 0xD0); // CONFIG2 内部参考电压和偏置电流 - writeRegister(0x03, 0xE0); // CONFIG3 启用偏置驱动器 + sendCommand(SDATAC); + writeRegister(0x01, 0x96); // 设置数据速率为1kSPS + writeRegister(0x02, 0xD0); // 内部参考电压和偏置电流 + writeRegister(0x03, 0xE0); // 启用偏置驱动器 + writeRegister(0x04, 0x20); // 设置MISC1寄存器,启用SRB1 for (int i = 0x05; i <= 0x0C; i++) { - writeRegister(i, 0x60); // CHxSET 设置PGA增益和输入类型 + writeRegister(i, 0x60); // 设置PGA增益和输入类型 } writeRegister(0x0D, 0xFF); // BIAS_SENSP writeRegister(0x0E, 0xFF); // BIAS_SENSN - // 启动连续数据读取模式 sendCommand(START); sendCommand(RDATAC); // 启动数据连续读取模式 - esp_timer_start_periodic(dataTimer, 1000); // 1000微秒 = 1毫秒 - esp_timer_stop(impedanceTimer); // 停止阻抗读取定时器 + esp_timer_start_periodic(dataTimer, 1000); // 1ms间隔 + esp_timer_stop(impedanceTimer); } -void startLeadOffDetectionMode() { +void startImpedanceMeasurementMode() { continuousReadMode = false; - readLeadOff = true; - // 复位芯片 + readImpedance = true; sendCommand(RESET); delay(100); - sendCommand(SDATAC); // 停止数据连续读取 - // 配置导联脱落检测 - writeRegister(0x0F, 0x02); // LOFF寄存器,设置为6nA - writeRegister(0x18, 0xFF); // 启用所有通道的正极导联检测 - writeRegister(0x19, 0xFF); // 启用所有通道的负极导联检测 - esp_timer_start_periodic(impedanceTimer, 100000); // 100000微秒 = 100毫秒 - esp_timer_stop(dataTimer); // 停止数据读取定时器 + sendCommand(SDATAC); + // 配置导联电流进行阻抗测量 + writeRegister(0x0F, 0x02); // 设置LOFF寄存器,导联电流为6nA或其他适当值 + writeRegister(0x18, 0xFF); // 启用所有正极通道的导联检测 + writeRegister(0x19, 0xFF); // 启用所有负极通道的导联检测 + esp_timer_start_periodic(impedanceTimer, 1000); // 1ms间隔 + esp_timer_stop(dataTimer); } void initADS1299() { - // 启动时序 digitalWrite(CLKSEL_PIN, HIGH); digitalWrite(CS_PIN, LOW); digitalWrite(START_PIN, LOW); @@ -145,48 +190,23 @@ void initADS1299() { digitalWrite(PWDN_PIN, HIGH); delay(100); - // 复位芯片 sendCommand(RESET); delay(100); - - // 停止连续读取模式 sendCommand(SDATAC); - - // 配置寄存器 - writeRegister(0x01, 0x96); // CONFIG1 设置数据速率为1kSPS - writeRegister(0x02, 0xD0); // CONFIG2 内部参考电压和偏置电流 - writeRegister(0x03, 0xE0); // CONFIG3 启用偏置驱动器 + writeRegister(0x01, 0x96); // 设置数据速率为1kSPS + writeRegister(0x02, 0xD0); // 内部参考电压和偏置电流 + writeRegister(0x03, 0xE0); // 启用偏置驱动器 + writeRegister(0x04, 0x20); // 设置MISC1寄存器,启用SRB1 for (int i = 0x05; i <= 0x0C; i++) { - writeRegister(i, 0x60); // CHxSET 设置PGA增益和输入类型 + writeRegister(i, 0x60); // 设置PGA增益和输入类型 } writeRegister(0x0D, 0xFF); // BIAS_SENSP writeRegister(0x0E, 0xFF); // BIAS_SENSN - // 启动连续数据读取模式 sendCommand(START); - sendCommand(RDATAC); + sendCommand(RDATAC); // 启动数据连续读取模式 } -void readLeadOffStatus() { - byte statP = readRegister(0x1C); // 读取LOFF_STATP寄存器 - byte statN = readRegister(0x1D); // 读取LOFF_STATN寄存器 - - Serial.print("Lead-Off Status:"); - - for (int i = 0; i < 8; i++) { - bool pStatus = statP & (1 << i); - bool nStatus = statN & (1 << i); - Serial.print(pStatus ? "Off" : "On"); - if (i != 7) { - Serial.print(","); - } else { - Serial.println(""); - } - } - // 读取导联检测结果 - // readLeadOffImpedance(); -} - -void readLeadOffImpedance() { +void measureImpedance() { byte data[27]; digitalWrite(CS_PIN, LOW); for (int i = 0; i < 27; i++) { @@ -194,14 +214,23 @@ void readLeadOffImpedance() { } digitalWrite(CS_PIN, HIGH); - // 转换数据 double channelData[9]; convertData(data, channelData); - - // 输出导联阻抗 - Serial.print("Lead-Off Impedance:"); - for (int i = 0; i < 8; i++) { + Serial.print("Channel:"); + for (int i = 0; i < 9; i++) { Serial.print(channelData[i], 6); + if (i != 8) { + Serial.print(","); + } else { + Serial.println(""); + } + } + Serial.print("Impedance Measurement:"); + for (int i = 0; i < 8; i++) { + double voltage = channelData[i]; + double current = 0.000006; // 假设使用6nA的导联电流 + double impedance = voltage / current; + Serial.print(impedance, 2); // 打印阻抗值 if (i != 7) { Serial.print(","); } else { @@ -211,12 +240,12 @@ void readLeadOffImpedance() { } void getDeviceID() { - digitalWrite(CS_PIN, LOW); // 低电平以进行通信 - SPI.transfer(SDATAC); // 停止连续读取数据模式 - SPI.transfer(RREG | 0x00); // 读取寄存器命令 - SPI.transfer(0x00); // 请求一个字节 - byte data = SPI.transfer(0x00); // 读取字节 - digitalWrite(CS_PIN, HIGH); // 高电平以结束通信 + digitalWrite(CS_PIN, LOW); + SPI.transfer(SDATAC); + SPI.transfer(RREG | 0x00); + SPI.transfer(0x00); + byte data = SPI.transfer(0x00); + digitalWrite(CS_PIN, HIGH); Serial.print("Device ID: "); Serial.println(data, BIN); } @@ -230,7 +259,7 @@ void sendCommand(byte cmd) { void writeRegister(byte reg, byte value) { digitalWrite(CS_PIN, LOW); SPI.transfer(WREG | reg); - SPI.transfer(0x00); // 写一个寄存器 + SPI.transfer(0x00); SPI.transfer(value); digitalWrite(CS_PIN, HIGH); } @@ -238,7 +267,7 @@ void writeRegister(byte reg, byte value) { byte readRegister(byte reg) { digitalWrite(CS_PIN, LOW); SPI.transfer(RREG | reg); - SPI.transfer(0x00); // 读取一个寄存器 + SPI.transfer(0x00); byte value = SPI.transfer(0x00); digitalWrite(CS_PIN, HIGH); return value; @@ -252,7 +281,6 @@ void readData() { } digitalWrite(CS_PIN, HIGH); - // 转换为9通道的double数据 double channelData[9]; convertData(data, channelData); Serial.print("Channel:"); @@ -269,9 +297,9 @@ void readData() { void convertData(byte *data, double *channelData) { for (int i = 0; i < 9; i++) { long value = ((long)data[3 * i + 3] << 16) | ((long)data[3 * i + 4] << 8) | data[3 * i + 5]; - if (value & 0x800000) { // 如果最高位为1,则为负数 - value |= 0xFF000000; // 扩展符号位 + if (value & 0x800000) { + value |= 0xFF000000; } - channelData[i] = (double)value * 4.5 / (double)0x7FFFFF; // 将值转换为电压 + channelData[i] = (double)value * 4.5 / (double)0x7FFFFF; } -} +} \ No newline at end of file diff --git a/Interface/main.py b/software/bci_gui.py similarity index 98% rename from Interface/main.py rename to software/bci_gui.py index 01ba3c0..138ec20 100644 --- a/Interface/main.py +++ b/software/bci_gui.py @@ -35,8 +35,8 @@ class Window(FramelessWindow): self.hBoxLayout = QHBoxLayout(self) self.navigationInterface = NavigationInterface(self, showMenuButton=True) self.stackWidget = QStackedWidget(self) - # channels_to_display = [0, 1, 2, 3, 4, 5, 6, 7] # Example control parameter list - channels_to_display = [2] + channels_to_display = [0, 1, 2, 3, 4, 5, 6, 7] # Example control parameter list + # channels_to_display = [2] pen_colors = ['r', 'g', 'b', 'c', 'm', 'y', 'k', 'w', 'orange'] pen_widths = [2, 2, 2, 2, 2, 2, 2, 2, 2] plot_num = 1000 diff --git a/Interface/plot_card.py b/software/plot_card.py similarity index 100% rename from Interface/plot_card.py rename to software/plot_card.py diff --git a/Interface/resource/PCB.mtl b/software/resource/PCB.mtl similarity index 100% rename from Interface/resource/PCB.mtl rename to software/resource/PCB.mtl diff --git a/Interface/resource/PCB.obj b/software/resource/PCB.obj similarity index 100% rename from Interface/resource/PCB.obj rename to software/resource/PCB.obj diff --git a/Interface/resource/PCB.step b/software/resource/PCB.step similarity index 100% rename from Interface/resource/PCB.step rename to software/resource/PCB.step diff --git a/Interface/resource/PCB.zip b/software/resource/PCB.zip similarity index 100% rename from Interface/resource/PCB.zip rename to software/resource/PCB.zip diff --git a/Interface/resource/dark/demo.qss b/software/resource/dark/demo.qss similarity index 100% rename from Interface/resource/dark/demo.qss rename to software/resource/dark/demo.qss diff --git a/Interface/resource/light/demo.qss b/software/resource/light/demo.qss similarity index 100% rename from Interface/resource/light/demo.qss rename to software/resource/light/demo.qss diff --git a/Interface/resource/logo.png b/software/resource/logo.png similarity index 100% rename from Interface/resource/logo.png rename to software/resource/logo.png diff --git a/Interface/resource/my_logo.jpg b/software/resource/my_logo.jpg similarity index 100% rename from Interface/resource/my_logo.jpg rename to software/resource/my_logo.jpg diff --git a/Interface/resource/school_logo.ico b/software/resource/school_logo.ico similarity index 100% rename from Interface/resource/school_logo.ico rename to software/resource/school_logo.ico diff --git a/Interface/resource/school_logo.png b/software/resource/school_logo.png similarity index 100% rename from Interface/resource/school_logo.png rename to software/resource/school_logo.png