Hi @bobwolff68 ,
Sorry I am a bit late to the party here…
I have created two large projects with LVGL and FreeRTOS and my approach doesn’t require mutexes…
I create a FreeRTOS thread for LVGL which carries out the initialisation. I then create a global ‘GUI’ message queue using FreeRTOS to receive messages/events from other parts of the system to update the GUI at runtime. This global message queue is managed by an LVGL timer which is used to process the message queue and perform tasks within the execution path of the LVGL thread only which negates the need for a mutex. This timer is called periodically by LVGL and updates screens widgets etc. on demand, I have found in my systems a period of 10mS usually gives a good performance, but depending on your CPU and other system load you may need to change this. Finally the thread goes on to call lv_task_handler()
periodically as required for the system to function. Code snippet GUI task:
// These are initialised in main.c
cpu0_globals->gui.msg_q = xQueueCreate( 128, sizeof( otg_sysmsg_t ) ); // Create GUI message queue
cpu0_globals->sys_action_q = xQueueCreate( 128, sizeof( otg_sysmsg_t ) ); // Create System message queue
void gui_update_task(void *p) {
// Initialise VGA Hardware
set_vga_prams( VGA_1440X900_60HZ_CVTRA );
// Initialise GUI
lv_init();
lv_theme_default_init(cpu0_globals->gui.disp, shmem_p->personality == OTG_IDU ? confp->sys.IDU_gui_colour : confp->sys.ODU_gui_colour,
get_theme_secondary_colour(), (((shmem_p->personality == OTG_IDU) ? confp->sys.IDU_style : confp->sys.ODU_style) ? 0 : 1), LV_FONT_DEFAULT);
lv_disp_drv_init((lv_disp_drv_t*)&cpu0_globals->gui.disp_drv);
lv_disp_draw_buf_init(&cpu0_globals->gui.disp_buf, (void*)LV_VBUF1_ADR, (void*)LV_VBUF2_ADR, (HOR_RES_MAX*VER_RES_MAX));
cpu0_globals->gui.disp_drv.flush_cb = vga_disp_flush;
cpu0_globals->gui.disp_drv.hor_res = HOR_RES_MAX; /*Set the horizontal resolution in pixels*/
cpu0_globals->gui.disp_drv.ver_res = VER_RES_MAX; /*Set the vertical resolution in pixels*/
cpu0_globals->gui.disp_drv.draw_buf = &cpu0_globals->gui.disp_buf;
cpu0_globals->gui.disp_drv.full_refresh = pdFALSE;
cpu0_globals->gui.disp_drv.direct_mode = pdTRUE;
cpu0_globals->gui.disp = lv_disp_drv_register((lv_disp_drv_t*)&cpu0_globals->gui.disp_drv);
lv_disp_set_bg_opa(NULL, LV_OPA_TRANSP);
startup_gui_create();
lv_timer_create((lv_timer_cb_t)process_msg_q, 10, NULL); // Check for GUI thread messages every 10ms
while(1) {
lv_task_handler();
vTaskDelay(pdMS_TO_TICKS(4));
}
}
I also have a system manager thread which consists of a second global FreeRTOS ‘System’ queue and task which receives system functions triggered by GUI actions, these can be actions which require file system access, networks requests or what ever. By doing this the GUI can be kept responsive during various background tasks, by scheduling these other tasks at a lower priority. For example if a configuration requires saving to flash this may take a second or two, if you click a button in LVGL and call the code to save to flash directly in the LVGL event handler it will block the GUI until the save returns. If you instead queue an event request, the GUI won’t block and the system manager will pull the event from the queue and execute the save to flash at a lower priority in the background leaving the GUI responsive during the flash update, which is much more pleasing to the user.
Code snippet for LVGL timer function receive and process requests to update parts of the GUI:
static void process_msg_q ( lv_timer_t *timer ) {
char *pmsg = NULL;
otg_sysmsg_t msg = { 0, NULL, 0, 0, shmem_p->personality, 0, NULL };
otg_eventdb_entry_t *db_entry;
if( ( xQueueReceive( cpu0_globals->gui.msg_q, &msg, 0 ) ) ) {
if( msg.id < LOGMSG_ID_END ){
if( (db_entry = get_log_msg_by_id( msg.id ) ) == NULL ) return;
if( cpu0_globals->gui.screen.main.stup_ta != NULL && (db_entry->category == log_cat_none) ) {
lv_textarea_add_text( cpu0_globals->gui.screen.main.stup_ta, db_entry->msg ); // All startup messages are sent raw to pseudo console
lv_textarea_add_text( cpu0_globals->gui.screen.main.stup_ta, "\n" );
} else {
if( msg.extra_data != NULL ) {
pmsg = strstr( msg.extra_data,"->" ); // This strips the date off the front of messages so we don't print it on screen
if( pmsg != NULL ) {
pmsg += 2;
} else pmsg = msg.extra_data;
}
show_sys_message( pmsg );
}
} else {
switch( msg.id ) {
case LOAD_MAIN_GUI:
lv_obj_del( cpu0_globals->gui.screen.main.stup_ta );
cpu0_globals->gui.screen.main.stup_ta = NULL;
lv_obj_del( cpu0_globals->gui.screen.main.bgrd );
main_gui_create();
break;
case RESET_GUI_LOG:
reset_gui_log();
break;
case REFRESH_GUI_LOG:
refresh_gui_log();
break;
case UPDATE_GUI_SYS_SCR:
sys_scrupdate();
break;
case UPDATE_GUI_SITE_SCR:
site_estupdate();
site_sysupdate();
site_antupdate();
break;
case UPDATE_GUI_SAT_SCR:
sat_scrupdate();
break;
case UPDATE_GUI_TRK_SCR:
track_scrupdate();
break;
case UPDATE_GUI_SIM_SCR:
sim_scrupdate();
break;
case UPDATE_GUI_THEME:
update_gui_theme( pdTRUE );
break;
default:
break;
}
}
if( msg.free_extra & FREE_EXTRA ) vPortFree( msg.extra_data );
}
}
Code snippet for system manager:
static void sysmansup( void *p ) {
otg_sysmsg_t msg = { 0, NULL, 0, 0, shmem_p->personality, log_src_sysman, NULL };
otg_netcom_pkt_t net_msg = { { 0, 0, 0, 0 }, NULL, NULL };
while( 1 ) {
if( xQueueReceive( cpu0_globals->sys_action_q, &msg, pdMS_TO_TICKS(111) ) ) {
switch(msg.id) {
case SAVE_CONFIG:
if( !save_config( confp, log_src_sysman ) ) {
msg.id = INF_SAVE_CONFIG_OK;
} else {
msg.id = ERR_SAVE_CONFIG;
}
break;
case REM_SAVE_CONFIG:
if( !save_config( confp, log_src_sysman ) ) {
msg.id = INF_RSAVE_CONFIG_OK;
} else {
msg.id = ERR_RSAVE_CONFIG;
}
break;
case SAVE_CONFIG_TLE:
if( !save_config( confp, log_src_sysman ) ) {
msg.id = INF_TLE_SAVE_CONFIG_OK;
} else {
msg.id = ERR_TLE_SAVE_CONFIG;
}
break;
case CLR_LOG_REQ:
msg.id = LOG_CLEAR_EVENT;
q_event( &msg ); // Special case so we queue here.
break;
case SYS_REBOOT:
msg.id = WARN_USR_REBOOT;
q_event( &msg );
vTaskDelay(pdMS_TO_TICKS(1000));
msg.id = WARN_REBOOT_INPROG;
msg.extra_data = NULL;
msg.edat_size = 0;
q_event( &msg );
vTaskDelay(pdMS_TO_TICKS(4000));
system_reboot();
break;
case ERASE_FP_FLASH:
if( erase_factory_prams(log_src_sysman) ) {
msg.id = INF_FP_FL_ERASE_OK;
} else {
msg.id = ERR_FP_FL_ERASE_FAIL;
}
break;
case PROG_FP_FLASH:
if( save_factory_prams(log_src_sysman) ) {
msg.id = INF_FP_FL_PROG_OK;
} else {
msg.id = ERR_FP_FL_PROG_FAIL;
}
break;
case LOAD_EVENT_LOG:
load_event_log();
break;
case UPDATE_CONFIG_TLES:
update_config_tles(log_src_sysman);
break;
case GET_SYSTEM_TEMPERATURE:
shmem_p->sys_temp = get_sys_temp( log_src_sysman ); // Update System Temperature as per TEMP_POLL_COUNT
break;
default:
break;
}
if( msg.id < LOGMSG_ID_END ) q_event( &msg );
}
if( xQueueReceive( cpu0_globals->event_q, &msg, 0 ) ) {
process_event( &msg );
}
}
cpu0_globals->spawn_stat &= ~SYSMD_RUN;
vTaskDelete(NULL);
}
I have created a full port of LVGL and FreeRTOS here which uses the described methodology for the Xilinx Zynq platform but the core parts discussed above could be easily ported to other platforms. If this is a useful approach for you to eliminate the use of mutexes (I, being quite old tend to forget to add the calls! ) and you want to discuss it further or have questions please don’t hesitate to comment here and I will do my best to respond quickly.
Kind Regards,
Pete