Anschließen eines 10 "ER-TFT101-1-Displays an den STM32F429 über FMC

Guten Tag an alle. In diesem Artikel wird die Verbindung des TFT-Displays ER-TFT101-1 (10 Zoll, RA8876-Treiber) mit dem Discovery-Board STM32F429L über eine 16-Bit-8080-Parallelschnittstelle unter Verwendung des Moduls FMC (Flexible Memory Controller) analysiert.




Informationen zur Bildschirmbaugruppe


Das ER-TFT101-1 von EastRising ist eine Baugruppe aus einer 10-Zoll-TFT-Matrix mit einer Auflösung von 1024 x 600 und einem Board mit einem RA8876-Treiber. Auf der Hauptplatine mit dem Treiber wird die gesamte erforderliche Leistung geroutet, es gibt einen 16-Megabyte-SD-RAM-Speicher (16-Bit-Bus, 166 MHz maximale Frequenz, 64 MB maximale Kapazität), es gibt einen Standard-microSD-Kartensteckplatz. Im EEPROM mit externen Schriftarten und im Flash-Speicher für Bilder mit Ausgangsanschlüssen zum Programmieren befinden sich leere Footprints. Optional kann auch ein resistives oder kapazitives Touchpanel an der Baugruppe installiert werden.

Auf dem Board befindet sich der RAiO RA8876-Treiber der Spitzenklasse mit einer maximalen Betriebsfrequenz von 120 MHz, der auf Wunsch selbst als Steuerungsmikrocontroller fungieren kann. Sie können ein kleines Programm schreiben (nur 12 Anweisungen) und es in einem externen Flash-Speicher ablegen. Wenn die Anzeige startet, startet dieses Programm zunächst und dupliziert im Wesentlichen alle Steuerungsoptionen über die externe Schnittstelle.



Der RA8876 hat keinen eigenen RAM und verwendet daher einen externen SD-RAM-Speicher. Mit DMA kann es Bilder aus dem Flash-Speicher lesen und in den Bildspeicher laden. Die Konfiguration ist sehr flexibel. Der Treiber wird über eine Standard-RGB-Schnittstelle mit einer Breite von 18 Bit an die Matrix selbst angeschlossen. Es werden nur 6 Bits pro rotem Kanal, 6 Bits pro grünem und 6 Bits pro blauem Kanal verwendet. Die unteren zwei Bits jedes Kanals werden nicht verwendet, was theoretisch 262.144 Farben ergibt.

Das DMA-Modul des RA8876 ist dem DMA2D von STM sehr ähnlich - es kann rechteckige Speicherbereiche mit Farbkonvertierung, Transparenz und anderen Chips von einem Ort zum anderen kopieren.

RA8876 verfügt außerdem über integrierte englische und chinesische Schriftarten (8 x 16, 12 x 24, 16 x 32 Pixel) mit flexiblen Einstellungen für die Anzeige (Drehung, Skalierung usw.) und eine separate Matrixschnittstelle (5 x 5) für Hardwaretasten (für Einzel- und nicht nur verwenden) mit einer Reihe von Einstellungen, wie z. B. langes und kurzes Drücken, aktivieren Sie das Display, indem Sie eine Taste drücken und mehrere Tasten gleichzeitig drücken.

Es gibt eine Bild-in-Bild-Funktion (ohne Transparenzunterstützung) zum Anzeigen von Popup-Fenstern und Menüs.

Der Treiber selbst kann grafische Grundelemente wie Quadrat, Kreis, Kurve, Oval, Dreieck, abgerundetes Quadrat mit und ohne Füllung zeichnen. Übrigens, in RA8875 und RA8876 gibt es einen kleinen Fehler, der das Dreieck ausfüllt, und jeder Fahrer hat seinen eigenen. Aber RAiO hat sich vom hohen Glockenturm aus nicht darum gekümmert ... Ich habe versucht, ihnen irgendwie einen Brief zu schreiben, also haben sie nicht einmal geantwortet. Wenn Sie solche Grundelemente zeichnen, können Sie auch mit einem langsamen Mikrocontroller schöne Grafiken erstellen.

Mit der Außenwelt kommuniziert der RA8876 über 8080/6800 8/16 Bit-, 3/4 Wire SPI- und I2C-Schnittstellen. Darüber hinaus kann der Treiberchip selbst als SPI- und I2C-Master fungieren. In RA8876 gibt es zwei PWM-Ausgänge, die zur flexiblen Steuerung der Hintergrundbeleuchtung verwendet werden können. Die maximale SPI-CLK-Frequenz wird bei 66 MHz mit einer Treiberfrequenz von 120 MHz angegeben, was theoretisch 6 Frames pro Sekunde einer Vollbildaktualisierung ergibt (bei 1024 x 600 x 16 Bit). Diese Verbindung wurde von mir getestet und hat gezeigt, dass sie das Recht auf Leben hat, wenn wir das Video nicht auf dem Bildschirm anzeigen.

