使用显示总线接口将 TFT 屏幕与 STM32F446 连接
Interfacing TFT screen with STM32F446 using display bus interface
我想了解如何将 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板上,屏幕模块的数据引脚在不同的端口上是分开的。
所以,我的问题是,我将如何做同样的事情,在操纵逻辑级别的同时发送命令?我假设,我可以根据命令一个接一个地写 set/reset 我的引脚,但是 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
how would it look with the BSRR registers?
位掩码可以应用于写入 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
这两行代码执行注释中所述的操作。但他们将保留所有其他引脚不变。
结果输出将取决于输出数据寄存器的先前状态。 - 对于 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 */
假设'command'是要发送的字节(我希望某处有闪光...)。
我会简单地编写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);
也许这很原始,但它有效并且很容易理解(即更难打错字)。下次告诉硬件设计师把线排得再好一点。。。很难想像比这更糟糕的了,位似乎颠倒了只是为了看看软件人是否可以应付它们!
我想了解如何将 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板上,屏幕模块的数据引脚在不同的端口上是分开的。 所以,我的问题是,我将如何做同样的事情,在操纵逻辑级别的同时发送命令?我假设,我可以根据命令一个接一个地写 set/reset 我的引脚,但是 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
how would it look with the BSRR registers?
位掩码可以应用于写入 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
这两行代码执行注释中所述的操作。但他们将保留所有其他引脚不变。
结果输出将取决于输出数据寄存器的先前状态。 - 对于 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 */
假设'command'是要发送的字节(我希望某处有闪光...)。
我会简单地编写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);
也许这很原始,但它有效并且很容易理解(即更难打错字)。下次告诉硬件设计师把线排得再好一点。。。很难想像比这更糟糕的了,位似乎颠倒了只是为了看看软件人是否可以应付它们!