Почему SSD1306 OLED-дисплей получает скремблированный текст при печати сообщений из отдельных задач в RTOS?

esp32 oled

ESP32 с RTOS, тестовое приложение с 2 независимыми задачами. Каждый из них выводит сообщение на OLED-дисплей. Почему текст часто скремблируется? Мьютекс был использован для совместного использования ресурса отображения.

#define DEBUG_ESP              //закомментируйте, чтобы деактивировать консоль отладки 
#ifdef DEBUG_ESP
  #define pDBGln(x) Serial.println(x)
  #define pDBG(x)   Serial.print(x)
#else 
  #define pDBG(...)
  #define pDBGln(...)
#endif 

//создайте дескриптор для мьютекса. Он будет использоваться для ссылки на мьютекс
SemaphoreHandle_t  xMutex;

//*********************************OLED-дисплей SSD1306
#define I2C_SDA              14
#define I2C_SCL              15
#include <Wire.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128 // ширина OLED-дисплея, в пикселях
#define SCREEN_HEIGHT 64 // высота OLED-дисплея, в пикселях
#define OLED_RESET     4 // Reset pin # (или -1 при совместном использовании Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
//*****************************************************

void setup() {

  // создайте мьютекс и назначьте ему уже созданный обработчик 
  xMutex = xSemaphoreCreateMutex();  

  Wire.begin(I2C_SDA,I2C_SCL);
  Serial.begin(115200);
        
   // SSD1306_SWITCHCAPVCC = генерировать напряжение дисплея от 3,3 В внутри
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { 
    pDBGln("SSD1306 allocation failed - Halt");
    for(;;); // Don't proceed, loop forever
  }else{
    // Показать начальное отображение содержимого буфера на экране --
    // библиотека инициализирует это с помощью заставки Adafruit.
    display.setTextSize(1);
    display.setTextColor(SSD1306_WHITE);  
    display.setCursor(0, 0);
    display.clearDisplay(); 
  }

    xTaskCreatePinnedToCore(
        task01, /* Функция для реализации задачи */
        "task01", /* Имя задачи */
        10000, /* Размер стека в словах */
        NULL, /* Входной параметр задачи */
        1, /* Приоритет задачи */
        NULL, /* Дескриптор задачи. */
        0); /* Ядро, в котором должна выполняться задача */      

    xTaskCreatePinnedToCore(
        task02, /* Функция для реализации задачи */
        "task02", /* Имя задачи */
        10000, /* Размер стека в словах */
        NULL, /* Входной параметр задачи */
        1, /* Приоритет задачи */
        NULL, /* Дескриптор задачи. */
        1); /* Ядро, в котором должна выполняться задача */ 
}

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;
    }
  }
}

Приведенный ниже код не использует ОСРВ и работает отлично: (мой проект требует ОСРВ)

#define DEBUG_ESP              //закомментируйте, чтобы деактивировать консоль отладки 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(); 
}

, 👍2

Обсуждение

На первый взгляд все выглядит нормально. Может быть, за кулисами есть что-то, что все портит. Возможно, вам следует поместить весь код отображения в один выделенный поток и передать данные в этот поток от других., @Majenko

Вы имеете в виду создание третьей задачи printToDisplay(текст строки)?, @Paulo Borges

Третья задача, которая выполняет инструкции по какому-либо другому маршруту (возможно, по трубе или чему-то подобному) из других задач и отображает информацию., @Majenko

Просто обнаружил, что если я помещу Task01 для запуска на том же ядре, что и Task02 (ядро 1), он отлично работает. И если я включу оба режима на core 0, беспорядок станет намного хуже. Проблема явно имеет какое-то отношение, когда задача, работающая на ядре 0, пытается выполнить запись на дисплей, даже защищенный мьютексом., @Paulo Borges

Ядро 0 используется для стека Wi-Fi и IP. Тогда, вероятно, есть какие-то проблемы со временем., @Majenko

Да, возможно. Все еще пытаюсь найти способ обойти это или придется с этим жить..., @Paulo Borges

Можете ли вы показать нам фотографию повреждения дисплея? Возможно, мы сможем увидеть закономерность, которая объяснила бы это., @Majenko

Пожалуйста, проверьте фотографии, которые я добавил вверху.Да, есть закономерность, которая повторяется, но я считаю, что это результат времени между выполнением задачи., @Paulo Borges

Итак, здесь происходит одно из двух событий, и в данный момент невозможно узнать, какое именно. Способ работы библиотеки заключается в том, что она имеет внутренний буферизатор кадров, который вы изменяете с помощью функций рисования/печати. Этот буферизатор кадров затем выводится полностью на дисплей одним махом с .display(). Либо этот внутренний буферизатор кадров поврежден во время рисования, либо связь с дисплеем прерывается при отправке. Я предлагаю, чтобы функция". display () " выполнялась только в потоке на ядре 1. Остальное может быть где угодно., @Majenko


1 ответ


1

Это формальный ответ на вопрос, основанный на комментариях Паулу Борхеса и Мадженко.


ESP32 является двухъядерным, что означает, что он поставляется с 2 32-разрядными микропроцессорами Xtensa LX6: core 0 и core 1.

Проблема заключается не в ошибке в вашем коде, а в том, что вы запускаете свои потоки на ядре 0 вместо ядра 1.

В ESP ядро 0 используется для радиочастотной связи. Даже с защитой от мьютекса весьма вероятно, что ваши задачи имеют проблемы с синхронизацией.

Переместите Task01 и Task02 в ядро 1, и ваша программа будет работать без каких-либо проблем с отображением.

,