Skip to content

Commit 4bcb873

Browse files
Initial port to new API
1 parent b99e323 commit 4bcb873

File tree

11 files changed

+1993
-399
lines changed

11 files changed

+1993
-399
lines changed

README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,30 @@ If you have bought the breakout the connection with the Arduino board is as foll
3636
| PD_ALM | NC | PD sensor fault indicator |
3737
| VCC | +5v | Supply voltage |
3838

39+
## New Features
40+
41+
### Custom SPI Pin Support
42+
43+
The library now supports custom SPI pins for different platforms (ESP32, ESP32-S3, ESP32-S2, ESP32-C3, ESP8266, STM32, etc.):
44+
45+
```cpp
46+
// Use custom SPI pins
47+
AFE44XX afe44xx(csPin, pwdnPin, drdyPin, sckPin, misoPin, mosiPin);
48+
49+
// Or configure after initialization
50+
afe44xx.setSPIPins(18, 19, 23); // ESP32 example
51+
```
52+
53+
**Platform Default Pins:**
54+
- **ESP32-S3**: SCK=12, MISO=13, MOSI=11
55+
- **ESP32-S2**: SCK=36, MISO=37, MOSI=35
56+
- **ESP32-C3**: SCK=6, MISO=5, MOSI=7
57+
- **ESP32**: SCK=18, MISO=19, MOSI=23
58+
- **ESP8266**: SCK=14, MISO=12, MOSI=13
59+
- **Arduino**: SCK=13, MISO=12, MOSI=11
60+
61+
See [Custom SPI Pins Documentation](docs/CUSTOM_SPI_PINS.md) for detailed usage examples.
62+
3963
# Visualizing Output
4064
4165
![openview output](./assets/AFE4490_openview.gif)
Lines changed: 224 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,121 +1,261 @@
11
//////////////////////////////////////////////////////////////////////////////////////////
22
//
3-
// Arduino library for the AFE4490 Pulse Oximeter Shield
3+
// Arduino library for the AFE44XX Pulse Oximeter Shield - Raw PPG Plot for OpenView
4+
// Updated to use the new improved library with error handling and diagnostics
45
//
5-
// Copyright (c) 2018 ProtoCentral
6-
//
7-
// This example will plot the PPG signal through openview processing GUI.
6+
// This example will plot the PPG signal through ProtoCentral OpenView processing GUI.
87
// GUI URL: https://github.com/Protocentral/protocentral_openview.git
98
//
9+
// Copyright (c) 2018 ProtoCentral
10+
//
1011
// This software is licensed under the MIT License(http://opensource.org/licenses/MIT).
1112
//
12-
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
13-
// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
14-
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
15-
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
16-
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
13+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
14+
// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
15+
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
16+
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
17+
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
18+
//
19+
// For information on how to use with ProtoCentral OpenView:
20+
// 1. Upload this sketch to your Arduino
21+
// 2. Open ProtoCentral OpenView software
22+
// 3. Select the appropriate COM port
23+
// 4. Choose "AFE4490 Raw PPG" visualization mode
24+
// 5. Click Connect to view real-time PPG waveforms
1725
//
18-
// For information on how to use, visit https://github.com/Protocentral/protocentral-afe4490-arduino
26+
// For information on how to use, visit https://github.com/Protocentral/protocentral-afe4490-arduino
1927
/////////////////////////////////////////////////////////////////////////////////////////
2028

21-
#include <SPI.h>
2229
#include "protocentral_afe44xx.h"
30+
#include "Protocentral_spo2_algorithm.h"
31+
#include "protocentral_hr_algorithm.h"
2332

24-
#define AFE44XX_CS_PIN 7
25-
#define AFE44XX_PWDN_PIN 4
26-
#define AFE44XX_INTNUM 0
33+
// Default pins CS=7, PWDN=4, DRDY=2
2734

