使用显示总线接口将 TFT 屏幕与 STM32F446 连接

Posted

技术标签:

【中文标题】使用显示总线接口将 TFT 屏幕与 STM32F446 连接【英文标题】:Interfacing TFT screen with STM32F446 using display bus interface 【发布时间】:2021-10-08 20:16:56 【问题描述】:

我正在尝试了解如何将 TFT 屏幕模块与定制 PCB 上的 STM32F4 芯片连接起来。 Here is the module and its basic info.

为了将命令和数据写入屏幕,屏幕模块上的 ILI9481 驱动程序使用显示总线接口 (DBI),其中数据通过数据线通过 8 位或 16 位发送。

看着library examples,我明白(如果我错了,请纠正我),为了发送一个字节的命令,它只是将芯片的数字引脚设置为高或低,具体取决于命令。例如,8 位通信中的命令 0x2 将是 00000010,其中 0 将是芯片 GPIO 引脚上的数字低电平,1 将是数字高电平,这意味着 8 根线中有 1 根处于活动状态(逻辑高电平)。我希望,我理解正确。

现在,当我查看示例时,通常这些数字引脚位于同一个 GPIO 端口上。如果我理解正确,GPIO 端口有一个寄存器,称为 BSRR,您可以在其中操作 GPIO 端口引脚的逻辑电平。如果数据引脚都在同一个 GPIO 端口上,我认为这会起作用(从示例中,c 是命令字节):

void STM32_TFT_8bit::write8(uint8_t c) 

  // BRR or BSRR avoid read, mask write cycle time
  // BSRR is 32 bits wide. 1's in the most significant 16 bits signify pins to reset (clear)
  // 1's in least significant 16 bits signify pins to set high. 0's mean 'do nothing'
  TFT_DATA->regs->BSRR = ((~c)<<16) | (c); //Set pins to the 8 bit number

  WR_STROBE;

但是,在我的 PCB 板上,屏幕模块的数据引脚是分开在不同的端口上的。 所以,我的问题是,我将如何做同样的事情,在操作逻辑级别的同时发送命令?我假设,我可以根据命令一个一个地写入设置/重置我的引脚,但是 BSRR 寄存器看起来如何?

如果我的数据引脚如下:

D0 -> PC12 D1 -> PC11 D2 -> PC10 D4 -> PA12 D5 -> PA11 D6 -> PA10 D7 -> PA9

通过寄存器的 0x9D (0b10011101) 命令会看起来像这样吗? :

   GPIOA->regs->BSRR = 0b0001101000000000; // A port: turn on PA9, PA11, PA12
   GPIOC->regs->BSRR = 0b0001010000000000; // C port: turn on PC10 and PC12

【问题讨论】:

【参考方案1】:

BSRR 寄存器看起来如何?

位掩码可以应用于写入BSRR 的值,例如:

/* set/reset selected GPIO output pins, ignore the rest */
static inline void _gpio_write(GPIO_TypeDef* GPIOx, uint16_t state, uint16_t mask)

    GPIOx->BSRR = ((uint32_t)(~state & mask) << 16) | (state & mask);

数据位在写入 GPIO 输出寄存器之前需要重新排列,例如:

#define BITS(w,b) (((w) & (1 << (b))) >> (b))

/* write a data/command byte to the data bus DB[7:0] of custom ILI9481 board
   used pin assignment: D0 -> PC12, D1 -> PC11, D2 -> PC10, (D3 -> PC1) (?)
                        D4 -> PA12, D5 -> PA11, D6 -> PA10, D7 -> PA9 */
static void _write_data_to_pins(uint8_t data)

    const uint16_t mask_c = 1<<12 | 1<<11 | 1<<10 | 1<<1; /* 0x1c02 */
    const uint16_t mask_a = 1<<12 | 1<<11 | 1<<10 | 1<<9; /* 0x1e00 */
    _gpio_write(GPIOC, (uint16_t)(BITS(data, 0) << 12 | BITS(data, 1) << 11 |
                                  BITS(data, 2) << 10 | BITS(data, 3) <<  1), mask_c);
    _gpio_write(GPIOA, (uint16_t)(BITS(data, 4) << 12 | BITS(data, 5) << 11 |
                                  BITS(data, 6) << 10 | BITS(data, 7) <<  9), mask_a);

