I2C sensors disabling Serial Communications on ESP32

Recently, I had been using the Adafruit Feather ESP32 V2 with ToF sensors from Pololu, and surprisingly I got into the same problem that I encountered when I was using these sensors with the MKR ZERO back in June 2022 - see this post

This time I was using the Classic SerialBT that comes with this ESP32 board.

Fortunately, the same solution applies to the ESP32 also (i.e. use Non-Blocking READ). So whatever the issue is, it is common to both boards.

Post links to the exact hardware you're using! Post your complete code! Post a wiring diagram! Post a link to the library you're using (or the name to find in the Library Manager of the IDE)!

1 Like

I have uses Pololu VL53L1X, VL6180X and VL53L0X TOF sensors on a number of microcontrollers and never had any problems with serial IO
as @pylon requests give us more information?

Hello @horace and @pylon,

Thank you for your interest in this issue. The Forum Administrator just bumped me up to Basic membership, so I hope that I can include several pictures per posting, as compared to just one previously!

The picture below shows the set up used: Adafruit Feather ESP32 V2 + 3 VL53L1X from Pololu. I also used the ROBOTIS software tool named TASK V2 as my PC interface for sending UDLR123456 Button signals to the Arduino Sketch on the ESP32 which in turn can send text strings back to this software tool.

Each button pushed by the user is represented by a 16-bit number which is then split up into a Low-Byte (Data_L) and a Hi-Byte (Data_H) which are then stuffed into a 6-byte packet (as per RC-100 protocol from ROBOTIS). Eventually motorized wheels will be attached to the above setup thus requiring remote control features for a more lengthy code, but a shorter basic sketch is listed below for the current purpose:

// Setting up SerialBT
#include <BluetoothSerial.h>
#if !defined(CONFIG_BT_ENABLED) || !defined(CONFIG_BLUEDROID_ENABLED)
#error Bluetooth is not enabled! Please run `make menuconfig` to and enable it
#endif
#if !defined(CONFIG_BT_SPP_ENABLED)
#error Serial Bluetooth not available or not enabled. It is only available for the ESP32 chip.
#endif
BluetoothSerial SerialBT;

// Setting the 3 ToF sensors
#include <Wire.h>
#include <VL53L1X.h>   // Pololu Library
const uint8_t sensorCount = 3;
int sensorRead = 0;  // via Keyboard Input with Serial.read() 
const uint8_t xshutPins[sensorCount] = { 13, 14, 15 };  // Digital Ports 13/14/15 used on Feather ESP32 V2
VL53L1X sensors[sensorCount];  // instantiate Sensor Objects
int L_ToF = 0;
int C_ToF = 0;
int R_ToF = 0;

////////// define RC-100 button key value ////////////////
#define RC100_BTN_U   (1)
#define RC100_BTN_D   (2)
#define RC100_BTN_L   (4)
#define RC100_BTN_R   (8)
#define RC100_BTN_1   (16)
#define RC100_BTN_2   (32)
#define RC100_BTN_3   (64)
#define RC100_BTN_4   (128)
#define RC100_BTN_5   (256)
#define RC100_BTN_6   (512)

uint8_t  state2 = 0;   // Init for RC-100 packet parameters
uint8_t  rc_index2 = 0;
bool     received2 = false;
uint16_t Data2 = 0;
uint16_t DataReceived2 = 0;

// Function Prototypes for RC-100 packet
bool rc100_Update2(uint8_t data2);
bool rc100_Available2(void);
uint16_t rc100_readData2(void);

/******END OF GLOBAL SETTINGS  ***************************/

void setup()
{
  Serial.begin(115200);
  while (!Serial) {}   // waiting for user to open Serial Monitor
  delay(1000);
  SerialBT.begin("ESP32_Blue"); //Bluetooth device name & set to 115.2 Kbps
  delay(1000);
  Serial.println("ESP32's BT device started");

// ToF setup
  Serial.println("Triple VL53L1Xs ToF Sensor Example");
  delay(1000);
  Wire.begin();  // have to use TwoWire for OpenRB-150
  Wire.setClock(400000); // use 400 kHz I2C
  // Disable/reset all sensors by driving their XSHUT pins low.
  for (uint8_t i = 0; i < sensorCount; i++)
  {
    pinMode(xshutPins[i], OUTPUT);
    digitalWrite(xshutPins[i], LOW);
  }
  // Enable, initialize, and start each sensor, one by one.
  for (uint8_t i = 0; i < sensorCount; i++)
  {
    pinMode(xshutPins[i], INPUT);
    delay(10);  // wait for sensor to start up
    sensors[i].setTimeout(500);  
    if (!sensors[i].init())
    {
      Serial.print("Failed to detect and initialize sensor ");
      Serial.println(i);
      while (1);
    }
    sensors[i].setAddress(0x2A + i);
    sensors[i].startContinuous(15);   // default=50 ms, 15 is about lowest value used without TIMEOUT error
  }  // end of big FOR Loop

}   // end of setup()

