Files
RadioPhotonic_PCB_software/App/Devices/lcd1602_display.c
2026-04-24 16:51:15 +03:00

200 lines
5.1 KiB
C

/**
* @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 <stdbool.h>
#include <stddef.h>
#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);
}
}