Conexão de um monitor ER-TFT101-1 de 10 "ao STM32F429 via FMC

Bom dia a todos. Neste artigo, analisaremos a conexão do monitor TFT ER-TFT101-1 (10 polegadas, driver RA8876) à placa STM32F429L Discovery através de uma interface paralela 8080 de 16 bits usando o módulo FMC (controlador de memória flexível).




Sobre montagem de exibição


O ER-TFT101-1 da EastRising é um conjunto de uma matriz TFT de 10 polegadas com uma resolução de 1024x600 e uma placa com um driver RA8876. Na placa-mãe com o driver, toda a energia necessária é roteada, há uma memória SD-RAM de 16 megabytes (barramento de 16 bits, frequência máxima de 166 MHz, capacidade máxima de 64 MB), há um slot para cartão microSD padrão. Existem pegadas vazias na EEPROM com fontes externas e na memória flash para imagens com conectores de saída para programá-las. Além disso, um painel de toque resistivo ou capacitivo pode opcionalmente ser instalado na montagem.

Na placa, está o driver RAiO RA8876 de ponta, com uma freqüência operacional máxima de 120 MHz, que, se desejado, pode funcionar como um microcontrolador de controle. Você pode escrever um pequeno programa (apenas 12 instruções) e colocá-lo em uma memória flash externa. Quando o visor iniciar, este programa começará a ser executado, duplicando essencialmente todas as opções de controle por meio da interface externa.



O RA8876 não possui sua própria RAM e, portanto, usa memória SD-RAM externa. Ele pode ler imagens da memória flash usando o DMA e carregá-lo no buffer de quadros e possui uma configuração muito flexível. O driver é conectado à própria matriz usando uma interface RGB padrão de 18 bits. Apenas 6 bits por canal vermelho, 6 bits por canal verde e 6 bits em azul são usados. Os dois bits inferiores de cada canal não são usados, o que, em teoria, fornece 262.144 cores.

O módulo DMA do RA8876 é muito semelhante ao DMA2D da STM - ele pode copiar seções retangulares da memória de um lugar para outro, com conversão de cores, transparência e outros chips.

O RA8876 também possui fontes inglesas e chinesas integradas (8x16,12x24,16x32 pixels) com configurações flexíveis para exibição (rotação, escala, etc.) e uma interface matricial separada (5 x 5) para botões de hardware (para Não use somente) com várias configurações, como pressão longa e curta, ative a tela pressionando um botão e pressionando vários botões ao mesmo tempo.

Há uma função picture-in-picture (sem suporte à transparência) para exibir janelas e menus pop-up.

O próprio driver pode desenhar primitivas gráficas, como quadrado, círculo, curva, oval, triângulo, quadrado arredondado, com e sem preenchimento. A propósito, no RA8875 e no RA8876 há um pequeno bug para preencher o triângulo, e cada driver possui o seu. Mas o RAiO não dava a mínima para a torre sineira ... tentei escrever uma carta para eles de alguma forma, para que eles nem respondessem. Desenhar essas primitivas permite criar belos gráficos, mesmo com um microcontrolador lento.

No mundo externo, o RA8876 se comunica através de interfaces 8080/6800 8/16 bits, 3/4 SPI e I2C. Além disso, o próprio chip do driver pode atuar como um mestre SPI e I2C. No RA8876, existem duas saídas PWM que podem ser usadas para controle flexível da luz de fundo. A frequência SPI CLK máxima é declarada em 66 MHz com uma frequência de driver de 120 MHz, o que, em teoria, fornece 6 quadros por segundo de uma atualização em tela cheia (em 1024 x 600 x 16 bits). Essa conexão foi testada por mim e mostrou que tem direito à vida se não exibirmos o vídeo na tela.

No nosso caso, conectaremos o monitor usando o protocolo 8080 com uma largura de 16 bits ao STM32F429ZIT6 através do módulo FMC (controlador de memória flexível), o que nos permitirá obter mais velocidade de preenchimento de tela e menos carga no microcontrolador.

Configuração dos pinos 8080 e FMC


Veremos o diagrama de conexão do 8080 na folha de dados no visor:



Examinamos os pinos necessários para conectar ao STM32 no CubeMX. Estamos interessados ​​no banco 1 (NOR Flash / PSRAM / SRAM / ROM / LDC 1).



Em relação ao XnWAIT na folha de dados, você pode ler o seguinte:
A velocidade de gravação contínua de dados determina a velocidade de atualização da tela. O intervalo ciclo a ciclo deve ser maior que 5 do período do relógio do sistema se o usuário sem adotar o XnWait para inserir o estado de espera. A especificação pode causar perda de dados ou falha na função se o mecanismo xnwait não for usado.
Literalmente, entre os ciclos operacionais do protocolo 8080, um atraso de 5 fragmentos do sistema RA8876 deve ser inserido se o usuário não usar o mecanismo XnWAIT para aguardar o lançamento do RA8876. Vamos usar este pino mais uma vez, como na prática, tentei inserir um atraso de cinco ciclos e não funcionou.

Em vez de um barramento de endereço completo para a unidade FMC, usamos apenas um pino A16.

  1. Configuramos os pinos de dados (D0 - D15) como uma função alternativa nº 12, como push-pool, velocidade máxima e sem suspensórios.
  2. Os pinos XnWAIT, XnWR, XnRD, XA0 e XnCS são configurados como uma função alternativa nº 12, como empurrar e puxar com um elevador até o mais (PULL UP).
  3. Configuramos o XnRST como um GPIO comum sem um suspender (ele está no próprio quadro).
  4. O XnINTR é configurável como um GPIO para a entrada com um aumento para o mais.