In unserem Fall verbinden wir das Display mit dem 8080-Protokoll mit einer Breite von 16 Bit über das FMC-Modul (Flexible Memory Controller) mit dem STM32F429ZIT6, wodurch der Mikrocontroller schneller und weniger belastet wird.

Konfiguration der Pins 8080 und FMC


Wir werden das Anschlussdiagramm für 8080 im Datenblatt auf dem Display betrachten:



Wir schauen uns die notwendigen Pins für die Verbindung zum STM32 in CubeMX an. Wir interessieren uns für Bank 1 (NOR Flash / PSRAM / SRAM / ROM / LDC 1).



In Bezug auf XnWAIT im Datenblatt können Sie Folgendes lesen:
Die kontinuierliche Datenschreibgeschwindigkeit bestimmt die Anzeigeaktualisierungsgeschwindigkeit. Das Intervall von Zyklus zu Zyklus muss größer als 5 der Systemtaktperiode sein, wenn der Benutzer nicht XnWait zum Einfügen des Wartezustands verwendet. Ein Überschreiten der Spezifikation kann dazu führen, dass Daten verloren gehen oder die Funktion fehlschlägt, wenn der xnwait-Mechanismus nicht verwendet wird.
Im wahrsten Sinne des Wortes sollte zwischen den Betriebszyklen des 8080-Protokolls eine Verzögerung von 5 RA8876-System-Shreds eingefügt werden, wenn der Benutzer den XnWAIT-Mechanismus nicht verwendet, um auf die Freigabe von RA8876 zu warten. Wir werden diesen Pin noch einmal verwenden, als in der Praxis habe ich versucht, eine Verzögerung von fünf Zyklen einzufügen, und es hat nicht funktioniert.

Anstelle eines vollständigen Adressbusses für die FMC-Einheit wird nur ein Pin A16 verwendet.

  1. Wir konfigurieren die Datenpins (D0 - D15) als alternative Funktion # 12, wie z. B. Push-Pool, maximale Geschwindigkeit und ohne Hosenträger.
  2. Die Pins XnWAIT, XnWR, XnRD, XA0 und XnCS sind als alternative Funktion # 12 konfiguriert, z. B. Push-Pull mit einem Lift zum Plus (PULL UP).
  3. Wir konfigurieren XnRST als reguläres GPIO ohne Suspender (es befindet sich auf der Platine selbst).
  4. XnINTR kann als GPIO für den Eingang konfiguriert werden, wobei das Plus angehoben wird.

Ich habe auch die Hintergrundbeleuchtung zu 100% angeschlossen, ohne sie über PWM zu steuern. Zu diesem Zweck wird Pin 14 am Anschluss der Anzeigebaugruppe mit VDD verbunden.

Ich habe den Konfigurationscode für die Pins nicht angegeben, weil Ich verwende meine eigenen Konfigurationsbibliotheken, und die GPIO-Konfiguration selbst wurde auf dem Hub und anderen Quellen bereits hundertmal gekaut.

Die Initialisierungsbibliothek ist hier .

FMC-Einstellungen


Drei Bänke für jede Bank sind für die Einrichtung der Bänke des FMC-Moduls verantwortlich (NOR Flash / PSRAM / SRAM / ROM / LDC 1). Dies sind FMC_BCRx, FMC_BTRx und FMC_BWTRx. In den STM32F429-MK-Definitionen werden die Register FMC_BCRx und FMC_BTRx zu einem gemeinsamen Array namens FMC_BTCR mit acht Elementen kombiniert, wobei das Nullelement FMC_BCR1 ist, das erste Element FMC_BTR1 ist und das zweite Element FMC_BCR2 ist und so weiter. FMC_BWTRx werden zu einem FMC_BWTR-Array mit sieben Elementen kombiniert, obwohl es vier geben sollte. Frag mich nicht warum ...

FMC_BCRx enthält grundlegende Einstellungen, FMC_BTRx enthält allgemeine Timings und FMC_BWTRx enthält separate Timings zum Lesen, wenn das Gerät dies erfordert.

Timing-Diagramm und Timings für das Zusammenspiel von STM32F429 und RA8876.



