在上一篇中,已经使用了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等单片机用来转换为数字信号,再通过单片机的简单计算后就可以得到心率数值,此外还可将脉搏波形通过串口上传到电脑显示波形。
整个传感器一共就三个引脚,电源VCC,地GND和模拟输出S,所以在硬件上只需要连接到MAX32630的一个外部ADC接口就可以了。本来以为蛮简单的一个事情,实际在操作过程中还是遇到了一些“坑”。
从MAX32630FTHR板的硬件原理图上可以看出J1的第2脚是输出3.3V的。从Pulsesensor的参数来看,输入电压可以是3.3~5V的,因此最开始决定就用板子上的3.3V来给心率传感器供电。但是在MBED上编写好相应的程序并下载进板子后,接上心率传感器,却发现传感器的绿灯LED不亮!经过测量后发现3.3V引脚没有电压输出,因此没有驱动传感器发光!
翻看MAX32630FTHR板的原理图,发现此3.3v是由MAX14690电源管理芯片控制输出的。MAX14690芯片是一款很强大的电源管理IC,其除了可以进行锂电池充电管理和监控外,还可以实现5路电源输出,包括2路buck开关电源和3路LDO线性电源。其中J1的第2脚3.3V就是LDO3输出的,而且此电压是可通过I2C总线写入寄存器的方式调节的。所以如果上电后不对MAX14690进行相应寄存器的写入就无法得到相应的输出电压。
幸运的是,在MBED平台上已经提供了MAX14690相应的库文件,被包含在了MAX32630FTHR库文件中,因此在建立工程的时候需要添加进来。并在主文件中添加“MAX32630FTHR pegasus(MAX32630FTHR::VIO_3V3);”。这样在初始化的时候,LDO3就会默认输出3.3v。
解决了第一个问题,MAX32630FTHR板可以正常输出3.3V了。这时需要考虑心率传感器的输出需要接到ADC的哪个通道,而且AD的量程是否合适。根据随板子提供的引脚卡片图5(很奇怪此图和MBED网站上引脚图图4在AD部分不一致),AIN0~AIN3的量程是1.2V,同时AIN0和AIN1引脚还可以测量到5V电压,不过引脚号就变成AIN4和AIN5了。说实话只看这些信息还是让人摸不到头脑应该如何使用,只能再去翻翻芯片的使用手册了。
根据芯片使用手册上的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,这点容易被忽略。
综合上面的信息,如果使用3.3V给心率传感器供电,其输出电压最高可能到3.3V,那就必须使用AIN4或者AIN5来进行AD采样和转化。此外为了更好地对脉搏信号进行测量,发现可以使用板子上的另一个电源输出引脚VBUS(J3的第3脚)来对心率传感器进行供电,因为此引脚是USB电源引脚,电压稳定在5V左右。
在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 = 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 = rate[i+1]; // and drop the oldest IBI value
runningTotal += rate; // 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)是用串口调试软件截图得到的,可以看到三类数据。
此外,还可以使用开源的Processing上位机软件和程序对数据进行可视化显示,见下图(图10)
在MAX32630FTHR板上,还使用了LED1来显示用户的心跳情况,LED1的闪烁完全跟随用户的心跳,最终的实验效果可以看下面的实验视频: