olikraus / u8g2

U8glib library for monochrome displays, version 2
Other
5.03k stars 1.04k forks source link

ESP32 display starts to work strange when sent to a core w/ TaskHandle #1672

Closed sdw2302 closed 2 years ago

sdw2302 commented 2 years ago

In my code the display always was on the loop and was working perfectly fine, but when I passed it to a core with TaskHandle the display updates very slowly and the images are messed up

Here are some pieces of my code:

xTaskCreatePinnedToCore(
    taskCode1,
    "task1",
    10000,
    NULL,
    1,
    &task1,
    0);
void taskCode1(void * pvParameters){
  for(;;){
    u8g2_prepare();

  if (testing) executeTest(u8x8);

  else {

    if (twoHandsControl(rel1Button.tOn(), rel2Button.tOn(), 100, 500)){
      releaseEnable.on();
    }
    else{
      releaseEnable.off();
    }

     vaqValue1 = analogRead(VaqAI_1);
     vaqValue2 = analogRead(VaqAI_2);
     batReading = (batReading * 19 + analogRead(BattAI)) / 20;

     batValue = linealitza((float)batReading);

     if (verbose){
       sprintf(vaqLevel1,"%d", vaqValue1);
       sprintf(vaqLevel2,"%d", vaqValue2);
       sprintf(batLevel,"%.2f", batValue);
       sprintf(batRead,"%d", batReading);

       u8x8.drawStr(0,20,vaqLevel1);
       u8x8.drawStr(0,30,vaqLevel2);
       u8x8.drawStr(40,20,batLevel);
       u8x8.drawStr(40,30,batRead);    
     }

     u8x8.drawXBMP(128-24, 0, 24, 24, batteryManagement(BATTERY_TYPE,batValue));

    if (millis() > 3000){
       u8x8.drawXBMP(0, 0, 48, 48, checkWarnings(takingState.isOn(), checkBatteryLevel(BATTERY_TYPE,batValue), vaqValue1, vaqValue2, pumpOn.isOn(), pumpOn.tOn(), pumpOn.tOff()));
    }

    else u8x8.drawXBMP(0, 0, 128, 96, splashIcon);

    if (takingState.tOff() > TIMEOUT * 1000) powerOff.on();
    if (takingState.tOff() > (TIMEOUT - TIMEOUT_DISPLAY) * 1000){
      u8x8.drawXBMP(0, 0, 48, 48, timeOutIcon);
      sprintf(timeString, "%02d':%02d\"", (TIMEOUT - takingState.tOff()/1000)/60, (TIMEOUT - takingState.tOff()/1000)%60);
      u8x8.drawStr(0, 48, timeString); 
    }

    if((powerButton.tOff() > 1000)&&(takingState.isOff())){
      switchOff = true;
      if (verbose)u8x8.drawStr(0,0,"Release button to switch off");
      else u8x8.drawXBMP(0, 0, 128, 96, splashIcon);
    }
    else{
      if ((!switchOff)&&(millis()>3000)){
        if (verbose)u8x8.drawStr(0,0,"Hello World!");
        if (takingState.isOn()){
          if (verbose)u8x8.drawStr(0,10,"Taking   ");
          else u8x8.drawXBMP(128-32, 25, 32, 32, takingIcon);
        }
        else{
          if (verbose)u8x8.drawStr(0,10,"Releasing");
          else u8x8.drawXBMP(128-32, 25, 32, 32, releasingIcon);
        }
      }

    }
    if ((switchOff) && (powerButton.tOn() > 50)){
      powerOff.on();
      if (verbose)u8x8.drawStr(0,0,"Bye bye World!");
      else u8x8.drawXBMP(0, 0, 128, 96, splashIcon);
    }
     u8x8.sendBuffer();
  }
  }
}

The second TaskHandle handles bluetooth communications, even if I remove that part the display keeps working incorrectly

olikraus commented 2 years ago

Maybe the body of the for loop should not be interrupted.

sdw2302 commented 2 years ago

Maybe the body of the for loop should not be interrupted.

I'm pretty new to Arduino in general so if you can explain me what do you mean by that it would be great :)

olikraus commented 2 years ago

taskCode1 looks like a preemption code, see here: https://en.wikipedia.org/wiki/Preemption_(computing) This means it might got interrupted. U8g2/U8x8 is a middleware library. By itself it does not handle mutual exclusions. It is, instead, the responsibility of the user or the underlaying HAL (hardware abstraction layer) to ensure that there are no conflicts. Simply saying: You should know what you do ;-)

