Skip to content

File syn_charlcd.c

File List > display > syn_charlcd.c

Go to the documentation of this file

#include "syn_charlcd.h"

#include "../util/syn_assert.h"

#include <string.h>

#define PCF8574_BL 0x08
#define PCF8574_EN 0x04
#define PCF8574_RW 0x02
#define PCF8574_RS 0x01

static void write_i2c_byte(SYN_SoftI2C *i2c, uint8_t dev_addr, uint8_t byte)
{
    syn_soft_i2c_start(i2c);
    syn_soft_i2c_write(i2c, (uint8_t)(dev_addr << 1));
    syn_soft_i2c_write(i2c, byte);
    syn_soft_i2c_stop(i2c);
}

static void send_i2c_nibble(SYN_CharLCD *lcd, uint8_t nibble, uint8_t mode)
{
    uint8_t data = (uint8_t)(nibble | mode | lcd->backlight_mask);
    write_i2c_byte(&lcd->i2c, lcd->i2c_addr, data);
    write_i2c_byte(&lcd->i2c, lcd->i2c_addr, (uint8_t)(data | PCF8574_EN));
    write_i2c_byte(&lcd->i2c, lcd->i2c_addr, (uint8_t)(data & ~PCF8574_EN));
}

static void send_i2c_byte(SYN_CharLCD *lcd, uint8_t val, uint8_t mode)
{
    send_i2c_nibble(lcd, val & 0xF0, mode);
    send_i2c_nibble(lcd, (uint8_t)((val << 4) & 0xF0), mode);
}

static void send_gpio_nibble(SYN_CharLCD *lcd, uint8_t nibble)
{
    for (int i = 0; i < 4; i++) {
        bool bit = (nibble & (1u << i)) != 0;
        syn_port_gpio_write(lcd->d_pins[i], bit ? SYN_GPIO_HIGH : SYN_GPIO_LOW);
    }

    /* Pulse Enable High then Low */
    syn_port_gpio_write(lcd->en_pin, SYN_GPIO_HIGH);
    syn_port_gpio_write(lcd->en_pin, SYN_GPIO_LOW);
}

static void send_gpio_byte(SYN_CharLCD *lcd, uint8_t val, bool is_data)
{
    syn_port_gpio_write(lcd->rs_pin, is_data ? SYN_GPIO_HIGH : SYN_GPIO_LOW);
    send_gpio_nibble(lcd, (uint8_t)(val >> 4));
    send_gpio_nibble(lcd, (uint8_t)(val & 0x0F));
}

static void write_command(SYN_CharLCD *lcd, uint8_t cmd)
{
    if (lcd->mode == SYN_CHARLCD_MODE_I2C) {
        send_i2c_byte(lcd, cmd, 0);
    } else {
        send_gpio_byte(lcd, cmd, false);
    }
}

static void write_data(SYN_CharLCD *lcd, uint8_t data)
{
    if (lcd->mode == SYN_CHARLCD_MODE_I2C) {
        send_i2c_byte(lcd, data, PCF8574_RS);
    } else {
        send_gpio_byte(lcd, data, true);
    }
}

SYN_Status syn_charlcd_init_i2c(SYN_CharLCD *lcd, SYN_GPIO_Pin scl, SYN_GPIO_Pin sda,
                                uint8_t i2c_addr, uint8_t cols, uint8_t rows)
{
    SYN_ASSERT(lcd != NULL);
    SYN_ASSERT(cols > 0 && rows > 0);

    memset(lcd, 0, sizeof(*lcd));
    lcd->mode = SYN_CHARLCD_MODE_I2C;
    lcd->cols = cols;
    lcd->rows = rows;
    lcd->i2c_addr = i2c_addr;
    lcd->backlight_mask = PCF8574_BL;
    lcd->backlight = true;

    syn_soft_i2c_init(&lcd->i2c, scl, sda, 10);

    /* HD44780 4-Bit Mode Initialization Sequence */
    send_i2c_nibble(lcd, 0x30, 0);
    send_i2c_nibble(lcd, 0x30, 0);
    send_i2c_nibble(lcd, 0x30, 0);
    send_i2c_nibble(lcd, 0x20, 0); /* 4-bit mode */

    write_command(lcd, 0x28); /* 2-line mode, 5x8 dots */
    write_command(lcd, 0x0C); /* Display ON, Cursor OFF, Blink OFF */
    write_command(lcd, 0x06); /* Entry mode: auto-increment */
    syn_charlcd_clear(lcd);

    return SYN_OK;
}

