尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

ESP32-S3上LVGL实现键盘输入实时生成二维码

ESP32-S3上LVGL实现键盘输入实时生成二维码 简介一套面向物联网嵌入式开发者的ESP32实战例程基于LVGL开源图形库实现通过键盘输入实时生成二维码的功能。例程采用Visual Studio Code ESP-IDF环境使用C语言编写在ESP32-S3上验证运行适合正在学习嵌入式GUI、二维码应用或物联网终端显示开发的工程师与学生。资源包共1218个文件以C源文件与头文件为主同时包含Python辅助脚本、Markdown说明文档、PNG预览图及字体、图片素材便于阅读和二次开发压缩包大小23.54MB内含引脚接线定义与详细代码注释可帮助快速移植适配其他硬件。目前已有228人学习下载。通过学习该例程可掌握LVGL控件调用、键盘事件处理、二维码生成库集成及ESP-IDF工程组织方法例程目录结构清晰代码注释完整便于快速定位界面创建、编码与显示等关键流程可为智能终端、门禁、支付等物联网场景提供直接参考。1. 从键盘输入到屏幕码图ESP32-S3 上 LVGL 二维码生成器的实用起点工业设备、医疗仪器、仓储终端这类物联网产品上经常遇到一个很实际的需求设备不在线、没有云端、没有打印机但要临时把一个编号或配置参数交给用户——最省事的办法就是让屏幕直接显示一个二维码。手机上扫一扫信息就过去了。很多团队第一反应是“生成二维码得先联网调一个接口”实际上在 ESP32-S3 这类 MCU 上用 LVGL 的开源图形库完全能本地完成从键盘输入到二维码刷新的全部逻辑不依赖任何外部服务。这套例程做的正是这件事用 ESP-IDF 编写 C 代码LVGL 提供控件和画布把用户在屏幕上输入的字符串实时编码成二维码素材包里还带了一批 BMP 位图和 LVGL demo 图片资源方便替换界面底图和图标。适合正在做带屏物联网设备、想把二维码能力离线集成到嵌入式界面上的开发者也适合作为 LVGL 输入控件、事件回调、图片资源加载这组技能点的入门范本。2. LVGL 二维码控件与 QR 码编码原理数据段、容错级别与色深匹配2.1 从字符串到模块矩阵QR 码编码在 MCU 上是怎么完成的QR 码不是简单把字符按 ASCII 画出来它的生成过程可以拆成数据编码、纠错码计算、模块布置、掩码优化四个阶段。LVGL 的lv_qrcode组件内部封装了这些步骤开发者只需要给它一个字符串、一个尺寸和一个颜色值它就能在内部生成一张位图并自动挂到画布上。对嵌入式侧来说最值得关心的是两个参数版本Version和容错级别Error Correction Level。版本决定了二维码的物理尺寸版本 1 是 21×21 模块每增加一个版本宽度加 4 模块容错级别决定二维码被遮挡、缺损时还能不能扫出来四个级别分别能容忍约 7%、15%、25%、30% 的码区损失。LVGL 里创建二维码控件的典型接口如下lv_obj_t *qr lv_qrcode_create(parent, size, color, bg_color); lv_qrcode_update(qr, Hello ESP32, strlen(Hello ESP32));第一行创建了一个指定宽高的二维码对象size是像素值color是二维码前景色bg_color是背景色。第二行执行实际编码和绘制传入字符串和长度。这样生成的二维码LVGL 内部会把它作为一个图片对象绘制到屏幕上刷新频率和普通图片控件一致不需要手动管理 framebuffer。2.2 容错级别、缩放与可读性的取舍LVGL 的二维码组件在底层的默认容错级别通常落在中等档位但这并不一定适合所有屏幕。如果设备屏幕本身尺寸小只能用 120×120 像素画二维码又要保证手机在 30 厘米外一扫即中我会倾向于把容错级别调高一档。二维码被识别的要求是每个模块至少覆盖手机摄像头的 4×4 像素左右模块数量越多、单个模块像素越少对屏幕清晰度要求越高。一个 120 像素的二维码画布如果内容是短网址编码器可能生成版本 329×29 模块每个模块约 4.1 像素刚好踩着边界如果内容长到版本 537×37 模块每个模块只有约 3.2 像素手机稍微晃动就扫不出来。这种情况下要么扩大画布要么提高前景背景对比度要么用更短的输入内容。这是 QR 码几个关键参数之间的关系实际调参时可以对照排查版本模块数数据容量字节M 容错适合场景121×2114单个短 ID225×2526设备序列号329×2942短 URL433×3362设备信息时间戳537×3784配置JSON片段例程包里的example_32bit.bmp、example_24bit.bmp、example_16bit.bmp三张图是不同色深的 BMP 测试素材它们和二维码生成的关联在于LVGL 绘制二维码时会按工程的LV_COLOR_DEPTH把颜色值写入显示缓冲。16bitRGB565模式是很多 SPI 屏幕的默认配置32bit 素材转成 C 数组后颜色会做一次降位转换如果不注意原来纯黑色的二维码前景可能在屏幕上变成暗灰色降低与白色背景的对比度直接影响扫码成功率。2.3 二维码组件的两种接入方式内置控件与外部库LVGL 8.x 时代的二维码能力需要通过lv_lib_qrcode外部组件引入它基于quirc的编码逻辑改造在 LVGL 9.x 之后才被整合进官方组件库。项目里如果是 8.3 版本需要在lv_conf.h中确认相关宏已经打开并且把lv_lib_qrcode源码目录加入 CMake 编译路径。常见做法是直接用 LVGL 官方仓库里配套的 qrcode 目录结构或者把lv_qrcode.c和lv_qrcode.h单独放进工程的components下。判断是否生效的方法很简单编译时搜索是否有lv_qrcode_create这个符号没有就说明组件没被编进去。需要注意lv_qrcode_create的size参数并不是越大约好。大尺寸二维码需要更大的内部缓冲做编码计算底层会把生成的模块矩阵展开成位图100×100 像素以上时这个临时缓冲可能占用十几 KB 内存。如果 ESP32-S3 没有开启 PSRAM多个控件同时驻留内存时容易触发堆分配失败表现是屏幕上的二维码区域一片空白或花屏。下一章开始进入例程的实际代码流程从建控件到接键盘事件完整走一遍。3. 键盘输入到二维码刷新lv_keyboard 事件回调与 lv_qrcode 联动实现3.1 界面布局与控件创建顺序例程的界面可以拆成三层顶部的文本框显示当前输入内容中部的虚拟键盘负责字符录入底部的区域放二维码和刷新按钮。LVGL 的lv_keyboard是现成的全键盘控件它本身不存储内容而是关联到一个lv_textarea用户点按键盘时字符自动进入文本框这减少了大量按键分发代码。我实现的布局代码段大致如下/* 创建文本框用于显示和编辑输入内容 */ lv_obj_t *ta lv_textarea_create(lv_scr_act()); lv_obj_set_size(ta, 280, 50); lv_obj_align(ta, LV_ALIGN_TOP_MID, 0, 10); lv_textarea_set_placeholder_text(ta, 请输入文本内容); /* 创建虚拟键盘并关联文本框 */ lv_obj_t *kb lv_keyboard_create(lv_scr_act()); lv_obj_set_size(kb, 280, 110); lv_obj_align(kb, LV_ALIGN_BOTTOM_MID, 0, 0); lv_keyboard_set_textarea(kb, ta); /* 创建二维码控件先显示一个默认内容 */ lv_obj_t *qr lv_qrcode_create(lv_scr_act(), 160, lv_color_black(), lv_color_white()); lv_obj_align(qr, LV_ALIGN_CENTER, 0, -10); lv_qrcode_update(qr, ESP32-LVGL, strlen(ESP32-LVGL));lv_keyboard_set_textarea(kb, ta)这一步把键盘和文本框栓在一起之后键盘上所有字母、数字、退格键都会自动作用到ta上。二维码控件放在屏幕中央偏上位置避免和键盘重叠。160 像素的初始尺寸是调试时临时设的后续可以根据屏幕实际分辨率调整。3.2 点击完成按钮后如何触发二维码刷新虚拟键盘默认自带一个“完成/OK”按钮按下后键盘会发出LV_EVENT_READY事件。在这个事件回调里读取文本框内容再调用lv_qrcode_update刷新二维码这是这套例程最核心的联动逻辑。事件回调函数可以这样写static void kb_event_cb(lv_event_t *e) { lv_event_code_t code lv_event_get_code(e); if (code LV_EVENT_READY) { /* 用户按了键盘上的完成键开始生成二维码 */ lv_obj_t *ta lv_event_get_user_data(e); const char *txt lv_textarea_get_text(ta); if (txt ! NULL strlen(txt) 0) { /* 更新二维码内容内部会自动重新编码 */ lv_qrcode_update(qr, txt, strlen(txt)); } } }lv_qrcode_update内部做了两件事释放旧的二维码位图数据然后重新编码新字符串并绘制。这意味着屏幕上原有的二维码不会残留残影因为整个画布内容被覆盖重画了。要特别注意的是txt指针的生命周期lv_textarea_get_text返回的指针指向文本框内部缓冲在下次键盘输入前是有效的但如果回调结束后文本框被销毁再访问这个指针就是悬垂指针。在只做更新不销毁的场景下没有这个问题一旦界面上增加了“清空并重新开始”的功能就要先把文本拷贝到局部缓冲再处理。3.3 事件回调里不能做的两件事LVGL 的事件回调运行在 GUI 线程上下文中这里不要做耗时操作尤其不要在这段代码里调用vTaskDelay或esp_rom_delay_us。二维码编码本身是纯计算任务短内容在 ESP32-S3 上耗时只有几毫秒可以直接放在回调里但如果允许用户输入几百字节的文本编码时间会明显上升界面会卡顿。对于这种场景我会把编码任务交给独立线程用队列把文本传给二维码任务生成完成后再通过lv_async_call回到 GUI 上下文更新控件。例程为了简洁直接同步执行工程上做产品时要留意这个边界。刷新时机还有一个容易踩的坑LVGL 的控件更新不是立即渲染到屏幕的lv_qrcode_update只是把新位图放进了控件的绘图缓存真正的显示要等到下一个lv_timer_handler()周期。如果你的主循环很短就调用一次lv_timer_handler那么二维码几乎是立刻显示如果主循环被其他耗时任务阻塞用户会感觉“按了完成键屏幕没反应”。排查此类问题时先看lv_timer_handler的调用频率不要一上来就怀疑二维码生成逻辑。3.4 用素材包里的 BMP 位图增强界面可读性压缩包里那批img_lv_demo_music_cover_*.c文件和img_demo_widgets_avatar.c是 LVGL 官方 demo 的图片数据源属于典型的“拿来就能当底图”的资源。如果觉得纯色背景太单调可以把其中一张封面图转成 C 数组后用lv_img_set_src设到屏幕底层二维码放在图片上方。这里有一个兼容性问题官方 demo 图片多数是 24bit 或 32bit 色深的 C 数组而工程如果配置为 16bit 色深LVGL 在编译时会报类型警告显示时颜色也会偏。处理办法是用 LVGL 的在线图片转换工具把源图统一转成 RGB565 格式的 C 数组再重新编译。4. ESP-IDF 工程配置与资源约束帧缓冲、双缓冲与 LVGL 内存池调优4.1 sdkconfig 里和 LVGL 二维码强相关的几个配置项把例程烧进 ESP32-S3首先要在idf.py menuconfig里核对几个配置。CONFIG_LV_COLOR_DEPTH建议设为 16对应 RGB565这是大多数 240×320 SPI 屏的原生格式也最省内存。CONFIG_LV_MEM_SIZE决定 LVGL 内部的动态内存池大小二维码控件在里面创建对象、分配位图缓存一般至少给8 * 1024更大的屏幕或更长输入内容建议16 * 1024。还要开启CONFIG_LV_USE_QRCODELVGL 9.x或确认外部组件已加入编译。最后是CONFIG_LV_DPI_DEF它影响文本和控件缩放Photoshop 里设计的 160×160 二维码在屏幕上显示的实际物理尺寸取决于这个 DPI 值如果设成 100二维码在 240×320 屏上会显得偏小。#if LV_COLOR_DEPTH ! 16 #error This project is optimized for RGB565, please adjust LV_COLOR_DEPTH #endif这段编译期检查放在main.c开头能提前拦截掉因为色深不匹配导致的显示问题比烧录后看图猜故障高效得多。4.2 PSRAM 与帧缓冲为什么大尺寸二维码需要外部内存ESP32-S3 内置的 SRAM 在 LVGL 的帧缓冲和二维码位图同时存在时很容易吃紧。一个 320×240、RGB565 的帧缓冲本身就要320*240*2 153600字节即 150KB如果开启双缓冲直接到 300KB。这种情况下再让 LVGL 去分配一块 160×160 的二维码位图约 51KB内置内存基本扛不住。结论很直接跑 LVGL 图形界面且要显示二维码芯片选型时直接选带 PSRAM 的 ESP32-S3 模组或者在 sdkconfig 里开启CONFIG_SPIRAM并把 LVGL 的内存分配器切到支持外部堆的版本。开启 PSRAM 后还要设置CONFIG_SPIRAM_USE_MALLOCy这样malloc才会从外部内存分配大块缓冲。LVGL 的lv_mem_init默认从内部堆划内存池如果希望 LVGL 用 PSRAM需要在lv_conf.h里把LV_MEM_POOL_ALLOC改为自定义的ps_malloc#define LV_MEM_POOL_ALLOC ps_malloc #define LV_MEM_POOL_FREE freeps_malloc是 ESP-IDF 提供的 PSRAM 堆分配接口和标准malloc的差别在于它只在外部 RAM 上分配释放仍用free。改完这两个宏后LVGL 的控件对象、显示缓冲、图片缓存都会优先落到 PSRAM内置 SRAM 的压力会明显下降具体表现就是界面滑动和二维码刷新不再偶尔卡一下。4.3 素材图片转换从 BMP 到 C 数组的自动化批量处理example_32bit.bmp、example_24bit.bmp、example_16bit.bmp这三张测试图除了验证色深兼容性还有一个实用场景手工把 BMP 拖进 LVGL 在线转换器太慢量产级项目里我会用 Python 脚本批量转。脚本的核心逻辑是用PIL读 BMP统一缩放到目标分辨率再按 RGB565 格式输出成 C 数组from PIL import Image def bmp_to_c(filename, output, max_width240, max_height240): img Image.open(filename).convert(RGB) img.thumbnail((max_width, max_height), Image.LANCZOS) pixels list(img.getdata()) with open(output, w) as f: f.write(fconst uint16_t img_data[] {{\n) for i, (r, g, b) in enumerate(pixels): r5 r 3 g6 g 2 b5 b 3 rgb565 (r5 11) | (g6 5) | b5 f.write(f0x{rgb565:04X},) if (i 1) % 12 0: f.write(\n) f.write(};\n)R5、G6、B5 三个位段的右移位数是 RGB565 转换的关键红色取高 5 位、绿色取高 6 位、蓝色取高 5 位合在一起成为一个 16bit 值。如果右移位数搞错图片颜色会整体偏绿或偏粉。转换完成后在 C 代码里用lv_img_set_src指向这个数组即可。需要提醒的是thumbnail不会放大原图如果源图本身小于目标尺寸输出就比预期小裁切时多留意这一点。4.4 换到其他 ESP32 型号时要改哪些东西例程标注在 ESP32-S3 上验证通过换到普通 ESP32 或 ESP32-C3 时最直接的影响是 PSRAM 能力和主频。ESP32-C3 没有 PSRAM帧缓冲只能放内部 SRAM二维码画布建议从 160 降到 120 像素。ESP32-C3 的主频是 160MHz比 S3 默认 240MHz 低二维码编码稍有卡顿可接受。GPIO 定义也要重新对照sdkconfig里的 LCD 引脚设置SPI 屏幕初始化代码中LCD_HOST、PIN_NUM_MOSI、PIN_NUM_SCLK这些宏建议单独放在一个lcd_pins.h里换板子只改这一个文件不用动 LVGL 逻辑。5. 二维码可读性验证与两个实用调试技巧5.1 不依赖扫码枪用已知内容做最小可读性基线验证生成的二维码是否正确不需要每次都掏手机扫。我会准备一组固定测试向量内容为https://example.com/esp32版本固定容错级别固定先在一台标准 PPI 屏幕上用手机扫通拍下照片记录当时的画布尺寸和内容长度作为基线。后续改了 UI 布局或者换了屏幕驱动把同样内容生成出来对比如果肉眼能看出模块边缘发虚或者出现锯齿优先检查颜色深度和缩放是否被 LVGL 的样式系统干扰。5.2 手机扫不出时按三个环节快速定位二维码扫不出来90% 的问题出在三个地方。第一画布上白色背景被窗口背景色污染lv_qrcode_create的bg_color参数必须和二维码周围区域的填充色保持一致。第二内容超过 100 字节时编码版本升高模块变小判断是否尺寸不足的方法是缩小画布到实际需要的最小值而不是继续放大因为放大不会改变模块数量只改变模块像素宽度。第三中文内容要特别注意 LVGL 文本框和二维码库的字符编码如果系统内码是 GBK二维码库期望 UTF-8扫出来就是一串乱码这种情况在生成前要iconv或utf8_encode做一次转码。5.3 一个提升扫码成功率的简单手段加入容错级别切换在产品里加一个“容错级别”设置项比反复调显示参数更直接。LVGL 二维码组件底层通常允许指定纠错等级如果接口没有暴露可以在编码前对输入字符串做一次预处理短内容用较高容错长内容自动降一级来避免版本跳变。示例代码如下const char *content SN:20250607-42; int len strlen(content); if (len 30) { lv_qrcode_set_error_level(qr, LV_QRCODE_LEVEL_H); } else { lv_qrcode_set_error_level(qr, LV_QRCODE_LEVEL_M); } lv_qrcode_update(qr, content, len);LV_QRCODE_LEVEL_H能容忍 30% 的码区污损屏幕上有一道轻微划痕或灰尘也不影响识别内容较长时切到LV_QRCODE_LEVEL_M避免纠错码挤占数据空间导致版本升级。这个设置项用lv_switch或lv_roller做交互都行事件逻辑和维护二维码刷新完全一致。最后建议把生成二维码的内容同时通过串口打印一份遇到设备屏幕在产线上无法贴合的极端情况至少还能靠日志里的字符串临时生成二维码不至于卡住测试流程。本文还有配套的精品资源点击获取
返回列表