从设备树到驱动:在RK3566上构建ST7789的SPI子系统框架

张开发
2026/4/9 21:42:32 15 分钟阅读

分享文章

从设备树到驱动:在RK3566上构建ST7789的SPI子系统框架
1. 理解SPI屏幕驱动开发的核心逻辑当你第一次拿到ST7789这样的SPI屏幕时可能会被各种专业术语搞得晕头转向。其实整个过程可以简化为三个关键步骤硬件描述、驱动适配和功能实现。就像组装乐高积木一样我们需要先把各个零件硬件模块正确连接设备树描述然后告诉系统如何操作这些零件驱动开发最后让积木动起来屏幕显示。在RK3566开发板上SPI子系统就像是一条高速公路而ST7789屏幕则是这条路上的一个服务区。设备树的作用就是在地图上标注这个服务区的位置和特性而驱动程序则是服务区的操作手册。我去年在泰山派开发板上调试这块屏幕时发现很多问题都源于对这两个概念的混淆。2. 设备树配置实战详解2.1 解读Rockchip SPI控制器配置打开rk3568.dtsi文件你会看到类似这样的SPI控制器定义spi3: spife640000 { compatible rockchip,rk3066-spi; reg 0x0 0xfe640000 0x0 0x1000; interrupts GIC_SPI 106 IRQ_TYPE_LEVEL_HIGH; clocks cru CLK_SPI3, cru PCLK_SPI3; pinctrl-0 spi3m0_cs0 spi3m0_pins; status disabled; };这里有几个关键点需要注意reg属性指定了控制器的物理地址和地址范围clocks定义了SPI时钟源pinctrl-0确定了默认的引脚复用配置status初始为disabled表示需要手动启用2.2 添加ST7789设备节点在实际项目中我们需要在自己的板级设备树文件中启用并扩展这个配置spi3 { status okay; pinctrl-names default; pinctrl-0 spi3m1_cs0 spi3m1_pins; spi_lcd0 { compatible sitronix,st7789v; reg 0; spi-max-frequency 32000000; reset-gpios gpio3 RK_PC5 GPIO_ACTIVE_LOW; dc-gpios gpio3 RK_PC6 GPIO_ACTIVE_HIGH; rotation 90; }; };我在调试时踩过一个坑最初忘记指定pinctrl配置导致SPI信号无法正常输出。后来用示波器抓取波形才发现这个问题所以特别提醒大家检查引脚复用配置。3. Linux SPI驱动开发指南3.1 驱动框架搭建SPI驱动的核心是实现spi_driver结构体static struct spi_driver st7789_driver { .driver { .name st7789, .of_match_table st7789_of_match, }, .probe st7789_probe, .remove st7789_remove, .id_table st7789_ids, };匹配表定义如下static const struct of_device_id st7789_of_match[] { { .compatible sitronix,st7789v }, {} };3.2 关键函数实现probe函数是驱动的入口点这里我们需要完成硬件初始化static int st7789_probe(struct spi_device *spi) { struct st7789 *lcd; lcd devm_kzalloc(spi-dev, sizeof(*lcd), GFP_KERNEL); spi_set_drvdata(spi, lcd); /* 初始化GPIO */ lcd-reset devm_gpiod_get(spi-dev, reset, GPIOD_OUT_HIGH); lcd-dc devm_gpiod_get(spi-dev, dc, GPIOD_OUT_LOW); /* 硬件复位序列 */ gpiod_set_value(lcd-reset, 0); msleep(10); gpiod_set_value(lcd-reset, 1); msleep(120); /* 发送初始化命令 */ st7789_init_sequence(lcd); /* 注册framebuffer */ return st7789_fb_init(lcd); }4. 屏幕初始化与数据传输优化4.1 初始化序列实现ST7789需要特定的命令序列才能正常工作static void st7789_write_command(struct st7789 *lcd, u8 cmd) { gpiod_set_value(lcd-dc, 0); spi_write(lcd-spi, cmd, sizeof(cmd)); } static void st7789_init_sequence(struct st7789 *lcd) { st7789_write_command(lcd, MIPI_DCS_SOFT_RESET); msleep(150); st7789_write_command(lcd, MIPI_DCS_SET_DISPLAY_OFF); /* 设置屏幕方向 */ st7789_write_command(lcd, MIPI_DCS_SET_ADDRESS_MODE); st7789_write_data(lcd, lcd-rotation); /* 更多初始化命令... */ }4.2 高效数据传输技巧直接使用spi_write()发送大量数据效率很低我们可以采用DMA传输static void st7789_write_data_dma(struct st7789 *lcd, const u8 *data, size_t len) { struct spi_transfer xfer { .tx_buf data, .len len, }; struct spi_message msg; gpiod_set_value(lcd-dc, 1); spi_message_init(msg); spi_message_add_tail(xfer, msg); spi_sync(lcd-spi, msg); }在实际测试中使用DMA传输240x240的16位色图像速度比普通SPI传输快3-5倍。不过要注意DMA缓冲区需要特殊处理建议使用dma_alloc_coherent()分配内存。5. 调试技巧与常见问题5.1 必备调试工具在开发过程中这些工具特别有用spidev_testSPI通信测试工具devmem2直接读写寄存器示波器/逻辑分析仪检查信号质量内核printk添加调试输出5.2 典型问题解决方案屏幕无反应检查电源和背光确认复位时序正确用逻辑分析仪抓取SPI信号显示花屏检查像素格式设置确认传输字节序调整SPI时钟相位(polarity/phase)性能低下启用DMA传输提高SPI时钟频率优化传输数据打包记得我第一次调试时屏幕显示全是乱码花了整整两天才发现是字节序问题。后来在驱动中添加了详细的调试打印大大提高了排查效率。

更多文章