Connexion d'un écran 10 "ER-TFT101-1 au STM32F429 via FMC

Bonne journée à tous. Dans cet article, nous analyserons la connexion de l'écran TFT ER-TFT101-1 (10 pouces, pilote RA8876) à la carte Discovery STM32F429L via une interface parallèle 8080 16 bits à l'aide du module FMC (flexible memory controller).




A propos de l'assemblage d'écran


Le ER-TFT101-1 d'EastRising est un assemblage d'une matrice TFT de 10 pouces avec une résolution de 1024x600 et d'une carte avec un pilote RA8876. La carte avec le pilote dispose de toutes les alimentations nécessaires, la mémoire SD-RAM est de 16 mégaoctets (bus 16 bits, fréquence maximale de 166 MHz, capacité maximale de 64 Mo), il y a un emplacement pour carte microSD standard. Il y a des empreintes vides sous EEPROM avec des polices externes et sous la mémoire flash pour les images avec des connecteurs de sortie pour les programmer. De plus, un panneau tactile résistif ou capacitif peut être installé en option sur l'ensemble.

Sur la carte se trouve le pilote RAiO RA8876 haut de gamme avec une fréquence de fonctionnement maximale de 120 MHz, qui, si vous le souhaitez, peut lui-même fonctionner comme un microcontrôleur de contrôle. Vous pouvez écrire un petit programme (seulement 12 instructions) et le mettre dans une mémoire flash externe. Lorsque l'affichage démarre, ce programme commencera à s'exécuter en premier lieu, dupliquant essentiellement toutes les options de contrôle via l'interface externe.



RA8876 n'a pas sa propre RAM et utilise donc une mémoire SD-RAM externe. Il peut lire des images à partir de la mémoire flash à l'aide de DMA et les charger dans son tampon de trame et a une configuration très flexible de celui-ci. Le pilote est connecté à la matrice elle-même à l'aide d'une interface RVB standard de 18 bits de large. Seuls 6 bits par canal rouge, 6 bits par canal vert et 6 bits bleu sont utilisés. Les deux bits inférieurs de chaque canal ne sont pas utilisés, ce qui donne en théorie 262 144 couleurs.

Le module DMA du RA8876 est très similaire au DMA2D de STM - il peut copier des sections rectangulaires de mémoire d'un endroit à un autre, avec conversion de couleur, transparence et autres puces.

Le RA8876 possède également des polices anglaises et chinoises intégrées (8x16,12x24,16x32 pixels) avec des paramètres flexibles pour leur affichage (rotation, échelle, etc.) et une interface matricielle séparée (5 x 5) pour les boutons matériels (pour les non seulement utiliser) avec un tas de paramètres, comme une pression longue et courte, réveiller l'affichage en appuyant sur un bouton et en appuyant sur plusieurs boutons en même temps.

Il existe une fonction d'image dans l'image (sans prise en charge de la transparence) pour afficher les fenêtres contextuelles et les menus.

Le pilote lui-même peut dessiner des primitives graphiques, telles que carré, cercle, courbe, ovale, triangle, carré arrondi, avec et sans remplissage. Soit dit en passant, dans RA8875 et RA8876, il y a un petit bug pour remplir le triangle, et chaque pilote a le sien. Mais RAiO n'en a rien à foutre du haut clocher ... J'ai essayé de leur écrire une lettre d'une manière ou d'une autre, alors ils n'ont même pas répondu. Le dessin de telles primitives vous permet de créer de beaux graphismes même avec un microcontrôleur lent.

Avec le monde extérieur, le RA8876 communique via des interfaces 8080/6800 8/16 bits, 3/4 fil SPI et I2C. De plus, la puce du pilote elle-même peut agir en tant que maître SPI et I2C. Dans le RA8876, deux sorties PWM peuvent être utilisées pour un contrôle flexible du rétroéclairage. La fréquence SPI CLK maximale est déclarée à 66 MHz avec une fréquence pilote de 120 MHz, ce qui donne en théorie 6 images par seconde d'une mise à jour plein écran (à 1024 x 600 x 16 bits). Cette connexion a été testée par mes soins et a montré qu'elle a droit à la vie si on n'affiche pas la vidéo à l'écran.

