Issue with I2C Communication in FreeRTOS Task on Custom SAMD21 Board

Problem:

I'm working on a project with a custom Arduino SAMD21 board where I need to read data from a VL53L5CX sensor. The I2C communication works perfectly in the setup() function, but when I try to initialize and communicate with the sensor within a FreeRTOS task, it fails to find the sensor. Which makes clear that it can't be a hardware problem.

Details:

Board: Custom Arduino SAMD21
Sensor: VL53L5CX (connected via I2C)
Library: Using SparkFun's VL53L5CX library
OS: FreeRTOS

Symptoms:

The sensor initializes and communicates correctly in the setup() function, and the device ID is successfully read. Within the FreeRTOS task (sensorThreadFunc), the sensor is not detected, and I2C communication fails.

Code Snippets:

Working Code in setup():

void setup() {
  SERIALLL.begin(115200);
  delay(7000); // Prevents USB driver crash on startup
  while (!Serial);

  SERIALLL.println("Program start");
  
  Wire.begin();           // Reset I2C bus to 100kHz
  Wire.setClock(1000000); // Set I2C freq to 1MHz

  // Check I2C communication
  Wire.beginTransmission(0x29);
  if (Wire.endTransmission() == 0) {
    SERIALLL.println("Sensor found at address 0x29");
    // Optionally, read a specific register (for example, the device ID register)
    byte registerAddress = 0x7F; // Example register address
    Wire.beginTransmission(0x29);
    Wire.write(registerAddress);
    Wire.endTransmission();
    Wire.requestFrom(0x29, (byte)1);
    if (Wire.available()) {
      byte data = Wire.read();
      SERIALLL.print("Register 0x");
      SERIALLL.print(registerAddress, HEX);
      SERIALLL.print(" = 0x");
      SERIALLL.println(data, HEX);
    } else {
      SERIALLL.println("Failed to read from register.");
    }
  } else {
    SERIALLL.println("Sensor not found");
  }
}

Failing Code in FreeRTOS Task:


void sensorThreadFunc(void *pvParameters) {
  (void)pvParameters;
  
  xSemaphoreTake(semSerial, portMAX_DELAY);
  SERIALLL.println("Sensor-Thread Active!");
  xSemaphoreGive(semSerial);

  Wire.begin();
  Wire.setClock(1000000);

  xSemaphoreTake(semSerial, portMAX_DELAY);
  SERIALLL.println("Scanning for I2C devices...");
  for (byte address = 1; address < 127; address++) {
    Wire.beginTransmission(address);
    if (Wire.endTransmission() == 0) {
      SERIALLL.print("I2C device found at address 0x");
      SERIALLL.println(address, HEX);
    }
  }
  xSemaphoreGive(semSerial);

  if (!VL53L5CX.begin()) {
    xSemaphoreTake(semSerial, portMAX_DELAY);
    SERIALLL.println("Sensor not found - Task deleted.");
    xSemaphoreGive(semSerial);
    vTaskDelete(NULL);
  }
  
  SERIALLL.println("Sensor initialized");
  for (;;) {
    // Sensor data reading and processing
  }
}

Output:

Register 0x7F = 0x0
Sensor-Thread Active!
Scanning for I2C devices...
I2C scan completed.
Checking for I2C device at address 0x29
No I2C device found at address 0x29

Troubleshooting Steps Tried:

Added delays before sensor initialization in the task.
Re-initialized the I2C bus within the task.
Checked for proper semaphore usage to avoid conflicts.
Verified hardware connections.

Additional Information:
I have programmed the same code on a regular Arduino Zero, and it works without any issues. My suspicion is that the problem might be related to using the internal quartz oscillator on the SAMD21, because I didn't implement one in my custom board.

Request:
Has anyone encountered a similar issue or can provide insight into why I2C communication fails within a FreeRTOS task but works in setup()? Any suggestions for resolving this would be greatly appreciated.

Can you show a full sketch ?
Please put the Serial and I2C code in a function (without semaphores), and then try it in setup(), the loop() and in a task.
Remove everything that is not needed, make is as simple as possible.
To check the sensor you only need this for a test:

Wire.begin();
delay(100);  // probably not needed
Wire.beginTransmission(0x29);
if (Wire.endTransmission() == 0)

Your code in setup() is now too much different from the code in the task. The task might not have enough stack size.

In which way did you install FreeRTOS ? FreeRTOS can run in different modes.

If I have to guess, then the interrupt for switching tasks seems to interfere with the Wire library. Either by setting/using the interrupt hardware, or with a memory problem. That is a wild guess, I give it 1% that I'm right.

Can you do your project without FreeRTOS ?

No, unfortunately, I can't share the entire sketch. I don't use loop(), only setup() and then I activate the scheduler. I also can't do without FreeRTOS, as I have too many tasks that I need to handle simultaneously. I can only say that it worked perfectly on the Arduino Zero before.

