项目背景
心脏运作可以揭露人体许多极具价值的信息,包括其健康状态、生活方式,甚至是情绪状态及心脏疾病的早期发病等。传统的医疗设备中,监测心跳速率和心脏活动是经由测量电生理讯号与心电图 (ECG) 来完成的,需要将电极连接到身体来量测心脏组织中所引发电气活动的信号。
整体方案
本项目系统上位机使用 LabVIEW VI,可以形象的看到6路心电信号,下位机则使用arduino开发板,连接我们的Olimex ECG/EMG扩展板,Arduino ADC 6路读取模拟输入, LabVIEW上位机以图形方式呈现读数, 并可以保存到文件中。
下位机设计
下位机则使用arduino开发板,arduino ARDUINO UNO 中介绍 ADC(模数转换)的概念。Arduino 板有六个 ADC 通道,如下图所示。其中任何一个或全部都可以用作模拟电压的输入。Arduino Uno ADC具有10 位分辨率(因此整数值来自 (0-(2^10) 1023))。这意味着它将 0 到 5 伏之间的输入电压映射为 0 到 1023 之间的整数值。
SHIELD-EKG-EMG是一个扩展模块, 用于ARDUINO兼容电路板, 如OLIMEXINO-328, OLIMEXINO-STM32和PIC32-PINGUINO及其它. 此子板也可兼容ARDUINO电路板, 包括ARDUINO UNO. 电路板配有安装用连接器. 此产品为EKG/EMG子板, 允许Arduino类的电路板捕捉心电图肌电图信号. 此子板添加了进行生物实验反馈的新途径. 此程序可使用户可监视心跳并记录脉搏, 通过监视和分析肌肉动作来识别姿势动作.
可堆叠子板(经针座)
开放硬件,开放软件项目(用户可访问所有的设计文件)
D4/D9数字输出生成校准信号
精确的微调电位器用于校准
输入连接器, 用于无源或有源电极.
可运行3.3V和5V Arduino电路板
从Olimex ECG/EMG心电图/肌电图扩展板读取Arduino模拟输入,以图形方式呈现读数,并保存到“记录的示例”文件夹中的文件。
将工程中的库文件夹中的文件复制到你自己的Arduino库中。一般为“C:\用户\您的名称\文档\Arduino\库”。
#include <compat/deprecated.h>
#include <FlexiTimer2.h>
//http://www.arduino.cc/playground/Main/FlexiTimer2
// All definitions
#define NUMCHANNELS 6
#define HEADERLEN 4
#define PACKETLEN (NUMCHANNELS * 2 + HEADERLEN + 1)
#define SAMPFREQ 256 // ADC sampling rate 256
#define TIMER2VAL (1024/(SAMPFREQ)) // Set 256Hz sampling frequency
#define LED1 13
#define CAL_SIG 9
// Global constants and variables
volatile unsigned char TXBuf[PACKETLEN]; //The transmission packet
volatile unsigned char TXIndex; //Next byte to write in the transmission packet.
volatile unsigned char CurrentCh; //Current channel being sampled.
volatile unsigned char counter = 0; //Additional divider used to generate CAL_SIG
volatile unsigned int ADC_Value = 0; //ADC current value
//~~~~~~~~~~
// Functions
//~~~~~~~~~~
/****************************************************/
/* Function name: Toggle_LED1 */
/* Parameters */
/* Input : No */
/* Output : No */
/* Action: Switches-over LED1. */
/****************************************************/
void Toggle_LED1(void){
if((digitalRead(LED1))==HIGH){ digitalWrite(LED1,LOW); }
else{ digitalWrite(LED1,HIGH); }
}
/****************************************************/
/* Function name: toggle_GAL_SIG */
/* Parameters */
/* Input : No */
/* Output : No */
/* Action: Switches-over GAL_SIG. */
/****************************************************/
void toggle_GAL_SIG(void){
if(digitalRead(CAL_SIG) == HIGH){ digitalWrite(CAL_SIG, LOW); }
else{ digitalWrite(CAL_SIG, HIGH); }
}
/****************************************************/
/* Function name: setup */
/* Parameters */
/* Input : No */
/* Output : No */
/* Action: Initializes all peripherals */
/****************************************************/
void setup() {
noInterrupts(); // Disable all interrupts before initialization
// LED1
pinMode(LED1, OUTPUT); //Setup LED1 direction
digitalWrite(LED1,LOW); //Setup LED1 state
pinMode(CAL_SIG, OUTPUT);
//Write packet header and footer
TXBuf[0] = 0xa5; //Sync 0
TXBuf[1] = 0x5a; //Sync 1
TXBuf[2] = 2; //Protocol version
TXBuf[3] = 0; //Packet counter
TXBuf[4] = 0x02; //CH1 High Byte
TXBuf[5] = 0x00; //CH1 Low Byte
TXBuf[6] = 0x02; //CH2 High Byte
TXBuf[7] = 0x00; //CH2 Low Byte
TXBuf[8] = 0x02; //CH3 High Byte
TXBuf[9] = 0x00; //CH3 Low Byte
TXBuf[10] = 0x02; //CH4 High Byte
TXBuf[11] = 0x00; //CH4 Low Byte
TXBuf[12] = 0x02; //CH5 High Byte
TXBuf[13] = 0x00; //CH5 Low Byte
TXBuf[14] = 0x02; //CH6 High Byte
TXBuf[15] = 0x00; //CH6 Low Byte
TXBuf[2 * NUMCHANNELS + HEADERLEN] = 0x01; // Switches state
// Timer2
// Timer2 is used to setup the analag channels sampling frequency and packet update.
// Whenever interrupt occures, the current read packet is sent to the PC
// In addition the CAL_SIG is generated as well, so Timer1 is not required in this case!
FlexiTimer2::set(TIMER2VAL, Timer2_Overflow_ISR);
FlexiTimer2::start();
// Serial Port
Serial.begin(57600);
//Set speed to 57600 bps
// MCU sleep mode = idle.
//outb(MCUCR,(inp(MCUCR) | (1<<SE)) & (~(1<<SM0) | ~(1<<SM1) | ~(1<<SM2)));
interrupts(); // Enable all interrupts after initialization has been completed
}
/****************************************************/
/* Function name: Timer2_Overflow_ISR */
/* Parameters */
/* Input : No */
/* Output : No */
/* Action: Determines ADC sampling frequency. */
/****************************************************/
void Timer2_Overflow_ISR()
{
// Toggle LED1 with ADC sampling frequency /2
Toggle_LED1();
//Read the 6 ADC inputs and store current values in Packet
for(CurrentCh=0;CurrentCh<6;CurrentCh++){
ADC_Value = analogRead(CurrentCh);
TXBuf[((2*CurrentCh) + HEADERLEN)] = ((unsigned char)((ADC_Value & 0xFF00) >> 8)); // Write High Byte
TXBuf[((2*CurrentCh) + HEADERLEN + 1)] = ((unsigned char)(ADC_Value & 0x00FF)); // Write Low Byte
}
// Send Packet
for(TXIndex=0;TXIndex<17;TXIndex++){
Serial.write(TXBuf[TXIndex]);
}
// Increment the packet counter
TXBuf[3]++;
// Generate the CAL_SIGnal
counter++; // increment the devider counter
if(counter == 12){ // 250/12/2 = 10.4Hz ->Toggle frequency
counter = 0;
toggle_GAL_SIG(); // Generate CAL signal with frequ ~10Hz
}
}
/****************************************************/
/* Function name: loop */
/* Parameters */
/* Input : No */
/* Output : No */
/* Action: Puts MCU into sleep mode. */
/****************************************************/
void loop() {
__asm__ __volatile__ ("sleep");
}`
上位机设计
设计一个 LabVIEW VI,
VI需要一些时间才能开始工作, 直到 LabVIEW 与 Arduino的串口输出同步。此时“当前状态”将从“初始状态”更改为“已校正偏移”。可以更改保存文件的名称。如果您想加入自己的信号处理算法,可以在“消费者循环”中执行此操作。
最后效果图:
总结
下位机程序已经在文章中了,需要下位机库文件和上位机labview程序的可以在评论区留下邮箱,如果这篇文章帮助了你,请好评三连呀!