Screen update in LVGL - Good practices - Opinion?

I know there are many topics about updating the LVGL screen, but I wanted an opinion on the efficiency of the code below.

I have a product that reads the temperature and puts the values ​​on the screen.

It is a simple temperature datalogger.

So I have a task that reads the temperature and another task that reads the time.

Then I have the routines (Tasks) that update the screen, I wanted to know if this way of writing is correct?

If I am efficient this way? It’s working well…But I would like to know if there is a more efficient way to do this?

I want to free up processing and RAM to implement other functions.

This Rotine take the temperature:

void v_LerSensor(void *parameters){
      DS18B20.begin();    // initialize the DS18B20 sensor
      float tempC;
      float tempF;
      while(1){
            DS18B20.requestTemperatures();       // send the command to get temperatures
            tempC = DS18B20.getTempCByIndex(0);  // read temperature in °C
            if (tempC!=-127.00){
              tempF = tempC * 9 / 5 + 32; // convert °C to °F
              xQueueSend(TempC_Queue, &tempC, pdMS_TO_TICKS(0));
            }else{
              Serial.println("[INFO] - Sensor desconectado");
              xTaskCreate(v_DisplayTempDesc, "v_DisplayTempDesc", 4096, NULL, 3, NULL);
            }
            vTaskDelay(30 / portTICK_PERIOD_MS);
      }
}

and rotine for date and hour:


void v_DataHora(void *parameters){
     char data_formatadaZ[64];
     while(1){
          time_t t = now();
          sprintf(datahora_formatadaX, "%02d/%02d/%04d %02d:%02d:%02d", day(t), month(t), year(t), hour(t), minute(t), second(t));
          sprintf(data_formatadaX, "%04d-%02d-%02d", year(t), month(t), day(t));
          strcpy(data_formatadaZ, datahora_formatadaX);
          xQueueSend(datahora_formatadaX_Queue, &data_formatadaZ, pdMS_TO_TICKS(0));
          v_RotinaEnviaMapa(datahora_formatadaX);
          v_RotinaApagarLog(datahora_formatadaX);
          vTaskDelay(10 / portTICK_PERIOD_MS);
     }
}

The code below is actually my question:

Is it correct to do it this way?

//Funções que rodam o tempo todo para atualizar o display:
        void v_DisplayTemp(void *parameters){
            float tempReceived;
            float tempReceivedAnt;
            char tempC_char[20];
            String systemUpTime;

            while(1){
                    if (xQueueReceive(TempC_Queue, &tempReceived, portMAX_DELAY)){
                        if (tempReceived !=tempReceivedAnt){
                            sprintf(tempC_char, "%.2fºC", tempReceived);
                            tempReceivedAnt=tempReceived;
                            if (xSemaphoreTake(xMutex, portMAX_DELAY)) {
                                lv_meter_set_indicator_value(MyGauge[0], indic1[0], tempReceived);
                                lv_label_set_text(ui_lblTemp, tempC_char);
                                lv_timer_handler();
                                //vTaskDelay(10 / portTICK_PERIOD_MS);
                                xSemaphoreGive(xMutex);

                            } 
                        }
                    }
            }
        }

        void v_DisplayUptime(void *parameters){
            String systemUpTime;
            while(1){
                  uptime::calculateUptime();
                  if (xSemaphoreTake(xMutex, 300)) {
                      systemUpTime = String("Uptime: ") + String(uptime::getDays())+"d "+String(uptime::getHours())+":"+String(uptime::getMinutes())+":"+String(uptime::getSeconds());
                      lv_label_set_text(ui_lblUptime, systemUpTime.c_str());
                      lv_timer_handler();
                      //vTaskDelay(10 / portTICK_PERIOD_MS);
                      xSemaphoreGive(xMutex);
                  }
            }
        }

        void v_DisplayHora(void *parameters){
            char timeReceived_char[64];
            while(1){
                    if (xQueueReceive(datahora_formatadaX_Queue, &timeReceived_char, portMAX_DELAY)){
                        if (xSemaphoreTake(xMutex, 100)) {
                            lv_label_set_text(ui_lblDataHora, timeReceived_char);
                            lv_timer_handler();
                            //vTaskDelay(10 / portTICK_PERIOD_MS);
                            xSemaphoreGive(xMutex);
                        } 
                    }
            }
        }
