尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

CH32V303上跑LVGL:USB鼠标直控嵌入式GUI方案

CH32V303上跑LVGL:USB鼠标直控嵌入式GUI方案 这次我们来看一个很实际的嵌入式 GUI 玩法在 CH32V303 上跑 LVGL然后用 USB 鼠标直接控制界面。很多人玩 LVGL 时习惯用触摸屏或者按键但鼠标这种标准 HID 指针设备在 MCU 项目里反而做得少。CH32V303 自带 USB 全速 Host 控制器不需要外挂 USB Host 芯片可以直接枚举有线鼠标再把它读到的位移和按键状态喂给 LVGL整个方案做下来很干净。这个方案真正有价值的地方在于它把“标准 USB HID 设备”和“LVGL 图形界面”打通了。也就是说你今天能用鼠标控制 LVGL明天换成 USB 键盘、条码枪、无线演示器思路基本一样。硬件门槛不高一块 CH32V303 开发板、一块屏幕、一个普通 USB 鼠标就能跑起来。软件方面需要 LVGL 源码、官方 EVT 例程和一个 RISC-V 编译环境。这篇文章会带你走完整条链路硬件准备与开发环境搭建、CH32V303 的 USB Host 初始化与 HID 鼠标报告解析、LVGL 指针输入设备注册、鼠标光标显示、滚轮和右键扩展、功能测试与效果验证、资源占用观察、常见问题排查最后给出一份可以直接参考的最佳实践清单。适合正在做 CH32V303 GUI 项目的开发者也适合想深入了解 LVGL 输入设备机制的读者。1. 核心能力速览能力项说明项目类型嵌入式 GUI 输入方案CH32V303 LVGL USB 鼠标主控CH32V303青稞 V4B RISC-V 内核主频 144MHz图形库LVGL建议用 v8.3 稳定版v9.x 需要适配输入设备 API输入设备USB 有线鼠标通过 CH32V303 内置 USB 全速 Host 接口读取启动方式编译烧录后上电运行不需要外接 USB Host 芯片接口能力LVGL 输入设备回调 USB HID 报告解析可扩展键盘/触摸批量任务支持 LVGL 批量创建控件、批量绑定事件可做自动化输入回放适合场景工业 HMI 原型、开发板交互演示、LVGL 鼠标输入适配研究开发工具MounRiver Studio 官方 IDE或 VS Code RISC-V GCC版权边界屏幕驱动、字体素材、UI 图标需使用合法授权资源从上面的表格能看出来这个方案的核心并不只是“鼠标”而是标准 HID 设备和 LVGL 图形框架之间的完整适配链路。CH32V303 作为 USB Host 读取鼠标报告再做坐标映射最后通过 LVGL 的lv_indev机制把数据变成界面事件。整条链路每一步都是可拆解、可验证的这对学习嵌入式图形交互很有价值。2. 鼠标控制 LVGL 的整体架构与原理在写代码之前先理解数据是怎么流动的。鼠标控制 LVGL 的完整链路如下物理层USB 鼠标通过 D/D- 连接到 CH32V303 的 USBFS Host 接口。协议层CH32V303 完成 USB 枚举识别鼠标为 HID 设备然后周期性读取中断 IN 端点上的鼠标报告。解析层把鼠标报告解析成按键状态、X 轴位移、Y 轴位移、滚轮位移。坐标层把位移增量累加到当前屏幕坐标上并做越界裁剪得到 LVGL 需要的绝对坐标。事件层LVGL 的lv_timer_handler周期性调用输入设备回调读取坐标和按键状态完成点击、拖拽、滚轮等事件派发。显示层LVGL 重绘界面屏幕刷新。这里有一个关键区别需要理解触摸屏是绝对坐标设备触摸点直接对应屏幕某个位置鼠标是相对位移设备核心数据是“往右移了多少像素”“往上移了多少像素”。所以接入 LVGL 时不能再把鼠标原始报告直接塞给 LVGL而是要在中间做一次“位移累加 坐标裁剪”。LVGL 的指针输入设备本身设计得比较通用。它通过lv_indev_drv_t注册一个回调LVGL 在每次刷新周期里调用这个回调拿到point.x、point.y和state然后靠这三个值去判断当前应该触发哪个控件的哪个事件。所以只要能在回调里给出正确的坐标和按下状态鼠标就能像触摸屏一样控制界面。滚轮和右键也值得单独说一下。LVGL 的指针输入设备结构体里没有专门的滚轮字段滚轮通常转换成按键事件比如向上滚动转换为LV_KEY_PREV向下滚动转换为LV_KEY_NEXT右键则可以通过当前坐标弹出自定义菜单这属于应用层设计。理解了这个架构后面每一步代码都有明确的位置。3. 硬件准备与开发环境搭建3.1 硬件清单器件规格/用途CH32V303 开发板需要引出 USBFS 接口能切换 Host/Device 模式显示屏建议选官方 EVT 已支持的屏例如 SPI 接口屏或 8080 并口屏USB 鼠标标准有线 USB 鼠标建议优先用全速 HID 兼容设备电源鼠标、屏幕、主控需要稳定供电枚举瞬间电流较大调试器WCH-Link 或兼容 RISC-V 调试器用于烧录和调试CH32V303 内部集成了 USB2.0 全速接口官方 EVT 里有 USB Host 相关例程。接线时重点检查 D/D- 的连接是否与开发板丝印一致接反会导致枚举失败。鼠标插入后建议用带供电的 USB 座不要从调试口取电减少供电不稳带来的枚举异常。3.2 软件环境CH32V303 的官方 IDE 是 MounRiver Studio基于 Eclipse内置编译器、调试器和例程导入功能适合快速跑通官方 EVT。如果你习惯命令行也可以用 VS Code RISC-V GCC OpenOCD 搭建环境。建议准备三份代码CH32V303 官方 EVT重点关注 USB Host 例程LVGL 源码建议使用 v8.3 分支对应屏幕的 LVGL 移植文件官方 EVT 或屏幕厂商一般会提供。3.3 推荐的开发流程不要一开始就把 USB Host、LVGL、屏幕驱动全部混在一起调问题会很难定位。推荐的顺序是先在 PC 上用 LVGL 模拟器把 UI 布局调好确认控件尺寸、底色、字体和交互逻辑没问题。在 CH32V303 上先跑通 LVGL 显示例程确保屏幕能正常渲染。单独跑官方 EVT 的 USB Host 例程确认鼠标能被枚举、能读到原始报告。最后合并两套代码把鼠标数据接入 LVGL 输入设备回调。这个顺序能让每一步的错误都停留在最小范围内。很多“光标乱跳”“点击没反应”的问题其实在模拟器阶段就已经能排除一部分界面逻辑问题。4. CH32V303 USB Host 模式读取 USB 鼠标数据4.1 USB Host 初始化与设备枚举CH32V303 的 USB Host 初始化包括 USB 控制器时钟使能、引脚配置、设备连接检测、总线复位、设备枚举和配置设备。官方 EVT 的 USB Host 例程会提供底层函数不需要从零写寄存器操作。流程概括如下/* 示意代码USB Host 初始化流程函数名以官方 EVT 为准 */ void usb_host_mouse_init(void) { USBFS_Host_Init(); /* 初始化USB FS主机控制器 */ USBH_WaitDeviceConnect(); /* 等待设备插入 */ USBH_DeviceReset(); /* 总线复位 */ USBH_DeviceEnumeration(); /* 枚举设备获取描述符 */ USBH_SetHIDBootProtocol(); /* 设置HID Boot Protocol可选 */ USBH_OpenPipe(); /* 打开中断IN端点 */ }鼠标枚举成功后系统会拿到一个中断 IN 端点。USB 全速设备的中断端点一般每 1ms 到 10ms 上报一次数据实际频率取决于设备描述符里的bInterval字段。主控只需要轮询这个端点就能不断收到鼠标报告。需要留意的是CH32V303 的 USB Host 对全速设备支持较好对低速设备需要确认官方手册和 EVT 是否支持。为了减少兼容性问题建议先拿最常见的全速有线 USB 鼠标来测。4.2 HID 鼠标报告解析标准 HID Boot Protocol 鼠标上报 4 个字节这是最常见的格式字节偏移含义report[0]按键状态bit0 左键bit1 右键bit2 中键report[1]X 轴位移有符号数report[2]Y 轴位移有符号数report[3]滚轮位移有符号数可选代码可以这样写/* 示意代码鼠标报告解析函数名按官方EVT调整 */ void usbh_mouse_report_handler(uint8_t *report, uint16_t len) { if (len 3) { return; } uint8_t btn report[0]; int8_t move_x (int8_t)report[1]; int8_t move_y (int8_t)report[2]; int8_t wheel (len 4) ? (int8_t)report[3] : 0; g_mouse_btn_left (btn 0x01) ? 1 : 0; g_mouse_btn_right (btn 0x02) ? 1 : 0; g_mouse_btn_middle (btn 0x04) ? 1 : 0; g_mouse_dx move_x; g_mouse_dy move_y; g_mouse_wheel wheel; }这里有几个细节容易踩坑report[1]和report[2]是有符号数必须用int8_t接收否则鼠标向左或向上移动时会变成很大的正数光标直接飞出屏幕。部分高精度鼠标会把 X/Y 位移扩展成 16 位这种情况下 4 字节 Boot Protocol 不够用需要读取设备的报告描述符来确认格式。解析代码在 USB 中断回调里执行时要尽量短只更新全局变量不要在回调里做 LVGL 操作。4.3 位移累加与坐标映射鼠标报告里的 dx/dy 是相对位移需要累加到当前坐标上并做越界裁剪。以 480x272 分辨率的屏幕为例#define HOR_RES 480 #define VER_RES 272 volatile int32_t g_mouse_x HOR_RES / 2; volatile int32_t g_mouse_y VER_RES / 2; volatile int8_t g_mouse_dx 0; volatile int8_t g_mouse_dy 0; volatile int8_t g_mouse_wheel 0; volatile uint8_t g_mouse_btn_left 0; void mouse_coord_update(void) { g_mouse_x g_mouse_dx; g_mouse_y g_mouse_dy; g_mouse_dx 0; g_mouse_dy 0; if (g_mouse_x 0) { g_mouse_x 0; } if (g_mouse_x HOR_RES) { g_mouse_x HOR_RES - 1; } if (g_mouse_y 0) { g_mouse_y 0; } if (g_mouse_y VER_RES) { g_mouse_y VER_RES - 1; } }坐标更新函数建议放在主循环里调用或者放在低优先级任务里不要在 USB 中断回调里做。原因很简单USB 中断回调频率可能高于 LVGL 刷新频率如果在中断里频繁修改变量LVGL 读取坐标时可能拿到中间状态。更稳妥的做法是用一个 FIFO 队列缓存鼠标事件主循环消费队列这样能避免坐标更新和 LVGL 读取之间的竞争。4.4 与 LVGL 刷新节拍的配合LVGL 靠lv_timer_handler()驱动这个函数会处理任务调度、输入设备轮询和屏幕刷新。典型主循环如下while (1) { mouse_coord_update(); /* 把USB增量转换为屏幕坐标 */ lv_timer_handler(); /* LVGL内部会调用mouse_read_cb */ delay_ms(5); }这样 USB Host 数据在中断里更新坐标在循环开头合并LVGL 在lv_timer_handler里通过read_cb读取最新坐标。整体节奏简单适合多数 CH32V303 项目。5. LVGL 输入设备注册与鼠标适配5.1 LVGL 版本选择LVGL v8.3 是当前资料最多、案例最丰富的版本API 相对稳定适合快速上手。LVGL v9.x 改了不少 API比如输入设备由lv_indev_drv_t lv_indev_drv_register()改为lv_indev_create() lv_indev_set_read_cb()。建议先按 v8.3 跑通整个链路再评估是否迁移到 v9.x。文末的扩展方向里会提到两个版本的差异点。5.2 v8.x 输入设备注册代码#include lvgl.h extern volatile int32_t g_mouse_x; extern volatile int32_t g_mouse_y; extern volatile uint8_t g_mouse_btn_left; static void mouse_read_cb(lv_indev_drv_t * drv, lv_indev_data_t * data) { (void)drv; >static void mouse_read_cb(lv_indev_t * indev, lv_indev_data_t * data) { (void)indev; >void mouse_handle_wheel(void) { if (g_mouse_wheel 0) { lv_group_send_data(lv_group_get_default(), LV_KEY_PREV); } else if (g_mouse_wheel 0) { lv_group_send_data(lv_group_get_default(), LV_KEY_NEXT); } g_mouse_wheel 0; }注意这个函数要在主循环里调用同时要确保目标列表、滚动区域已经加入到默认 group 中。右键处理则更偏应用层可以在mouse_read_cb里记录右键按下状态然后在主循环检测右键松开的坐标弹出一个上下文菜单。5.5 鼠标与触摸共存很多开发板本身带触摸屏接了鼠标之后触摸不能废掉。LVGL 支持多输入设备共存可以同时注册触摸输入设备和鼠标输入设备触摸设备类型也是LV_INDEV_TYPE_POINTER。鼠标设备类型同样是LV_INDEV_TYPE_POINTER。LVGL 会分别维护每个输入设备的状态同一个控件既能被鼠标点也能被触摸点。这对实际项目很友好用户既可以用手指直接操作也可以插上鼠标做更精细的控制非常适合工业 HMI 这类需要兼顾效率和精度的场景。6. 功能测试与效果验证6.1 测试前准备先把测试界面搭好至少包含四类控件按钮、列表、滑块、开关。按钮用来验证点击事件列表用来验证滚轮滑块用来验证拖拽开关用来验证状态切换。void ui_test_screen_init(void) { /* 创建按钮 */ lv_obj_t * btn lv_btn_create(lv_scr_act(), NULL); lv_obj_set_pos(btn, 20, 20); lv_obj_set_size(btn, 100, 50); lv_obj_set_event_cb(btn, btn_event_cb); /* 创建列表 */ lv_obj_t * list lv_list_create(lv_scr_act(), NULL); lv_obj_set_pos(list, 20, 90); lv_obj_set_size(list, 180, 140); for (int i 0; i 10; i) { lv_list_add_btn(list, NULL, test item); } /* 创建滑块 */ lv_obj_t * slider lv_slider_create(lv_scr_act(), NULL); lv_obj_set_pos(slider, 220, 20); lv_obj_set_size(slider, 150, 20); /* 创建开关 */ lv_obj_t * sw lv_switch_create(lv_scr_act(), NULL); lv_obj_set_pos(sw, 220, 70); }这个界面覆盖了鼠标输入最常见的交互类型。后面所有测试都围绕它进行。6.2 鼠标移动测试操作缓慢移动鼠标观察光标是否平滑跟随再快速甩动观察光标是否丢步、跳变或超出屏幕边界。预期光标与鼠标移动方向一致位移比例正常到达屏幕边缘后自动停止不会跑到屏幕外面。判断标准光标运动平滑没有乱跳快速移动后坐标累积正常不会出现一直往一个方向滑行的现象。失败排查如果光标方向反了检查move_y的符号如果光标乱跳优先打印原始报告确认report[1]和report[2]是否被当成无符号数如果光标停了但鼠标还能控制其他功能检查g_mouse_dx是否清零。6.3 鼠标左键点击测试操作左键单击按钮观察按钮按下、弹起的过程确认事件回调被触发。预期按下时按钮进入按下状态松开时触发点击事件控制台能打印出对应日志。判断标准按钮视觉状态变化正确事件回调至少触发一次回调里的参数正确。失败排查如果按钮根本没反应打印>static void btn_event_cb(lv_obj_t * obj, lv_event_t event) { if (event LV_EVENT_CLICKED) { uintptr_t id (uintptr_t)lv_obj_get_user_data(obj); printf(btn %d clicked\r\n, (int)id); } } void ui_batch_test_init(void) { for (int i 0; i 16; i) { lv_obj_t * btn lv_btn_create(lv_scr_act(), NULL); lv_obj_set_pos(btn, (i % 4) * 90 10, (i / 4) * 60 10); lv_obj_set_size(btn, 80, 50); lv_obj_set_user_data(btn, (void *)(uintptr_t)i); lv_obj_set_event_cb(btn, btn_event_cb); } }鼠标点击每个按钮串口会依次打印按钮编号这样就能快速确认鼠标命中测试和事件派发是否正常。对于产线自动化测试场景这套思路可以进一步扩展为脚本回放鼠标操作不需要人在现场逐个验证。7. 资源占用与性能观察7.1 如何观察资源占用MCU 项目不像 PC 那样能直接看到“显存占用”但可以通过编译链接文件和运行时测速来判断资源是否吃紧。Flash / SRAM 占用编译完成后查看列表文件或链接脚本输出能看到代码段和数据段的占用。CPU 占用可以用一个 GPIO 在主循环里翻转电平用示波器或逻辑分析仪测量翻转频率间接计算主循环负载。LVGL 帧率在lv_timer_handler里计数或者使用 LVGL 自带的帧率监测函数观察实际刷新帧数。7.2 关键影响因素因素说明屏幕分辨率与色深RGB565 比 RGB888 省一半缓存刷新带宽也更低屏幕接口RGB 屏需要较大的显存SPI/8080 屏主要受 DMA 传输带宽限制LVGL buffer 配置单 buffer 省内存但可能撕裂双 buffer 更流畅但占用翻倍USB 轮询频率全速中断端点一般 1ms 到 10ms 上报一次对 CPU 影响小于屏幕刷新控件复杂度阴影、圆角、抗锯齿、动画都会提高 CPU 占用7.3 降低内存与 CPU 占用的方法关掉不用的 LVGL 功能宏LV_USE_SHADOW、LV_USE_ANIMATION、LV_USE_BLEND等用不到就置 0。合理配置LV_MEM_SIZE在lv_conf.h里分配一个适合项目规模的动态内存池不要贪大。图片资源直接用 C 数组或外部 Flash避免在运行时解码大图。
返回列表