Somos amigos STM32 con pantalla LCD 1604 en bus I2C (biblioteca HAL)

Hola Habr!

En este art铆culo, me gustar铆a hablar sobre mi experiencia al conectar pantallas LCD al microcontrolador STM32 utilizando la biblioteca HAL en el bus I2C.

imagen

Conectar茅 la pantalla 1602 y 2004. Ambos tienen un adaptador I2C soldado basado en el chip PCF8574T. La placa de depuraci贸n ser谩 Nucleo767ZI y el entorno de desarrollo ser谩 STM32CubeIDE 1.3.0.

No hablar茅 en detalle sobre el principio de funcionamiento del bus I2C, le aconsejo que mire aqu铆 y aqu铆 .

Creamos el proyecto, seleccionamos la placa de depuraci贸n:

imagen

indicamos que usaremos I2C1. Tambi茅n conectar茅 UART5 para comunicarme con la placa, esto es necesario para recibir informaci贸n de la placa sobre la direcci贸n de pantalla.

imagen

imagen

En la misma ventana, puede ver los n煤meros de las patas a las que est谩 conectada la pantalla, en mi caso result贸 as铆:

imagen

para comenzar, conecte solo una pantalla, comenzar茅 con 1602. Adem谩s, conectar茅 el adaptador USB-UART CH340, conocido por los controladores arduin experimentados, para recibir datos de la placa.

imagen

Tenga en cuenta que el adaptador conecta RX a TX y TX a RX, el puente en el adaptador es 3.3V

imagen

Echemos un vistazo m谩s de cerca al trabajo con el chip y la pantalla PCF8574T. A continuaci贸n se muestra un diagrama esquem谩tico de un m贸dulo con una pantalla:

imagen

el chip PCF8574T tiene una funci贸n similar al registro de desplazamiento 74hc595: recibe bytes a trav茅s de la interfaz I2C y asigna los valores del bit correspondiente a sus salidas (P0-P7).

Considere qu茅 pines del microcircuito est谩n conectados a la pantalla y de qu茅 son responsables:

  • El terminal P0 del microcircuito est谩 conectado al terminal RS de la pantalla, que es responsable de recibir los datos de la pantalla (1) o las instrucciones de operaci贸n de la pantalla (0);
  • El pin P1 est谩 conectado a R \ W, si 1 - escribe datos en la pantalla, 0 - lee;
  • El terminal P2 est谩 conectado a CS: el terminal, tras el cambio de estado del cual se est谩 leyendo;
  • Conclusi贸n P3 - control de luz de fondo;
  • Conclusiones P4 - P7 se utilizan para transmitir datos a la pantalla.

Se pueden conectar varios dispositivos a un bus I2C al mismo tiempo. Para poder acceder a un dispositivo espec铆fico, cada uno de ellos tiene su propia direcci贸n, primero lo descubriremos. Si los contactos A1, A2 y A3 en la placa adaptadora no est谩n sellados, lo m谩s probable es que la direcci贸n sea 0x27, pero es mejor verificar. Para hacer esto, escribiremos una peque帽a funci贸n que mostrar谩 las direcciones de todos los dispositivos que est谩n conectados al bus I2C:

void I2C_Scan ()
{
	//  ,  
        HAL_StatusTypeDef res;                          
	//    
        char info[] = "Scanning I2C bus...\r\n";      
        //    UART	
        HAL_UART_Transmit(&huart5, (uint8_t*)info, strlen(info), HAL_MAX_DELAY);  
	/* &huart5 -   UART
	 * (uint8_t*)info -     
	 * strlen(info) -   
	 * HAL_MAX_DELAY - 
	 */
        //    
	for(uint16_t i = 0; i < 128; i++)              
	{
            // ,      i  
            res = HAL_I2C_IsDeviceReady(&hi2c1, i << 1, 1, HAL_MAX_DELAY);                   
	    //  , 
            if(res == HAL_OK)                              
	    {
	    	char msg[64];
	    	//   i,   ,     
                // 16 :
	    	snprintf(msg, sizeof(msg), "0x%02X", i);
	    	//    
	    	HAL_UART_Transmit(&huart5, (uint8_t*)msg, strlen(msg), HAL_MAX_DELAY);
	    	//    
	    	HAL_UART_Transmit(&huart5, (uint8_t*)"\r\n", 2, HAL_MAX_DELAY);
	    }
	    else HAL_UART_Transmit(&huart5, (uint8_t*)".", 1, HAL_MAX_DELAY);
	}
	HAL_UART_Transmit(&huart5, (uint8_t*)"\r\n", 2, HAL_MAX_DELAY);
}