I also tested the sensor in its own sketch, and it works flawlessly. My suspicion is that FreeRTOS might be using the internal quartz for timing, but I don't know for sure and I can't find any good resources about the topic.

I wrote "full sketch", but I should have written: a minimal sketch that shows the problem :wink:
There is a website for snippets: https://www.snippets-r-us.com/
On this forum there is a saying: the problem is in the part that you are not showing.

Make a small test sketch and show it to us.
You do use the loop(). It is a task that runs. If there is nothing in there, then it runs at 100%, eating precious CPU time.
In some configurations, the FreeRTOS is already running when setup() is called. Trying to start a running schedular can have weird results.

Sometimes memory problems don't show up, until something is changed. That could explain that it runs on a Arduino Zero. But you are right, it is very suspicious, but I don't know about the internal quartz. Sorry.

I managed to make a test sketch which is demonstrating the problem. I figured out that it's not about the FreeRTOS OS. The Code is configuring a Modbus Slave and tries to search for my i2c device.

The Code:

#include <Arduino.h>
#include <ModbusRTUSlave.h>
#include <Adafruit_NeoPixel.h>
#include <Wire.h>

#define SLAVE_DE 7
#define DATA_PIN 10
#define NUM_PIXELS 3

uint16_t holdingRegisters[2];
ModbusRTUSlave modbus(Serial1, SLAVE_DE);

Adafruit_NeoPixel led_Stripe(NUM_PIXELS, DATA_PIN, NEO_GRB + NEO_KHZ800);

void i2cTest();

void setup() {
  delay(7000);
  SerialUSB.println("Before .begin()");
  i2cTest();

  modbus.configureHoldingRegisters(holdingRegisters, 2);
  modbus.begin(16, 115200);

  SerialUSB.println("After .begin()");
  i2cTest();

  led_Stripe.begin();
}

void loop() {
  modbus.poll();

  for (int i = 0; i < NUM_PIXELS; i++) {
    led_Stripe.setPixelColor(i, led_Stripe.ColorHSV(holdingRegisters[0]));
  }
  led_Stripe.show();

  SerialUSB.println("before digitalWrite()");
  i2cTest();
  digitalWrite(SLAVE_DE, HIGH);
  SerialUSB.println("after digitalWrite()");
  i2cTest();
  digitalWrite(SLAVE_DE, LOW);

  delay(200);
}

void i2cTest() {
  Wire.begin();           // This resets I2C bus to 100kHz
  Wire.setClock(1000000); // Sensor has max I2C freq of 1MHz

  SerialUSB.print("Device: ");

  Wire.beginTransmission(0x29);
  byte error = Wire.endTransmission();

  if (error == 0) {
    SerialUSB.println("found! \n");
  } else {
    SerialUSB.println("not found! \n");
  }
}

The Output:

Before .begin()
Device: found! 

After .begin()
Device: not found! 

before digitalWrite()
Device: not found! 

after digitalWrite()
Device: found! 

before digitalWrite()
Device: not found! 

after digitalWrite()
Device: found! 

before digitalWrite()
Device: not found! 

after digitalWrite()
Device: found! 

before digitalWrite()
Device: not found! 

after digitalWrite()
Device: found! 

before digitalWrite()
Device: not found! 

after digitalWrite()
Device: found! 

As soon as I set Pin 7 low, the I2C bus stops working. My first thought was that the pin is grounding the bus. I measured SDA/SCL with an oscilloscope and couldn't find anything suspicious. Both were on 3.3V and showed the transmission protocoll.

This is the Modbus Library I am using: ModbusRTUSlave.

Keep your tests small please.

Can you try the test for the I2C bus and use pinMode and digitalWrite for pin 7 in the sketch without any modbus or ledstrip code.

A good test is to cut pin 7 on the chip. Then the software and hardware is separated.
There could be a short circuit to something else, causing a glitch that the scope does not detect.
Does the sensor respond in the same way on the I2C bus with pin 7 is low ?
You might be doing something wrong over and over again. Check for the most silly mistakes, make no assumptions.

I discovered the issue. After testing the I2C protocol on SDA and SCL, I found it to be very clean. Then, I periodically changed Pin 7 (every 3000ms) and observed that the protocol remained intact. However, when Pin 7 was LOW, the slave device didn't acknowledge. This indicated a potential problem with the device itself.

Upon re-examining my schematic, I realized that I had connected the I2C reset of the device to Pin 3 of the SAMD21 with a 47k pulldown resistor. On the Arduino, I used just a pulldown resistor, with no connection to Pin 3. I assumed that Pin 3 was floating, and when I pulled Pin 7 LOW, it affected Pin 3/I2C reset. When I actively forced Pin 3 LOW, everything worked perfectly.

I thought this might interest you. Thank you for your help anyway! This experience also taught me how to better navigate and use this forum for publishing my issues.

Thanks for the update. I'm glad you found the problem :smiley:
So you didn't have to cut a pin from the processor :wink:

1 Like

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.