//ADS1256 cpp file /* Name: ADS1256.cpp Created: 2022/07/14 Author: Curious Scientist Editor: Notepad++ Comment: Visit https://curiousscientist.tech/blog/ADS1256-custom-library Special thanks to: Abraão Queiroz for spending time on the code and suggesting corrections for ESP32 microcontrollers Benjamin Pelletier for pointing out and fixing an issue around the handling of the DRDY signal RadoMmm for suggesting an improvement on the ADC-to-Volts conversion */ #include "Arduino.h" #include "ADS1256.h" #include "SPI.h" #define convertSigned24BitToLong(value) ((value) & (1l << 23) ? (value) - 0x1000000 : value) //Constructor ADS1256::ADS1256(const int8_t DRDY_pin, const int8_t RESET_pin, const int8_t SYNC_pin, const int8_t CS_pin,float VREF, SPIClass* spi): _spi(spi), _DRDY_pin(DRDY_pin), _RESET_pin(RESET_pin), _SYNC_pin(SYNC_pin), _CS_pin(CS_pin), _VREF(VREF), _PGA(0) { pinMode(_DRDY_pin, INPUT); if(RESET_pin != PIN_UNUSED) { pinMode(_RESET_pin, OUTPUT); } if(SYNC_pin != PIN_UNUSED) { pinMode(_SYNC_pin, OUTPUT); } if(CS_pin != PIN_UNUSED) { pinMode(_CS_pin, OUTPUT); } updateConversionParameter(); } //Initialization void ADS1256::InitializeADC() { //Chip select LOW CS_LOW(); //We do a manual chip reset on the ADS1256 - Datasheet Page 27/ RESET if(_RESET_pin != PIN_UNUSED) { digitalWrite(_RESET_pin, LOW); delay(200); digitalWrite(_RESET_pin, HIGH); //RESET is set to high delay(1000); } //Sync pin is also treated if it is defined if(_SYNC_pin != PIN_UNUSED) { digitalWrite(_SYNC_pin, HIGH); //RESET is set to high } #ifndef ADS1256_SPI_ALREADY_STARTED //Guard macro to allow external initialization of the SPI _spi->begin(); #endif //Applying arbitrary default values to speed up the starting procedure if the user just want to get quick readouts //We both pass values to the variables and then send those values to the corresponding registers delay(200); _STATUS = 0b00110110; //BUFEN and ACAL enabled, Order is MSB, rest is read only writeRegister(STATUS_REG, _STATUS); delay(200); _MUX = 0b00000001; //MUX AIN0+AIN1 writeRegister(MUX_REG, _MUX); delay(200); _ADCON = 0b00000000; //ADCON - CLK: OFF, SDCS: OFF, PGA = 0 (+/- 5 V) writeRegister(ADCON_REG, _ADCON); delay(200); updateConversionParameter(); _DRATE = 0b10000010; //100SPS writeRegister(DRATE_REG, _DRATE); delay(200); sendDirectCommand(0b11110000); //Offset and self-gain calibration delay(200); _isAcquisitionRunning = false; //MCU will be waiting to start a continuous acquisition } void ADS1256::waitForLowDRDY() { while (digitalRead(_DRDY_pin) == HIGH) {} } void ADS1256::waitForHighDRDY() { #if F_CPU >= 48000000 //Fast MCUs need this protection to wait until DRDY goes high after a conversion while (digitalRead(_DRDY_pin) == LOW) {} #endif } void ADS1256::stopConversion() //Sending SDATAC to stop the continuous conversion { waitForLowDRDY(); //SDATAC should be called after DRDY goes LOW (p35. Figure 33) _spi->transfer(0b00001111); //Send SDATAC to the ADC CS_HIGH(); //We finished the command sequence, so we switch it back to HIGH _spi->endTransaction(); _isAcquisitionRunning = false; //Reset to false, so the MCU will be able to start a new conversion } void ADS1256::setDRATE(uint8_t drate) //Setting DRATE (sampling frequency) { writeRegister(DRATE_REG, drate); _DRATE = drate; delayMicroseconds(500); } void ADS1256::setMUX(uint8_t mux) //Setting MUX (input channel) { writeRegister(MUX_REG, mux); _MUX = mux; //delayMicroseconds(500); } void ADS1256::setPGA(uint8_t pga) //Setting PGA (input voltage range) { _PGA = pga; _ADCON = readRegister(ADCON_REG); //Read the most recent value of the register _ADCON = (_ADCON & 0b11111000) | (_PGA & 0b00000111); // Clearing and then setting bits 2-0 based on pga writeRegister(ADCON_REG, _ADCON); delayMicroseconds(1000); //Delay to allow the PGA to settle after changing its value updateConversionParameter(); //Update the multiplier according top the new PGA value } uint8_t ADS1256::getPGA() //Reading PGA from the ADCON register { uint8_t pgaValue = readRegister(ADCON_REG) & 0b00000111; //Reading the ADCON_REG and keeping the first three bits. return(pgaValue); } void ADS1256::setCLKOUT(uint8_t clkout) //Setting CLKOUT { _ADCON = readRegister(ADCON_REG); //Read the most recent value of the register //Values: 0, 1, 2, 3 if(clkout == 0) { //00 bitWrite(_ADCON, 6, 0); bitWrite(_ADCON, 5, 0); } else if(clkout == 1) { //01 (default) bitWrite(_ADCON, 6, 0); bitWrite(_ADCON, 5, 1); } else if(clkout == 2) { //10 bitWrite(_ADCON, 6, 1); bitWrite(_ADCON, 5, 0); } else if(clkout == 3) { //11 bitWrite(_ADCON, 6, 1); bitWrite(_ADCON, 5, 1); } else{} writeRegister(ADCON_REG, _ADCON); delay(100); } void ADS1256::setSDCS(uint8_t sdcs) //Setting SDCS { _ADCON = readRegister(ADCON_REG); //Read the most recent value of the register //Values: 0, 1, 2, 3 if(sdcs == 0) { //00 (default) bitWrite(_ADCON, 4, 0); bitWrite(_ADCON, 3, 0); } else if(sdcs == 1) { //01 bitWrite(_ADCON, 4, 0); bitWrite(_ADCON, 3, 1); } else if(sdcs == 2) { //10 bitWrite(_ADCON, 4, 1); bitWrite(_ADCON, 3, 0); } else if(sdcs == 3) { //11 bitWrite(_ADCON, 4, 1); bitWrite(_ADCON, 3, 1); } else{} writeRegister(ADCON_REG, _ADCON); delay(100); } void ADS1256::setByteOrder(uint8_t byteOrder) //Setting byte order (MSB/LSB) { _STATUS = readRegister(STATUS_REG); //Read the most recent value of the register if(byteOrder == 0) { //Byte order is MSB (default) bitWrite(_STATUS, 3, 0); //Set value of _STATUS at the third bit to 0 } else if(byteOrder == 1) { //Byte order is LSB bitWrite(_STATUS, 3, 1); //Set value of _STATUS at the third bit to 1 } else{} writeRegister(STATUS_REG, _STATUS); delay(100); } uint8_t ADS1256::getByteOrder() //Getting byte order (MSB/LSB) { uint8_t statusValue = readRegister(STATUS_REG); //Read the whole STATUS register return bitRead(statusValue, 3); } void ADS1256::setAutoCal(uint8_t acal) //Setting ACAL (Automatic SYSCAL) { _STATUS = readRegister(STATUS_REG); //Read the most recent value of the register if(acal == 0) { //Auto-calibration is disabled (default) bitWrite(_STATUS, 2, 0); //_STATUS |= B00000000; } else if(acal == 1) { //Auto-calibration is enabled bitWrite(_STATUS, 2, 1); //_STATUS |= B00000100; } else{} writeRegister(STATUS_REG, _STATUS); delay(100); } uint8_t ADS1256::getAutoCal() //Getting ACAL (Automatic SYSCAL) { uint8_t statusValue = readRegister(STATUS_REG); //Read the whole STATUS register return bitRead(statusValue, 2); } void ADS1256::setBuffer(uint8_t bufen) //Setting input buffer (Input impedance) { _STATUS = readRegister(STATUS_REG); //Read the most recent value of the register if(bufen == 0) { //Analog input buffer is disabled (default) //_STATUS |= B00000000; bitWrite(_STATUS, 1, 0); } else if(bufen == 1) { //Analog input buffer is enabled (recommended) //_STATUS |= B00000010; bitWrite(_STATUS, 1, 1); } else{} writeRegister(STATUS_REG, _STATUS); delay(100); } uint8_t ADS1256::getBuffer() //Getting input buffer (Input impedance) { uint8_t statusValue = readRegister(STATUS_REG); //Read the whole STATUS register return bitRead(statusValue, 1); } void ADS1256::setGPIO(uint8_t dir0, uint8_t dir1, uint8_t dir2, uint8_t dir3) //Setting GPIO { _GPIO = readRegister(IO_REG); //Read the most recent value of the register //Default: 11100000 - DEC: 224 - Ref: p32 I/O section //Sets D3-D0 as input or output uint8_t GPIO_bit7, GPIO_bit6, GPIO_bit5, GPIO_bit4; //Bit7: DIR3 if(dir3 == 1) { GPIO_bit7 = 1; //D3 is input (default) } else { GPIO_bit7 = 0; //D3 is output } bitWrite(_GPIO, 7, GPIO_bit7); //----------------------------------------------------- //Bit6: DIR2 if(dir2 == 1) { GPIO_bit6 = 1; //D2 is input (default) } else { GPIO_bit6 = 0; //D2 is output } bitWrite(_GPIO, 6, GPIO_bit6); //----------------------------------------------------- //Bit5: DIR1 if(dir1 == 1) { GPIO_bit5 = 1; //D1 is input (default) } else { GPIO_bit5 = 0; //D1 is output } bitWrite(_GPIO, 5, GPIO_bit5); //----------------------------------------------------- //Bit4: DIR0 if(dir0 == 1) { GPIO_bit4 = 1; //D0 is input } else { GPIO_bit4 = 0; //D0 is output (default) } bitWrite(_GPIO, 4, GPIO_bit4); //----------------------------------------------------- writeRegister(IO_REG, _GPIO); delay(100); } void ADS1256::writeGPIO(uint8_t dir0value, uint8_t dir1value, uint8_t dir2value, uint8_t dir3value) //Writing GPIO { _GPIO = readRegister(IO_REG); //Sets D3-D0 output values //It is important that first one must use setGPIO, then writeGPIO uint8_t GPIO_bit3, GPIO_bit2, GPIO_bit1, GPIO_bit0; //Bit3: DIR3 if(dir3value == 1) { GPIO_bit3 = 1; } else { GPIO_bit3 = 0; } bitWrite(_GPIO, 3, GPIO_bit3); //----------------------------------------------------- //Bit2: DIR2 if(dir2value == 1) { GPIO_bit2 = 1; } else { GPIO_bit2 = 0; } bitWrite(_GPIO, 2, GPIO_bit2); //----------------------------------------------------- //Bit1: DIR1 if(dir1value == 1) { GPIO_bit1 = 1; } else { GPIO_bit1 = 0; } bitWrite(_GPIO, 1, GPIO_bit1); //----------------------------------------------------- //Bit0: DIR0 if(dir0value == 1) { GPIO_bit0 = 1; } else { GPIO_bit0 = 0; } bitWrite(_GPIO, 0, GPIO_bit0); //----------------------------------------------------- writeRegister(IO_REG, _GPIO); delay(100); } uint8_t ADS1256::readGPIO(uint8_t gpioPin) //Reading GPIO { uint8_t GPIO_bit3, GPIO_bit2, GPIO_bit1, GPIO_bit0, GPIO_return; _GPIO = readRegister(IO_REG); //Read the GPIO register //Save each bit values in a variable GPIO_bit3 = bitRead(_GPIO, 3); GPIO_bit2 = bitRead(_GPIO, 2); GPIO_bit1 = bitRead(_GPIO, 1); GPIO_bit0 = bitRead(_GPIO, 0); delay(100); switch(gpioPin) //Selecting which value should be returned { case 0: GPIO_return = GPIO_bit0; break; case 1: GPIO_return = GPIO_bit1; break; case 2: GPIO_return = GPIO_bit2; break; case 3: GPIO_return = GPIO_bit3; break; } return GPIO_return; } void ADS1256::sendDirectCommand(uint8_t directCommand) { //Direct commands can be found in the datasheet Page 34, Table 24. _spi->beginTransaction(SPISettings(1920000, MSBFIRST, SPI_MODE1)); CS_LOW(); //REF: P34: "CS must stay low during the entire command sequence" delayMicroseconds(5); _spi->transfer(directCommand); //Send Command delayMicroseconds(5); CS_HIGH(); //REF: P34: "CS must stay low during the entire command sequence" _spi->endTransaction(); } float ADS1256::convertToVoltage(int32_t rawData) //Converting the 24-bit data into a voltage value { return(conversionParameter * rawData); } void ADS1256::writeRegister(uint8_t registerAddress, uint8_t registerValueToWrite) { waitForLowDRDY(); _spi->beginTransaction(SPISettings(1920000, MSBFIRST, SPI_MODE1)); //SPI_MODE1 = output edge: rising, data capture: falling; clock polarity: 0, clock phase: 1. CS_LOW(); //CS must stay LOW during the entire sequence [Ref: P34, T24] delayMicroseconds(5); //see t6 in the datasheet _spi->transfer(0x50 | registerAddress); // 0x50 = 01010000 = WREG _spi->transfer(0x00); //2nd (empty) command byte _spi->transfer(registerValueToWrite); //pass the value to the register CS_HIGH(); _spi->endTransaction(); } long ADS1256::readRegister(uint8_t registerAddress) //Reading a register { waitForLowDRDY(); _spi->beginTransaction(SPISettings(1920000, MSBFIRST, SPI_MODE1)); //SPI_MODE1 = output edge: rising, data capture: falling; clock polarity: 0, clock phase: 1. CS_LOW(); //CS must stay LOW during the entire sequence [Ref: P34, T24] _spi->transfer(0x10 | registerAddress); //0x10 = 0001000 = RREG - OR together the two numbers (command + address) _spi->transfer(0x00); //2nd (empty) command byte delayMicroseconds(5); //see t6 in the datasheet uint8_t regValue = _spi->transfer(0xFF); //read out the register value CS_HIGH(); _spi->endTransaction(); return regValue; } long ADS1256::readSingle() //Reading a single value ONCE using the RDATA command { _spi->beginTransaction(SPISettings(1920000, MSBFIRST, SPI_MODE1)); CS_LOW(); //REF: P34: "CS must stay low during the entire command sequence" waitForLowDRDY(); _spi->transfer(0b00000001); //Issue RDATA (0000 0001) command delayMicroseconds(7); //Wait t6 time (~6.51 us) REF: P34, FIG:30. _outputBuffer[0] = _spi->transfer(0); // MSB _outputBuffer[1] = _spi->transfer(0); // Mid-byte _outputBuffer[2] = _spi->transfer(0); // LSB //Shifting and combining the above three items into a single, 24-bit number _outputValue = ((long)_outputBuffer[0]<<16) | ((long)_outputBuffer[1]<<8) | (_outputBuffer[2]); _outputValue = convertSigned24BitToLong(_outputValue); CS_HIGH(); //We finished the command sequence, so we set CS to HIGH _spi->endTransaction(); return(_outputValue); } long ADS1256::readSingleContinuous() //Reads the recently selected input channel using RDATAC { if(_isAcquisitionRunning == false) { _isAcquisitionRunning = true; _spi->beginTransaction(SPISettings(1920000, MSBFIRST, SPI_MODE1)); CS_LOW(); //REF: P34: "CS must stay low during the entire command sequence" waitForLowDRDY(); _spi->transfer(0b00000011); //Issue RDATAC (0000 0011) delayMicroseconds(7); //Wait t6 time (~6.51 us) REF: P34, FIG:30. } else { waitForLowDRDY(); } _outputBuffer[0] = _spi->transfer(0); // MSB _outputBuffer[1] = _spi->transfer(0); // Mid-byte _outputBuffer[2] = _spi->transfer(0); // LSB _outputValue = ((long)_outputBuffer[0]<<16) | ((long)_outputBuffer[1]<<8) | (_outputBuffer[2]); _outputValue = convertSigned24BitToLong(_outputValue); waitForHighDRDY(); return _outputValue; } long ADS1256::cycleSingle() { if(_isAcquisitionRunning == false) { _isAcquisitionRunning = true; _cycle = 0; _spi->beginTransaction(SPISettings(1920000, MSBFIRST, SPI_MODE1)); CS_LOW(); //CS must stay LOW during the entire sequence [Ref: P34, T24] _spi->transfer(0x50 | 1); // 0x50 = WREG //1 = MUX _spi->transfer(0x00); _spi->transfer(SING_0); //AIN0+AINCOM CS_HIGH(); delay(50); CS_LOW(); //CS must stay LOW during the entire sequence [Ref: P34, T24] } else {} if(_cycle < 8) { _outputValue = 0; waitForLowDRDY(); //Step 1. - Updating MUX switch (_cycle) { //Channels are written manually case 0: //Channel 2 updateMUX(SING_1); //AIN1+AINCOM break; case 1: //Channel 3 updateMUX(SING_2); //AIN2+AINCOM break; case 2: //Channel 4 updateMUX(SING_3); //AIN3+AINCOM break; case 3: //Channel 5 updateMUX(SING_4); //AIN4+AINCOM break; case 4: //Channel 6 updateMUX(SING_5); //AIN5+AINCOM break; case 5: //Channel 7 updateMUX(SING_6); //AIN6+AINCOM break; case 6: //Channel 8 updateMUX(SING_7); //AIN7+AINCOM break; case 7: //Channel 1 updateMUX(SING_0); //AIN0+AINCOM break; } //Step 2. _spi->transfer(0b11111100); //SYNC delayMicroseconds(4); //t11 delay 24*tau = 3.125 us //delay should be larger, so we delay by 4 us _spi->transfer(0b11111111); //WAKEUP //Step 3. //Issue RDATA (0000 0001) command _spi->transfer(0b00000001); delayMicroseconds(7); //Wait t6 time (~6.51 us) REF: P34, FIG:30. _outputBuffer[0] = _spi->transfer(0x0F); // MSB _outputBuffer[1] = _spi->transfer(0x0F); // Mid-byte _outputBuffer[2] = _spi->transfer(0x0F); // LSB _outputValue = ((long)_outputBuffer[0]<<16) | ((long)_outputBuffer[1]<<8) | (_outputBuffer[2]); _outputValue = convertSigned24BitToLong(_outputValue); _cycle++; //Increase cycle - This will move to the next MUX input channel if(_cycle == 8) { _cycle = 0; //Reset to 0 - Restart conversion from the 1st input channel } } return _outputValue; } long ADS1256::cycleDifferential() { if(_isAcquisitionRunning == false) { _cycle = 0; _isAcquisitionRunning = true; _spi->beginTransaction(SPISettings(1920000, MSBFIRST, SPI_MODE1)); //Set the AIN0+AIN1 as inputs manually CS_LOW(); //CS must stay LOW during the entire sequence [Ref: P34, T24] _spi->transfer(0x50 | 1); // 0x50 = WREG //1 = MUX _spi->transfer(0x00); _spi->transfer(DIFF_0_1); //AIN0+AIN1 CS_HIGH(); delay(50); CS_LOW(); //CS must stay LOW during the entire sequence [Ref: P34, T24] } else {} if(_cycle < 4) { _outputValue = 0; //DRDY has to go low waitForLowDRDY(); //Step 1. - Updating MUX switch (_cycle) { case 0: //Channel 2 updateMUX(DIFF_2_3); //AIN2+AIN3 break; case 1: //Channel 3 updateMUX(DIFF_4_5); //AIN4+AIN5 break; case 2: //Channel 4 updateMUX(DIFF_6_7); //AIN6+AIN7 break; case 3: //Channel 1 updateMUX(DIFF_0_1); //AIN0+AIN1 break; } _spi->transfer(0b11111100); //SYNC delayMicroseconds(4); //t11 delay 24*tau = 3.125 us //delay should be larger, so we delay by 4 us _spi->transfer(0b11111111); //WAKEUP //Step 3. _spi->transfer(0b00000001); //Issue RDATA (0000 0001) command delayMicroseconds(7); //Wait t6 time (~6.51 us) REF: P34, FIG:30. _outputBuffer[0] = _spi->transfer(0); // MSB _outputBuffer[1] = _spi->transfer(0); // Mid-byte _outputBuffer[2] = _spi->transfer(0); // LSB _outputValue = ((long)_outputBuffer[0]<<16) | ((long)_outputBuffer[1]<<8) | (_outputBuffer[2]); _outputValue = convertSigned24BitToLong(_outputValue); _cycle++; if(_cycle == 4) { _cycle = 0; //After the 4th cycle, we reset to zero so the next iteration reads the 1st MUX again } } return _outputValue; } void ADS1256::updateConversionParameter() { conversionParameter = ((2.0 * _VREF) / 8388608.0) / (pow(2, _PGA)); //Calculate the "bit to Volts" multiplier //8388608 = 2^{23} - 1, REF: p23, Table 16. } void ADS1256::updateMUX(uint8_t muxValue) { _spi->transfer(0x50 | MUX_REG); //Write to the MUX register (0x50 is the WREG command) _spi->transfer(0x00); _spi->transfer(muxValue); //Write the new MUX value } inline void ADS1256::CS_LOW() { if (_CS_pin != PIN_UNUSED) //Sets CS LOW if it is not an unused pin { digitalWrite(_CS_pin, LOW); } } inline void ADS1256::CS_HIGH() { if (_CS_pin != PIN_UNUSED) //Sets CS HIGH if it is not an unused pin { digitalWrite(_CS_pin, HIGH); } }