赞
踩
主要介绍的是arduino中SparkFun_MAX3010x_Sensor_Library这个库。
SparkFun_MAX3010x_Sensor_Library链接地址
这个库可以在arduino中直接搜索下载。
主要分析的是SpO2这个部分。examples中是示例,src中是源码。
如果对max30102的初始化过程不清楚,可以看下面这篇文章。
#include <Wire.h>
#include "MAX30105.h"
#include "spo2_algorithm.h"
虽然这里标注的是#include "MAX30105.h"
,但是MAX30102也可以使用。
#include <Wire.h>
其实是不用引用的,因为在#include "MAX30105.h"
中已经引用过了,这里引用可能是为了可读性。
MAX30105 particleSensor;
这里没什么好说的,就是创建了一个MAX30105的对象。
if (!particleSensor.begin(Wire, I2C_SPEED_FAST)) //Use default I2C port, 400kHz speed
{
Serial.println(F("MAX30105 was not found. Please check wiring/power."));
while (1);
}
这里的F指的是把字符串存放在flash闪存中,不得不说这个处理还是很细节的。
代码的核心部分是particleSensor.begin(Wire, I2C_SPEED_FAST)
。
I2C_SPEED_FAST
:
代表的是iic的速度,下面是它的定义。
#define I2C_SPEED_STANDARD 100000
#define I2C_SPEED_FAST 400000
因为现在购买的一般是黑色的MAX30102的模块,这种模块是MAX30102和电容电阻都在一个面,而且面积小,手指在按上去的时候很容易接触到信号线的触点,所以会干扰到iic信号。一般的解决方法是做绝缘,或者把信号线速率降低。
所以这里更建议设置为I2C_SPEED_STANDARD
。
Wire
:
这里其实就是传递一个TowWire的引用。
再看一下这个函数的原型。
在H文件中的引用是这样的
boolean begin(TwoWire &wirePort = Wire, uint32_t i2cSpeed = I2C_SPEED_STANDARD, uint8_t i2caddr = MAX30105_ADDRESS);
这样写是代表这三个选项都是可选的,也就是说,你不传递任何值,也是可以正确初始化的。
看一下三个参数
也就是说如果什么都不传递,i2cSpeed的速率默认是低速的,所以在初始化时,传递了Wire和I2C_SPEED_FAST,把速度设置为高速。
为啥不直接传I2C_SPEED_FAST,还要传个Wire呢?因为i2cSpeed是第二的参数,所以想要赋值第二个参数,你先得赋值第一个参数。
所以如果想让iic运行在低速时,begin是不用传递参数的。
这里的TwoWire只是Wire类的别名,功能上是完全等价的,为什么要用TwoWire呢,其实就是告诉你,如果你的开发板上有两个iic,而你恰好想用第二条,你就可以传递一个Wire1。
begin在C文件中的实现如下
boolean MAX30105::begin(TwoWire &wirePort, uint32_t i2cSpeed, uint8_t i2caddr) { _i2cPort = &wirePort; //Grab which port the user wants us to use _i2cPort->begin(); _i2cPort->setClock(i2cSpeed); _i2caddr = i2caddr; // Step 1: Initial Communication and Verification // Check that a MAX30105 is connected if (readPartID() != MAX_30105_EXPECTEDPARTID) { // Error -- Part ID read from MAX30105 does not match expected part ID. // This may mean there is a physical connectivity problem (broken wire, unpowered, etc). return false; } // Populate revision ID readRevisionID(); return true; }
前四行代码功能就是开启iic总线
_i2cPort = &wirePort;
在赋值TowWire类_i2cPort->begin();
在初始化iic总线_i2cPort->setClock(i2cSpeed);
在设置iic速率_i2caddr = i2caddr;
在设置iic地址。进一步深入发现readPartID()
和readRevisionID
其核心是调用了readRegister8
,而读取和写入一般都是成对出现的,写入的函数是writeRegister8
uint8_t MAX30105::readPartID() {
return readRegister8(_i2caddr, MAX30105_PARTID);
}
MAX30105_PARTID:值为0xFF。作用是读取部件id。部件id固定是0x15,所以可以推出来MAX_30105_EXPECTEDPARTID的值是0x15。
void MAX30105::readRevisionID() {
revisionID = readRegister8(_i2caddr, MAX30105_REVISIONID);
}
MAX30105_REVISIONID:值为0xFE。作用是读取版本号。
来看看读写函数
这是一个用iic总线读取8位数据的函数。
看一下函数原型
uint8_t MAX30105::readRegister8(uint8_t address, uint8_t reg) {
_i2cPort->beginTransmission(address);
_i2cPort->write(reg);
_i2cPort->endTransmission(false);
_i2cPort->requestFrom((uint8_t)address, (uint8_t)1); // Request 1 byte
if (_i2cPort->available())
{
return(_i2cPort->read());
}
return (0); //Fail
}
_i2cPort->beginTransmission(address);
的作用是设置传输设备的地址
_i2cPort->write(reg);
的作用是设置要传输的数据,这里代表的就是寄存器地址
_i2cPort->endTransmission(false);
的作用是结束iic传输,并重新发送一个开始信号,释放资源,如果是传输true的话,会返回一个状态码,指示是否传输成功
状态码 | 表示 |
---|---|
0 | 表示传输成功 |
1 | 表示数据量超过了传送缓存的容纳限制 |
2 | 表示在传送地址时收到了NACK(非确认信号) |
3 | 表示在传送数据时收到了NACK |
4 | 表示其他错误 |
_i2cPort->requestFrom((uint8_t)address, (uint8_t)1);
的作用是读取一个字节
_i2cPort->available()
的作用是判断是否有可用的数据供读取
_i2cPort->read()
的作用是把这个数据读取出来
如果读取失败了,就返回0,这也是在使用这个库时最好iic用低速率的原因。当iic总线收到干扰时(没有做绝缘,手指触到到之后干扰到iic总线信号传输,因为人体相当于一个几十到几百uF的电容)就会直接返回0,这时并不能确定时总线受到干扰,信号没回来,还是传输回来的就是0。
这时候看到的数据就是不稳定的。
介绍完了读函数,写函数就没什么好说的了。看一下源码。
void MAX30105::writeRegister8(uint8_t address, uint8_t reg, uint8_t value) {
_i2cPort->beginTransmission(address);
_i2cPort->write(reg);
_i2cPort->write(value);
_i2cPort->endTransmission();
}
加了一个value,就是需要在reg这个地址的寄存内写入的数据。
不同的是_i2cPort->endTransmission();
里面没有传递参数,这代表着,发送停止信号,结束iic传输。在读函数中,有参数是因为还要执行一遍读取操作,而写函数不需要再读取了。
Serial.println(F("Attach sensor to finger with rubber band. Press any key to start conversion"));
while (Serial.available() == 0) ; //wait until user presses a key
Serial.read();
这一段没什么好说的,就是初始化成功了,然后让你随便输入个东西,然后继续执行后面的内容
实际开发中用不到这一块代码
byte ledBrightness = 60; //Options: 0=Off to 255=50mA
byte sampleAverage = 4; //Options: 1, 2, 4, 8, 16, 32
byte ledMode = 2; //Options: 1 = Red only, 2 = Red + IR, 3 = Red + IR + Green
byte sampleRate = 100; //Options: 50, 100, 200, 400, 800, 1000, 1600, 3200
int pulseWidth = 411; //Options: 69, 118, 215, 411
int adcRange = 4096; //Options: 2048, 4096, 8192, 16384
particleSensor.setup(ledBrightness, sampleAverage, ledMode, sampleRate, pulseWidth, adcRange); //Configure sensor with these settings
这里开始设置MAX3010X的各种参数,对其进行初始化。直接看函数原型吧
在H文件中的定义如下
void setup(byte powerLevel = 0x1F, byte sampleAverage = 4, byte ledMode = 3, int sampleRate = 400, int pulseWidth = 411, int adcRange = 4096);
也都是可选参数
CPP文件中的实现如下
void MAX30105::setup(byte powerLevel, byte sampleAverage, byte ledMode, int sampleRate, int pulseWidth, int adcRange) { softReset(); //Reset all configuration, threshold, and data registers to POR values //FIFO Configuration //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- //The chip will average multiple samples of same type together if you wish if (sampleAverage == 1) setFIFOAverage(MAX30105_SAMPLEAVG_1); //No averaging per FIFO record else if (sampleAverage == 2) setFIFOAverage(MAX30105_SAMPLEAVG_2); else if (sampleAverage == 4) setFIFOAverage(MAX30105_SAMPLEAVG_4); else if (sampleAverage == 8) setFIFOAverage(MAX30105_SAMPLEAVG_8); else if (sampleAverage == 16) setFIFOAverage(MAX30105_SAMPLEAVG_16); else if (sampleAverage == 32) setFIFOAverage(MAX30105_SAMPLEAVG_32); else setFIFOAverage(MAX30105_SAMPLEAVG_4); //setFIFOAlmostFull(2); //Set to 30 samples to trigger an 'Almost Full' interrupt enableFIFORollover(); //Allow FIFO to wrap/roll over //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- //Mode Configuration //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- if (ledMode == 3) setLEDMode(MAX30105_MODE_MULTILED); //Watch all three LED channels else if (ledMode == 2) setLEDMode(MAX30105_MODE_REDIRONLY); //Red and IR else setLEDMode(MAX30105_MODE_REDONLY); //Red only activeLEDs = ledMode; //Used to control how many bytes to read from FIFO buffer //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- //Particle Sensing Configuration //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- if(adcRange < 4096) setADCRange(MAX30105_ADCRANGE_2048); //7.81pA per LSB else if(adcRange < 8192) setADCRange(MAX30105_ADCRANGE_4096); //15.63pA per LSB else if(adcRange < 16384) setADCRange(MAX30105_ADCRANGE_8192); //31.25pA per LSB else if(adcRange == 16384) setADCRange(MAX30105_ADCRANGE_16384); //62.5pA per LSB else setADCRange(MAX30105_ADCRANGE_2048); if (sampleRate < 100) setSampleRate(MAX30105_SAMPLERATE_50); //Take 50 samples per second else if (sampleRate < 200) setSampleRate(MAX30105_SAMPLERATE_100); else if (sampleRate < 400) setSampleRate(MAX30105_SAMPLERATE_200); else if (sampleRate < 800) setSampleRate(MAX30105_SAMPLERATE_400); else if (sampleRate < 1000) setSampleRate(MAX30105_SAMPLERATE_800); else if (sampleRate < 1600) setSampleRate(MAX30105_SAMPLERATE_1000); else if (sampleRate < 3200) setSampleRate(MAX30105_SAMPLERATE_1600); else if (sampleRate == 3200) setSampleRate(MAX30105_SAMPLERATE_3200); else setSampleRate(MAX30105_SAMPLERATE_50); //The longer the pulse width the longer range of detection you'll have //At 69us and 0.4mA it's about 2 inches //At 411us and 0.4mA it's about 6 inches if (pulseWidth < 118) setPulseWidth(MAX30105_PULSEWIDTH_69); //Page 26, Gets us 15 bit resolution else if (pulseWidth < 215) setPulseWidth(MAX30105_PULSEWIDTH_118); //16 bit resolution else if (pulseWidth < 411) setPulseWidth(MAX30105_PULSEWIDTH_215); //17 bit resolution else if (pulseWidth == 411) setPulseWidth(MAX30105_PULSEWIDTH_411); //18 bit resolution else setPulseWidth(MAX30105_PULSEWIDTH_69); //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- //LED Pulse Amplitude Configuration //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- //Default is 0x1F which gets us 6.4mA //powerLevel = 0x02, 0.4mA - Presence detection of ~4 inch //powerLevel = 0x1F, 6.4mA - Presence detection of ~8 inch //powerLevel = 0x7F, 25.4mA - Presence detection of ~8 inch //powerLevel = 0xFF, 50.0mA - Presence detection of ~12 inch setPulseAmplitudeRed(powerLevel); setPulseAmplitudeIR(powerLevel); setPulseAmplitudeGreen(powerLevel); setPulseAmplitudeProximity(powerLevel); //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- //Multi-LED Mode Configuration, Enable the reading of the three LEDs //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- enableSlot(1, SLOT_RED_LED); if (ledMode > 1) enableSlot(2, SLOT_IR_LED); if (ledMode > 2) enableSlot(3, SLOT_GREEN_LED); //enableSlot(1, SLOT_RED_PILOT); //enableSlot(2, SLOT_IR_PILOT); //enableSlot(3, SLOT_GREEN_PILOT); //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- clearFIFO(); //Reset the FIFO before we begin checking the sensor }
看起来挺长的,但是都是类似与枚举的if-else,不是很复杂。
softReset(); //Reset all configuration, threshold, and data registers to POR values
函数代码如下
void MAX30105::softReset(void) {
bitMask(MAX30105_MODECONFIG, MAX30105_RESET_MASK, MAX30105_RESET);
// Poll for bit to clear, reset is then complete
// Timeout after 100ms
unsigned long startTime = millis();
while (millis() - startTime < 100)
{
uint8_t response = readRegister8(_i2caddr, MAX30105_MODECONFIG);
if ((response & MAX30105_RESET) == 0) break; //We're done!
delay(1); //Let's not over burden the I2C bus
}
}
这个bitMask
函数还是挺有意思的,之前做这类操作的时候没有想过用这种方法。
后面的部分就是读取了esp32启动以来的毫秒数,然后做循环,判断这个位是不是设置成功了。
就是等待100毫秒,看复位成功了没有。
话说这个操作方式确实比较精准,误差不会太大。
这个函数是一个位操作的函数,控制一位或者几位的bit的赋值。
代码如下:
void MAX30105::bitMask(uint8_t reg, uint8_t mask, uint8_t thing)
{
// Grab current register context
uint8_t originalContents = readRegister8(_i2caddr, reg);
// Zero-out the portions of the register we're interested in
originalContents = originalContents & mask;
// Change contents
writeRegister8(_i2caddr, reg, originalContents | thing);
}
操作流程如下:
这个操作一开始看,感觉有点傻,为啥不直接传输thing,然后在里面进行取反操作,何必多次一举,看了其他调用这个函数的代码,我大概搞清楚了。
这个操作不是单纯的对一位bit进行操作,而是对多位进行操作,打个比方
现在有第0位到第2位的bit是代表一个模式设置,这个模式有101,110,111三种。那么就可以传输一个二进制是1111 1000
的mask,那与读取出来的值进行与操作之后,就把第0位到第2位的数据清零了,这时thing再传递三种模式的其中一种。这样就可以做到任意位数的赋值,这种方式还挺巧妙的。
但是也能感觉到作者在写mark的数据定义的时候挺烦的,一会2进制赋值,一会16进制赋值。
FIFO就可以想象成一个队列,先进先出,用于缓存数据的。
代码如下
if (sampleAverage == 1) setFIFOAverage(MAX30105_SAMPLEAVG_1); //No averaging per FIFO record
else if (sampleAverage == 2) setFIFOAverage(MAX30105_SAMPLEAVG_2);
else if (sampleAverage == 4) setFIFOAverage(MAX30105_SAMPLEAVG_4);
else if (sampleAverage == 8) setFIFOAverage(MAX30105_SAMPLEAVG_8);
else if (sampleAverage == 16) setFIFOAverage(MAX30105_SAMPLEAVG_16);
else if (sampleAverage == 32) setFIFOAverage(MAX30105_SAMPLEAVG_32);
else setFIFOAverage(MAX30105_SAMPLEAVG_4);
setFIFOAverage
函数中只有bitMask(MAX30105_FIFOCONFIG, MAX30105_SAMPLEAVG_MASK, numberOfSamples);
这一段代码
定义的代码如下
static const uint8_t MAX30105_FIFOCONFIG = 0x08;
static const uint8_t MAX30105_SAMPLEAVG_MASK = (byte)~0b11100000;
static const uint8_t MAX30105_SAMPLEAVG_1 = 0x00;
static const uint8_t MAX30105_SAMPLEAVG_2 = 0x20;
static const uint8_t MAX30105_SAMPLEAVG_4 = 0x40;
static const uint8_t MAX30105_SAMPLEAVG_8 = 0x60;
static const uint8_t MAX30105_SAMPLEAVG_16 = 0x80;
static const uint8_t MAX30105_SAMPLEAVG_32 = 0xA0;
具体表达了什么意思可以看前言里我写的文章里的FIFO配置
章节,我在这里也做了部分引用
地址 功能 B7 B6 B5 B4 B3 B2 B1 B0 R/W 0x08 FIFO配置 SMP_AVE[2] SMP_AVE[1] SMP_AVE[0] FIFO_ROL LOVER_EN FIFO_A_FULL[3] FIFO_A_FULL[2] FIFO_A_FULL[1] FIFO_A_FULL[0] RW SMP_AVE:平均值,为了减少数据吞吐量,通过设置这个寄存器,相邻的样本(在每个单独的通道中)可以在芯片上进行平均和抽取。
SMP_AVE 平均量 000 1(不平均) 001 2 010 4 011 8 100 16 101 32 110 32 111 32 FIFO_ROL LOVER_EN:FIFO被填满之后的控制。如果是0,在你读取之前都不会更新,如果是1,会更新覆盖之前的数据
这其实也是FIFO的设置,当设置为1时如果FIFO中的数据满了,那么就会覆盖老的数据,设置为0则不会覆盖。
enableFIFORollover(); //Allow FIFO to wrap/roll over
内部也就是调用了bitMask,代码如下
void MAX30105::enableFIFORollover(void) {
bitMask(MAX30105_FIFOCONFIG, MAX30105_ROLLOVER_MASK, MAX30105_ROLLOVER_ENABLE);
}
同上,可以看前言里的MAX30102分析。
可以看出来,设置的过程是按照功能划分的,更新使能和FIFO配置都是一个寄存器里的内容,却分成了两个部分来写。可读性比较好,但是执行效率就不怎么高了。
设置红光和红外光,三种模式,同上,可以看前言里的MAX30102分析。
if (ledMode == 3) setLEDMode(MAX30105_MODE_MULTILED); //Watch all three LED channels
else if (ledMode == 2) setLEDMode(MAX30105_MODE_REDIRONLY); //Red and IR
else setLEDMode(MAX30105_MODE_REDONLY); //Red only
activeLEDs = ledMode; //Used to control how many bytes to read from FIFO buffer
设置ADC的采样范围,具体参数,可以看前言里的MAX30102分析。
if(adcRange < 4096) setADCRange(MAX30105_ADCRANGE_2048); //7.81pA per LSB
else if(adcRange < 8192) setADCRange(MAX30105_ADCRANGE_4096); //15.63pA per LSB
else if(adcRange < 16384) setADCRange(MAX30105_ADCRANGE_8192); //31.25pA per LSB
else if(adcRange == 16384) setADCRange(MAX30105_ADCRANGE_16384); //62.5pA per LSB
else setADCRange(MAX30105_ADCRANGE_2048);
采样率和脉冲宽度是相关的,因为采样率设置了脉冲宽度时间的上限。如果用户选择的采样率对于所选LED_PW设置来说太高,则将尽可能高的采样率编程到寄存器中。具体参数,可以看前言里的MAX30102分析。
if (sampleRate < 100) setSampleRate(MAX30105_SAMPLERATE_50); //Take 50 samples per second
else if (sampleRate < 200) setSampleRate(MAX30105_SAMPLERATE_100);
else if (sampleRate < 400) setSampleRate(MAX30105_SAMPLERATE_200);
else if (sampleRate < 800) setSampleRate(MAX30105_SAMPLERATE_400);
else if (sampleRate < 1000) setSampleRate(MAX30105_SAMPLERATE_800);
else if (sampleRate < 1600) setSampleRate(MAX30105_SAMPLERATE_1000);
else if (sampleRate < 3200) setSampleRate(MAX30105_SAMPLERATE_1600);
else if (sampleRate == 3200) setSampleRate(MAX30105_SAMPLERATE_3200);
else setSampleRate(MAX30105_SAMPLERATE_50);
这些位设置LED脉冲宽度(IR和Red具有相同的脉冲宽度),因此间接设置每个样本中ADC的积分时间。ADC分辨率与积分时间直接相关。具体参数,可以看前言里的MAX30102分析。
if (pulseWidth < 118) setPulseWidth(MAX30105_PULSEWIDTH_69); //Page 26, Gets us 15 bit resolution
else if (pulseWidth < 215) setPulseWidth(MAX30105_PULSEWIDTH_118); //16 bit resolution
else if (pulseWidth < 411) setPulseWidth(MAX30105_PULSEWIDTH_215); //17 bit resolution
else if (pulseWidth == 411) setPulseWidth(MAX30105_PULSEWIDTH_411); //18 bit resolution
else setPulseWidth(MAX30105_PULSEWIDTH_69);
设置脉冲宽度,具体参数,可以看前言里的MAX30102分析。
setPulseAmplitudeRed(powerLevel);
setPulseAmplitudeIR(powerLevel);
这两个设置在MAX30102中是无效的,因为数据手册中这个地址的寄存器并没有分配功能,但是因为MAX30105是向下兼容的,所以MAX30102使用也不会出问题。
setPulseAmplitudeGreen(powerLevel);
setPulseAmplitudeProximity(powerLevel);
enableSlot(1, SLOT_RED_LED);
if (ledMode > 1) enableSlot(2, SLOT_IR_LED);
if (ledMode > 2) enableSlot(3, SLOT_GREEN_LED);
void MAX30105::enableSlot(uint8_t slotNumber, uint8_t device) { uint8_t originalContents; switch (slotNumber) { case (1): bitMask(MAX30105_MULTILEDCONFIG1, MAX30105_SLOT1_MASK, device); break; case (2): bitMask(MAX30105_MULTILEDCONFIG1, MAX30105_SLOT2_MASK, device << 4); break; case (3): bitMask(MAX30105_MULTILEDCONFIG2, MAX30105_SLOT3_MASK, device); break; case (4): bitMask(MAX30105_MULTILEDCONFIG2, MAX30105_SLOT4_MASK, device << 4); break; default: //Shouldn't be here! break; } }
如果是MAX30102最大只可以设置到2。
具体参数,可以看前言里的MAX30102分析。
clearFIFO(); //Reset the FIFO before we begin checking the sensor
先看一下函数内部都做了什么吧
这样看还是比较混乱,可以对照这数据手册把信息采集的流程列一下,图片就不放了,markdown放图片不方便移植
所以LED设置是在设置红外和红灯
LED采样和ADC设置都是在设置模拟信号输入方式
FIFO和更新是在设置数据在数据寄存器的存储方式和规则
bufferLength = 100; //buffer length of 100 stores 4 seconds of samples running at 25sps
//read the first 100 samples, and determine the signal range
for (byte i = 0 ; i < bufferLength ; i++)
{
while (particleSensor.available() == false) //do we have new data?
particleSensor.check(); //Check the sensor for new data
redBuffer[i] = particleSensor.getRed();
irBuffer[i] = particleSensor.getIR();
particleSensor.nextSample(); //We're finished with this sample so move to next sample
Serial.print(F("red="));
Serial.print(redBuffer[i], DEC);
Serial.print(F(", ir="));
Serial.println(irBuffer[i], DEC);
}
这个部分其实是可以放在setup中的,不知道作者为什么把他放在了loop中。
因为这里在loop中实际只执行了一次,后面作者用了一个while(1)的死循环去执行了其他操作。
particleSensor.available()
的作用是判断是否有新的数据写进来。
内部的数据是用数组实现的一个环形链表。
如果没有新的数据,那么就检查是否有新的数据传输进来。
particleSensor.check()
就是起到检查是否有新数据的作用。
在函数内部是一直在判断读指针和写指针的数据,如果不相同则是有新的数据过来。
particleSensor.nextSample()
就是看是不是有新数据,有新数据就把尾指针加一。
剩下的部分就是把数据打印出来了。
这里的redBuffer
和irBuffer
默认都是大小为100的数组。
从这里开始下面的篇幅就不逐一深入到每一个函数里去看了。
这段函数的主要功能就是把redBuffer
和irBuffer
里面写满数据,用于后面的误差统计计算。
//calculate heart rate and SpO2 after first 100 samples (first 4 seconds of samples)
maxim_heart_rate_and_oxygen_saturation(irBuffer, bufferLength, redBuffer, &spo2, &validSPO2, &heartRate, &validHeartRate);
这是一个引用了#include "spo2_algorithm.h"
库的计算公式,这个公式内部的计算还是比较复杂的,而且使用起来不是特别稳定。
分析这个函数有些复杂,而且这篇文章到这里也太长了,挖个坑,有时间单独写出来吧。
和上一个小节题目都一样,因为上两个小节本质上是在做初始化的操作,从功能上看,并不能算作是loop中的内容,其实放在setup中更加合理。
while (1) { //dumping the first 25 sets of samples in the memory and shift the last 75 sets of samples to the top for (byte i = 25; i < 100; i++) { redBuffer[i - 25] = redBuffer[i]; irBuffer[i - 25] = irBuffer[i]; } //take 25 sets of samples before calculating the heart rate. for (byte i = 75; i < 100; i++) { while (particleSensor.available() == false) //do we have new data? particleSensor.check(); //Check the sensor for new data digitalWrite(readLED, !digitalRead(readLED)); //Blink onboard LED with every data read redBuffer[i] = particleSensor.getRed(); irBuffer[i] = particleSensor.getIR(); particleSensor.nextSample(); //We're finished with this sample so move to next sample //send samples and calculation result to terminal program through UART Serial.print(F("red=")); Serial.print(redBuffer[i], DEC); Serial.print(F(", ir=")); Serial.print(irBuffer[i], DEC); Serial.print(F(", HR=")); Serial.print(heartRate, DEC); Serial.print(F(", HRvalid=")); Serial.print(validHeartRate, DEC); Serial.print(F(", SPO2=")); Serial.print(spo2, DEC); Serial.print(F(", SPO2Valid=")); Serial.println(validSPO2, DEC); } //After gathering 25 new samples recalculate HR and SP02 maxim_heart_rate_and_oxygen_saturation(irBuffer, bufferLength, redBuffer, &spo2, &validSPO2, &heartRate, &validHeartRate); }
这样看是挺多的,但是如果把串口输出部分,删除其实也没有多少,代码如下:
while (1) { //dumping the first 25 sets of samples in the memory and shift the last 75 sets of samples to the top for (byte i = 25; i < 100; i++) { redBuffer[i - 25] = redBuffer[i]; irBuffer[i - 25] = irBuffer[i]; } //take 25 sets of samples before calculating the heart rate. for (byte i = 75; i < 100; i++) { while (particleSensor.available() == false) //do we have new data? particleSensor.check(); //Check the sensor for new data digitalWrite(readLED, !digitalRead(readLED)); //Blink onboard LED with every data read redBuffer[i] = particleSensor.getRed(); irBuffer[i] = particleSensor.getIR(); particleSensor.nextSample(); //We're finished with this sample so move to next sample } //After gathering 25 new samples recalculate HR and SP02 maxim_heart_rate_and_oxygen_saturation(irBuffer, bufferLength, redBuffer, &spo2, &validSPO2, &heartRate, &validHeartRate); }
首先是
for (byte i = 25; i < 100; i++)
{
redBuffer[i - 25] = redBuffer[i];
irBuffer[i - 25] = irBuffer[i];
}
这一部分,这里就是腾挪数据,想象redBuffer
和irBuffer
中下标为0是栈底,99是栈顶(因为数组的大小为100,所以在栈顶是99)。这里其实就是把栈底的数据(也就是旧数据,进行了删除),在栈顶空出来25个数据的空间。
for (byte i = 75; i < 100; i++)
{
while (particleSensor.available() == false) //do we have new data?
particleSensor.check(); //Check the sensor for new data
digitalWrite(readLED, !digitalRead(readLED)); //Blink onboard LED with every data read
redBuffer[i] = particleSensor.getRed();
irBuffer[i] = particleSensor.getIR();
particleSensor.nextSample(); //We're finished with this sample so move to next sample
}
te, &validHeartRate);
}
首先是
```c++
for (byte i = 25; i < 100; i++)
{
redBuffer[i - 25] = redBuffer[i];
irBuffer[i - 25] = irBuffer[i];
}
这一部分,这里就是腾挪数据,想象redBuffer
和irBuffer
中下标为0是栈底,99是栈顶(因为数组的大小为100,所以在栈顶是99)。这里其实就是把栈底的数据(也就是旧数据,进行了删除),在栈顶空出来25个数据的空间。
for (byte i = 75; i < 100; i++)
{
while (particleSensor.available() == false) //do we have new data?
particleSensor.check(); //Check the sensor for new data
digitalWrite(readLED, !digitalRead(readLED)); //Blink onboard LED with every data read
redBuffer[i] = particleSensor.getRed();
irBuffer[i] = particleSensor.getIR();
particleSensor.nextSample(); //We're finished with this sample so move to next sample
}
这里做的是继续获取数据,把栈顶的25个空位填满,最后再送到maxim_heart_rate_and_oxygen_saturation(irBuffer, bufferLength, redBuffer, &spo2, &validSPO2, &heartRate, &validHeartRate);
中去计算。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。