Dans notre cas, nous allons connecter l'affichage en utilisant le protocole 8080 avec une largeur de 16 bits au STM32F429ZIT6 via le module FMC (contrôleur de mémoire flexible), ce qui nous permettra d'obtenir plus de vitesse de remplissage d'écran et moins de charge sur le microcontrôleur.

Configuration des broches 8080 et FMC


Nous allons regarder le schéma de connexion pour 8080 dans la fiche technique à l'écran:



Nous examinons les broches nécessaires pour la connexion au STM32 dans CubeMX. Nous sommes intéressés par la banque # 1 (NOR Flash / PSRAM / SRAM / ROM / LDC 1).



Concernant XnWAIT dans la fiche technique, vous pouvez lire ce qui suit:
La vitesse d'écriture continue des données détermine la vitesse de mise à jour de l'affichage. L'intervalle de cycle à cycle doit être supérieur à 5 de la période d'horloge système si l'utilisateur sans adopter XnWait pour insérer l'état d'attente. Une spécification excessive peut entraîner la perte de données ou l'échec de la fonction si le mécanisme xnwait n'est pas utilisé.
Littéralement, entre les cycles de fonctionnement du protocole 8080, un délai de 5 unités système RA8876 doit être inséré si l'utilisateur n'utilise pas le mécanisme XnWAIT pour attendre la sortie de RA8876. Nous utiliserons à nouveau cette épingle, car en pratique, j'ai essayé d'insérer un retard de cinq cycles, et cela n'a pas fonctionné.

Au lieu d'un bus d'adresse complet pour l'unité FMC, nous utilisons une seule broche A16.

  1. Nous configurons les broches de données (D0 - D15) comme une fonction alternative # 12, comme le pool de poussée, la vitesse maximale et sans bretelles.
  2. Les broches XnWAIT, XnWR, XnRD, XA0 et XnCS sont configurées comme une fonction alternative # 12, telle que push-pull avec un ascenseur vers le plus (PULL UP).
  3. Nous configurons XnRST comme un GPIO ordinaire sans bretelle (il est sur la carte elle-même).
  4. XnINTR est configurable en tant que GPIO à l'entrée avec un ascenseur au plus.

J'ai également connecté le rétro-éclairage à 100% sans le contrôler via PWM. Pour ce faire, la broche n ° 14 du connecteur de l'ensemble écran est connectée au VDD.

Je ne fournis pas le code de configuration pour les broches, car J'utilise mes propres bibliothèques de configuration, et la configuration GPIO elle-même a déjà été centrée sur le concentrateur et sur d'autres sources.

La bibliothèque d'initialisation est ici .

Paramètres FMC


Trois banques pour chaque banque sont responsables de la configuration des banques du module FMC (NOR Flash / PSRAM / SRAM / ROM / LDC 1). Ce sont FMC_BCRx, FMC_BTRx et FMC_BWTRx. Dans les définitions STM32F429 MK, les registres FMC_BCRx et FMC_BTRx sont combinés en un tableau commun appelé FMC_BTCR avec huit éléments, où l'élément zéro est FMC_BCR1, le premier élément est FMC_BTR1, le deuxième élément est FMC_BCR2, etc. FMC_BWTRx sont combinés en un tableau FMC_BWTR avec sept éléments, bien qu'il devrait y en avoir quatre. Ne me demandez pas pourquoi ...

FMC_BCRx contient des paramètres de base, FMC_BTRx contient des temporisations générales et FMC_BWTRx contient des temporisations distinctes pour la lecture, si le périphérique l'exige.

Diagramme de synchronisation et synchronisations pour l'interaction de STM32F429 et RA8876.



Pour faciliter la configuration, nous allons insérer les temporisations du protocole 8080 dans les constantes. J'ai choisi les horaires moi-même de manière empirique, en réduisant un peu la valeur, car La table de chronométrage avec une fiche technique ressemble plus à un cheval sphérique dans le vide.

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

La valeur du registre FMC_BTCRx après réinitialisation est 0x0FFF FFFF, c'est-à-dire des durées maximales sont définies. Si vous avez un nouvel affichage ou une nouvelle mémoire, réduisez simplement les temps et essayez de lancer.

Initialisation de l'affichage


