update readme

This commit is contained in:
ProtoDrive000 2024-06-23 13:10:18 +08:00
parent 8f3bea42ee
commit 3ec231189f
15 changed files with 249 additions and 151 deletions

View File

@ -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_())

141
README.md
View File

@ -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引脚
SRBSignal 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

View File

@ -1,15 +1,34 @@
#include <SPI.h>
#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;
}
}
}

View File

@ -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

View File

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

Before

Width:  |  Height:  |  Size: 180 KiB

After

Width:  |  Height:  |  Size: 180 KiB

View File

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

Before

Width:  |  Height:  |  Size: 110 KiB

After

Width:  |  Height:  |  Size: 110 KiB