Let me be more specific: U8g2/U8x8 had been developed for Arduino. Arduino does not allow preemtive tasks. Additionally the u8g2 Arduino port will use the Arduino HAL functions only. So all in all U8g2/U8x8 will use standard Arduino features and is safe agains mutual exclusion problems as far Arduino is safe against this.

However it seems, that you use U8g2/U8x8 on a none-Arduino system. This obvioulsy must have a different HAL layer for U8g2/U8x8 and is as a consequence not supported by me with respect to mutual exclusion problems, because in general any HAL of other systems other than Arduino are not supported (in fact I usualy didn't wrote/implement them).

Let me give an example (i have to use an example, because your initial post includes very less information): If you use an i2c display, then u8g2 will access i2c bus via a contributed HAL. If the u8g2/u8x8 task gets interrupted and a different task accesses the same i2c bus, then the display data might get corrupted (please remember: I probably didn't implement your HAL, so i do not know to which extend that other developer had considerd mutual exclusion). Basically at a time, only one task should access i2c bus (mutual exclusion).

One simple way to ensure this, is to mark a certain code as none-interruptable. This is, what I ment before: If you would mark the content of taskCode1 has "must not be interrupted" then maybe it will work, because then the i2c data transfer will not be interrupted by any other task on the same bus.

However, I do not know your system (and you didn't mention it) I can not even google this for you, so I can not tell you how to achive this.

sdw2302 commented 2 years ago

Thank you very much! I am using a custom 4MB ESP32 with this display: https://www.az-delivery.de/en/products/1-3zoll-i2c-oled-display and i've got the 1st taskCode with the display code, and 2nd taskCode with bluetooth serial

How can i mark the taskCode1 as non-interrumpable?

olikraus commented 2 years ago

How can i mark the taskCode1 as non-interrumpable?

As I said, it looks like you use a none-Arduino environment. So I can not answer this (and you still did not mention the environment)

sdw2302 commented 2 years ago

I'm using the Arduino IDE

olikraus commented 2 years ago

I'm using the Arduino IDE

But xTaskCreatePinnedToCore is a FreeRTOS function.

sdw2302 commented 2 years ago

Is it good or bad? Because the Bluetooth works perfectly

olikraus commented 2 years ago

Is it good or bad? Because the Bluetooth works perfectly

It is not an Arduino Environment, so it is not supported by this project.

Arduino Environments means, that you use functions listed here: https://www.arduino.cc/reference/en/

However you have used "xTaskCreatePinnedToCore". No doubt: You can call whatever function you like, but you can not expect support here. "xTaskCreatePinnedToCore" is dangerous function and does NOT belong to the Arduino Environment. You should know what you do ;-)

sdw2302 commented 2 years ago

Okay, thank you :) Is there any way I can pin a part of code to a core in Arduino without that FreeRTOS stuff?

olikraus commented 2 years ago

Okay, thank you :) Is there any way I can pin a part of code to a core in Arduino without that FreeRTOS stuff?

No

sdw2302 commented 2 years ago

Okay, thank you very much once again :)

olikraus commented 2 years ago

:-/

Hope I was able to help and clarify a little bit

zalexzperez commented 6 months ago

Hi,

I'm also experiencing a very slow display update on my i2C display (SH1107) using an ESP32-S3 board running the update display code in a freeRTOS task. With an SPI display, the update speed is unaffected.

Here's a basic program that loops and prints a speed value from 0 to 300:

#include <Arduino.h>
#include <U8g2lib.h>

#ifdef U8X8_HAVE_HW_SPI
#include <SPI.h>
#endif
#ifdef U8X8_HAVE_HW_I2C
#include <Wire.h>
#endif

// Limit freeRTOS tasks to run on the ESP32’s application CPU (CPU1)
#if CONFIG_FREERTOS_UNICORE
static const BaseType_t app_cpu = 0;
#else
static const BaseType_t app_cpu = 1;
#endif

