【嵌入式GUI】 LVGL_原初配置_相关bug

MCU:stm32f407 VET6

显示屏:TFT 3.5寸 480*320  ILI9488

开发软件:CLion        GUI Guider

AI:     智普清言4.0

说明:本篇暂时只介绍添加LVGL的过程和解决些许驱动问题的思路,不作过深的介绍,是有很多移植LVGL的优秀教程的。

一、准备工作        ૮(˶ᵔ ᵕ ᵔ˶)ა

1,下载LVGL库

本篇版本为LVGL 8.2,下面教程同正点原子        点击下载

点击Code后,再点击下面那个Download ZIP

2,整理文件

删除不必要的文件,保留如下

本篇工程项目为LVGL_RTOS

在你创建的工程项目下的Middlewares/Third_Party里,再创建如下目录

|

|——GUI

|          |——lvgl

|——GUI_APP

下面三者移至lvgl里,剩一个demos移至GUI_APP

lv_conf_template.h更名为lv_conf.h,同时把clang-format off下面的值改为1

3,包含文件

        按需把新加的LVGL库包含进去,不要把GUI_APP也包含进去了。此处仅包含了GUI/lvgl/srcGUI/lvgl/examplesGUI/lvgl/下的两个头文件

需要说明的是,下面的“CMakeLists代码”添加了其他你可能没有的目录,请自行对照

include_directories(
        # 用户库
        Application/inc
        Drivers/USER/inc
        FunctionalModuleLayer/inc
        DATA
        # FreeRTOS库
        Middlewares/Third_Party/FreeRTOS/Source/include
        Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS_V2
        Middlewares/Third_Party/FreeRTOS/Source/portable/GCC/ARM_CM4F
        # LVGL库
        Middlewares/Third_Party/LVGL/GUI
        Middlewares/Third_Party/LVGL/GUI/lvgl/src
        Middlewares/Third_Party/LVGL/GUI/lvgl/examples/porting
        # 官方驱动
        Core/Inc Drivers/STM32F4xx_HAL_Driver/Inc
        Drivers/STM32F4xx_HAL_Driver/Inc/Legacy
        Drivers/CMSIS/Device/ST/STM32F4xx/Include Drivers/CMSIS/Include)

add_definitions(-DDEBUG -DUSE_HAL_DRIVER -DSTM32F407xx)

file(GLOB_RECURSE SOURCES
        # 用户目录
        "Application/*.*" "FunctionalModuleLayer/*.*"
        "DATA/*.*"
        # LVGL库
        "Middlewares/Third_Party/LVGL/GUI/lvgl/src/*.*"
        "Middlewares/Third_Party/LVGL/GUI/lvgl/lvgl.h"
        "Middlewares/Third_Party/LVGL/GUI/lvgl/lv_conf.h"
        "Middlewares/Third_Party/LVGL/GUI/lvgl/examples/porting/*.*"
        # RTOS库
        "Middlewares/Third_Party/FreeRTOS/*.*"
        # 官方驱动
        "Core/*.*" "Drivers/*.*"
       )

二、配置        ૮₍ ˃ ⤙ ˂ ₎ა

这里面只包含里界面的初始化,没有触摸屏。

1,初始化

①添加LED驱动文件

一般来说,驱动和相应的协议是自带的。注意哈,不同屏幕的驱动不同,放置的目录也不同

②改条件编译

LVGL/lvgl/examples/proting/下找到下图文件,把条件编译指令改为1,同时添加LCD头文件

③添加初始化代码

在.c文件中找到disp_init函数,并添加自己LCD驱动的初始化代码

④宏定义与像素设置

设置自己屏幕的像素大小,此处为480*320分辨率(默认横屏,填写的顺序与你的分辨率一致)

找到下图代码,此处采用单缓冲模式,把剩下两种不用的模式屏蔽掉(不屏蔽的话RAM可能会爆)

并在其下面修改像素大小,由于这里默认为480*320,所以本篇未改

⑤打点输出函数

添加自己驱动的打点输出(区域打点),没有对应的LCD驱动代码可以跳到后面目录

 ⑥配置时基 

本篇使用TIM6定时器中断作为LVGL的时基,频率设置为1KHz,不要忘记开启中断。也可以使用FreeRTOS的钩子函数,如果你使用了FreeRTOS的话。

在合适的位置添上中断回调函数,并包含头文件

#include  "lvgl.h"

然后在中断函数里,调用

 lv_tick_inc(1);

然后在while循环里加上一个延时函数(如delay_ms(5);)和一个lv_timer_handler(); 延时5ms即可,不必精确

        本篇由于使用RTOS,故放在了一个任务里面。osDelay(x)延时不能去掉,有时候执行lv_timer_handler()一次的时间会超过了5ms,所以要慎用osDelayUntil(5)

谨记!lv_timer_handler(); 千万不能放在优先级大于等于 lv_tick_inc(1);所处的中断。换言之,最好不要放在中断里,不然内?存会爆!

osDelay(5);
lv_timer_handler();

2,测试demo[可选]

        用example下的get_started即可,有很多教程的。至于压力测试stress,这个真的得看实际情况,至少我这里是带不动的(板子特殊?)

三、BUG   (⌯꒪꒫꒪)੭

本篇采用的实验平台有些特殊,自带的LCD驱动很少,没有LCD_Color_Fill(...)于是就有了下篇

1.打印机理

【disp_flush的工作机理】

        每调用一次disp_flush即是处理一次缓冲数组,把缓冲数组里的数据打印在屏幕上。

        形参为两个坐标(sx,sy)(ex,ey),以及一个缓冲数组color

