in a project I want to use 3 different accelaration sensors over I2C (ADXL343, ADXL313 and MMA8451). They are connected to an Arduino Nano ESP32 (ESP32-S3). I'm using Arduino IDE V2.2.1 to programm the Nano ESP.
For reaching high data rate I want to use both I2C ports. In my code I'm using the integrated FIFO from these sensors to read 20 samples fast (something like "bulk read"). The sensors creating a signal when there are 20 new values in FIFO. So the signal forces an interrupt on ESP.
The following description and example uses just two sensors (ADXL313 and ADXL343).
When I create a tasks for each sensor an put them on differenet cores everything works fine. But when I create them on the same core the both I2C ports seems to be "synced". The "bulk read" is only fast when both ports are reading, so there is a timing problem when using different data rates. The following records showing the problem. (upper port (green signal) works with a sample rate of 400 Hz, the bottom (purple signal) with 800 Hz) The thick vertical event in the signal is a bulk read with 20 fast readings, the thin line is just a single read. The first record shows the desired behaviour. The bulk readings are indipendent. In the second record, the bulk readings are synced and only working when both ports are communicating. When only one port communicates there are delays between each single read until the other port is reading too. Then fast read is performed.
When changing task prioritys, the single reads aren't there anymore, but communication is only executed, when both ports are reading, not at interrupt, when it should be done. Here is an example code:
For bulk reading I added a function to wire lib (Wire.cpp) called fastRequestFrom. It directly calls the I2C driver function 'i2c_master_write_read_device' without any start and stop. At debug I see, that this function itself produces the delay between the single reads when using both I2C ports on same core. Nothing else is blocking fast code execution. The full function is
Code: Select all
size_t TwoWire::fastRequestFrom(uint16_t address, size_t size, uint8_t reg, uint8_t counts, uint8_t *data)
{
beginTransmission(address);
txBuffer[0] = reg;
txLength = 1;
//size_t _rxLength = 0;
nonStopTask = xTaskGetCurrentTaskHandle();
esp_err_t ret = ESP_FAIL;
rxIndex = 0;
rxLength = 0;
int i = 0;
if(nonStopTask == xTaskGetCurrentTaskHandle()){
while(i < counts){
ret = i2c_master_write_read_device(num, address, txBuffer, txLength, &data[i*6], size, _timeOutMillis/ portTICK_RATE_MS);
if(ret == ESP_OK){
rxLength += size;
}
i++;
}
}
xSemaphoreGive(lock);
return rxLength;
}
Code: Select all
#define ADXL313_BW ADXL313_BW_400 // gleiches Register und Wert auch für ADXL_343
#define ADXL343_BW ADXL343_BW_200 // gleiches Register und Wert auch für ADXL_343
#define WATERMARK_ADXL 20
#define COUNTS_REQUEST 20
// Mapping for ADXL313 Lib compatibility
#define ADXL343_FIFO_MODE_BYPASS 0x00
#define ADXL343_FIFO_MODE_STREAM 0x02
#define ADXL343_FIFO_CTL ADXL345_FIFO_CTL
#define ADXL343_INT1_PIN ADXL345_INT1_PIN
#define ADXL343_DATA_FORMAT ADXL345_DATA_FORMAT
#define ADXL343_DEVICE 0x53
#define ADXL343_DATA_X0 ADXL345_DATAX0
#define ADXL343_POWER_CTL ADXL345_POWER_CTL
#define ADXL343_MEASURE_BIT 0x03
#define ADXL343_AUTOSLEEP_BIT 0x04
#define ADXL343_INT_DATA_READY_BIT ADXL345_INT_DATA_READY_BIT
#define ADXL343_INT_WATERMARK_BIT ADXL345_INT_WATERMARK_BIT
#define ADXL343_INT_OVERRUNY_BIT ADXL345_INT_OVERRUNY_BIT
/********************** RANGE SETTINGS OPTIONS **********************/
#define ADXL343_RANGE_2_G 2 // 0-0.5G
#define ADXL343_RANGE_4_G 4 // 0-1G
#define ADXL343_RANGE_8_G 8 // 0-2G
#define ADXL343_RANGE_16_G 16 // 0-4G
/********************** BANDWIDTH RATE CODES (HZ) *******************/
#define ADXL343_BW_1600 0xF // 1111 IDD = 170uA
#define ADXL343_BW_800 0xE // 1110 IDD = 115uA
#define ADXL343_BW_400 0xD // 1101 IDD = 170uA
#define ADXL343_BW_200 0xC // 1100 IDD = 170uA (115 low power)
#define ADXL343_BW_100 0xB // 1011 IDD = 170uA (82 low power)
#define ADXL343_BW_50 0xA // 1010 IDD = 170uA (64 in low power)
#define ADXL343_BW_25 0x9 // 1001 IDD = 115uA (57 in low power)
#define ADXL343_BW_12_5 0x8 // 1000 IDD = 82uA (50 in low power)
#define ADXL343_BW_6_25 0x7 // 0111 IDD = 65uA (43 in low power)
#define ADXL343_BW_3_125 0x6 // 0110 IDD = 57uA
#define ADXL313_BYTES_PER_CH 2
#define N_CH 3 // Anzahl der Channel
#define ADXL313_COUNTS_PER_REQUEST COUNTS_REQUEST // Anzahl der Messungen
#define ADXL313_REQUESTS 32 // Anzahl der Messungen
#define ADXL343_BYTES_PER_CH 2
#define N_CH 3 // Anzahl der Channel
#define ADXL343_COUNTS_PER_REQUEST COUNTS_REQUEST // Anzahl der Messungen
#define ADXL343_REQUESTS 32 // Anzahl der Messungen
#define CORE_0 0
#define CORE_1 1
#include <Wire.h>
#include <SparkFunADXL313.h> //Click here to get the library: http://librarymanager/All#SparkFun_ADXL313
ADXL313 accADXL313;
#include <SparkFun_ADXL345.h> //intern uses Wire (not Wire1!)
ADXL345 accADXL343 = ADXL345();
uint8_t buffADXL313[ADXL313_REQUESTS * ADXL313_COUNTS_PER_REQUEST * ADXL313_BYTES_PER_CH * N_CH * 2]; // 32 Abfragen in 100 ms (3200 Hz) * 20 Messwerte pro Abfrage (Aber Schwellwert bei 15 Werten!) * 3 Kanäle * je 2 Byte * 2 für Lesen und schreiben (eine Hälfte wird gefüllte während die gefüllte gelesen und gelöscht wird)
volatile byte reqADXL313 = 0; // zählt die Abfragen!
volatile bool bufPartADXL313 = 0; // Zeigt an welcher der Beiden Puffer gerade beschrieben wird
uint8_t buffADXL343[ADXL343_REQUESTS * ADXL343_COUNTS_PER_REQUEST * ADXL343_BYTES_PER_CH * N_CH * 2]; // 32 Abfragen in 100 ms (3200 Hz) * 20 Messwerte pro Abfrage (Aber Schwellwert bei 15 Werten!) * 3 Kanäle * je 2 Byte * 2 für Lesen und schreiben (eine Hälfte wird gefüllte während die gefüllte gelesen und gelöscht wird)
volatile byte reqADXL343 = 0; // zählt die Abfragen!
volatile bool bufPartADXL343 = 0; // Zeigt an welcher der Beiden Puffer gerade beschrieben wird
volatile bool interruptFlagADXL313 = false; // global variabl to keep track of new interrupts. Only ever set true by ISR
volatile bool interruptFlagADXL343 = false; // global variabl to keep track of new interrupts. Only ever set true by ISR
unsigned long lastWatermarkTime; // used for printing timestamps in debug
// watermark has caused an interrupt
void IRAM_ATTR ADXL313_ISR()
{
interruptFlagADXL313 = true;
}
void IRAM_ATTR ADXL343_ISR()
{
interruptFlagADXL343 = true;
}
void setup()
{
pinMode(D3, INPUT); // setup for interrupt 313
pinMode(A6, INPUT); // setup for interrupt 343
pinMode(LED_BUILTIN, OUTPUT); // setup for LED-debug
delay(1000);
xTaskCreatePinnedToCore(readAcc343, "Sensoren auslesen" // A name just for humans
, 2048 // The stack size can be checked by calling `uxHighWaterMark = uxTaskGetStackHighWaterMark(NULL);`
, NULL // Task parameter which can modify the task behavior. This must be passed as pointer to void.
,4, NULL, CORE_0);
xTaskCreatePinnedToCore(readAcc313, "Sensoren auslesen" // A name just for humans
, 2048 // The stack size can be checked by calling `uxHighWaterMark = uxTaskGetStackHighWaterMark(NULL);`
, NULL // Task parameter which can modify the task behavior. This must be passed as pointer to void.
,4, NULL, CORE_0);
}
void loop(){
}
void readAcc313(void *pvParameters)
{
(void)pvParameters;
Wire1.begin(D4, D5, 1000000); // ADXL 313
configureADXL313();
delay(50);
accADXL313.measureModeOn(); // unset standby --> measurement mode on
while(1){
if (interruptFlagADXL313 == true) // FIFO watermark reached (this variable is only ever set true in int1_ISR)
{
interruptFlagADXL313 = false;
readI2C313(&Wire1, reqADXL313);
reqADXL313++;
reqADXL313 &= 0x3F;
}
}
}
void readAcc343(void *pvParameters)
{
(void)pvParameters;
Wire.begin(A4, A5, 1000000); // ADXL343
configureADXL343();
delay(50);
setRegisterBit(ADXL343_POWER_CTL, ADXL343_MEASURE_BIT, true); // unset standby --> measurement mode on
while(1){
if (interruptFlagADXL343 == true) // FIFO watermark reached (this variable is only ever set true in int1_ISR)
{
interruptFlagADXL343 = false;
readI2C343(&Wire, reqADXL343);
reqADXL343++;
reqADXL343 &= 0x3F;;
}
}
}
size_t readI2C313(TwoWire * _I2C, byte req) {
//byte nVals = _I2C->fastRequestFrom((uint16_t)ADXL313_I2C_ADDRESS_DEFAULT, ADXL313_BYTES_PER_CH * N_CH, ADXL313_DATA_X0, ADXL313_COUNTS_PER_REQUEST, &buffADXL313[req * ADXL313_BYTES_PER_CH * N_CH * ADXL313_COUNTS_PER_REQUEST]); // Request 6 Bytes
byte nVals = _I2C->fastRequestFrom((uint16_t)ADXL313_I2C_ADDRESS_DEFAULT, ADXL313_BYTES_PER_CH * N_CH, ADXL313_DATA_X0, ADXL313_COUNTS_PER_REQUEST, &buffADXL313[req * ADXL313_BYTES_PER_CH * N_CH * ADXL313_COUNTS_PER_REQUEST]); // Request 6 Bytes
return (byte)nVals;
}
size_t readI2C343(TwoWire * _I2C, byte req) {
byte nVals = _I2C->fastRequestFrom((uint16_t)ADXL343_DEVICE, ADXL343_BYTES_PER_CH * N_CH, ADXL343_DATA_X0, ADXL343_COUNTS_PER_REQUEST, &buffADXL343[req * ADXL343_BYTES_PER_CH * N_CH * ADXL343_COUNTS_PER_REQUEST]); // Request 6 Bytes
return (byte)nVals;
}
void configureADXL313(void){
if (accADXL313.begin(ADXL313_I2C_ADDRESS_DEFAULT, Wire1) == false) //Begin communication over I2C
{
Serial.println("The sensor did not respond. Please check wiring.");
while (1); //Freeze
}
accADXL313.standby(); // Must be in standby before changing settings.
accADXL313.setRange(ADXL313_RANGE_4_G);
accADXL313.setBandwidth(ADXL313_BW);
// setup activity sensing options
accADXL313.setActivityX(false); // disable x-axis participation in detecting activity
accADXL313.setActivityY(false); // disable y-axis participation in detecting activity
accADXL313.setActivityZ(false); // disable z-axis participation in detecting activity
// setup inactivity sensing options
accADXL313.setInactivityX(false); // disable x-axis participation in detecting inactivity
accADXL313.setInactivityY(false); // disable y-axis participation in detecting inactivity
accADXL313.setInactivityZ(false); // disable z-axis participation in detecting inactivity
// FIFO SETUP
accADXL313.setFifoMode(ADXL313_FIFO_MODE_STREAM);
accADXL313.setFifoSamplesThreshhold(WATERMARK_ADXL); // 1-32 // Interrupt bei 15 Werten und dann 20 Werte auslesen
accADXL313.setInterruptMapping(ADXL313_INT_WATERMARK_BIT, ADXL313_INT1_PIN);
// enable/disable interrupts
// note, we set them all here, just in case there were previous settings,
// that need to be changed for this example to work properly.
accADXL313.InactivityINT(false); // disable inactivity
accADXL313.ActivityINT(false); // disable activity
accADXL313.DataReadyINT(false); // disable dataReady
accADXL313.WatermarkINT(true); // enable fifo watermark
accADXL313.autosleepOff(); // just in case it was set from a previous setup
accADXL313.lowPowerOff();
accADXL313.setFullResBit(true);
accADXL313.clearFifo(); // clear FIFO for a fresh start on this example.
attachInterrupt(digitalPinToInterrupt(D3), ADXL313_ISR, RISING); // note, the INT output on the ADXL313 is default active HIGH.
//accADXL313.printAllRegister();
}
void configureADXL343(void){
accADXL343.powerOn();
setRegisterBit(ADXL343_POWER_CTL, ADXL343_MEASURE_BIT, false); // Set standby --> measurement mode off
accADXL343.setRangeSetting(ADXL343_RANGE_4_G);
accADXL343.set_bw(ADXL343_BW);
// setup activity sensing options
accADXL343.setActivityX(false); // disable x-axis participation in detecting activity
accADXL343.setActivityY(false); // disable y-axis participation in detecting activity
accADXL343.setActivityZ(false); // disable z-axis participation in detecting activity
// setup inactivity sensing options
accADXL343.setInactivityX(false); // disable x-axis participation in detecting inactivity
accADXL343.setInactivityY(false); // disable y-axis participation in detecting inactivity
accADXL343.setInactivityZ(false); // disable z-axis participation in detecting inactivity
// FIFO SETUP
setFifoMode(ADXL343_FIFO_MODE_STREAM);
setFifoSamplesThreshhold(WATERMARK_ADXL); // 1-32 // Interrupt bei 15 Werten und dann 20 Werte auslesen
accADXL343.setInterruptMapping(ADXL343_INT_WATERMARK_BIT, ADXL343_INT1_PIN);
// enable/disable interrupts
// note, we set them all here, just in case there were previous settings,
// that need to be changed for this example to work properly.
accADXL343.InactivityINT(false); // disable inactivity
accADXL343.ActivityINT(false); // disable activity
accADXL343.FreeFallINT(false); // disable inactivity
accADXL343.doubleTapINT(false); // disable activity
accADXL343.singleTapINT(false); // disable inactivity
DataReadyINT(false); // disable dataReady
OverrunINT(false); // disable overrun
WatermarkINT(true); // enable fifo wat-***ermark
setRegisterBit(ADXL343_POWER_CTL, ADXL343_AUTOSLEEP_BIT, false);; // just in case it was set from a previous setup
accADXL343.setLowPower(false);
accADXL343.setFullResBit(true);
//Clear FIFO
byte mode = getFifoMode(); // get current mode
setFifoMode(ADXL343_FIFO_MODE_BYPASS); // set mode to bypass temporarily to clear contents
setFifoMode(mode); // return mode to previous selection.; // clear FIFO for a fresh start on this example.
attachInterrupt(digitalPinToInterrupt(A6), ADXL343_ISR, RISING); // note, the INT output on the ADXL343 is default active HIGH.
}
/* ----------------------------------------ADXL343 Helper-Functions--------------------------------------*/
/* ------------------------------------------------------------------------------------------------------*/
/* ------------------------------------------------------------------------------------------------------*/
/* ------------------------------------------------------------------------------------------------------*/
/* ------------------------------------------------------------------------------------------------------*/
void setFifoMode(byte mode){
byte _s = (mode << 6);
byte _b;
readFromI2C(ADXL343_FIFO_CTL, 1, &_b);
_s |= (_b & 0b00111111);
writeToI2C(ADXL343_FIFO_CTL, _s);
}
byte getFifoMode(){
byte _b;
readFromI2C(ADXL343_FIFO_CTL, 1, &_b);
byte mode = (_b & 0b11000000);
mode = (mode >> 6);
return mode;
}
void setFifoSamplesThreshhold(byte samples) {
byte _s = samples;
byte _b;
readFromI2C(ADXL343_FIFO_CTL, 1, &_b);
_s |= (_b & 0b11100000);
writeToI2C(ADXL343_FIFO_CTL, _s);
}
void DataReadyINT(bool status) {
if(status) {
accADXL343.setInterrupt( ADXL343_INT_DATA_READY_BIT, 1);
}
else {
accADXL343.setInterrupt( ADXL343_INT_DATA_READY_BIT, 0);
}
}
void OverrunINT(bool status) {
if(status) {
accADXL343.setInterrupt(ADXL343_INT_OVERRUNY_BIT, 1);
}
else {
accADXL343.setInterrupt(ADXL343_INT_OVERRUNY_BIT, 0);
}
}
void WatermarkINT(bool status) {
if(status) {
accADXL343.setInterrupt( ADXL343_INT_WATERMARK_BIT, 1);
}
else {
accADXL343.setInterrupt( ADXL343_INT_WATERMARK_BIT, 0);
}
}
void writeToI2C(byte _add, byte _val){
Wire.beginTransmission(ADXL343_DEVICE);
Wire.write(_add);
Wire.write(_val);
Wire.endTransmission();
}
void readFromI2C(byte _add, int _num, byte _buff[]) {
Wire.beginTransmission(ADXL343_DEVICE);
Wire.write(_add);
Wire.endTransmission();
Wire.requestFrom(ADXL343_DEVICE, _num); // Request 6 Bytes
int i = 0;
while(Wire.available())
{
_buff[i] = Wire.read(); // Receive Byte
i++;
}
}
void setRegisterBit(byte regAdress, int bitPos, bool state) {
byte _b;
readFromI2C(regAdress, 1, &_b);
if (state) {
_b |= (1 << bitPos); // Forces nth Bit of _b to 1. Other Bits Unchanged.
}
else {
_b &= ~(1 << bitPos); // Forces nth Bit of _b to 0. Other Bits Unchanged.
}
writeToI2C(regAdress, _b);
}
- add 'fastRequestFrom' to wire lib
- readfromI2C in "SparkFun_ADXL345" must be modified -> Remove 'Wire.begin' and 'Wire.endTransmission' before and after Wire.requestfrom
- 'print_Byte()' is a function definition SparkFunADXL313.h and SparkFun_ADXL345.h an so redefined. I just put them into privat and added the function to the class
- the code only works, when setting the board "Ardunio Nano ESP32 - Arduino ESP32 Boards" in the IDE, while the "Ardunio Nano ESP32 - esp32" doesn't works. The letter is based on lib 2.0.11 while the other is based on lib 2.0.13.
Finally the question is: Why are the I2C ports seems to be "synced" when using them on the same core? There is no problem with slow reads, but reading with max sample rate (3200 Hz) produces a watchdog reset.
Best regards,
Patrick