const uint8_t font_Mplus1m_bold_64px[1170] U8G2_FONT_SECTION("font_Mplus1m_bold_large") =
    "\12\2\6\6\6\7\1\1\7+@\2\377>\0>\0\0\0\0\0\4u\60\302+\260u\224\370\36\332"
    "\304\262P\230)\23i \316\303y:M\243`\64\214\203\261\60\222\305\262P\26\313BY,\13\205\241"
    ",\24F\262P\32\311Bi$\213\304\221,\20\10\262@ \310\342\210 \213#\202,\14\11\262\60$"
    "\310\242\230\34\14\2\21,\216E\201\10\26\307\202@\10\213cA \204\305\261\30\20\303\342X\14\7b"
    "q,\4\4\261\70\26\1\242X\34\213\0Q,\216\211\261@$\306\2\211\34\13\4\202,\20\10\262@"
    "\36\311\2y$\13\304\241,\220\206\262H\32\312\42a,\13e\261,\224\305\262P\26\12CYb\26"
    "\311\202a\34\14\246Q\60\234.\207\3i\244\314t\251*\327D{|\10\11\0\61\63+\260\265\337\350"
    "\34\33$\223\134\24\253B]\244L\244]\324\10\30\321b`D\211\202\21\35\16FTH\30\321`a"
    "D\201\206\361\61\377\377\377\377\377\263\1\62Y+\260\65\224\370\36\31\245\302H\32\210\343t\34O\343i"
    ">\14c\200\60\210\304\301\30\30\307Rp\32\313 b,\2\211\261\4&\306\307\274o\361\61\363\30\37"
    "\223\307\370\30\36\343cx\14\257\341\61\274F\327\360\32]\223\327\350\232:\7\347\350\232\353\34\234S\7"
    "Z\300\177\61\0\63G+\260\65\34R\302\177\235\243k\376\77\317\321M\260\212e\251.\24f\312H\31"
    "I\23i\270\207\347\360\32\37\363\237\327X\2Rc\31<\15\306\260\70\230\5\1a<\215\247\351\70\35"
    "\207\363l \215t\271$\235b\2\0\64h+\260\365\17\10\32\235\23\367\264AiQ\231\24\66uQ"
    "YU\225\42XT\212`A)\204\5\245\20\26\223bXL\212a!)\210\205\244 \26\221\242XD"
    "\212b\1)\214\5\244\60\26\217\342X<\212c\341(\220\205\243@\26\215\42Y\64\212d\301(\224\5"
    "\243P\26\213bY,\212e\261\234\210\363_\266\0\227\377\243\1\65R+\260\265\334q\272\177\216\2\134"
    "\376OPP\31I\23m \316\323q:\216\247\361\64\37\206Q\70\230\305\321`\24\11\343c|\215\217"
    "\371\347\65<\306\307`\2Q\203\31<\15\246\260\70\32\305\20i<M\307\351\70\234g\3i\242\314d"
    "\301\42^\202\2\0\66\223+\260\365\244\350&\226\205\272L\30)\23iA[\217#)<\215F\340\64"
    "<\306\307\370\26\37s\337\2\134\34\4\212\61\60 F\0q>\14h\1-!%\244\210\22Q\362\60"
    "\32\211\3iR\30HCa \214\205\201\60\230\5\302`\26\10\203Y \14f\201\60\30\306\301`\30"
    "\7\203a\34\14\206q\60\230\5\302`\26\10\203Y$\213\205\221,\26F\262X\30\11Ca$\214\244"
    "\241\60\20\306\342 \34\13\210\361\64\36\207\3i\42\215t\261(\31\304KD\0\67T+\260\65\34R"
    "\302\177\340\342[\200\213o\1.}\13p\361-\300\305\307\370\26\340\342c|\13p\361\61\276\305\307\364"
    "->\246o\361\61y\214\217\311c|L\36\343c\362\30\37\223\327\360\30\37\303kx\214\217\311kx"
    "\214\217\341\65<\306\307\360\32\77@\0\0\70\245+\260\65\244\360\42X\205\302H\32\210\343x\232\317\2"
    "ZBJ\343h$\215\204iY$\214\205\201\60\26\6\262h\26\310\242Y \213f\201,\32E\302X"
    "\26\11cY(\213e\241\60\222\305\322\70\30L\243`\64\17\201\361l\242\214\204\241,V\345\262P\30"
    "I\3q\34\17\323\20 \13\303x(\213\244\221\60\224\6\262`\30\307\242a\34\13\207a,\34\206\261"
    "p\30\306\302a\30\13\207a,\34\206\261p\30\306\302a\30\14\246a\60\30\306\321P\32\210\303x "
    "#D\244\200\230\17\343q\70Qf\262`\21/\21\1\0\71\230+\260\65\244\350\42X\245\302H\32h"
    "\363t\34\17\363\265\70\12\207\322@\30\12#a(\14e\221\60\26\6\302X\30\10ca \14f\201"
    "\60\230\5\302`\26\10\203Y \14f\201\60\30\306\301`\30\7\203a\34\14\206q\60\226\306\301X\32"
    "\7ci \213\245\201\60\22\7\302@\36\210\243\200 \243D\224\204\26\60\346\323x\236G\300\210\26\3"
    "\203\12\4\202\203\361\61}\13p\361\61\363\30N\340i\70\204\305\341 \10\310\303y\66\320\6\322D\31"
    ")#](\213\65\351\24\24\0\0\0\0\4\377\377\0";