【缓冲数组】

        此处分辨率为480*320,默认横屏(320*480就是竖屏)。故缓冲数组的大小可设为320*rows,即缓冲数组里存了rows行的color数据。且rows是有最大限制的,本篇设备为43,rows建议使用宏定义

        除此之外,缓冲数组里存储的是颜色数据,是以先左后右,再上后下的扫描方向(3)排列的。

【LVGL打印】

        LVGL默认的坐标系是【左上角为起始位置,右下角为终止位置】,一般的显示器也是这样的坐标系。

       在此坐标系中, (sx,sy)是左上角的那个小坐标(源坐标),而(ex,ey)是右上角的那个大坐标

     

2.驱动相关

①区域颜色填充驱动代码

void LCD_Color_Fill(u16 sx,u16 sy,u16 ex,u16 ey,u16 *color)
{
    uint16_t i, j;
    uint16_t length, width;
    length = ex - sx+1;
    width = ey - sy+1;
    for (j = 0; j < width; j++)
    {
        for (i = 0; i <length; i++)
        {
            /*下面可以封装为一个函数*/
            LCD_WRITE_CMD(0x002A);
            LCD_WRITE_DATA((i+sx) >> 8);
            LCD_WRITE_DATA(0x00FF & (i+sx));
            LCD_WRITE_DATA((i+sx) >> 8);
            LCD_WRITE_DATA(0x00FF & (i+sx));
            LCD_WRITE_CMD(0x002B);
            LCD_WRITE_DATA((j+sy) >> 8);
            LCD_WRITE_DATA(0x00FF & (j+sy));
            LCD_WRITE_DATA((j+sy) >> 8);
            LCD_WRITE_DATA(0x00FF & (j+sy));
            LCD_WRITE_CMD(0x002C);
            /*上面可以封装为一个函数*/

            LCD_WRITE_DATA(color[i + length * j]);
        }
    }
}

经由AI极致优化过的代码

void LCD_Color_Fill(u16 sx,u16 sy,u16 ex,u16 ey,u16 *color)
{
    uint16_t i, j;
    uint16_t length, width;
    length = ex - sx + 1; // 矩形区域的长度
    width = ey - sy + 1;   // 矩形区域的宽度

    // 设置列起始地址(X坐标)
    LCD_WRITE_CMD(0x002A);
    LCD_WRITE_DATA(sx >> 8);
    LCD_WRITE_DATA(0x00FF & sx);
    LCD_WRITE_DATA((sx + length - 1) >> 8); // 设置列结束地址
    LCD_WRITE_DATA(0x00FF & (sx + length - 1));

    // 遍历矩形区域的每一行
    for (j = 0; j < width; j++)
    {
        // 设置页起始地址(Y坐标),在每行开始时设置一次
        LCD_WRITE_CMD(0x002B);
        LCD_WRITE_DATA((sy + j) >> 8);
        LCD_WRITE_DATA(0x00FF & (sy + j));
        LCD_WRITE_DATA((sy + j) >> 8); // 假设这里也是设置页结束地址,如果是单行则起始地址和结束地址相同
        LCD_WRITE_DATA(0x00FF & (sy + j));

        // 开始写入数据
        LCD_WRITE_CMD(0x002C);
        // 一次性写入整行的颜色数据
        for (i = 0; i < length; i++)
        {
            LCD_WRITE_DATA(color[i + length * j]);
        }
    }

// 如果需要,可以在这里添加代码来复位列地址和页地址,以避免潜在的问题
}

参照AI和一些博客后优化的代码(与LCD_Clear及其相似),其中前面的这些指令可以用宏定义内联函数优化

void LCD_Color_Fill(u16 sx,u16 sy,u16 ex,u16 ey,u16 *color)
{
    uint16_t i, j;
    uint16_t length, width;
    length = ex - sx + 1;// 矩形区域的长度
    width = ey - sy + 1; // 矩形区域的宽度

    // 设置列起始地址(X坐标)
    LCD_WRITE_CMD(0x002A);
    LCD_WRITE_DATA(sx >> 8);
    LCD_WRITE_DATA(0x00FF & sx);
    LCD_WRITE_DATA((ex) >> 8);// 设置列结束地址
    LCD_WRITE_DATA(0x00FF &ex);
    // 设置页起始地址(Y坐标),在每行开始时设置一次
    LCD_WRITE_CMD(0x002B);
    LCD_WRITE_DATA(sy >> 8);
    LCD_WRITE_DATA(0x00FF & sy);
    LCD_WRITE_DATA(ey >> 8);// 假设这里也是设置页结束地址,如果是单行则起始地址和结束地址相同
    LCD_WRITE_DATA(0x00FF & ey);

    // 开始写入数据
    LCD_WRITE_CMD(0x002C);
    // 遍历矩形区域的每一行
    for (j = 0; j < width; j++)
    {
        for (i = 0; i < length; i++)
        {
            LCD_WRITE_DATA(color[i + length * j]);// 一次性写入整行的颜色数据
        }
    }
}

        想必你可能会对这些指令感到熟悉,因为它与LCD_SetWindow并无二致,只不过我的LCD库里没有这个函数。。。

3,RAM爆了(GUI Guider生成的代码)

        以前没用过GUI Guider,刚一上手,结果就爆了。。。下面就讲讲具体的过程

        使用GUI Guider生成代码时,把相应文件也添加进去了,结果内存爆了,直接超了3倍多。至于下面报错提示是什么意思,可以非常方便地用AI分析出来

        查了一段时间也没查出来(包括联网),于是开始进行原始文件的替换,最终筛选出了一些可能的结果,其中是lv_conf.h的嫌疑最大。于是通过VS Code把它与原始的lv_conf.h对比一下,看看有什么不同

        运气使然,没找几行呢就找到了,是GUI Guider默认的内存太大了,改成合适的值即可(如右边的48)。小错真难找

4,图像无法自动刷新

①使用FreeRTOS的情况下

lv_task_handler()运行极慢,不能自动刷新。只好手动刷新。这时你要考虑lvgl的心跳有没有正常运作,绝大部分情况是 lv_tick_inc(1);没有运行

lv_refr_now(lv_disp_get_default());//手动刷新屏幕

下面是错误的例子

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == TIM7)//RTOS时基
    {
        HAL_IncTick();
    }
    else if (htim->Instance == TIM6)//LVGL时基
    {
// 错误的例子
        lv_tick_inc(1);
    }
}

解决办法是,用FreeRTOS的钩子函数配置时基,而非使用定时器中断。

 LVGL和FreeRTOS移植遇到的BUG,LVGL无法正常显示_lvgl freertos移植

void vApplicationTickHook( void )
{
	lv_tick_inc(1);
}

        需要注意的是,钩子函数配好后要检查一遍能不能正常运行。避免后面花大量时间来解决这个小问题。

        如果你是跟我一样使用的是C++环境,那么要注意了,在这个钩子函数外加上extern语句,不然编译器找不到,找不到就不会执行这个函数,而是执行_weak那个原始钩子函数。谓之为“失败的钩子函数”

/*钩子函数——用于配置LVGL的时基础*/
extern "C" {
void vApplicationTickHook()
{
    lv_tick_inc(1);
}
}

②补充一点

        如果你连lv_refr_now(NULL);都无法使用的话,那么可能是堆栈分配得不够,可以看看项目的堆栈配置和lvgl内存的分配

5,界面白屏/黑屏

        如果你调试时发现程序可以正常运转,但是界面没有任何反应,那应该是给lvgl分配的内存太小了,到lv_conf.h中把下面的值适量改大一些就行了

/*Size of the memory available for `lv_mem_alloc()` in bytes (>= 2kB)*/
#define LV_MEM_SIZE (16U * 1024U)

6,使用FreeRTOS时,含lv_task_handler的线程不执行但不报错

        这个可谓是我见过最逆天的bug了,属实没想到过,一个弃置的函数也能影响到FreeRTOS的运行。

        事情是这样的,我在调试过程中,定义了两个线程(任务),由于内存不够,所以它们不会同时使用。就是下面这两个任务,每一个都是内存消耗大户

void GUITask(void *argument)
{
    for (;;)
    {
        lv_task_handler();
        osDelay(5);// 特别奇怪,不加延迟或者使用osDelayUntil都不行
    }
}



void tempTask(void *argument)
{
    for (;;)
    {
        for (uint8_t i = 0; i < 3; ++i)
        {
           LCD_Clear(tempColor[i]);
           LCD_DrawPicture(80, 20, 400, 260, (u8 *) gImage[i]);
           delay_ms(1000);
        }
    }
}

        然后我在初始化函数里创建线程,很明显,第二个线程我压根没创建,也就是说它没有被调用过,理论上这个函数就不应该会对其他函数产生影响。结果却是花了几个小时才在无意中注释掉了其中一个任务,这才正常了,下次对于不用的函数还是用预编译或者注释屏蔽掉吧

void GUI_Init()
{
    /**外设初始化*/
    FSMC_init();

    //LCD_FSMC_DMA_Init();

    /**LVGL初始化*/
    lv_init();
    taskENTER_CRITICAL();
    LCD_Init();//  液晶初始化
    taskEXIT_CRITICAL();
    lv_port_disp_init();
    /**用户界面*/
    setup_ui(&guider_ui);   //界面初始化
    events_init(&guider_ui);//事件初始化

    /**初始化*/
    LCD_Clear(BLACK);//  设置液晶背景



    /**创建任务*/
    osThreadAttr_t attr = {
            .name = "GUITask",          // 可选的,为线程命名
            .stack_size = 512 * 4,    // 设置栈大小为512字节
            .priority = osPriorityNormal// 可选的,设置线程优先级
    };
    osThreadNew(GUITask, nullptr, &attr);//GUI任务

   //osThreadNew(tempTask, nullptr, nullptr);//tempTask任务
}

解决办法

void GUITask(void *argument)
{
    for (;;)
    {
        lv_task_handler();
        osDelay(5);// 特别奇怪,不加延迟或者使用osDelayUntil都不行
    }
}


/*

void tempTask(void *argument)
{
    for (;;)
    {
        for (uint8_t i = 0; i < 3; ++i)
        {
           LCD_Clear(tempColor[i]);
           LCD_DrawPicture(80, 20, 400, 260, (u8 *) gImage[i]);
           delay_ms(1000);
        }
    }
}
*/

可能的原因

7,LVGL文本显示异常_文件编码

        初始化时文本框可以正常显示,但是在其他函数里调用就会显示一个框。如果你的字库没有问题,那就是编码问题,比如你在A文件初始化文本框,这个A文件使用的UTF-8编码,而在B文件用的是GBK,那么在B文件中改为文本框内容就会造成显示异常,变成一个框。

8,使用C++的类来初始化绑定事件,但白屏进入硬件错误中断

卡死在blocksize之类的地方,意思就是lvlg分配的内存不够,然而把内存拉到99.99%,但仍无法使用。后来才发现,是两个屏幕的事件初始化不能放在一块,只能在两个界面初始化函数之后初始化绑定事件

#ifndef MYEVENT_SUBROUTINE_H
#define MYEVENT_SUBROUTINE_H

#include "ACDetection.h"
#include "LD3320_USART.h"
#include "LED.h"
#include "TmpHumi.h"
#include "gui_guider.h"
#include "step_motor.h"
#include <functional>
/**
 * 屏幕抽象类
 * 抽象类,提供事件绑定的简化
 */
class UIScreen
{
protected:
    lv_ui *_ui{};

public:
    explicit UIScreen(lv_ui *ui) : _ui(ui) {}

