2

ESP32 with RTOS, test app with 2 independent tasks. Each prints a message to OLED display. Why text gets frequently scrambled? Mutex have been used to share display resource.

enter image description here enter image description here

#define DEBUG_ESP              //comment out to deactivate debug console verbose
#ifdef DEBUG_ESP
  #define pDBGln(x) Serial.println(x)
  #define pDBG(x)   Serial.print(x)
#else 
  #define pDBG(...)
  #define pDBGln(...)
#endif 

//create handle for the mutex. It will be used to reference mutex
SemaphoreHandle_t  xMutex;

//*********************************SSD1306 OLED Display
#define I2C_SDA              14
#define I2C_SCL              15
#include <Wire.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define OLED_RESET     4 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
//*****************************************************

void setup() {

  // create mutex and assign it a already create handler 
  xMutex = xSemaphoreCreateMutex();  

  Wire.begin(I2C_SDA,I2C_SCL);
  Serial.begin(115200);
        
   // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { 
    pDBGln("SSD1306 allocation failed - Halt");
    for(;;); // Don't proceed, loop forever
  }else{
    // Show initial display buffer contents on the screen --
    // the library initializes this with an Adafruit splash screen.
    display.setTextSize(1);
    display.setTextColor(SSD1306_WHITE);  
    display.setCursor(0, 0);
    display.clearDisplay(); 
  }

    xTaskCreatePinnedToCore(
        task01,             /* Function to implement the task */
        "task01",           /* Name of the task */
        10000,              /* Stack size in words */
        NULL,               /* Task input parameter */
        1,                  /* Priority of the task */
        NULL,               /* Task handle. */
        0);                 /* Core where the task should run */      

    xTaskCreatePinnedToCore(
        task02,             /* Function to implement the task */
        "task02",           /* Name of the task */
        10000,              /* Stack size in words */
        NULL,               /* Task input parameter */
        1,                  /* Priority of the task */
        NULL,               /* Task handle. */
        1);                 /* Core where the task should run */ 
}

void loop() {}

void task01( void * parameter ){
  while(1){
    printToDisplay("Task01..............");
    vTaskDelay(1500);
  }
}

void task02( void * parameter ){
  while(1){
    printToDisplay("Task02..............");
    vTaskDelay(2500);
  }
}

void printToDisplay(String text){
  bool myMutex;
  while(1){
    // take mutex
    myMutex = xSemaphoreTake(xMutex, portMAX_DELAY);
    if(myMutex){ 
      static byte   lineCounter = 0;
      static String displayText[8];
      if(lineCounter>7){
        for(int i=0;i<7;i++){
          displayText[i] = displayText[i+1];
        }
        displayText[7] = text;
        display.clearDisplay();  
        display.setCursor(0, 0);  
        for(int i=0;i<8;i++){
          display.println(displayText[i]);   
        }    
      }else{
        displayText[lineCounter] = text; 
        display.println(displayText[lineCounter]);
        if(lineCounter<8){lineCounter++;}
      }  
      display.display(); 

      // release mutex
      xSemaphoreGive(xMutex); 
      break;
    }
  }
}

The code below uses no RTOS and works perfectly: (my project requires RTOS)

#define DEBUG_ESP              //comment out to deactivate debug console verbose
#ifdef DEBUG_ESP
  #define pDBGln(x) Serial.println(x)
  #define pDBG(x)   Serial.print(x)
#else 
  #define pDBG(...)
  #define pDBGln(...)
#endif 

byte lineCounter = 0;

//*********************************SSD1306 OLED Display
#define I2C_SDA              14
#define I2C_SCL              15
#include <Wire.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define OLED_RESET     4 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
//*****************************************************

void setup() {

  Wire.begin(I2C_SDA,I2C_SCL);
  Serial.begin(115200);
        
   // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { 
    pDBGln("SSD1306 allocation failed - Halt");
    for(;;); // Don't proceed, loop forever
  }else{
    // Show initial display buffer contents on the screen --
    // the library initializes this with an Adafruit splash screen.
    display.setTextSize(1);
    display.setTextColor(SSD1306_WHITE);  
    display.setCursor(0, 0);
    display.clearDisplay(); 
  }
}

void loop() {
  pDBGln("Start Display Test...");
  task01();
  task02();
}

void task01(){
  printToDisplay("Task01..............");
  delay(500);
}

void task02(){
  printToDisplay("Task02..............");
  delay(500);
}

void printToDisplay(String text){
  static byte   lineCounter = 0;
  static String displayText[8];
  if(lineCounter>7){
    for(int i=0;i<7;i++){
      displayText[i] = displayText[i+1];
    }
    displayText[7] = text;
    display.clearDisplay();  
    display.setCursor(0, 0);  
    for(int i=0;i<8;i++){
      display.println(displayText[i]);   
    }    
  }else{
    displayText[lineCounter] = text; 
    display.println(displayText[lineCounter]);
    if(lineCounter<8){lineCounter++;}
  }  
  display.display(); 
}
  • At first glance that all looks fine. Maybe there's something behind the scenes that's messing it up. Maybe you should have all the display code in one dedicated thread and pass data to that thread from the others. – Majenko Apr 11 '21 at 14:29
  • You mean create a third task printToDisplay(String text)? – Paulo Borges Apr 11 '21 at 14:50
  • A third task that takes instruction through some other route (maybe a pipe or something similar) from the other tasks and displays the information. – Majenko Apr 11 '21 at 15:07
  • Just found that if I put Task01 to run on the same core as Task02 (core 1) it works perfectly. And if I put both to run on core 0 the mess becomes a lot worse. The problem has clearly something to do when the task running on core 0 tries to write to the display even protected by Mutex. – Paulo Borges Apr 11 '21 at 15:40
  • 1
    Core 0 is used for the Wifi and IP stack. There's probably some timing issue then. – Majenko Apr 11 '21 at 15:48
  • Yes, possibly. Still trying to find a way around it or will have to live with it... – Paulo Borges Apr 11 '21 at 16:01
  • Can you show us a picture of the display corruption? We may be able to see a pattern that would explain it. – Majenko Apr 12 '21 at 10:21
  • Please check pictures I added at the top.Yes, there is a pattern that repeats but I believe it to be a result of the timing between task execution. – Paulo Borges Apr 12 '21 at 12:42
  • Ok, so there is one of two things happening here, and it's impossible at the moment to know which. The way the library works is it has an internal framebuffer which is what you modify with the drawing/printing functions. That framebuffer is then output in its entirety to the display in one go with `.display()`. Either that internal framebuffer is being corrupted while drawing, or the communication with the display is being broken when sending. I suggest having the `.display()` only ever happen in a thread on core 1. The rest can be wherever. – Majenko Apr 12 '21 at 13:36

1 Answers1

1

This is a formal answer to the question based on the comments between Paulo Borges and Majenko.


The ESP32 is dual core, meaning that it comes with 2 Xtensa LX6 32-bit microprocessors: core 0 and core 1.

The issue is not a bug in your code but rather that you are running your threads on Core 0 instead of Core 1.

In the ESP, the Core 0 is used for the RF communications. Even with the mutex protection, it is highly likely that your tasks are having timing issues.

Move Task01 and Task02 into Core 1 and your program will run without any display issues.

sa_leinad
  • 3,118
  • 1
  • 20
  • 49