Esta funci贸n sondea todas las direcciones de 0 a 127 y si se recibe una respuesta de esta direcci贸n, env铆a el n煤mero de direcci贸n en forma hexadecimal a UART.

Para comunicarme con la junta, utilizo el programa Termite. Por defecto, la velocidad UART del microcontrolador est谩 establecida en 115200, debe configurarla en termitas. Llamamos a la funci贸n en el cuerpo principal del programa, flasheamos la placa y nos conectamos en termitas a nuestro microcontrolador: los

imagen

puntos muestran todas las direcciones de las cuales no se recibi贸 la respuesta. La direcci贸n en mi pantalla es 0x26, ya que solde茅 el puente A0. Ahora conecte la segunda pantalla paralela a la primera y vea lo que dar谩 el programa:

imagen

Tenemos dos direcciones: 0x26 (pantalla 1602) y 0x27 (pantalla 2004). Ahora sobre c贸mo trabajar con la pantalla. El microcontrolador env铆a un byte de direcci贸n y todos los dispositivos conectados al bus lo comprueban con el suyo. Si coincide, entonces el m贸dulo inicia la comunicaci贸n con el microcontrolador. En primer lugar, debe configurar la pantalla: desde d贸nde ir谩 el recuento de caracteres y en qu茅 direcci贸n, c贸mo se comportar谩 el cursor, etc. Despu茅s de eso, ya ser谩 posible transmitir informaci贸n para mostrar a la pantalla. La peculiaridad es que solo podemos usar 4 bits para transmitir informaci贸n, es decir Los datos deben dividirse en dos partes. Los datos se almacenan en los bits altos (4-7), y los bits bajos se usan para indicar si la luz de fondo se encender谩 (3 bits), si los datos vienen para la salida o la configuraci贸n de la pantalla (salida RS, 0 bits) y 2 bits, por el cambio en el que tiene lugar la lectura, es decire para enviar 1 byte de datos, debe enviar 4 bytes: el primer byte contendr谩 4 bits de informaci贸n, el segundo bit al estado 1, el segundo byte es una repetici贸n del primero, solo el segundo bit al estado 0. Los bytes 3 y 4 son similares, solo que contienen segunda mitad de los datos. Suena un poco incomprensible, te mostrar茅 un ejemplo:

void I2C_send(uint8_t data, uint8_t flags)
{
	HAL_StatusTypeDef res;
	 //     
        for(;;) {
                // ,      lcd_addr                                                                                        
	        res = HAL_I2C_IsDeviceReady(&hi2c1, LCD_ADDR, 1, HAL_MAX_DELAY);               
	         //  ,     
                if(res == HAL_OK) break;                                                  
	    }
        //    1111 0000      0  3,    4  7
	uint8_t up = data & 0xF0;   
        //   ,  data   4                	
        uint8_t lo = (data << 4) & 0xF0;          
                                          
	uint8_t data_arr[4];
         // 4-7   ,  0-3   
	data_arr[0] = up|flags|BACKLIGHT|PIN_EN; 
         //  ,       0  
	data_arr[1] = up|flags|BACKLIGHT;         
	data_arr[2] = lo|flags|BACKLIGHT|PIN_EN;
	data_arr[3] = lo|flags|BACKLIGHT;

	HAL_I2C_Master_Transmit(&hi2c1, LCD_ADDR, data_arr, sizeof(data_arr), HAL_MAX_DELAY);
	HAL_Delay(LCD_DELAY_MS);
}

Tom茅moslo en orden. Al principio, hay variables que almacenan la direcci贸n de visualizaci贸n, y bits de configuraci贸n que deben enviarse cada vez junto con los datos. En la funci贸n de env铆o, primero verificamos si hay un m贸dulo en la direcci贸n registrada. En caso de recibir el mensaje HAL_OK, comenzamos a formar bytes para enviar. Al comienzo del byte que enviaremos, es necesario dividirlo en dos partes, escribir ambas en los bits altos. Supongamos que queremos que la pantalla muestre el car谩cter 's', en el sistema binario es 1110011 ( calculadora) Usando la operaci贸n l贸gica y escribimos = 01110000 en la variable, es decir escribe solo los bits m谩s significativos. Los bits de orden inferior se desplazan a la izquierda 4 caracteres al principio, y luego se escriben en la variable lo = 00110000. A continuaci贸n, formamos una matriz de 4 bytes que contienen informaci贸n sobre el car谩cter que se mostrar谩. Ahora asignamos bits de configuraci贸n (0-3 bits) a los bytes existentes. Despu茅s de eso, enviamos el byte de direcci贸n y 4 bytes de informaci贸n a la pantalla usando la funci贸n HAL_I2C_Master_Transmit ();