    // 简化事件绑定
    static void bindEvent(lv_obj_t *obj, lv_event_cb_t handler)
    {
        lv_obj_add_event_cb(obj, handler, LV_EVENT_ALL, nullptr);
    }
};


/****************************************************************************
 *
 * 音乐界面类集合
 *
 ****************************************************************************/

/**音乐子程序*/
class MusicSubroutine
{
public:
    static void musicResume() { /* 实现音乐恢复播放 */ }
    static void musicPause() { /* 实现音乐暂停 */ }
};

extern bool state_tracks_up;

/**音乐事件处理函数*/
class MusicHandler
{
public:
    // 播放暂停
    static void PlayPause(lv_event_t *e)
    {
        if (lv_obj_get_state(lv_event_get_target(e)) & LV_STATE_CHECKED)
        {
            MusicSubroutine::musicResume();
        }
        else
        {
            MusicSubroutine::musicPause();
        }
    }

    // 下一曲
    static void Next(lv_event_t *e)
    {
        switch (lv_event_get_code(e))
        {
            case LV_EVENT_CLICKED:
            {
                //      lv_demo_music_album_next(true);
                break;
            }
            default:
                break;
        }
    }

    // 上一曲
    static void Prev(lv_event_t *e)
    {
        switch (lv_event_get_code(e))
        {
            case LV_EVENT_CLICKED:
            {
                //      lv_demo_music_album_next(false);
                break;
            }
            default:
                break;
        }
    }

    //切换界面
    static void SwitchScreens(lv_event_t *e)
    {
        switch (lv_event_get_code(e))
        {
            case LV_EVENT_CLICKED:
            {
                osThreadResume(TmpHumi_TaskHandleId);
                ui_load_scr_animation(&guider_ui, &guider_ui.MainScreen, guider_ui.MainScreen_del, &guider_ui.MusicScreen_del, setup_scr_MainScreen, LV_SCR_LOAD_ANIM_MOVE_RIGHT, 800, 60, true, false);

                break;
            }
            default:
                break;
        }
    }

    /**
 * @brief 音乐屏幕按钮事件处理函数
 *
 * @param e 事件对象,包含事件的相关信息
 *
 * 该函数用于处理音乐屏幕上的按钮事件,特别是当按钮被点击时,
 * 它将启动一个动画来改变音乐播放器的位置,以提供不同的视觉效果。
 * 根据按钮的点击状态,动画将播放器向上或向下移动到一个新的位置。
 */

    static void Tracks(lv_event_t *e)
    {
        lv_event_code_t code = lv_event_get_code(e);
        switch (code)
        {
            case LV_EVENT_CLICKED:
            {
                // 根据 state_tracks_up 状态决定播放器是向上还是向下移动
                if (state_tracks_up)
                {
                    lv_anim_t screen_player_anim_y;
                    // 初始化动画,用于将音乐播放器向上移动
                    //Write animation: screen_playermove in y direction
                    lv_anim_init(&screen_player_anim_y);
                    lv_anim_set_var(&screen_player_anim_y, guider_ui.MusicScreen_player);
                    lv_anim_set_time(&screen_player_anim_y, 1000);
                    lv_anim_set_exec_cb(&screen_player_anim_y, (lv_anim_exec_xcb_t) lv_obj_set_y);
                    lv_anim_set_values(&screen_player_anim_y, -767, 0);
                    lv_anim_set_path_cb(&screen_player_anim_y, &lv_anim_path_linear);
                    lv_anim_start(&screen_player_anim_y);
                    state_tracks_up = false;
                }
                else
                {
                    lv_anim_t screen_player_anim_y;
                    // 初始化动画,用于将音乐播放器向下移动
                    //Write animation: screen_playermove in y direction
                    lv_anim_init(&screen_player_anim_y);
                    lv_anim_set_var(&screen_player_anim_y, guider_ui.MusicScreen_player);
                    lv_anim_set_time(&screen_player_anim_y, 1000);
                    lv_anim_set_exec_cb(&screen_player_anim_y, (lv_anim_exec_xcb_t) lv_obj_set_y);
                    lv_anim_set_values(&screen_player_anim_y, 0, -767);
                    lv_anim_set_path_cb(&screen_player_anim_y, &lv_anim_path_linear);
                    lv_anim_start(&screen_player_anim_y);
                    state_tracks_up = true;
                }
                break;
            }
            default:
                break;
        }
    }
};


/**音乐界面*/
class MusicScreen : public UIScreen
{
public:
    using UIScreen::UIScreen;

    void initAndBindEvents()
    {
        bindEvent(_ui->MusicScreen_imgbtn_SwitchScreens, MusicHandler::SwitchScreens);//切换界面
        bindEvent(_ui->MusicScreen_imgbtn_play, MusicHandler::PlayPause);             //播放暂停
        bindEvent(_ui->MusicScreen_img_icn_next, MusicHandler::Next);                 //下一曲
        bindEvent(_ui->MusicScreen_img_icn_prev, MusicHandler::Prev);                 //上一曲
    }
};

/****************************************************************************
 *
 * 主界面类集合
 *
 ****************************************************************************/

extern osThreadId_t controlDoor_taskId;
extern bool is_door_on ;//门一开始是关的,所以要显示开
extern bool is_step_motor_on;


