TFT驱动IC检测
项目背景
现有量产项目中原TFT驱动IC(GC9107)因供应链问题停产,需紧急切换至替代型号(NV3023A),同时必须:
兼容已出货产品的维护需求
确保新旧硬件版本软件一致性
关键问题
通讯协议差异
问题发现:直接调用现有SPI接口无法读取IC ID
根本原因:
TFT驱动IC使用半双工SPI接口
SDA引脚为双向通信,需动态切换GPIO方向
非标准四线SPI接口(缺少独立MISO线)
硬件连接要求:
MCU的MOSI → 驱动IC的SDA
MCU的SCK → 驱动IC的SCL
模拟SPI调试
技术实现:
采用GPIO模拟SPI通信
动态控制SDA引脚方向(输入/输出切换)
技术挑战
硬件架构
接口拓扑结构
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ MCU │ │ 驱动IC层 │ │ TFT显示屏 │
│ │ │ │ │ │
│ ┌──────────┐ │ │ ┌──────────┐ │ │ │
│ │ SPI控制器 │───┼─────→│ 命令解析器 │ │ │ │
│ └──────────┘ │ │ └──────────┘ │ │ │
│ │ │ │ │ │ │ │
│ ┌──────────┐ │ │ ┌──────────┐ │ │ ┌──────────┐ │
│ │ GPIO控制 │───┼─────→│ 时序发生器 │───┼─────→│ 像素阵列 │ │
│ └──────────┘ │ │ └──────────┘ │ │ └──────────┘ │
└─────────────────┘ └─────────────────┘ └─────────────────┘引脚功能详细定义
驱动芯片接口引脚:CS、SCL、SDA、RS、D/C
电路设计要点
MCU侧连接方案:
MCU_SPI_MOSI ──╮
├─► 串联电阻(22Ω) ──► TFT_SDA
MCU_GPIO_IN ───╯
▲
方向控制信号(由MCU控制)
时钟线处理:
MCU_SPI_SCK ──► 串联电阻(22Ω) ──► TFT_SCL
│
上拉电阻(10kΩ) ──► VCC软件实现
核心思路:上电后通过SPI接口读取芯片ID,根据ID识别驱动IC类型,执行对应的初始化程序,最后进入正常业务显示流程。
系统流程图
程序流程:上电 → GPIO初始化 → 模拟SPI读取IC ID → ID识别 → 相应IC初始化 → 启用硬件SPI → 业务显示流程

