ESP32 tft.fillScreen в задаче FreeRtos

#include <Arduino.h>
#include "SPIFFS.h"
#include <FreeRTOSConfig.h>

#define STACK_SIZE_LCD 4096
StaticTask_t xTaskBuffLcd;
StackType_t xStackBuffLcd[STACK_SIZE_LCD];
QueueHandle_t queue;
struct Ttouch
{
  uint16_t x;
  uint16_t y;
};
uint32_t j;

#include <TFT_eSPI.h> // Hardware-specific library// ESP32 various dev board     : CS:  5, DC: 27, RST: 33, BL: 22, SCK: 18, MOSI: 23, MISO: nil
#include <SPI.h>

TFT_eSPI tft = TFT_eSPI(); // Invoke custom library

//***********************************************************
static void lcdTask(void *pvParameters)
{

  Serial.println("Start lcdTask");

  portMUX_TYPE m;
  vPortCPUInitializeMutex(&m);
  for (;;)
  {
    Ttouch t;
    xQueueReceive(queue, &t, portMAX_DELAY);
    if (t.x < 50 && t.y < 50)
    {
      // vPortEnterCritical(&m);
      tft.fillScreen(TFT_BLACK);
      // vPortExitCritical(&m);
    }
    else
      tft.fillCircle(t.x, t.y, 2, TFT_WHITE);
  }
}

void setup()
{
  Serial.begin(115200);
  Serial.println("\r\nreset\n");

  tft.init();
  tft.setRotation(1);
  tft.fillScreen(TFT_BLACK);
  tft.print("Start setup");

  queue = xQueueCreate(5, sizeof(Ttouch));
  if (queue == NULL)
    Serial.println("queue error");

  TaskHandle_t xHandleLcd = NULL;
  xHandleLcd = xTaskCreateStaticPinnedToCore(lcdTask, "Lcd", STACK_SIZE_LCD, NULL, 1, xStackBuffLcd, &xTaskBuffLcd, 0);
  if (xHandleLcd == NULL)
    Serial.println("Error creating lcd task!");
}

void loop()
{
  Ttouch t;
  bool pressed = tft.getTouch(&t.x, &t.y); // Draw a white spot at the detected coordinates
  if (pressed)
  {
    xQueueSend(queue, &t, 0);
    j++;
    Serial.print("th ");
    Serial.println(j);
  }
  vTaskDelay(1);
}

Пробую разобраться с FreeRtos. В задаче lcdTask при выполнении строки tft.fillScreen(TFT_BLACK); программа виснет. Как мне кажется, в tft.fillScreen могут использоваться прерывания и прямой доступ к памяти, из-за этого задача виснет, возможно планировщик в момент работы tft.fillScreen начинает переключать задачу на loop. Критическая секция vPortEnterCritical(&m) помогает, зависать перестает, как правильно поступают в таких случаях? Возможно мои выводы не верны. Хочется разобраться с этим вопросом. Первая программа с использованием FreeRtos и такие грабли.

Ты не понимаешь что такое прерывание.

А бывает кривой?

Где ты это вообще откопал? Какой-то внутренний недокументированный вызов.

Как ведет себя твоя функция, и что с ней происходит, легко определить, прочитав ее исходный код.

ты чё? реально не знаешь что значит термин “прямой доступ к памяти”?

