当前位置: 首页 > news >正文

手把手教你为i.MX6ULL开发板驱动1.3寸ST7789 TFT屏(含完整设备树与驱动代码)

i.MX6ULL开发板驱动1.3寸ST7789 TFT屏全流程实战指南

在嵌入式Linux开发中,显示设备的驱动往往是项目开发的关键环节之一。本文将详细介绍如何在i.MX6ULL开发板上驱动1.3寸ST7789 TFT屏幕的全过程,从硬件连接到设备树配置,再到驱动编写和测试,提供一套完整的解决方案。

1. 硬件准备与连接

在开始软件配置前,确保已准备好以下硬件组件:

  • i.MX6ULL开发板(如正点原子阿尔法)
  • 1.3寸ST7789驱动的TFT屏幕(240×240分辨率)
  • 杜邦线若干
  • 5V/3.3V电源适配器

ST7789通常通过SPI接口与主控通信,需要连接以下信号线:

TFT屏引脚开发板GPIO功能描述
VCC3.3V电源正极
GNDGND电源地
SCLSPI3_SCLK时钟信号
SDASPI3_MOSI数据输入
RESGPIO1_IO01复位信号
DCGPIO1_IO04数据/命令选择
CSGPIO1_IO20片选信号

硬件连接注意事项:

  • 确保电源电压匹配,ST7789通常工作在3.3V
  • 信号线长度不宜过长,避免信号完整性问题
  • 如果屏幕有背光控制,可连接到PWM输出引脚实现亮度调节

2. 设备树配置与编译

设备树是Linux内核识别硬件的重要配置文件,我们需要为SPI接口和GPIO添加相应节点。

2.1 修改设备树源文件

找到开发板对应的设备树文件(如imx6ull-alientek-emmc.dts),添加以下内容:

/* 在根节点下添加GPIO控制节点 */ / { ips_reset: ips-reset { compatible = "gpio-reset"; gpios = <&gpio1 1 GPIO_ACTIVE_LOW>; status = "okay"; }; ips_dc: ips-dc { compatible = "gpio-control"; gpios = <&gpio1 4 GPIO_ACTIVE_HIGH>; status = "okay"; }; }; /* 在iomuxc节点中添加引脚复用配置 */ &iomuxc { pinctrl_ips: ipsgrp { fsl,pins = < MX6UL_PAD_GPIO1_IO01__GPIO1_IO01 0x10B0 /* RESET */ MX6UL_PAD_GPIO1_IO04__GPIO1_IO04 0x10B0 /* DC */ MX6UL_PAD_UART2_CTS__GPIO1_IO20 0x10B0 /* CS */ >; }; }; /* 配置SPI3控制器 */ &ecspi3 { fsl,spi-num-chipselects = <1>; cs-gpios = <&gpio1 20 GPIO_ACTIVE_LOW>; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_ecspi3 &pinctrl_ips>; status = "okay"; st7789: st7789@0 { compatible = "sitronix,st7789"; spi-max-frequency = <50000000>; reg = <0>; reset-gpios = <&gpio1 1 GPIO_ACTIVE_LOW>; dc-gpios = <&gpio1 4 GPIO_ACTIVE_HIGH>; width = <240>; height = <240>; buswidth = <8>; fps = <30>; }; };

2.2 设备树编译与更新

配置完成后,需要编译设备树并更新到开发板:

# 编译设备树 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs # 将生成的dtb文件拷贝到开发板 scp arch/arm/boot/dts/imx6ull-alientek-emmc.dtb root@开发板IP:/boot/ # 在开发板上更新设备树 cp /boot/imx6ull-alientek-emmc.dtb /sys/firmware/devicetree/base/

验证设备树节点是否成功创建:

# 检查SPI设备节点 ls /sys/bus/spi/devices/spi3.0/ # 检查GPIO控制节点 ls /sys/class/gpio/

3. Linux驱动开发

ST7789驱动可以采用标准的SPI框架实现,下面展示关键部分的驱动代码。

3.1 驱动框架初始化

#include <linux/module.h> #include <linux/spi/spi.h> #include <linux/delay.h> #include <linux/gpio/consumer.h> #define DRIVER_NAME "st7789" struct st7789_data { struct spi_device *spi; struct gpio_desc *reset_gpio; struct gpio_desc *dc_gpio; u16 width; u16 height; }; static int st7789_write_command(struct st7789_data *st7789, u8 cmd) { int ret; struct spi_transfer xfer = { .len = 1, .tx_buf = &cmd, }; struct spi_message msg; gpiod_set_value(st7789->dc_gpio, 0); // 命令模式 spi_message_init(&msg); spi_message_add_tail(&xfer, &msg); ret = spi_sync(st7789->spi, &msg); return ret; } static int st7789_write_data(struct st7789_data *st7789, u8 *data, size_t len) { int ret; struct spi_transfer xfer = { .len = len, .tx_buf = data, }; struct spi_message msg; gpiod_set_value(st7789->dc_gpio, 1); // 数据模式 spi_message_init(&msg); spi_message_add_tail(&xfer, &msg); ret = spi_sync(st7789->spi, &msg); return ret; }