void loop()
{
  // ToF code
//  read_sensors_nb(); 
  read_sensors_b(); 
  
  // Processing RC-100 Buttons
  DataReceived2 = 0;
  if (rc100_Available2()) 
  {
    DataReceived2 = rc100_readData2();
    Serial.println("DataReceived2 = " + String(DataReceived2));
//    SerialBT.println("DataReceived2 = " + String(DataReceived2));
   
    if (DataReceived2 == 0)  // All Buttons Released
    {
      SerialBT.println("All Buttons Released\r\n\n");
    }
    else 
    {
      if (DataReceived2 == RC100_BTN_U)  // Up Button Detected
      {
        SerialBT.println("Forward");
      }
      else if (DataReceived2 == RC100_BTN_D)  // Down Button Detected
      {
        SerialBT.println("Backward");
      }   
      else if (DataReceived2 == RC100_BTN_L)  // Down Button Detected
      {
        SerialBT.println("Left");
      }
      else if (DataReceived2 == RC100_BTN_R)  // Down Button Detected
      {
        SerialBT.println("Right");
      }
    }  // end of big else
    
    delay(50);  // a small delay for SerialBT packets
  }  // end of receiving RC-100 data
}  // end of loop()

void read_sensors_nb() {
  // ToF Sensors Read in Non-Blocking Mode to keep SerialBT AVAILABLE
  if (sensors[0].dataReady())
  {
    L_ToF = sensors[0].read(false);  // non-blocking mode
  }
  if (sensors[1].dataReady())
  {
    C_ToF = sensors[1].read(false);  // non-blocking mode
  }
  if (sensors[2].dataReady())
  {
    R_ToF = sensors[2].read(false);  // non-blocking mode
  }
  Serial.println(String(L_ToF) + "\t" + String(C_ToF) + "\t" + String(R_ToF));
//  SerialBT.println(String(L_ToF) + "\t" + String(C_ToF) + "\t" + String(R_ToF));
}  // end of read_sensors_nb()

void read_sensors_b()   // ToFs read in Blocking mode
{
  L_ToF = sensors[0].read();  // in Blocking Mode
  C_ToF = sensors[1].read();
  R_ToF = sensors[2].read();
  Serial.println(String(L_ToF) + "\t" + String(C_ToF) + "\t" + String(R_ToF));
//  SerialBT.println(String(L_ToF) + "\t" + String(C_ToF) + "\t" + String(R_ToF));
}   // end of read_sensors_b()

bool rc100_Update2(uint8_t data2)
{
  bool ret2 = false;
  static uint8_t save_data2;
  static uint8_t inv_data2;
  static uint32_t time_t2;

  inv_data2 = ~data2;

  if (millis()-time_t2 > 100)
  {
    state2 = 0;
  }

  switch(state2)
  {
    case 0:
      if (data2 == 0xFF)
      {
        state2 = 1;
        time_t2 = millis();
      }
      break;

    case 1:
      if (data2 == 0x55)
      {
        state2 = 2;
        received2 = false;
        data2 = 0;
      }
      else
      {
        state2 = 0;
      }
      break;

    case 2:
      Data2 = data2;  // Low Byte of Data
      save_data2 = data2;
      state2 = 3;
      break;

    case 3:
      if (save_data2 == inv_data2)
      {
        state2 = 4;
      }
      else
      {
        state2 = 0;
      }
      break;

    case 4:
      Data2 |= data2<<8;   // High Byte of Data
      save_data2 = data2;
      state2 = 5;
      break;

    case 5:
      if (save_data2 == inv_data2)
      {
        received2 = true;
        ret2 = true;  // once per valid packet read in
      }
      state2 = 0;
      break;

    default:
      state2 = 0;
      break;
  }

  return ret2;
}

bool rc100_Available2(void)
{
//  Serial.println("Inside rc100_Available2(void)"); 
  if(SerialBT.available())   
    {
//      Serial.println("Inside SerialBT.available()"); 
      return rc100_Update2(SerialBT.read()); 
    }
}

uint16_t rc100_readData2(void)
{
  return Data2;
}

The Global Definitions section has 3 code sections for various devices:

  1. SerialBT() – classic BT communications found on the ESP32
  2. 3 VL53L1Xs used as a group of I2C devices connected to Pins D13, D14 and D15 on the ESP32’s header.
  3. Constants and Functions used for RC-100 communications between ESP32 and PC via SerialBT().

Function setup() initializes SerialBT() which is set to 115.2 Kbps via Device Manager on the PC, and also the 3 VL53L1Xs used.