// Display constructor selection (Full list at: https://github.com/olikraus/u8g2/wiki/u8g2setupcpp#constructor-reference)
U8G2_SH1107_64X128_F_HW_I2C u8g2(U8G2_R1, U8X8_PIN_NONE);

const uint8_t DISP_HRESOLUTION = 128; // Display horizontal resolution in pixels
const uint8_t DISP_VRESOLUTION = 64;  // Display vertical resolution in pixels

// freeRTOS function
void update_speed_screen(void *parameter);

void setup()
{
  Wire.setPins(48, 21); // Custom i2C pins
  u8g2.begin();
  u8g2.sendF("ca", 0xa8, 0x3f); // Innitialization command to fix low framerate on my specific display

  u8g2.setBitmapMode(1); // Background color treated as transparent.

  u8g2.clearBuffer(); // Clear the internal memory

  // Create a freeRTOS task that updates the speed screen periodically
  xTaskCreatePinnedToCore(
      update_speed_screen,   // Function to be called
      "Update speed screen", // Name of task
      4096,                  // Stack size (bytes in ESP32)
      NULL,                  // Parameter to pass to function
      1,                     // Task priority (0 to configMAX_PRIORITES - 1)
      NULL,                  // Task handle
      app_cpu                // CPU to run the task
  );

  // Delete setup and loop task
  vTaskDelete(NULL);
}

void loop()
{
}

void update_speed_screen(void *parameter)
{
  while (1)
  {
    u8g2_uint_t textWidth;
    uint8_t cursorX_position;

    static uint16_t carSpeed = 0;

    char text[4];
    sprintf(text, "%u", carSpeed); // Save carSpeed variable in a char buffer to later get its length in pixels
    u8g2.clearBuffer();

    if (carSpeed < 10)
    {
      /* Print speed value */
      u8g2.setFont(font_Mplus1m_bold_64px); // 64px height
      textWidth = u8g2.getStrWidth(text);
      cursorX_position = (DISP_HRESOLUTION - textWidth) / 2;
      u8g2.setFontPosTop();
      u8g2.setCursor(cursorX_position, 0);
      u8g2.print(carSpeed);

      /* Also print km/h */
      u8g2.setFont(u8g2_font_9x18B_tr);
      textWidth = u8g2.getStrWidth("km/h");
      u8g2.setFontPosBaseline();
      u8g2.setCursor((128 - textWidth), 64);
      u8g2.print("km/h");
    }
    else
    {
      /* Print speed value */
      u8g2.setFont(font_Mplus1m_bold_64px); // 64px height
      textWidth = u8g2.getStrWidth(text);
      cursorX_position = (DISP_HRESOLUTION - textWidth) / 2;
      u8g2.setFontPosTop();
      u8g2.setCursor(cursorX_position, 0);
      u8g2.print(carSpeed);

      u8g2.setFontPosBaseline(); // Restore font reference position
    }

    u8g2.sendBuffer();

    if (carSpeed >= 300)
    {
      carSpeed = 0;
    }
    else
    {
      carSpeed++;
    }

    vTaskDelay(25 / portTICK_PERIOD_MS); // Wait for 25ms
  }
}

@olikraus After reading your comment about marking a certain code as non-interruptible, I read espressif's doc about critical section, so I initialized a spinlock globally and inside the freeRTOS task that updates the display, I used the critical section like this:

    taskENTER_CRITICAL(&my_spinlock);
    // We are now in a critical section
    u8g2.sendBuffer();
    taskEXIT_CRITICAL(&my_spinlock);

But that makes the board crash all the time (Guru Meditation Error: Core 1 panic'ed (Interrupt wdt timeout on CPU1).)

I don't know what else to try.

olikraus commented 6 months ago

You need to extend or disable the timeout for the watch-dog-time (wdt): https://docs.espressif.com/projects/esp-idf/en/v4.4/esp32/api-reference/system/wdts.html

zalexzperez commented 6 months ago

Thanks, but I think I will just use the SPI display... FPS test throws between 50 to more than 250fps using HW SPI, but less than 30FPS using the i2C display (always without freeRTOS)