static void lcdTask(void *pvParameters)
{
  for (;;)
  {
    Ttouch t;
    xQueueReceive(queue, &t, portMAX_DELAY);
    tft.fillScreen(TFT_BLACK);
}

Можете объяснить почему эта задача зависает?
Еще раз повторюсь, если в задаче FreeRtos вызываются функции использующие аппаратные ресурсы, например DMA, то как пишется задача FreeRtos? Пока предполагаем что только в одной задаче используется доступ к SPI с использованием DMA.

Не знаю. Что такое контроллер прямого доступа к памяти, на который ты ссылаешься, - знаю. Только при чем он тут, опять же.

ТС! Вставь перед чтением из очереди delay(1); и все станет хорошо.

Enter critical и exit critical - макросы при правильном проектировании приложения почти никогда не нужны. Ибо обращаться к ресурсам из разных потоков - лучший способ выстрела себе в ногу! :wink: Честно. Многозадачная ОС имеет свои особенности.

1 лайк

Потому, что ей негде отдать управление диспетчеру. Вставь delay.

Не заработало, вот что в терминале

th 1
E (32397) task_wdt: Task watchdog got triggered. The following tasks did not reset the watchdog in time:
E (32397) task_wdt:  - IDLE (CPU 0)
E (32397) task_wdt: Tasks currently running: 
E (32397) task_wdt: CPU 0: Lcd
E (32397) task_wdt: CPU 1: loopTask
E (32397) task_wdt: Aborting.
abort() was called at PC 0x400db671 on core 0
Backtrace: 0x40083435:0x3ffbe9dc |<-CORRUPTED
static void lcdTask(void *pvParameters)
{
  for (;;)
  {
    Ttouch t;
    vTaskDelay(1);
    xQueueReceive(queue, &t, portMAX_DELAY);
    if (t.x < 50 && t.y < 50)
      tft.fillScreen(TFT_BLACK);
    else
      tft.fillCircle(t.x, t.y, 2, TFT_WHITE);
  }
}

void loop()
{
  Ttouch t;
  bool pressed = tft.getTouch(&t.x, &t.y); 
  if (pressed)
  {
    xQueueSend(queue, &t, 0);
    j++;
    Serial.print("th ");
    Serial.println(j);
  }
  vTaskDelay(1);
}

Увелич делей. Можешь, кстати, писать delay(), а не вот это вот все. :wink: Для проверки, вместо тфт своего, просто вывод в сериал сперва напиши.

Не исключено, что tft.fillScreen(TFT_BLACK) долго висит в потоке без отдачи процессорного времени.

Заменить это дело на временную затычку можно для того, чтобы убедиться, что задача в целом исполняется нормально.

Если библиотека не адаптирована, то ничего не сделаешь - будет изводить не тем, так этим и валить процессы рандомно для пользователя.

1 лайк

Кстати, у меня какое-то фантомное воспоминание о том, что в idle piority task с wdt попроще как-то. Хотя, я программингом не занимаюсь с утра до вечера - могу и путать.

А заряжать DMA на передачу двух байт для КАЖДОГО пикселя это не медленнее чем просто вывести два байта ?

void TFT_eSPI::pushBlock(uint16_t color, uint32_t len)
{
  uint8_t colorBin[] = { (uint8_t) (color >> 8), (uint8_t) color };
  if(len) spi.writePattern(&colorBin[0], 2, 1); len--;
  while(len--) {WR_L; WR_H;}
}

ТСу - как вариант закрашивать экран частями через fillrect … c отдачей управления/сбросом WDT между этими частями …

Не могёт иметь место быть! :wink:
В ЕСП32 луп исполняется с Айдл приорити в точно таком же потоке. Новый от лупа не отличается даже с лупой, Прости за “калом бур”.
Если библиотека вообще умеет “в ЕСП32”, то и так тоже сумеет.

Если ёрничать перестать, то у ТС есть огрехи, но не в этом. Может сегодня, когда (если) встану и позавтракаю, то напишу Трактат о принципах работы с FreeRTOS. Коротенько, минут на сорок. :wink:

Я вижу, что таску он не с tskIDLE_PRIORITY запускает, и потом у её внутри вачдог начинает возбуждаться. Если он делеев напихал, то единственный затык - это функция неизвестной библиотеки.
По мне так выходит.

 xHandleLcd = xTaskCreateStaticPinnedToCore(lcdTask, "Lcd", STACK_SIZE_LCD, NULL, 1, xStackBuffLcd, &xTaskBuffLcd, 0);

Так-то понятно, что и с очередями работа недотырена с мануала - нет проверки pdPASS == xQueueReceive , но это собаку не должно дёргать.

Вопрос решен, ответ в сообщении WladDrakula

Ибо обращаться к ресурсам из разных потоков - лучший способ выстрела себе в ногу!

в первой задаче обращение к SPI в функции tft.getTouch(&t.x, &t.y), во второй в ф. tft.fillScreen.

Круг также через SPI отрисовывается - там не зависало ?

Успевает выполнится, до переключения контекста. Задача опроса нажатия на тач всегда прерывает задачу отрисовки. Я как то упустил из виду что TFT_eSPI не использует вывод IRQ тача, а использует опрос SPI.

Вот и первая “ачивка” в многозадачной ОС. :wave:

Для работы с ресурсом выделяй всегда ОДИН поток и общайся с ним через xQueue.
В данном случае следует сделать поток в котором исполнять ЛЮБОЙ запрос к объекту TFT. Если по мере создания кода нужен метод от TFT, то его следует вызывать через “обертку” указанной очереди.

наткнулся как надо правильно решать проблему многозадачности на FreeRTOS