1. I.MX6ULL与ST7789 LCD屏幕的硬件适配基础
I.MX6ULL作为一款广泛应用于嵌入式领域的处理器,其灵活的SPI接口配置能力使其成为驱动小尺寸LCD屏幕的理想选择。ST7789控制器驱动的LCD屏幕(如常见的1.3寸240x240分辨率型号)因其性价比高、接口简单,在物联网设备和便携式仪器中广受欢迎。这种组合在实际项目中非常实用,特别是当你的开发板没有配备RGB接口屏幕时。
我第一次接触这个组合是在一个智能家居中控项目里。当时需要一块能够显示简单UI的屏幕,但开发板上的RGB接口已经被其他外设占用。ST7789的SPI接口只需要4根线(SCLK、MOSI、DC、CS)就能工作,RESET和背光控制还是可选的,这大大简化了硬件连接。实测下来,这种方案在保证显示效果的同时,对系统资源的占用也很低。
硬件连接时要注意几个关键点:
- SPI时钟线(SCLK)要尽量短,避免信号完整性问题
- 如果屏幕支持3.3V电平,可以直接与I.MX6ULL连接,否则需要电平转换
- 背光控制如果不需要调光,可以直接接3.3V
- RESET引脚最好保留,有些屏幕在上电时需要正确的复位时序
2. 设备树配置详解:SPI控制器与引脚复用
设备树配置是让LCD屏幕正常工作的第一步,也是最容易出错的地方。在I.MX6ULL上,我们需要配置两个关键部分:SPI控制器节点和引脚复用(pinctrl)。
先来看SPI控制器的配置。假设我们使用ECSPI1接口,在设备树文件(通常是arch/arm/boot/dts/100ask_imx6ull-14x14.dts)中找到ecspi1节点。除了基本的SPI配置外,ST7789需要特别注意三个GPIO:
&ecspi1 { pinctrl-names = "default"; pinctrl-0 = <&pinctrl_ecspi1>; fsl,spi-num-chipselects = <1>; cs-gpios = <&gpio4 24 GPIO_ACTIVE_LOW>; dc-gpios = <&gpio4 21 GPIO_ACTIVE_HIGH>; rst-gpio = <&gpio4 23 GPIO_ACTIVE_HIGH>; status = "okay"; spidev: st7789s@0 { compatible = "100ask,st7789s"; spi-max-frequency = <25000000>; reg = <0>; }; };这里有几个容易踩坑的地方:
- cs-gpios的GPIO_ACTIVE_LOW表示片选是低电平有效,这个要根据屏幕规格书确认
- dc-gpios用于区分命令和数据,必须配置正确
- spi-max-frequency不要超过屏幕支持的最大值,25MHz对ST7789已经足够
接下来是引脚复用配置。I.MX6ULL的引脚复用非常灵活,但也容易配置冲突:
pinctrl_ecspi1: spi_st7789s { fsl,pins = < MX6UL_PAD_CSI_DATA04__ECSPI1_SCLK 0x000010B1 MX6UL_PAD_CSI_DATA06__ECSPI1_MOSI 0x000010B1 MX6UL_PAD_CSI_DATA07__ECSPI1_MISO 0x000010B1 MX6UL_PAD_CSI_DATA03__GPIO4_IO24 0x000010B0 MX6UL_PAD_CSI_DATA00__GPIO4_IO21 0x000010B0 MX6UL_PAD_CSI_DATA02__GPIO4_IO23 0x000010B0 >; };配置完成后,建议用以下命令检查配置是否生效:
cat /proc/device-tree/soc/aips-bus@02000000/spba-bus@02000000/ecspi@02008000/status cat /proc/device-tree/soc/aips-bus@02000000/spba-bus@02000000/ecspi@02008000/st7789s@0/compatible3. SPI驱动框架与ST7789驱动实现
Linux内核的SPI驱动框架分为控制器驱动和设备驱动两部分。我们主要关注设备驱动,也就是ST7789的驱动实现。
首先需要定义spi_driver结构体,这是驱动与内核的接口:
static struct spi_driver st7789s_driver = { .probe = st7789s_probe, .remove = st7789s_remove, .driver = { .owner = THIS_MODULE, .name = ST7789S_NAME, .of_match_table = st7789s_of_match, }, .id_table = st7789s_id, };数据发送是SPI驱动的核心功能。ST7789需要区分命令和数据,这通过DC引脚实现:
static int st7789_write_reg(struct st7789_dev *dev, u8 reg, u8 *buf, int len) { int ret; struct spi_transfer t[2] = { { .tx_buf = ®, .len = 1, }, { .tx_buf = buf, .len = len, } }; struct spi_message m; gpio_set_value(dev->dc_gpio, 0); // 命令模式 spi_message_init(&m); spi_message_add_tail(&t[0], &m); ret = spi_sync(dev->spi, &m); if (buf && len) { gpio_set_value(dev->dc_gpio, 1); // 数据模式 spi_message_init(&m); spi_message_add_tail(&t[1], &m); ret = spi_sync(dev->spi, &m); } return ret; }在实际项目中,我发现ST7789的初始化序列非常重要。不同厂商的屏幕可能有细微差别,最好从供应商那里获取准确的初始化代码。一个典型的初始化序列如下:
static void st7789s_reginit(struct st7789_dev *dev) { st7789_write_reg(dev, ST7789_SWRESET, NULL, 0); mdelay(150); st7789_write_reg(dev, ST7789_SLPOUT, NULL, 0); mdelay(500); st7789_write_reg(dev, ST7789_COLMOD, "\x05", 1); mdelay(10); // 更多初始化命令... }4. 驱动调试与性能优化技巧
驱动开发中最耗时的往往是调试阶段。以下是我在实际项目中总结的几个调试技巧:
首先确保SPI通信基本正常:
# 检查SPI设备是否注册成功 ls /dev/spidev* # 使用spidev_test工具测试SPI通路 ./spidev_test -D /dev/spidev1.0 -v如果屏幕没有任何反应,按以下步骤排查:
- 检查电源和背光是否正常
- 用示波器检查SPI时钟和数据线是否有信号
- 确认RESET时序是否正确,有些屏幕需要特定的复位脉冲
- 检查DC引脚在发送命令和数据时的电平变化
性能优化方面,有几个关键点:
- 使用DMA传输可以显著提高刷新率
- 合理设置SPI时钟分频,过高的频率会导致数据错误
- 实现双缓冲机制可以减少屏幕闪烁
- 针对ST7789的特性,使用其局部刷新功能
一个实用的性能优化示例是改进刷屏函数:
static void st7789_fb_flush(struct st7789_dev *dev, u16 *buf) { u8 x[4] = {0}; // 设置刷新区域为全屏 x[0] = 0; x[1] = 0; x[2] = (dev->width-1)>>8; x[3] = (dev->width-1)&0xff; st7789_write_reg(dev, ST7789_CASET, x, 4); x[0] = 0; x[1] = 0; x[2] = (dev->height-1)>>8; x[3] = (dev->height-1)&0xff; st7789_write_reg(dev, ST7789_RASET, x, 4); // 启用内存写入 st7789_write_reg(dev, ST7789_RAMWR, NULL, 0); // 使用DMA传输帧缓冲 gpio_set_value(dev->dc_gpio, 1); spi_write(dev->spi, (u8 *)buf, dev->width * dev->height * 2); }在调试过程中,可能会遇到屏幕显示颜色不正确的问题。这通常是因为颜色格式不匹配,ST7789支持RGB565和RGB666等格式,需要确保驱动和屏幕初始化设置一致。