#include "lcd2004.hpp" #include "pins.h" #include "blk_box_drivers/i2c.h" #include "driver/i2c_master.h" #include "driver/gpio.h" #include "freertos/FreeRTOS.h" #include "esp_log.h" #include "esp_rom_sys.h" #include const static char* TAG = "LCD2004"; // masks const uint8_t ENABLE_MASK = 0x04; const uint8_t BACKLIGHT_MASK = 0x08; const uint8_t DATA_MASK = 0x01; const uint8_t CMD_CLEAR_DISPLAY = 0x01; const uint8_t CMD_RETURN_HOME = 0x02; const uint8_t CMD_ENTRY_MODE_SET = 0x04; const uint8_t ENTRY_MODE_INC_DEC = 0x02; const uint8_t ENTRY_MODE_SHIFT = 0x01; const uint8_t CMD_DISPLAY_CONTROL = 0x08; const uint8_t DISPLAY_CONTROL_SHOW = 0x04; const uint8_t DISPLAY_CONTROL_CURSOR = 0x02; const uint8_t DISPLAY_CONTROL_BLINK = 0x01; const uint8_t CMD_CURSOR_OR_DISPLAY_SHIFT = 0x10; const uint8_t CURSOR_OR_DISPLAY_SHIFT_SC = 0x08; const uint8_t CURSOR_OR_DISPLAY_SHIFT_RL = 0x04; const uint8_t CMD_FUNCTION_SET = 0x20; const uint8_t FUNCTION_SET_DL = 0x10; const uint8_t FUNCTION_SET_N = 0x08; const uint8_t FUNCTION_SET_F = 0x04; const uint8_t CMD_SET_CGRAM_ADDRESS = 0x40; const uint8_t CMD_SET_DDRAM_ADDRESS = 0x80; LCD2004I2C::LCD2004I2C() { backlight = 0; display_control = 0; entry_mode = 0; } void LCD2004I2C::init(uint8_t addr) { i2c_device_config_t dev_config = { .dev_addr_length = I2C_ADDR_BIT_LEN_7, .device_address = addr, .scl_speed_hz = LCD2004_CLK_SPEED_HZ, .scl_wait_us = 0, // default .flags = { .disable_ack_check = 0, } }; // TODO: replace all these ESP_ERROR_CHECK with proper error handling that doesn't just crash the program ESP_ERROR_CHECK(i2c_master_bus_add_device(i2c_main_bus_handle, &dev_config, &lcd_device_handle)); ESP_LOGD(TAG, "LCD2004 device added to bus"); // Initialize the LCD // set to 8-bit mode to bring it to a known state for (int i = 0; i < 3; i++) { send4_cmd(CMD_FUNCTION_SET | FUNCTION_SET_DL); vTaskDelay(pdMS_TO_TICKS(5)); } // set to 4-bit mode, while in 8 bit mode send4_cmd(CMD_FUNCTION_SET); // use all 4 rows send_cmd(CMD_FUNCTION_SET | FUNCTION_SET_N); // display, set cursor visible send_cmd(CMD_DISPLAY_CONTROL | DISPLAY_CONTROL_SHOW); // clear display send_cmd(CMD_CLEAR_DISPLAY); // set cursor to increment to the right send_cmd(CMD_ENTRY_MODE_SET | ENTRY_MODE_INC_DEC); display_control = DISPLAY_CONTROL_SHOW; entry_mode = ENTRY_MODE_INC_DEC; ESP_LOGI(TAG, "LCD2004 initialized!"); } // i2c wrappers /// Sends `cmd` to the device as a command. void LCD2004I2C::send_cmd(uint8_t cmd) { uint8_t control_bits = backlight; uint8_t high = control_bits | (cmd & 0xF0); uint8_t high_enable = high | ENABLE_MASK; uint8_t low = control_bits | ((cmd << 4) & 0xF0); uint8_t low_enable = low | ENABLE_MASK; uint8_t i2c_data[] = { high_enable, high, low_enable, low }; ESP_ERROR_CHECK(i2c_master_transmit(lcd_device_handle, i2c_data, sizeof(i2c_data), I2C_MASTER_TIMEOUT_MS)); } void LCD2004I2C::send_data(uint8_t data) { uint8_t control_bits = DATA_MASK | backlight; uint8_t high = control_bits | (data & 0xF0); uint8_t high_enable = high | ENABLE_MASK; uint8_t low = control_bits | ((data << 4) & 0xF0); uint8_t low_enable = low | ENABLE_MASK; // data pins are on pins D7 to D4 uint8_t i2c_data[] = { high_enable, high, low_enable, low }; ESP_ERROR_CHECK(i2c_master_transmit(lcd_device_handle, i2c_data, sizeof(i2c_data), I2C_MASTER_TIMEOUT_MS)); } /// Sends the ***HIGH*** 4 bits of `cmd` as a command. void LCD2004I2C::send4_cmd(uint8_t cmd) { uint8_t control_bits = backlight; uint8_t high = control_bits | (cmd & 0xF0); uint8_t high_enable = high | ENABLE_MASK; // data pins are on pins D7 to D4 uint8_t i2c_data[] = { high_enable, high }; ESP_ERROR_CHECK(i2c_master_transmit(lcd_device_handle, i2c_data, sizeof(i2c_data), I2C_MASTER_TIMEOUT_MS)); } // LCD2004 class implementations bool LCD2004I2C::get_backlight() { return backlight != 0; } void LCD2004I2C::set_backlight(bool backlight) { if (get_backlight() == backlight) { return; } this->backlight = backlight ? BACKLIGHT_MASK : 0; uint8_t data = this->backlight; ESP_ERROR_CHECK(i2c_master_transmit(lcd_device_handle, &data, 1, I2C_MASTER_TIMEOUT_MS)); } void LCD2004I2C::clear() { send_cmd(CMD_CLEAR_DISPLAY); esp_rom_delay_us(1600); entry_mode |= ENTRY_MODE_INC_DEC; } void LCD2004I2C::move_cursor(uint8_t row, uint8_t col) { const uint8_t ROW_OFFSETS[4] = {0x00, 0x40, 0x14, 0x54}; uint8_t addr = ROW_OFFSETS[row] + col; send_cmd(CMD_SET_DDRAM_ADDRESS | addr); } bool LCD2004I2C::get_show_hide() { return (display_control & DISPLAY_CONTROL_SHOW) != 0; } void LCD2004I2C::show_hide(bool show) { if (get_show_hide() == show) { return; } if (show) { display_control |= DISPLAY_CONTROL_SHOW; } else { display_control &= ~DISPLAY_CONTROL_SHOW; } send_cmd(CMD_DISPLAY_CONTROL | display_control); } bool LCD2004I2C::get_show_cursor() { return (display_control & DISPLAY_CONTROL_CURSOR) != 0; } void LCD2004I2C::show_cursor(bool show_cursor) { if (get_show_cursor() == show_cursor) { return; } if (show_cursor) { display_control |= DISPLAY_CONTROL_CURSOR; } else { display_control &= ~DISPLAY_CONTROL_CURSOR; } send_cmd(CMD_DISPLAY_CONTROL | display_control); } bool LCD2004I2C::get_blink_cursor() { return (display_control & DISPLAY_CONTROL_BLINK) != 0; } void LCD2004I2C::blink_cursor(bool blink_cursor) { if (get_blink_cursor() == blink_cursor) { return; } if (blink_cursor) { display_control |= DISPLAY_CONTROL_BLINK; } else { display_control &= ~DISPLAY_CONTROL_BLINK; } send_cmd(CMD_DISPLAY_CONTROL | display_control); } void LCD2004I2C::show_blink_cursor(bool show_cursor, bool blink_cursor) { if (get_show_cursor() == show_cursor && get_blink_cursor() == blink_cursor) { return; } if (blink_cursor) { display_control |= DISPLAY_CONTROL_BLINK; } else { display_control &= ~DISPLAY_CONTROL_BLINK; } if (show_cursor) { display_control |= DISPLAY_CONTROL_CURSOR; } else { display_control &= ~DISPLAY_CONTROL_CURSOR; } send_cmd(CMD_DISPLAY_CONTROL | display_control); } bool LCD2004I2C::get_scroll_direction() { return (entry_mode & ENTRY_MODE_INC_DEC) != 0; } void LCD2004I2C::scroll_direction(bool left_to_right) { if (get_scroll_direction() == left_to_right) { return; } if (left_to_right) { entry_mode |= ENTRY_MODE_INC_DEC; } else { entry_mode &= ~ENTRY_MODE_INC_DEC; } send_cmd(CMD_ENTRY_MODE_SET | entry_mode); } bool LCD2004I2C::get_display_shift() { return (entry_mode & ENTRY_MODE_SHIFT) != 0; } void LCD2004I2C::shift_display(bool shift_display) { if (get_display_shift() == shift_display) { return; } if (shift_display) { entry_mode |= ENTRY_MODE_SHIFT; } else { entry_mode &= ~ENTRY_MODE_SHIFT; } send_cmd(CMD_ENTRY_MODE_SET | entry_mode); } void LCD2004I2C::shift_cursor_left() { send_cmd(CMD_CURSOR_OR_DISPLAY_SHIFT); } void LCD2004I2C::shift_cursor_right() { send_cmd(CMD_CURSOR_OR_DISPLAY_SHIFT | CURSOR_OR_DISPLAY_SHIFT_RL); } void LCD2004I2C::shift_display_left() { send_cmd(CMD_CURSOR_OR_DISPLAY_SHIFT | CURSOR_OR_DISPLAY_SHIFT_SC); } void LCD2004I2C::shift_display_right() { send_cmd(CMD_CURSOR_OR_DISPLAY_SHIFT | CURSOR_OR_DISPLAY_SHIFT_SC | CURSOR_OR_DISPLAY_SHIFT_RL); } void LCD2004I2C::create_custom_char(uint8_t location, const uint8_t char_map[8]) { uint8_t loc = location % 8; send_cmd(CMD_SET_CGRAM_ADDRESS | (loc << 3)); for (int i = 0; i < 8; i++) { send_data(char_map[i]); } } void LCD2004I2C::print_char(char c) { if (c == '\x08') { // custom char 0 is represented by \x08 since \x00 is a null terminator c = 0; } send_data(static_cast(c)); } void LCD2004I2C::print_u8(uint8_t b) { if (b == 0x08) { // custom char 0 is represented by \x08 since \x00 is a null terminator b = 0; } send_data(b); } void LCD2004I2C::write_str(const char* s) { // TODO: replace this with a single I2C transmission. while (*s) { print_char(*s); s++; } }