3.2 屏幕初始化序列

ST7789需要按照特定序列进行初始化,以下是关键初始化步骤:

static int st7789_init_sequence(struct st7789_data *st7789) { int ret = 0; // 硬件复位 gpiod_set_value(st7789->reset_gpio, 0); msleep(20); gpiod_set_value(st7789->reset_gpio, 1); msleep(120); // 发送初始化命令 const u8 init_cmds[] = { 0x36, 0x00, // MADCTL: Memory Data Access Control 0x3A, 0x05, // COLMOD: Interface Pixel Format 0xB2, 0x0C, 0x0C, 0x00, 0x33, 0x33, // PORCTRL: Porch Setting 0xB7, 0x35, // GCTRL: Gate Control 0xBB, 0x19, // VCOMS: VCOM Setting 0xC0, 0x2C, // LCMCTRL: LCM Control 0xC2, 0x01, // VDVVRHEN: VDV and VRH Command Enable 0xC3, 0x12, // VRHS: VRH Set 0xC4, 0x20, // VDVS: VDV Set 0xC6, 0x0F, // FRCTRL2: Frame Rate Control 0xD0, 0xA4, 0xA1, // PWCTRL1: Power Control 1 0xE0, // PVGAMCTRL: Positive Voltage Gamma Control 0xD0, 0x04, 0x0D, 0x11, 0x13, 0x2B, 0x3F, 0x54, 0x4C, 0x18, 0x0D, 0x0B, 0x1F, 0x23, 0xE1, // NVGAMCTRL: Negative Voltage Gamma Control 0xD0, 0x04, 0x0C, 0x11, 0x13, 0x2C, 0x3F, 0x44, 0x51, 0x2F, 0x1F, 0x1F, 0x20, 0x23, 0x21, // INVON: Display Inversion On 0x11, // SLPOUT: Sleep Out 0x29, // DISPON: Display On }; for (int i = 0; i < ARRAY_SIZE(init_cmds); ) { if (init_cmds[i] == 0xE0 || init_cmds[i] == 0xE1) { ret = st7789_write_command(st7789, init_cmds[i]); i++; ret |= st7789_write_data(st7789, (u8 *)&init_cmds[i], 14); i += 14; } else if (i < ARRAY_SIZE(init_cmds) - 1 && init_cmds[i+1] != 0xE0 && init_cmds[i+1] != 0xE1) { ret = st7789_write_command(st7789, init_cmds[i]); i++; ret |= st7789_write_data(st7789, (u8 *)&init_cmds[i], 1); i++; } else { ret = st7789_write_command(st7789, init_cmds[i]); i++; } if (ret < 0) { dev_err(&st7789->spi->dev, "Init sequence error at cmd 0x%02x\n", init_cmds[i-1]); return ret; } msleep(10); } return 0; }

3.3 实现帧缓冲接口

为了与Linux显示子系统集成,需要实现帧缓冲(fb)接口:

#include <linux/fb.h> static int st7789_fb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, u_int transp, struct fb_info *info) { // 设置颜色寄存器 return 0; } static int st7789_fb_blank(int blank_mode, struct fb_info *info) { // 控制屏幕空白模式 struct st7789_data *st7789 = info->par; switch (blank_mode) { case FB_BLANK_UNBLANK: st7789_write_command(st7789, 0x29); // DISPON break; case FB_BLANK_NORMAL: case FB_BLANK_VSYNC_SUSPEND: case FB_BLANK_HSYNC_SUSPEND: case FB_BLANK_POWERDOWN: st7789_write_command(st7789, 0x28); // DISPOFF break; } return 0; } static struct fb_ops st7789_fb_ops = { .owner = THIS_MODULE, .fb_setcolreg = st7789_fb_setcolreg, .fb_blank = st7789_fb_blank, .fb_fillrect = cfb_fillrect, .fb_copyarea = cfb_copyarea, .fb_imageblit = cfb_imageblit, }; static int st7789_probe(struct spi_device *spi) { struct fb_info *info; struct st7789_data *st7789; int ret = 0; // 分配帧缓冲结构 info = framebuffer_alloc(sizeof(*st7789), &spi->dev); if (!info) return -ENOMEM; st7789 = info->par; st7789->spi = spi; // 获取GPIO资源 st7789->reset_gpio = devm_gpiod_get(&spi->dev, "reset", GPIOD_OUT_LOW); if (IS_ERR(st7789->reset_gpio)) { ret = PTR_ERR(st7789->reset_gpio); goto err_fb_release; } st7789->dc_gpio = devm_gpiod_get(&spi->dev, "dc", GPIOD_OUT_LOW); if (IS_ERR(st7789->dc_gpio)) { ret = PTR_ERR(st7789->dc_gpio); goto err_fb_release; } // 初始化帧缓冲信息 info->fbops = &st7789_fb_ops; info->flags = FBINFO_FLAG_DEFAULT; info->pseudo_palette = &st7789->pseudo_palette; // 设置显示参数 info->var.xres = st7789->width; info->var.yres = st7789->height; info->var.xres_virtual = info->var.xres; info->var.yres_virtual = info->var.yres; info->var.bits_per_pixel = 16; info->var.red.offset = 11; info->var.red.length = 5; info->var.green.offset = 5; info->var.green.length = 6; info->var.blue.offset = 0; info->var.blue.length = 5; // 分配显示缓冲区 info->screen_size = info->var.xres * info->var.yres * info->var.bits_per_pixel / 8; info->screen_buffer = dma_alloc_coherent(&spi->dev, info->screen_size, &info->fix.smem_start, GFP_KERNEL); if (!info->screen_buffer) { ret = -ENOMEM; goto err_fb_release; } info->fix.smem_len = info->screen_size; info->fix.type = FB_TYPE_PACKED_PIXELS; info->fix.visual = FB_VISUAL_TRUECOLOR; info->fix.line_length = info->var.xres * info->var.bits_per_pixel / 8; // 初始化屏幕 ret = st7789_init_sequence(st7789); if (ret) goto err_dma_free; // 注册帧缓冲设备 ret = register_framebuffer(info); if (ret < 0) goto err_dma_free; spi_set_drvdata(spi, info); dev_info(&spi->dev, "fb%d: %s frame buffer device\n", info->node, info->fix.id); return 0; err_dma_free: dma_free_coherent(&spi->dev, info->screen_size, info->screen_buffer, info->fix.smem_start); err_fb_release: framebuffer_release(info); return ret; }

4. 驱动编译与测试

4.1 编写Makefile

obj-m := st7789.o KDIR := /path/to/kernel/source PWD := $(shell pwd) all: $(MAKE) -C $(KDIR) M=$(PWD) modules clean: $(MAKE) -C $(KDIR) M=$(PWD) clean

4.2 编译与加载驱动

# 交叉编译驱动 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- # 将驱动拷贝到开发板 scp st7789.ko root@开发板IP:/lib/modules/$(uname -r)/kernel/drivers/video/ # 在开发板上加载驱动 depmod -a modprobe st7789

4.3 测试驱动功能

编写简单的测试程序验证驱动功能:

#include <stdio.h> #include <fcntl.h> #include <sys/ioctl.h> #include <linux/fb.h> #include <sys/mman.h> int main() { int fbfd = open("/dev/fb0", O_RDWR); if (fbfd == -1) { perror("open fb device"); return -1; } // 获取屏幕信息 struct fb_var_screeninfo vinfo; ioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo); // 映射帧缓冲 char *fbp = mmap(0, vinfo.yres_virtual * vinfo.xres_virtual * 2, PROT_READ | PROT_WRITE, MAP_SHARED, fbfd, 0); // 绘制彩色条纹 for (int y = 0; y < vinfo.yres; y++) { for (int x = 0; x < vinfo.xres; x++) { int offset = (y * vinfo.xres + x) * 2; if (x < 80) { // 红色区域 *(unsigned short *)(fbp + offset) = 0xF800; } else if (x < 160) { // 绿色区域 *(unsigned short *)(fbp + offset) = 0x07E0; } else { // 蓝色区域 *(unsigned short *)(fbp + offset) = 0x001F; } } } munmap(fbp, 0); close(fbfd); return 0; }

编译并运行测试程序:

arm-linux-gnueabihf-gcc -o fb_test fb_test.c ./fb_test

5. 性能优化与调试技巧

5.1 SPI传输优化

ST7789支持最高80MHz的SPI时钟,但在实际应用中需要考虑信号完整性和功耗:

// 在驱动probe函数中添加 spi->max_speed_hz = 50000000; // 50MHz spi->mode = SPI_MODE_3; // CPOL=1, CPHA=1 spi_setup(spi);

优化建议:

  • 使用DMA传输减少CPU开销
  • 批量发送像素数据而非单像素传输
  • 合理使用双缓冲技术减少画面撕裂

5.2 常见问题排查

问题1:屏幕无显示

  • 检查电源和背光是否正常
  • 用逻辑分析仪确认SPI信号
  • 验证复位时序是否正确
  • 检查设备树配置是否生效

问题2:显示花屏

  • 降低SPI时钟频率测试
  • 检查数据/命令(DC)信号时序
  • 确认颜色格式配置(通常为RGB565)

问题3:刷新率低

  • 优化区域刷新而非全屏刷新
  • 使用硬件加速功能
  • 检查SPI总线是否被其他设备占用

5.3 高级功能实现

部分刷新(Partial Refresh)

void st7789_set_window(struct st7789_data *st7789, u16 x1, u16 y1, u16 x2, u16 y2) { u8 buf[4]; st7789_write_command(st7789, 0x2A); // CASET buf[0] = x1 >> 8; buf[1] = x1 & 0xFF; buf[2] = x2 >> 8; buf[3] = x2 & 0xFF; st7789_write_data(st7789, buf, 4); st7789_write_command(st7789, 0x2B); // RASET buf[0] = y1 >> 8; buf[1] = y1 & 0xFF; buf[2] = y2 >> 8; buf[3] = y2 & 0xFF; st7789_write_data(st7789, buf, 4); st7789_write_command(st7789, 0x2C); // RAMWR }

睡眠模式与低功耗

void st7789_enter_sleep(struct st7789_data *st7789) { st7789_write_command(st7789, 0x10); // SLPIN msleep(120); // 等待屏幕完全进入睡眠 } void st7789_exit_sleep(struct st7789_data *st7789) { st7789_write_command(st7789, 0x11); // SLPOUT msleep(120); // 等待屏幕完全唤醒 }

通过以上完整的开发流程,开发者可以成功在i.MX6ULL开发板上驱动ST7789 TFT屏幕,并实现高性能的图形显示功能。实际项目中,还可以进一步优化驱动性能,添加触摸支持等功能,打造更完善的嵌入式显示解决方案。

http://www.jsqmd.com/news/775727/

相关文章:

  • 在树莓派4B(ARM64)上源码编译PyQt5完整流程:从Python3.7到解决Qt::ItemDataRole编译错误
  • 程序员提效神器:Gemini3.1Pro自动生成代码注释与文档
  • 透明背景图片制作方法大全:从零基础到高效批量处理
  • 【AISMM+ESG融合实践手册】:全球仅12家通过奇点认证的企业都在用的6步嵌入法(附ISO/IEC 42001映射表)
  • 如何为每个Android应用独立设置虚拟位置?FakeLocation精准位置控制方案
  • Qdrant向量数据库MCP服务器:AI智能体标准化工具集成指南
  • CoPaw:开源个人AI工作站部署与实战指南
  • 百度网盘解析工具完整指南:告别限速下载的终极方案
  • ARM调试器在SoC开发中的核心价值与应用实践
  • 如何在Zotero中实现文献阅读进度可视化和智能管理?终极指南
  • 解锁碧蓝航线全自动游戏体验:你的智能航海助手
  • 科研图表数据提取终极指南:如何用WebPlotDigitizer高效获取隐藏数据?
  • SynthID-Image:不可见数字水印技术解析与实践
  • 多终端命令历史实时同步工具multicli的设计与部署指南
  • 为什么92%的AI厂商误读AISMM?奇点大会闭门报告泄露:市场定位错配导致ROI下降47%的实证数据
  • WarcraftHelper完整指南:魔兽争霸III游戏优化终极教程
  • 终极跨平台硬件调优指南:Universal x86 Tuning Utility如何释放你的Intel/AMD设备全部潜力
  • 多智能体协作平台AgentLayer:从架构设计到工程实践
  • Scroll Reverser终极指南:揭秘macOS滚动方向深度定制技术
  • PotPlayer字幕翻译终极指南:免费实现实时双语字幕的完整教程
  • GDScript代码质量工具链:从格式化到静态分析的工程实践
  • Windows全局钩子与透明窗口实现鼠标光标高亮器技术解析
  • 如何快速掌握Jasminum:面向中文研究者的Zotero终极解决方案
  • Sorbetto:为Ruby开发者打造的VS Code增强插件,提升Sorbet开发体验
  • XXMI启动器:一站式二次元游戏模组管理终极指南,告别繁琐手动配置
  • ClipTalk:基于Go的短视频去水印与语音转文字API服务实战
  • 开源工具token-usage-ui:可视化监控LLM API Token用量与成本
  • WarcraftHelper开源工具终极指南:魔兽争霸III游戏优化完整教程
  • 如何免费解锁WeMod Pro功能:Wand-Enhancer终极本地增强指南
  • LLM动态干预技术:实时调控与合规实践