From b3bcb4108da4d9f745405ec6bb08d94545eeca30 Mon Sep 17 00:00:00 2001 From: Mitchell M Date: Sat, 4 Apr 2026 11:38:16 -0500 Subject: [PATCH] char lcd --- CMakeLists.txt | 2 +- blk_box.cpp | 2 + drivers/CMakeLists.txt | 4 + drivers/char_lcd.cpp | 317 ++++++++++++++++++++++++++ drivers/char_lcd_headers.cpp | 0 drivers/helpers.cpp | 28 +++ drivers/lcd2004.cpp | 328 +++++++++++++++++++++++++++ drivers/lcd2004.hpp | 112 +++++++++ drivers/leds.cpp | 2 + include/blk_box_drivers/char_lcd.hpp | 104 +++++++++ include/blk_box_drivers/helpers.hpp | 9 + 11 files changed, 907 insertions(+), 1 deletion(-) create mode 100644 drivers/char_lcd.cpp create mode 100644 drivers/char_lcd_headers.cpp create mode 100644 drivers/helpers.cpp create mode 100644 drivers/lcd2004.cpp create mode 100644 drivers/lcd2004.hpp create mode 100644 include/blk_box_drivers/char_lcd.hpp create mode 100644 include/blk_box_drivers/helpers.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 2979175..9dbf1f0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,8 +1,8 @@ idf_component_register( SRCS "blk_box.cpp" INCLUDE_DIRS "include" "." - REQUIRES led_strip PRIV_REQUIRES + led_strip esp_driver_gpio esp_driver_i2c ) diff --git a/blk_box.cpp b/blk_box.cpp index dd67d49..2b081d7 100644 --- a/blk_box.cpp +++ b/blk_box.cpp @@ -3,9 +3,11 @@ #include "blk_box_drivers/i2c.h" #include "blk_box_drivers/inputs.hpp" #include "blk_box_drivers/leds.hpp" +#include "blk_box_drivers/char_lcd.hpp" void init_blk_box(BlkBoxInitConfig cfg) { init_main_i2c(); init_expander(); init_leds(); + init_lcd(); } diff --git a/drivers/CMakeLists.txt b/drivers/CMakeLists.txt index 7bd8d59..9cc1c6a 100644 --- a/drivers/CMakeLists.txt +++ b/drivers/CMakeLists.txt @@ -1,6 +1,10 @@ set(SOURCES + "char_lcd_headers.cpp" + "char_lcd.cpp" + "helpers.cpp" "inputs.cpp" "i2c.cpp" + "lcd2004.cpp" "leds.cpp" ) diff --git a/drivers/char_lcd.cpp b/drivers/char_lcd.cpp new file mode 100644 index 0000000..2115f25 --- /dev/null +++ b/drivers/char_lcd.cpp @@ -0,0 +1,317 @@ +#include "blk_box_drivers/char_lcd.hpp" + +#include "lcd2004.hpp" + +#include +#include +#include + +// mutex is for all these vars +SemaphoreHandle_t lcd_mutex; + +LCD2004I2C lcd; + +static CursorMode cursor_resting_mode = CursorMode::Hide; +static CursorMode cursor_print_mode = CursorMode::Hide; +static uint8_t resting_cursor_row = 0; +static uint8_t resting_cursor_col = 0; + +static bool is_header_enabled = false; + +static const char *TAG = "char_lcd"; +static const char* EMPTY_ROW = " "; + +// TODO: move this to power.cpp +// static void monitor_battery_task(void* _arg) { + // (void) _arg; + + // while (true) { + // vTaskDelay(pdMS_TO_TICKS(1'000)); + // lcd_print_header_bat(); + // } +// } + +// static bool replay_handler(const char* event, char* arg) { +// if (strcmp(event, "LCD_CLEAR") == 0) { +// lcd_clear(); +// } +// else if (strcmp(event, "LCD_SET_DISPLAY") == 0) { +// lcd_set_display(strcmp(arg, "true") == 0); +// } +// else if (strcmp(event, "LCD_CURSOR_VIS") == 0) { +// lcd_set_cursor_vis(strcmp(arg, "true") == 0); +// } +// else if (strcmp(event, "LCD_CURSOR_BLINK") == 0) { +// lcd_set_cursor_blink(strcmp(arg, "true") == 0); +// } +// else if (strcmp(event, "LCD_SCROLL_DISPLAY_LEFT") == 0) { +// lcd_scroll_display_left(); +// } +// else if (strcmp(event, "LCD_SCROLL_DISPLAY_RIGHT") == 0) { +// lcd_scroll_display_right(); +// } +// else if (strcmp(event, "LCD_LEFT_TO_RIGHT") == 0) { +// lcd_left_to_right(); +// } +// else if (strcmp(event, "LCD_RIGHT_TO_LEFT") == 0) { +// lcd_right_to_left(); +// } +// else if (strcmp(event, "LCD_AUTOSCROLL") == 0) { +// lcd_set_autoscroll(strcmp(arg, "true") == 0); +// } +// else if (strcmp(event, "LCD_BACKLIGHT") == 0) { +// lcd_set_backlight(strcmp(arg, "true") == 0); +// } +// else if (strcmp(event, "LCD_CREATE_CHAR") == 0) { +// char* location_str = strtok(arg, ","); +// uint8_t location = atoi(location_str); + +// uint8_t charmap[8]; +// for (int i = 0; i < 8; i++) { +// char* str = strtok(NULL, ","); +// charmap[i] = atoi(str); +// } + +// lcd_create_char(location, charmap); +// } +// else if (strcmp(event, "LCD_PRINT") == 0) { +// char* str = strtok(arg, ","); +// uint8_t row = atoi(str); +// str = strtok(NULL, ","); +// uint8_t col = atoi(str); +// // get remaining part of string. +// str = strtok(NULL, ""); + +// // TODO: handle \r and \n +// lcd_print(row, col, str); +// } else { +// return false; +// } + +// return true; +// } + +void init_lcd() { + ESP_LOGI(TAG, "Initializing LCD..."); + + lcd_mutex = xSemaphoreCreateRecursiveMutex(); + assert(lcd_mutex != NULL); + + lcd = LCD2004I2C(); + lcd.init(LCD_ADDR); + + // register_replay_fn(replay_handler); + + // xTaskCreate(monitor_battery_task, "bat_monitor", 1024*2, nullptr, 0, nullptr); + + ESP_LOGI(TAG, "LCD initialized!"); +} + +void LCDController::clear() { + xSemaphoreTakeRecursive(lcd_mutex, portMAX_DELAY); + + if (!is_header_enabled) { + lcd.clear(); + + // if (is_state_tracking()) { + // event_occured("LCD_CLEAR", NULL); + // } + } else { + print(1, 0, EMPTY_ROW); + print(2, 0, EMPTY_ROW); + print(3, 0, EMPTY_ROW); + } + + xSemaphoreGiveRecursive(lcd_mutex); +} + +bool LCDController::get_backlight() { + xSemaphoreTakeRecursive(lcd_mutex, portMAX_DELAY); + bool backlight = lcd.get_backlight(); + xSemaphoreGiveRecursive(lcd_mutex); + return backlight; +} + +void LCDController::set_backlight(bool backlight) { + xSemaphoreTakeRecursive(lcd_mutex, portMAX_DELAY); + lcd.set_backlight(backlight); + xSemaphoreGiveRecursive(lcd_mutex); + + // if (is_state_tracking()) { + // sprintf(buf, "%d", backlight); + // event_occured("LCD_BACKLIGHT", backlight ? "true" : "false"); + // } +} + +void LCDController::set_display_show(bool show_display) { + xSemaphoreTakeRecursive(lcd_mutex, portMAX_DELAY); + lcd.show_hide(show_display); + xSemaphoreGiveRecursive(lcd_mutex); +} + +bool LCDController::get_display_show() { + xSemaphoreTakeRecursive(lcd_mutex, portMAX_DELAY); + bool show_display = lcd.get_show_hide(); + xSemaphoreGiveRecursive(lcd_mutex); + return show_display; +} + +/// Changes the cursor display mode to the given mode. +static void change_cursor_display(CursorMode cursor_display_mode) { + xSemaphoreTakeRecursive(lcd_mutex, portMAX_DELAY); + lcd.show_blink_cursor( + cursor_display_mode != CursorMode::Hide, + cursor_display_mode == CursorMode::Blink + ); + xSemaphoreGiveRecursive(lcd_mutex); +} + +/// Moves the position of the resting cursor. +/// +/// If the resting cursor mode is `Hide`, then this position has no effect on the display, but is still stored +/// for when it is put into resting cursor mode. +void LCDController::set_resting_cursor_pos(uint8_t row, uint8_t col) { + xSemaphoreTakeRecursive(lcd_mutex, portMAX_DELAY); + + // update pos if we're not hiding the resting cursor + if ( + (resting_cursor_row != row || resting_cursor_col != col) && + cursor_resting_mode != CursorMode::Hide + ) { + lcd.move_cursor(row, col); + } + + resting_cursor_row = row; + resting_cursor_col = col; + xSemaphoreGiveRecursive(lcd_mutex); +} + +/// Gets the position of the resting cursor. +/// +/// This will return the value of the resting cursor position even if the resting cursor mode is `Hide` +/// even though the values are not meaningful during that time. +void LCDController::get_cursor_resting_position(uint8_t* row, uint8_t* col) { + xSemaphoreTakeRecursive(lcd_mutex, portMAX_DELAY); + if (row) *row = resting_cursor_row; + if (col) *col = resting_cursor_col; + xSemaphoreGiveRecursive(lcd_mutex); +} + +/// This puts the display in and out of resting cursor mode. +/// +/// If the resting mode is not `Hide`, then the cursor will be displayed in the resting position when not printing. +/// +/// The cursor mode will change to the "cursor print mode" +/// during prints, then return to it's resting location and +/// switch back to the "cursor resting mode". +void LCDController::set_resting_cursor_mode(CursorMode new_mode) { + xSemaphoreTakeRecursive(lcd_mutex, portMAX_DELAY); + if (cursor_resting_mode != new_mode) { + cursor_resting_mode = new_mode; + change_cursor_display(cursor_resting_mode); + if (cursor_resting_mode != CursorMode::Hide) { + lcd.move_cursor(resting_cursor_row, resting_cursor_col); + } + } + xSemaphoreGiveRecursive(lcd_mutex); +} + +/// Gets the display mode of the cursor when it is resting. +CursorMode LCDController::get_resting_cursor_mode() { + xSemaphoreTakeRecursive(lcd_mutex, portMAX_DELAY); + CursorMode mode = cursor_resting_mode; + xSemaphoreGiveRecursive(lcd_mutex); + return mode; +} + +/// Sets the display mode of the cursor during printing. +void LCDController::set_cursor_print_mode(CursorMode new_mode) { + xSemaphoreTakeRecursive(lcd_mutex, portMAX_DELAY); + cursor_print_mode = new_mode; + xSemaphoreGiveRecursive(lcd_mutex); +} + +/// Gets the display mode of the cursor during printing. +CursorMode LCDController::get_cursor_print_mode() { + xSemaphoreTakeRecursive(lcd_mutex, portMAX_DELAY); + CursorMode mode = cursor_print_mode; + xSemaphoreGiveRecursive(lcd_mutex); + return mode; +} + +void LCDController::create_custom_char(uint8_t location, const uint8_t charmap[]) { + if (location == 8) location = 0; + + xSemaphoreTakeRecursive(lcd_mutex, portMAX_DELAY); + lcd.create_custom_char(location, charmap); + xSemaphoreGiveRecursive(lcd_mutex); + + // if (is_state_tracking()) { + // snprintf(buf, sizeof(buf), + // "%d,%d,%d,%d,%d,%d,%d,%d,%d", location, + // charmap[0], charmap[1], charmap[2], charmap[3], charmap[4], charmap[5], charmap[6], charmap[7] + // ); + // event_occured("LCD_CREATE_CHAR", buf); + // } +} + +/// Prints a string to the given row and column. +/// +/// Do not print across lines, as that leads to goofy behavior. +void LCDController::print(uint8_t row, uint8_t col, const char* str) { + xSemaphoreTakeRecursive(lcd_mutex, portMAX_DELAY); + lcd.move_cursor(row, col); + change_cursor_display(cursor_print_mode); + lcd.write_str(str); + + if (cursor_resting_mode != CursorMode::Hide) { + lcd.move_cursor(resting_cursor_row, resting_cursor_col); + } + if (cursor_resting_mode != cursor_print_mode) { + change_cursor_display(cursor_resting_mode); + } + + xSemaphoreGiveRecursive(lcd_mutex); + + // if (is_state_tracking()) { + // // TODO: handle \r and \n and others + // snprintf(buf, sizeof(buf), "%d,%d,%s", row, col, str); + // event_occured("LCD_PRINT", buf); + // } +} + +void LCDController::set_lcd_header_enabled(bool enable) { + xSemaphoreTakeRecursive(lcd_mutex, portMAX_DELAY); + bool old_header_enabled = is_header_enabled; + is_header_enabled = enable; + + // print the header in response to enabling/disabling it + if (enable && !old_header_enabled) { + print_header(); + } else if (!enable && old_header_enabled) { + print(0, 0, EMPTY_ROW); + } + xSemaphoreGiveRecursive(lcd_mutex); +} + +bool LCDController::header_enabled() { + xSemaphoreTakeRecursive(lcd_mutex, portMAX_DELAY); + bool enabled = is_header_enabled; + xSemaphoreGiveRecursive(lcd_mutex); + return enabled; +} + +bool LCDController::lock(uint32_t ticks_to_wait) { + return xSemaphoreTakeRecursive(lcd_mutex, ticks_to_wait); +} + +void LCDController::unlock() { + xSemaphoreGiveRecursive(lcd_mutex); +} + +void LCDController::print_header() { + // TODO: + // lcd_print_header_star_code(); + // lcd_print_header_step(); + // lcd_print_header_bat(); +} diff --git a/drivers/char_lcd_headers.cpp b/drivers/char_lcd_headers.cpp new file mode 100644 index 0000000..e69de29 diff --git a/drivers/helpers.cpp b/drivers/helpers.cpp new file mode 100644 index 0000000..8d46f97 --- /dev/null +++ b/drivers/helpers.cpp @@ -0,0 +1,28 @@ +#include "blk_box_drivers/helpers.hpp" +#include "blk_box_drivers/char_lcd.hpp" +#include "freertos/FreeRTOS.h" + +#include + +void lcd_do_splash() { + const uint8_t custom_char[6][8] = { + { 0x01, 0x01, 0x02, 0x02, 0x07, 0x07, 0x0F, 0x0D }, + { 0x10, 0x10, 0x18, 0x18, 0x1C, 0x0C, 0x0E, 0x06 }, + { 0x00, 0x00, 0x01, 0x01, 0x03, 0x03, 0x07, 0x07 }, + { 0x19, 0x1B, 0x13, 0x17, 0x07, 0x0F, 0x0F, 0x1F }, + { 0x13, 0x1B, 0x1F, 0x1F, 0x00, 0x1F, 0x1F, 0x1F }, + { 0x00, 0x00, 0x10, 0x10, 0x00, 0x18, 0x1C, 0x1C }, + }; + + LCDController::lock(portMAX_DELAY); + LCDController::create_custom_char(1, custom_char[0]); + LCDController::create_custom_char(2, custom_char[1]); + LCDController::create_custom_char(3, custom_char[2]); + LCDController::create_custom_char(4, custom_char[3]); + LCDController::create_custom_char(5, custom_char[4]); + LCDController::create_custom_char(6, custom_char[5]); + + LCDController::print(1, 6, "\x01\x02Marino"); + LCDController::print(2, 5, "\x03\x04\x05\x06""DEV"); + LCDController::unlock(); +} diff --git a/drivers/lcd2004.cpp b/drivers/lcd2004.cpp new file mode 100644 index 0000000..a2d4a57 --- /dev/null +++ b/drivers/lcd2004.cpp @@ -0,0 +1,328 @@ +#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++; + } +} diff --git a/drivers/lcd2004.hpp b/drivers/lcd2004.hpp new file mode 100644 index 0000000..6e06a4c --- /dev/null +++ b/drivers/lcd2004.hpp @@ -0,0 +1,112 @@ +#ifndef LCD2004_HPP +#define LCD2004_HPP + +#include "driver/i2c_master.h" +#include + +#define LCD2004_CLK_SPEED_HZ 100000 + +#define I2C_MASTER_TIMEOUT_MS 1000 + +class LCD2004I2C { +private: + i2c_master_dev_handle_t lcd_device_handle; + /// Weather or not the backlight is on. + /// `BACKLIGHT_MASK` if backlight is on, + /// `0` if backlight is off. + uint8_t backlight; + /// The mask for the display control command. + uint8_t display_control; + uint8_t entry_mode; + + void send_cmd(uint8_t cmd); + void send_data(uint8_t data); + void send4_cmd(uint8_t cmd); + +public: + /// Constructs a new LCD2004 device. + LCD2004I2C(); + + /// Initializes the LCD2004 device. Must be called before any other methods. + /// + /// The address is usually `0x27`. + void init(uint8_t addr); + + /// Gets the state of the backlight of the module. + bool get_backlight(); + + /// Sets the backlight of the module to be on or off. + void set_backlight(bool backlight); + + /// Clears the display, and resets the cursor to + void clear(); + + /// Moves the cursor to `row` and `col`. + void move_cursor(uint8_t row, uint8_t col); + + /// Returns `true` iff the display is showing. + bool get_show_hide(); + + /// Shows or hides the display. + void show_hide(bool show); + + /// Returns `true` iff the cursor display is showing. + bool get_show_cursor(); + + /// Shows or hides the cursor. + void show_cursor(bool show_cursor); + + /// Returns `true` iff the cursor is blinking. + /// + /// This value is stored even if the cursor is not visible. + bool get_blink_cursor(); + + /// Blinks or stops blinking the cursor. + void blink_cursor(bool blink_cursor); + + /// Sets the cursor show state and blink state. + void show_blink_cursor(bool show_cursor, bool blink_cursor); + + /// Returns `true` iff the display scrolls left to right. + bool get_scroll_direction(); + + /// Sets the scroll direction for the display. + void scroll_direction(bool left_to_right); + + /// Returns `true` iff the display is shifting with the cursor. + bool get_display_shift(); + + /// Sets the display to shift with the cursor. + /// + /// This is like autoscrolling. + void shift_display(bool shift_display); + + /// Shifts the cursor left 1 + void shift_cursor_left(); + + /// Shifts the cursor right 1 + void shift_cursor_right(); + + /// Shifts the display left 1 + void shift_display_left(); + + /// Shifts the display right 1 + void shift_display_right(); + + /// Sets the character map for a custom character. + /// + /// There are 8 locations (0..=7), for 8 characters. + /// You can print these characters by sending '\x00' - '\x07'. + void create_custom_char(uint8_t location, const uint8_t char_map[8]); + + /// Prints a single character to the display. + void print_char(char c); + + /// Prints a single byte to the display. + void print_u8(uint8_t b); + + /// Writes a string to the display. + void write_str(const char* s); +}; + +#endif // LCD2004_HPP \ No newline at end of file diff --git a/drivers/leds.cpp b/drivers/leds.cpp index 443c882..5159755 100644 --- a/drivers/leds.cpp +++ b/drivers/leds.cpp @@ -57,6 +57,8 @@ void init_leds() { /// Create the LED strip object ESP_ERROR_CHECK(led_strip_new_rmt_device(&strip_config, &rmt_config, &led_strip)); + + ESP_ERROR_CHECK(led_strip_clear(led_strip)); } void LEDController::set_led(uint32_t led, uint32_t color) { diff --git a/include/blk_box_drivers/char_lcd.hpp b/include/blk_box_drivers/char_lcd.hpp new file mode 100644 index 0000000..f9cebae --- /dev/null +++ b/include/blk_box_drivers/char_lcd.hpp @@ -0,0 +1,104 @@ +#ifndef CHAR_LCD_HPP +#define CHAR_LCD_HPP + +#include + +#define LCD_ADDR 0x27 +#define LCD_ROWS 4 +#define LCD_COLS 20 + +/// A possible display mode for the cursor. +enum class CursorMode: uint8_t { + /// The cursor is not displayed. + Hide = 0b00, + /// The cursor is displayed as an underline. + Show = 0b01, + /// The cursor is displayed as a blinking block. + Blink = 0b11, +}; + +/// Initializes the 2004 Character LCD +void init_lcd(); + +class LCDController { +public: + /// Clears the display and resets the cursor to the home position (0, 0). + static void clear(); + + /// Gets the backlight state of the LCD. + static bool get_backlight(); + /// Sets the backlight state of the LCD. + static void set_backlight(bool backlight); + + /// Shows or hides the display. + static void set_display_show(bool show_display); + + /// Gets the display state of the LCD. + static bool get_display_show(); + + /// Moves the position of the resting cursor. + /// + /// If the resting cursor mode is `Hide`, then this position has no effect on the display, but is still stored + /// for when it is put into resting cursor mode. + static void set_resting_cursor_pos(uint8_t row, uint8_t col); + + /// Gets the position of the resting cursor. + /// + /// This will return the value of the resting cursor position even if the resting cursor mode is `Hide` + /// even though the values are not meaningful during that time. + static void get_cursor_resting_position(uint8_t* row, uint8_t* col); + + /// This puts the display in and out of resting cursor mode. + /// + /// If the resting mode is not `Hide`, then the cursor will be displayed in the resting position when not printing. + /// + /// The cursor mode will change to the "cursor print mode" + /// during prints, then return to it's resting location and + /// switch back to the "cursor resting mode". + static void set_resting_cursor_mode(CursorMode new_mode); + + /// Gets the display mode of the cursor when it is resting. + static CursorMode get_resting_cursor_mode(); + + /// Sets the display mode of the cursor during printing. + static void set_cursor_print_mode(CursorMode new_mode); + + /// Gets the display mode of the cursor during printing. + static CursorMode get_cursor_print_mode(); + + /// Sets the character map for a custom character. + /// + /// There are 8 locations (1..=8), for 8 characters. + /// You can print these characters by sending '\x01' - '\x08'. + static void create_custom_char(uint8_t location, const uint8_t charmap[]); + + /// Prints a string to the given row and column. + /// + /// Do not print across lines, as that leads to goofy behavior. + static void print(uint8_t row, uint8_t col, const char* str); + + /// Enables or disables the header row. + /// + /// Try to keep it enabled, since its used as a status display and shows the starcode the user is typing. + /// + /// But can be disabled if needed. + static void set_lcd_header_enabled(bool enable); + + /// Gets whether the header row is enabled. + static bool header_enabled(); + + /// Prints the LCD header. Usually, you will not need to call this manually. + static void print_header(); + + /// Locks the LCD to allow you to perform multiple operations uninterrupted. + /// + /// Every lock should have an unlock. Do not hold the lock for too long. + /// + /// This will wait up to `ticks_to_wait` ticks to acquire the lock, and return `false` if it fails to acquire the lock within that time. + static bool lock(uint32_t ticks_to_wait); + + /// Unlocks the LCD after a lock. + static void unlock(); +}; + +#endif /* CHAR_LCD_HPP */ diff --git a/include/blk_box_drivers/helpers.hpp b/include/blk_box_drivers/helpers.hpp new file mode 100644 index 0000000..34e7a00 --- /dev/null +++ b/include/blk_box_drivers/helpers.hpp @@ -0,0 +1,9 @@ +#ifndef HELPERS_HPP +#define HELPERS_HPP + +// Does the MarinoDev splash screen. +void lcd_do_splash(); + + + +#endif // HELPERS_HPP