/** * @file lcd1602_display.c * @brief Minimal SPLC780D/HD44780-compatible LCD1602 driver in 4-bit mode. * * Architectural note: * The chosen wiring keeps the whole standalone UI on GPIOG. This driver uses * write-only transfers with fixed delays, which keeps the implementation * compact and avoids a bidirectional R/W pin or extra state. */ #include "lcd1602_display.h" #include #include #include "main.h" #define LCD1602_COLUMNS 16u #define LCD1602_LINE_1_ADDRESS 0x00u #define LCD1602_LINE_2_ADDRESS 0x40u #define LCD1602_COMMAND_DELAY_US 50u #define LCD1602_CLEAR_DELAY_US 2000u #define LCD1602_ENABLE_PULSE_US 1u #define LCD1602_POWER_ON_DELAY_US 50000u #define LCD1602_DATA_MASK (UI_LCD_D4_Pin | UI_LCD_D5_Pin | UI_LCD_D6_Pin | UI_LCD_D7_Pin) static uint8_t g_cycle_counter_available = 0u; static void lcd1602_enable_cycle_counter(void); static void lcd1602_delay_us(uint32_t delay_us); static void lcd1602_write_nibble(uint8_t nibble); static void lcd1602_pulse_enable(void); static void lcd1602_write_byte(bool is_data, uint8_t value); static void lcd1602_write_command(uint8_t command); static void lcd1602_write_data(uint8_t value); static void lcd1602_set_cursor(uint8_t address); static void lcd1602_write_padded_line(const char *text); void lcd1602_display_init(void) { lcd1602_enable_cycle_counter(); HAL_GPIO_WritePin(UI_LCD_RS_GPIO_Port, UI_LCD_RS_Pin, GPIO_PIN_RESET); HAL_GPIO_WritePin(UI_LCD_E_GPIO_Port, UI_LCD_E_Pin, GPIO_PIN_RESET); UI_LCD_D4_GPIO_Port->BSRR = ((uint32_t)LCD1602_DATA_MASK) << 16u; lcd1602_delay_us(LCD1602_POWER_ON_DELAY_US); lcd1602_write_nibble(0x03u); lcd1602_delay_us(5000u); lcd1602_write_nibble(0x03u); lcd1602_delay_us(150u); lcd1602_write_nibble(0x03u); lcd1602_delay_us(150u); lcd1602_write_nibble(0x02u); lcd1602_delay_us(LCD1602_COMMAND_DELAY_US); lcd1602_write_command(0x28u); lcd1602_write_command(0x08u); lcd1602_write_command(0x01u); lcd1602_write_command(0x06u); lcd1602_write_command(0x0Cu); } void lcd1602_display_set_lines(const char *line1, const char *line2) { lcd1602_set_cursor(LCD1602_LINE_1_ADDRESS); lcd1602_write_padded_line(line1); lcd1602_set_cursor(LCD1602_LINE_2_ADDRESS); lcd1602_write_padded_line(line2); } static void lcd1602_enable_cycle_counter(void) { uint32_t start_cycles; uint32_t spin; CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; DWT->CYCCNT = 0u; DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; start_cycles = DWT->CYCCNT; for (spin = 0u; spin < 64u; ++spin) { __NOP(); } /* * DWT-based microsecond delays are convenient, but they are not * guaranteed to be available in every production boot/debug state. * Falling back to HAL_Delay() keeps the LCD optional rather than * letting the standalone UI block the whole firmware startup. */ g_cycle_counter_available = (DWT->CYCCNT != start_cycles) ? 1u : 0u; } static void lcd1602_delay_us(uint32_t delay_us) { if (delay_us == 0u) { return; } if (g_cycle_counter_available != 0u) { uint32_t start_cycles = DWT->CYCCNT; uint32_t target_cycles = delay_us * (SystemCoreClock / 1000000u); while ((DWT->CYCCNT - start_cycles) < target_cycles) { } return; } while (delay_us > 1000u) { HAL_Delay(1u); delay_us -= 1000u; } HAL_Delay(1u); } static void lcd1602_write_nibble(uint8_t nibble) { uint32_t bsrr = ((uint32_t)LCD1602_DATA_MASK) << 16u; if ((nibble & 0x01u) != 0u) { bsrr |= UI_LCD_D4_Pin; } if ((nibble & 0x02u) != 0u) { bsrr |= UI_LCD_D5_Pin; } if ((nibble & 0x04u) != 0u) { bsrr |= UI_LCD_D6_Pin; } if ((nibble & 0x08u) != 0u) { bsrr |= UI_LCD_D7_Pin; } UI_LCD_D4_GPIO_Port->BSRR = bsrr; lcd1602_pulse_enable(); } static void lcd1602_pulse_enable(void) { HAL_GPIO_WritePin(UI_LCD_E_GPIO_Port, UI_LCD_E_Pin, GPIO_PIN_SET); lcd1602_delay_us(LCD1602_ENABLE_PULSE_US); HAL_GPIO_WritePin(UI_LCD_E_GPIO_Port, UI_LCD_E_Pin, GPIO_PIN_RESET); lcd1602_delay_us(LCD1602_COMMAND_DELAY_US); } static void lcd1602_write_byte(bool is_data, uint8_t value) { HAL_GPIO_WritePin(UI_LCD_RS_GPIO_Port, UI_LCD_RS_Pin, is_data ? GPIO_PIN_SET : GPIO_PIN_RESET); lcd1602_write_nibble((uint8_t)(value >> 4u)); lcd1602_write_nibble((uint8_t)(value & 0x0Fu)); } static void lcd1602_write_command(uint8_t command) { lcd1602_write_byte(false, command); if ((command == 0x01u) || (command == 0x02u)) { lcd1602_delay_us(LCD1602_CLEAR_DELAY_US); } } static void lcd1602_write_data(uint8_t value) { lcd1602_write_byte(true, value); } static void lcd1602_set_cursor(uint8_t address) { lcd1602_write_command((uint8_t)(0x80u | address)); } static void lcd1602_write_padded_line(const char *text) { uint8_t index; uint8_t value; for (index = 0u; index < LCD1602_COLUMNS; ++index) { value = ' '; if ((text != NULL) && (text[index] != '\0')) { value = (uint8_t)text[index]; } lcd1602_write_data(value); } }