STM32-FreeRTOS如何适配LVGL

发布于:2025-08-04 ⋅ 阅读:(10) ⋅ 点赞:(0)

嫌文字看着累的话,直接搜账号名嵌入式crafter(小破站 / 某音 / 某红书同号),视频里把讲解的内容和实战操作揉在一起了,比对着文档啃效率高多了。

心跳函数

如果使用STM32Cubemx配置的FreeRTOS,需要先打开USE_TICK_HOOK。

添加图片注释,不超过 140 字(可选)

如果是自己移植的需要在FreeRTOSConfig中打开这个宏定义,vApplicationTickHook这个函数才会有效。

添加图片注释,不超过 140 字(可选)

LVGL 内部有很多定时器和动画需要依赖“时间”的概念。它自己不产生时间,而是需要用户每过 1 毫秒调用 lv_tick_inc(1) 通知它。vApplicationTickHook() 是 FreeRTOS 的 Tick Hook 回调函数,每 1ms 系统时钟节拍(SysTick)就会触发一次。

把 lv_tick_inc(1) 放在这里,就是利用了这个“时钟中断”,实现毫秒级 LVGL 计时。⚠️ 如果你不调用 lv_tick_inc(1),LVGL 的动画、定时器、长按等功能会出问题(比如 UI 没响应、定时器不执行)。

void vApplicationTickHook()
{
    //告诉lvgl已经过去了1毫秒
    lv_tick_inc(1);
}

创建LVGL任务

lv_timer_handler() 是 LVGL 的核心处理函数,相当于主循环(loop),你必须周期性调用它,才能让 UI 动起来。推荐的调用周期是每 5~10ms 一次,所以这里配合了 osDelay(5) 来做周期调度。

osThreadId_t lvglTaskHandle;
const osThreadAttr_t lvglTask_attributes = {
  .name = "defaultTask",
  .priority = (osPriority_t) osPriorityHigh,
  .stack_size = 128 * 8
};

lvglTaskHandle = osThreadNew(lvgl_Task, NULL, &defaultTask_attributes);

void lvgl_Task(void *argument)
{
  /* USER CODE BEGIN defaultTask */
  /* Infinite loop */
  for(;;)
  {
    	lv_timer_handler();  
    	osDelay(5);
  }
  /* USER CODE END defaultTask */
}

界面的切换

LVGL界面的切换和显示API的调用,最好都是放在同一个任务中。因为LVGL的API并不是线程安全的,如果你在多个任务同时去调用LVGL的API,会产生不可预料的问题。我这边是导致界面经常会卡死。

