🏗️ 驱动架构
用户空间 (App)
↕ read/write/mmap ↕
Linux Framebuffer (/dev/fb0)
↕ fb_ops 回调 ↕
ILI9486 驱动 (ili9486.c)
↕ SPI传输 ↕
ILI9486 LCD 面板
三种驱动实现方式
| 方式 |
代码量 |
优点 |
缺点 |
| fbtft框架 |
200-300行 |
简单,内核已有框架 |
功能有限 |
| DRM/KMS |
1000+行 |
现代标准,硬件加速 |
复杂,学习曲线陡 |
| 字符设备 |
500+行 |
完全控制,灵活 |
需要自己处理所有细节 |
💡 推荐方案
使用fbtft框架,只需实现init函数和少量回调,代码量最少,可以快速上手。驱动文件已创建在 /root/work_materials/ili9486_driver.c
📡 SPI通信协议详解
时序图
SPI模式
| 模式 |
CPOL |
CPHA |
说明 |
| Mode 0 |
0 |
0 |
空闲SCK低,上升沿采样(推荐) |
| Mode 3 |
1 |
1 |
空闲SCK高,上升沿采样 |
关键代码
static int ili9486_write_cmd(struct ili9486_priv *priv, u8 cmd)
{
gpiod_set_value(priv->dc, 0);
return spi_write(priv->spi, &cmd, 1);
}
static int ili9486_write_data(struct ili9486_priv *priv, u8 data)
{
gpiod_set_value(priv->dc, 1);
return spi_write(priv->spi, &data, 1);
}
static int ili9486_write_data16(struct ili9486_priv *priv, u16 data)
{
u8 buf[2] = { data >> 8, data & 0xFF };
return ili9486_write_data_buf(priv, buf, 2);
}
🎬 ILI9486 初始化序列
初始化流程图
上电
↓
硬件复位 (RST)
↓
软复位 (0x01)
↓
退出睡眠 (0x11)
↓
配置寄存器
↓
开显示 (0x29)
MADCTL 旋转控制 (0x36)
| 旋转角度 |
MADCTL值 |
说明 |
| 0° |
0x48 |
竖屏,正常方向 |
| 90° |
0x28 |
横屏,向右旋转 |
| 180° |
0x88 |
竖屏,上下翻转 |
| 270° |
0xE8 |
横屏,向左旋转(默认) |
MADCTL位定义
Bit 7 (MY): 行地址顺序 0=自顶向下 1=自底向上
Bit 6 (MX): 列地址顺序 0=自左向右 1=自右向左
Bit 5 (MV): 行/列交换 0=正常 1=交换
Bit 4 (ML): 垂直刷新顺序 0=自顶向下 1=自底向上
Bit 3 (RGB): RGB/BGR顺序 0=RGB 1=BGR
像素格式 (0x3A)
| 值 |
格式 |
说明 |
| 0x55 |
RGB565 |
16位(常用) |
| 0x66 |
RGB666 |
18位 |
| 0x77 |
RGB888 |
24位 |
💻 完整驱动代码
📁 文件位置
驱动源码已保存到:/root/work_materials/ili9486_driver.c
项目文件结构
ili9486-driver/
├── ili9486.c ← 主驱动文件(完整代码)
├── Makefile ← 编译脚本
├── Kconfig ← 内核配置菜单
└── README.md ← 说明文档
驱动核心结构
struct ili9486_priv {
struct spi_device *spi;
struct gpio_desc *dc;
struct gpio_desc *reset;
struct gpio_desc *led;
u16 *fbmem;
int rotate;
int width;
int height;
};
static const struct of_device_id ili9486_of_match[] = {
{ .compatible = "ilitek,ili9486" },
{ }
};
关键函数
-
ili9486_write_cmd() - 发送命令
DC引脚拉低,发送命令字节
-
ili9486_write_data() - 发送数据
DC引脚拉高,发送参数或像素数据
-
ili9486_init_sequence() - 初始化序列
完整的ILI9486寄存器配置
-
ili9486_set_window() - 设置显示窗口
设置像素操作的区域
-
ili9486_fb_write() - 写屏回调
当framebuffer改变时调用
-
ili9486_probe() - 驱动入口
初始化GPIO、SPI、分配framebuffer
🌳 设备树配置
Orange Pi PC 设备树节点
&spi0 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&spi0_pins &spi0_cs_pin>;
cs-gpios = <&pio 0 2 GPIO_ACTIVE_LOW>;
ili9486@0 {
compatible = "ilitek,ili9486";
reg = <0>;
spi-max-frequency = <32000000>;
rotate = <270>;
bgr;
fps = <30>;
dc-gpios = <&pio 1 8 GPIO_ACTIVE_LOW>;
reset-gpios = <&pio 1 9 GPIO_ACTIVE_LOW>;
led-gpios = <&pio 1 7 GPIO_ACTIVE_HIGH>;
};
};
⚠️ 引脚映射
Orange Pi PC的GPIO编号与树莓派不同。需要参考全志H3的GPIO映射表,将LCD引脚正确连接到SPI和GPIO。
GPIO引脚参考
| LCD引脚 |
Orange Pi GPIO |
说明 |
| LCD_CS |
PA2 (SPI0_CS0) |
片选 |
| LCD_RS/DC |
PH8 |
数据/命令选择 |
| LCD_SCK |
PA5 (SPI0_SCK) |
SPI时钟 |
| LCD_SI/MOSI |
PA6 (SPI0_MOSI) |
SPI数据 |
| RST |
PH9 |
复位 |
| LED |
PH7 |
背光控制 |
LCD 3.5寸 SPI模块引脚图
LCD 3.5" TFT · ILI9486
13.3V●25V
3NC●45V
5NC●6GND
7NC●8NC
9GND●10NC
11TP_IRQ●12NC
13NC●14GND
15NC●16NC
173.3V●18LCD_RS
19LCD_SI●20GND
21TP_SO●22RST
23LCD_SCK●24LCD_CS
25GND●26TP_CS
MPI3501 模块背面 · 2×20排针接口
功能引脚说明
Pin 24 LCD_CS — LCD片选(低有效)
Pin 18 LCD_RS — 命令/数据选择
Pin 23 LCD_SCK — SPI时钟
Pin 19 LCD_SI — SPI数据输入(MOSI)
Pin 22 RST — 复位引脚
Pin 11 TP_IRQ — 触摸中断(按下拉低)
Pin 21 TP_SO — 触摸SPI数据输出(MISO)
Pin 26 TP_CS — 触摸芯片片选(低有效)
电源引脚
Pin 1, 17 — 3.3V 电源
Pin 2, 4 — 5V 电源
Pin 6, 9, 14, 20, 25 — GND
⚠️ 注意事项
- 此模块为RPi直插设计,26Pin排针直接插到树莓派GPIO
- LCD和触摸屏共用SPI MOSI/SCK,片选分开(LCD_CS/TP_CS)
- 使用前需安装LCD驱动(fbcp或自定义驱动)
- XPT2046触摸控制器需要独立的SPI MISO(TP_SO)
⚙️ 编译与测试
Makefile
obj-m := ili9486.o
KDIR := /path/to/linux-orangepi
CROSS_COMPILE := arm-linux-gnueabihf-
ARCH := arm
all:
$(MAKE) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) \
-C $(KDIR) M=$(PWD) modules
clean:
$(MAKE) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) \
-C $(KDIR) M=$(PWD) clean
编译测试步骤
-
安装交叉编译工具链
sudo apt-get install gcc-arm-linux-gnueabihf
-
编译驱动
make
-
传输到Orange Pi
scp ili9486.ko root@orangepi:~/
-
加载驱动
ssh root@orangepi
insmod ili9486.ko
-
检查framebuffer设备
ls -la /dev/fb*
dmesg | tail -20
测试显示
echo -n "Hello LCD!" > /dev/fb0
apt-get install fbset
fbset -a
fbgrab /tmp/screenshot.png
🔍 调试技巧
SPI通信调试
echo 8 > /proc/sys/kernel/printk
ls -la /dev/spidev*
cat /sys/class/spi_master/spi0/spi0.0/modalias
GPIO调试
cat /sys/kernel/debug/gpio
echo 8 > /sys/class/gpio/export
echo out > /sys/class/gpio/gpio8/direction
echo 1 > /sys/class/gpio/gpio8/value
常见问题排查
| 问题 |
可能原因 |
解决方法 |
| 白屏 |
未初始化或SPI通信失败 |
检查接线、SPI配置、初始化序列 |
| 花屏 |
时序问题或数据错误 |
降低SPI频率、检查时序 |
| 无显示 |
背光未开启 |
检查LED引脚 |
| 颜色错误 |
BGR/RGB顺序错误 |
修改MADCTL的RGB位 |
逻辑分析仪
🔧 推荐工具
使用Saleae或DSLogic抓取SPI波形,检查CS、SCK、MOSI、DC四根线的时序和数据是否正确。
🚀 优化方向
DMA传输
static void ili9486_fb_write_dma(struct fb_info *info,
int x, int y, int w, int h)
{
struct ili9486_priv *priv = info->par;
struct spi_transfer t = {
.tx_buf = priv->txbuf,
.len = w * h * 2,
.speed_hz = priv->spi->max_speed_hz,
};
struct spi_message m;
spi_message_init(&m);
spi_message_add_tail(&t, &m);
spi_sync(priv->spi, &m);
}
性能对比
| 方案 |
刷新率 |
CPU占用 |
说明 |
| 基础写屏 |
15 FPS |
80% |
简单实现 |
| DMA传输 |
30 FPS |
30% |
使用DMA |
| 部分刷新 |
60 FPS |
10% |
只更新变化区域 |
| 双缓冲 |
60 FPS |
5% |
前后台缓冲 |
更多优化
✅ 优化建议
- 使用ILI9486内置的硬件滚动功能
- 实现脏矩形检测,只刷新变化区域
- 利用双缓冲减少撕裂
- 优化SPI传输批量数据
🔍 LCD-show 代码分析
goodtft/LCD-show 项目代码架构分析,理解SPI LCD驱动的实现原理
整体架构
用户空间 App
↕ read/write/mmap ↕
Linux Framebuffer (/dev/fb0)
↕ fbcp 像素搬运 ↕
fbcp-ili9341 用户态程序
↕ 直接操作BCM2835寄存器 ↕
ILI9486 SPI LCD
💡 核心思路
fbcp 不是内核驱动,是用户态程序。读 /dev/fb0(HDMI帧缓冲)→ 对比前后帧差异 → 通过SPI写到LCD。绕过内核,直接mmap操作BCM2835的SPI寄存器。
目录结构
fbcp-ili9341/
├── fbcp-ili9341.cpp ← 主循环:读fb0 → diff对比 → 推送SPI
├── spi.cpp ← SPI通信层:mmap操作BCM2835寄存器
├── gpu.cpp ← GPU帧缓冲获取
├── diff.cpp ← 差分算法:只更新变化的像素
├── ili9486.cpp ← ILI9486初始化序列 ★你要的部分
├── ili9341.cpp ← ILI9341初始化
├── mpi3501.cpp ← MPI3501初始化
├── display.h ← 编译时选择屏幕型号
└── config.h ← GPIO引脚、SPI频率等配置
ILI9486 初始化序列(ili9486.cpp)
void InitILI9486() {
SET_GPIO(TFT_RESET_PIN);
usleep(120000);
CLEAR_GPIO(TFT_RESET_PIN);
usleep(120000);
SET_GPIO(TFT_RESET_PIN);
usleep(120000);
spi->clk = 34;
SPI_TRANSFER(0xB0, 0x00, 0x00);
SPI_TRANSFER(0x11);
usleep(120000);
SPI_TRANSFER(0x3A, 0x00, 0x55);
SPI_TRANSFER(0x20);
SPI_TRANSFER(0xC0, 0x00, 0x09, 0x00, 0x09);
SPI_TRANSFER(0xC1, 0x00, 0x41, 0x00, 0x00);
SPI_TRANSFER(0xC2, 0x00, 0x33);
SPI_TRANSFER(0xC5, 0x00, 0x00, 0x00, 0x36);
SPI_TRANSFER(0x36, 0x00, madctl);
SPI_TRANSFER(0xE0, ...);
SPI_TRANSFER(0xE1, ...);
SPI_TRANSFER(0xB6, 0x00, 0x00, 0x00, 0x02, 0x00, 0x3B);
SPI_TRANSFER(0x11);
usleep(120000);
SPI_TRANSFER(0x29);
SPI_TRANSFER(0x38);
SPI_TRANSFER(0x13);
spi->clk = SPI_BUS_CLOCK_DIVISOR;
}
SPI通信方式对比
| 项目 |
fbcp(用户态) |
fbtft(内核态) |
| SPI访问 |
mmap直接操作BCM2835寄存器 |
spi_write() 内核API |
| 帧缓冲 |
读/dev/fb0,自己搬运 |
注册fb_device,内核管理 |
| 性能 |
高(绕过内核) |
中(经过内核栈) |
| 复杂度 |
中等(~360行) |
简单(~200行) |
| 适用平台 |
仅BCM2835(RPi) |
任意Linux(含全志H3) |
| 初始化序列 |
完全相同 |
完全相同 |
⚠️ 你写Orange Pi PC驱动的要点
- ILI9486初始化序列直接从ili9486.cpp抄,一字不差
- SPI传输换成内核API:
spi_write(spi, buf, len)
- fbcp的mmap方式不适用全志H3,必须走内核fbtft框架
- GPIO操作换成全志的gpiod或sunxi-pinctrl
- 关键区别:fbcp用16位SPI总线宽度,fbtft默认8位
MADCTL 旋转控制详解
0x48
0x28
0x88
0xE8
uint8_t madctl = 0;
madctl |= MADCTL_BGR_PIXEL_ORDER;
madctl |= MADCTL_ROW_COLUMN_EXCHANGE;
madctl ^= MADCTL_COLUMN_ADDRESS_ORDER_SWAP | MADCTL_ROW_ADDRESS_ORDER_SWAP;