嵌入式Linux驱动开发实战:基于ST7735的TFT屏SPI驱动与Framebuffer集成

张开发
2026/4/16 18:40:57 15 分钟阅读

分享文章

嵌入式Linux驱动开发实战:基于ST7735的TFT屏SPI驱动与Framebuffer集成
1. ST7735 TFT屏驱动开发基础第一次接触ST7735驱动的朋友可能会觉得有点懵这个小小的屏幕背后居然需要这么多代码支持。其实理解它的工作原理后你会发现并没有想象中那么复杂。ST7735是一款常见的TFT液晶驱动芯片支持SPI接口通信广泛应用于各种嵌入式设备中。我去年在开发智能家居控制面板时就遇到过这个芯片当时为了赶项目进度连续调试了三天三夜。最让人头疼的是不同尺寸的屏幕初始化序列居然不一样这点后面会重点讲到。SPI通信是驱动ST7735的核心。这里有个生活化的比喻想象SPI就像餐厅的点餐流程。主控芯片是顾客ST7735是厨师MOSI线是顾客点餐的嘴巴MISO线是厨师回应的耳朵SCK是双方确认的点头动作CS片选就是决定要不要跟这个厨师说话。Linux内核已经帮我们封装好了SPI核心层我们要做的就是按照餐厅规矩来点餐。2. 设备树配置实战设备树配置是嵌入式Linux开发的必修课。对于ST7735来说我们需要在设备树中明确几个关键点首先是SPI控制器的配置。以i.MX6ULL为例配置示例如下ecspi3 { fsl,spi-num-chipselects 1; cs-gpio gpio1 20 GPIO_ACTIVE_LOW; pinctrl-names default; pinctrl-0 pinctrl_ecspi3; status okay; st7735: st77350 { compatible sitronix,st7735; spi-max-frequency 32000000; reg 0; }; };其次是GPIO的配置。ST7735需要RESET和DC两个控制引脚pinctrl_st7735: st7735grp { fsl,pins MX6UL_PAD_GPIO1_IO01__GPIO1_IO01 0x10B0 /* RESET */ MX6UL_PAD_GPIO1_IO04__GPIO1_IO04 0x10B0 /* DC */ ; };我在实际项目中踩过一个坑CS片选信号的处理。有些开发板硬件设计时已经固定了CS引脚这时在设备树中就不需要再配置cs-gpio属性否则会导致冲突。建议先用示波器检查CS信号是否正常。3. 驱动程序设计精要ST7735的驱动开发主要分为三个部分SPI通信、初始化序列和Framebuffer集成。先来看SPI通信的核心函数static int st7735_write_regs(struct spi_device *spi, u8 *buf, u16 len) { struct spi_transfer xfer { .tx_buf buf, .len len, }; struct spi_message msg; spi_message_init(msg); spi_message_add_tail(xfer, msg); return spi_sync(spi, msg); }初始化序列是驱动ST7735的关键。不同尺寸的屏幕初始化参数可能不同这是很多开发者容易忽略的地方。比如1.44寸和1.8寸屏的初始化命令就有差异/* 1.44寸屏初始化序列 */ static const struct st7735_cmd st7735_init_seq[] { {0x11, NULL, 0, 120}, {0x36, \x08, 1, 0}, {0x3A, \x05, 1, 0}, ... }; /* 1.8寸屏初始化序列 */ static const struct st7735_cmd st7735r_init_seq[] { {0x11, NULL, 0, 120}, {0x36, \xC0, 1, 0}, {0x3A, \x05, 1, 0}, ... };我在项目中遇到过屏幕显示颜色异常的问题最后发现是初始化序列中Gamma校正参数设置不当导致的。建议拿到新屏幕时先用厂家提供的初始化参数再逐步优化。4. Framebuffer集成技巧将ST7735驱动集成到Linux Framebuffer子系统后可以无缝支持各种图形应用。核心是实现fb_info结构体的初始化和操作集static struct fb_ops st7735_fb_ops { .owner THIS_MODULE, .fb_setcolreg st7735_setcolreg, .fb_fillrect cfb_fillrect, .fb_copyarea cfb_copyarea, .fb_imageblit cfb_imageblit, }; static int st7735_fb_probe(struct spi_device *spi) { struct fb_info *info; info framebuffer_alloc(sizeof(struct st7735_par), spi-dev); info-var st7735_var; info-fix st7735_fix; info-fbops st7735_fb_ops; register_framebuffer(info); }这里有个性能优化的技巧ST7735是SPI接口刷屏速度有限。可以采用以下方法优化使用DMA传输减少CPU占用实现双缓冲机制只刷新屏幕变化区域在QT应用支持方面需要特别注意颜色格式的转换。ST7735通常使用RGB565格式而QT应用可能输出ARGB32格式void convert_argb32_to_rgb565(u32 *src, u16 *dst, int width, int height) { for (int i 0; i width * height; i) { u32 pixel src[i]; u8 r (pixel 16) 0xFF; u8 g (pixel 8) 0xFF; u8 b pixel 0xFF; dst[i] ((r 3) 11) | ((g 2) 5) | (b 3); } }5. 双屏同显实现方案在工业HMI等场景中经常需要实现主屏和副屏同时显示。基于Framebuffer架构我们可以这样实现首先为每个屏幕创建独立的fb设备static int st7735_probe(struct spi_device *spi) { /* 主屏 */ fb_info_primary framebuffer_alloc(...); register_framebuffer(fb_info_primary); /* 副屏 */ fb_info_secondary framebuffer_alloc(...); register_framebuffer(fb_info_secondary); }然后通过修改QT的环境变量来指定显示设备export QT_QPA_PLATFORMlinuxfb:fb/dev/fb1我在实际项目中实现过镜像显示和扩展显示两种模式。镜像显示相对简单只需要将相同的内容写入两个fb设备即可。扩展显示则需要处理不同屏幕分辨率的适配问题。调试双屏时建议先用fbset工具检查每个fb设备的状态fbset -i /dev/fb0 fbset -i /dev/fb16. 常见问题排查指南在ST7735驱动开发过程中我总结了一些常见问题及解决方法屏幕白屏但背光亮检查SPI通信是否正常用逻辑分析仪抓波形确认复位时序是否正确复位脉冲宽度要足够验证初始化序列是否匹配屏幕型号显示颜色异常检查颜色格式设置RGB565/RGB888确认Gamma校正参数测试基础颜色是否正常红、绿、蓝三原色屏幕闪烁或残影调整刷新频率检查电源稳定性优化刷屏算法避免全屏刷新SPI通信失败确认SPI模式CPOL/CPHA检查片选信号是否正常降低SPI时钟频率测试记得有一次调试时屏幕显示总是错位最后发现是屏幕行列地址设置命令理解有误。ST7735的CASET和RASET命令参数需要特别注意字节顺序。7. 性能优化实战对于嵌入式设备来说显示性能优化至关重要。以下是几个经过验证的优化方法部分刷新优化void st7735_partial_refresh(struct fb_info *info, int x1, int y1, int x2, int y2) { /* 设置刷新区域 */ write_reg(ST7735_CASET); write_data(x1 8); write_data(x1 0xFF); write_data(x2 8); write_data(x2 0xFF); write_reg(ST7735_RASET); write_data(y1 8); write_data(y1 0xFF); write_data(y2 8); write_data(y2 0xFF); /* 只传输变化区域数据 */ write_reg(ST7735_RAMWR); spi_write(only_changed_data, (x2-x1)*(y2-y1)*2); }DMA传输配置static int st7735_dma_write(struct spi_device *spi, void *buf, size_t len) { struct dma_async_tx_descriptor *desc; struct spi_transfer t { .tx_buf buf, .len len, }; struct spi_message m; spi_message_init(m); spi_message_add_tail(t, m); desc dmaengine_prep_slave_sg(spi-master-dma_tx, t.tx_sg.sgl, t.tx_sg.nents, DMA_MEM_TO_DEV, DMA_PREP_INTERRUPT); ... }帧率控制技巧使用VSYNC信号同步实现帧缓冲机制动态调整刷新率在智能家居项目中通过优化我们将ST7735的刷新率从15fps提升到了30fps界面流畅度明显改善。关键是把大块数据传输改为DMA方式并实现了脏矩形刷新算法。8. 高级功能扩展掌握了基础驱动后可以进一步实现一些高级功能屏幕旋转支持void st7735_set_rotation(struct st7735_par *par, int rotation) { u8 madctl 0; switch (rotation) { case 0: madctl ST7735_MADCTL_MX | ST7735_MADCTL_MY | ST7735_MADCTL_RGB; break; case 90: madctl ST7735_MADCTL_MY | ST7735_MADCTL_MV | ST7735_MADCTL_RGB; break; ... } write_reg(ST7735_MADCTL); write_data(madctl); }低功耗模式实现睡眠/唤醒命令动态调整背光亮度关闭不必要区域的刷新触摸屏集成通过SPI或I2C接口接入触摸控制器实现input子系统支持校准触摸坐标硬件加速利用SoC的2D加速引擎实现blit加速操作优化alpha混合等特效在工业HMI项目中我们实现了通过proc文件系统动态调整屏幕参数的功能static int st7735_proc_show(struct seq_file *m, void *v) { seq_printf(m, Brightness: %d\n, par-brightness); seq_printf(m, Rotation: %d\n, par-rotation); ... } static ssize_t st7735_proc_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { /* 解析用户输入并调整参数 */ ... }9. 项目实战经验去年负责的智能快递柜项目就用到了ST7735驱动开发。这个项目有几个特殊需求需要在-20℃~60℃环境下稳定工作屏幕需要支持常亮显示要求快速启动从开机到显示控制在1秒内针对这些需求我们做了以下优化改进了低温下的初始化序列增加了预热步骤实现了持久化显示模式避免屏幕长时间静态显示烧屏优化启动流程将屏幕初始化与系统启动并行化调试过程中发现一个有趣的现象在低温环境下屏幕的响应速度会变慢。通过实验我们找到了温度与初始化延迟时间的对应关系实现了动态调整int get_init_delay_by_temp(int temp) { if (temp 0) return 150; // 低于0度增加延迟 else if (temp 10) return 120; else return 100; }另一个经验是关于屏幕寿命的。工业设备通常需要24小时运行我们实现了以下保护机制像素位移防止烧屏动态背光调节定时屏幕刷新这些优化使得屏幕在连续工作一年后仍保持良好的显示效果。

更多文章