Buen dia a todos. En este artículo analizaremos la conexión de la pantalla TFT ER-TFT101-1 (10 pulgadas, controlador RA8876) a la placa de descubrimiento STM32F429L a través de una interfaz paralela 8080 de 16 bits utilizando el módulo FMC (controlador de memoria flexible).


Acerca del ensamblaje de la pantalla
El ER-TFT101-1 de EastRising es un conjunto de una matriz TFT de 10 pulgadas con una resolución de 1024x600 y una placa con un controlador RA8876. En la placa base con el controlador, se enruta toda la potencia necesaria, hay una memoria SD-RAM de 16 megabytes (bus de 16 bits, frecuencia máxima de 166 MHz, capacidad máxima de 64 MB), hay una ranura para tarjeta microSD estándar. Hay huellas vacías debajo de EEPROM con fuentes externas y debajo de memoria flash para imágenes con conectores de salida para programarlas. Además, opcionalmente se puede instalar un panel táctil resistivo o capacitivo en el conjunto.
En la placa está el controlador RAiO RA8876 de gama alta con una frecuencia máxima de funcionamiento de 120 MHz, que, si se desea, puede funcionar como un microcontrolador de control. Puede escribir un pequeño programa (solo 12 instrucciones) y guardarlo en una memoria flash externa. Cuando se inicia la pantalla, este programa comenzará a ejecutarse en primer lugar, esencialmente duplicando todas las opciones de control a través de la interfaz externa.

RA8876 no tiene su propia RAM y, por lo tanto, utiliza una memoria SD-RAM externa. Puede leer imágenes de la memoria flash usando DMA y cargarlo en su búfer de cuadros y tiene una configuración muy flexible. El controlador está conectado a la matriz misma mediante una interfaz RGB estándar de 18 bits de ancho. Solo se utilizan 6 bits por canal rojo, 6 bits por canal verde y 6 bits por canal azul. Los dos bits inferiores de cada canal no se utilizan, lo que en teoría da 262.144 colores.
El módulo DMA del RA8876 es muy similar al DMA2D de STM: puede copiar secciones rectangulares de memoria de un lugar a otro, con conversión de color, transparencia y otros chips.
RA8876 también tiene fuentes incorporadas en inglés y chino (8x16,12x24,16x32 píxeles) con configuraciones flexibles para su visualización (rotación, escala, etc.) y una interfaz de matriz separada (5 x 5) para botones de hardware (para dispositivos independientes y no solo usar) con un montón de configuraciones, como presionar larga y brevemente, activar la pantalla presionando un botón y presionando varios botones al mismo tiempo.
Hay una función de imagen en imagen (sin soporte de transparencia) para mostrar las ventanas emergentes y los menús.
El propio controlador puede dibujar primitivas gráficas, como cuadrado, círculo, curva, óvalo, triángulo, cuadrado redondeado, con y sin relleno. Por cierto, en RA8875 y RA8876 hay un pequeño error para llenar el triángulo, y cada controlador tiene el suyo. Pero a RAiO no le importó nada desde el alto campanario ... Intenté escribirles una carta de alguna manera, así que ni siquiera respondieron. Dibujar tales primitivas le permite crear hermosos gráficos incluso con un microcontrolador lento.
Con el mundo exterior, el RA8876 se comunica a través de 8080/6800 8/16 bit, 3/4 hilos SPI e interfaces I2C. Además, el chip del controlador en sí puede actuar como un maestro SPI e I2C. En RA8876, hay dos salidas PWM que se pueden usar para un control flexible de la luz de fondo. La frecuencia máxima de SPI CLK se declara a 66 MHz con una frecuencia de controlador de 120 MHz, que en teoría proporciona 6 cuadros por segundo de una actualización de pantalla completa (a 1024 x 600 x 16 bits). Esta conexión fue probada por mí y demostró que tiene derecho a la vida si no mostramos el video en la pantalla.
En nuestro caso, conectaremos la pantalla utilizando el protocolo 8080 con un ancho de 16 bits al STM32F429ZIT6 a través del módulo FMC (controlador de memoria flexible), lo que nos permitirá obtener más velocidad de llenado de pantalla y menos carga en el microcontrolador.
Configuración de pines 8080 y FMC
Observaremos el diagrama de conexión para 8080 en la hoja de datos en la pantalla:

Observamos los pines necesarios para conectarse al STM32 en CubeMX. Estamos interesados en el banco # 1 (NOR Flash / PSRAM / SRAM / ROM / LDC 1).

Con respecto a XnWAIT en la hoja de datos, puede leer lo siguiente:
La velocidad de escritura continua de datos determina la velocidad de actualización de la pantalla. El intervalo de ciclo a ciclo debe ser mayor que 5 del período de reloj del sistema si el usuario no adopta XnWait para insertar el estado de espera. Si se supera la especificación, los datos se perderán o la función fallará si no se utiliza el mecanismo xnwait.
Literalmente, entre los ciclos operativos del protocolo 8080, se debe insertar un retraso de 5 fragmentos del sistema RA8876 si el usuario no utiliza el mecanismo XnWAIT para esperar el lanzamiento de RA8876. Usaremos este pin una vez más, como En la práctica, traté de insertar un retraso de cinco ciclos, y no funcionó.
En lugar de un bus de dirección completa para la unidad FMC, usamos solo un pin A16.
- Configuramos los pines de datos (D0 - D15) como una función alternativa # 12, como el grupo de empuje, la velocidad máxima y sin ningún tipo de tirantes.
- Los pines XnWAIT, XnWR, XnRD, XA0 y XnCS están configurados como una función alternativa n. ° 12, como push-pull con elevación al plus (PULL UP).
- Configuramos XnRST como un GPIO normal sin un tirante (está en el tablero).
- XnINTR es configurable como un GPIO a la entrada con una elevación al plus.
También conecté la luz de fondo al 100% sin controlarla a través de PWM. Para hacer esto, el pin # 14 en el conector del ensamblaje de la pantalla está conectado a VDD.
No proporciono el código de configuración para los pines, Utilizo mis propias bibliotecas de configuración, y la configuración de GPIO en sí ya ha sido masticada cientos de veces en el concentrador y en otras fuentes.
La biblioteca de inicialización está aquí .
Configuraciones de FMC
Tres bancos para cada banco son responsables de configurar los bancos del módulo FMC (NOR Flash / PSRAM / SRAM / ROM / LDC 1). Estos son FMC_BCRx, FMC_BTRx y FMC_BWTRx. En el STM32F429 MK define, los registros FMC_BCRx y FMC_BTRx se combinan en una matriz común llamada FMC_BTCR con ocho elementos, donde el elemento cero es FMC_BCR1, el primer elemento es FMC_BTR1, el segundo elemento es FMC_BCR2, y así sucesivamente. FMC_BWTRx se combinan en una matriz FMC_BWTR con siete elementos, aunque debería haber cuatro. No me preguntes por qué ...
FMC_BCRx contiene configuraciones básicas, FMC_BTRx contiene temporizaciones generales y FMC_BWTRx contiene temporizaciones separadas para la lectura, si el dispositivo lo requiere.
Diagrama de tiempos y tiempos para la interacción de STM32F429 y RA8876.