Function loop() really just has 2 tasks:

  1. Read in latest values from the ToF sensors either in Non-Blocking mode or in Blocking mode. I actually started out using Blocking mode but got into trouble right away (explained in a later section). After consulting with Pololu Tech Support who suggested to try Non-Blocking mode instead, this mode became my “work-around” solution. See picture below for details of Functions read_sensors_nb() and read_sensors_b().

  1. The remaining code in setup() was for processing and acting on the RC-100 packets sent over from the PC via SerialBT. The RC-100 packet Capture and Processing tasks are performed by 3 functions: rc100_Update2(), rc100_Available2(), rc100_readData2().

An overview of these RC-100 processes can be summarized as follows:
For each iteration of Function loop():

  • Parameter DataReceived2 is reset to zero.
  • Each time that Function rc100_Available2() is invoked, it uses Function rc100_Update2() to read in 1 byte from the SerialBT() buffer and compare to the “expected” order of bytes FF, 55, Data_L, ~Data_L, Data_H, and ~Data_H for a proper and uncorrupted RC-100 packet. Thus the RC-100 protocol has built-in error-checking.
  • In summary, if a “good” packet was received by SerialBT(), Function rc100_Available2() would evaluate FALSE the first 5 times going through Function loop(), and on the 6th time Function rc100_Available2() would evaluate TRUE. Then Parameter DataReceived2 gets assigned a numerical value corresponding to the Button pressed by the operator on the TASK V2 interface. For example, Button UP yields 1, while Button DOWN yields 2, and Button RIGHT yields 8, and so forth…
  • Also, the net result is that 6 sets of ToF sensor data are collected for every RC-100 packet received and processed.

The picture below is a run-time screen capture of the Arduino IDE window, the Arduino Serial Monitor window, and the TASK V2 window, for a NON-BLOCKING typical run. And you can see that every task/feature is working out as it should – as I pushed on Button R (i.e. numerical value 8):

The picture below shows a run-time screen capture when ToF sensors are read in BLOCKING mode, whereas Sensors Data streamed in fine, but NO information about the Button that I pushed would show up on the Serial Monitor, unlike in the previous NON-BLOCKING case.

My interpretation is that somehow read_sensors_b() prevents rc100Available2() from ever evaluating to TRUE (by messing around with SerialBT() data buffer during the time waiting for the ToF sensors to be ready?). This is what I meant by “I2C disabling/interfering with Serial Communications” in the title of this post. As I had encountered the same issue back in June 2022 when I was using a MKR ZERO, so I think that this is an “Arduino Feature”! It was good that Pololu Library gave me a work-around solution, but I am very much interested in understanding what was really going on inside the Arduino Core between I2C and UART (and probably SPI too). Does anyone know of relevant documentations that I may have missed?

One last troubleshooting step that I did was to enable “text printing” from ESP32 to PC via SerialBT() inside Function read_sensors_b(), and this last picture shows that there was no problem with that task.

So the issue seemed to be how to program ESP32 to handle more appropriately two data streams coming in at the same time, one via I2C and the other via SerialBT(). Any suggestions that anyone may have? Besides making the I2C process NON-BLOCKING? Thank you in advance!

Wrong, it's how you handle the two streams. If you use a blocking variant, the processor is actively waiting until some event occurs. During that wait your program is doing nothing else, so it cannot read out the serial buffer of course.

Additionally, you handle one byte from the Bluetooth serial at most in every run of loop. As a byte may come in in about 80µs there is almost no time to check an I2C device (which is operating at a lower speed usually). So the serial buffer may fill up too fast.

Thank you for describing the uneven competing processes between UART & I2C. So "fast UART" is not necessarily good in this case.

As I can't afford to slow down my BT data flow rate, I slowed down the "effective" rate of accessing the Blocking process using a counter as shown below:

image

It seemed to work good enough.

Fast UART is only good if you need it and if you can afford it aka if it works at that speed (for software or hardware reasons).

Another rather easy way would have been to read more than one byte from the SerialBT buffer if available.

bool rc100_Available2(void)
{
  while (SerialBT.available())   
    {
      if (rc100_Update2(SerialBT.read())) return true;
    }
  return false;
}

Of course reading the sensors in non-blocking mode is usually the way to prefer but you may have other requirements.

Hello @pylon

Thank you for the suggestion. I am going to try it out later.

UPDATE: 3/4/2023
I just tried out your solution, and it worked great. In a way your solution gives higher priority to the SerialBT process over the ToF sensors process, and I found out that the overall data throughput was then the same whether I used Blocking or Non-Blocking sensor reads:

Blocking result:

Non-Blocking result:

So it would appear that the time to take one "Blocking" or "Non-Blocking" set of sensors is shorter than the time needed to process all 6 bytes of an RC100 packet. So like you mentioned previously, if "non-blocking" procedures are available, use them!

Also may be that is why I had found that (counter % 4) worked about the best in my previous approach and using (counter % 3) would start to mess with the SerialBT's process.

Thank you for sharing your expertise!

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