1
1
// ////////////////////////////////////////////////////////////////////////////////////////
2
2
//
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
4
5
//
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.
8
7
// GUI URL: https://github.com/Protocentral/protocentral_openview.git
9
8
//
9
+ // Copyright (c) 2018 ProtoCentral
10
+ //
10
11
// This software is licensed under the MIT License(http://opensource.org/licenses/MIT).
11
12
//
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
17
25
//
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
19
27
// ///////////////////////////////////////////////////////////////////////////////////////
20
28
21
- #include < SPI.h>
22
29
#include " protocentral_afe44xx.h"
30
+ #include " Protocentral_spo2_algorithm.h"
31
+ #include " protocentral_hr_algorithm.h"
23
32
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
27
34
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
32
38
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
34
44
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);
43
46
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 ;
45
55
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 ;
49
60
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};
50
70
51
71
void setup ()
52
72
{
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 );
61
76
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 );
76
124
}
77
125
78
126
void loop ()
79
127
{
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);
85
154
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 ();
88
156
89
- if (ppg_stream_cnt >= 19 )
157
+ static unsigned long lastDiagTime = 0 ;
158
+ if (millis () - lastDiagTime > 10000 )
90
159
{
91
- ppg_buf_ready = true ;
92
- ppg_stream_cnt = 0 ;
160
+ checkSensorDiagnostics () ;
161
+ lastDiagTime = millis () ;
93
162
}
94
163
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
+ }
97
184
98
- if (afe44xx_raw_data.buffer_count_overflow )
185
+ hrCalc.statHRMAlgo (data.redData );
186
+ if (hrCalc.HeartRate > 0 )
99
187
{
188
+ heartRate = hrCalc.HeartRate ;
189
+ hrValid = 1 ;
190
+ }
191
+ }
100
192
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 ));
116
197
117
-
118
- afe44xx_raw_data.buffer_count_overflow = false ;
198
+ if (spo2Valid && spo2 > 0 )
199
+ {
200
+ dataPacket[8 ] = (uint8_t )constrain (spo2, 0 , 100 );
119
201
}
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));
121
234
}
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