Para facilitar la configuración, insertaremos los tiempos del protocolo 8080 en constantes. Elegí los tiempos empíricamente, reduciendo un poco el valor, porque La tabla de tiempos con una hoja de datos es más como un caballo esférico en el vacío.
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;
El valor del registro FMC_BTCRx después del restablecimiento es 0x0FFF FFFF, es decir Se establecen los tiempos máximos. Si tiene una nueva pantalla o memoria, simplemente reduzca los tiempos e intente ejecutar.
Visualización de inicialización
Trabajar con la pantalla se reduce a leer o escribir en ciertas áreas de la memoria. FMC se encarga del resto del trabajo. Para simplificar el trabajo, definimos dos definiciones:
#define LCD_DATA 0x60020000 #define LCD_REG 0x60000000
Y ahora describimos las funciones de bajo nivel:
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; };
Además, la función de inicialización de la pantalla en sí. El código se toma del proveedor de pantallas y se rediseñó ligeramente para adaptarse a sus necesidades. Las subfunciones ocupan una gran cantidad y en este artículo no las daré.
Enlace al conductor en el github void RA8876_Init(void) { RA8876_PLL_Init ();
Sobre framebuffer y área activa un poco más. Para estas dos configuraciones, definimos las siguientes definiciones:
#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) es la dirección de inicio en la SDRAM. En 16 megabytes de memoria, podemos colocar 13 capas completas en tamaño 1228800 bytes (1024 * 600 * 2).
La función Frame_Buffer_Start_Address establece el área de memoria inicial para el framebuffer (lo que se muestra actualmente).
La función Canvas_Window_Start_Address establece el área de memoria inicial para el lienzo. Además, el lienzo puede ser más grande que el framebuffer, por lo que puede desplazar la imagen en la pantalla. Por ejemplo, para un juego de plataformas, puede hacer un lienzo largo que mida 13312 x 600 píxeles y luego desplazarse horizontalmente moviendo el framebuffer horizontalmente a través de él.
Si compara la salida de gráficos con LTDM de STM32, entonces no todo es tan color de rosa aquí. Al mismo tiempo, el controlador mismo puede mostrar solo una capa final (buffer) y LTDC a la vez dos, mezclándolas, sin requerir su participación en este proceso.
Dibujando primitivas
Código de un proveedor de pantallas con características preconstruidas:
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 ejemplo, para dibujar un triángulo relleno, establecemos tres puntos: Triangle_Point1_XY, Triangle_Point2_XY, Triangle_Point2_XY y ejecutamos la función Start_Triangle_Fill.
Trabajar con DMA
Por conveniencia, escribí mi función con una estructura como parámetro pasado:
struct GFX_BTE_options { unsigned long layer_s0_addr;
Descripción de los códigos de operación (CÓDIGO DE OPERACIÓN):0000: escribir en la memoria con ROP usando MK.
0001: Leer memoria sin ROP usando MK.
0010: Copie un bloque de memoria hacia adelante usando ROP.
0011: Copiar un bloque de memoria en la dirección inversa usando ROP.
0100: Escribir en la memoria (con transparencia) sin ROP usando MK.
0101: Copie (mueva) un bloque de memoria (con transparencia) en dirección hacia adelante sin ROP.
0110: Rellenar con un patrón usando ROP.
0111: Rellene la plantilla con chromakey.
1000: extensión de color
1001: color mejorado con transparencia
1010: mover un bloque de memoria hacia adelante con mezcla alfa
1011: Escribir en la memoria con mezcla alfa con MK.
1100: llena el área de memoria con un color sólido.
1101: reservado
1110: Reservado
1111: reservado
Descripción de códigos ráster (CÓDIGO ROP):0000b: 0 (negro)
0001b: ~ S0 ・ ~ S1 o ~ (S0 + S1)
0010b: ~ S0 ・ S1
0011b: ~ S0
0100b: S0 ・ ~ S1
0101b: ~ S1
0110b: S0 ^ S1
0111b: ~ S0 + ~ S1 o ~ (S0 ・ S1)
1000b: S0 ・ S1
1001b: ~ (S0 ^ S1)
1010b: S1
1011b: ~ S0 + S1
1100b: S0
1101b: S0 + ~ S1
1110b: S0 + S1
1111b: 1 (blanco)
S0 es la capa cero, S1 es la primera capa. La interacción entre ellos ocurre mediante operaciones aritméticas y de bits.
Salida de fuente en línea
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 ();
La versión completa del controlador se puede encontrar en el github en el enlaceGracias a todos por leer!