Una pantalla LCD estándar (con el chip controlador Hitachi HD44780) no sabe qué es el bus I2C. Habla en un protocolo paralelo muy antiguo y engorroso. Para no lidiar con eso, los ingenieros le sueldan en la parte trasera una "Mochila I2C" (Backpack). Este módulo contiene un chip PCF8574.
El PCF8574 es un Expansor de I/O de 8 bits. Tú le envías un solo byte por el cable I2C (SDA), y él traduce esos 8 bits en 8 pines físicos de salida lógica, controlando la pantalla en tu lugar. Su dirección estándar en la red I2C suele ser 0x27 o 0x3F.
Como solo tenemos 8 pines en el expansor, no podemos enviar los 8 bits de un caracter al mismo tiempo (porque la LCD requiere pines adicionales de control). La solución de la industria es utilizar el Modo a 4 Bits. Enviamos el caracter en dos partes: primero los 4 bits altos (High Nibble) y luego los 4 bits bajos (Low Nibble).
El cableado interno entre el expansor y la pantalla está estandarizado de la siguiente manera (del Bit 7 al Bit 0):
Ejercicio 1: Crea la librería Bare-Metal para la pantalla LCD. Utilizaremos la función i2c_escribir_byte() de tu driver. Deberemos enviar los comandos de inicialización mágica (0x33, 0x32, 0x28) requeridos por Hitachi para sincronizar la pantalla a 4-bits, y diseñaremos las funciones para posicionar el cursor y escribir cadenas de texto.
/**
* @file i2c_lcd_driver.c
* @brief Driver Bare-Metal para Pantalla LCD 20x4 con Módulo I2C PCF8574
*/
#include "stm32f401xe.h"
#include <stdint.h>
// Dirección I2C común del Expansor PCF8574. (Modificada por 0x27 << 1)
#define LCD_I2C_ADDR 0x4E
// Mapas de Bits del PCF8574
#define LCD_BIT_RS 0x01 // Register Select (0 = Comando, 1 = Dato)
#define LCD_BIT_RW 0x02 // Read/Write (Lo mantenemos en 0)
#define LCD_BIT_EN 0x04 // Enable
#define LCD_BIT_BL 0x08 // Backlight (Luz de fondo)
// Dependencia del driver I2C base (Día 123)
extern void i2c_iniciar_transaccion(uint8_t direccion);
extern void i2c_escribir_dato(uint8_t dato); // Transmite y espera ACK
extern void i2c_detener_transaccion(void);
extern void delay_ms_bloqueante(uint32_t ms); // Timer de retardo simple
/**
* @brief Envia un byte físico al expansor I2C empaquetado en una mini-transacción
*/
void lcd_enviar_i2c(uint8_t dato_empaquetado) {
i2c_iniciar_transaccion(LCD_I2C_ADDR);
i2c_escribir_dato(dato_empaquetado);
i2c_detener_transaccion();
}
/**
* @brief Ejecuta el pulso Enable (High -> Low) para que la LCD procese la línea
*/
void lcd_pulso_enable(uint8_t dato_listo) {
lcd_enviar_i2c(dato_listo | LCD_BIT_EN); // EN en Alto
delay_ms_bloqueante(1); // Espera de estabilidad (1ms)
lcd_enviar_i2c(dato_listo & ~LCD_BIT_EN); // EN en Bajo (Ejecuta lectura)
delay_ms_bloqueante(1);
}
/**
* @brief Envía un byte completo (Comando o Caracter) dividido en dos Nibbles
*/
void lcd_enviar_byte(uint8_t byte_crudo, uint8_t modo_rs) {
// Nibble Alto (Enviamos los 4 bits altos en su lugar, sumando Backlight y RS)
uint8_t nibble_alto = (byte_crudo & 0xF0) | LCD_BIT_BL | modo_rs;
lcd_pulso_enable(nibble_alto);
// Nibble Bajo (Desplazamos los 4 bits bajos a la zona alta)
uint8_t nibble_bajo = ((byte_crudo << 4) & 0xF0) | LCD_BIT_BL | modo_rs;
lcd_pulso_enable(nibble_bajo);
}
/**
* @brief Secuencia mágica de inicialización de Hitachi HD44780
*/
void lcd_inicializar(void) {
// La pantalla tarda hasta 50ms en encender tras aplicar VDD
delay_ms_bloqueante(50);
// Forzar modo a 4-bits enviando comandos en Nibbles directos
lcd_pulso_enable(0x30 | LCD_BIT_BL);
delay_ms_bloqueante(5);
lcd_pulso_enable(0x30 | LCD_BIT_BL);
delay_ms_bloqueante(1);
lcd_pulso_enable(0x30 | LCD_BIT_BL);
delay_ms_bloqueante(1);
lcd_pulso_enable(0x20 | LCD_BIT_BL); // Set 4-bit mode!
delay_ms_bloqueante(1);
// Configurar Pantalla
lcd_enviar_byte(0x28, 0); // 4-bits, 2 líneas, formato 5x8
lcd_enviar_byte(0x0C, 0); // Display ON, Cursor OFF
lcd_enviar_byte(0x06, 0); // Auto-incremento del cursor
lcd_enviar_byte(0x01, 0); // Limpiar pantalla
delay_ms_bloqueante(2);
}
/**
* @brief Ubica el cursor en una posición (Fila 0-3, Columna 0-19)
*/
void lcd_set_cursor(uint8_t fila, uint8_t col) {
// Offsets de memoria de la pantalla 20x4
uint8_t offset[] = {0x00, 0x40, 0x14, 0x54};
// El comando SET_DDRAM_ADDR tiene el bit 7 (0x80) en Alto
lcd_enviar_byte(0x80 | (col + offset[fila]), 0);
}
/**
* @brief Escribe una cadena de texto (String)
*/
void lcd_escribir_cadena(char *texto) {
while (*texto) {
lcd_enviar_byte((uint8_t)(*texto), LCD_BIT_RS); // RS=1 para Datos
texto++;
}
}
int main(void) {
// inicializar_i2c1(); // Driver del Día 123
lcd_inicializar();
// Pintando la interfaz del Despachador
lcd_set_cursor(0, 0);
lcd_escribir_cadena("ESTACION: CENTRO 01");
lcd_set_cursor(1, 0);
lcd_escribir_cadena("Prc: $10.50 MXN / Lt");
while(1) {
// (Las lecturas de litros calculadas en el Día 115 se
// transforman a String y se envían a la Fila 3).
// lcd_set_cursor(2, 0);
// lcd_escribir_cadena(string_litros_compensados);
}
return 0;
}
Objetivo del día: Experiencia de Usuario y Certificación Visual.
El reglamento de gasolineras y gaseras exige una pantalla legible a plena luz del sol. Además, la pantalla no debe estar físicamente conectada a las mismas pistas eléctricas que las válvulas de potencia, pues un arco eléctrico en la botonera frontal podría saltar a la pantalla y de ahí a tu microcontrolador, friendo el sistema entero.
Al utilizar este módulo I2C para controlar tu LCD de **20 columnas por 4 filas**, no solo creas una interfaz limpia con espacio suficiente para mostrar los litros facturados, el precio unitario y la compensación por temperatura simultáneamente. También logras una arquitectura donde el panel frontal del Despachador requiere **solo 4 cables (VDD, GND, SDA, SCL)**, haciéndolo increíblemente fácil de mantener por los técnicos de la estación, reducir el costo de arneses y aumentar la inmunidad al ruido industrial.
RS=0) y Texto (RS=1).