祝大家好。 在本文中,我们将使用FMC(柔性内存控制器)模块通过16位8080并行接口分析TFT显示器ER-TFT101-1(10英寸,RA8876驱动器)与STM32F429L Discovery板的连接。


关于显示器组装
EastRising的ER-TFT101-1是一个分辨率为1024x600的10英寸TFT矩阵和一个带有RA8876驱动器的电路板的组件。 在带有驱动程序的主板上,所有必需的电源都已布线,有一个16 MB的SD-RAM内存(16位总线,最大频率166 MHz,最大容量64 MB),还有一个标准的microSD卡插槽。 带有外部字体的EEPROM下和闪存下的空脚印,带有用于对其编程的输出连接器的图像。 同样,可以将电阻式或电容式触摸面板安装在组件上。
板上是最高工作频率为120 MHz的高端RAiO RA8876驱动器,如果需要,它本身可以用作控制微控制器。 您可以编写一个小程序(仅12条指令)并将其放入外部闪存中。 当显示开始时,该程序将首先开始运行,实质上是通过外部接口复制所有控制选项。

RA8876没有自己的RAM,因此使用外部SD-RAM存储器。 它可以使用DMA从闪存读取图像并将其加载到其帧缓冲区中,并具有非常灵活的配置。 该驱动程序使用标准的18位宽RGB接口连接到矩阵本身。 每个红色通道仅使用6位,每个绿色通道仅使用6位,蓝色通道仅使用6位。 不使用每个通道的低两位,理论上它提供262,144种颜色。
RA8876的DMA模块与STM的DMA2D非常相似-它可以通过颜色转换,透明度和其他芯片,将矩形的存储器从一个地方复制到另一个地方。
RA8876还具有内置的英文和中文字体(8x16、12x24、16x32像素),具有灵活的显示设置(旋转,缩放等),以及单独的矩阵接口(5 x 5),用于硬件按钮(独立和(不但可以使用),还可以通过长按和短按等一系列设置,同时按下一个按钮并同时按下几个按钮来唤醒显示。
有画中画功能(不支持透明度),用于显示任何弹出窗口和菜单。
驱动程序本身可以绘制图形图元,例如正方形,圆形,曲线,椭圆形,三角形,圆角正方形(带有或不带有填充)。 顺便说一下,在RA8875和RA8876中,有一个小错误可以填充三角形,并且每个驱动程序都有自己的驱动程序。 但是RAiO并没有在高耸的钟楼上对此表示可恶...我试图以某种方式给他们写封信,所以他们甚至都没有回答。 绘制此类图元,即使使用速度较慢的微控制器也可以创建精美的图形。
与外部世界相比,RA8876通过8080/6800 8/16位,3/4线SPI和I2C接口进行通信。 此外,驱动器芯片本身可以充当SPI和I2C主设备。 在RA8876中,有两个PWM输出可用于灵活控制背光。 最高SPI CLK频率声明为66 MHz,驱动器频率为120 MHz,理论上每秒可提供6帧全屏更新(1024 x 600 x 16位)。 此连接经过我的测试,表明如果我们不将视频显示在屏幕上,它就有生命的权利。
在我们的案例中,我们将使用FMC模块(柔性内存控制器)将使用宽度为16位的8080协议的显示器连接到STM32F429ZIT6,这将使我们获得更高的屏幕填充速度和更少的微控制器负载。
8080和FMC引脚的配置
我们将在显示屏的数据表中查看8080的连接图:

我们将介绍在CubeMX中连接STM32所需的引脚。 我们对#1组(NOR Flash / PSRAM / SRAM / ROM / LDC 1)感兴趣。

关于数据表中的XnWAIT,您可以阅读以下内容:
连续的数据写入速度决定了显示更新速度。 如果用户不采用XnWait插入等待状态,则周期间隔必须大于系统时钟周期的5。 如果不使用xnwait机制,则超出规格可能导致数据丢失或功能失败。
从字面上看,在8080协议的操作周期之间,如果用户不使用XnWAIT机制等待RA8876的释放,则应插入5个RA8876系统碎片。 我们将再次使用该引脚,因为 在实践中,我尝试插入五个周期的延迟,但没有成功。
代替FMC单元的完整地址总线,我们仅使用一个引脚A16。
- 我们将数据引脚(D0-D15)配置为替代功能#12,例如推挽式,最大速度且没有任何悬挂装置。
- XnWAIT,XnWR,XnRD,XA0和XnCS引脚被配置为替代功能#12,例如带有上拉(PULL UP)的推挽式。
- 我们将XnRST配置为不带悬挂器的常规GPIO(位于板本身上)。
- XnINTR可配置为输入的GPIO,并提升为加号。
我也将背光灯连接到100%,而不通过PWM控制它。 为此,将显示器组合件连接器上的#14引脚连接到VDD。
我没有提供引脚的配置代码,因为 我使用自己的配置库,并且在集线器和其他来源上GPIO配置本身已经被咀嚼了一百次。
初始化库在这里 。
FMC设置
每个存储体三个存储体负责设置FMC模块的存储体(NOR Flash / PSRAM / SRAM / ROM / LDC 1)。 它们是FMC_BCRx,FMC_BTRx和FMC_BWTRx。 在STM32F429 MK定义中,将FMC_BCRx和FMC_BTRx寄存器组合为一个公用数组,称为FMC_BTCR,具有八个元素,其中零元素为FMC_BCR1,第一个元素为FMC_BTR1,第二个元素为FMC_BCR2,依此类推。 FMC_BWTRx组合成具有七个元素的FMC_BWTR数组,尽管应该有四个。 不要问我为什么...
FMC_BCRx包含基本设置,FMC_BTRx包含常规时序,而FMC_BWTRx包含单独的读取时序(如果设备需要的话)。
STM32F429和RA8876交互的时序图和时序。

为了简化配置,我们将8080协议时序插入常量中。 我自己凭经验选择时间,从而降低了价值,因为 带有数据表的时序表更像是真空中的球形马。
unsigned long ADDSET = 0; unsigned long ADDHLD = 1; unsigned long DATAST = 5; unsigned long BUSTURN = 0; unsigned long CLKDIV = 1; unsigned long DATLAT = 0; unsigned long ACCMOD = 0; RCC->AHB3ENR |= RCC_AHB3ENR_FMCEN;
复位后FMC_BTCRx寄存器的值为0x0FFF FFFF,即 设置最大时间。 如果您有新的显示器或内存,请降低计时并尝试运行。
显示初始化
使用显示器的工作归结为读取或写入某些内存区域。 FMC负责其余的工作。 为了简化工作,我们定义了两个定义:
#define LCD_DATA 0x60020000 #define LCD_REG 0x60000000
现在,我们描述底层功能:
void LCD_CmdWrite (unsigned char cmd) { *(unsigned short *)(LCD_REG) = cmd; }; void LCD_DataWrite (unsigned short data) { *(unsigned short *)(LCD_DATA)= data; }; unsigned char LCD_StatusRead(void) { unsigned short data = *(unsigned short *)(LCD_REG); return data; }; unsigned char LCD_DataRead(void) { unsigned short data = * (unsigned short *)(LCD_DATA); return (unsigned char)data; }; void LCD_RegisterWrite(unsigned char cmd, unsigned char data) { *(unsigned short *)(LCD_REG) = cmd; *(unsigned short *)(LCD_DATA) = data; }; unsigned char LCD_RegisterRead (unsigned char cmd) { volatile unsigned char data = 0; LCD_CmdWrite (cmd); data = LCD_DataRead (); return data; };
此外,显示初始化功能本身。 该代码取自显示提供商,并经过了略微重新设计以满足您的需求。 子功能占据了大量资源,在本文中,我将不再赘述。
链接到github上的驱动程序 void RA8876_Init(void) { RA8876_PLL_Init ();
关于帧缓冲区和活动区域更多。 对于这两个设置,我们定义以下定义:
#define PAGE0_START_ADDR 0 #define PAGE1_START_ADDR (1024 * 600 * 2 * 1) #define PAGE2_START_ADDR (1024 * 600 * 2 * 2) #define PAGE3_START_ADDR (1024 * 600 * 2 * 3) #define PAGE4_START_ADDR (1024 * 600 * 2 * 4) #define PAGE5_START_ADDR (1024 * 600 * 2 * 5) #define PAGE6_START_ADDR (1024 * 600 * 2 * 6) #define PAGE7_START_ADDR (1024 * 600 * 2 * 7) #define PAGE8_START_ADDR (1024 * 600 * 2 * 8) #define PAGE9_START_ADDR (1024 * 600 * 2 * 9) #define PAGE10_START_ADDR (1024 * 600 * 2 * 10) #define PAGE11_START_ADDR (1024 * 600 * 2 * 11) #define PAGE12_START_ADDR (1024 * 600 * 2 * 12)
每页(PAGEx_START_ADDR)是SDRAM中的起始地址。 在16 MB的内存中,我们可以放置13个完整层,大小为1228800字节(1024 * 600 * 2)。
Frame_Buffer_Start_Address函数设置帧缓冲区的初始存储区(当前正在显示的内容)。
Canvas_Window_Start_Address函数设置画布的初始存储区。 此外,画布可能大于帧缓冲区,因此您可以在屏幕上滚动图像。 例如,对于平台游戏,您可以制作一个长13132 x 600像素的画布,然后将其水平滚动,在其上水平移动帧缓冲区。
如果将图形输出与STM32的LTDM进行比较,那么这里的一切并不是那么乐观。 同时,驱动程序本身只能显示最后一层(缓冲),LTDC立即显示两层,将它们混合在一起,而无需您参与此过程。
绘制图元
来自具有预建功能的显示提供商的代码:
void Start_Line (void); void Start_Triangle (void); void Start_Triangle_Fill (void); void Line_Start_XY (unsigned short WX, unsigned short HY); void Line_End_XY (unsigned short WX, unsigned short HY); void Triangle_Point1_XY (unsigned short WX, unsigned short HY); void Triangle_Point2_XY (unsigned short WX, unsigned short HY); void Triangle_Point3_XY (unsigned short WX, unsigned short HY); void Square_Start_XY (unsigned short WX, unsigned short HY); void Square_End_XY (unsigned short WX, unsigned short HY); void Start_Circle_or_Ellipse (void); void Start_Circle_or_Ellipse_Fill (void); void Start_Left_Down_Curve (void); void Start_Left_Up_Curve (void); void Start_Right_Up_Curve (void); void Start_Right_Down_Curve (void); void Start_Left_Down_Curve_Fill (void); void Start_Left_Up_Curve_Fill (void); void Start_Right_Up_Curve_Fill (void); void Start_Right_Down_Curve_Fill (void); void Start_Square (void); void Start_Square_Fill (void); void Start_Circle_Square (void); void Start_Circle_Square_Fill (void); void Circle_Center_XY (unsigned short WX, unsigned short HY); void Ellipse_Center_XY (unsigned short WX, unsigned short HY); void Circle_Radius_R (unsigned short WX); void Ellipse_Radius_RxRy (unsigned short WX, unsigned short HY); void Circle_Square_Radius_RxRy (unsigned short WX, unsigned short HY);
例如,要绘制一个填充的三角形,我们设置三个点:Triangle_Point1_XY,Triangle_Point2_XY,Triangle_Point2_XY并运行Start_Triangle_Fill函数。
使用DMA
为了方便起见,我使用结构作为传递参数编写了函数:
struct GFX_BTE_options { unsigned long layer_s0_addr;
操作码说明(OPERATION CODE):0000:使用MK通过ROP写入内存。
0001:使用MK在没有ROP的情况下读取内存。
0010:使用ROP正向复制存储块。
0011:使用ROP反向复制存储块。
0100:使用MK无需ROP写入内存(透明)。
0101:在没有ROP的情况下正向复制(移动)存储块(透明)。
0110:使用ROP填充图案。
0111:用抠像填充模板。
1000:颜色扩展
1001:具有增强的色彩和透明度
1010:使用Alpha混合向前移动内存块
1011:使用MK通过alpha混合写入内存。
1100:用纯色填充存储区域。
1101:保留
1110:保留
1111:保留
光栅码说明(ROP CODE):0000b:0(黑色)
0001b:〜S0 ・〜S1或〜(S0 + S1)
0010b:〜S0 ・ S1
0011b:〜S0
0100b:S0 ・〜S1
0101b:〜S1
0110b:S0 ^ S1
0111b:〜S0 +〜S1或〜(S0 ・ S1)
1000b:S0 ・ S1
1001b:〜(S0 ^ S1)
1010b:S1
1011b:〜S0 + S1
1100b:S0
1101b:S0 +〜S1
1110b:S0 + S1
1111b:1(白色)
S0是零层,S1是第一层。 它们之间的相互作用是使用算术和位运算发生的。
内联字体输出
void GFX_Show_String_TMODE (short x, short y, char *ptr, unsigned short charColor, unsigned short bkColor) { Foreground_color_65k (charColor); Background_color_65k (bkColor); CGROM_Select_Internal_CGROM (); Font_Select_12x24_24x24 (); Text_Mode (); Goto_Text_XY (x, y); LCD_CmdWrite (0x04); while (*ptr != '\0') { LCD_DataWrite (*ptr); Check_Mem_WR_FIFO_not_Full (); ++ptr; } Check_2D_Busy (); Graphic_Mode ();
完整版本的驱动程序可以在github的链接上找到。谢谢大家的阅读!