Pero no se apresure a descargar el programa, porque al principio debe establecer la configuraci贸n de la pantalla. El sitio tiene una excelente tabla traducida con comandos para personalizar la pantalla. Despu茅s de verificarlo con la documentaci贸n, llegu茅 a la siguiente configuraci贸n que es 贸ptima para m铆:

  I2C_send(0b00110000,0);   // 8  
  I2C_send(0b00000010,0);   //     
  I2C_send(0b00001100,0);   //   ,  
  I2C_send(0b00000001,0);   //  

Colocamos estos comandos antes del comienzo de un bucle infinito, de modo que la configuraci贸n se env铆e una vez antes de comenzar a trabajar (como la configuraci贸n de vac铆o en arduinki). La funci贸n I2C_send, adem谩s del byte, requiere que especifique si se enviar谩n las configuraciones de visualizaci贸n o los datos. Si el segundo argumento de la funci贸n es 0, entonces la configuraci贸n, y si es 1, entonces los datos.

Y el 煤ltimo toque: necesita una funci贸n que env铆e una fecha l铆mite car谩cter por car谩cter. Aqu铆 todo es bastante simple:

void LCD_SendString(char *str)
{
    // *char    
    //    
	while(*str) 
        {                                   
                //    
		I2C_send((uint8_t)(*str), 1); 
                //     1               
                str++;                                     
        }
}

Despu茅s de haber reunido todas estas funciones juntas, puede escribir:

  LCD_SendString("  Hello");
  I2C_send(0b11000000,0);   //  
  LCD_SendString("    Habr");

imagen

Bueno, descubrimos la pantalla 1602, ahora 2004. La diferencia entre ellos es m铆nima, incluso este c贸digo funcionar谩 bien. Toda la diferencia se reduce a organizar las direcciones de las celdas en la pantalla. En ambas pantallas, la memoria contiene 80 celdas, en la pantalla 1602, las primeras 16 celdas son responsables de la primera l铆nea y las celdas 40 a 56 son responsables de la segunda l铆nea. Las celdas de memoria restantes no se muestran, por lo que si env铆a 17 caracteres a la pantalla, la 煤ltima no se transferir谩 en la segunda l铆nea, y se grabar谩 en una celda de memoria que no tiene salida a la pantalla. Un poco m谩s claro, la memoria se organiza de la siguiente manera:

imagen

para usar el avance de l铆nea, utilic茅 el comando I2C_send (0b11000000,0); , solo va a 40 celdas. La exhibici贸n de 2004 es m谩s interesante.

La primera fila son las celdas 1 a 20
La segunda fila son las celdas 40 a 60
La tercera fila: celdas del 21 al 40
La cuarta fila: celdas del 60 al 80,
es decir si envias un comando

LCD_SendString("___________________1___________________2___________________3___________________4");

Obtenemos lo siguiente:

imagen

Para organizar las transiciones entre l铆neas, debe mover manualmente el cursor a la celda de memoria deseada, o puede complementar mediante programaci贸n la funci贸n. Hasta ahora me he decidido por la versi贸n manual:

  I2C_send(0b10000000,0);   //   1 
  LCD_SendString("  Hello Habr");
  I2C_send(0b11000000,0);   //   2 
  LCD_SendString(" STM32 + LCD 1602");
  I2C_send(0b10010100,0);   //   3 
  LCD_SendString(" +LCD 2004A");
  I2C_send(0b11010100,0);   //   4 
  LCD_SendString(" library HAL");

Resultado:

imagen

eso es probablemente todo con estas pantallas, enlaces 煤tiles gracias a los cuales pude resolverlo todo:

  1. El c贸digo se ve铆a mucho aqu铆
  2. Las tablas para la configuraci贸n de visualizaci贸n se miraron aqu铆
  3. El procedimiento fue mirado aqu铆

Programa y hoja de datos

PS: no olvide ajustar el brillo de la pantalla de antemano.

All Articles