28-
#define CES_CMDIF_PKT_START_1 0x0A
29-
#define CES_CMDIF_PKT_START_2 0xFA
30-
#define CES_CMDIF_TYPE_DATA 0x02
31-
#define CES_CMDIF_PKT_STOP 0x0B
35+
#define AFE44XX_CS_PIN 10
36+
#define AFE44XX_PWDN_PIN 21
37+
#define AFE44XX_DRDY_PIN 14
3238

33-
AFE44XX afe44xx(AFE44XX_CS_PIN, AFE44XX_PWDN_PIN);
39+
#define CES_CMDIF_PKT_START_1 0x0A
40+
#define CES_CMDIF_PKT_START_2 0xFA
41+
#define CES_CMDIF_TYPE_DATA 0x02
42+
#define CES_CMDIF_PKT_STOP 0x0B
43+
#define DATA_LEN 10
3444

35-
afe44xx_data afe44xx_raw_data;
36-
uint8_t ppg_data_buff[20];
37-
int16_t ppg_wave_ir;
38-
uint16_t ppg_stream_cnt = 0;
39-
uint8_t sp02;
40-
uint8_t heartrate;
41-
bool ppg_buf_ready = false;
42-
bool spo2_calc_done = false;
45+
AFE44XX afe44xx(AFE44XX_CS_PIN, AFE44XX_PWDN_PIN, AFE44XX_DRDY_PIN);
4346

44-
#define DATA_LEN 10
47+
spo2_algorithm spo2Calc;
48+
hr_algo hrCalc;
49+
50+
uint16_t irBuffer[100];
51+
uint16_t redBuffer[100];
52+
uint8_t bufferIndex = 0;
53+
uint8_t decimationCounter = 0;
54+
const uint8_t DECIMATION_FACTOR = 20;
4555

46-
char DataPacket[10];
47-
const char DataPacketFooter[2]={0x00, CES_CMDIF_PKT_STOP};
48-
const char DataPacketHeader[6] = {CES_CMDIF_PKT_START_1, CES_CMDIF_PKT_START_2, DATA_LEN, ((uint8_t)(DATA_LEN >> 8)), CES_CMDIF_TYPE_DATA};
56+
int32_t heartRate = 0;
57+
int32_t spo2 = 0;
58+
int8_t spo2Valid = 0;
59+
int8_t hrValid = 0;
4960

61+
uint8_t ppgDataBuff[20];
62+
uint16_t ppgStreamCnt = 0;
63+
bool ppgBufReady = false;
64+
bool sensorInitialized = false;
65+
66+
char dataPacket[DATA_LEN];
67+
const char dataPacketFooter[2] = {0x00, CES_CMDIF_PKT_STOP};
68+
const char dataPacketHeader[5] = {CES_CMDIF_PKT_START_1, CES_CMDIF_PKT_START_2, DATA_LEN,
69+
((uint8_t)(DATA_LEN >> 8)), CES_CMDIF_TYPE_DATA};
5070

5171
void setup()
5272
{
53-
Serial.begin(57600);
54-
Serial.println("Intilaziting AFE44xx.. ");
55-
56-
SPI.begin();
57-
58-
afe44xx.afe44xx_init();
59-
Serial.println("Inited...");
60-
}
73+
Serial.begin(57600);
74+
while (!Serial)
75+
delay(10);
6176

