200 lines
5.1 KiB
C
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);
|
|
}
|
|
}
|