class MainSubroutine
{
public:
    static void CollectAcceleration()
    {
        osThreadSuspend(TmpHumi_TaskHandleId);
        //改变量程0-255
        lv_chart_set_range(guider_ui.MainScreen_chart_humidity, LV_CHART_AXIS_PRIMARY_Y, -255, 255);
        lv_chart_set_range(guider_ui.MainScreen_chart_temperature, LV_CHART_AXIS_PRIMARY_Y, -255, 255);
        //换成图标数据,采集温度的任务暂停,开始采集加速度
        lv_label_set_text(guider_ui.MainScreen_label_temperature, "X");
        lv_label_set_text(guider_ui.MainScreen_label_humidity, "Y");
        lv_chart_set_axis_tick(guider_ui.MainScreen_chart_humidity, LV_CHART_AXIS_PRIMARY_Y, 10, 5, 6, 5, true, 40);

        for (uint8_t i = 0; i < 20; i++)
        {
            lv_chart_set_next_value(guider_ui.MainScreen_chart_humidity, guider_ui.MainScreen_chart_humidity_0, 0);
            lv_chart_set_next_value(guider_ui.MainScreen_chart_temperature, guider_ui.MainScreen_chart_temperature_0, 0);
        }

        osThreadResume(ACDetectionTaskHandle);
    }
    static void CollectTemperature()
    {
        osThreadSuspend(ACDetectionTaskHandle);
        lv_chart_set_range(guider_ui.MainScreen_chart_temperature, LV_CHART_AXIS_PRIMARY_Y, 0, 50);
        lv_chart_set_range(guider_ui.MainScreen_chart_humidity, LV_CHART_AXIS_PRIMARY_Y, 0, 100);
        lv_label_set_text(guider_ui.MainScreen_label_temperature, "温度");
        lv_label_set_text(guider_ui.MainScreen_label_humidity, "湿度");
        lv_chart_set_axis_tick(guider_ui.MainScreen_chart_humidity, LV_CHART_AXIS_PRIMARY_Y, 10, 5, 5, 4, true, 40);
        for (uint8_t i = 0; i < 20; i++)
        {
            lv_chart_set_next_value(guider_ui.MainScreen_chart_humidity, guider_ui.MainScreen_chart_humidity_0, 0);
            lv_chart_set_next_value(guider_ui.MainScreen_chart_temperature, guider_ui.MainScreen_chart_temperature_0, 0);
        }
        osThreadResume(TmpHumi_TaskHandleId);
    }

    static void ControlDoorState()
    {
        //做出改变
        if (!is_door_on)
        {
            lv_label_set_text(guider_ui.MainScreen_label_door, "开门");
            lv_imgbtn_set_state(guider_ui.MainScreen_imgbtn_door, LV_IMGBTN_STATE_RELEASED);
        }
        else
        {
            lv_label_set_text(guider_ui.MainScreen_label_door, "关门");
            lv_imgbtn_set_state(guider_ui.MainScreen_imgbtn_door, LV_IMGBTN_STATE_CHECKED_RELEASED);
        }

        is_door_on = !is_door_on;
        // 根据门的状态切换图片资源或状态
        // 启动步进电机逻辑
        is_step_motor_on = true;
        osThreadResume(controlDoor_taskId);
    }
};


/**
 * @brief 门的控制
 */

void controlDoor_task(void *argument);
extern bool is_switch_on;
extern bool is_voice_recognize_on;
class MainHandler
{
private:

public:


public:
    MainHandler()
    {
        is_voice_recognize_on = false;
        is_switch_on = false;
    }

    //切换界面
    static void SwitchScreens(lv_event_t *e)
    {
        switch (lv_event_get_code(e))
        {
            case LV_EVENT_CLICKED:
            {
                osThreadSuspend(TmpHumi_TaskHandleId);
                ui_load_scr_animation(&guider_ui, &guider_ui.MusicScreen, guider_ui.MusicScreen_del, &guider_ui.MainScreen_del, setup_scr_MusicScreen, LV_SCR_LOAD_ANIM_MOVE_LEFT, 800, 60, true, false);
            }
            default:
                break;
        }
    }

    static void Voice_Recognize(lv_event_t *e)
    {
        switch (lv_event_get_code(e))
        {
            case LV_EVENT_CLICKED:
                is_voice_recognize_on = !is_voice_recognize_on;
                if (is_voice_recognize_on)
                {
                    LD3320_USART3_ReceiveByte_Start_IT(LD_code);
                }
                else
                {
                    LD3320_USART3_ReceiveByte_Stop_IT();
                }

                break;
            default:
                break;
        }
    }


    /**
 * @brief 灯的控制
 */

    static void Switch_LED(lv_event_t *e)
    {
        lv_event_code_t code = lv_event_get_code(e);
        switch (code)
        {
            case LV_EVENT_CLICKED:
                if (!is_switch_on)
                {
                    LED_All_ON();
                    lv_imgbtn_set_state(guider_ui.MainScreen_imgbtn_switchLED, LV_IMGBTN_STATE_CHECKED_RELEASED);
                }
                else
                {
                    LED_All_OFF();
                    lv_imgbtn_set_state(guider_ui.MainScreen_imgbtn_switchLED, LV_IMGBTN_STATE_RELEASED);
                }
                is_switch_on = !is_switch_on;//放在后面是确保语音识别时,可以直接通过设置变量来改变开关状态
                break;
            default:
                break;
        }
    }


    static void DoorControl(lv_event_t *e)
    {
        if (lv_event_get_code(e) == LV_EVENT_CLICKED)
        {
            //显示开门,则表示按下开关后应该关门,所以要反着判断
            if (!is_step_motor_on)
            {
               MainSubroutine::ControlDoorState();
            }
            else
            {
                if (is_door_on)
                {
                    lv_label_set_text(guider_ui.MainScreen_label_door, "开门");
                    lv_imgbtn_set_state(guider_ui.MainScreen_imgbtn_door, LV_IMGBTN_STATE_RELEASED);
                }
                else
                {
                    lv_label_set_text(guider_ui.MainScreen_label_door, "关门");
                    lv_imgbtn_set_state(guider_ui.MainScreen_imgbtn_door, LV_IMGBTN_STATE_CHECKED_RELEASED);
                }
            }

            // 当步进电机开启时,不执行任何图片或状态切换
        }
    }


    //加速度检测
    static void AccelerationDetection(lv_event_t *e)
    {
        switch (lv_event_get_code(e))
        {
            case LV_EVENT_CLICKED:
                is_ac_on = !is_ac_on;
                if (is_ac_on)
                {
                    MainSubroutine::CollectAcceleration();
                }
                else
                {
                    MainSubroutine::CollectTemperature();
                }
                break;
            default:
                break;
        }
    }

    //震动检测
    static void VibrationDetection(lv_event_t *e)
    {
        switch (lv_event_get_code(e))
        {
            case LV_EVENT_CLICKED:
                is_checkForShock_on = !is_checkForShock_on;
                if (is_checkForShock_on)
                {
                    osThreadResume(ACDetectionTaskHandle);
                }
                else
                {
                    //停止检测
                    if (!is_ac_on)
                        osThreadSuspend(ACDetectionTaskHandle);
                }
                break;

            default:
                break;
        }
    }
};

class MainScreen : public UIScreen
{
public:
    using UIScreen::UIScreen;

    void initAndBindEvents()
    {
        bindEvent(_ui->MainScreen_imgbtn_SwitchScreens, MainHandler::SwitchScreens);    //切换界面
        bindEvent(_ui->MainScreen_imgbtn_voice_recognize, MainHandler::Voice_Recognize);//语音识别
        bindEvent(_ui->MainScreen_imgbtn_door, MainHandler::DoorControl);               //门控
        bindEvent(_ui->MainScreen_imgbtn_acc, MainHandler::AccelerationDetection);      //加速度检测
        bindEvent(_ui->MainScreen_imgbtn_vibration, MainHandler::VibrationDetection);   //震动检测
        bindEvent(_ui->MainScreen_imgbtn_switchLED, MainHandler::Switch_LED);           //灯控
    }
};

#endif//MYEVENT_SUBROUTINE_H

四、面向AI的编程思想        ✨

        这个思想可能以前也有人提过,不过确实是我对昨晚的总结。下面先讲应用场景

【应用场景】

1,报错提示看不懂也查不到解决方法

2,代码不知道如何优化

3,一些指令、参数等看不懂,资料手册也不好查

4,对某一功能实现没有什么头绪

5,代码逻辑纠错

…… ……

        虽然应用场景看着挺让人期待的,但其实以当前的AI并不能完全胜任,当然能胜任的话咱也危了。目前AI最大的优点不是逻辑思考能力(事实上非常拉胯),其最大的依仗是它那庞大的资料库,以及能和你进行一定程度上互动的能力。而这,就足够了

1,纠错

        举个实例,我遇到的问题是打印颠倒,但查了很多资料却没有查到相似的案例 。于是乎呢,在漫长的训练AI过程和代码摸索中,终于大致明白了打印的大致过程,同时也找到了问题的突破点,即LCD_Init()

下面是先前的LCD_Init(),注释稀稀疏疏,难以让人看懂

void LCD_Init9488(void)
{
    TFT_RST = 1;
    delay_100ms();
    TFT_RST = 0;
    delay_100ms();
    TFT_RST = 1;
    delay_100ms();
    LCD_WRITE_CMD(0x11);
    delay_100ms();

    LCD_WRITE_CMD(0xd0);
    LCD_WRITE_DATA(0x07);
    LCD_WRITE_DATA(0x47); // 45 47 47
    LCD_WRITE_DATA(0x19); // 1c 19 1b
    LCD_WRITE_CMD(0xd1);
    LCD_WRITE_DATA(0x00);
    LCD_WRITE_DATA(0x36); // 2a/2F  36/3B  3a/3C
    LCD_WRITE_DATA(0x1f); // 1a       1f     1f
    LCD_WRITE_CMD(0xd2);
    LCD_WRITE_DATA(0x01);
    LCD_WRITE_DATA(0x11);
    LCD_WRITE_CMD(0xE4);
    LCD_WRITE_DATA(0xa0);
    LCD_WRITE_CMD(0xf3);
    LCD_WRITE_DATA(0x00);
    LCD_WRITE_DATA(0x2a);
    LCD_WRITE_CMD(0xc0);
    LCD_WRITE_DATA(0x10);
    LCD_WRITE_DATA(0x3b);
    LCD_WRITE_DATA(0x00);
    LCD_WRITE_DATA(0x02);
    LCD_WRITE_DATA(0x11);
    LCD_WRITE_CMD(0xc5);
    LCD_WRITE_DATA(0x03);
    LCD_WRITE_CMD(0xc8);
    LCD_WRITE_DATA(0x00);
    LCD_WRITE_DATA(0x35);
    LCD_WRITE_DATA(0x23);
    LCD_WRITE_DATA(0x07);
    LCD_WRITE_DATA(0x00);
    LCD_WRITE_DATA(0x04);
    LCD_WRITE_DATA(0x45);
    LCD_WRITE_DATA(0x53);
    LCD_WRITE_DATA(0x77);
    LCD_WRITE_DATA(0x70);
    LCD_WRITE_DATA(0x00);
    LCD_WRITE_DATA(0x04);
    //*************GAMMA SETTING ***************
 /**************************设置LCD显示区域****************************/
    LCD_WRITE_CMD(0x0036); // 显示行列设置
    LCD_WRITE_CMD(0x0036);  // 显示行列设置
    LCD_WRITE_DATA(0x00A9); // ILI9488
    LCD_WRITE_CMD(0x003a);
    LCD_WRITE_DATA(0x0055);
    LCD_WRITE_CMD(0x0020);
    LCD_WRITE_CMD(0x2a);
    LCD_WRITE_DATA(0x00);
    LCD_WRITE_DATA(0x00);
    LCD_WRITE_DATA(0x01);
    LCD_WRITE_DATA(0xDF);

    LCD_WRITE_CMD(0x2b);
    LCD_WRITE_DATA(0x00);
    LCD_WRITE_DATA(0x00);
    LCD_WRITE_DATA(0x01);
    LCD_WRITE_DATA(0x3F);

    delay_100ms();
    LCD_WRITE_CMD(0x29);
    LCD_WRITE_CMD(0x2c);
    delay_100ms();
    LCD_Clear1(BLACK);
}

AI解释每条指令并在原代码的基础上添加上注释,于是变成了下面这样,这样就显而易见了

void LCD_Init9488(void)
{

    // 复位TFT显示屏
    TFT_RST = 1;  // 将TFT复位引脚设为高电平
    delay_100ms();// 等待100毫秒
    TFT_RST = 0;  // 将TFT复位引脚设为低电平
    delay_100ms();// 等待100毫秒
    TFT_RST = 1;  // 将TFT复位引脚设为高电平,完成复位操作
    delay_100ms();// 等待100毫秒

    // 向LCD发送命令退出睡眠模式
    LCD_WRITE_CMD(0x11);// 退出睡眠模式
    delay_100ms();      // 等待100毫秒,确保命令执行完毕

    // 设置LCD电源控制
    LCD_WRITE_CMD(0xd0); // 电源控制命令
    LCD_WRITE_DATA(0x07);// 设置参数
    LCD_WRITE_DATA(0x47);// 设置参数
    LCD_WRITE_DATA(0x19);// 设置参数

    LCD_WRITE_CMD(0xd1); // 电源控制命令
    LCD_WRITE_DATA(0x00);// 设置参数
    LCD_WRITE_DATA(0x36);// 设置参数
    LCD_WRITE_DATA(0x1f);// 设置参数

    LCD_WRITE_CMD(0xd2); // 电源控制命令
    LCD_WRITE_DATA(0x01);// 设置参数
    LCD_WRITE_DATA(0x11);// 设置参数

    // 其他LCD驱动相关设置
    LCD_WRITE_CMD(0xE4); // 驱动模式设置
    LCD_WRITE_DATA(0xa0);// 设置参数

    LCD_WRITE_CMD(0xf3); // 帧速率控制
    LCD_WRITE_DATA(0x00);// 设置参数
    LCD_WRITE_DATA(0x2a);// 设置参数

    LCD_WRITE_CMD(0xc0); // MV偏压控制
    LCD_WRITE_DATA(0x10);// 设置参数
    LCD_WRITE_DATA(0x3b);// 设置参数
    LCD_WRITE_DATA(0x00);// 设置参数
    LCD_WRITE_DATA(0x02);// 设置参数
    LCD_WRITE_DATA(0x11);// 设置参数

    LCD_WRITE_CMD(0xc5); // VCOM控制
    LCD_WRITE_DATA(0x03);// 设置参数

    LCD_WRITE_CMD(0xc8); // Gamma设置
    LCD_WRITE_DATA(0x00);// 设置Gamma参数
    LCD_WRITE_DATA(0x35);// 设置Gamma参数
    LCD_WRITE_DATA(0x23);// 设置Gamma参数
    LCD_WRITE_DATA(0x07);// 设置Gamma参数
    LCD_WRITE_DATA(0x00);// 设置Gamma参数
    LCD_WRITE_DATA(0x04);// 设置Gamma参数
    LCD_WRITE_DATA(0x45);// 设置Gamma参数
    LCD_WRITE_DATA(0x53);// 设置Gamma参数
    LCD_WRITE_DATA(0x77);// 设置Gamma参数
    LCD_WRITE_DATA(0x70);// 设置Gamma参数
    LCD_WRITE_DATA(0x00);// 设置Gamma参数
    LCD_WRITE_DATA(0x04);// 设置Gamma参数

    /**************************设置LCD显示区域****************************/
    LCD_WRITE_CMD(0x0036); // 显示行列设置
    LCD_WRITE_DATA(0x00A9);// 设置参数

    LCD_WRITE_CMD(0x003a); // RGB信号格式设置
    LCD_WRITE_DATA(0x0055);// 设置参数

    LCD_WRITE_CMD(0x0020);// RAM写入控制

    LCD_WRITE_CMD(0x2a); // 水平地址设置
    LCD_WRITE_DATA(0x00);// 设置参数
    LCD_WRITE_DATA(0x00);// 设置参数
    LCD_WRITE_DATA(0x01);// 设置参数
    LCD_WRITE_DATA(0xDF);// 设置参数

    LCD_WRITE_CMD(0x2b); // 垂直地址设置
    LCD_WRITE_DATA(0x00);// 设置参数
    LCD_WRITE_DATA(0x00);// 设置参数
    LCD_WRITE_DATA(0x01);// 设置参数
    LCD_WRITE_DATA(0x3F);// 设置参数

    delay_100ms();      // 等待100毫秒
    LCD_WRITE_CMD(0x29);// 唤醒命令
    LCD_WRITE_CMD(0x2c);// 写入RAM命令
    delay_100ms();      // 等待100毫秒

    LCD_Clear1(BLACK);  // 清除屏幕,设置为白色
}

        但这还远远不够,因为我真正要解决的问题是,打印颠倒。虽然我对这些指令不熟,但注释已经帮了我第一个忙,那就是定位问题的突破点,很显然应该要修改显示行列设置

然而哪怕它联网也没找到,所幸前些天我正好下载了ILI9488的芯片手册,直接搜索指令36,稍微找找就定位到了指令所在的目录

由于英语对嵌入式很重要,于是我把资料发给AI了(图片直接复制到输入框)

        在试过之后,你可能会发现AI给的代码,让人一言难尽,就是它往往没有什么编译错误,却会有一些很明显的逻辑错误。不过它能到这一步就完全足够了,剩下的就需要靠我们自己了

void LCD_Init9488(void)
{

    // 复位TFT显示屏
    TFT_RST = 1;  // 将TFT复位引脚设为高电平
    delay_100ms();// 等待100毫秒
    TFT_RST = 0;  // 将TFT复位引脚设为低电平
    delay_100ms();// 等待100毫秒
    TFT_RST = 1;  // 将TFT复位引脚设为高电平,完成复位操作
    delay_100ms();// 等待100毫秒

    // 向LCD发送命令退出睡眠模式
    LCD_WRITE_CMD(0x11);// 退出睡眠模式
    delay_100ms();      // 等待100毫秒,确保命令执行完毕

    // 设置LCD电源控制
    LCD_WRITE_CMD(0xd0); // 电源控制命令
    LCD_WRITE_DATA(0x07);// 设置参数
    LCD_WRITE_DATA(0x47);// 设置参数
    LCD_WRITE_DATA(0x19);// 设置参数

    LCD_WRITE_CMD(0xd1); // 电源控制命令
    LCD_WRITE_DATA(0x00);// 设置参数
    LCD_WRITE_DATA(0x36);// 设置参数
    LCD_WRITE_DATA(0x1f);// 设置参数

    LCD_WRITE_CMD(0xd2); // 电源控制命令
    LCD_WRITE_DATA(0x01);// 设置参数
    LCD_WRITE_DATA(0x11);// 设置参数

    // 其他LCD驱动相关设置
    LCD_WRITE_CMD(0xE4); // 驱动模式设置
    LCD_WRITE_DATA(0xa0);// 设置参数

    LCD_WRITE_CMD(0xf3); // 帧速率控制
    LCD_WRITE_DATA(0x00);// 设置参数
    LCD_WRITE_DATA(0x2a);// 设置参数

    LCD_WRITE_CMD(0xc0); // MV偏压控制
    LCD_WRITE_DATA(0x10);// 设置参数
    LCD_WRITE_DATA(0x3b);// 设置参数
    LCD_WRITE_DATA(0x00);// 设置参数
    LCD_WRITE_DATA(0x02);// 设置参数
    LCD_WRITE_DATA(0x11);// 设置参数

    LCD_WRITE_CMD(0xc5); // VCOM控制
    LCD_WRITE_DATA(0x03);// 设置参数

    LCD_WRITE_CMD(0xc8); // Gamma设置
    LCD_WRITE_DATA(0x00);// 设置Gamma参数
    LCD_WRITE_DATA(0x35);// 设置Gamma参数
    LCD_WRITE_DATA(0x23);// 设置Gamma参数
    LCD_WRITE_DATA(0x07);// 设置Gamma参数
    LCD_WRITE_DATA(0x00);// 设置Gamma参数
    LCD_WRITE_DATA(0x04);// 设置Gamma参数
    LCD_WRITE_DATA(0x45);// 设置Gamma参数
    LCD_WRITE_DATA(0x53);// 设置Gamma参数
    LCD_WRITE_DATA(0x77);// 设置Gamma参数
    LCD_WRITE_DATA(0x70);// 设置Gamma参数
    LCD_WRITE_DATA(0x00);// 设置Gamma参数
    LCD_WRITE_DATA(0x04);// 设置Gamma参数

    /**************************设置LCD显示区域****************************/
    LCD_WRITE_CMD(0x0036); // 显示行列设置
    LCD_WRITE_DATA(0x00E9);// 设置参数1110 1001
    /*前两位为调整方向*/
    //默认方向为A9,左下角与右上角
    //  29为右下角与左上角
    //E9为左上角,右下角


    LCD_WRITE_CMD(0x003a); // RGB信号格式设置
    LCD_WRITE_DATA(0x0055);// 设置参数

    LCD_WRITE_CMD(0x0020);// RAM写入控制

    LCD_WRITE_CMD(0x2a); // 水平地址设置
    LCD_WRITE_DATA(0x00);// 设置参数
    LCD_WRITE_DATA(0x00);// 设置参数
    LCD_WRITE_DATA(0x01);// 设置参数
    LCD_WRITE_DATA(0xDF);// 设置参数

    LCD_WRITE_CMD(0x2b); // 垂直地址设置
    LCD_WRITE_DATA(0x00);// 设置参数
    LCD_WRITE_DATA(0x00);// 设置参数
    LCD_WRITE_DATA(0x01);// 设置参数
    LCD_WRITE_DATA(0x3F);// 设置参数

    delay_100ms();      // 等待100毫秒
    LCD_WRITE_CMD(0x29);// 唤醒命令
    LCD_WRITE_CMD(0x2c);// 写入RAM命令
    delay_100ms();      // 等待100毫秒
    TFTLED = 0x01;      // 背光寄存器初始化
    LCD_Clear1(WHITE);  // 清除屏幕,设置为白色
}

总结一下:AI可以帮助你快速定位错误,因为有些奇葩错误网上极有可能是找不到的(外网可能有)。至于剩下的当然要靠我们自己了

不过最后还是发现了,是GUI Guider里面的一个选项没有设置,在上面有关GUI Guider的目录中提到过。

2,灵感与设计思路(未完)

        举个例子,如果此时此刻学习一个项目需要用到RTOSLVGL,但同时你又对RTOS和LVGL不怎么熟悉。那么可以把你的思路说给AI,或者干脆把需求给AI,让其为你提供一个可能性方案。即便方案并不能符合期待,也多少可借此获得灵感,厘清思路。

   

  • 20
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值