//-----------------------------------------------------------------------------------------------------------------------------------------------
//Executa somente se ....Estiver habilitado, não é o caso no momento.
        void StatusMQTT(bool status){
            if (xSemaphoreTake(xMutex, 200)) {
                if (status){
                  lv_obj_clear_flag(ui_imgMQTT, LV_OBJ_FLAG_HIDDEN);
                }else{
                  lv_obj_add_flag(ui_imgMQTT, LV_OBJ_FLAG_HIDDEN);
                }
                lv_timer_handler();
                xSemaphoreGive(xMutex);
            }
        }

        void StatusSDCard(bool status){
                if (status){
                  lv_obj_clear_flag(ui_imgSDCard, LV_OBJ_FLAG_HIDDEN);
                }else{
                  lv_obj_add_flag(ui_imgSDCard, LV_OBJ_FLAG_HIDDEN);
                }
                lv_timer_handler();
        }
//-----------------------------------------------------------------------------------------------------------------------------------------------
//Tarefas que mantem as imagens do LVGL atualizadas
        void StatusWifi_img(bool status){
          if (xSemaphoreTake(xMutex, portMAX_DELAY)) {
              if (status){
                lv_obj_clear_flag(ui_imgWifiOn, LV_OBJ_FLAG_HIDDEN);
                lv_obj_add_flag(ui_imgWifiOff, LV_OBJ_FLAG_HIDDEN);
              }else{
                lv_obj_clear_flag(ui_imgWifiOff, LV_OBJ_FLAG_HIDDEN);
                lv_obj_add_flag(ui_imgWifiOn, LV_OBJ_FLAG_HIDDEN);
              }
              lv_timer_handler();
              xSemaphoreGive(xMutex);
          }
        }

        void vStatusError(void *pvParameters){
          bool status = (bool)pvParameters;
          TickType_t now;
          now = xTaskGetTickCount();
                if (status){
                  xSemaphoreTake(xMutex, portMAX_DELAY);
                      lv_obj_clear_flag(ui_imgError, LV_OBJ_FLAG_HIDDEN);
                      lv_label_set_text(ui_lblMSGEmail, "Erro!");
                      lv_timer_handler();
                  xSemaphoreGive(xMutex);
                  vTaskDelayUntil(&now,pdMS_TO_TICKS(25000));
                  xSemaphoreTake(xMutex, portMAX_DELAY);
                      lv_obj_add_flag(ui_imgError, LV_OBJ_FLAG_HIDDEN);
                      lv_label_set_text(ui_lblMSGEmail, "Mapa Enviado por e-mail:");
                      lv_timer_handler();
                  xSemaphoreGive(xMutex);              
                }else{
                  xSemaphoreTake(xMutex, portMAX_DELAY);
                      lv_obj_add_flag(ui_imgError, LV_OBJ_FLAG_HIDDEN);
                      lv_label_set_text(ui_lblMSGEmail, "Mapa Enviado por e-mail:");
                      lv_timer_handler();
                  xSemaphoreGive(xMutex);
                }
            vTaskDelete(NULL); 
        }

        void vStatusEmail(void *pvParameters){
              bool status = (bool)pvParameters;
              TickType_t now;
              now = xTaskGetTickCount();
                  if (status){
                    xSemaphoreTake(xMutex, portMAX_DELAY);
                        lv_obj_add_flag(ui_imgEmailFalha, LV_OBJ_FLAG_HIDDEN);
                        lv_obj_clear_flag(ui_imgEmailEnviado, LV_OBJ_FLAG_HIDDEN);
                        lv_timer_handler();
                    xSemaphoreGive(xMutex);
                    vTaskDelayUntil(&now,pdMS_TO_TICKS(25000));
                    xSemaphoreTake(xMutex, portMAX_DELAY);
                        lv_obj_add_flag(ui_imgEmailEnviado, LV_OBJ_FLAG_HIDDEN);
                        lv_timer_handler();
                    xSemaphoreGive(xMutex);
                  }else{
                    xSemaphoreTake(xMutex, portMAX_DELAY);
                        lv_obj_add_flag(ui_imgEmailEnviado, LV_OBJ_FLAG_HIDDEN);
                        lv_obj_clear_flag(ui_imgEmailFalha, LV_OBJ_FLAG_HIDDEN);
                        lv_timer_handler();
                    xSemaphoreGive(xMutex);
                    vTaskDelayUntil(&now,pdMS_TO_TICKS(25000));
                    xSemaphoreTake(xMutex, portMAX_DELAY);
                        lv_obj_add_flag(ui_imgEmailFalha, LV_OBJ_FLAG_HIDDEN);
                        lv_timer_handler();
                    xSemaphoreGive(xMutex);
                  }
              vTaskDelete(NULL);        
        }

        void v_DisplaySinalWifi(void *parameters) {
              int SinalWifi;
              while(1) {
                  if (wifiConectado) {
                      if (xQueueReceive(SinalWifi_Queue, &SinalWifi, portMAX_DELAY)){
                        if (xSemaphoreTake(xMutex, portMAX_DELAY)) {
                          String sinalWifiStr = String(SinalWifi);
                            lv_label_set_text(ui_lblSinal, sinalWifiStr.c_str());
                            lv_timer_handler();
                            xSemaphoreGive(xMutex);
                        } 
                      }
                  } else {
                      if (xSemaphoreTake(xMutex, portMAX_DELAY)) {
                              lv_label_set_text(ui_lblSinal, "DESCONECTADO");
                              lv_timer_handler();
                              xSemaphoreGive(xMutex);
                          } 
                  }
                  vTaskDelay(1500 / portTICK_PERIOD_MS);
              }    
        }
