【问题标题】:Interfacing TFT screen with STM32F446 using display bus interface使用显示总线接口将 TFT 屏幕与 STM32F446 连接
【发布时间】: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

【问题讨论】:

    标签: stm32 microcontroller gpio stm32f4


    【解决方案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; 清除单个引脚状态,每次写入访问都发生在快速、小型、不间断/原子操作中。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-11-03
      • 2020-09-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多