void Lvgl_Task(void *argument)
{
    /* USER CODE BEGIN LvglTask */
    const char* week_str[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
    const char* month_str[] = {
        "JAN", "FEB", "MAR", "APR", "MAY", "JUN",
        "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"
    };
    static lv_obj_t *last_screen = NULL;  // 用于记录“上一个界面”            
    static uint16_t count = 0;
    char buf[128];
    
    ui_init();
    user_event_bind();
    //printf("UI Init OK\r\n");
    
    for (;;)
    {
        count++;
        lv_timer_handler();  // LVGL 刷新处理
        osDelay(5);          // 建议 5~10ms,刷新帧率约 100~200FPS
        
        if(count % 40 == 0)
        {
            lv_obj_t *cur = lv_scr_act();
            
            int16_t angle_minutes = (int16_t)((GetTime.Minutes * 1.0f / 60.0f) * 3600.0f);
            int16_t angle_hours = (int16_t)((GetTime.Hours * 1.0f / 12.0f) * 3600.0f) + (int16_t)((GetTime.Minutes * 1.0f / 60.0f) * 300.0f);
   
            
            if (cur == ui_Screen1)
            {
                //printf(">> Update Screen1\r\n");
                
                lv_img_set_angle(ui_Screen1Hours, angle_hours);
                lv_img_set_angle(ui_Screen1Minute, angle_minutes);
                lv_img_set_angle(ui_Screen1Second, GetTime.Seconds * 60);
                                
                lv_label_set_text(ui_Screen1Week, week_str[GetData.WeekDay]);

                if (GetData.Month >= 1 && GetData.Month <= 12)
                    snprintf(buf, sizeof(buf), "%02d %s", GetData.Date, month_str[GetData.Month - 1]);
                
                lv_label_set_text(ui_Screen1Month, buf);

                snprintf(buf, sizeof(buf), "20%02d", GetData.Year);
                lv_label_set_text(ui_Screen1Year, buf);
                
                snprintf(buf, sizeof(buf), "%d", gSportStep);
                lv_label_set_text(ui_Screen1Step, buf);
            }
            
            if (cur == ui_Screen2)
            {
                //printf(">> Update Screen2\r\n");
                snprintf(buf, sizeof(buf), "%d", GetTime.Hours);
                lv_label_set_text(ui_Screen2Hours, buf);
                
                snprintf(buf, sizeof(buf), "%d", GetTime.Minutes);
                lv_label_set_text(ui_Screen2Minute, buf);
                
                lv_label_set_text(ui_Screen2Week, week_str[GetData.WeekDay]);

                snprintf(buf, sizeof(buf), "%02d %s", GetData.Date, month_str[GetData.Month - 1]);
                lv_label_set_text(ui_Screen2Month, buf);

                snprintf(buf, sizeof(buf), "20%02d", GetData.Year);
                lv_label_set_text(ui_Screen2Year, buf);
                
                snprintf(buf, sizeof(buf), "%d", gSportStep);
                lv_label_set_text(ui_Screen2Step, buf);
            }                
            
            if (cur == ui_Screen3)
            {
                //printf(">> Update Screen3\r\n");
                snprintf(buf, sizeof(buf), "%d", n_sp02);
                lv_label_set_text(ui_blood_oxygen, buf);

                snprintf(buf, sizeof(buf), "%d", n_heart_rate/4);
                lv_label_set_text(ui_heartbeat, buf);
                            
            }                
            if (cur == ui_Screen4)
            {
                //printf(">> Update Screen4\r\n");
                snprintf(buf, sizeof(buf), "%d", n_heart_rate/4);
                lv_label_set_text(ui_Screen4Heartbeat, buf);
            }                
            
            if (cur == ui_ScreenMenu)
            {
                int light_level = lv_slider_get_value(ui_LightSlider);
                Update_Backlight(light_level);
            }        
            
            if (cur == ui_ScreenAlarm) 
            {
                snprintf(buf, sizeof(buf), "%d", GetTime.Hours);
                lv_label_set_text(ui_ScreenAlarmHour, buf);
                
                snprintf(buf, sizeof(buf), "%d", GetTime.Minutes);
                lv_label_set_text(ui_ScreenAlarmMin, buf);

                int rollerHour_val = lv_roller_get_selected(ui_RollerHour);
                int rollerMin_val = lv_roller_get_selected(ui_RollerMin);
                bool switch_state = lv_obj_has_state(ui_Switch1, LV_STATE_CHECKED);
                
//                    printf("Alarm: %d:%d\r\n", rollerHour_val, rollerMin_val);
//                    printf("Current Switch State: %s\n", switch_state ? "ON" : "OFF");
                
                Update_Alarm(rollerHour_val, rollerMin_val, switch_state);                
            }
        }
        
        if(count % 200 == 0)
        {
            //printf("LVGL RUN FOR 1s\r\n");
            
            if (osSemaphoreAcquire(menuPageChangeSem, 0) == osOK)
            {
                lv_obj_t *cur = lv_scr_act();  // 当前界面

                if (cur == ui_ScreenMenu)
                {
                    // 返回之前的界面(如果存在)
                    if (last_screen != NULL && lv_obj_is_valid(last_screen))
                    {
                        lv_scr_load_anim(last_screen, LV_SCR_LOAD_ANIM_NONE, 300, 0, false);
                        //printf("change to lastScreen\r\n");
                    }
                }
                else
                {
                    // 记录当前界面为“上一个界面”
                    last_screen = cur;

                    // 进入菜单界面
                    if (lv_obj_is_valid(ui_ScreenMenu))
                    {
                        lv_scr_load_anim(ui_ScreenMenu, LV_SCR_LOAD_ANIM_NONE, 300, 0, false);
                        //printf("change to ScreenMenu\r\n");
                    }
                }
            }        
        }
        
        if(count >= 1000)
            count = 0;
    }
    /* USER CODE END LvglTask */
}

结尾

最后我也是准备了两份LVGL的代码,一份是标准库手工移植FreeRTOS-LVGL的例程,一份是HAL库STM32Cubemx生成的FreeRTOS的LVGL例程。有需要的朋友可以点赞关注下,私信我发给你。


网站公告

今日签到

点亮在社区的每一天
去签到