前言
主要介紹的是arduino中SparkFun_MAX3010x_Sensor_Library這個庫。
SparkFun_MAX3010x_Sensor_Library連結地址
這個庫可以在arduino中直接搜尋下載。
主要分析的是SpO2這個部分。examples中是示例,src中是原始碼。
如果對max30102的初始化過程不清楚,可以看下面這篇文章。
MAX02分析
例項程式碼分析
引用部分
標頭檔案的引用
#include <Wire.h>
#include "MAX30105.h"
#include "spo2_algorithm.h"
雖然這裡標註的是#include "MAX30105.h"
,但是MAX30102也可以使用。
#include <Wire.h>
其實是不用引用的,因為在#include "MAX30105.h"
中已經引用過了,這裡引用可能是為了可讀性。
建立類
MAX30105 particleSensor;
這裡沒什麼好說的,就是建立了一個MAX30105的物件。
iic初始化部分
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的引用。
MAX30105::begin
再看一下這個函式的原型。
在H檔案中的引用是這樣的
boolean begin(TwoWire &wirePort = Wire, uint32_t i2cSpeed = I2C_SPEED_STANDARD, uint8_t i2caddr = MAX30105_ADDRESS);
這樣寫是代表這三個選項都是可選的,也就是說,你不傳遞任何值,也是可以正確初始化的。
看一下三個引數
- wirePort就是接受了一個Wire類
- i2cSpeed預設是100kHz的速率
- i2caddr是MAX30102的地址,預設是0x57
也就是說如果什麼都不傳遞,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。作用是讀取版本號。
MAX30105::readRegister8
來看看讀寫函式
這是一個用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。
這時候看到的資料就是不穩定的。
MAX30105::writeRegister8
介紹完了讀函式,寫函式就沒什麼好說的了。看一下原始碼。
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();
這一段沒什麼好說的,就是初始化成功了,然後讓你隨便輸入個東西,然後繼續執行後面的內容
實際開發中用不到這一塊程式碼
MAX3010X初始化部分
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的各種引數,對其進行初始化。直接看函式原型吧
MAX30105::setup
在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毫秒,看復位成功了沒有。
話說這個操作方式確實比較精準,誤差不會太大。
void MAX30105::bitMask
這個函式是一個位操作的函式,控制一位或者幾位的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);
}
操作流程如下:
- 讀取該地址的值
- 把讀取到的資料和mask做與操作
- 寫入第二步得到的數值與thing的或運算的值
這個操作一開始看,感覺有點傻,為啥不直接傳輸thing,然後在裡面進行取反操作,何必多次一舉,看了其他呼叫這個函式的程式碼,我大概搞清楚了。
這個操作不是單純的對一位bit進行操作,而是對多位進行操作,打個比方
現在有第0位到第2位的bit是代表一個模式設定,這個模式有101,110,111三種。那麼就可以傳輸一個二進位制是1111 1000
的mask,那與讀取出來的值進行與操作之後,就把第0位到第2位的資料清零了,這時thing再傳遞三種模式的其中一種。這樣就可以做到任意位數的賦值,這種方式還挺巧妙的。
但是也能感覺到作者在寫mark的資料定義的時候挺煩的,一會2進位制賦值,一會16進位制賦值。
FIFO配置
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配置都是一個暫存器裡的內容,卻分成了兩個部分來寫。可讀性比較好,但是執行效率就不怎麼高了。
LED設定
設定紅光和紅外光,三種模式,同上,可以看前言裡的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檢測設定
設定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);
SpO2取樣率控制
取樣率和脈衝寬度是相關的,因為取樣率設定了脈衝寬度時間的上限。如果使用者選擇的取樣率對於所選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脈寬控制和ADC解析度
這些位設定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);
LED脈衝寬度設定
設定脈衝寬度,具體引數,可以看前言裡的MAX30102分析。
setPulseAmplitudeRed(powerLevel);
setPulseAmplitudeIR(powerLevel);
非MAX30102有效暫存器
這兩個設定在MAX30102中是無效的,因為資料手冊中這個地址的暫存器並沒有分配功能,但是因為MAX30105是向下相容的,所以MAX30102使用也不會出問題。
setPulseAmplitudeGreen(powerLevel);
setPulseAmplitudeProximity(powerLevel);
多LED模式控制
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。
清除FIFO
具體引數,可以看前言裡的MAX30102分析。
clearFIFO(); //Reset the FIFO before we begin checking the sensor
小結
先看一下函式內部都做了什麼吧
這樣看還是比較混亂,可以對照這資料手冊把資訊採集的流程列一下,圖片就不放了,markdown放圖片不方便移植
所以LED設定是在設定紅外和紅燈
LED取樣和ADC設定都是在設定模擬訊號輸入方式
FIFO和更新是在設定資料在資料暫存器的儲存方式和規則
MAX30102資料採集
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
裡面寫滿資料,用於後面的誤差統計計算。
MAX30102資料計算
//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"
庫的計算公式,這個公式內部的計算還是比較複雜的,而且使用起來不是特別穩定。
分析這個函式有些複雜,而且這篇文章到這裡也太長了,挖個坑,有時間單獨寫出來吧。
MAX30102資料計算
和上一個小節題目都一樣,因為上兩個小節本質上是在做初始化的操作,從功能上看,並不能算作是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
}
這裡做的是繼續獲取資料,把棧頂的25個空位填滿,最後再送到maxim_heart_rate_and_oxygen_saturation(irBuffer, bufferLength, redBuffer, &spo2, &validSPO2, &heartRate, &validHeartRate);
中去計算。