eefocus_3891719 发表于 4 天前

【Avnet | NXP FRDM-MCXN947试用活动】测评4--FLEXIO_SPI 驱动TFT LCD

本帖最后由 eefocus_3891719 于 2024-11-19 10:54 编辑

# TFT LCD 模组介绍

模组名字为 **SPI_Module_MSP3323**,驱动芯片为 **ILI9341**,屏幕尺寸为 `240x320` 像素,自带GRAM。通过4线SPI驱动,可以发送数据和命令也可以读取屏幕的IC参数甚至像素点颜色。

模组背面

!(https://www.eefocus.com/forum/data/attachment/forum/202411/18/113611f9cd4tk9l5uz3cav.png)

模组管脚

!(https://www.eefocus.com/forum/data/attachment/forum/202411/18/113622dfnbksgbbzwcmtdd.png)

# MCXN947 的接口

当前只移植显示接口,只需要关注显示屏相关的引脚。

MCXN947 驱动 TFT LCD 的方式为4线 SPI,选择 FLEXIO_SPI_EDMA 方式驱动。 FLEXIO_SPI 相关的管脚为如下的 **FLEXIO_Dxx**。

| 编号| 显示屏      | 引脚功能说明                           | MCXN947            |
| --- | ---------- | ---------------------------------- | -------------------- |
| 1   | VCC      | 液晶屏电源正                           |                      |
| 2   | GND      | 液晶屏电源地                           |                      |
| 3   | LCD_CS   | 液晶屏片选控制信号,低电平有效                  | J8.28 -- FLEXIO0_D31 |
| 4   | LCD_RST    | 液晶屏复位控制信号,低电平复位                  | J3.1               |
| 5   | LCD_RS   | 液晶屏命令/数据选择控制信号;1 -- 数据;0 -- 命令 | J3.3               |
| 6   | SDI (MOSI) | SPI_MOSI 写数据信号                     | J8.25 -- FLEXIO0_D28 |
| 7   | SCK      | SPI_SCLK                           | J8.27 -- FLEXIO0_D30 |
| 8   | LED      | 液晶屏背光控制信号(可以PWM控制亮度;不需要可以不接)       | J3.5               |
| 9   | SDO(MISO)| SPI_MISO 读数据信号                     | J8.26 -- FLEXIO0_D29 |
| 10| CTP_SCL    | 电容触摸屏IIC总线时钟信号                     |                      |
| 11| CTP_RST    | 电容触摸屏复位控制信号,低电平复位                  |                      |
| 12| CTP_SDA    | 电容触摸屏IIC总线数据信号                     |                      |
| 13| CTP_INT    | 电容触摸屏IIC总线触摸中断信号,产生触摸时输入低电平到主控   |                      |
| 14| SD_CS      | SD卡片选控制信号,低电平有效(不使用SD卡功能时,可以不接)    |                      |

## FLEXIO 介绍

MCXN947 只有一个 FLEXIO 模块,即 **FLEXIO0**。**FLEXIO** 是一个高度可配置的模块,它提供了:

1. 模拟各种串行或者并行通信协议;
2. 灵活的16位定时器,支持各种触发、复位、使能和禁用条件;
3. 可编程逻辑块,允许实现片上的数字逻辑功能,并且可配置内部和外部模块的交互;
4. 可编程状态机,用于从CPU中卸载基本的系统控制功能;

## FLEXIO 框图

下图提供了 FLEXIO 计时器和移位寄存器配置的高层次总览。

FLEXIO 使用移位器、计时器和外部触发器来讲数据移入或移除FLEXIO。如方框图所示,计时器控制这个数据移位的时间,您可以将计时器配置为使用通用计时器功能,外部触发器或各种其他条件来决定控制逻辑。

!(https://www.eefocus.com/forum/data/attachment/forum/202411/18/113639zzhf7t1ipy69ef8q.png)

## FLEXIO 特性

1. 具有传输、接收、数据匹配、逻辑和状态模式的32位移位寄存器阵列:
   1. 支持连续数据传输的双缓冲移位操作;
   2. 支持大块数据传输的移位连接;
   3. 支持自动启动和停止位生成;
   4. 1,2,4,8,16或者32为多移位宽度的并行接口支持;
   5. 中断,DMA或者轮询传输和接收操作;
2. 高度灵活的16位定时器,支持各种内部或者外部触发,重置,启用和禁用条件:
   1. 可编程波特率独立于总线时钟频率,并支持在停止模式器件的异步操作;
   2. 可编程逻辑模式,用于集成外部数字逻辑功能,或组合引脚,移位器,或定时器功能,以产生复杂的输出;
   3. 可编程状态机,用于从CPU卸载基本的系统控制功能,支持最多8个状态,8个输出,每个状态和3个可选输入;
3. 集成的通用I/O寄存器和引脚上升或下降的边缘中断,以简化软件支持;
4. 支持广泛的协议,包括但不限于:I2C, SPI, I2S, Camera IF, Motorola 68K 或 Intel 8080 bus, PWM 波形发生器,输入捕获(脉冲边缘间隔测量),如 **SENT**

## 管脚配置

当前只需要点亮LCD,只需要配置显示相关的管脚,如下图所示:

1. 在MCUXpress Config Tools 中新建一个功能组,命名为 **TFT_LCD_Init**,
2. 设置管脚路由信息,并修改各个管脚的标志符;

!(https://www.eefocus.com/forum/data/attachment/forum/202411/18/113653jn8anzfd1jmun00z.png)

- 其中 **LCD_RST**, **LCD_RS**, **LCD_LED** 都是 GPIO Output,分别对应 **J3.1**, **J3.3**, **J3.5**。
- FLEXIO 模拟的 SPI 管脚分别是 **J8.27**, **J8.28**, **J8.26**, **J8.25**。

# 移植

## 移植思路

SPI 接口驱动 TFT LCD 主要设计到3个控制管脚的输出,SPI发送、读取8比特数据。

### 3个GPIO控制管脚

都初始化为 GPIO OUTPUT。

- 其中 **LCD_LED** 是背光管脚,拉高即点亮屏幕,拉低熄屏。当前只需要初始化时输出高电平即可;
- 其中 **LCD_RS** 是数据、命令选择功能,需要提供管脚的电平设置功能,提供两个宏定义即可;
- 其中 **LCD_RST** 是 LCD 复位管脚,输出低电平表示复位,提供两个宏定义即可;

### SPI管脚

初始化 FLEXIO0 的4个管脚,然后初始化 **FLEXIO_SPI**,并关联这4个管脚。

## 关键代码

### GPIO 控制管脚

下面的 `port_LCD_CtrlPin_Init()` 函数是我新增的,仅仅设置 **LCD_LED** 输出高电平,因为这3个管脚的初始化已经由 **MCUXpresso Config Tools** 配置好并生成了初始化代码,见下方的 `TFT_LCD_Init()` 函数。

```c
/**
* @brief LCD 控制管脚初始化
* LCD_RST --> J3.1 P2_0复位管脚:低电平复位
* LCD_RS--> J3.3 P1_22 命令数据选择管脚:高电平--数据;低电平--命令
* LCD_LED --> J3.5 P2_3背光管脚:高电平点亮,也可以 PWM 调节亮度
*
* @param
*/
void port_LCD_CtrlPin_Init(void)
{
        // 这3个管脚已经在 BOARD_InitBootPins() 中初始化了

        LCD_LED(1);
}
```

```c
/* FUNCTION ************************************************************************************************************
*
* Function Name : TFT_LCD_Init
* Description   : Configures pin routing and optionally pin electrical features.
*
* END ****************************************************************************************************************/
void TFT_LCD_Init(void)
{
    /* Enables the clock for GPIO1: Enables clock */
    CLOCK_EnableClock(kCLOCK_Gpio1);
    /* Enables the clock for GPIO2: Enables clock */
    CLOCK_EnableClock(kCLOCK_Gpio2);
    /* Enables the clock for PORT1: Enables clock */
    CLOCK_EnableClock(kCLOCK_Port1);
    /* Enables the clock for PORT2: Enables clock */
    CLOCK_EnableClock(kCLOCK_Port2);
    /* Enables the clock for PORT4: Enables clock */
    CLOCK_EnableClock(kCLOCK_Port4);

    gpio_pin_config_t LCD_RS_config = {
      .pinDirection = kGPIO_DigitalOutput,
      .outputLogic = 0U
    };
    /* Initialize GPIO functionality on pin PIO1_22 (pin L4)*/
    GPIO_PinInit(TFT_LCD_LCD_RS_GPIO, TFT_LCD_LCD_RS_PIN, &LCD_RS_config);

    gpio_pin_config_t LCD_RST_config = {
      .pinDirection = kGPIO_DigitalOutput,
      .outputLogic = 0U
    };
    /* Initialize GPIO functionality on pin PIO2_0 (pin H2)*/
    GPIO_PinInit(TFT_LCD_LCD_RST_GPIO, TFT_LCD_LCD_RST_PIN, &LCD_RST_config);

    gpio_pin_config_t LCD_LED_config = {
      .pinDirection = kGPIO_DigitalOutput,
      .outputLogic = 0U
    };
    /* Initialize GPIO functionality on pin PIO2_3 (pin J3)*/
    GPIO_PinInit(TFT_LCD_LCD_LED_GPIO, TFT_LCD_LCD_LED_PIN, &LCD_LED_config);

    /* PORT1_22 (pin L4) is configured as PIO1_22 */
    PORT_SetPinMux(TFT_LCD_LCD_RS_PORT, TFT_LCD_LCD_RS_PIN, kPORT_MuxAlt0);

    PORT1->PCR = ((PORT1->PCR &
                     /* Mask bits to zero which are setting */
                     (~(PORT_PCR_DSE_MASK | PORT_PCR_IBE_MASK)))

                      /* Drive Strength Enable: High. */
                      | PORT_PCR_DSE(PCR_DSE_dse1)

                      /* Input Buffer Enable: Enables. */
                      | PORT_PCR_IBE(PCR_IBE_ibe1));

    /* PORT2_0 (pin H2) is configured as PIO2_0 */
    PORT_SetPinMux(TFT_LCD_LCD_RST_PORT, TFT_LCD_LCD_RST_PIN, kPORT_MuxAlt0);

    PORT2->PCR = ((PORT2->PCR &
                      /* Mask bits to zero which are setting */
                      (~(PORT_PCR_DSE_MASK | PORT_PCR_IBE_MASK)))

                     /* Drive Strength Enable: High. */
                     | PORT_PCR_DSE(PCR_DSE_dse1)

                     /* Input Buffer Enable: Enables. */
                     | PORT_PCR_IBE(PCR_IBE_ibe1));

    /* PORT2_3 (pin J3) is configured as PIO2_3 */
    PORT_SetPinMux(TFT_LCD_LCD_LED_PORT, TFT_LCD_LCD_LED_PIN, kPORT_MuxAlt0);

    PORT2->PCR = ((PORT2->PCR &
                      /* Mask bits to zero which are setting */
                      (~(PORT_PCR_DSE_MASK | PORT_PCR_IBE_MASK)))

                     /* Drive Strength Enable: High. */
                     | PORT_PCR_DSE(PCR_DSE_dse1)

                     /* Input Buffer Enable: Enables. */
                     | PORT_PCR_IBE(PCR_IBE_ibe1));

    /* PORT4_20 (pin T10) is configured as FLEXIO0_D28 */
    PORT_SetPinMux(TFT_LCD_LCD_MOSI_PORT, TFT_LCD_LCD_MOSI_PIN, kPORT_MuxAlt6);

    PORT4->PCR = ((PORT4->PCR &
                     /* Mask bits to zero which are setting */
                     (~(PORT_PCR_DSE_MASK | PORT_PCR_IBE_MASK)))

                      /* Drive Strength Enable: High. */
                      | PORT_PCR_DSE(PCR_DSE_dse1)

                      /* Input Buffer Enable: Enables. */
                      | PORT_PCR_IBE(PCR_IBE_ibe1));

    /* PORT4_21 (pin T11) is configured as FLEXIO0_D29 */
    PORT_SetPinMux(TFT_LCD_LCD_MISO_PORT, TFT_LCD_LCD_MISO_PIN, kPORT_MuxAlt6);

    PORT4->PCR = ((PORT4->PCR &
                     /* Mask bits to zero which are setting */
                     (~(PORT_PCR_DSE_MASK | PORT_PCR_IBE_MASK)))

                      /* Drive Strength Enable: High. */
                      | PORT_PCR_DSE(PCR_DSE_dse1)

                      /* Input Buffer Enable: Enables. */
                      | PORT_PCR_IBE(PCR_IBE_ibe1));

    /* PORT4_22 (pin T12) is configured as FLEXIO0_D30 */
    PORT_SetPinMux(TFT_LCD_LCD_SCK_PORT, TFT_LCD_LCD_SCK_PIN, kPORT_MuxAlt6);

    PORT4->PCR = ((PORT4->PCR &
                     /* Mask bits to zero which are setting */
                     (~(PORT_PCR_DSE_MASK | PORT_PCR_IBE_MASK)))

                      /* Drive Strength Enable: High. */
                      | PORT_PCR_DSE(PCR_DSE_dse1)

                      /* Input Buffer Enable: Enables. */
                      | PORT_PCR_IBE(PCR_IBE_ibe1));

    /* PORT4_23 (pin U12) is configured as FLEXIO0_D31 */
    PORT_SetPinMux(TFT_LCD_LCD_CS_PORT, TFT_LCD_LCD_CS_PIN, kPORT_MuxAlt6);

    PORT4->PCR = ((PORT4->PCR &
                     /* Mask bits to zero which are setting */
                     (~(PORT_PCR_DSE_MASK | PORT_PCR_IBE_MASK)))

                      /* Drive Strength Enable: High. */
                      | PORT_PCR_DSE(PCR_DSE_dse1)

                      /* Input Buffer Enable: Enables. */
                      | PORT_PCR_IBE(PCR_IBE_ibe1));
}
```

并提供宏定义实现 GPIO 管脚的输出高低电平功能:

```c
// 硬件 CS,此处忽略
#define LCD_CS_SET
#define LCD_CS_CLR

// 数据、命令选择
#define LCD_RS_SET         GPIO_PinWrite(TFT_LCD_LCD_RS_GPIO, TFT_LCD_LCD_RS_GPIO_PIN, 1)
#define LCD_RS_CLR         GPIO_PinWrite(TFT_LCD_LCD_RS_GPIO, TFT_LCD_LCD_RS_GPIO_PIN, 0)

#define LCD_RST_SET          GPIO_PinWrite(TFT_LCD_LCD_RST_GPIO, TFT_LCD_LCD_RST_GPIO_PIN, 1)
#define LCD_RST_CLR          GPIO_PinWrite(TFT_LCD_LCD_RST_GPIO, TFT_LCD_LCD_RST_GPIO_PIN, 0)
```

### FLEXIO_SPI 初始化

对应的管脚路由已经在 `TFT_LCD_Init()` 中配置好了,此处仅需要初始化 **FLEXIO_SPI** 并关联对应的4个管脚。

#### 全局宏定义和变量

```c
/*******************************************************************************
* Definitions
******************************************************************************/
#define BOARD_FLEXIO_BASE      (FLEXIO0)
#define FLEXIO_SPI_MOSI_PIN    28U
#define FLEXIO_SPI_MISO_PIN    29U
#define FLEXIO_SPI_SCK_PIN   30U
#define FLEXIO_SPI_CSn_PIN   31U
#define FLEXIO_CLOCK_FREQUENCY CLOCK_GetFlexioClkFreq()

#define EXAMPLE_FLEXIO_SPI_DMA_BASEADDR DMA0
#define FLEXIO_SPI_TX_DMA_CHANNEL       (0U)
#define FLEXIO_SPI_RX_DMA_CHANNEL       (1U)
#define FLEXIO_TX_SHIFTER_INDEX         0U
#define FLEXIO_RX_SHIFTER_INDEX         2U
#define EXAMPLE_TX_DMA_SOURCE         kDma0RequestMuxFlexIO0ShiftRegister0Request
#define EXAMPLE_RX_DMA_SOURCE         kDma0RequestMuxFlexIO0ShiftRegister2Request

#define FLEXIO_SPI_BAUD_HIGH            (1000000 * 25)
#define FLEXIO_SPI_BAUD_LOW             (1000000 * 1)

/*******************************************************************************
* Variables
******************************************************************************/

static flexio_spi_master_edma_handle_t g_spiHandle;
static edma_handle_t txHandle;
static edma_handle_t rxHandle;
FLEXIO_SPI_Type spiDev;
flexio_spi_master_config_t userConfig;
volatile bool completeFlag = false;


static void spi_master_completionCallback(FLEXIO_SPI_Type *base,
                                          flexio_spi_master_edma_handle_t *handle,
                                          status_t status,
                                          void *userData)
{
    if (status == kStatus_Success)
    {
      completeFlag = true;
    }
}
```

#### FLEXIO_SPI 初始化函数

初始化 **FLEXIO_SPI** 并初始化 EDMA。

```c
/**
* @brief SPI 初始化
*
*/
void port_LCD_SPI_Init(void)
{
        uint8_t i                  = 0;
        uint8_t err                = 0;

        dma_request_source_t dma_request_source_tx;
        dma_request_source_t dma_request_source_rx;
        edma_config_t config;

        /* attach PLL0 to FLEXIO */
        CLOCK_SetClkDiv(kCLOCK_DivFlexioClk, 1u);
        CLOCK_AttachClk(kPLL0_to_FLEXIO);

        /* Init FlexIO SPI. */
        /*
                * userConfig.enableMaster = true;
                * userConfig.enableInDoze = false;
                * userConfig.enableInDebug = true;
                * userConfig.enableFastAccess = false;
                * userConfig.baudRate_Bps = 500000U;
                * userConfig.phase = kFLEXIO_SPI_ClockPhaseFirstEdge;
                * userConfig.dataMode = kFLEXIO_SPI_8BitMode;
                */
        FLEXIO_SPI_MasterGetDefaultConfig(&userConfig);

        // NOTE: 此处修改 SPI 通信速率
        userConfig.baudRate_Bps = FLEXIO_SPI_BAUD_HIGH;

        spiDev.flexioBase      = BOARD_FLEXIO_BASE;
        spiDev.SDOPinIndex   = FLEXIO_SPI_MOSI_PIN;
        spiDev.SDIPinIndex   = FLEXIO_SPI_MISO_PIN;
        spiDev.SCKPinIndex   = FLEXIO_SPI_SCK_PIN;
        spiDev.CSnPinIndex   = FLEXIO_SPI_CSn_PIN;
        spiDev.shifterIndex = FLEXIO_TX_SHIFTER_INDEX;
        spiDev.shifterIndex = FLEXIO_RX_SHIFTER_INDEX;
        spiDev.timerIndex   = 0U;
        spiDev.timerIndex   = 1U;

        dma_request_source_tx = (dma_request_source_t)EXAMPLE_TX_DMA_SOURCE;
        dma_request_source_rx = (dma_request_source_t)EXAMPLE_RX_DMA_SOURCE;

#if defined(FSL_FEATURE_SOC_DMAMUX_COUNT) && FSL_FEATURE_SOC_DMAMUX_COUNT
        /*Init EDMA for example.*/
        DMAMUX_Init(EXAMPLE_FLEXIO_SPI_DMAMUX_BASEADDR);
        /* Request DMA channels for TX & RX. */
        DMAMUX_SetSource(EXAMPLE_FLEXIO_SPI_DMAMUX_BASEADDR, FLEXIO_SPI_TX_DMA_CHANNEL, dma_request_source_tx);
        DMAMUX_SetSource(EXAMPLE_FLEXIO_SPI_DMAMUX_BASEADDR, FLEXIO_SPI_RX_DMA_CHANNEL, dma_request_source_rx);
        DMAMUX_EnableChannel(EXAMPLE_FLEXIO_SPI_DMAMUX_BASEADDR, FLEXIO_SPI_TX_DMA_CHANNEL);
        DMAMUX_EnableChannel(EXAMPLE_FLEXIO_SPI_DMAMUX_BASEADDR, FLEXIO_SPI_RX_DMA_CHANNEL);
#endif
        EDMA_GetDefaultConfig(&config);
        EDMA_Init(EXAMPLE_FLEXIO_SPI_DMA_BASEADDR, &config);
        EDMA_CreateHandle(&txHandle, EXAMPLE_FLEXIO_SPI_DMA_BASEADDR, FLEXIO_SPI_TX_DMA_CHANNEL);
        EDMA_CreateHandle(&rxHandle, EXAMPLE_FLEXIO_SPI_DMA_BASEADDR, FLEXIO_SPI_RX_DMA_CHANNEL);

#if defined(FSL_FEATURE_EDMA_HAS_CHANNEL_MUX) && FSL_FEATURE_EDMA_HAS_CHANNEL_MUX
        EDMA_SetChannelMux(EXAMPLE_FLEXIO_SPI_DMA_BASEADDR, FLEXIO_SPI_TX_DMA_CHANNEL, dma_request_source_tx);
        EDMA_SetChannelMux(EXAMPLE_FLEXIO_SPI_DMA_BASEADDR, FLEXIO_SPI_RX_DMA_CHANNEL, dma_request_source_rx);
#endif

        FLEXIO_SPI_MasterInit(&spiDev, &userConfig, FLEXIO_CLOCK_FREQUENCY);
}
```

#### SPI 传输函数

此处仅实现了单个字节的发送和接收函数,通过 eDMA 中断来判断收发是否完成。

```c
uint8_t port_LCD_SPI_TxByte(uint8_t data)
{
        flexio_spi_transfer_t xfer = {0};
        uint8_t readBack = 0;

        /* Send to slave. */
        xfer.txData   = &data;
        xfer.rxData   = &readBack;
        xfer.dataSize = 1;
        xfer.flags    = kFLEXIO_SPI_8bitMsb;

        FLEXIO_SPI_MasterTransferCreateHandleEDMA(&spiDev, &g_spiHandle,
                spi_master_completionCallback, NULL, &txHandle, &rxHandle);
        FLEXIO_SPI_MasterTransferEDMA(&spiDev, &g_spiHandle, &xfer);
        while (!completeFlag);
        completeFlag = false;

        return readBack;
}
```

### LCD 读写寄存器、数据函数

下面是 LCD 抽象层的函数,仅仅需要控制管脚输出高低电平、SPI传输单个字节即可。

```c
/*****************************************************************************
* @name       :void LCD_WR_REG(uint8_t data)
* @date       :2018-08-09
* @function   :Write an 8-bit command to the LCD screen
* @parameters :data:Command value to be written
* @retvalue   :None
******************************************************************************/
void LCD_WR_REG(uint8_t data)
{
        LCD_CS_CLR;
        LCD_RS_CLR;
        port_LCD_SPI_TxByte(data);
        LCD_CS_SET;
}

/*****************************************************************************
* @name       :void LCD_WR_DATA(uint8_t data)
* @date       :2018-08-09
* @function   :Write an 8-bit data to the LCD screen
* @parameters :data:data value to be written
* @retvalue   :None
******************************************************************************/
void LCD_WR_DATA(uint8_t data)
{
        LCD_CS_CLR;
        LCD_RS_SET;
        port_LCD_SPI_TxByte(data);
        LCD_CS_SET;
}
```

# 运行

屏幕成功点亮,也能显示中英文字符,绘制图片出了点差错,但是刷屏速度好慢。尝试提高了 SPI 速度,编译器优化等级debug/release 都试过,刷屏的百叶窗效果还是很明显。

!(https://www.eefocus.com/forum/data/attachment/forum/202411/18/113719o3fu5c37bea753k7.png)

!(https://www.eefocus.com/forum/data/attachment/forum/202411/18/113729xsnc7rnkq8zh7zpr.png)

演示视频见B站:

(https://www.bilibili.com/video/BV1M7UeYgEf6/)

页: [1]
查看完整版本: 【Avnet | NXP FRDM-MCXN947试用活动】测评4--FLEXIO_SPI 驱动TFT LCD