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引脚方向(输入/输出切换)

技术挑战

挑战维度

具体问题

影响级别

接口差异

半双工vs全双工SPI

时序兼容

新旧IC通信时序差异

电源管理

初始化序列电压要求不同

容错机制

ID读取失败处理

硬件架构

接口拓扑结构

┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│      MCU        │    │   驱动IC层       │    │   TFT显示屏     │
│                 │    │                 │    │                 │
│  ┌──────────┐   │    │  ┌──────────┐   │    │                 │
│  │ SPI控制器 │───┼─────→│ 命令解析器 │   │    │                 │
│  └──────────┘   │    │  └──────────┘   │    │                 │
│        │        │    │        │        │    │                 │
│  ┌──────────┐   │    │  ┌──────────┐   │    │  ┌──────────┐   │
│  │ GPIO控制  │───┼─────→│ 时序发生器 │───┼─────→│ 像素阵列  │   │
│  └──────────┘   │    │  └──────────┘   │    │  └──────────┘   │
└─────────────────┘    └─────────────────┘    └─────────────────┘

引脚功能详细定义

驱动芯片接口引脚:CS、SCL、SDA、RS、D/C

引脚

标准SPI

TFT驱动IC

方向

关键特性

SCL/SCK

时钟

时钟

输出

1-20MHz可变频率

SDA

MOSI/MISO

双向数据

双向

半双工,需方向控制

CS

片选

片选

输出

低电平有效,脉冲宽度>50ns

RS

N/A

寄存器选择

输出

0=命令,1=数据

D/C

N/A

数据/命令选择

输出

与RS功能类似

电路设计要点

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接口核心信号线

信号线

全称

方向

功能描述

SCLK

Serial Clock

Master→Slave

同步时钟信号

MOSI

Master Output Slave Input

Master→Slave

主设备发送,从设备接收

MISO

Master Input Slave Output

Slave→Master

从设备发送,主设备接收

CS/SS

Chip Select/Slave Select

Master→Slave

片选信号(低电平有效)

关键时序参数

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
          │    ↑    ↑    ↑    ↑
          └────┴────┴────┴────┘
              输出变化点(下降沿)

模式特性对比表

模式

CPOL

CPHA

时钟空闲

采样边沿

数据变化边沿

常用度

模式0

0

0

低电平

上升沿

下降沿

★★★★★

模式1

0

1

低电平

下降沿

上升沿

★★★☆☆

模式2

1

0

高电平

下降沿

上升沿

★☆☆☆☆

模式3

1

1

高电平

上升沿

下降沿

★★☆☆☆

时序关系可视化

四种模式时序对比:
         模式0         模式1         模式2         模式3
SCLK: __/‾\__/‾\__   __/‾\__/‾\__   ‾‾\__/‾‾\__   ‾‾\__/‾‾\__
         ↑    ↑         ↓    ↓         ↓    ↓         ↑    ↑
         采样点         采样点         采样点         采样点
         ↓    ↓         ↑    ↑         ↑    ↑         ↓    ↓
数据: XXXX D0  D1  XXXX D0  D1  XXXX D0  D1  XXXX D0  D1  XXXX


TFT驱动IC检测
https://www.ctuhub.top//archives/detection-of-LCD-driver-IC
作者
w-lzh
发布于
2025年12月23日
更新于
2025年12月23日
许可协议