Initializing HD44780-Based 16x2 LCD with STM32 Using 8-Bit Mode

The HD44780 controller governs standard character LCD modules like the 1602. Proper initialization requires issuing specific function commadns in a precise sequence and timing, respecting data bus width, display geometry, and internal state transitions.

Core HD44780 Command Summary

Function RS R/W DB7–DB0 Timing Notes
Clear Display 0 0 00000001 1.64 ms Resets DDRAM, sets AC=0, positions cursor at top-left
Return Home 0 0 0000001X 1.64 ms Sets AC=0, moves cursor to origin; DDRAM unchanged
Entry Mode Set 0 0 000001ID 40 µs I/D: 1 = increment (right), 0 = decrement (left); S: 1 = shift display on write
Display On/Off Control 0 0 00001DCB 40 µs D: display enable; C: cursor enable; B: blink enible
Cursor or Display Shift 0 0 0001SCRL 40 µs S/C: 0 = cursor, 1 = display; R/L: 1 = right, 0 = left
Function Set 0 0 001DLNFX 40 µs DL: 1 = 8-bit, 0 = 4-bit; N: 1 = 2-line; F: 1 = 5×10 font
Set CGRAM Address 0 0 01AAAAAA 40 µs Loads 6-bit address for custom character RAM
Set DDRAM Address 0 0 1AAAAAAA 40 µs Loads 7-bit address for display data RAM
Read Busy Flag / AC 0 1 40 µs Reads BF (bit 7) and AC (bits 6–0)
Write Data 1 0 DDDDDDDD 40 µs Writes ASCII or CGRAM pattern to current AC location
Read Data 1 1 40 µs Reads from current AC location

Hardware Interface Assumpsions

  • 8-bit parallel interface: D0–D7 mapped across GPIO pins on PA9–PA12, PB5, PB14–PB15, and PC13.
  • Control lines: RS (PA5), RW (PB12), EN (PB13).
  • Busy flag read via PC13 (shared with D7), requiring dynamic pin mode switching.

Initialization Sequence

The following routine configures the display for 2-line operation, 5×7 font, 8-bit data interface, visible display without cursor, auto-incrementing cursor, and clears the screen:

void lcd_init_1602(void) {
    // Ensure stable power-up delay before first command
    delay_ms(50);
    
    // Function set: 8-bit, 2-line, 5×7 font
    lcd_send_command(0x38);
    
    // Display on, no cursor, no blink
    lcd_send_command(0x0C);
    
    // Entry mode: increment cursor, no display shift
    lcd_send_command(0x06);
    
    // Clear display and reset AC
    lcd_send_command(0x01);
    
    // Final stabilization
    delay_ms(2);
}

Command Transmission Routine

Each command is latched using an EN pulse after setting control signals and data bus:

void lcd_send_command(uint8_t cmd) {
    lcd_wait_ready();
    
    // Configure control lines: RS=0, RW=0
    GPIO_ResetBits(GPIOA, GPIO_Pin_5);      // RS low
    GPIO_ResetBits(GPIOB, GPIO_Pin_12);     // RW low
    
    // Output command bits on data bus
    GPIO_WriteBit(GPIOB, GPIO_Pin_14, (BitAction)(cmd & 0x01));
    GPIO_WriteBit(GPIOB, GPIO_Pin_15, (BitAction)((cmd >> 1) & 0x01));
    GPIO_WriteBit(GPIOA, GPIO_Pin_9,  (BitAction)((cmd >> 2) & 0x01));
    GPIO_WriteBit(GPIOA, GPIO_Pin_10, (BitAction)((cmd >> 3) & 0x01));
    GPIO_WriteBit(GPIOA, GPIO_Pin_11, (BitAction)((cmd >> 4) & 0x01));
    GPIO_WriteBit(GPIOA, GPIO_Pin_12, (BitAction)((cmd >> 5) & 0x01));
    GPIO_WriteBit(GPIOB, GPIO_Pin_5,  (BitAction)((cmd >> 6) & 0x01));
    GPIO_WriteBit(GPIOC, GPIO_Pin_13, (BitAction)((cmd >> 7) & 0x01));
    
    // Pulse EN high-to-low
    GPIO_SetBits(GPIOB, GPIO_Pin_13);
    delay_us(1);
    GPIO_ResetBits(GPIOB, GPIO_Pin_13);
    
    // Minimum post-command delay
    delay_us(40);
}

Busy Flag Polling

To avoid fixed delays, the controller's busy flag (BF) is polled by temporarily reconfiguring PC13 as an input:

void lcd_wait_ready(void) {
    // Switch PC13 to input mode (floating)
    GPIOC->CRH &= ~0x00F00000;
    GPIOC->CRH |= 0x00400000; // CNF = 01 (input)
    
    // Prepare for read: RS=0, RW=1, EN=1
    GPIO_ResetBits(GPIOA, GPIO_Pin_5);   // RS
    GPIO_SetBits(GPIOB, GPIO_Pin_12);    // RW
    GPIO_SetBits(GPIOB, GPIO_Pin_13);    // EN
    
    // Delay >1 µs before sampling
    delay_us(1);
    
    // Wait while BF = 1 (busy)
    while (GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_13) == SET) {
        // Spin until BF clears
    }
    
    // Restore PC13 as output (push-pull)
    GPIOC->CRH &= ~0x00F00000;
    GPIOC->CRH |= 0x00300000; // MODE = 11, CNF = 00
    
    // Disable EN
    GPIO_ResetBits(GPIOB, GPIO_Pin_13);
}

Pin Configuration Macros

#define LCD_RS_HIGH()  GPIO_SetBits(GPIOA, GPIO_Pin_5)
#define LCD_RS_LOW()   GPIO_ResetBits(GPIOA, GPIO_Pin_5)
#define LCD_RW_HIGH()  GPIO_SetBits(GPIOB, GPIO_Pin_12)
#define LCD_RW_LOW()   GPIO_ResetBits(GPIOB, GPIO_Pin_12)
#define LCD_EN_HIGH()  GPIO_SetBits(GPIOB, GPIO_Pin_13)
#define LCD_EN_LOW()   GPIO_ResetBits(GPIOB, GPIO_Pin_13)

Tags: STM32 LCD1602 HD44780 Embedded C Peripheral Initialization

Posted on Sun, 05 Jul 2026 16:28:12 +0000 by curioadmin