diff --git a/RotaxMonitor/.gitignore b/RotaxMonitor/.gitignore new file mode 100644 index 0000000..89cc49c --- /dev/null +++ b/RotaxMonitor/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/RotaxMonitor/.vscode/extensions.json b/RotaxMonitor/.vscode/extensions.json new file mode 100644 index 0000000..080e70d --- /dev/null +++ b/RotaxMonitor/.vscode/extensions.json @@ -0,0 +1,10 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ], + "unwantedRecommendations": [ + "ms-vscode.cpptools-extension-pack" + ] +} diff --git a/RotaxMonitor/include/README b/RotaxMonitor/include/README new file mode 100644 index 0000000..49819c0 --- /dev/null +++ b/RotaxMonitor/include/README @@ -0,0 +1,37 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the convention is to give header files names that end with `.h'. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/RotaxMonitor/lib/README b/RotaxMonitor/lib/README new file mode 100644 index 0000000..9379397 --- /dev/null +++ b/RotaxMonitor/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into the executable file. + +The source code of each library should be placed in a separate directory +("lib/your_library_name/[Code]"). + +For example, see the structure of the following example libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional. for custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +Example contents of `src/main.c` using Foo and Bar: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +The PlatformIO Library Dependency Finder will find automatically dependent +libraries by scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/RotaxMonitor/platformio.ini b/RotaxMonitor/platformio.ini new file mode 100644 index 0000000..4af6d29 --- /dev/null +++ b/RotaxMonitor/platformio.ini @@ -0,0 +1,34 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:esp32-s3-devkitc1-n8r8] +platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.37/platform-espressif32.zip +board = esp32-s3-devkitc1-n8r8 +framework = arduino +lib_deps = + hideakitai/DebugLog@^0.8.4 + bblanchon/ArduinoJson@^7.4.2 +build_type = release + +[env:esp32-s3-devkitc1-n8r8-debug] +platform = ${env:esp32-s3-devkitc1-n8r8.platform} +board = ${env:esp32-s3-devkitc1-n8r8.board} +framework = ${env:esp32-s3-devkitc1-n8r8.framework} +lib_deps = ${env:esp32-s3-devkitc1-n8r8.lib_deps} + +build_type = debug +build_flags = + -O0 + -g3 + -ggdb + -fno-inline + -fno-ipa-sra + -fno-tree-sra + -fno-builtin diff --git a/RotaxMonitor/src/AD5292.cpp b/RotaxMonitor/src/AD5292.cpp new file mode 100644 index 0000000..348e041 --- /dev/null +++ b/RotaxMonitor/src/AD5292.cpp @@ -0,0 +1,40 @@ +/* + AD5292.h - AD5292 library for Arduino + MIT License + Copyright (c) 2023 Markus Kreitzer. + Website: https://github.com/markuskreitzer/AD5292 +*/ + +#include "AD5292.h" + +AD5292::AD5292(uint8_t csPin, uint32_t spiFreq) : _csPin(csPin), _spiFreq(spiFreq) {} + +void AD5292::begin() { + pinMode(_csPin, OUTPUT); + digitalWrite(_csPin, HIGH); + SPI.begin(); +} + +bool AD5292::setWiperPosition(uint16_t position) { + if (position > 1023) { + return false; // Invalid position value + } + + uint16_t command = (0x01 << 10) | (position & 0x03FF); + sendCommand(command); + delay(6); + + return true; // Successful operation +} + +void AD5292::sendCommand(uint16_t command) { + SPI.beginTransaction(SPISettings(_spiFreq, MSBFIRST, SPI_MODE1)); + digitalWrite(_csPin, LOW); + SPI.transfer16(command); + digitalWrite(_csPin, HIGH); + SPI.endTransaction(); +} + +void AD5292::setSpiFrequency(uint32_t spiFreq) { + _spiFreq = spiFreq; +} diff --git a/RotaxMonitor/src/AD5292.h b/RotaxMonitor/src/AD5292.h new file mode 100644 index 0000000..9a0dfce --- /dev/null +++ b/RotaxMonitor/src/AD5292.h @@ -0,0 +1,46 @@ +/* + AD5292.h - AD5292 library for Arduino + MIT License + Copyright (c) 2023 Markus Kreitzer. + Website: https://github.com/nostoslabs/AD5292 + +AD5292 Commands (From Datasheet): +DC - CONT - COMMAND +0) 00 - 0000 - XXXXXXXXXX Do Nothing +1) 00 - 0001 - NNNNNNNNNN Write To RDAC +2) 00 - 0010 - XXXXXXXXXX Read RDAC from SDO in next frame +3) 00 - 0011 - XXXXXXXXXX Store wiper setting: store RDAC setting to 20-TP memory. +4) 00 - 0100 - XXXXXXXXXX Reset: refresh RDAC with 20-TP stored value. +5) 00 - 0101 - XXXXXNNNNN Read contents of 20-TP memory, or status of 20-TP memory, from the SDO output in the next frame. +6) 00 - 0110 - XXXXXXNNNN Write contents of serial data to control register. +7) 00 - 0111 - XXXXXXXXXX Read control register from the SDO output in the next frame. +8) 00 - 1000 - XXXXXXXXXN Software shutdown. + N = 0 (normal mode). + N = 1 (device placed in shutdown mode). + +N: a digit in serial buffer (MSB) +X: Don't Care + +*/ + +#ifndef AD5292_H +#define AD5292_H + +#include +#include + +class AD5292 { +public: + AD5292(uint8_t csPin, uint32_t spiFreq = 1000000); + + void begin(); + bool setWiperPosition(uint16_t position); + void setSpiFrequency(uint32_t spiFreq); + +private: + uint8_t _csPin; + uint32_t _spiFreq; + void sendCommand(uint16_t command); +}; + +#endif // AD5292_H diff --git a/RotaxMonitor/src/ADS1256.cpp b/RotaxMonitor/src/ADS1256.cpp new file mode 100644 index 0000000..3372b47 --- /dev/null +++ b/RotaxMonitor/src/ADS1256.cpp @@ -0,0 +1,766 @@ +//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; + delay(200); +} + +void ADS1256::setMUX(uint8_t mux) //Setting MUX (input channel) +{ + writeRegister(MUX_REG, mux); + _MUX = mux; + delay(200); +} + +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); + delay(200); + + 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(); + delay(100); + +} + +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(); + delay(100); + 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); + } +} \ No newline at end of file diff --git a/RotaxMonitor/src/ADS1256.h b/RotaxMonitor/src/ADS1256.h new file mode 100644 index 0000000..1ded2e2 --- /dev/null +++ b/RotaxMonitor/src/ADS1256.h @@ -0,0 +1,189 @@ +//ADS1256 header file +/* + Name: ADS1256.h + 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 +*/ + +#ifndef _ADS1256_h +#define _ADS1256_h + +#include + +//Differential inputs +#define DIFF_0_1 0b00000001 //A0 + A1 as differential input +#define DIFF_2_3 0b00100011 //A2 + A3 as differential input +#define DIFF_4_5 0b01000101 //A4 + A5 as differential input +#define DIFF_6_7 0b01100111 //A6 + A7 as differential input + +//Single-ended inputs +#define SING_0 0b00001111 //A0 + GND (common) as single-ended input +#define SING_1 0b00011111 //A1 + GND (common) as single-ended input +#define SING_2 0b00101111 //A2 + GND (common) as single-ended input +#define SING_3 0b00111111 //A3 + GND (common) as single-ended input +#define SING_4 0b01001111 //A4 + GND (common) as single-ended input +#define SING_5 0b01011111 //A5 + GND (common) as single-ended input +#define SING_6 0b01101111 //A6 + GND (common) as single-ended input +#define SING_7 0b01111111 //A7 + GND (common) as single-ended input + +//PGA settings //Input voltage range +#define PGA_1 0b00000000 //± 5 V +#define PGA_2 0b00000001 //± 2.5 V +#define PGA_4 0b00000010 //± 1.25 V +#define PGA_8 0b00000011 //± 625 mV +#define PGA_16 0b00000100 //± 312.5 mV +#define PGA_32 0b00000101 //+ 156.25 mV +#define PGA_64 0b00000110 //± 78.125 mV + +//Datarate //DEC +#define DRATE_30000SPS 0b11110000 //240 +#define DRATE_15000SPS 0b11100000 //224 +#define DRATE_7500SPS 0b11010000 //208 +#define DRATE_3750SPS 0b11000000 //192 +#define DRATE_2000SPS 0b10110000 //176 +#define DRATE_1000SPS 0b10100001 //161 +#define DRATE_500SPS 0b10010010 //146 +#define DRATE_100SPS 0b10000010 //130 +#define DRATE_60SPS 0b01110010 //114 +#define DRATE_50SPS 0b01100011 //99 +#define DRATE_30SPS 0b01010011 //83 +#define DRATE_25SPS 0b01000011 //67 +#define DRATE_15SPS 0b00110011 //51 +#define DRATE_10SPS 0b00100011 //35 +#define DRATE_5SPS 0b00010011 //19 +#define DRATE_2SPS 0b00000011 //3 + +//Status register +#define BITORDER_MSB 0 +#define BITORDER_LSB 1 +#define ACAL_DISABLED 0 +#define ACAL_ENABLED 1 +#define BUFFER_DISABLED 0 +#define BUFFER_ENABLED 1 + +//Register addresses +#define STATUS_REG 0x00 +#define MUX_REG 0x01 +#define ADCON_REG 0x02 +#define DRATE_REG 0x03 +#define IO_REG 0x04 +#define OFC0_REG 0x05 +#define OFC1_REG 0x06 +#define OFC2_REG 0x07 +#define FSC0_REG 0x08 +#define FSC1_REG 0x09 +#define FSC2_REG 0x0A + +//Command definitions +#define WAKEUP 0b00000000 +#define RDATA 0b00000001 +#define RDATAC 0b00000011 +#define SDATAC 0b00001111 +#define RREG 0b00010000 +#define WREG 0b01010000 +#define SELFCAL 0b11110000 +#define SELFOCAL 0b11110001 +#define SELFGCAL 0b11110010 +#define SYSOCAL 0b11110011 +#define SYSGCAL 0b11110100 +#define SYNC 0b11111100 +#define STANDBY 0b11111101 +#define RESET 0b11111110 +//---------------------------------------------------------------- + + +class ADS1256 +{ +public: +static constexpr int8_t PIN_UNUSED = -1; + + //Constructor + 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); + + //Initializing function + void InitializeADC(); + //ADS1256(int drate, int pga, int byteOrder, bool bufen); + + //Read a register + long readRegister(uint8_t registerAddress); + + //Write a register + void writeRegister(uint8_t registerAddress, uint8_t registerValueToWrite); + + //Individual methods + void setDRATE(uint8_t drate); + void setPGA(uint8_t pga); + uint8_t getPGA(); + void setMUX(uint8_t mux); + void setByteOrder(uint8_t byteOrder); + uint8_t getByteOrder(); + void setBuffer(uint8_t bufen); + uint8_t getBuffer(); + void setAutoCal(uint8_t acal); + uint8_t getAutoCal(); + void setGPIO(uint8_t dir0, uint8_t dir1, uint8_t dir2, uint8_t dir3); + void writeGPIO(uint8_t dir0value, uint8_t dir1value, uint8_t dir2value, uint8_t dir3value); + uint8_t readGPIO(uint8_t gpioPin); + void setCLKOUT(uint8_t clkout); + void setSDCS(uint8_t sdcs); + void sendDirectCommand(uint8_t directCommand); + + //Get a single conversion + long readSingle(); + + //Single input continuous reading + long readSingleContinuous(); + + //Cycling through the single-ended inputs + long cycleSingle(); //Ax + COM + + //Cycling through the differential inputs + long cycleDifferential(); //Ax + Ay + + //Converts the reading into a voltage value + float convertToVoltage(int32_t rawData); + + //Stop AD + void stopConversion(); + +private: + +SPIClass* _spi; //Pointer to an SPIClass object + +void waitForLowDRDY(); // Block until DRDY is low +void waitForHighDRDY(); // Block until DRDY is high +void updateMUX(uint8_t muxValue); +inline void CS_LOW(); +inline void CS_HIGH(); + +void updateConversionParameter(); //Refresh the conversion parameter based on the PGA + +float _VREF = 0; //Value of the reference voltage +float conversionParameter = 0; //PGA-dependent multiplier +//Pins +int8_t _DRDY_pin; //Pin assigned for DRDY +int8_t _RESET_pin; //Pin assigned for RESET +int8_t _SYNC_pin; //Pin assigned for SYNC +int8_t _CS_pin; //Pin assigned for CS + +//Register values +byte _DRATE; //Value of the DRATE register +byte _ADCON; //Value of the ADCON register +byte _MUX; //Value of the MUX register +byte _PGA; //Value of the PGA (within ADCON) +byte _GPIO; //Value of the GPIO register +byte _STATUS; //Value of the status register +byte _GPIOvalue; //GPIO value +byte _ByteOrder; //Byte order + +byte _outputBuffer[3]; //3-byte (24-bit) buffer for the fast acquisition - Single-channel, continuous +long _outputValue; //Combined value of the _outputBuffer[3] +bool _isAcquisitionRunning; //bool that keeps track of the acquisition (running or not) +uint8_t _cycle; //Tracks the cycles as the MUX is cycling through the input channels +}; +#endif \ No newline at end of file diff --git a/RotaxMonitor/src/channels.h b/RotaxMonitor/src/channels.h new file mode 100644 index 0000000..f305685 --- /dev/null +++ b/RotaxMonitor/src/channels.h @@ -0,0 +1,11 @@ +// ADC Channels + +#define A1_RAW 0 +#define A2_RAW 1 +#define B1_RAW 2 +#define B2_RAW 3 + +#define A1_COND 4 +#define A2_COND 5 +#define B1_COND 6 +#define B2_COND 7 diff --git a/RotaxMonitor/src/isr.h b/RotaxMonitor/src/isr.h new file mode 100644 index 0000000..b16cfb7 --- /dev/null +++ b/RotaxMonitor/src/isr.h @@ -0,0 +1,40 @@ +#include + +// ===================== +// Event Flags (bitmask) +// ===================== +#define PKDT_FLAG_AP (1 << 0) +#define PKDT_FLAG_AN (1 << 1) +#define PKDT_FLAG_BP (1 << 2) +#define PKDT_FLAG_BN (1 << 3) + +// Task handle +TaskHandle_t pkdtTaskHandle = NULL; + +// ===================== +// ISR (Pass return bitmask to ISR management function) +// one function for each wake up pin conncted to a trigger +// ===================== +void IRAM_ATTR pkdt_isr_ap() { + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + xTaskNotifyFromISR(pkdtTaskHandle, PKDT_FLAG_AP, eSetBits, &xHigherPriorityTaskWoken); + portYIELD_FROM_ISR(xHigherPriorityTaskWoken); +} + +void IRAM_ATTR pkdt_isr_an() { + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + xTaskNotifyFromISR(pkdtTaskHandle, PKDT_FLAG_AN, eSetBits, &xHigherPriorityTaskWoken); + portYIELD_FROM_ISR(xHigherPriorityTaskWoken); +} + +void IRAM_ATTR pkdt_isr_bp() { + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + xTaskNotifyFromISR(pkdtTaskHandle, PKDT_FLAG_BP, eSetBits, &xHigherPriorityTaskWoken); + portYIELD_FROM_ISR(xHigherPriorityTaskWoken); +} + +void IRAM_ATTR pkdt_isr_bn() { + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + xTaskNotifyFromISR(pkdtTaskHandle, PKDT_FLAG_BN, eSetBits, &xHigherPriorityTaskWoken); + portYIELD_FROM_ISR(xHigherPriorityTaskWoken); +} diff --git a/RotaxMonitor/src/main.cpp b/RotaxMonitor/src/main.cpp new file mode 100644 index 0000000..0919746 --- /dev/null +++ b/RotaxMonitor/src/main.cpp @@ -0,0 +1,88 @@ +#define DEBUGLOG_DEFAULT_LOG_LEVEL_INFO + +// Arduino Libraries +#include +#include +#include +#include + +// Definitions +#include +#include +#include + +// Device Libraries +#include +#include + +void pkdtTask(void *pvParameters) { + uint32_t notifiedValue; + + while (true) { + // attende eventi + xTaskNotifyWait( + 0x00, // non pulire all'ingresso + 0xFFFFFFFF, // pulisci tutti i bit all'uscita + ¬ifiedValue, // valore ricevuto + portMAX_DELAY + ); + + // 🔥 QUI GIRA SU CORE 0 + switch (notifiedValue) + case PKDT_FLAG_AP: { + handlePKDT(PKDT_AP); + break; + } + default: + LOG_ERROR("Invalid Interrupt: ", notifiedValue); + } +} + +void setup() { + Serial.begin(9600); + LOG_ATTACH_SERIAL(Serial); + LOG_SET_LEVEL(DebugLogLevel::LVL_INFO); + LOG_INFO("ESP32 Chip:", ESP.getChipModel()); + LOG_INFO("ESP32 PSram:", ESP.getPsramSize()); + LOG_INFO("ESP32 Flash:", ESP.getFlashChipSize()); + LOG_INFO("ESP32 Heap:", ESP.getHeapSize()); + LOG_INFO("ESP32 Sketch:", ESP.getFreeSketchSpace()); + + // Initialize Interrupt pins on peak detectors + pinMode(PKDT_AP, INPUT_PULLDOWN); + pinMode(PKDT_AN, INPUT_PULLDOWN); + pinMode(PKDT_BP, INPUT_PULLDOWN); + pinMode(PKDT_BN, INPUT_PULLDOWN); + // interrupt + attachInterrupt(PKDT_AP, pkdt_isr_ap, RISING); + attachInterrupt(PKDT_AN, pkdt_isr_an, RISING); + attachInterrupt(PKDT_BP, pkdt_isr_bp, RISING); + attachInterrupt(PKDT_BN, pkdt_isr_bn, RISING); + + // Init SPI interface + SPI.begin(); + + // Init ADC + auto adc = ADS1256(ADC_DRDY, ADC_RST, ADC_SYNC, ADC_CS, 0.0, SPI); + ADS1256. +} + +void loop() { + // task su core 0 + auto isrTask = xTaskCreatePinnedToCore( + pkdtTask, + "pkdtTask", + 4096, + NULL, + 2, // priorità leggermente più alta + &pkdtTaskHandle, + 0 + ); + + if (isrTask != pdPASS){ + LOG_ERROR("Unble to initialize ISR task"); + } + +} + + diff --git a/RotaxMonitor/src/pins.h b/RotaxMonitor/src/pins.h new file mode 100644 index 0000000..540e64c --- /dev/null +++ b/RotaxMonitor/src/pins.h @@ -0,0 +1,67 @@ +// ===================== +// SPI BUS +// ===================== +#define SPI_MOSI 11 +#define SPI_MISO 13 +#define SPI_SCK 12 + +// ===================== +// CHIP SELECT +// ===================== +#define ADC_CS 10 +#define POT_CS 9 + +// ===================== +// ADC CONTROL +// ===================== +#define ADC_DRDY 4 +#define ADC_RST 5 +#define ADC_SYNC 6 + +// ===================== +// DIGITAL POT +// ===================== +#define POT_DRDY 7 + +// ===================== +// RELAY +// ===================== +#define PICK_RELAY 8 + +// ===================== +// PEAK DETECTORS (DIGITAL INPUT) +// ===================== +#define PKDT_AP 1 +#define PKDT_AN 2 +#define PKDT_BP 3 +#define PKDT_BN 14 + +// ===================== +// TRIGGER INPUTS +// ===================== +#define TRIG_AP 15 +#define TRIG_AN 16 +#define TRIG_BP 17 +#define TRIG_BN 18 + +// ===================== +// SOFT START DETECT +// ===================== +#define SOFT_A 21 +#define SOFT_B 47 + +// ===================== +// STATUS OUTPUT +// ===================== +#define STA_1 35 +#define STA_2 36 +#define STA_3 37 +#define STA_4 38 + +// ===================== +// BUTTON INPUT +// ===================== +#define BTN_1 39 +#define BTN_2 40 +#define BTN_3 41 +#define BTN_4 42 diff --git a/RotaxMonitor/test/README b/RotaxMonitor/test/README new file mode 100644 index 0000000..9b1e87b --- /dev/null +++ b/RotaxMonitor/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Test Runner and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html diff --git a/esp32-c3-devkit-m1.png b/esp32-c3-devkit-m1.png new file mode 100644 index 0000000..6813b7e Binary files /dev/null and b/esp32-c3-devkit-m1.png differ