Firmware and SPI for a biopotential chip
Reading a register, reading a frame on data-ready, unpacking 24-bit two's complement, and shipping samples to a computer without losing any. The four things every acquisition firmware does.
On this pageA weekend
SPI in one paragraph
Four wires. The microcontroller drives a clock. On each clock edge one bit goes out on one wire and one bit comes in on another. A fourth wire, chip select, tells the chip that the conversation is for it. The chip’s datasheetDatasheet The manufacturer's document for a chip: pinout, limits, typical performance, and the reference circuit you should start from. Glossary entry says which clock edge it samples on and whether the clock idles high or low; the ADS1299ADS1299 Texas Instruments' 8-channel, 24-bit biopotential front-end chip used in OpenBCI and many research amplifiers. Related: Delta-sigma ADCDelta-sigma ADC A converter that samples very fast with a crude 1-bit decision and averages, achieving 24-bit resolution at modest rates; what the ADS1299 uses. , OpenBCIOpenBCI The open-source EEG hardware company whose Cyton (ADS1299) and Ganglion boards are the default hobby and research-lite amplifiers. . Glossary entry wants clock idle low, data captured on the falling edge, which in the usual notation is mode 1. Get that wrong and you read garbage that looks almost right, which is worse than nothing.
Step one: read the ID register
Everything else waits on this.
// ESP32 with the Arduino core; pins are examples
#include <SPI.h>
const int CS = 5, DRDY = 4, RST = 16, START = 17;
SPISettings ads(1000000, MSBFIRST, SPI_MODE1);
uint8_t readReg(uint8_t addr) {
SPI.beginTransaction(ads); digitalWrite(CS, LOW);
SPI.transfer(0x20 | addr); // RREG command: 001r rrrr
SPI.transfer(0x00); // read 1 register
uint8_t v = SPI.transfer(0x00);
digitalWrite(CS, HIGH); SPI.endTransaction();
return v;
}
void setup() {
Serial.begin(500000); SPI.begin();
pinMode(CS, OUTPUT); pinMode(DRDY, INPUT); pinMode(RST, OUTPUT); pinMode(START, OUTPUT);
digitalWrite(CS, HIGH); digitalWrite(START, LOW);
digitalWrite(RST, LOW); delay(1); digitalWrite(RST, HIGH); delay(10);
sendCmd(0x11); // SDATAC: stop continuous mode so registers can be read
Serial.printf("ID = 0x%02X (expect 0x3E)\n", readReg(0x00));
}
Start the clock slow, a megahertz. Speed comes later.
Step two: configure
Write the configuration registers with the values from the datasheetDatasheet The manufacturer's document for a chip: pinout, limits, typical performance, and the reference circuit you should start from. Glossary entry and the reference firmwareFirmware The program running on the microcontroller, which configures the chip, reads samples on time, and ships them to the computer. Related: SPISPI A simple four-wire bus for talking to chips like the ADS1299 from a microcontroller. . Glossary entry, then read them back and print them. A register that does not read back what you wrote means a write that did not land (timing) or a bit that is read-only. Do not proceed until every one reads back.
Step three: read frames on data-ready
The chip pulls DRDY low when a sample set is ready. Attach an interrupt to that pin that sets a flag; in the main loop, when the flag is set, clock out the frame. For the ADS1299ADS1299 Texas Instruments' 8-channel, 24-bit biopotential front-end chip used in OpenBCI and many research amplifiers. Related: Delta-sigma ADCDelta-sigma ADC A converter that samples very fast with a crude 1-bit decision and averages, achieving 24-bit resolution at modest rates; what the ADS1299 uses. , OpenBCIOpenBCI The open-source EEG hardware company whose Cyton (ADS1299) and Ganglion boards are the default hobby and research-lite amplifiers. . Glossary entry that is 27 bytes: 3 status bytes then 8 channels of 3 bytes each, most significant byte first.
volatile bool ready = false;
void IRAM_ATTR onDrdy() { ready = true; }
int32_t chan[8];
void readFrame() {
uint8_t b[27];
SPI.beginTransaction(ads); digitalWrite(CS, LOW);
for (int i = 0; i < 27; i++) b[i] = SPI.transfer(0x00);
digitalWrite(CS, HIGH); SPI.endTransaction();
for (int c = 0; c < 8; c++) {
int32_t v = ((int32_t)b[3 + 3*c] << 16) | ((int32_t)b[4 + 3*c] << 8) | b[5 + 3*c];
if (v & 0x800000) v |= 0xFF000000; // sign-extend 24-bit two's complement
chan[c] = v;
}
}
The sign extension line is the one everyone gets wrong once. Without it, negative voltages appear as huge positive numbers and your trace has a cliff in it.
Step four: scale and ship
Counts to volts: one count is (2 × VREF / gainGain How many times an amplifier multiplies its input; 1000× turns 50 microvolts into 50 millivolts, enough for an ADC to see. Related: Instrumentation amplifierInstrumentation amplifier A precision differential amplifier with very high input impedance and high CMRR, built from three op-amps and sold as a single chip such as the INA128 or AD8232. . Glossary entry) / 2²⁴. With VREF 4.5 V and gainGain How many times an amplifier multiplies its input; 1000× turns 50 microvolts into 50 millivolts, enough for an ADC to see. Related: Instrumentation amplifierInstrumentation amplifier A precision differential amplifier with very high input impedance and high CMRR, built from three op-amps and sold as a single chip such as the INA128 or AD8232. . Glossary entry 24 that is about 22.35 nV. Multiply in floating point on the microcontroller, or ship raw counts and scale on the computer; shipping counts is fewer bytes and loses nothing.
Ship as binary, not text, once you are past the debugging stage: a start byte, a sample counter, 24 bytes of channels, an end byte. The sample counter is essential; it is how the computer knows if a frame was dropped. At 250 samples per second times 27 bytes that is under 7 kilobytes per second, trivial for USB or Wi-Fi. Match the OpenBCIOpenBCI The open-source EEG hardware company whose Cyton (ADS1299) and Ganglion boards are the default hobby and research-lite amplifiers. Related: ADS1299ADS1299 Texas Instruments' 8-channel, 24-bit biopotential front-end chip used in OpenBCI and many research amplifiers. , BrainFlowBrainFlow A library that talks to OpenBCI and most consumer EEG headsets with one API, in Python and other languages. . Glossary entry Cyton’s frame format if you want every existing tool to read your board.
Do not lose samples
The interrupt sets a flag and nothing else; reading SPISPI A simple four-wire bus for talking to chips like the ADS1299 from a microcontroller. Related: FirmwareFirmware The program running on the microcontroller, which configures the chip, reads samples on time, and ships them to the computer. . Glossary entry inside the interrupt causes trouble. The main loop reads the frame promptly. Serial or Wi-Fi transmission happens after the frame is read, and if the link is slow, buffer frames in a ring and send in batches. Test by counting: run for ten minutes and check the sample counter never skips. A firmwareFirmware The program running on the microcontroller, which configures the chip, reads samples on time, and ships them to the computer. Related: SPISPI A simple four-wire bus for talking to chips like the ADS1299 from a microcontroller. . Glossary entry that drops one frame in a thousand produces spectra that are subtly wrong, and you will not know until the phantomPhantom A physical stand-in for tissue, such as saline gelatin with embedded wires, used to test an instrument against known signals. Related: CharacterizationCharacterization Measuring what your instrument actually does, such as its noise floor, bandwidth, and rejection, instead of assuming the datasheet. . Glossary entry head tells you.