传感器系统的高速采集与医疗传感应用
1. 高速数据采集
1.1 高速采集需求与ESP32能力
在某些情况下,我们需要以高于每秒几百个样本的速率来观察和生成信号,例如分析RS - 232等通信通道或观察声学信号。幸运的是,ESP32能够通过直接内存访问(DMA)模式,将样本从ADC直接传输到内存,而无需使用CPU,实现每秒超过200,000个样本的采样速率。它利用了ESP32芯片上通常用于数字传输音乐的I2S(Inter - IC Sound)支持。此外,ESP还具有在相同频率范围内的正弦和矩形信号内置频率发生器。
1.2 高速模式配置代码
为了将这些功能添加到数据采集系统中,我们可以使用以下基于ESP32示例HiFreq ADC的代码片段来配置高速模式:
#include <driver/i2s.h>
bool i2s_adc_enabled=false;
void i2sInit(uint32_t i2s_sample_rate){
i2s_config_t i2s_config = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX
| I2S_MODE_ADC_BUILT_IN),
.sample_rate = i2s_sample_rate,
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
.communication_format = I2S_COMM_FORMAT_STAND_I2S,
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
.dma_buf_count = 8,
.dma_buf_len = 1024,
// 1024 is maximum length
.use_apll = true,
// needed for low frequencies
.tx_desc_auto_clear = false,
.fixed_mclk = 0
};
if(ESP_OK != i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL)){
Serial.print("Error installing I2S.");
}
if(ESP_OK != i2s_set_adc_mode(ADC_UNIT_1, ADC1_CHANNEL_4)){ // pin 32
Serial.print("Error setting up ADC.");
}
if(ESP_OK != adc1_config_channel_atten(ADC1_CHANNEL_4, ADC_ATTEN_DB_11)){
Serial.print("Error setting up ADC attenuation.");
}
if(ESP_OK != i2s_adc_enable(I2S_NUM_0)){
Serial.print("Error enabling ADC.");
}
Serial.print("I2S ADC setup ok\n");
}
1.3 正弦频率发生器代码
正弦频率发生器也使用ESP32上的I2S硬件。以下是相关代码:
#include <driver/dac.h>
#include <soc/sens_reg.h>
#include "soc/rtc.h"
float dac25freq=0;
int dac25scale=0;
// output full scale
void cwDACinit(float freq, int scale, int offset) { //...cwDACinit
if (abs(freq) < 1e-3) {dac_output_disable(DAC_CHANNEL_1); return;}
int frequency_step=max(1.0,floor(0.5+0.95*65536.0*freq
/RTC_FAST_CLK_FREQ_APPROX));
SET_PERI_REG_MASK(SENS_SAR_DAC_CTRL1_REG, SENS_SW_TONE_EN);
SET_PERI_REG_MASK(SENS_SAR_DAC_CTRL2_REG, SENS_DAC_CW_EN1_M);
SET_PERI_REG_BITS(SENS_SAR_DAC_CTRL2_REG, SENS_DAC_INV1, 2,
SENS_DAC_INV1_S);
SET_PERI_REG_BITS(SENS_SAR_DAC_CTRL1_REG, SENS_SW_FSTEP,
frequency_step, SENS_SW_FSTEP_S);
SET_PERI_REG_BITS(SENS_SAR_DAC_CTRL2_REG, SENS_DAC_SCALE1,
scale, SENS_DAC_SCALE1_S);
SET_PERI_REG_BITS(SENS_SAR_DAC_CTRL2_REG, SENS_DAC_DC1,
offset, SENS_DAC_DC1_S);
dac_output_enable(DAC_CHANNEL_1);
}
1.4 网页功能更新
在esp32 - daq.html文件中,我们更新了
webSocketEvent()
函数,以处理JSON编码命令的响应。特别是,
START
命令现在需要区分慢速或高速模式下的采集启动。我们约定,正的采样周期值定义慢速采集模式下的采样速度,而负值则通过调用
i2sInit()
初始化高速采集。
if (strstr(cmd,"START")) {
sendMSG("INFO","ESP32: Received Start command");
sample_period=val;
if (sample_period > 0) {
SampleSlow.attach_ms(sample_period,sampleslow_action);
Serial.print("sample_period = "); Serial.println(sample_period);
} else {
if (i2s_adc_enabled) {
i2s_adc_disable(I2S_NUM_0); i2s_driver_uninstall(I2S_NUM_0);}
uint32_t i2s_sample_rate=(uint32_t) abs(sample_period);
i2sInit(i2s_sample_rate);
i2s_adc_enabled=true;
}
} else if (strstr(cmd,"STOP")) {
if (sample_period>0) {
sendMSG("INFO","ESP32: Received Stop command");
SampleSlow.detach();
} else {
if (ESP_OK != i2s_adc_disable(I2S_NUM_0)) {
Serial.printf("Error disabling ADC.");
}
if (ESP_OK != i2s_driver_uninstall(I2S_NUM_0)) {
Serial.printf("Error uninstalling I2S driver.");
}
i2s_adc_enabled=false;
if (dac25freq>0) {cwDACinit(dac25freq,dac25scale,0);}
}
} else if (strstr(cmd,"SETDAC")) {
Serial.printf("DAC25 frequency = %d\n",val);
if (val < 0) {dac25scale=-val-1;} else {dac25freq=val;}
cwDACinit(dac25freq,dac25scale,0);
}
1.5 矩形信号发生器配置
我们通过在
setup()
函数的末尾添加以下代码,将矩形信号发生器配置为在引脚27上连续输出1 kHz信号:
ledcSetup(0, 1000, 2); ledcAttachPin(27, 0); ledcWrite(0, 2);
1.6 数据采集与处理
在
loop()
函数的末尾,我们添加代码以使用
i2s_read()
采集数据,并重新排列
buffer[]
中的样本。
if (i2s_adc_enabled) {
size_t bytes_read;
uint16_t buffer[1024], swap; //={0};
i2s_read(I2S_NUM_0, &buffer, sizeof(buffer), &bytes_read, 15);
for (int j=0; j<bytes_read/2; j+=2) {
// swap left and right channel
swap=buffer[j+1]; buffer[j+1]=buffer[j]; buffer[j]=swap;
}
doc.to<JsonObject>();
// clear DynamicJsonDocument doc
for (int j=0;j<bytes_read/2;j++){doc["WF0"][j]=(buffer[j]&0x0FFF)>>3;}
serializeJson(doc,out);
webSocket.sendTXT(websock_num,out,strlen(out));
delay(2000);
}
1.7 浏览器控制功能
我们通过在esp32 - daq.html文件中添加代码,从浏览器控制ESP32的新功能。例如,为采样速度的
SELECT
菜单添加一个
OPTGROUP
,以向ESP32发送负值频率,用于配置高速ADC。
<OPTGROUP label="Hi - speed I2S ADC">
<OPTION value="-1000">1 kSamples/s</OPTION>
<OPTION value="-10000">10 kSamples/s</OPTION>
<OPTION value="-20000">20 kSamples/s</OPTION>
<OPTION value="-50000">50 kSamples/s</OPTION>
<OPTION value="-100000">100 kSamples/s</OPTION>
<OPTION value="-200000">200 kSamples/s</OPTION>
<OPTION value="-277777">277.777 kSamples/s</OPTION>
<OPTION value="-500000">500 kSamples/s</OPTION>
<OPTION value="-1000000">1 MSamples/s</OPTION>
</OPTGROUP>
</SELECT>
同时,我们使用另一个
SELECT
菜单控制正弦频率发生器。
<SELECT onchange="setDacFrequency(this.value);">
<OPTGROUP label="DAC frequency">
<OPTION value="0">DAC25 OFF</OPTION>
<OPTION value="2000">DAC25 = 2 kHz</OPTION>
<OPTION value="5000">DAC25 = 5 kHz</OPTION>
<OPTION value="10000">DAC25 = 10 kHz</OPTION>
<OPTION value="20000">DAC25 = 20 kHz</OPTION>
<OPTION value="50000">DAC25 = 50 kHz</OPTION>
<OPTION value="100000">DAC25 = 100 kHz</OPTION>
<OPTION value="200000">DAC25 = 200 kHz</OPTION>
</OPTGROUP>
<OPTGROUP label="DAC output scale">
<OPTION value="-1">1/1 scale</OPTION>
<OPTION value="-2">˜1/2 scale</OPTION>
<OPTION value="-3">˜1/4 scale</OPTION>
<OPTION value="-4">˜1/6 scale</OPTION>
</OPTGROUP>
</SELECT>
1.8 系统测试
我们通过在引脚25上生成2 kHz正弦信号,以200 kS/s的速率对其进行采样,并在图中显示波形,验证了系统的功能。同时,我们还可以使用该系统分析TSOP2238红外接收器的信号。
1.9 项目思路
- 添加功能以在下降或上升沿触发采集。
- 修改代码以从引脚33读取快速ADC。
- 添加菜单以选择引脚27上矩形信号发生器的频率和脉冲宽度。
-
加倍提供给
i2s_read()函数的buffer[]数组的大小,并记录更长的波形。 - 添加放大器以测量并显示麦克风的小电压。
- 讨论在DAC输出端添加作为线路缓冲器的运算放大器,在ADC输入端添加跨阻放大器,以测量组件的阻抗。
2. 医疗传感应用
2.1 医疗传感系统概述
我们使用NodeMCU测量心电图(ECG)、血液中的氧饱和度水平和生物阻抗,并使用Web界面显示测量结果。此外,我们还添加了电路来模拟人体的阻抗和心电图,并提供将测量值保存到NodeMCU内部闪存磁盘并稍后重放的选项。
2.2 硬件组成
- AD8232心电图传感器 :通过电缆连接三个电极,将测量的小电压进行放大和清理,并将模拟电压输出到NodeMCU的模拟输入引脚A0。
- MAX30102脉冲血氧仪 :通过比较手指尖端的红色和红外光反射来测量血氧饱和度(SPO2),通过I2C线连接到NodeMCU的引脚D1和D2。
- AD5933生物阻抗网络分析仪 :用于测量连接到模拟人体阻抗的电路(Cole模型)的两个电极之间的阻抗,通过I2C线与NodeMCU接口。
2.3 代码实现
2.3.1 初始化部分
// MediSense, V. Ziemann, 221106
const char* ssid = "messnetz";
const char* password = "zxcvZXCV";
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <WebSocketsServer.h>
#include <ArduinoJson.h>
#include <FS.h>
#include <SPI.h>
ESP8266WebServer server2(80);
// port 80
WebSocketsServer webSocket = WebSocketsServer(81);
// port 81
#include <Ticker.h>
Ticker SampleSlow,ECGticker;
#include <DFRobot_MAX30102.h>
DFRobot_MAX30102 Sensor;
#include "I2Crw.h"
#include "AD5933.h"
int16_t Ival[512],Qval[512]; float mag50=1, phase50=0;
volatile uint8_t websock_num=0,info_available=0,output_ready=0;
int mmode=0,sample_period=100,samples[3],icounter=0,recording=0;
int ringbuf[50],ibuf=0,ped=0;
// pedestal subtraction for oximeter
char info_buffer[80];
// for websock messages
char out[300];
// space for websocket messages
DynamicJsonDocument doc(300);
float Rcal=10.0;
// calibration resistor
uint16_t ecgtrace[200],ecg_counter=0;;
#define CS D8
// chip select for SPI - DAC
2.3.2 回调函数
-
cardio_action()
:读取与AD8232相关的IO端口信号,并将其放入
samples[]数组。
void cardio_action() {
samples[0]=analogRead(0)/2;
// adjust scale
samples[1]=100*digitalRead(D3)+50*digitalRead(D4);
samples[2]=0;
output_ready=1;
}
-
oximeter_action()
:读取MAX30102的信号,确定原始信号的基座,并将减去基座并适当缩放的信号放入
samples[]数组。
void oximeter_action() {
float ir=0;
if (mmode==3) {ir=(float)Sensor.getIR();
} else if (mmode==4) {ir=(float)Sensor.getRed();}
ringbuf[ibuf]=ir; ibuf++; if (ibuf>=50) {ibuf=0;}
ped=ringbuf[0]; for (int k=1;k<50;k++) {ped=min(ped,ringbuf[k]);}
samples[0]=100+(ir-ped)/4.0;
// adjust scale
icounter=icounter+1;
if (icounter==200){samples[1]=30000; icounter=0;} else {samples[1]=0;}
samples[2]=0;
output_ready=1;
}
-
impedance_action()
:编程AD5933产生50 kHz信号,读取I和Q信号,将其转换为幅度和相位,并将适当缩放的值放入
samples[]数组。
void impedance_action() {
AD5933_sweep2(50e3,0.01,0,mode,pga,Ival,Qval);
float mag=sqrt(Ival[0]*Ival[0]+Qval[0]*Qval[0]);
float phase=atan2(Qval[0],Ival[0])*180/3.1415926;
samples[0]=Rcal*(mag50/mag-1.0)*512.0;
// samples[0]=128+128*log10(mag50/mag); // log scale
samples[1]=256+phase-phase50;
samples[2]=0;
output_ready=1;
}
2.3.3 其他函数
- set_dac() :将值传递给MCP4921 DAC。
void set_dac(uint16_t val) {
val|=(B0011 << 12);
digitalWrite(CS,LOW);
SPI.transfer(highByte(val));
SPI.transfer(lowByte(val));
digitalWrite(CS,HIGH);
}
-
ecg_action()
:不断更新DAC,使用存储在
ecgtrace[]数组中的心电图轨迹值。
void ecg_action() {
set_dac(ecgtrace[ecg_counter]);
ecg_counter++;
if (ecg_counter==200) {ecg_counter=0;}
}
-
send_samples()
:将
samples[]数组格式化为JSON文档并通过WebSocket接口发送到网页显示。
void send_samples(int samples[]) {
for (int k=0;k<3;k++) {doc["ADC"][k]=samples[k];}
serializeJson(doc,out); webSocket.sendTXT(websock_num,out,strlen(out));
}
- sendMSG() :发送其他WebSocket消息。
void sendMSG(char *nam, const char *msg) {
(void) sprintf(info_buffer,"{\"%s\":\"%s\"}",nam,msg);
if (strstr(msg,"!")==msg) {
webSocket.sendTXT(websock_num,info_buffer,strlen(info_buffer));
} else {
info_available=1;
}
}
-
webSocketEvent()
:处理WebSocket消息,根据
mmode变量执行不同的操作。
void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload,
size_t length) {
Serial.printf("webSocketEvent(%d, %d, ...)\r\n", num, type);
websock_num=num;
switch(type) {
case WStype_DISCONNECTED:
Serial.printf("[%u] Disconnected!\r\n", num);
break;
case WStype_CONNECTED:
{
IPAddress ip = webSocket.remoteIP(num);
Serial.printf("[%u] Connected from %d.%d.%d.%d url: %s\r\n",
num, ip[0], ip[1], ip[2], ip[3], payload);
}
sendMSG("INFO","ESP: Successfully connected");
break;
case WStype_TEXT:
{
Serial.printf("[%u] get Text: %s\r\n", num, payload);
DynamicJsonDocument root(300);
//........parse JSON
deserializeJson(root,payload);
const char *cmd = root["cmd"];
const int val = root["val"];
if (strstr(cmd,"START")) {
sendMSG("INFO","ESP: Received Start command");
sample_period=val;
Serial.print("sample_period = "); Serial.println(sample_period);
if (mmode==0) {
SampleSlow.attach_ms(sample_period,cardio_action);
} else if (mmode==3 || mmode==4) {
sample_period=max(50,sample_period);
SampleSlow.attach_ms(sample_period,oximeter_action);
} else if (mmode==8) {
SampleSlow.attach_ms(sample_period,impedance_action);
}
} else if (strstr(cmd,"STOP")) {
sendMSG("INFO","ESP: Received Stop command");
SampleSlow.detach();
} else if (strstr(cmd,"MMODE")) {
mmode=val;
if (mmode==3 & mmode==4) {
if (!Sensor.begin()){sendMSG("INFO","ESP: No MAX30102");mmode=-1;
} else {
Sensor.sensorConfiguration(60,SAMPLEAVG_8,MODE_MULTILED,
SAMPLERATE_400,PULSEWIDTH_411,ADCRANGE_16384);
}
} else if (mmode==5) {
if (!Sensor.begin()){sendMSG("INFO","ESP: No MAX30102");mmode=-1;
} else {
sendMSG("INFO","ESP: MAX30102 is up and running");
Sensor.sensorConfiguration(50,SAMPLEAVG_4,MODE_MULTILED,
SAMPLERATE_100,PULSEWIDTH_411,ADCRANGE_16384);
}
} else if (mmode==200) {
recording=1; sendMSG("INFO","ESP: Recording ON");
} else if (mmode==201) {
recording=0; sendMSG("INFO","ESP: Recording OFF");
} else if (mmode==300) { // Start ECG output
ECGticker.attach_ms(5,ecg_action);
sendMSG("INFO","ESP: Starting ECG output");
} else if (mmode==301) { // Stop ECG output
ECGticker.detach();
set_dac(0);
sendMSG("INFO","ESP: Stopping ECG output");
}
} else {
Serial.println("Unknown command");
sendMSG("INFO","ESP: Unknown command received");
}
}
}
}
2.3.4 setup()函数
初始化IO引脚,连接到WLAN,启动WebSocket服务器,初始化SPIFFS文件系统,初始化MAX30102和AD5933的频率范围,并加载校准值和心电图轨迹。
void setup() {
pinMode(D3,INPUT);
pinMode(D4,INPUT);
pinMode(D0,OUTPUT);
digitalWrite(D0,HIGH); // LED on NodeMCU
Serial.begin(115200);
delay(1000);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {delay(500); Serial.print(".");}
Serial.print("\nConnected to ");
Serial.print(ssid);
Serial.print(" with IP address: "); Serial.println(WiFi.localIP());
webSocket.begin();
webSocket.onEvent(webSocketEvent);
if (!SPIFFS.begin()) {
Serial.println("ERROR: no SPIFFS filesystem found");
} else {
server2.begin();
server2.serveStatic("/", SPIFFS, "/medisense.html");
server2.onNotFound(handle_notfound);
Serial.print("SPIFFS file system OK and server started on port 80");
listdir();
}
while (!Sensor.begin()) {Serial.println("No MAX30102"); delay(1000);}
Sensor.sensorConfiguration(60,SAMPLEAVG_8,MODE_MULTILED,SAMPLERATE_200,
PULSEWIDTH_411,ADCRANGE_16384);
freq=1e4; finc=200; npts=511;
// AD5933 frequency range
File f=SPIFFS.open("/calib50kHz.dat","r");
// 50kHz calibration data
if (f) {
float re,im; char line [80];
f.readStringUntil(’\n’).toCharArray(line,80);
sscanf(line," %g %g %g %g",&re,&im,&mag50,&phase50);
f.close();
}
f=SPIFFS.open("/ecg.dat","r");
if (f) {
uint16_t v; char line[20];
for (int k=0;k<200;k++) {
f.readStringUntil(’\n’).toCharArray(line,80);
sscanf(line,"%d",&v);// Serial.println(v);
ecgtrace[k]=v;
}
f.close();
}
pinMode(CS,OUTPUT); digitalWrite(CS,HIGH);
// init SPI - CS
SPI.begin(); SPI.setBitOrder(MSBFIRST);
}
2.3.5 loop()函数
处理HTTP服务器和WebSocket请求,根据
info_available
和
output_ready
变量发送消息和数据,根据
mmode
变量执行不同的操作。
void loop() {
server2.handleClient();
// http server
webSocket.loop();
// websocket
if (info_available==1) {
info_available=0;
webSocket.sendTXT(websock_num,info_buffer,strlen(info_buffer));
}
if (output_ready==1) {
output_ready=0;
if (recording==0) {
send_samples(samples);
} else if (recording==1) {
// record data on local file system
File f=SPIFFS.open("/data.txt","a");
// open for append
if (!f) {
Serial.println("Unable To Open file");
} else {
f.print(samples[0]); f.print("\t"); f.println(samples[1]);
f.close();
}
sendMSG("INFO","ESP: Recording...");
}
}
if (mmode==5) {
// SPO2 rate displayed on status line
char msg[50];
int32_t SPO2,HR;
// values
int8_t SPO2_OK,HR_OK;
// data OK?
Sensor.heartrateAndOxygenSaturation(&SPO2,&SPO2_OK,&HR,&HR_OK);
sprintf(msg,"!ESP: SPO2,HR = %d, %d (%d,%d)",SPO2,HR,SPO2_OK,HR_OK);
sendMSG("INFO",msg);
} else if (mmode==6) {
// AD5933 calibrate
digitalWrite(D0,LOW);
//turns LED on
sendMSG("INFO","!ESP calibrating...");
AD5933_sweep(freq,finc,npts,mode,pga,Ival,Qval);
sendMSG("INFO","ESP: AD5933 Calibration complete");
digitalWrite(D0,HIGH);
//turns LED off
File f=SPIFFS.open("/calib.dat","w");
for (int k=0;k<npts+1;k++) {
f.print(Ival[k]); f.print("\t"); f.print(Qval[k]);
f.print("\t"); f.print(sqrt(Ival[k]*Ival[k]+Qval[k]*Qval[k]));
f.print("\t"); f.println(atan2(Qval[k],Ival[k])*180/3.1415926);
}
f.close();
AD5933_sweep(50e3,0.01,0,mode,pga,Ival,Qval); // calibrate at 50 kHz
f=SPIFFS.open("/calib50kHz.dat","w");
f.print(Ival[0]); f.print("\t"); f.print(Qval[0]);
f.print("\t"); f.print(sqrt(Ival[0]*Ival[0]+Qval[0]*Qval[0]));
f.print("\t"); f.println(atan2(Qval[0],Ival[0])*180/3.1415926);
f.close();
mmode=-1;
} else if (mmode==7) {
// AD5933 frequency sweep
char line[80];
float re,im,ab,ph;
sendMSG("INFO","!ESP sweeping...");
digitalWrite(D0,LOW);
//turns LED on
AD5933_sweep(freq,finc,npts,mode,pga,Ival,Qval);
digitalWrite(D0,HIGH);
//turns LED on
sendMSG("INFO","!ESP: AD5933 Sweep complete");
File f=SPIFFS.open("/calib.dat","r");
for (int k=0;k<npts+1;k++) {
float mag=sqrt(Ival[k]*Ival[k]+Qval[k]*Qval[k]);
float phase=atan2(Qval[k],Ival[k])*180/3.1415926;
f.readStringUntil(’\n’).toCharArray(line,80);
sscanf(line," %g %g %g %g",&re,&im,&ab,&ph);
samples[0]=Rcal*(ab/mag-1.0)*512.0;
// samples[0]=128+128*log10(ab/mag);
samples[1]=256+25.6*(phase-ph);
samples[2]=0;
send_samples(samples); send_samples(samples);
Serial.print(ab/mag); Serial.print("\t"); Serial.print(phase-ph);
Serial.print("\t"); Serial.println(samples[0]);
}
f.close();
mmode=-1;
} else if (mmode==100) {
// Directory listing
mmode=-1;
listdir();
} else if (mmode==101) {
// Remove data file
mmode=-1;
SPIFFS.remove("/data.txt");
sendMSG("INFO","ESP: Removing file /data.txt");
} else if (mmode==202) {
// replay data from local file
Serial.println("Replaying data");
File f=SPIFFS.open("/data.txt","r");
// open for read
if (!f) {
Serial.println("Cannot open file /data.txt");
sendMSG("INFO","ESP: cannot open file /data.txt");
} else {
Serial.print("File size = "); Serial.println(f.size());
char msg[80]; sprintf(msg,"ESP: File size of /data.txt = %d",f.size());
sendMSG("INFO",msg);
char line[80];
samples[2]=0;
while (f.position()<f.size()) {
f.readStringUntil(’\n’).toCharArray(line,80);
sscanf(line," %d %d",&samples[0],&samples[1]);
send_samples(samples);
}
}
f.close();
mmode=-1;
}
yield();
}
2.4 浏览器控制
在medisense.html文件中,我们添加了发送JSON格式的
MMODE
消息的功能,以设置NodeMCU上的
mmode
变量。
<SELECT onchange="sendSpecialCommand(this.value);">
<OPTION value="0">ECG live</OPTION>
<OPTION value="3">Oximeter IR live</OPTION>
<OPTION value="4">Oximeter Red live</OPTION>
<OPTION value="5">Oximeter SPO2 live</OPTION>
<OPTION value="6">AD5933 Calibrate</OPTION>
<OPTION value="7">AD5933 Frequency sweep</OPTION>
<OPTION value="8">AD5933 at 50kHz</OPTION>
<OPTION value="100">List directory</OPTION>
<OPTION value="101">Remove data file</OPTION>
<OPTION value="200">Recording ON</OPTION>
<OPTION value="201">Recording OFF</OPTION>
<OPTION value="202">Replay recording</OPTION>
<OPTION value="300">Start ECG output</OPTION>
<OPTION value="301">Stop ECG output</OPTION>
</SELECT>
function sendSpecialCommand(v) {
toStatus("Special Command " + v + " ");
websock.send(JSON.stringify({ "cmd" : "MMODE", "val" : v }));
}
2.5 系统运行
将代码上传到NodeMCU,将medisense.html文件放入草图的数据子目录,并使用Arduino IDE的Tools菜单中的ESP8266 Sketch Data Upload命令将其上传到NodeMCU。系统运行后,我们可以通过浏览器访问NodeMCU的网页,选择不同的模式进行测量和记录。
2.6 项目思路
- 了解心电图特征与心脏活动的关系。
- 在手指和MAX30102脉冲血氧仪之间放置彩色但大部分透明的薄膜,观察其对原始信号和SPO2值的影响。
- 使用基本电路理论分析Cole模型。
- 了解四端阻抗测量与两端阻抗测量的差异和优势。
- 参考相关资料,为AD5933构建更好的模拟前端。
- 为了减少原始校准数据中的噪声,对校准数据拟合二次多项式,并在后续使用。
- 添加MLX90614温度计,进行无接触式人体温度测量。
- 测量香蕉两端的阻抗,并尝试不同类型的电极。
- 确定一块木头的阻抗,并观察其在木头变湿时的变化。
- 将软件移植到ESP32。
- 将AD5933连接到ESP - 01控制器,使系统便携。
- 将MAX30102和MLX90614连接到ESP - 01控制器。
- 讨论如何将AD8232与不暴露内置ADC到外部引脚的ESP - 01控制器接口,并构建电路。
3. 高速采集与医疗传感技术点分析
3.1 高速采集技术要点
| 技术点 | 说明 |
|---|---|
| I2S模式 | ESP32通过I2S模式实现高速采样,直接将ADC样本传输到内存,无需CPU干预,提高采样速率。 |
| 频率发生器 | 正弦和矩形频率发生器利用ESP32的I2S硬件,通过直接写寄存器的方式配置信号频率和输出比例。 |
| 网页控制 | 在网页中通过JSON编码命令控制采集模式,区分慢速和高速采集,以及频率发生器的开关和参数设置。 |
| 数据处理 | 采集的数据需要进行通道交换和格式转换,以适应显示需求,并通过WebSocket发送到浏览器。 |
3.2 医疗传感技术要点
| 技术点 | 说明 |
|---|---|
| 传感器选择 | 选用AD8232测量心电图,MAX30102测量血氧饱和度,AD5933测量生物阻抗,各传感器通过不同接口与NodeMCU连接。 |
| 代码结构 |
代码分为初始化、回调函数、主函数等部分,通过Ticker定时采集数据,根据不同的
mmode
变量执行不同的操作。
|
| 文件系统 | 使用SPIFFS文件系统存储校准数据、心电图轨迹和测量记录,方便数据的保存和重放。 |
| 浏览器控制 |
在网页中添加选择菜单,通过发送JSON格式的
MMODE
消息控制NodeMCU的不同功能。
|
3.3 技术流程图
graph LR
classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px
A([开始]):::startend --> B{模式选择}:::decision
B -->|高速采集| C(初始化I2S):::process
C --> D(采集数据):::process
D --> E(数据处理):::process
E --> F(发送数据到浏览器):::process
B -->|医疗传感| G(初始化传感器):::process
G --> H(定时采集数据):::process
H --> I{mmode判断}:::decision
I -->|不同模式| J(执行相应操作):::process
J --> K(保存或发送数据):::process
F --> L([结束]):::startend
K --> L
4. 总结与展望
4.1 总结
本文介绍了高速数据采集和医疗传感两个方面的应用。在高速采集部分,利用ESP32的I2S模式实现了每秒超过200,000个样本的采样速率,并通过网页控制实现了慢速和高速采集的切换,以及正弦和矩形频率发生器的配置。在医疗传感部分,使用NodeMCU连接AD8232、MAX30102和AD5933传感器,测量心电图、血氧饱和度和生物阻抗,并通过网页显示测量结果,同时支持数据的保存和重放。
4.2 展望
- 功能扩展 :可以进一步添加更多的传感器,如加速度计、陀螺仪等,实现更多的测量功能。
- 算法优化 :对采集的数据进行更深入的分析和处理,如使用机器学习算法进行心电图的自动诊断、血氧饱和度的精确计算等。
- 系统集成 :将高速采集和医疗传感系统集成到一个更完整的平台中,实现远程监测和数据共享。
- 硬件升级 :考虑使用性能更强大的微控制器或开发板,提高系统的处理能力和稳定性。
4.3 项目实践建议
- 逐步实现 :对于初学者来说,可以先从简单的功能开始实现,如高速采集或单个医疗传感器的测量,逐步熟悉系统的架构和代码。
- 调试和测试 :在开发过程中,要进行充分的调试和测试,确保系统的稳定性和准确性。可以使用示波器、逻辑分析仪等工具辅助调试。
- 参考资料 :参考相关的文档和示例代码,如ESP32、NodeMCU的官方文档,传感器的数据手册等,获取更多的技术支持。
- 团队合作 :如果项目规模较大,可以考虑团队合作,分工完成不同的模块,提高开发效率。
4.4 常见问题及解决方法
| 问题 | 解决方法 |
|---|---|
| 高速采集数据丢失 | 检查I2S配置和DMA缓冲区大小,适当增加缓冲区长度,调整采样速率。 |
| 传感器数据不准确 | 检查传感器的连接和配置,进行校准操作,确保传感器正常工作。 |
| 网页控制无响应 | 检查网络连接和WebSocket通信,确保消息发送和接收正常。 |
| 文件系统操作失败 | 检查SPIFFS文件系统的初始化和权限,确保文件读写操作正确。 |
超级会员免费看
923

被折叠的 条评论
为什么被折叠?