62-
void send_data_serial_port(void)
63-
{
64-
for (int i = 0; i < 5; i++)
65-
{
66-
Serial.write(DataPacketHeader[i]); // transmit the data over USB
67-
}
68-
for (int i = 0; i < DATA_LEN; i++)
69-
{
70-
Serial.write(DataPacket[i]); // transmit the data over USB
71-
}
72-
for (int i = 0; i < 2; i++)
73-
{
74-
Serial.write(DataPacketFooter[i]); // transmit the data over USB
75-
}
77+
Serial.println("AFE44XX Raw PPG Data for ProtoCentral OpenView");
78+
Serial.println("==============================================");
79+
80+
AFE44xxConfig config = AFE44XX::getDefaultConfig();
81+
config.chipType = AFE44xxChipType::AFE4490;
82+
config.led1Current = LEDCurrent::CURRENT_50MA;
83+
config.led2Current = LEDCurrent::CURRENT_50MA;
84+
config.tiaGain = TIAGain::GAIN_500K;
85+
config.sampleRate = SampleRate::RATE_500HZ;
86+
config.averagingFactor = 3;
87+
config.enableDiagnostics = true;
88+
89+
Serial.println("Initializing AFE44XX...");
90+
AFE44xxError error = afe44xx.begin(config);
91+
92+
if (error != AFE44xxError::NONE)
93+
{
94+
Serial.print("ERROR: Initialization failed - ");
95+
Serial.println(afe44xx.getErrorString(error));
96+
Serial.println("Please check connections and reset Arduino");
97+
98+
while (1)
99+
{
100+
delay(5000);
101+
Serial.println("Check connections: CS=7, PWDN=4, DRDY=2, SPI pins");
102+
}
103+
}
104+
105+
error = afe44xx.performSelfTest();
106+
if (error != AFE44xxError::NONE)
107+
{
108+
Serial.print("WARNING: Self-test failed - ");
109+
Serial.println(afe44xx.getErrorString(error));
110+
Serial.println("Continuing anyway...");
111+
}
112+
else
113+
{
114+
Serial.println("Self-test passed!");
115+
}
116+
117+
hrCalc.initStatHRM();
118+
sensorInitialized = true;
119+
120+
Serial.println("Ready! Open ProtoCentral OpenView and connect to this COM port");
121+
Serial.println("Select 'AFE4490 Raw PPG' mode in OpenView");
122+
123+
delay(2000);
76124
}
77125

78126
void loop()
79127
{
80-
delay(8);
81-
82-
afe44xx.get_AFE44XX_Data(&afe44xx_raw_data);
83-
ppg_wave_ir = (int16_t)(afe44xx_raw_data.IR_data >> 8);
84-
ppg_wave_ir = ppg_wave_ir;
128+
if (!sensorInitialized)
129+
{
130+
delay(1000);
131+
return;
132+
}
133+
134+
AFE44xxData data;
135+
AFE44xxError error = afe44xx.readData(data);
136+
137+
if (error != AFE44xxError::NONE)
138+
{
139+
initializeErrorPacket();
140+
sendDataSerialPort();
141+
delay(100);
142+
return;
143+
}
144+
145+
if (!data.dataValid)
146+
{
147+
delay(8);
148+
return;
149+
}
150+
151+
processSpO2Calculation(data);
152+
153+
prepareDataPacket(data);
85154

86-
ppg_data_buff[ppg_stream_cnt++] = (uint8_t)ppg_wave_ir;
87-
ppg_data_buff[ppg_stream_cnt++] = (ppg_wave_ir >> 8);
155+
sendDataSerialPort();
88156

89-
if (ppg_stream_cnt >= 19)
157+
static unsigned long lastDiagTime = 0;
158+
if (millis() - lastDiagTime > 10000)
90159
{
91-
ppg_buf_ready = true;
92-
ppg_stream_cnt = 0;
160+
checkSensorDiagnostics();
161+
lastDiagTime = millis();
93162
}
94163

95-
memcpy(&DataPacket[0], &afe44xx_raw_data.IR_data, sizeof(signed long));
96-
memcpy(&DataPacket[4], &afe44xx_raw_data.RED_data, sizeof(signed long));
164+
delay(8);
165+
}
166+
167+
void processSpO2Calculation(const AFE44xxData &data)
168+
{
169+
decimationCounter++;
170+
if (decimationCounter >= DECIMATION_FACTOR)
171+
{
172+
irBuffer[bufferIndex] = (uint16_t)(data.irData >> 4);
173+
redBuffer[bufferIndex] = (uint16_t)(data.redData >> 4);
174+
bufferIndex = (bufferIndex + 1) % 100;
175+
decimationCounter = 0;
176+
177+
static uint8_t sampleCount = 0;
178+
sampleCount++;
179+
if (sampleCount >= 100) {
180+
spo2Calc.estimate_spo2(irBuffer, 100, redBuffer, &spo2, &spo2Valid, &heartRate, &hrValid);
181+
sampleCount = 0;
182+
}
183+
}
97184

98-
if (afe44xx_raw_data.buffer_count_overflow)
185+
hrCalc.statHRMAlgo(data.redData);
186+
if (hrCalc.HeartRate > 0)
99187
{
188+
heartRate = hrCalc.HeartRate;
189+
hrValid = 1;
190+
}
191+
}
100192

