项目背景
心脏运作可以揭露人体许多极具价值的信息,包括其健康状态、生活方式,甚至是情绪状态及心脏疾病的早期发病等。传统的医疗设备中,监测心跳速率和心脏活动是经由测量电生理讯号与心电图 (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程序的可以在评论区留下邮箱,如果这篇文章帮助了你,请好评三连呀!