Também conectei a luz de fundo a 100% sem controlá-la via PWM. Para fazer isso, o pino 14 no conector do conjunto da tela está conectado ao VDD.

Não forneço o código de configuração para os pinos, porque Eu uso minhas próprias bibliotecas de configuração, e a própria configuração do GPIO já foi mastigada centenas de vezes no hub e em outras fontes.

A biblioteca de inicialização está aqui .

Configurações FMC


Três bancos para cada banco são responsáveis ​​pela configuração dos bancos do módulo FMC (NOR Flash / PSRAM / SRAM / ROM / LDC 1). Estes são FMC_BCRx, FMC_BTRx e FMC_BWTRx. No STM32F429 MK define, os registros FMC_BCRx e FMC_BTRx são combinados em uma matriz comum chamada FMC_BTCR com oito elementos, em que o elemento zero é FMC_BCR1, o primeiro elemento é FMC_BTR1, o segundo elemento é FMC_BCR2 e assim por diante. FMC_BWTRx são combinados em uma matriz FMC_BWTR com sete elementos, embora deva haver quatro. Não me pergunte por que ...

FMC_BCRx contém configurações básicas, FMC_BTRx contém horários gerais e FMC_BWTRx contém horários separados para leitura, se o dispositivo exigir.

Diagrama de tempo e tempos para a interação de STM32F429 e RA8876.



Para facilitar a configuração, inseriremos os tempos do protocolo 8080 em constantes. Escolhi os horários empiricamente, reduzindo um pouco do valor, porque A tabela de tempo com uma folha de dados é mais como um cavalo esférico no vácuo.

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

O valor do registro FMC_BTCRx após a redefinição é 0x0FFF FFFF, ou seja, horários máximos são definidos. Se você tiver uma nova tela ou memória, reduza os tempos e tente executar.

Inicialização de exibição


Trabalhar com a tela se resume à leitura ou gravação em determinadas áreas da memória. A FMC cuida do resto do trabalho. Para simplificar o trabalho, definimos duas definições:

 #define LCD_DATA 0x60020000 #define LCD_REG 0x60000000 

E agora descrevemos as funções de baixo nível:

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

Além disso, a própria função de inicialização de exibição. O código é retirado do provedor de vídeo e foi ligeiramente redesenhado para atender às suas necessidades. As subfunções ocupam uma quantidade enorme e, neste artigo, não darei a elas.

Link para o driver no 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 (); } 

Sobre framebuffer e área ativa um pouco mais. Para essas duas configurações, definimos as seguintes definições:

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

Cada página (PAGEx_START_ADDR) é o endereço inicial no SDRAM. Em 16 megabytes de memória, podemos colocar 13 camadas completas no tamanho 1228800 bytes (1024 * 600 * 2).
A função Frame_Buffer_Start_Address define a área de memória inicial do framebuffer (o que está sendo exibido no momento).

A função Canvas_Window_Start_Address define a área de memória inicial para a tela. Além disso, a tela pode ser maior que o buffer de moldura, para que você possa rolar a imagem na tela. Por exemplo, para um jogo de plataforma, você pode criar uma tela longa medindo 13312 x 600 pixels e rolar horizontalmente, movendo o buffer de moldura horizontalmente sobre ele.

Se você comparar a saída gráfica com o LTDM do STM32, tudo aqui não é tão bom. Ao mesmo tempo, o próprio driver pode exibir apenas uma camada final (buffer) e LTDC de uma vez duas, misturando-as, sem exigir sua participação nesse processo.

Primitivas de desenho


Código de um provedor de vídeo com recursos pré-criados:

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

Por exemplo, para desenhar um triângulo preenchido, definimos três pontos: Triangle_Point1_XY, Triangle_Point2_XY, Triangle_Point2_XY e executamos a função Start_Triangle_Fill.

Trabalhar com DMA


Por conveniência, escrevi minha função com uma estrutura como um parâmetro passado:

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

Descrição dos códigos de operação (CÓDIGO DE OPERAÇÃO):

0000: Grava na memória com o ROP usando MK.
0001: Leia memória sem ROP usando MK.
0010: Copie um bloco de memória na direção direta usando ROP.
0011: Copiando um bloco de memória na direção reversa usando ROP.
0100: Grava na memória (com transparência) sem ROP usando MK.
0101: Copie (mova) um bloco de memória (com transparência) na direção direta sem ROP.
0110: Preencha com um padrão usando ROP.
0111: Preencha o modelo com chromakey.
1000: Extensão de Cor
1001: Cor aprimorada com transparência
1010: Movendo um bloco de memória para frente com mistura alfa
1011: Escreva na memória com mistura alfa usando MK.
1100: Preenche a área da memória com uma cor sólida.
1101: Reservado
1110: Reservado
1111: Reservado

Descrição dos códigos raster (CÓDIGO ROP):

0000b: 0 (preto)
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 (branco)

S0 é a camada zero, S1 é a primeira camada. A interação entre eles ocorre usando operações aritméticas e de bits.

Saída de fonte embutida


 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 } 

A versão completa do driver pode ser encontrada no github no link

Obrigado a todos pela leitura!

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


All Articles