Timing problem using both I2C ports on one core

jimboino
Posts: 2
Joined: Wed Feb 07, 2024 11:00 am

Timing problem using both I2C ports on one core

Postby jimboino » Wed Feb 07, 2024 2:55 pm

Hi,

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)
DS0004.PNG
created on different cores
DS0004.PNG (22.18 KiB) Viewed 630 times
DS0003.PNG
created on the same core
DS0003.PNG (22.52 KiB) Viewed 630 times
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.
DS0005.PNG
created on same core with different priority
DS0005.PNG (23.23 KiB) Viewed 630 times
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;
}
The main code is:

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);  
}
There are some notes for using this code:
- 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

jimboino
Posts: 2
Joined: Wed Feb 07, 2024 11:00 am

Re: Timing problem using both I2C ports on one core

Postby jimboino » Mon Feb 12, 2024 8:14 am

Hey,

any ideas?
Or bad description? Or bad english :)?
Wrong subforum? Maybe it is not an arduino specific problem and comes from ESP-Kernel?

Give me a feedback please :)

Best regards,

Patrick

Who is online

Users browsing this forum: No registered users and 137 guests