Zur Vereinfachung der Konfiguration werden die 8080-Protokoll-Timings in Konstanten eingefügt. Ich habe die Timings selbst empirisch gewählt und den Wert ein wenig reduziert, weil Der Zeitplan mit Datenblatt ähnelt eher einem kugelförmigen Pferd im luftleeren Raum.

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 FMC_Bank1->BTCR[0] = 0; //    FMC_Bank1->BTCR[0] |= FMC_BCR1_MWID_0; //    16  FMC_Bank1->BTCR[0] |= FMC_BCR1_WREN; //    FMC_Bank1->BTCR[0] |= FMC_BCR1_WAITEN; //   XnWAIT FMC_Bank1->BTCR[0] |= FMC_BCR1_ASYNCWAIT; // XnWAIT    FMC_Bank1->BTCR[0] |= FMC_BCR1_WAITCFG; //  XnWAIT FMC_Bank1->BTCR[1] = 0; //    FMC_Bank1->BTCR[1] |= (ADDSET << 0) | (ADDHLD << 4) | (DATAST << 8) | (BUSTURN << 16) | (CLKDIV << 20) | (DATLAT << 24) | (ACCMOD << 28); //   FMC_Bank1->BTCR[0] |= 1; //    

Der Wert des FMC_BTCRx-Registers nach dem Zurücksetzen ist 0x0FFF FFFF, d.h. maximale Zeiten sind festgelegt. Wenn Sie eine neue Anzeige oder einen neuen Speicher haben, verringern Sie einfach die Zeiteinstellungen und versuchen Sie, sie auszuführen.

Initialisierung anzeigen


Beim Arbeiten mit dem Display geht es darum, bestimmte Speicherbereiche zu lesen oder zu beschreiben. FMC kümmert sich um den Rest der Arbeit. Um die Arbeit zu vereinfachen, definieren wir zwei Definitionen:

 #define LCD_DATA 0x60020000 #define LCD_REG 0x60000000 

Und jetzt beschreiben wir die Low-Level-Funktionen:

 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; }; 

Ferner funktioniert die Anzeigeinitialisierung selbst. Der Code stammt vom Display-Anbieter und wurde leicht an Ihre Bedürfnisse angepasst. Subfunktionen nehmen eine Menge ein und in diesem Artikel werde ich sie nicht geben.

Link zum Fahrer auf dem Github

 void RA8876_Init(void) { RA8876_PLL_Init (); //   RA8876  120  RA8876_SDRAM_Init (); //  SDRAM  166  TFT_24bit (); //    24   ??? Host_Bus_16bit (); //   16  RGB_16b_16bpp (); //   16  MemWrite_Left_Right_Top_Down (); //       . Graphic_Mode (); //    Memory_Select_SDRAM (); //   SDRAM /*  RGB     RA8876 */ HSCAN_L_to_R (); VSCAN_T_to_B (); PDATA_Set_RGB (); PCLK_Falling (); DE_High_Active (); HSYNC_High_Active (); VSYNC_High_Active (); LCD_HorizontalWidth_VerticalHeight (1024, 600); LCD_Horizontal_Non_Display (160); LCD_HSYNC_Start_Position (160); LCD_HSYNC_Pulse_Width (70); LCD_Vertical_Non_Display (23); LCD_VSYNC_Start_Position (12); LCD_VSYNC_Pulse_Width (10); //   Frame_Buffer_Start_Address (PAGE0_START_ADDR); Frame_Buffer_Width (1024); Frame_Buffer_Start_XY (0, 0); Frame_Buffer_Color_Mode_16bpp (); //   Canvas_Window_Start_Address (PAGE0_START_ADDR); Canvas_Window_Width (1024); Canvas_Window_Start_XY (0, 0); Canvas_Window_WH (1024, 600); Canvas_Memory_XY_Mode (); Canvas_Window_Color_Mode_16bpp (); } 

Über Framebuffer und Aktivbereich ein bisschen mehr. Für diese beiden Einstellungen definieren wir die folgenden Definitionen:

 #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) 

Jede Seite (PAGEx_START_ADDR) ist die Startadresse im SDRAM. In 16 Megabyte Speicher können 13 vollständige Schichten mit einer Größe von 1228800 Bytes (1024 * 600 * 2) abgelegt werden.
Die Funktion Frame_Buffer_Start_Address legt den anfänglichen Speicherbereich für den Framebuffer fest (was gerade angezeigt wird).

Die Funktion Canvas_Window_Start_Address legt den anfänglichen Speicherbereich für die Zeichenfläche fest. Darüber hinaus ist die Leinwand möglicherweise größer als der Framebuffer, sodass Sie das Bild auf dem Bildschirm verschieben können. Für ein Plattformspiel können Sie beispielsweise eine lange Leinwand mit einer Größe von 13312 x 600 Pixel erstellen und anschließend einen horizontalen Bildlauf durchführen, indem Sie den Framebuffer horizontal darüber bewegen.

Wenn Sie die Grafikausgabe mit LTDM von STM32 vergleichen, ist hier nicht alles so rosig. Gleichzeitig kann der Treiber selbst nur eine letzte (Puffer-) Schicht und LTDC gleichzeitig zwei anzeigen und diese mischen, ohne dass Sie an diesem Prozess teilnehmen müssen.

Primitive zeichnen


Code von einem Display-Anbieter mit vorgefertigten Funktionen:

 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); 

