English | 中文说明
An Arduino library for the Texas Instruments AFE4490 pulse-oximeter analog front end. Its packaging and high-level reading API follow the approachable shape of SparkFun's MAX3010x library, while the transport and configuration are implemented specifically for the AFE4490's SPI interface and timer architecture.
This is a sensor driver, not a medical SpO2 algorithm or a medical device.
This project is an AFE4490 adaptation of the library organization and
high-level API concepts from the
SparkFun MAX3010x Sensor Library.
In particular, its Arduino packaging, example-first workflow, begin() /
setup() experience, and FIFO-style software buffer are intentionally familiar
to users of the SparkFun library.
The AFE4490 is not register-compatible with the MAX3010x. Its 32-bit SPI transport, complete register map, timing generator, reset sequence, diagnostics, and signal-chain configuration were therefore implemented specifically from the TI AFE4490 datasheet and EVM documentation. The implementation was also cross-checked against the ProtoCentral AFE4490 Arduino library.
This is an independent community project. It is not affiliated with or endorsed by SparkFun Electronics, Texas Instruments, or ProtoCentral. Upstream copyright and license notices are preserved in NOTICE.md.
Important
The AFE4490 stores only the latest conversion results. If the host does not read them before the next pulse repetition, the previous sample is overwritten and cannot be recovered.
The six result registers at 0x2A through 0x2F contain LED2, ambient2, LED1,
ambient1, LED2-minus-ambient, and LED1-minus-ambient. They are result registers,
not a sample queue. The signal path is therefore:
AFE4490 latest-result registers -> immediate SPI read -> MCU software buffer
The library's four-entry buffer and methods such as available(),
getFIFOIR(), and getFIFORed() are implemented entirely in MCU RAM. Their
FIFO-style names preserve the familiar SparkFun API, but they do not imply
that the AFE4490 contains a hardware FIFO.
At the default 500-Hz pulse repetition rate, a new result is produced every
2 ms. The host must service ADC_RDY and call check() within that interval
when every sample matters. onDataReady() records a pending event; multiple
events that occur before check() cannot preserve the older register values.
The library reports this condition through bufferOverflowed(), as well as
ordinary overflow of its four-entry MCU buffer.
Avoid long delay() calls, blocking storage writes, or heavy processing in the
sampling loop. Lower the sample rate or use a dedicated high-priority sampling
task and a larger host-side queue when the application cannot guarantee this
service time.
Install AFE4490_Sensor_Library.zip with Arduino IDE's
Sketch > Include Library > Add .ZIP Library, or place the
AFE4490_Sensor_Library folder in the Arduino libraries directory. Restart the
IDE, then open an example from File > Examples > AFE4490 Sensor Library.
- 32-bit SPI frames: 8-bit address plus 24-bit register data
- SPI Mode 0, MSB first, configurable clock up to the 16-MHz datasheet limit
- Complete documented register map and guarded low-level register access
- Datasheet-derived four-phase timing for configurable pulse repetition rates
- LED current, Tx reference, TIA, ambient cancellation, filter, and power APIs
- Ordered, deadline-checked single-SPISTE reads of all six result registers
- 22-bit sign extension for ADC channels and 24-bit sign extension for LED-minus-ambient results
- ADC_RDY interrupt handoff and a small SparkFun-style software buffer
- Built-in communication check and cable/sensor diagnostics
The constructor order is:
AFE4490 afe(chipSelectPin, resetPin, powerDownPin, dataReadyPin);For the ProtoCentral AFE4490 breakout v2:
| Breakout | Arduino Uno | Constructor role |
|---|---|---|
| CS0 | 7 | chip select |
| START | 5 | active-low RESET |
| PWDN | 4 | active-low AFE_PDN |
| DRDY | 2 | ADC_RDY |
| MOSI | 11 | SPI MOSI |
| MISO | 12 | SPI MISO |
| SCK | 13 | SPI clock |
| VCC | 5V | breakout supply |
| GND | GND | ground |
Use AFE4490::NO_PIN for a signal that is not connected. Connecting RESET is
strongly recommended. TI requires a hardware RESET after the AFE_PDN pin has
been asserted.
Bare AFE4490 ICs use RX_DIG_SUP logic levels. Do not assume that a 5-V microcontroller can connect directly; use a correctly powered level shifter or a breakout that already provides one.
#include <SPI.h>
#include <AFE4490.h>
AFE4490 afe(7, 5, 4); // CS, RESET/START, PWDN
void setup()
{
Serial.begin(115200);
if (!afe.begin() || !afe.setup())
{
Serial.println("AFE4490 initialization failed");
while (true) {}
}
}
void loop()
{
AFE4490::Sample sample;
if (afe.readSample(sample))
{
// Conventional ProtoCentral probe mapping: LED1 = IR, LED2 = red.
Serial.print(sample.led2MinusAmbient);
Serial.print(',');
Serial.println(sample.led1MinusAmbient);
}
delay(2);
}As explained in the warning above, readSample() returns the latest conversion;
the previous hardware result may be overwritten at the next pulse repetition.
For continuous acquisition, prefer the ADC_RDY interrupt pattern below. Without
an ADC_RDY pin, check() rate-limits reads to the configured PRP so it does not
enqueue the same result repeatedly, but it cannot prove phase alignment without
the hardware edge.
ADC_RDY is only one 4-MHz CLKOUT cycle wide, about 250 ns. Polling it is not
reliable. Connect it to an interrupt-capable pin and call onDataReady() from
the ISR as its first action; do not perform SPI transfers inside the ISR.
The four channel registers update sequentially at the 25%, 50%, 75%, and next
0% ADC-reset edges. check() timestamps ADC_RDY, reads them in that order, and
checks each register against its next update deadline. A late or interrupted
read is discarded, sets bufferOverflowed(), and reports
ErrorSampleOverrun rather than returning a mixed-period sample.
The timestamp is captured in software, so globally disabling interrupts for a
large part of a PRP can still make the ISR arrive too late to prove coherence.
void dataReadyISR()
{
afe.onDataReady();
}
// In setup:
attachInterrupt(digitalPinToInterrupt(2), dataReadyISR, RISING);
// In loop:
if (afe.check())
{
Serial.println(afe.getFIFOIR());
afe.nextSample();
}See Example2_DataReadyInterrupt for the complete sketch.
AFE4490::Config defaults to the widely used ProtoCentral/EVM-compatible
500-Hz schedule:
| Setting | Default |
|---|---|
| Pulse repetition rate | 500 Hz (PRPCOUNT = 7999) |
| ADC averaging | 8 conversions |
| SPI | Mode 0, 2 MHz, MSB first |
| LED1 / LED2 DAC code | 0x14 / 0x14 |
| Tx reference | 0.75 V |
| LED range | high, 150-mA full scale at 0.75-V Tx reference |
| TIA feedback | 500 kOhm, 5 pF |
| Stage 2 | bypassed |
| Ambient cancellation | 0 uA |
| Filter corner | 500 Hz |
| Accurate-settling wait | 1 second |
At 500 Hz, the generated edge counts match TI SBAS602H Table 2, including a 50-count (12.5-us) LED/sample settling interval and four-count ADC reset pulses. Other rates preserve the same offsets and are rejected when the requested ADC averaging no longer fits inside a conversion quarter.
The default LED current is about 11.72 mA per LED (20 / 256 * 150 mA).
Select the Tx reference according to the transmitter supply: TI recommends
0.5 V for 3-V operation and 0.75 V or 1.0 V for 5-V operation.
The feedbackCapacitanceCode is the raw five-bit CF field, not a capacitance in
pF. It is a sum of the datasheet's switched capacitor weights plus the fixed
5-pF base.
- AFE4490 reads require setting
CONTROL0.SPI_READfirst. The library tracks this state and clears it before every write. - AFE4490 has no part-ID register.
begin()verifies communication by writing and reading back a legal PRPCOUNT value while the timer is stopped, then restores the previous values. Once configured,isConnected()performs only a readback comparison and does not reset the running device. - Multiple 32-bit operations within one active SPISTE pulse are explicitly supported by the AFE4490. The result read uses this mode to shorten the time between the first and last result register, while ADC_RDY timestamps guard the device's sequential register-update deadlines.
- Public
writeRegister()calls mask every register to its documented writable bits. It still does not validate arbitrary cross-register timing relationships; use the high-level timing API unless raw control is necessary. - Existing public ports often use
CONTROL1 = 0x010707. Bit 16 is reserved. This library writes0x000107for timer enabled plus eight averages. getIR()maps to LED1-minus-ambient andgetRed()maps to LED2-minus-ambient, matching the common ProtoCentral probe wiring. If your LEDs are wired in the opposite order, use the explicitSamplefields.- Changing signal-chain controls requires settling time.
setup()waits the datasheet's conservative 1 second by default. - ADC results are invalid during diagnostics. If the timer was running,
runDiagnostics()marksbufferOverflowed()to expose the acquisition gap. - AFE4490 digital outputs, including SPISOMI, do not automatically become
high-impedance when SPISTE is inactive. A shared SPI bus requires explicit
CONTROL2.DIGOUT_TRISTATEmanagement or external isolation; the library does not automate ownership of a multi-device bus.
Run the native host suite with:
./tests/run_tests.shThe suite covers default TI timing, sign extension, timed SPI burst reads, reserved-bit masking, invalid configurations, communication failure, ADC_RDY event overruns and late service, drift-free poll throttling, buffering, and diagnostic state restoration.
- TI AFE4490 datasheet, revision H
- TI AFE4400/AFE4490 EVM development guide
- TI AFE4490 EVM firmware source page
- SparkFun MAX3010x Sensor Library
- ProtoCentral AFE4490 Arduino library
The detailed source review and design decisions are recorded in
extras/REFERENCE_AUDIT.md. The 2026-07-26 defect review is recorded in
extras/CODE_AUDIT_2026-07-26.md.