嵌入式Linux--基于SPI框架驱动ST7789 TFT屏(一)

张开发
2026/4/16 12:28:37 15 分钟阅读

分享文章

嵌入式Linux--基于SPI框架驱动ST7789 TFT屏(一)
1. 嵌入式Linux驱动开发基础在开始讲解ST7789 TFT屏的驱动开发之前我们先来了解一下嵌入式Linux驱动开发的基本概念。嵌入式Linux驱动本质上是一个内核模块它负责管理硬件设备并与用户空间程序进行交互。对于显示设备来说驱动需要完成初始化、配置和图像数据传输等工作。Linux内核提供了完善的SPI子系统框架开发者只需要按照框架规范实现设备树配置和驱动代码即可。SPI(Serial Peripheral Interface)是一种同步串行通信接口具有传输速度快、接线简单等特点非常适合连接像ST7789这样的显示控制器。在实际项目中我遇到过不少新手开发者对Linux驱动开发感到困惑。其实理解驱动开发的关键在于掌握三个核心概念设备树、字符设备框架和SPI子系统。设备树用来描述硬件连接信息字符设备框架提供用户空间访问接口SPI子系统则处理底层通信协议。2. 硬件准备与设备树配置2.1 硬件连接ST7789是一款常用的TFT液晶显示控制器支持240x240分辨率。在i.MX6ULL平台上我们需要通过SPI接口与ST7789通信。典型的硬件连接方式如下VCC接3.3V电源GND接地SCL接SPI时钟线(对应i.MX6ULL的SPI3_SCLK)SDA接SPI数据线(对应i.MX6ULL的SPI3_MOSI)RES接GPIO复位引脚(如GPIO1_IO01)DC接GPIO数据/命令选择引脚(如GPIO1_IO04)CS接SPI片选线(如GPIO1_IO20)在实际项目中我发现很多显示问题都源于硬件连接错误。建议在焊接前先用万用表检查线路通断确保所有信号线连接正确。2.2 设备树配置设备树是Linux内核识别硬件的关键。我们需要在imx6ull-alientek-emmc.dts文件中添加SPI控制器和GPIO的配置ecspi3 { fsl,spi-num-chipselects 1; cs-gpio gpio1 20 GPIO_ACTIVE_LOW; pinctrl-names default; pinctrl-0 pinctrl_ecspi3; status okay; spidev: ipsTft0 { compatible alientek,ipsTft; spi-max-frequency 100000000; reg 0; }; }; iomuxc { pinctrl_ipsRes: ipsRes { fsl,pins MX6UL_PAD_GPIO1_IO01__GPIO1_IO01 0x10B0 ; }; pinctrl_ipsDc: ipsDc { fsl,pins MX6UL_PAD_GPIO1_IO04__GPIO1_IO04 0x10B0 ; }; };这段配置做了以下几件事启用ECSPI3控制器配置GPIO1_IO20作为片选信号定义复位引脚(GPIO1_IO01)和数据/命令选择引脚(GPIO1_IO04)设置SPI设备节点指定兼容性字符串和最大时钟频率在实际调试中我发现SPI时钟频率设置过高会导致显示异常。建议开始时使用较低频率(如10MHz)稳定后再逐步提高。3. 驱动代码实现3.1 驱动框架搭建Linux字符设备驱动通常包含以下基本结构#include linux/module.h #include linux/spi/spi.h #define ipsTft_CNT 1 #define ipsTft_NAME ipsTft struct ipsTft_dev { dev_t devid; struct cdev cdev; struct class *class; struct device *device; struct device_node *nd; int major; void *private_data; int dc_gpio; int res_gpio; int cs_gpio; }; static struct ipsTft_dev ipsTftdev; static const struct file_operations ipsTft_ops { .owner THIS_MODULE, .open ipsTft_open, .release ipsTft_release, }; static int ipsTft_probe(struct spi_device *spi) { /* 设备号分配与注册 */ /* 字符设备初始化 */ /* 创建设备节点 */ /* GPIO初始化 */ /* SPI参数配置 */ return 0; } static int ipsTft_remove(struct spi_device *spi) { /* 资源释放 */ return 0; } /* SPI设备匹配表 */ static const struct of_device_id ipsTft_of_match[] { { .compatible alientek,ipsTft }, { } }; static struct spi_driver ipsTft_driver { .probe ipsTft_probe, .remove ipsTft_remove, .driver { .name ipsTft, .of_match_table ipsTft_of_match, }, }; module_spi_driver(ipsTft_driver);这个框架实现了最基本的SPI驱动结构包括设备探测、移除和操作函数集。在实际开发中我建议先确保这个框架能正常加载再逐步添加功能。3.2 SPI通信实现ST7789通过SPI接收命令和数据。每个传输前需要设置DC引脚电平低电平表示命令高电平表示数据。下面是核心通信函数static s32 ipsTft_write_regs(struct ipsTft_dev *dev, u8 *buf, u8 len) { int ret; struct spi_message m; struct spi_transfer *t; struct spi_device *spi dev-private_data; t kzalloc(sizeof(*t), GFP_KERNEL); t-tx_buf buf; t-len len; spi_message_init(m); spi_message_add_tail(t, m); ret spi_sync(spi, m); kfree(t); return ret; } void write_command(struct ipsTft_dev *dev, u8 cmd) { gpio_set_value(dev-dc_gpio, 0); ipsTft_write_regs(dev, cmd, 1); } void write_data(struct ipsTft_dev *dev, u8 data) { gpio_set_value(dev-dc_gpio, 1); ipsTft_write_regs(dev, data, 1); }在调试SPI通信时我习惯用逻辑分析仪抓取波形确认时序和电平是否符合ST7789的规格要求。常见的SPI模式为Mode 0或Mode 3需要与设备规格一致。4. ST7789初始化与显示控制4.1 初始化序列ST7789上电后需要发送一系列命令进行初始化。我们可以定义命令结构体数组struct spi_lcd_cmd { u8 reg_addr; u8 len; int delay_ms; }; static const struct spi_lcd_cmd cmds[] { {0x36, 1, 30}, // 内存数据访问控制 {0x3A, 1, 30}, // 颜色格式设置 {0xB2, 5, 30}, // porch设置 // 更多初始化命令... }; static const u8 spi_lcd_datas[] { 0x00, // 对应0x36命令的数据 0x05, // 对应0x3A命令的数据 0x0C, 0x0C, 0x00, 0x33, 0x33, // 对应0xB2命令的数据 // 更多初始化数据... };初始化函数会遍历这个数组依次发送每个命令和对应的数据void ipsTft_reginit(struct ipsTft_dev *dev) { int i, j, n 0; // 硬件复位 gpio_set_value(dev-res_gpio, 0); mdelay(20); gpio_set_value(dev-res_gpio, 1); mdelay(20); // 发送初始化序列 for (i 0; i ARRAY_SIZE(cmds); i) { write_command(dev, cmds[i].reg_addr); for (j 0; j cmds[i].len; j) { write_data(dev, spi_lcd_datas[n]); } if (cmds[i].delay_ms) { mdelay(cmds[i].delay_ms); } } // 清屏测试 LCD_Clear(dev, RED); }在实际项目中我发现不同批次的ST7789可能需要微调初始化序列。建议保留调试接口方便现场调整参数。4.2 显示区域设置与刷屏ST7789采用行列地址机制绘制前需要设置显示区域void Address_set(struct ipsTft_dev *dev, unsigned int x1, unsigned int y1, unsigned int x2, unsigned int y2) { write_command(dev, 0x2A); // 列地址设置 write_data(dev, x1 8); write_data(dev, x1); write_data(dev, x2 8); write_data(dev, x2); write_command(dev, 0x2B); // 行地址设置 write_data(dev, y1 8); write_data(dev, y1); write_data(dev, y2 8); write_data(dev, y2); write_command(dev, 0x2C); // 内存写入 }刷屏函数通过循环写入像素数据实现全屏填充void LCD_Clear(struct ipsTft_dev *dev, u16 Color) { unsigned int i, j; Address_set(dev, 0, 0, LCD_W-1, LCD_H-1); for (i 0; i LCD_W; i) { for (j 0; j LCD_H; j) { write_data(dev, Color 8); write_data(dev, Color); } } }为了提高刷屏效率可以考虑使用DMA传输或优化SPI时钟频率。在我的测试中将SPI时钟提升到50MHz可以使240x240屏幕的刷新率达到15fps左右。

更多文章