Hi Guys,
I wonder if anyone can help me.
I am building a MIDI router/processor, supporting DIN (serial), USB Host and USB Device.
I also want an LCD for the GUI, currently using an ILI9341/XPT2046.
The MIDI routing needs to run with minimal latency and jitter, it is very high priority, the GUI is much lower priority.
So I started out with Kurts wonderfull ILI9341_t3n lib which provides async api to the display, this gets rid of any blocking there.
Then I looked at finding some pre-rolled gui stuff and started looking at LGVL.
I have LGVL working on the Teensy 4.1 using ILI9341_t3n and async SPI, example code included at bottom.
The issue is the call to process LVGL can take quite a while, this time is not caused by the flushing to the frame buffer in DisplayFlush() which is very quick but elsewhere in the LVGL lib and there doesn't seem to be much you can do about this.
So what I am looking at is a way of processing my MIDI at a higher priority than the call to lv_task_handler(), I'm guessing using interrupts is a no go so are there any other ways?
It would be nice to use LGVL, but if I cannot get over this I will need to roll my own
Cheers
Andy
Sample output:
I wonder if anyone can help me.
I am building a MIDI router/processor, supporting DIN (serial), USB Host and USB Device.
I also want an LCD for the GUI, currently using an ILI9341/XPT2046.
The MIDI routing needs to run with minimal latency and jitter, it is very high priority, the GUI is much lower priority.
So I started out with Kurts wonderfull ILI9341_t3n lib which provides async api to the display, this gets rid of any blocking there.
Then I looked at finding some pre-rolled gui stuff and started looking at LGVL.
I have LGVL working on the Teensy 4.1 using ILI9341_t3n and async SPI, example code included at bottom.
The issue is the call to process LVGL can take quite a while, this time is not caused by the flushing to the frame buffer in DisplayFlush() which is very quick but elsewhere in the LVGL lib and there doesn't seem to be much you can do about this.
So what I am looking at is a way of processing my MIDI at a higher priority than the call to lv_task_handler(), I'm guessing using interrupts is a no go so are there any other ways?
It would be nice to use LGVL, but if I cannot get over this I will need to roll my own
Cheers
Andy
Code:
#include <Arduino.h>
#include <lvgl.h>
#include <ILI9341_t3n.h>
#include <XPT2046_Touchscreen.h>
#define TFT_DC 9
#define TFT_CS 10
#define TS_CS_PIN 8
#define TS_TIRQ_PIN 2
#define THREAD_TIME 100
#define FB_BUF_SIZE (320*240)
#define LVGL_BUF_SIZE (320*10)
// touchscreen
XPT2046_Touchscreen g_touchScreen(TS_CS_PIN, TS_TIRQ_PIN);
ILI9341_t3n g_tft = ILI9341_t3n(TFT_CS, TFT_DC);
DMAMEM uint16_t fbTft[FB_BUF_SIZE];
// LVGL
lv_disp_buf_t g_lvDispBuf;
DMAMEM lv_color_t g_lvColorBuffer[LVGL_BUF_SIZE];
lv_disp_drv_t g_lvDisplayDriver;
lv_obj_t *g_plvBtn1 = NULL;
lv_obj_t *g_plvLeftLed = NULL;
lv_obj_t *g_plvRightLed = NULL;
// True if left LED is lit
bool g_bSignalLeft = true;
////////////////////////////////////////////////////////////////////////////////////
// Flush the LVGL data to the framebuffer
////////////////////////////////////////////////////////////////////////////////////
void DisplayFlush(lv_disp_drv_t *pDisplayDriver, const lv_area_t *pArea, lv_color_t *pColor)
{
uint32_t w = (pArea->x2 - pArea->x1 + 1);
uint32_t h = (pArea->y2 - pArea->y1 + 1);
g_tft.waitUpdateAsyncComplete(); // just incase!
g_tft.writeRect(pArea->x1, pArea->y1, w, h, &pColor->full);
lv_disp_flush_ready(pDisplayDriver);
}
////////////////////////////////////////////////////////////////////////////////////
// Handle the touch screen
////////////////////////////////////////////////////////////////////////////////////
bool HandleTouchscreen(lv_indev_drv_t *pIndevDriver, lv_indev_data_t *pData)
{
boolean touched = g_touchScreen.tirqTouched() && g_touchScreen.touched();
if (!touched)
{
pData->state = LV_INDEV_STATE_REL;
}
else
{
pData->state = LV_INDEV_STATE_PR;
TS_Point p = g_touchScreen.getPoint();
uint16_t touchX = p.x / 12;
uint16_t touchY = p.y / 16;
if (touchX > 320 || touchY > 240)
{
Serial.printf("Outside bounds (%u, %u)\n", touchX, touchY);
}
else
{
/*Set the coordinates*/
pData->point.x = touchX;
pData->point.y = touchY;
}
}
return false;
}
////////////////////////////////////////////////////////////////////////////////////
// Handle LGVL events
// When button pressed swap lit LED.
////////////////////////////////////////////////////////////////////////////////////
void event_handler(lv_obj_t *pObj, lv_event_t event)
{
if (event == LV_EVENT_CLICKED)
{
g_bSignalLeft = !g_bSignalLeft;
if (g_bSignalLeft)
{
lv_led_on(g_plvLeftLed);
lv_led_off(g_plvRightLed);
}
else
{
lv_led_on(g_plvRightLed);
lv_led_off(g_plvLeftLed);
}
}
}
////////////////////////////////////////////////////////////////////////////////////
// Setup
////////////////////////////////////////////////////////////////////////////////////
void setup()
{
Serial.begin(115200);
g_touchScreen.begin();
g_touchScreen.setRotation(1);
g_tft.begin(100000000);
g_tft.setRotation(1);
g_tft.fillScreen(ILI9341_BLACK);
g_tft.useFrameBuffer(true);
g_tft.setFrameBuffer(fbTft);
g_tft.updateChangedAreasOnly(true);
lv_init();
lv_disp_buf_init(&g_lvDispBuf, g_lvColorBuffer, NULL, LVGL_BUF_SIZE);
lv_disp_drv_init(&g_lvDisplayDriver);
g_lvDisplayDriver.hor_res = 320;
g_lvDisplayDriver.ver_res = 240;
g_lvDisplayDriver.flush_cb = DisplayFlush;
g_lvDisplayDriver.buffer = &g_lvDispBuf;
lv_disp_drv_register(&g_lvDisplayDriver);
lv_indev_drv_t indexDriver;
lv_indev_drv_init(&indexDriver);
indexDriver.type = LV_INDEV_TYPE_POINTER;
indexDriver.read_cb = HandleTouchscreen;
lv_indev_drv_register(&indexDriver);
g_plvBtn1 = lv_btn_create(lv_scr_act(), NULL);
lv_obj_set_event_cb(g_plvBtn1, event_handler);
lv_obj_align(g_plvBtn1, NULL, LV_ALIGN_CENTER, 0, 0);
lv_obj_t *label = lv_label_create(g_plvBtn1, NULL);
lv_label_set_text(label, "Switch LEDs");
g_plvLeftLed = lv_led_create(lv_scr_act(), NULL);
lv_obj_align(g_plvLeftLed, NULL, LV_ALIGN_CENTER, -80, -80);
lv_led_on(g_plvLeftLed);
g_plvRightLed = lv_led_create(lv_scr_act(), NULL);
lv_obj_align(g_plvRightLed, NULL, LV_ALIGN_CENTER, 80, -80);
lv_led_off(g_plvRightLed);
}
void loop()
{
static elapsedMillis elapsedMS;
// do not process LGVL when DMA is active
if(!g_tft.asyncUpdateActive())
{
elapsedMicros m;
lv_task_handler();
if(m>10)
Serial.printf("lv_task_handler %uus\n", (uint32_t)m); Serial.flush();
}
// 30 ms refresh rate
if(elapsedMS > 30)
{
g_tft.updateScreenAsync();
elapsedMS = 0;
}
}
Sample output:
Code:
lv_task_handler 86us
lv_task_handler 86us
lv_task_handler 5271us
lv_task_handler 41us
lv_task_handler 911us
lv_task_handler 871us
lv_task_handler 870us
lv_task_handler 870us
lv_task_handler 870us
lv_task_handler 580us
lv_task_handler 5114us
lv_task_handler 972us
lv_task_handler 2545us
lv_task_handler 925us
lv_task_handler 885us
lv_task_handler 888us
lv_task_handler 872us
lv_task_handler 871us
lv_task_handler 871us
lv_task_handler 872us