Added libraries and firmware skeleton

This commit is contained in:
Emanuele Trabattoni
2026-03-24 17:07:49 +01:00
parent 9240a0be41
commit 948120c001
15 changed files with 1390 additions and 0 deletions

5
RotaxMonitor/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
.pio
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch

10
RotaxMonitor/.vscode/extensions.json vendored Normal file
View File

@@ -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"
]
}

View File

@@ -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

46
RotaxMonitor/lib/README Normal file
View File

@@ -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 <Foo.h>
#include <Bar.h>
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

View File

@@ -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

View File

@@ -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;
}

46
RotaxMonitor/src/AD5292.h Normal file
View File

@@ -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 <Arduino.h>
#include <SPI.h>
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

View File

@@ -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);
}
}

189
RotaxMonitor/src/ADS1256.h Normal file
View File

@@ -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 <SPI.h>
//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

View File

@@ -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

40
RotaxMonitor/src/isr.h Normal file
View File

@@ -0,0 +1,40 @@
#include <Arduino.h>
// =====================
// 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);
}

88
RotaxMonitor/src/main.cpp Normal file
View File

@@ -0,0 +1,88 @@
#define DEBUGLOG_DEFAULT_LOG_LEVEL_INFO
// Arduino Libraries
#include <Arduino.h>
#include <DebugLog.h>
#include <DebugLogEnable.h>
#include <SPI.h>
// Definitions
#include <isr.h>
#include <pins.h>
#include <channels.h>
// Device Libraries
#include <ADS1256.h>
#include <AD5292.h>
void pkdtTask(void *pvParameters) {
uint32_t notifiedValue;
while (true) {
// attende eventi
xTaskNotifyWait(
0x00, // non pulire all'ingresso
0xFFFFFFFF, // pulisci tutti i bit all'uscita
&notifiedValue, // 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");
}
}

67
RotaxMonitor/src/pins.h Normal file
View File

@@ -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

11
RotaxMonitor/test/README Normal file
View File

@@ -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

BIN
esp32-c3-devkit-m1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 KiB