//-----------------------------------------------------------------------------------------------------------------------------------------------
// Essa tarefa abaixo é executada somente se o sensor desconectar. 
void v_DisplayTempDesc(void *parameters){
      if (xSemaphoreTake(xMutex, portMAX_DELAY)) {
          lv_meter_set_indicator_value(MyGauge[0], indic1[0], -100);
          lv_label_set_text(ui_lblTemp, "DESCON");
          lv_timer_handler();
          xSemaphoreGive(xMutex);
      } 
      vTaskDelete(NULL);
}

Any suggestion ?

@kdschlosser @kisvegabor @embeddedt

I am not an expert, but this looks fine to me, as per my limited knowledge, but other experts can comment.
I would like to add one point only.
Check this link.
Operating system and interrupts — LVGL documentation

I will do something like this, as shown below.

void v_DisplayTemp(void *parameters)
 {
  float tempReceived;
  float tempReceivedAnt;
  char tempC_char[20];
  String systemUpTime;

  while(1)
  {
    if (xSemaphoreTake(xMutex, portMAX_DELAY)) 
    {
      lv_timer_handler();
      // give semaphor when flushing is complete, if u are using dma this could be a problem     
      xSemaphoreGive(xMutex);
    } 
 
   if (xQueueReceive(TempC_Queue, &tempReceived, pdMS_TO_TICKS(10)))
    {
      if (tempReceived !=tempReceivedAnt)
      {
        sprintf(tempC_char, "%.2fºC", tempReceived);
        tempReceivedAnt=tempReceived;
        lv_meter_set_indicator_value(MyGauge[0], indic1[0], tempReceived);
        lv_label_set_text(ui_lblTemp, tempC_char);        
      }
    }
  }
}

The following lines are from the link, I shared above.

If you need to use real tasks or threads, you need a mutex which should be invoked before the call of lv_timer_handler and released after it. Also, you have to use the same mutex in other tasks and threads around every LVGL (lv_…) related function call and code. This way you can use LVGL in a real multitasking environment. Just make use of a mutex to avoid the concurrent calling of LVGL functions.

I will not wait for the data in the Queue, to unblock the calling of lv_timer_handler.
Plus I will also take care of releasing the semaphore, because sometimes we use DMA, and the semaphore should be released when the transfer is completed.
I don’t know if this is correct or the best way, but I do this way, and all the graphics updates I do in the same task, and by doing this I don’t have to take care of putting additional semaphore lock on the lvgl update functions.

You made an excellent suggestion to me!
Thank you very much!

I loved it and I’m going to implement it.

However, one question remains:

Don’t I need a Mutex when I change other objects?

     sprintf(tempC_char, "%.2fºC", tempReceived);
        tempReceivedAnt=tempReceived;
        lv_meter_set_indicator_value(MyGauge[0], indic1[0], tempReceived);
        lv_label_set_text(ui_lblTemp, tempC_char);   

I am not an expert, I am also learning so not 100% sure, but as per the link I shared, if lv_timer_handler and other lvgl functions are called from the same task, then it is not required.

So if you use lvgl function in same task then not required, if not then you have to use the same mutex used to protect the lv_timer_handler

We are both learning… so.

From what appears in the example, it includes all LVGL tasks within the mutex.

I think it should be included then.

1 Like

Hmmm, actually if you see the function name it is named as other_thread which indicates that this is a different thread or different task, hence mutex is needed here. (Maybe I am wrong, but this is what I understood as of now)

