This commit is contained in:
ProtoDrive000 2024-11-10 22:42:37 +08:00
parent 1b08dbe151
commit 0ff0edfa8c
27 changed files with 334390 additions and 158 deletions

Binary file not shown.

View File

@ -1,8 +1,9 @@
<h1 align = "center">🌟ADS1299 8通道脑机接口设备🌟</h1>
<h1 align = "center">🌟YuEEG 8通道脑机接口设备🌟</h1>
![Img](https://img.shields.io/badge/Arduino-passing-green)
![Img](https://img.shields.io/badge/PlatformIO-passing-green)
![Img](https://img.shields.io/badge/YuEEG-V1-grey)
![Img](https://img.shields.io/badge/YuEEG-V1\.6-grey)
> [!CAUTION]
> 项目基于自定义的MIT协议除本项目拥有者以外该项目不允许用来参加任何商业比赛。
@ -11,6 +12,12 @@
本项目旨在利用TI的ADS1299芯片构建一个8通道的脑机接口BCI设备。项目涵盖硬件电路设计、芯片驱动开发以及数据接收和显示的上位机程序。
## 项目亮点
- **8通道高分辨率EEG采集**,适合科研和创客项目。
- **定制3D打印脑电帽外壳**,设计舒适,便于精确放置电极。
- **兼容Arduino的驱动代码**便于集成和控制支持多种EEG应用和项目开发。
## 目录 📁
1. [项目背景](#项目背景)
@ -37,7 +44,20 @@
### 电路设计图:
![Img](https://imgpool.protodrive.xyz/img/yank-note-picgo-img-20240623132646.png#pic_center%20=400x)
>嘉立创链接:[https://oshwhub.com/protodrive000/1299_pro](https://a360.co/3AnxQdK%20)
访问密码yutaov5
|||
|-|-|
|![Img](https://imgpool.protodrive.xyz/img/yank-note-picgo-img-20241110221805.png#pic_center =400x)|![Img](https://imgpool.protodrive.xyz/img/yank-note-picgo-img-20241110221809.png#pic_center =400x)|
### 外壳模型设计图:
>3D外壳文件https://a360.co/3AnxQdK
访问密码yutaov5
||||
|-|-|-|
|![Img](https://imgpool.protodrive.xyz/img/yank-note-picgo-img-20241110221759.png#pic_center =400x)|![Img](https://imgpool.protodrive.xyz/img/yank-note-picgo-img-20241110221635.PNG#pic_center =400x)|![Img](https://imgpool.protodrive.xyz/img/yank-note-picgo-img-20241110221644.png#pic_center =400x)|
## 软件开发 💻
@ -185,12 +205,11 @@ SRBSignal Reference Buffer引脚在ADS1299中用于信号参考电极的
- **图形库**pyqt + fluent
### 界面截图:
![Img](https://imgpool.protodrive.xyz/img/yank-note-picgo-img-20241110222325.png#pic_center%20=400x)
![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)
<video width="640" height="360" controls>
<source src="https://imgpool.protodrive.xyz/img/8c357eebb239c2a005d27d91e568f3a7.mp4" type="video/mp4">
</video>
## 使用指南 📚
@ -200,14 +219,60 @@ SRBSignal Reference Buffer引脚在ADS1299中用于信号参考电极的
2. 将EEG电极正确连接到被测试者。
3. 使用USB线连接设备和电脑。
### 软件设置
驱动代码是为支持ESP32的Arduino IDE编写的代码通过SPI与ADS1299进行数据采集。
### 先决条件
- **Arduino IDE**:从[Arduino官方网站](https://www.arduino.cc/en/software)下载最新版本。
- **ESP32开发板包**在Arduino IDE中通过开发板管理器添加ESP32开发板支持。
### 安装步骤
1. **克隆此仓库**
```bash
git clone https://github.com/YuTaoV5/YuEEG.git
cd YuEEG
### 软件运行:
1. 安装所需库:
2. 安装所需库:
```bash
pip install pyserial pyqt5
pip install pyqtgraph PyQt-Fluent-Widgets
pip install vtk scikit-learn
```
### 加载代码:
打开Arduino IDE选择 文件 > 打开 并选择 ino 文件。
在 工具 > 开发板 下选择你的ESP32开发板。
在 工具 > 端口 下设置正确的COM端口。
### 上传代码:
编译并将代码上传到ESP32开发板。
## 代码使用方法
上传完成后ESP32开发板将开始与ADS1299芯片进行通信并将EEG数据输出到串口监视器。
- 模式切换:使用以下串口命令切换不同模式:
- 1连续读取模式
- 2阻抗测量模式
- 3自检模式
>示例用法
上传完成后在Arduino IDE中打开串口监视器波特率设为115200。你将看到来自每个通道的实时EEG数据。
``` C
Channel 1: 0.123456, Channel 2: 0.654321, ..., Channel 8: 0.345678
```
## 贡献指南
欢迎社区贡献!请随时提交问题、功能请求或拉取请求。
## 未来改进
- 增加对不同微控制器的支持。
- 扩展脑电帽设计,以便调整电极位置。
## 致谢
特别感谢德州仪器提供ADS1299芯片并感谢开源社区的启发与支持。
## 项目结构 🗂️
```
├── hardware

View File

@ -1,16 +1,36 @@
#include <SPI.h>
#include "esp_timer.h"
// 定义宏,简化寄存器配置
#define CONFIG_1 0xD5
#define CONFIG_2 0xD0
#define CONFIG_3 0xEC
#define CHnSET 0x60 //0x20 ==> 12倍增益 0x40 ==> 24倍增益
#define ENABLE_SRB1 0x20
#define BIAS_SENSP 0xFF
#define BIAS_SENSN 0xFF
#define LEAD_OFF_CURRENT 0x00
#define ENABLE_POSITIVE_LEAD_OFF 0x00
#define ENABLE_NEGATIVE_LEAD_OFF 0x00
// 定义模式常量
#define MODE_CONTINUOUS_READ 1
#define MODE_IMPEDANCE_MEASURE 2
#define MODE_SELF_TEST 3
int currentMode = MODE_CONTINUOUS_READ; // 当前模式
volatile bool dataReady = false; // 标志位
double channelDataBuffer[9]; // 缓冲区,用于保存读取的数据
// 函数声明
void IRAM_ATTR onDataTimer(void* arg);
void IRAM_ATTR onImpedanceTimer(void* arg);
void IRAM_ATTR onDRDYInterrupt(); // DRDY引脚中断服务函数
void setup();
void drawSprite();
void readButtons();
void loop();
void startContinuousReadMode();
void startImpedanceMeasurementMode();
void startSelfTestMode();
void initADS1299();
void measureImpedance();
void getDeviceID();
@ -21,16 +41,13 @@ void readData();
void convertData(byte *data, double *channelData);
// 定义引脚
#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
#define CS_PIN A3
#define SCLK_PIN SCK
#define MOSI_PIN MOSI
#define MISO_PIN MISO
#define DRDY_PIN A0
#define START_PIN A2
#define RESET_PIN A1
// 定义命令
#define WAKEUP 0x02
#define STANDBY 0x04
@ -43,45 +60,6 @@ void convertData(byte *data, double *channelData);
#define RREG 0x20
#define WREG 0x40
volatile boolean startRead = 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;
}
}
void IRAM_ATTR onImpedanceTimer(void* arg) {
if (digitalRead(DRDY_PIN) == LOW) {
startImpedanceRead = true;
}
}
void setup() {
// 初始化串口
Serial.begin(115200);
@ -92,10 +70,13 @@ void setup() {
pinMode(MOSI_PIN, OUTPUT);
pinMode(MISO_PIN, INPUT);
pinMode(DRDY_PIN, INPUT);
pinMode(CLKSEL_PIN, OUTPUT);
pinMode(START_PIN, OUTPUT);
pinMode(RESET_PIN, OUTPUT);
pinMode(PWDN_PIN, OUTPUT);
digitalWrite(CS_PIN, LOW);
digitalWrite(START_PIN, LOW);
digitalWrite(RESET_PIN, HIGH);
delay(100);
// 初始化SPI
SPI.begin(SCLK_PIN, MISO_PIN, MOSI_PIN, CS_PIN);
@ -108,104 +89,142 @@ void setup() {
getDeviceID();
Serial.println("ADS1299 initialized");
// 配置ESP32定时器
const esp_timer_create_args_t dataTimer_args = {
.callback = &onDataTimer,
.name = "ADS1299 Data Timer"
};
const esp_timer_create_args_t impedanceTimer_args = {
.callback = &onImpedanceTimer,
.name = "ADS1299 Impedance Timer"
};
esp_timer_create(&dataTimer_args, &dataTimer);
esp_timer_create(&impedanceTimer_args, &impedanceTimer);
// 配置外部中断DRDY_PIN为低时触发中断
attachInterrupt(digitalPinToInterrupt(DRDY_PIN), onDRDYInterrupt, FALLING);
currentMode = MODE_CONTINUOUS_READ;
}
void loop() {
// 处理串口输入切换ADS1299功能
if (Serial.available()) {
char cmd = Serial.read();
Serial.print("收到:");
Serial.println(cmd);
if (cmd == '1') {
currentMode = MODE_CONTINUOUS_READ;
startContinuousReadMode();
} else if (cmd == '2') {
currentMode = MODE_IMPEDANCE_MEASURE;
startImpedanceMeasurementMode();
} else if (cmd == '3') {
currentMode = MODE_SELF_TEST;
startSelfTestMode();
}
}
// 检查是否有数据准备好
if (dataReady) {
// 清除标志位
dataReady = false;
// 打印数据
Serial.print("Channel:");
for (int i = 0; i < 9; i++) {
Serial.print(channelDataBuffer[i], 6);
if (i != 8) {
Serial.print(",");
} else {
Serial.println("");
}
}
}
}
if (startRead) {
startRead = false;
readData();
// DRDY引脚的中断服务函数
void IRAM_ATTR onDRDYInterrupt() {
if (currentMode == MODE_CONTINUOUS_READ) {
readData(); // 读取数据
} else if (currentMode == MODE_IMPEDANCE_MEASURE) {
measureImpedance(); // 测量阻抗
} else if (currentMode == MODE_SELF_TEST) {
readData(); // 读取数据
}
if (startImpedanceRead) {
startImpedanceRead = false;
measureImpedance();
}
}
void startContinuousReadMode() {
continuousReadMode = true;
readImpedance = false;
sendCommand(RESET);
delay(100);
sendCommand(SDATAC);
writeRegister(0x01, 0x96); // 设置数据速率为1kSPS
writeRegister(0x02, 0xD0); // 内部参考电压和偏置电流
writeRegister(0x03, 0xE0); // 启用偏置驱动器
writeRegister(0x04, 0x20); // 设置MISC1寄存器启用SRB1
// 使用宏配置寄存器
writeRegister(0x01, CONFIG_1);
writeRegister(0x02, CONFIG_2);
writeRegister(0x03, CONFIG_3);
writeRegister(0x04, 0x00);
for (int i = 0x05; i <= 0x0C; i++) {
writeRegister(i, 0x60); // 设置PGA增益和输入类型
writeRegister(i, CHnSET);
}
writeRegister(0x0D, 0xFF); // BIAS_SENSP
writeRegister(0x0E, 0xFF); // BIAS_SENSN
writeRegister(0x0D, BIAS_SENSP);
writeRegister(0x0E, BIAS_SENSN);
writeRegister(0x15, ENABLE_SRB1); // 启用SRB1
// 启动数据连续读取
sendCommand(START);
sendCommand(RDATAC); // 启动数据连续读取模式
esp_timer_start_periodic(dataTimer, 1000); // 1ms间隔
esp_timer_stop(impedanceTimer);
sendCommand(RDATAC);
}
void startImpedanceMeasurementMode() {
continuousReadMode = false;
readImpedance = true;
sendCommand(RESET);
delay(100);
sendCommand(SDATAC);
// 配置导联电流进行阻抗测量
writeRegister(0x0F, 0x02); // 设置LOFF寄存器导联电流为6nA或其他适当值
writeRegister(0x18, 0xFF); // 启用所有正极通道的导联检测
writeRegister(0x19, 0xFF); // 启用所有负极通道的导联检测
esp_timer_start_periodic(impedanceTimer, 1000); // 1ms间隔
esp_timer_stop(dataTimer);
writeRegister(0x0F, LEAD_OFF_CURRENT); // 设置导联电流
writeRegister(0x18, ENABLE_POSITIVE_LEAD_OFF); // 启用正极导联检测
writeRegister(0x19, ENABLE_NEGATIVE_LEAD_OFF); // 启用负极导联检测
}
void startSelfTestMode() {
sendCommand(RESET);
delay(100);
sendCommand(SDATAC);
// 配置CONFIG2寄存器启用测试信号
writeRegister(0x01, 0xD4);
writeRegister(0x02, 0x10);
writeRegister(0x03, 0xEC);
writeRegister(0x04, 0x00); // MISC1寄存器启用SRB1
// 配置所有通道输入为内部测试信号
for (int i = 0x05; i <= 0x0C; i++) {
writeRegister(i, 0x65); // 设置每个通道为测试信号
}
// 启动数据连续读取
sendCommand(START);
sendCommand(RDATAC);
}
void initADS1299() {
digitalWrite(CLKSEL_PIN, HIGH);
digitalWrite(CS_PIN, LOW);
digitalWrite(START_PIN, LOW);
digitalWrite(RESET_PIN, HIGH);
digitalWrite(PWDN_PIN, HIGH);
delay(100);
sendCommand(RESET);
delay(100);
sendCommand(SDATAC);
writeRegister(0x01, 0x96); // 设置数据速率为1kSPS
writeRegister(0x02, 0xD0); // 内部参考电压和偏置电流
writeRegister(0x03, 0xE0); // 启用偏置驱动器
writeRegister(0x04, 0x20); // 设置MISC1寄存器启用SRB1
sendCommand(65);
// 使用宏定义配置寄存器
writeRegister(0x01, CONFIG_1);
writeRegister(0x02, CONFIG_2);
writeRegister(0x03, CONFIG_3);
writeRegister(0x04, 0x00); // MISC1寄存器
for (int i = 0x05; i <= 0x0C; i++) {
writeRegister(i, 0x60); // 设置PGA增益和输入类型
writeRegister(i, CHnSET); // 设置PGA增益和输入类型
}
writeRegister(0x0D, 0xFF); // BIAS_SENSP
writeRegister(0x0E, 0xFF); // BIAS_SENSN
writeRegister(0x0D, BIAS_SENSP);
writeRegister(0x0E, BIAS_SENSN);
writeRegister(0x15, ENABLE_SRB1); // 启用SRB1
sendCommand(START);
sendCommand(RDATAC); // 启动数据连续读取模式
}
void readData() {
byte data[27];
digitalWrite(CS_PIN, LOW);
for (int i = 0; i < 27; i++) {
data[i] = SPI.transfer(0x00);
}
digitalWrite(CS_PIN, HIGH);
// 转换数据,并将结果保存在全局缓冲区
convertData(data, channelDataBuffer);
// 设置标志,表示数据已经准备好
dataReady = true;
}
void measureImpedance() {
byte data[27];
digitalWrite(CS_PIN, LOW);
@ -239,15 +258,14 @@ void measureImpedance() {
}
}
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);
Serial.print("Device ID: ");
Serial.println(data, BIN);
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) {
value |= 0xFF000000;
}
channelData[i] = (double)value * 4.5 / (double)0x7FFFFF;
}
}
void sendCommand(byte cmd) {
@ -273,33 +291,13 @@ byte readRegister(byte reg) {
return value;
}
void readData() {
void getDeviceID() {
digitalWrite(CS_PIN, LOW);
byte data[27];
for (int i = 0; i < 27; i++) {
data[i] = SPI.transfer(0x00);
}
SPI.transfer(SDATAC);
SPI.transfer(RREG | 0x00);
SPI.transfer(0x00);
byte data = SPI.transfer(0x00);
digitalWrite(CS_PIN, HIGH);
double channelData[9];
convertData(data, channelData);
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("Device ID: ");
Serial.println(data, BIN);
}
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) {
value |= 0xFF000000;
}
channelData[i] = (double)value * 4.5 / (double)0x7FFFFF;
}
}

BIN
参考资料/3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 441 KiB

Binary file not shown.

BIN
参考资料/ads1299.pdf Normal file

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long