ID读取协议
通信时序规范
读取NV3023A ID序列:
┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
│ CS↓ │ CMD:04h│ 方向切换 │ 读取周期1│ 读取周期2│ 读取周期3│ 读取周期4│ CS↑ │
└─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
│ │ │ │ │ │ │
└─────┘ └───────┴───────┴───────┴───────┘
≈500ns 每个周期≈400ns,总计约2.1μs模拟SPI关键代码
#if (SCREEN_DRIVER_CHECK == 1)
void tft_sda_2_output(void)
{
GPIO_DeInit(CW_GPIOB, GPIO_PIN_6);
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pins = GPIO_PIN_6;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_Init(CW_GPIOB, &GPIO_InitStruct);
}
void tft_sda_2_input(void)
{
GPIO_DeInit(CW_GPIOB, GPIO_PIN_6);
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pins = GPIO_PIN_6;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_Init(CW_GPIOB, &GPIO_InitStruct);
delay_us(10); // 等待GPIO稳定
}
// 软件模拟SPI
void tft_spi_sw_write(uint8_t dat)
{
tft_sda_2_output();
// 初始状态:时钟高电平(空闲状态)
PB08_SETHIGH();
for (uint8_t i = 0; i < 8; i++)
{
// 在时钟下降沿之前设置数据
if (dat & 0x80)
PB06_SETHIGH();
else
PB06_SETLOW();
delay_us(2); // 数据建立时间(tSU)
// 第一个边沿(下降沿):数据被LCD采样
PB08_SETLOW();
delay_us(5); // 时钟低电平时间
// 第二个边沿(上升沿):准备下一个数据位
PB08_SETHIGH();
delay_us(2);
dat <<= 1;
}
// 结束时保持时钟高电平(空闲状态)
PB08_SETHIGH();
}
uint8_t tft_spi_sw_read(void)
{
uint8_t dat = 0;
tft_sda_2_input();
delay_us(10); // 等待GPIO稳定
// 初始状态:时钟高电平(空闲状态)
PB08_SETHIGH();
for (uint8_t i = 0; i < 8; i++)
{
// 下降沿:主机从LCD读取数据(模式3的采样边沿)
PB08_SETLOW();
delay_us(2); // 等待数据稳定
// 在时钟低电平期间读取数据
dat <<= 1;
if (PB06_GETVALUE())
dat |= 0x01;
// 上升沿:LCD准备下一个数据位
PB08_SETHIGH();
delay_us(5);
}
// 结束时保持时钟高电平
PB08_SETHIGH();
// 恢复输出模式
tft_sda_2_output();
return dat;
}
/* 说明
1、虽然TFT 控制与FLASH控制都是使用SPI接口,但是TFT控制是半双工SPI,FLASH控制是全双工SPI。
2、SPI TFT屏使用的是半双工SPI,SDA引脚是双向的。这需要动态切换GPIO方向。
*/
uint32_t tft_ic_id_read(void)
{
uint32_t temp = 0, temp0 = 0, temp1 = 0, temp2 = 0;
// 确保初始状态
tft_sda_2_output();
PB08_SETHIGH(); // SCK高(空闲)
PB06_SETLOW(); // SDA低
// CS拉高(不选中)
LCD_0IN85_CS_1;
delay_us(100);
LCD_0IN85_DC_0; // 命令模式
delay_us(10);
LCD_0IN85_CS_0;
delay_us(10);
tft_spi_sw_write(0x04); // 发送指令,读取ID
LCD_0IN85_DC_1; // 数据模式
delay_us(10); // 等待IC准备数据
temp0 = tft_spi_sw_read();
delay_us(10);
temp1 = tft_spi_sw_read();
delay_us(10);
temp2 = tft_spi_sw_read();
delay_us(10);
LCD_0IN85_CS_1;
delay_us(100);
printf(".t-%X %X %X\n", temp0, temp1, temp2);
/*把数据组合起来,作为函数的返回值*/
temp = (temp0 << 16) | (temp1 << 8) | temp2;
// 恢复
tft_sda_2_output();
;
return temp;
}
#endif半双工SPI的问题
方向切换时序要求
理想切换时序:
┌───┐ ┌───┐
SCL ──┘ └───────────────────┘ └───
┌───────────────────────────┐
SDA(TX) ─┘ └────
│方向切换间隙│
┌───────────────────────────┐
SDA(RX) ─┘ └────硬件SPI复用冲突解决
分阶段初始化策略
阶段1:最小化GPIO初始化
├── 禁用所有SPI外设时钟
├── 配置SCL为通用推挽输出
├── 配置SDA为浮空输入(安全状态)
└── 保持其他引脚默认状态
阶段2:ID识别后重新配置
├── 释放GPIO控制权
├── 使能SPI外设时钟
├── 配置SPI模式(模式3,MSB first)
└── 设置SPI速度(兼容两种IC)硬件SPI配置详情
static void tft_spi_init(void)
{
SPI_InitTypeDef SPI_InitStructure = {0};
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; // SPI_Direction_2Lines_FullDuplex; //
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; // 主机模式
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; // 帧数据长度为8bit
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; // 时钟空闲电平为高
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; //
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; // 片选信号由SSI寄存器控制
SPI_InitStructure.SPI_BaudRatePrescaler = 0; // 0
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; // 最高有效位 MSB 收发在前
SPI_InitStructure.SPI_Speed = SPI_Speed_High;
SPI_Init(CW_SPI3, &SPI_InitStructure);
SPI_Cmd(CW_SPI3, ENABLE);
}实际调试发现
由于GC9107无法通过命令读取ID,系统采用以下容错机制:
成功读取到有效ID → 识别为对应芯片
读取失败或返回全0 → 默认识别为GC9107
GC9107的ID读取失败分析:
可能原因1:芯片在命令模式下不响应04h命令
可能原因2:需要特定的前置初始化序列
实际验证:直接读取返回全0,作为默认芯片处理
NV3023A的ID特征:
// ID结构分析:0x00199812
// 字节分解:00 19 98 12
// 含义推测:
// 00: 制造商代码(保留)
// 19: 芯片系列
// 98: 版本号
// 12: 批次号/校验码NV3023A的ID特征与数据手册描述并不一致,按照手册应该是0x00333025
特征不同正在与厂家确认中,也有可能SPI模式问题
附录-标准SPI的四种模式
SPI接口核心信号线
关键时序参数
t_SCK: 时钟周期
t_SU: 建立时间 (Setup Time) - 数据在时钟边沿前保持稳定的时间
t_HD: 保持时间 (Hold Time) - 数据在时钟边沿后保持稳定的时间
t_D: 传输延迟 (Propagation Delay)CPOL - Clock Polarity(时钟极性)
CPOL = 0: 时钟空闲时为低电平 (Low when idle)
____ ____
SCLK: |______| 空闲低电平
CPOL = 1: 时钟空闲时为高电平 (High when idle)
____ ____
SCLK: | |____| 空闲高电平CPHA - Clock Phase(时钟相位)
CPHA = 0: 数据在第一个时钟边沿采样
┌── 数据在第一个边沿采样
↓
SCLK: __/‾\__/‾\__/‾\__/‾\__
数据: xxxx D0 D1 D2 D3 xxxx
CPHA = 1: 数据在第二个时钟边沿采样
┌── 数据在第二个边沿采样
↓
SCLK: __/‾\__/‾\__/‾\__/‾\__
数据: xxxx D0 D1 D2 D3 xxxx模式0:CPOL=0, CPHA=0
大多数SPI从设备(Flash、EEPROM、传感器)
嵌入式系统中最常用的模式
通信速率要求不极致的应用
时钟极性: 空闲时低电平
采样时刻: 上升沿采样
输出时刻: 下降沿变化
详细时序图:
┌── 片选有效
↓
CS: ‾‾‾|________________|‾‾‾
│ │
SCLK: |__/‾\__/‾\__/‾\__|
│ │ │ │ │
┌───┐ ┌───┐ ┌───
│ 采样点 │ │ │
↓ ↓ ↓ ↓ ↓
MOSI: XXXXX D0 D1 D2 D3 XXXXX
MISO: XXXXX D0' D1' D2' D3' XXXXX
│ ↑ ↑ ↑ ↑
└────┴────┴────┴────┘
输出变化点(下降沿)模式1:CPOL=0, CPHA=1
部分ADC/DAC芯片
某些特殊的传感器
与模式0设备需要时序隔离的场景
时钟极性: 空闲时低电平
采样时刻: 下降沿采样
输出时刻: 上升沿变化
时序图:
CS: ‾‾‾|________________|‾‾‾
│ │
SCLK: |__/‾\__/‾\__/‾\__|
│ │ │ │ │
┌───┐ ┌───┐ ┌───
│ 输出点 │ │ │
↓ ↓ ↓ ↓ ↓
MOSI: XXXXX D0 D1 D2 D3 XXXXX
│ ↑ ↑ ↑ ↑
└────┴────┴────┴────┘
采样点(下降沿)
MISO: XXXXX D0' D1' D2' D3' XXXXX模式2:CPOL=1, CPHA=0
较少使用的模式
某些特定厂家的专有芯片
需要高电平空闲的噪声敏感环境
时钟极性: 空闲时高电平
采样时刻: 下降沿采样(注意:与模式1不同)
输出时刻: 上升沿变化
时序图:
CS: ‾‾‾|________________|‾‾‾
│ │
SCLK: |‾‾\__/‾‾\__/‾‾\__|
│ │ │ │ │
┌───┐ ┌───┐ ┌───
│ 输出点 │ │ │
↓ ↓ ↓ ↓ ↓
MOSI: XXXXX D0 D1 D2 D3 XXXXX
│ ↑ ↑ ↑ ↑
└────┴────┴────┴────┘
采样点(下降沿)
MISO: XXXXX D0' D1' D2' D3' XXXXX模式3:CPOL=1, CPHA=1
部分SD卡初始化阶段
某些无线模块(如nRF24L01)
需要高电平空闲且上升沿采样的设备
时钟极性: 空闲时高电平
采样时刻: 上升沿采样
输出时刻: 下降沿变化
时序图:
CS: ‾‾‾|________________|‾‾‾
│ │
SCLK: |‾‾\__/‾‾\__/‾‾\__|
│ │ │ │ │
┌───┐ ┌───┐ ┌───
│ 采样点 │ │ │
↓ ↓ ↓ ↓ ↓
MOSI: XXXXX D0 D1 D2 D3 XXXXX
MISO: XXXXX D0' D1' D2' D3' XXXXX
│ ↑ ↑ ↑ ↑
└────┴────┴────┴────┘
输出变化点(下降沿)模式特性对比表
时序关系可视化
四种模式时序对比:
模式0 模式1 模式2 模式3
SCLK: __/‾\__/‾\__ __/‾\__/‾\__ ‾‾\__/‾‾\__ ‾‾\__/‾‾\__
↑ ↑ ↓ ↓ ↓ ↓ ↑ ↑
采样点 采样点 采样点 采样点
↓ ↓ ↑ ↑ ↑ ↑ ↓ ↓
数据: XXXX D0 D1 XXXX D0 D1 XXXX D0 D1 XXXX D0 D1 XXXX