测试:

/* just for testing: read the written data bits back and arrange them in a byte */
static uint8_t _read_data_from_pins(void)

    const uint32_t reg_c = GPIOC->ODR;
    const uint32_t reg_a = GPIOA->ODR;
    return (uint8_t)(BITS(reg_c, 12) << 0 | BITS(reg_c, 11) << 1 |
                     BITS(reg_c, 10) << 2 | BITS(reg_c,  1) << 3 |
                     BITS(reg_a, 12) << 4 | BITS(reg_a, 11) << 5 |
                     BITS(reg_a, 10) << 6 | BITS(reg_a,  9) << 7);


/* somewhere in main loop of test project */

    uint8_t d = 0xff;
    do 
        _write_data_to_pins(d);
        if (d != _read_data_from_pins()) 
            Error_Handler();
        
     while (d--);

(注意:问题中仅列出了 8 个数据引脚 DB[7:0] 中的 7 个,PC1 在此处分配给数据引脚 D3。)

(注意:编译器可以轻松优化大多数位移位,至少使用-O1 以通过 GCC 获得一些紧凑的结果。)


GPIOA->regs->BSRR = 0b0001101000000000; // A port: turn on PA9, PA11, PA12
GPIOC->regs->BSRR = 0b0001010000000000; // C port: turn on PC10 and PC12

这两个代码行执行 cmets 中所述的操作。但他们将保持所有其他引脚不变。

结果输出将取决于输出数据寄存器的先前状态。 - 对于LOW数据引脚,需要将BSRR[31:16]中对应的GPIO端口位设置为1,以便一次更新所有8位数据总线。

回答实际问题: 不,在将两个引用的位模式写入两个BSRR 寄存器后,数据总线上的输出不会是 0x9D (0b1001'1101)。 - 就我而言,它看起来像这样(如果我错了,请纠正我):

/* write 0x9D (0b1001'1101) to the data bus
   used pin assignment: D0 -> PC12, D1 -> PC11, D2 -> PC10, (D3 -> PC1) (?)
                        D4 -> PA12, D5 -> PA11, D6 -> PA10, D7 -> PA9 */
GPIOC->BSRR = 0x8001402; /* = 0b00001000'00000000'00010100'00000010 */
GPIOA->BSRR = 0xc001200; /* = 0b00001100'00000000'00010010'00000000 */

【讨论】:

【参考方案2】:

假设 'command' 是要发送的字节(我希望某处有一个 strobe...)。

我会像这样简单地编写8行代码(如果我很好地理解STM32的端口寄存器):

if (command & 1) GPIOC->BSRR |= 1 << 12; else GPIOC->BSRR &= ~(1 << 12);
if (command & 2) GPIOC->BSRR |= 1 << 11; else GPIOC->BSRR &= ~(1 << 11);
...
if (command & 128) GPIOA->BSRR |= 1 << 9; else GPIOA->BSRR &= ~(1 << 9);

也许这很原始,但它很有效并且很容易理解(即更难打错字)。下次告诉硬件设计师把线布置得更好一点……很难想象比这更糟糕的事情了,这些位似乎颠倒了,只是为了看看软件人能不能应付它们!

【讨论】:

BSRR 是一个只写寄存器,所以这不起作用。使用|=&amp;= 运算符始终会导致读-修改-写操作。 ODR 可以这样使用。 - 或者更好,使用:BSRR = (1u &lt;&lt; bit); 设置,BSRR = (1u &lt;&lt; bit) &lt;&lt; 16u; 清除单个引脚状态,每次写入访问都发生在快速、小型、不间断/原子操作中。

以上是关于使用显示总线接口将 TFT 屏幕与 STM32F446 连接的主要内容,如果未能解决你的问题,请参考以下文章

如何设置快速 STM32 F4 FSMC 来控制 STM32F4Discovery 板上的显示?

使用STM32F4的CCM内存

SDRAM 与 STM32F429BI 接口存在闪烁问题

STM32F1基于STM32CubeMX配置硬件SPI驱动1.8寸TFT LCD128X160 ST7735S屏幕

STM32CubeMX | 41 - 使用LTDC驱动TFT-LCD屏幕(RGB屏)

STM32F4 HAL库开发 -- 温度传感器(DS18B20)