Travailler avec l'écran revient à lire ou à écrire dans certaines zones de la mémoire. FMC s'occupe du reste du travail. Pour simplifier le travail, nous définissons deux définitions:

 #define LCD_DATA 0x60020000 #define LCD_REG 0x60000000 

Et maintenant, nous décrivons les fonctions de bas niveau:

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

De plus, la fonction d'initialisation de l'affichage elle-même. Le code provient du fournisseur d'affichage et est légèrement repensé pour répondre à vos besoins. Les sous-fonctions occupent une énorme quantité et dans cet article, je ne les donnerai pas.

Lien vers le pilote sur le 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 (); } 

À propos du framebuffer et de la zone active un peu plus. Pour ces deux paramètres, nous définissons les définitions suivantes:

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

Chaque page (PAGEx_START_ADDR) est l'adresse de départ dans la SDRAM. En 16 mégaoctets de mémoire, nous pouvons placer 13 couches complètes de taille 1228800 octets (1024 * 600 * 2).
La fonction Frame_Buffer_Start_Address définit la zone de mémoire initiale pour le framebuffer (ce qui est actuellement affiché).

La fonction Canvas_Window_Start_Address définit la zone mémoire initiale du canevas. De plus, la toile peut être plus grande que le tampon de cadre, vous pouvez donc faire défiler l'image sur l'écran. Par exemple, pour un jeu de plateforme, vous pouvez créer un long canevas mesurant 13312 x 600 pixels, puis le faire défiler horizontalement, en déplaçant horizontalement le framebuffer.

Si vous comparez la sortie graphique avec LTDM de STM32, alors tout n'est pas si rose ici. Dans le même temps, le pilote lui-même ne peut afficher qu'une seule couche finale (tampon) et LTDC immédiatement deux, en les mélangeant, sans nécessiter votre participation à ce processus.

Dessin de primitives


Code d'un fournisseur d'affichage avec des fonctionnalités prédéfinies:

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

Par exemple, pour dessiner un triangle plein, nous définissons trois points: Triangle_Point1_XY, Triangle_Point2_XY, Triangle_Point2_XY et exécutons la fonction Start_Triangle_Fill.

Travailler avec DMA


Pour plus de commodité, j'ai écrit ma fonction avec une structure comme paramètre passé:

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

Description des codes de fonctionnement (CODE D'OPÉRATION):

0000: Écrire en mémoire avec ROP en utilisant MK.
0001: Lire la mémoire sans ROP à l'aide de MK.
0010: Copiez un bloc de mémoire vers l'avant à l'aide de ROP.
0011: Copie d'un bloc mémoire en sens inverse à l'aide de ROP.
0100: écriture dans la mémoire (avec transparence) sans ROP à l'aide de MK.
0101: Copiez (déplacez) un bloc de mémoire (avec transparence) vers l'avant sans ROP.
0110: Remplissez avec un motif en utilisant ROP.
0111: remplissez le modèle de chromakey.
1000: Extension de couleur
1001: Couleur améliorée avec transparence
1010: Déplacement d'un bloc de mémoire vers l'avant avec le mélange alpha
1011: écriture dans la mémoire avec un mélange alpha à l'aide de MK.
1100: remplit la zone mémoire d'une couleur unie.
1101: Réservé
1110: Réservé
1111: Réservé

Description des codes raster (ROP CODE):

0000b: 0 (noir)
0001b: ~ S0 ・ ~ S1 ou ~ (S0 + S1)
0010b: ~ S0 ・ S1
0011b: ~ S0
0100b: S0 ・ ~ S1
0101b: ~ S1
0110b: S0 ^ S1
0111b: ~ S0 + ~ S1 ou ~ (S0 ・ S1)
1000b: S0 ・ S1
1001b: ~ (S0 ^ S1)
1010b: S1
1011b: ~ S0 + S1
1100b: S0
1101b: S0 + ~ S1
1110b: S0 + S1
1111b: 1 (blanc)

S0 est la couche zéro, S1 est la première couche. L'interaction entre eux se produit à l'aide d'opérations arithmétiques et binaires.

Sortie de police en ligne


 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 } 

La version complète du pilote peut être trouvée sur le github au lien

Merci à tous d'avoir lu!

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


All Articles