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)