Um beispielsweise ein gefülltes Dreieck zu zeichnen, setzen wir drei Punkte: Triangle_Point1_XY, Triangle_Point2_XY, Triangle_Point2_XY und führen die Funktion Start_Triangle_Fill aus.

Arbeite mit DMA


Der Einfachheit halber habe ich meine Funktion mit einer Struktur als übergebenen Parameter geschrieben:

 struct GFX_BTE_options { unsigned long layer_s0_addr; //     0 unsigned long layer_s1_addr; //     1 unsigned long layer_d_addr; //     unsigned short layer_s0_width; //    0 unsigned short layer_s1_width; //    1 unsigned short layer_d_width; //    unsigned short layer_s0_start_x; //     0 unsigned short layer_s0_start_y; //   Y  0 unsigned short layer_s1_start_x; //   X  1 unsigned short layer_s1_start_y; //   Y  1 unsigned short layer_d_start_x; //   X   unsigned short layer_d_start_y; //   Y   unsigned short window_size_x; //     unsigned short window_size_y; //     unsigned char rop_code; //  ROP unsigned char operation_code; //   DMA };       DMA: void GFX_BTE_operation (struct GFX_BTE_options options) { BTE_S0_Color_16bpp (); BTE_S0_Memory_Start_Address (options.layer_s0_addr); BTE_S0_Image_Width (options.layer_s0_width); BTE_S0_Window_Start_XY (options.layer_s0_start_x, options.layer_s0_start_y); BTE_S1_Color_16bpp (); BTE_S1_Memory_Start_Address (options.layer_s1_addr); BTE_S1_Image_Width (options.layer_s1_width); BTE_S1_Window_Start_XY (options.layer_s1_start_x, options.layer_s1_start_y); BTE_Destination_Color_16bpp (); BTE_Destination_Memory_Start_Address (options.layer_d_addr); BTE_Destination_Image_Width (options.layer_d_width); BTE_Destination_Window_Start_XY (options.layer_d_start_x, options.layer_d_start_y); BTE_Window_Size (options.window_size_x, options.window_size_y); BTE_ROP_Code (options.rop_code); BTE_Operation_Code (options.operation_code); BTE_Enable (); Check_BTE_Busy (); } 

Beschreibung der Betriebscodes (BETRIEBSCODE):

0000: Mit MK mit ROP in den Speicher schreiben.
0001: Speicher ohne ROP mit MK auslesen.
0010: Kopieren Sie einen Speicherblock mit ROP in Vorwärtsrichtung.
0011: Kopieren eines Speicherblocks mit ROP in umgekehrter Richtung.
0100: Schreiben in den Speicher (mit Transparenz) ohne ROP mit MK.
0101: Kopieren (Verschieben) eines Speicherblocks (mit Transparenz) in Vorwärtsrichtung ohne ROP.
0110: Mit ROP mit einem Muster füllen.
0111: Fülle die Vorlage mit Chromakey.
1000: Farberweiterung
1001: Verbesserte Farbe mit Transparenz
1010: Verschieben eines Speicherblocks mit Alpha-Überblendung
1011: Schreiben in den Speicher mit Alpha-Überblendung mit MK.
1100: Füllt den Speicherbereich mit einer Volltonfarbe.
1101: Reserviert
1110: Reserviert
1111: Reserviert

Beschreibung der Rastercodes (ROP CODE):

0000b: 0 (Schwarz)
0001b: ~ S0 ・ ~ S1 oder ~ (S0 + S1)
0010b: ~ S0 ≤ S1
0011b: ~ S0
0100b: S0 ~ S1
0101b: ~ S1
0110b: S0 ^ S1
0111b: ~ S0 + ~ S1 oder ~ (S0 ≤ S1)
1000b: S0 ≤ S1
1001b: ~ (S0 ^ S1)
1010b: S1
1011b: ~ S0 + S1
1100b: S0
1101b: S0 + ~ S1
1110b: S0 + S1
1111b: 1 (weiß)

S0 ist die Nullschicht, S1 ist die erste Schicht. Die Wechselwirkung zwischen ihnen erfolgt unter Verwendung von Arithmetik- und Bitoperationen.

Inline-Schriftausgabe


 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 (); //back to graphic mode } 

Die Vollversion des Treibers finden Sie auf dem Github unter dem Link

Vielen Dank fürs Lesen!

Source: https://habr.com/ru/post/de483538/


All Articles