static void gui_task(void *pvParameter)
{
  gui_q_msg_t msg;
  msg.event_id = GUI_MNG_EV_NONE;

  while(1)
  {
    vTaskDelay(pdMS_TO_TICKS(20));

    // refresh the display
    gui_refresh();

    // wait only 5 ms and then proceed
    if( xQueueReceive(gui_event, &msg, pdMS_TO_TICKS(10)) )
    {
      // the below is the code to handle the state machine
      if( GUI_MNG_EV_NONE != msg.event_id )
      {
        switch( msg.event_id )
        {
          case GUI_MNG_EV_TEMP_HUMID:
            gui_update_temp_humid();
            break;
          default:
            break;
        } // switch case end
      }   // if event received in limit end
    }     // xQueueReceive end
  }
}

/**
 * @brief gui refresh, this function will refresh the lvgl
 * @param  none
 */
static int max_flushing_time = 0;
static void gui_refresh( void )
{
  int64_t start_time = 0;
  if( GUI_LOCK() )
  {
    start_time = esp_timer_get_time();
    lv_timer_handler();
    // Semaphore is released when flushing is completed, this is checked using
    // tft_flush_status function, and then we release the semaphore
    // GUI_UNLOCK();
  }

  // check flushing status
  if( tft_flush_status() == true )
  {
    // printf("Flushing Time: %d" PRId64 ", %" PRId64 "\n", esp_timer_get_time(), start_time);
    int time_taken = (int32_t)((esp_timer_get_time() - start_time)/1000);
    if( time_taken > max_flushing_time )
    {
      max_flushing_time = time_taken;
      printf("Flushing Time: %d ms\n", max_flushing_time );
    }
    GUI_UNLOCK();
  }
}

The above code snippet is from my project, from this link.

Also if you ask me, we shouldn’t lock and unlock other the LVGL function calls if the lv_timer_handler is present in the same task, although it doesn’t do any harm, will waste only some CPU cycles.
The most important thing is that we should call other LVGL function in the same task, then as per my understanding mutex is not needed, if using in other tasks, mutex is needed and that too the same mutex.
In the example I shared above, I am updating everything in the same task, so mutex is needed only for lv_timer_handler function.

Also check this message.

I would like to highlight again, that as you I am learning, so I might be wrong.
Let’s wait for some comments from some experts.

Reading Colleague’s Post,

Yes, every function that refers to LVGL, both TimerHandle and others such as SetText, must share the same Mutex lock.

I have already implemented your suggestions, such as separating tasks.

It looked like this:

        void v_AtualizaDisplay(void *parameters){
            while(1){
                  if (xSemaphoreTake(xMutex, portMAX_DELAY)) {
                      lv_timer_handler();
                      xSemaphoreGive(xMutex);
                  }
                  vTaskDelay(10 / portTICK_PERIOD_MS);
            }
        }

        void v_DisplayTemp(void *parameters){
            float tempReceived;
            float tempReceivedAnt;
            char tempC_char[20];

            while(1){
                  if (xQueueReceive(TempC_Queue, &tempReceived, portMAX_DELAY)){
                      if (tempReceived !=tempReceivedAnt){
                          sprintf(tempC_char, "%.2fºC", tempReceived);
                          tempReceivedAnt=tempReceived;
                          lv_meter_set_indicator_value(MyGauge[0], indic1[0], tempReceived);
                          lv_label_set_text(ui_lblTemp, tempC_char);
                      }
                  }
                  vTaskDelay(30 / portTICK_PERIOD_MS);
            }
        }

        void v_DisplayHora(void *parameters){
            char timeReceived_char[64];
            while(1){
                  xQueueReceive(datahora_formatadaX_Queue, &timeReceived_char, 50);
                  if (xSemaphoreTake(xMutex, portMAX_DELAY)) {
                      lv_label_set_text(ui_lblDataHora, timeReceived_char);
                      xSemaphoreGive(xMutex);
                  }
                  vTaskDelay(50 / portTICK_PERIOD_MS); 
            }
        }

        void v_DisplayUptime(void *parameters){
            String systemUpTime;
            while(1){
                  uptime::calculateUptime();
                  if (xSemaphoreTake(xMutex, 300)) {
                      systemUpTime = String("Uptime: ") + String(uptime::getDays())+"d "+String(uptime::getHours())+":"+String(uptime::getMinutes())+":"+String(uptime::getSeconds());
                      lv_label_set_text(ui_lblUptime, systemUpTime.c_str());
                      xSemaphoreGive(xMutex);
                  }
                  vTaskDelay(2000 / portTICK_PERIOD_MS); 
            }
        }

Thread good practice name is total oposite for your example. You waste RTOS resources and MCU ticks…
Read and use Timers — LVGL documentation

The topic title suggests exactly that.

An opinion on how it should be.

Could you kindly help us?

I read the suggested documentation and it’s still not clear how it should be done.

help us please ?