1. 项目概述:CH32V307VCT6驱动ST7735S屏幕与LVGL整合
最近在做一个基于RISC-V内核CH32V307VCT6的项目,需要驱动一块128x160分辨率的ST7735S TFT屏幕,并搭载LVGL图形库实现UI界面。经过两周的调试和优化,终于把整套系统跑通了,过程中踩了不少坑,也积累了一些实战经验,在这里做个完整记录。
CH32V307是沁恒微电子推出的一款RISC-V内核MCU,主频144MHz,内置480KB SRAM,性能足够驱动小型GUI应用。ST7735S则是常见的1.8寸TFT驱动芯片,通过SPI接口通信。LVGL作为轻量级开源图形库,版本9.4对RISC-V架构有良好支持。三者的组合非常适合需要图形界面的嵌入式设备开发。
2. 硬件设计与连接
2.1 核心硬件选型考量
选择CH32V307VCT6主要看中其大内存和硬件SPI接口,这对GUI应用至关重要。ST7735S屏幕成本低廉且驱动简单,128x160分辨率足以显示基础UI元素。硬件连接上需要注意以下几点:
- SPI时钟线(SCK)需要加10-100Ω电阻抑制信号振铃
- 背光控制建议使用PWM调光而非简单开关
- 复位线(RST)建议保留测试点方便调试
- 所有信号线长度尽量控制在15cm以内
2.2 具体引脚连接方案
根据项目提供的config.h文件,实际连接如下表所示:
| 屏幕引脚 | MCU引脚 | 功能说明 |
|---|---|---|
| BLK | PB9 | 背光控制 |
| DC | PB10 | 数据/命令选择 |
| RST | PB11 | 硬件复位 |
| CS | PB12 | 片选信号 |
| SCL | PB13 | SPI时钟 |
| SDA | PB15 | SPI数据输出 |
这里PB15用作MOSI而非PB14是个需要注意的点,因为CH32V307的SPI2_MOSI实际对应PB15引脚。如果错误连接会导致通信失败。
3. 底层驱动实现
3.1 SPI接口配置
SPI配置在SPI.c文件中实现,关键参数设置如下:
c复制SPI_InitStructure.SPI_Direction = SPI_Direction_1Line_Tx;
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8;
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
选择SPI_BaudRatePrescaler_8(18MHz)是经过实测的稳定值。初期尝试过更高频率,但会出现屏幕显示异常。建议调试阶段先用保守值,稳定后再尝试提高。
3.2 ST7735S驱动实现
驱动代码主要分布在ST7735S.c和ST7735S.h文件中。核心函数包括:
ST7735S_Init():完成硬件初始化和屏幕参数配置ST7735S_DrawImageRGB565():实现像素块写入ST7735_SetRotation():设置屏幕旋转方向
初始化流程特别注意需要严格按照数据手册的时序要求:
- 硬件复位(拉低RST至少10ms)
- 发送SLPOUT命令(0x11)后延迟120ms
- 配置色彩模式(0x3A)为RGB565
- 发送DISPON命令(0x29)开启显示
4. LVGL移植与配置
4.1 LVGL基础配置
从GitHub获取LVGL v9.4源码后,需要修改lv_conf.h配置文件:
c复制#define LV_COLOR_DEPTH 16 // 必须与屏幕色彩模式匹配
#define LV_MEM_SIZE (32 * 1024) // 根据可用SRAM调整
#define LV_DISP_DEF_REFR_PERIOD 20 // 刷新周期20ms
#define LV_DRAW_LAYER_SIMPLE_BUF_SIZE (8 * 1024)
内存分配需要特别注意,CH32V307虽然有480KB SRAM,但默认链接脚本可能只分配部分给堆。如果出现内存不足,需要修改链接脚本增加堆大小。
4.2 显示接口移植
显示接口移植主要在main.c中实现,关键步骤:
- 创建显示缓冲:
c复制static uint8_t g_lv_draw_buf1[128 * 16 * 2] = {0}; // 128像素宽 x 16行 x 2字节
- 注册显示设备:
c复制lv_display_t * disp = lv_display_create(128, 160);
lv_display_set_buffers(disp, g_lv_draw_buf1, NULL, sizeof(g_lv_draw_buf1), LV_DISPLAY_RENDER_MODE_PARTIAL);
- 实现刷新回调:
c复制static void LVGL_FlushCB(lv_display_t * disp, const lv_area_t * area, uint8_t * px_map) {
LVGL_SwapRGB565Bytes(px_map, pixel_count); // 字节序转换
ST7735S_DrawImageRGB565(area->x1, area->y1,
area->x2 - area->x1 + 1,
area->y2 - area->y1 + 1,
px_map);
lv_display_flush_ready(disp); // 必须调用
}
5. 工程配置与优化
5.1 编译环境设置
使用MounRiver Studio开发环境时,需要注意:
- 在.cproject文件中排除不必要的C++源文件:
xml复制<entry excluding="lvgl_v9.4/lvgl/src/drivers/display/lovyan_gfx|lvgl_v9.4/lvgl/src/libs/thorvg" ... />
- 添加正确的头文件包含路径,建议使用相对路径:
c复制#include "../lvgl_v9.4/lvgl/lvgl.h"
5.2 性能优化技巧
- 调整LVGL任务周期:
c复制#define LVGL_TICK_PERIOD_MS 5 // 5ms tick适合大多数应用
- 使用双缓冲减少闪烁:
c复制lv_display_set_buffers(disp, buf1, buf2, sizeof(buf1), LV_DISPLAY_RENDER_MODE_PARTIAL);
- 优化SPI传输:
- 启用DMA传输
- 使用硬件NSS信号
- 适当提高时钟频率
6. 常见问题与解决方案
6.1 显示异常排查
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 花屏 | SPI时钟过快 | 降低SPI分频系数 |
| 颜色错误 | 色彩模式不匹配 | 检查LV_COLOR_DEPTH和屏幕配置 |
| 显示偏移 | 初始化参数错误 | 调整ST7735S的行列偏移寄存器 |
6.2 内存不足处理
当出现内存分配失败时:
- 检查链接脚本中的堆大小
- 减少LVGL内存配置:
c复制#define LV_MEM_SIZE (16 * 1024) // 最小可降至16KB
#define LV_DRAW_LAYER_SIMPLE_BUF_SIZE (4 * 1024)
- 优化UI设计,减少同时显示的控件数量
6.3 中文显示问题
要显示中文需要:
- 在lv_conf.h中启用中文字体:
c复制#define LV_FONT_SIMSUN_16_CJK 1
- 指定控件使用中文字体:
c复制lv_obj_set_style_text_font(btn, &lv_font_simsun_16_cjk, 0);
- 注意中文字体会显著增加Flash占用
7. 实战经验分享
经过实际项目验证,总结以下几点经验:
- SPI线长超过20cm时,建议在MCU端串联33Ω电阻抑制反射
- 背光控制使用PWM调光可以避免低频闪烁
- LVGL的局部刷新模式能显著降低CPU负载
- 调试阶段建议启用LVGL的监视器功能:
c复制lv_mem_monitor_t mon;
lv_mem_monitor(&mon);
printf("Used: %d, Frag: %d%%\n", mon.used_pct, mon.frag_pct);
- 对于静态界面,可以缓存渲染结果进一步优化性能
这个方案已经成功应用在多个工业HMI项目中,稳定性得到验证。后续可以考虑添加触摸屏支持,使用LVGL的输入设备接口实现完整的人机交互功能。