101-
if (afe44xx_raw_data.spo2 == -999)
102-
{
103-
DataPacket[8] = 0;
104-
sp02 = 0;
105-
DataPacket[9] = 0;
106-
heartrate = 0;
107-
}
108-
else
109-
{
110-
DataPacket[8] = afe44xx_raw_data.spo2;
111-
sp02 = (uint8_t)afe44xx_raw_data.spo2;
112-
DataPacket[9] = afe44xx_raw_data.heart_rate;
113-
heartrate = (uint8_t) afe44xx_raw_data.heart_rate;
114-
spo2_calc_done = true;
115-
}
193+
void prepareDataPacket(const AFE44xxData &data)
194+
{
195+
memcpy(&dataPacket[0], &data.irData, sizeof(int32_t));
196+
memcpy(&dataPacket[4], &data.redData, sizeof(int32_t));
116197

117-
118-
afe44xx_raw_data.buffer_count_overflow = false;
198+
if (spo2Valid && spo2 > 0)
199+
{
200+
dataPacket[8] = (uint8_t)constrain(spo2, 0, 100);
119201
}
120-
send_data_serial_port();
202+
else
203+
{
204+
dataPacket[8] = 0;
205+
}
206+
207+
if (hrValid && heartRate > 0)
208+
{
209+
dataPacket[9] = (uint8_t)constrain(heartRate, 0, 255);
210+
}
211+
else
212+
{
213+
dataPacket[9] = 0;
214+
}
215+
}
216+
217+
void initializeErrorPacket()
218+
{
219+
memset(dataPacket, 0, DATA_LEN);
220+
}
221+
222+
void sendDataSerialPort()
223+
{
224+
uint8_t fullPacket[5 + DATA_LEN + 2];
225+
uint8_t idx = 0;
226+
227+
memcpy(&fullPacket[idx], dataPacketHeader, 5);
228+
idx += 5;
229+
memcpy(&fullPacket[idx], dataPacket, DATA_LEN);
230+
idx += DATA_LEN;
231+
memcpy(&fullPacket[idx], dataPacketFooter, 2);
232+
233+
Serial.write(fullPacket, sizeof(fullPacket));
121234
}
235+
236+
void checkSensorDiagnostics()
237+
{
238+
AFE44xxDiagnostics diag;
239+
AFE44xxError error = afe44xx.getDiagnostics(diag);
240+
241+
if (error != AFE44xxError::NONE)
242+
{
243+
return;
244+
}
245+
246+
if (diag.ledFault || diag.pdShort || diag.pdOpen || diag.gainError)
247+
{
248+
afe44xx.clearFaults();
249+
}
250+
251+
if (diag.ambientLight > 2000)
252+
{
253+
afe44xx.setLEDCurrent(1, LEDCurrent::CURRENT_75MA);
254+
afe44xx.setLEDCurrent(2, LEDCurrent::CURRENT_75MA);
255+
}
256+
else if (diag.ambientLight < 100)
257+
{
258+
afe44xx.setLEDCurrent(1, LEDCurrent::CURRENT_25MA);
259+
afe44xx.setLEDCurrent(2, LEDCurrent::CURRENT_25MA);
260+
}
261+
}

0 commit comments

Comments
 (0)