SYN_Status syn_charlcd_init_gpio(SYN_CharLCD *lcd, SYN_GPIO_Pin rs, SYN_GPIO_Pin en,
                                 SYN_GPIO_Pin d4, SYN_GPIO_Pin d5, SYN_GPIO_Pin d6, SYN_GPIO_Pin d7,
                                 uint8_t cols, uint8_t rows)
{
    SYN_ASSERT(lcd != NULL);
    SYN_ASSERT(cols > 0 && rows > 0);

    memset(lcd, 0, sizeof(*lcd));
    lcd->mode = SYN_CHARLCD_MODE_GPIO_4BIT;
    lcd->cols = cols;
    lcd->rows = rows;
    lcd->backlight = true;

    lcd->rs_pin = rs;
    lcd->en_pin = en;
    lcd->d_pins[0] = d4;
    lcd->d_pins[1] = d5;
    lcd->d_pins[2] = d6;
    lcd->d_pins[3] = d7;

    syn_port_gpio_init(rs, SYN_GPIO_OUTPUT);
    syn_port_gpio_init(en, SYN_GPIO_OUTPUT);
    for (int i = 0; i < 4; i++) {
        syn_port_gpio_init(lcd->d_pins[i], SYN_GPIO_OUTPUT);
    }

    syn_port_gpio_write(rs, SYN_GPIO_LOW);
    syn_port_gpio_write(en, SYN_GPIO_LOW);

    /* HD44780 4-Bit Mode Initialization Sequence */
    send_gpio_nibble(lcd, 0x03);
    send_gpio_nibble(lcd, 0x03);
    send_gpio_nibble(lcd, 0x03);
    send_gpio_nibble(lcd, 0x02); /* 4-bit mode */

    write_command(lcd, 0x28);
    write_command(lcd, 0x0C);
    write_command(lcd, 0x06);
    syn_charlcd_clear(lcd);

    return SYN_OK;
}

void syn_charlcd_clear(SYN_CharLCD *lcd)
{
    if (lcd == NULL)
        return;
    write_command(lcd, 0x01); /* Clear display */
}

void syn_charlcd_set_cursor(SYN_CharLCD *lcd, uint8_t col, uint8_t row)
{
    if (lcd == NULL || lcd->cols == 0)
        return;

    static const uint8_t row_offsets[] = {0x00, 0x40, 0x14, 0x54};
    if (row >= 4)
        row = 3;
    if (col >= lcd->cols)
        col = (uint8_t)(lcd->cols - 1);

    write_command(lcd, (uint8_t)(0x80 | (col + row_offsets[row])));
}

void syn_charlcd_print(SYN_CharLCD *lcd, const char *str)
{
    if (lcd == NULL || str == NULL)
        return;

    while (*str) {
        write_data(lcd, (uint8_t)*str++);
    }
}

void syn_charlcd_set_backlight(SYN_CharLCD *lcd, bool enable)
{
    if (lcd == NULL || lcd->mode != SYN_CHARLCD_MODE_I2C)
        return;
    lcd->backlight = enable;
    lcd->backlight_mask = enable ? PCF8574_BL : 0x00;
    write_i2c_byte(&lcd->i2c, lcd->i2c_addr, lcd->backlight_mask);
}

void syn_charlcd_create_char(SYN_CharLCD *lcd, uint8_t slot, const uint8_t charmap[8])
{
    if (lcd == NULL || charmap == NULL)
        return;
    slot &= 0x07;
    write_command(lcd, (uint8_t)(0x40 | (slot << 3)));
    for (int i = 0; i < 8; i++) {
        write_data(lcd, charmap[i]);
    }
}