本帖最后由 anning865 于 2017-7-15 09:43 编辑
在上一篇中,已经使用了MBED平台点亮了MAX32630FTHR板自带的LED流水灯,初步摸清了该板的编译下载环境,接下来这篇主要讲MAX32630FTHR板上的ADC使用,以及采用了一款心率传感器pulsesensor来完成整个心率采集和显示功能。
ADC漫谈:在第一篇帖子中谈到,MAX32630这款芯片拥有强大的数据处理和储存能力,但相对应的ADC显得弱鸡了一些。该ADC类型是 Delta-Sigma,分辨率为10bit,最高时钟8MHz(采样率最高7.8ksps),外部输入通道只有4个,不支持DMA功能,这个配置与同类型绝大多数的ARM内核MCU相比都是相形见绌的。但是考虑到该芯片强大的数字接口和超小的封装后就可以发现,这款芯片真的是针对可穿戴设备和便携式设备来做的。在可穿戴设备领域,由于对功耗和体积的要求,绝大部分的传感器接口已经都是数字化了,而相应的模拟部分变得很少,可能最大的应用就是电池电压和电量监控了。所以MAX32630这款芯片可以明显感觉到其数字能力得到了强化而相应的模拟能力却进行了弱化,然而在可穿戴领域这样的模拟功能可以说是够用了,因此我们可以说这款芯片的市场定位是非常清晰而明确的,如果你非要将这款芯片用在数据采集领域,那就真的用错了地方。
实验背景介绍:这次使用我比较熟悉而且现成可用的心率传感器Pulsesensor来进行实验。
PulseSensor是一款用于脉搏心率测量、脉搏波形测量和HRV分析的光电反射式模拟传感器。将其佩戴于手指、耳垂等处,通过导线连接可将采集到的模拟信号传输给类似Arduino等单片机用来转换为数字信号,再通过单片机的简单计算后就可以得到心率数值,此外还可将脉搏波形通过串口上传到电脑显示波形。
图1:pulsesensor心率传感器
传感器的具体参数为:
电路板直径:16mm
电路板厚度:1.2mm
LED峰值波长:515nm
供电电压:3.3~5v
检测信号类型:光反射信号(PPG)
输出信号类型:模拟信号
信号放大倍数:330倍
输出信号大小:0~VCC
电流大小:~4ma(5v下)
整个传感器一共就三个引脚,电源VCC,地GND和模拟输出S,所以在硬件上只需要连接到MAX32630的一个外部ADC接口就可以了。本来以为蛮简单的一个事情,实际在操作过程中还是遇到了一些“坑”。
实验过程:
坑1:MAX14690的电源输出
从MAX32630FTHR板的硬件原理图上可以看出J1的第2脚是输出3.3V的。从Pulsesensor的参数来看,输入电压可以是3.3~5V的,因此最开始决定就用板子上的3.3V来给心率传感器供电。但是在MBED上编写好相应的程序并下载进板子后,接上心率传感器,却发现传感器的绿灯LED不亮!经过测量后发现3.3V引脚没有电压输出,因此没有驱动传感器发光!
图2:MAX32630FTHR板引脚图(MBED平台)
翻看MAX32630FTHR板的原理图,发现此3.3v是由MAX14690电源管理芯片控制输出的。MAX14690芯片是一款很强大的电源管理IC,其除了可以进行锂电池充电管理和监控外,还可以实现5路电源输出,包括2路buck开关电源和3路LDO线性电源。其中J1的第2脚3.3V就是LDO3输出的,而且此电压是可通过I2C总线写入寄存器的方式调节的。所以如果上电后不对MAX14690进行相应寄存器的写入就无法得到相应的输出电压。
图3:MAX32630FTHR板原理图
幸运的是,在MBED平台上已经提供了MAX14690相应的库文件,被包含在了MAX32630FTHR库文件中,因此在建立工程的时候需要添加进来。并在主文件中添加“MAX32630FTHR pegasus(MAX32630FTHR::VIO_3V3);”。这样在初始化的时候,LDO3就会默认输出3.3v。
图4:MBED程序框架图
坑2:ADC的量程设置
解决了第一个问题,MAX32630FTHR板可以正常输出3.3V了。这时需要考虑心率传感器的输出需要接到ADC的哪个通道,而且AD的量程是否合适。根据随板子提供的引脚卡片图5(很奇怪此图和MBED网站上引脚图图4在AD部分不一致),AIN0~AIN3的量程是1.2V,同时AIN0和AIN1引脚还可以测量到5V电压,不过引脚号就变成AIN4和AIN5了。说实话只看这些信息还是让人摸不到头脑应该如何使用,只能再去翻翻芯片的使用手册了。
图5:MAX32630FTHR板引脚图(随板附带卡片)
根据芯片使用手册上的ADC内部结构图(图6)和AD使用说明(图7)可以看出来,该芯片只有四个外部ADC通道0~3,如果在不外接基准源的情况下,芯片ADC使用内部基准源1.2V,所以量程是到1.2V。但是通道4和5是将通道0和1的电压降低为1/5后再输入ADC进行测量的,所以在通道0和1可以承受5V电压的情况下(根据图8,AIN0和1可以承受5.5V),AIN4和AIN5的量程相当于扩大了,如果要测量0~5V范围内的信号需要在程序中使用AIN4或者AIN5来进行编程。此外还有一点要注意,根据图7的计算公式可以看到,AIN0~3的量程可以是0~1.2V(adc_scale==1)或者是0~0.6V(adc_scale==0 );而AIN4和5的量程是0~6V,虽然引脚AIN0最大只能承受5.5V,但是这时AD的转化结果1023代表6V,这点容易被忽略。
图6:ADC内部结构图
图7:AD使用说明
图8:ADC极限电气参数
综合上面的信息,如果使用3.3V给心率传感器供电,其输出电压最高可能到3.3V,那就必须使用AIN4或者AIN5来进行AD采样和转化。此外为了更好地对脉搏信号进行测量,发现可以使用板子上的另一个电源输出引脚VBUS(J3的第3脚)来对心率传感器进行供电,因为此引脚是USB电源引脚,电压稳定在5V左右。
最后的硬件连接方式为:
pulsesensor<->MAX32630FTHR板
S<->AIN4
VCC<->VBUS
GND<->GND
实验结果:
在MBED平台上移植并修改心率采集和计算的程序,并将脉搏波形通过串口以115200速率上传到PC端的Processing软件并显示。
下位机程序如下:
- #include "mbed.h"
- #include "max32630fthr.h"
- #define false 0
- #define true 1
- MAX32630FTHR pegasus(MAX32630FTHR::VIO_3V3);
- Ticker tick;
- DigitalOut led1(LED1);//led blinking with heart beat
- AnalogIn pulsepin(AIN_4);//analog input pin
- Serial pc(USBTX, USBRX);//USB TO SERIAL
- // these variables are volatile because they are used during the shorterrupt service routine!
- volatile short BPM; // used to hold the pulse rate
- volatile short Signal; // holds the incoming raw data
- volatile short IBI = 600; // holds the time between beats, must be seeded!
- volatile char Pulse = false; // true when pulse wave is high, false when it's low
- volatile char QS = false; // becomes true when Arduoino finds a beat.
- volatile short rate[10]; // array to hold last ten IBI values
- volatile unsigned long sampleCounter = 0; // used to determine pulse timing
- volatile unsigned long lastBeatTime = 0; // used to find IBI
- volatile short P =426; // used to find peak in pulse wave, seeded
- volatile short T = 426; // used to find trough in pulse wave, seeded
- volatile short thresh = 426; // used to find instant moment of heart beat, seeded
- volatile short amp = 100; // used to hold amplitude of pulse waveform, seeded
- volatile char firstBeat = true; // used to seed rate array so we startup with reasonable BPM
- volatile char secondBeat = false; // used to seed rate array so we startup with reasonable BPM
- //void ledFadeToBeat()
- //{
- // fadeRate -= 15; // set LED fade value
- // fadeRate = constrain(fadeRate,0,255); // keep LED fade value from going shorto negative numbers!
- // analogWrite(fadePin,fadeRate); // fade LED
- //}
- void timer_isr(void)
- {
- //cli(); // disable shorterrupts while we do this
-
- Signal = pulsepin.read_u16()>>6; // read the Pulse Sensor
- sampleCounter += 2; // keep track of the time in mS with this variable
- short N = sampleCounter - lastBeatTime; // monitor the time since the last beat to avoid noise
- // find the peak and trough of the pulse wave
- if(Signal < thresh && N > (IBI/5)*3) { // avoid dichrotic noise by waiting 3/5 of last IBI
- if (Signal < T) { // T is the trough
- T = Signal; // keep track of lowest poshort in pulse wave
- }
- }
- if(Signal > thresh && Signal > P) { // thresh condition helps avoid noise
- P = Signal; // P is the peak
- } // keep track of highest poshort in pulse wave
- // NOW IT'S TIME TO LOOK FOR THE HEART BEAT
- // signal surges up in value every time there is a pulse
- if (N > 250) { // avoid high frequency noise
- if ( (Signal > thresh) && (Pulse == false) && (N > (IBI/5)*3) ) {
- Pulse = true; // set the Pulse flag when we think there is a pulse
- led1=1; // turn on pin 13 LED
- IBI = sampleCounter - lastBeatTime; // measure time between beats in mS
- lastBeatTime = sampleCounter; // keep track of time for next pulse
- if(secondBeat) { // if this is the second beat, if secondBeat == TRUE
- secondBeat = false; // clear secondBeat flag
- for(short i=0; i<=9; i++) { // seed the running total to get a realisitic BPM at startup
- rate[i] = IBI;
- }
- }
- if(firstBeat) { // if it's the first time we found a beat, if firstBeat == TRUE
- firstBeat = false; // clear firstBeat flag
- secondBeat = true; // set the second beat flag
- //sei(); // enable shorterrupts again
- return; // IBI value is unreliable so discard it
- }
- // keep a running total of the last 10 IBI values
- unsigned short runningTotal = 0; // clear the runningTotal variable
- for(short i=0; i<=8; i++) { // shift data in the rate array
- rate[i] = rate[i+1]; // and drop the oldest IBI value
- runningTotal += rate[i]; // add up the 9 oldest IBI values
- }
- rate[9] = IBI; // add the latest IBI to the rate array
- runningTotal += rate[9]; // add the latest IBI to runningTotal
- runningTotal /= 10; // average the last 10 IBI values
- BPM = 60000/runningTotal; // how many beats can fit shorto a minute? that's BPM!
- QS = true; // set Quantified Self flag
- // QS FLAG IS NOT CLEARED INSIDE THIS ISR
- }
- }
- if (Signal < thresh && Pulse == true) { // when the values are going down, the beat is over
- led1=0; // turn off pin 13 LED
- Pulse = false; // reset the Pulse flag so we can do it again
- amp = P - T; // get amplitude of the pulse wave
- thresh = amp/2 + T; // set thresh at 50% of the amplitude
- P = thresh; // reset these for next time
- T = thresh;
- }
- if (N > 2500) { // if 2.5 seconds go by without a beat
- thresh = 426; // set thresh default
- P = 426; // set P default
- T = 426; // set T default
- lastBeatTime = sampleCounter; // bring the lastBeatTime up to date
- firstBeat = true; // set these to avoid noise
- secondBeat = false; // when we get the heartbeat back
- }
- //sei(); // enable shorterrupts when youre done!
- }
- void sendDataToProcessing(char symbol, short data )
- {
- pc.putc(symbol); // symbol prefix tells Processing what type of data is coming
- pc.printf("%d\r\n", data); // the data to send culminating in a carriage return
- }
- int main()
- {
- pc.baud(115200); // we agree to talk fast!
- tick.attach_us(&timer_isr,2000); // sets up to read Pulse Sensor signal every 2mS
- while(1) {
- sendDataToProcessing('S', Signal); // send Processing the raw Pulse Sensor data
- if (QS == true) { // Quantified Self flag is true when arduino finds a heartbeat
- //fadeRate = 255; // Set 'fadeRate' Variable to 255 to fade LED with pulse
- sendDataToProcessing('B',BPM); // send heart rate with a 'B' prefix
- sendDataToProcessing('Q',IBI); // send time between beats with a 'Q' prefix
- QS = false; // reset the Quantified Self flag for next time
- }
- //ledFadeToBeat();
- wait(0.02); // take a break
- }
- }
复制代码
程序的主要思想:pulsesensor传感器根据返回的光强,输出脉搏的电压波形曲线。下位机采样电压曲线,并数字化后发送到上位机显示,同时下位机随时计算相邻两个脉搏波的峰值点的时间差并滤波,得到两次心跳之间的时间,即IBI数值;心率值BPM=60/IBI,就可以通过计算后得出。
通过串口输出数据主要有三类:以“S”为前缀的,表示脉搏波电压数据(脉象图的数值化表示);以“B”为前缀的,表示BPM数值(心率值);以“Q”为前缀的,表示IBI数值(相邻两个心跳之间的时间)。下图(图9)是用串口调试软件截图得到的,可以看到三类数据。
图9:脉搏数据通过串口显示
此外,还可以使用开源的Processing上位机软件和程序对数据进行可视化显示,见下图(图10)
图10:processing显示的脉搏波形和心率数值
在MAX32630FTHR板上,还使用了LED1来显示用户的心跳情况,LED1的闪烁完全跟随用户的心跳,最终的实验效果可以看下面的实验视频: