彩屏LVGL UI设计及工具使用
📎 原始文档:https://www.kdocs.cn/l/cu689yidv5lJ 彩屏LVGL UI设计及工具使用
💡资料链接:
- cmake(用于makefile的构建,目前使用的是3.26版本,建议安装在c盘)
- mingw(cmake需要依赖于mingw,目前使用的是8.10版本,建议安装在c盘) 👉cmake&mingw安装教程.otl👈
- LVGL设计辅助(提供可视化图形界面设计):
- LVGL参考文档:
- https://docs.lvgl.io/8.3/index.html
- http://lvgl.100ask.net/7.11/
- https://lvgl.100ask.net/8.2/index.html
- https://deepinout.com/lvgl-tutorials/lvgl-getting-started/lvgl-intro.html
- LVGL源码中docs文件夹,可见于SDK的code\sdk\lv_charging_case\lv_frame\lvgl\docs路径
- LVGL工具 图片转换工具:
- vscode(用于代码编辑与编译)
📌简介:
彩屏SDK已移植官方LVGL,当前使用的LVGL版本是8.3.5,并且接入杰理imb 2D加速模块,用户可以参考相关说明文档(链接可见于文档顶部),使用LVGL进行UI设计开发,也可以使用上位机进行辅助开发,但需要注意上位机提及的相关条款。需要注意的是,LVGL使用到的资源文件格式与imb能识别的格式有所差别,因此SDK中提供资源打包工具,用于图片资源的格式转换与压缩。
⭐整体开发流程:
- 移植屏驱:先让屏幕亮起来,保证屏幕能正常显示
- 移植tp驱动:确保tp报点正常,LVGL的触摸能正常触发
- LVGL应用层逻辑开发:构建UI的布局,添加UI的事件处理
- 蓝牙通讯调试:UI交互与具体功能的结合
🤖UI开发流程:

- SDK重要文件说明
- LVGL相关文件路径说明

与LVGL相关模块全部放置于lv_charging_case路径下,其中:
GPU2D:LVGL接入imb 2D加速模块
lv_frame:LVGL源码、UI设计的应用层文件等
adapter:中间适配层,用于存放蓝牙、UI线程间交互的接口
custom:用于存放用户UI设计自定义代码
generated:用于存放用户UI设计逻辑相关代码
UI:用于存放用户UI设计逻辑相关代码
import:用于存放LVGL中使用到的图片资源
❗(以上4个路径设置仅作参考,不做硬性要求,用户可以根据自身需求来修改目录结构)
lvgl:用于存放LVGL源码
jlres:用于存放资源索引文件
porting:用于存放LVGL驱动层文件,包括显示,输入设备和文件系统,一般改动较少
res-pack-tools:资源打包工具
工具脚本:
makefile_clear.bat:用于删除过程文件
makefile_maker.bat:用于makefile文件的构建(该脚本依赖于cmake,其中cmake下载链接可见于文档顶部)
- SDK代码编译 ├─sdk │
├─lv_charging_case │ │
│ ├─GPU2D │ │ gpu2d.mk │ │
│ ├─lv_frame │ │ ├─custom │ │ │ custom.mk │ │ │
│ │ ├─generated │ │ │ generated.mk │ │ ├─jlres │ │ │ jlres.mk │ │ ├─lvgl │ │ │ lvgl.mk │ │ └─ui │ │ ui.mk │ └─porting │ lv_port.mk │
└─Makefile
如上图所示,整个SDK工程通过Makefile进行管理(建议使用vscode进行编译),子目录下采用单个.mk文件进行管理,其中子目录下的.mk文件将当前路径下的所有.c文件的路径添加进编译路径,并添加查找头文件路径,最终汇总到sdk路径下的Makefile里面,此时运行Makefile即可编译到所有代码。

在sdk路径下空白处右键,点击“Open with Code”,并点击菜单栏-终端-运行生成任务或者快捷键Ctrl+Shift+B即可进行编译。
*注意:至于 全编译 选项,vscode里面并没有提供,那么更换了.a文件以后怎样才能进行全编译,把新的.a文件编译进去呢?其实只需要在一个确保可以编译进去的文件里的空行处轻轻敲个回车就行,比如在app_main.c文件的末尾敲个回车,目的是为了让代码发生变化,只需要代码发生变化就会将新的.a文件重新编译,当然也不一定是要在空行处敲回车,只需要保证代码发生变化即可,在这里以回车为例是因为这样的操作不影响到原本代码的逻辑。
- 屏驱模块说明
2.1 推屏流程简介
SDK中已经集成屏幕驱动模块,用户只需要按照规则去使用即可。
整体推屏流程如上图所示:
用户需要提供屏幕的初始化命令,将相关设置配置到推屏接口,并完成lcd设备的注册,为了做好屏驱管理,用户可以添加相关的宏,去管理自己的屏驱驱动,完成lcd设备注册后,lcd驱动模块会获取到对应的屏驱信息,包括初始化命令,屏幕分辨率等,LVGL的显示驱动也会从lcd驱动模块中获取到对应信息,并调用杰理lcd推屏接口进行推屏。
2.2 屏驱重要参数说明
上图中列举了部分屏幕参数,其中:
LCD_DRIVE_CONFIG:推屏时序选择,如QSPI_RGB565_SUBMODE2_1T2B对应的是QSPI时序,数据结构为RGB565,数据4线等,具体可以参考SDK中的doc\固件资料\UI\701N屏幕驱动配置说明.pdf以及imd.h里面的注释说明。
SCR_X、SCR_Y:屏幕x轴和y轴偏移,不需要偏移的话默认填0即可。
SCR_W、SCR_H、LCD_W和LCD_H:屏驱宽高,一般填屏幕分辨率即可。
LCD_BLOCK_W:分块buf的宽度,一般是按行分块,宽度设置为跟LCD_W一致即可。
LCD_BLOCK_H:分块buf高度,可以根据需求修改。
BUF_NUM:buf数量默认设置2,一般不需要修改。
LCD_FORMAT:推屏数据格式,默认设置RGB565,一般不需要修改。
以上提及的数据,一般只需要修改SCR_W、SCR_H、LCD_W和LCD_H和LCD_BLOCK_W即可。
屏幕初始化命令如上图所示,用户添加新的屏驱时需要按照一定规则去修改屏驱初始化命令,其中_BEGIN_和_END_分别为开始标志位和结束标志位,这两个标志位跟实际上推屏数据无关,只是作为杰理lcd模块用于控制数据发送的信号。BEGIN_后的第一个参数是命令,后续的则是数据,如_BEGIN, 0xc0, 0x5a, 0x5a, _END_中,命令是0xc0,数据是0x5a, 0x5a。可以理解为,以上命令是屏厂驱动的SPI_WriteComm(0xc0);SPI_WriteData(0x5a);SPI_WriteData(0x5a);转换而来。


fps:fps为设置的帧率,默认为60,底层会根据fps算出合适的时钟速率,如果想要了解fps和时钟速率的一个对应关系,可以将app_config.c里面的log_tag_const_d_UI AT(.LOG_TAG_CONST)设置为true,在打印中搜索fps相关打印即可找到两者直接的对应关系。
debug_mode_en:debug模式开关,置为true后不会推合成后的数据,而是根据debug_mode_color设定的颜色值去显示,一般用于推屏数据的排查。
debug_mode_color:debug模式时显示的颜色值。
注册lcd设备,在这个步骤中,会将屏驱相关信息注册进去,用户可以根据需求修改logo以及名称。
2.3 屏驱移植
用户可以在板级文件(board_701n_demo.cfg)中,看到目前支持的屏驱,也可以自行添加新的屏驱,接下来我们以添加NB3030为例去了解如何新增屏驱:
2.3.1 添加屏幕驱动的宏

#define TCFG_LCD_NB3030_ENABLE ENABLE_THIS_MOUDLE
如上图,添加TCFG_LCD_NB3030_ENABLE的宏,并开启该宏。
2.3.2 添加新的屏驱文件
在code/sdk/cpu/br28/ui_driver/lcd_drive/lcd_spi路径下,拷贝一份屏驱,如将lcd_spi_sh_8601a_454x454.c拷贝一份,并重新命名为oled_spi.nb3030_172x320.c。
2.3.3 在Makefile中添加屏驱

cpu/br28/ui_driver/lcd_drive/lcd_spi/oled_spi_nb3030_172x320.c \
在makefile中添加oled_spi_nb3030_172x320.c路径,才会将该文件编译。
2.3.4 修改相关参数
参考2.2章节去修改屏幕分辨率,初始化命令,时序等相关参数即可。
2.3.5 修改屏幕相关引脚
需要板级.c文件(当前使用的板级是board_701n_demo.c)里的引脚配置与原理图的一致。
到这一步为止,屏驱添加流程就结束了。
- TP驱动移植 TP驱动的宏管理与屏驱的类似,在板级文件中添加相关的宏进行管理:
一般TP厂会提供驱动,只需要把iic接口换上,并且添加三个参数即可,需要添加的三个参数如下:

volatile u8 touch_down = 1; // 按下标志位
volatile int touch_x = 0; // x轴报点
volatile int touch_y = 0; // y轴报点
这三个参数是用于传递触摸事件信息给LVGL,只需要在TP驱动对应的touch_event_handler中添加这三个值的获取即可,LVGL的输入设备中会轮询读取这三个值,获取相应触摸事件。上图中touch_y = VK_Y - mytouch_data.point.y - 1;是对TP进行了镜像处理,若不需要镜像,修改为touch_y = mytouch_data.point.y;即可。
调试时,可以打开LVGL鼠标进行辅助:
在lv_rtos_port.c中,将lv_obj_add_flag(cursor_obj, LV_OBJ_FLAG_HIDDEN);注释掉,取消默认隐藏,即可显示鼠标,且跟随手拖动。其中lv_img_set_src(cursor_obj, &ID_ic_xia); 可以改为任意图片,只需要确保打包的资源里面存在对应图片即可,如将cursor.png添加打包后,可以通过lv_img_set_src(cursor_obj, "F:/cursor.bin");的方式进行加载,至于维护需要通过这种方式加载,可以参考下文。
4. LVGL UI设计
4.1 LVGL UI文件说明
以下提及的UI文件仅作参考,不需要完全按照所提及的形式去进行开发,用户可以根据自己的编程习惯以及工程管理习惯去进行开发。
SDK中的LVGL例程文件主要集中在generated路径下:
其中gui_guider.c里面主要放置ui的程序入口函数setup_ui(),这个函数的具体的作用是加载对应页面,并且对该页面的控件进行初始化设置,其设置风格如下:
LVGL中对控件的设置如上所示,LVGL提供了相当丰富的接口,供用户去修改控件的各个属性。
而events_init.c则是进行对应事件进行配置,可以根据以下例子进行了解:
回调函数添加事件处理,该回调事件是在图片控件按下时会执行,比如想要在某个图片控件按下时执行切换到上一首歌曲的操作,只需要在这里添加切换上一首歌曲的逻辑即可,这样就可以完成了UI交互层和功能逻辑层的合并。
最终会在上图所在添加事件回调,添加后等对应事件触发,就会执行相应回调。
4.2 LVGL常用接口
4.2.1 添加/清除标志
/**
* Set one or more flags
* @param obj pointer to an object
* @param f R-ed values from `lv_obj_flag_t` to set.
*/
void lv_obj_add_flag(lv_obj_t * obj, lv_obj_flag_t f);
/**
* Clear one or more flags
* @param obj pointer to an object
* @param f OR-ed values from `lv_obj_flag_t` to set.
*/
void lv_obj_clear_flag(lv_obj_t * obj, lv_obj_flag_t f);
/* example1:隐藏控件 */
lv_obj_add_flag(obj, LV_OBJ_FLAG_HIDDEN);
/* example2:显示控件 */
lv_obj_clear_flag(obj, LV_OBJ_FLAG_HIDDEN);
4.2.2 添加/清除状态
/**
* Add one or more states to the object. The other state bits will remain unchanged.
* If specified in the styles, transition animation will be started from the previous state to the current.
* @param obj pointer to an object
* @param state the states to add. E.g `LV_STATE_PRESSED | LV_STATE_FOCUSED`
*/
void lv_obj_add_state(lv_obj_t * obj, lv_state_t state);
/**
* Remove one or more states to the object. The other state bits will remain unchanged.
* If specified in the styles, transition animation will be started from the previous state to the current.
* @param obj pointer to an object
* @param state the states to add. E.g `LV_STATE_PRESSED | LV_STATE_FOCUSED`
*/
void lv_obj_clear_state(lv_obj_t * obj, lv_state_t state);
/* example1:触发选中事件 */
lv_obj_add_state(obj, LV_STATE_CHECKED);
/* example2:清除选中事件 */
lv_obj_clear_state(obj, LV_STATE_CHECKED);
4.2.3 图片显示
/**
* Set the image data to display on the object
* @param obj pointer to an image object
* @param src_img 1) pointer to an ::lv_img_dsc_t descriptor (converted by LVGL's image converter) (e.g. &my_img) or
* 2) path to an image file (e.g. "S:/dir/img.bin")or
* 3) a SYMBOL (e.g. LV_SYMBOL_OK)
*/
void lv_img_set_src(lv_obj_t * obj, const void * src);
/* example1:将bg图片设置到img图片控件上进行显示 */
/* 这种方式对应以上说明的第一种,通过将图片转换为c数组的方式,去加载c数组进行显示 */
lv_img_set_src(img, &_bg_40x20);
/* example2:将bg.bin文件设置到img图片控件上进行显示 */
/* 这种方式对应以上说明的第二种,通过文件路径的形式去进行显示 */
/* 推荐使用第二种,结合资源打包工具使用流程较为简单,无需手动转换 */
lv_img_set_src(img, "F:/bg.bin");
4.2.4 文字显示
/**
* Set a new formatted text for a label. Memory will be allocated to store the text by the label.
* @param obj pointer to a label object
* @param fmt `printf`-like format
* @example lv_label_set_text_fmt(label1, "%d user", user_num);
*/
void lv_label_set_text_fmt(lv_obj_t * obj, const char * fmt, ...) LV_FORMAT_ATTRIBUTE(2, 3);
/* example1:label控件格式化输出显示 */
static const char *string = “hello”;
lv_label_set_text_fmt(label, "%s", string);
以上为一些最基础的接口,更详细的接口说明可见于文档顶部中的LVGL参考链接。
- 工具使用说明 与资源相关的工具全部放置在code\sdk\lv_charging_case\res-pack-tools路径下:

与makefile相关工具则放置在code\sdk\lv_charging_case路径下:
- 资源转换工具说明
👀提问:为什么需要进行资源转换?
答:为了进一步适配imb 2D加速模块
LVGL的资源访问方式主要有两种,就是以上提到的通过c数组的方式和通过bin文件路径的这两种方式,但这两种方式均是没有进行压缩,最终生成的资源文件空间较大,另一方面是部分图片格式数据结构与imb能识别的格式有所出入,故需要利用杰理资源转换工具进行生成,打包,以便于适配2D加速模块。
上图为资源访问的示意图,从LVGL应用层角度来说,跟LVGL访问资源的常规方式并无差别,只是底层针对性地做了适配,采用路径方式访问可以更好地获得加速效果,但也保留了原本的通过数组访问的方式。
另外杰理资源打包工具,会将图片资源进行压缩,进一步减少资源空间占用。 在此,建议用户使用路径的方式去访问图片,因为这种方式转换成本最低,SDK中提供转换工具进行转换使用,如图片名为bg.png,当需要把这张图片加载到图片控件上时,则建议用户“lv_img_set_src(img, "F:/bg.bin");”以路径的方式去使用,最后再采用资源打包工具将路径与id关联起来即可。
以上便是资源打包工具的作用。
❗资源打包工具使用说明
资源打包工具的目录是code\sdk\lv_charging_case\res-pack-tools\imb-rle-pack
工具以 可执行程序 的形式呈现,双击run.exe即可运行,无需依赖其他环境。
│ copy.bat │ run.exe │ run.py │
├─imbtools │ │ Clear.bat │ │ ResBuilder.exe │ │ ResBuilder.xml │ │
│ ├─images │ └─output │ ui.str │ ui_str_index.h │
└─output ui.res ui_pic_index.h usr_pic_index.c usr_pic_index.h
工具目录结构如上,其中重点的有几个部分:
images文件夹:存放图片原文件,可以理解为是工具的输入,默认支持.png和.bmp格式。
output文件夹:工具转换结束后的输出文件夹。
ui.res:打包压缩后的资源文件。
usr_pic_index.c:打包压缩后资源的路径id映射文件,底层会根据该路径转换为id访问资源。
usr_pic_index.h:打包压缩后资源访问id,可以直接通过id的方式去对资源进行访问。
使用步骤非常简单,将图片资源放到images文件夹,双击运行run.exe后进行资源打包,打包结束后,工具会自动调用copy.bat,将资源自动拷贝到sdk\cpu\br28\tools\ui_resource路径下,也就是下载目录,在编译代码时,download.bat会将ui.res进行打包并下载。至于使用的时机,则是资源发生变化时需要手动运行,比如替换了某张图片或者增加某张图片后,请在下载代码前双击运行,重新生成一下资源。
以下两个文件为核心工具,但一般情况下不需要修改,有兴趣的话可以稍作了解,但大部分情况下不需要人为去改动:
Resbuilder.exe:核心打包工具,会解析Resbuilder.xml,根据里面的配置去执行打包压缩。
Resbuilder.xml:可以理解为是打包工具的配置文件,可以在该文件下修改资源格式,如png格式可以选择修改为ARGB8565、ARGB8888等。
- 资源更换工具说明 在一些情况下,比如多个项目的功能需求一模一样,只是UI风格有所区别,此时如果手动更换UI,逐个控件去进行图片替换,是极为麻烦且耗时的,在此提供了一个转换工具,可以直接进行资源更换,但要满足一个大前提的情况下才能使用:
两个项目的对应的图片尺寸,命名方式,格式必须完全一致。
通过工具进行校验,若满足以上前提,则会自动生成图片资源。
其中资源更换工具的目录是code\sdk\lv_charging_case\res-pack-tools\资源更换工具入口,双击 校验并生成图片.exe 运行。
可以直接使用资源校验工具去进行图片对比,其中\res-pack-tools\imb-rle-pack\imbtools\images为开发者输入图片目录,也就是源参考的图片目录;而\res-pack-tools\资源更换工具入口\USER_IMAGE为客户端更换图片目录,也就是用于更换的新的图片,双击 校验并生成图片.exe 运行,工具会自动校验两个目录下图片的名字,尺寸,校验通过后会重新生产资源,校验失败则会有提示,用户可以根据提示去修改图片,如下所示:
资源对比发现USER_IMAGE目录下缺少了0.png,以及ic_jia.png分辨率不一致,当前分辨率为(80, 60),应当修改为(60, 60),用户可以根据此提示去对不符合要求的图片进行整改。
需要注意的是,当运行资源打包工具code\sdk\lv_charging_case\res-pack-tools\imb-rle-pack\run.exe时,会将USER_IMAGE覆盖,注意做好备份。
👉资源下载说明
在code\sdk\cpu\br28\tools\download\watch路径下的download.bat中配置需要下载的资源如下所示:
而资源文件则是存放在code\sdk\cpu\br28\tools\ui_resource路径下:
将资源文件的拷贝工作由脚本来完成。
- 图片缩放工具说明
该工具的目录是code\sdk\lv_charging_case\res-pack-tools\图片缩放工具,在UI开发过程中,我们需要对一些图片尺寸进行简单的微调,这个时候可以使用图片缩放工具进行处理,在input_folder_path中放入输入图片,双击 run.exe后,输入输出图片的宽高即可,如下图以将图片缩放到20*30为例:
此时会将图片按照要求将图片缩放到设定尺寸并输出至output_folder_path路径下。
- 用户动画更换入口
👀提问:什么是外部动画?
答:外部动画是相对于内部动画来说的,所谓的内部动画可以理解为LVGL原本就支持的动画控件的使用方式,常用方式如下:
const char * anim_imgs[5] = {
"F:/wallpaper_small_00.bin",
"F:/wallpaper_small_01.bin",
"F:/wallpaper_small_02.bin",
"F:/wallpaper_small_03.bin",
"F:/wallpaper_small_04.bin"
};
//Write codes anim
ui->anim = lv_animimg_create(ui->screen);
lv_animimg_set_src(ui->anim, (const void **) anim_imgs, 5);
lv_animimg_set_duration(ui->anim, 100*5);
lv_animimg_set_repeat_count(ui->anim, LV_ANIM_REPEAT_INFINITE);
// lv_animimg_start(ui->anim);
lv_obj_set_pos(ui->anim, 2, 2);
lv_obj_set_size(ui->anim, 140, 60);
lv_obj_add_flag(ui->anim, LV_OBJ_FLAG_CLICKABLE);
lv_obj_clear_flag(ui->anim, LV_OBJ_FLAG_SCROLLABLE);
以上代码配置了一个指针数组anim_imgs去存放内部动画所需要的切图资源,但这种做法在切换动画时就显得不太灵活,需要修改里面的切图路径,故推出外部动画,将每个动画打包成一份独立的资源文件,以文件名作为输入进行访问,同时不需要考虑更换动画时切图数量是否跟之前一致的问题。
故外部动画的优点在于使用灵活,能够快速方便地给动画控件进行资源切换,但缺点是会消耗一定的ram。
该工具目录位于code\sdk\lv_charging_case\res-pack-tools\用户动画更换入口,需要依赖于\code\sdk\lv_charging_case\res-pack-tools\helper-tools
该工具的作用是生成外部动画打包资源,其中外部动画的资源会被打包成.bin文件和.res的文件,最终被合并成以文件夹命名的文件,添加到下载目录。
这里以充电动画CHG为例进行一次讲解:
code\sdk\lv_charging_case\res-pack-tools\用户动画更换入口\CHG路径下文件作用如下:
100ms文件夹:这里的“100ms”为动画切换间隔,工具会去解析该文件夹的文件名,获取动画切换的间隔,故在此必须按照xxxms这种方式命名,否则工具将无法获取动画间隔,从而导致生成资源失败,若想把动画间隔改为200ms,将该文件夹重命名为“200ms”即可,里面存放的则是动画的切图文件。
CHG.bin:该文件主要包含文件名,动画间隔以及图片数量的信息,用于给底层解析外部动画的信息。
CHG.res:打包后的外部动画资源文件,可以直观地看出当前动画资源文件为400kb,用户可以通过调整100ms文件夹中的图片去进一步缩减资源文件大小。
此时如果100ms同级目录下如果有gif图片,则会以gif优先,将gif解析为一张张切图,并放置在以gif播放间隔命名的文件夹中,并生成资源。
当所有动画资源生成完成后,可以将code\sdk\lv_charging_case\res-pack-tools\用户动画更换入口\pack_code.txt的内容复制到download.bat中,并决定需要将哪些外部动画添加下载:
之后调用usr_update_animing接口将动画控件与外部动画资源绑定在一起即可。
- Makefile生成工具
🙈提问:为什么需要使用Makefile生成工具?
答:为了针对目录结构发生改变后进行快速Makefile构建,以及便于移植。
在第一章中,提到了该SDK是通过Makefile进行管理的,那么当目录结构发生变化,比如/a路径下存在abc.c文件,若此时把abc.c移动到/b路径下,则需要手动将Makefile中abc.c的文件路径进行修改,否则编译时会报错,并提示“No rule to make /a/abc.o”。如果改动文件较少,一般不成问题,但如果改动较大,涉及到的文件有上百个,上千个,手动去修改是不太合理的,故可以通过工具去重新构建Makefile,避免人为修改。
工具目录为code\sdk\lv_charging_case,需要依赖cmake和mingw(下载链接可见于文档顶部),其中:
makefile_clear.bat:用于清除过程文件,包括.mk文件,工具会遍历\lv_charging_case路径下的所有过程文件并且进行删除。
makefile_maker.bat:用于重新构建Makefile,生成每个子目录下的.mk文件,工具会遍历\lv_charging_case路径下的所有.c文件,将.c文件的路径添加到编译路径,并且会遍历所有.h文件,将包含了头文件的路径添加到头文件查找路径。
以上两个均为批处理文件,双击运行即可。使用时机为\lv_charging_case路径下的目录结构发生变化时,若不希望手动修改Makefile,则可以通过工具重新构建Makefile。
- 其他UI框架接入耳机和982 在某些场景下,会有接入LVGL以外的UI框架的需求,此时能以LVGL的逻辑层为参考,去接入三方交互的逻辑,但在此之前,我们先了解一下耳机、982与充电仓之间是如何交互的。
首先,耳机和982共同维护一套变量,这套变量已结构体的形式在充电仓SDK中呈现,其每个成员的意义如下(该结构体可见于code\sdk\apps\watch\smartbox\smartbox_user_app.h):
struct s_box_info {
u8 phone_state ; //手机连接状态,0:不连接 1:连接
u8 earphone_state; //耳机连接状态,0:不连接 1:连接
u8 l_ear_bat ; //L耳电量
u8 r_ear_bat ; //R耳电量
u8 box_bat ; //充电仓电量
u8 music_state; //耳机播放状态
u8 earphone_vol ; //耳机音量
u8 anc_mode ; //anc模式,0关 1开 2通透
u8 eq_mode; //eq模式
u8 ui_bl_levl; //仓背光亮度
u8 l_charging; //L耳充电状态
u8 r_charging; //R耳充电状态
u8 box_charging; //充电仓充电状态
u8 l_finding; //查找L耳
u8 r_finding; //查找R耳
u8 box_clid_status; //充电仓开关盖状态
u8 language; //语言
u8 low_power; //低电状态
u8 phone_call_state;//通话状态
u8 phone_call_mute; //通话是否静音
u8 local_page; //本地页面记录
struct sys_time phone_time;//系统时间
};
其中,982通过串口与充电仓通信,而耳机则是通过ble与充电仓进行通信(使用RCSP),实时更新box_info的变量,UI再根据这些状态,去进行显示等操作。那么,接入其他UI框架之前,需要保证充电仓能正常与耳机,982进行通信,涉及到这块功能的关键文件如下:
- smartbox_user_app.c - smartbox_user_app.h - lib_wsbox.a - uart_test.c - uart_test.h - uart_types.h - uart_v1.c - uart.h 以上文件需要全部移植到用户的SDK中,移植成功后,SDK已经兼备了实现以上功能的具体文件,但还需要在充电仓中主动更新连接状态(可见于code\sdk\apps\watch\smartbox\bt_manage\bt_trans_data\le_smartbox_multi_client.c):
在上图两个地方调用sbox_ble_connect_flag_set、sbox_bt_connect_flag_set主动更新蓝牙连接,并且按照彩屏仓SDK板级文件board_701n_demo_cfg.h的宏进行配置,建议用对比工具将板级文件中蓝牙相关的配置进行对比,同步。以上操作完成后,彩屏仓与蓝牙、982的通信基本已经打通,至于UI与蓝牙之间的交互,可参考中间层文件events_adapter.c。
充电仓可以通过以下接口去获取box_info的状态值(可见于code\sdk\lv_charging_case\lv_frame\adapter\events_adapter.c):
在box_info_base_cb中,右边的函数如sbox_bt_connect_flag_get等为获取状态值的具体实现,而左边的带有lv前缀的函数如lv_phone_state_get则是用于UI线程中获取相关状态值,通过这种方式即可在UI中获取对应状态值并且进行相关处理。同时,充电仓也可以通过box_info_base_cb里面的接口,主动更新box_info的状态值。
UI除了获取状态以外,还需要主动与耳机去进行通信,如进行音量控制等,这些接口则是存放于box_info_send_cb中:
与box_info_base_cb类似,右边的接口为具体实现,而左边的则是在UI中直接调用的接口。在events_adapter.c中,box_info_base_cb、box_info_send_cb中配置的接口是直接跟耳机、982相关的,除此之外的则是跟LVGL相关的,如配置亮灭屏,RTC等,因此,在其他UI框架中与982、耳机进行通信,只需要将box_info_base_cb、box_info_send_cb里面使用到的配置接口,在其他UI框架中对应的回调函数里面进行调用即可。
接下来以LVGL的一个音乐播放暂停功能为例,介绍如何接入蓝牙相关接口:
static void screen_loop_imgbtn_music_play_or_stop_event_handler (lv_event_t *e)
{
lv_event_code_t code = lv_event_get_code(e);
switch (code) {
case LV_EVENT_CLICKED:
{
int music_status = box_info_base_cb.lv_music_state_get(); // 获取音乐播放状态
switch (music_status)
{
case MUSIC_STATE_PAUSE:
box_info_send_cb.lv_music_play_cmd_send(); // 发送播放音乐命令
box_info_base_cb.lv_music_state_set(MUSIC_STATE_PLAY); // 主动更新状态为音乐播放
break;
case MUSIC_STATE_PLAY:
box_info_send_cb.lv_music_pause_cmd_send(); // 发送暂停音乐命令
box_info_base_cb.lv_music_state_set(MUSIC_STATE_PAUSE); // 主动更新状态为音乐暂停
break;
default:
break;
}
break;
}
default:
break;
}
}
以上例子中,screen_loop_imgbtn_music_play_or_stop_event_handler作为screen_loop_imgbtn_music_play_or_stop控件的回调函数,当控件被点击时,回调函数会被执行,此时通过box_info_base_cb.lv_music_state_get()获取音乐状态,再根据状态去调用蓝牙命令发送接口,去实现音乐的播放暂停控制。故对于其他UI框架来说,在对应的回调中调用状态获取接口,状态设置接口,以及蓝牙命令发送接口即可。
总结一下,整体接入流程为:
1. 移植关键文件(涉及充电仓与耳机,982通信); 2. 充电仓中主动更新连接状态; 3. 比对充电仓板级文件中蓝牙相关配置; 4. 参考events_adapter.c中配置的接口,在用户UI框架对应的回调中去调用相关接口去搭建UI交互逻辑。
- 常见问题
- 其他UI框架如何进行屏幕旋转? 如果其他UI框架(包含杰理UI框架跟自有UI框架)的屏幕驱动使用的是公版sdk的流程,而不是自己重构流程,均可以使用以下补丁:
lcd_drive.c下载链接
用户可以根据自身需求更改旋转角度,其中scr_x,scr_y的配置与旋转的角度无关。