add error checking and initial backlight state to char lcd

This commit is contained in:
2026-09-29 23:54:27 -05:00
parent 8d635d41c6
commit ef79b2a835
6 changed files with 135 additions and 98 deletions
+1 -1
View File
@@ -16,7 +16,7 @@ void init_blk_box(BlkBoxInitConfig cfg) {
init_main_i2c();
init_expander();
init_leds();
init_lcd();
init_lcd(cfg.lcd_backlight);
init_ssegs();
init_tft();
+22 -12
View File
@@ -5,6 +5,7 @@
#include <freertos/FreeRTOS.h>
#include <freertos/semphr.h>
#include <esp_log.h>
#include <esp_err.h>
// mutex is for all these vars
SemaphoreHandle_t lcd_mutex;
@@ -21,6 +22,12 @@ static bool is_header_enabled = false;
static const char *TAG = "char_lcd";
static const char* EMPTY_ROW = " ";
static void log_lcd_error(const char* operation, esp_err_t err) {
if (err != ESP_OK) {
ESP_LOGE(TAG, "LCD %s failed: %s", operation, esp_err_to_name(err));
}
}
// TODO: move this to power.cpp
// static void monitor_battery_task(void* _arg) {
// (void) _arg;
@@ -91,14 +98,17 @@ static const char* EMPTY_ROW = " ";
// return true;
// }
void init_lcd() {
void init_lcd(bool initial_backlight) {
ESP_LOGI(TAG, "Initializing LCD...");
lcd_mutex = xSemaphoreCreateRecursiveMutex();
assert(lcd_mutex != NULL);
lcd = LCD2004I2C();
lcd.init(LCD_ADDR);
esp_err_t err = lcd.init(LCD_ADDR, initial_backlight);
if (err != ESP_OK) {
ESP_LOGE(TAG, "LCD initialization failed: %s", esp_err_to_name(err));
}
// register_replay_fn(replay_handler);
@@ -111,7 +121,7 @@ void LCDController::clear() {
xSemaphoreTakeRecursive(lcd_mutex, portMAX_DELAY);
if (!is_header_enabled) {
lcd.clear();
log_lcd_error("clear", lcd.clear());
// if (is_state_tracking()) {
// event_occured("LCD_CLEAR", NULL);
@@ -134,7 +144,7 @@ bool LCDController::get_backlight() {
void LCDController::set_backlight(bool backlight) {
xSemaphoreTakeRecursive(lcd_mutex, portMAX_DELAY);
lcd.set_backlight(backlight);
log_lcd_error("set backlight", lcd.set_backlight(backlight));
xSemaphoreGiveRecursive(lcd_mutex);
// if (is_state_tracking()) {
@@ -145,7 +155,7 @@ void LCDController::set_backlight(bool backlight) {
void LCDController::set_display_show(bool show_display) {
xSemaphoreTakeRecursive(lcd_mutex, portMAX_DELAY);
lcd.show_hide(show_display);
log_lcd_error("set display", lcd.show_hide(show_display));
xSemaphoreGiveRecursive(lcd_mutex);
}
@@ -159,10 +169,10 @@ bool LCDController::get_display_show() {
/// 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(
log_lcd_error("set cursor display", lcd.show_blink_cursor(
cursor_display_mode != CursorMode::Hide,
cursor_display_mode == CursorMode::Blink
);
));
xSemaphoreGiveRecursive(lcd_mutex);
}
@@ -178,7 +188,7 @@ void LCDController::set_resting_cursor_pos(uint8_t row, uint8_t col) {
(resting_cursor_row != row || resting_cursor_col != col) &&
cursor_resting_mode != CursorMode::Hide
) {
lcd.move_cursor(row, col);
log_lcd_error("move cursor", lcd.move_cursor(row, col));
}
resting_cursor_row = row;
@@ -243,7 +253,7 @@ 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);
log_lcd_error("create custom character", lcd.create_custom_char(location, charmap));
xSemaphoreGiveRecursive(lcd_mutex);
// if (is_state_tracking()) {
@@ -260,12 +270,12 @@ void LCDController::create_custom_char(uint8_t location, const uint8_t charmap[]
/// 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);
log_lcd_error("move cursor", lcd.move_cursor(row, col));
change_cursor_display(cursor_print_mode);
lcd.write_str(str);
log_lcd_error("write string", lcd.write_str(str));
if (cursor_resting_mode != CursorMode::Hide) {
lcd.move_cursor(resting_cursor_row, resting_cursor_col);
log_lcd_error("restore cursor", lcd.move_cursor(resting_cursor_row, resting_cursor_col));
}
if (cursor_resting_mode != cursor_print_mode) {
change_cursor_display(cursor_resting_mode);
+83 -59
View File
@@ -49,7 +49,8 @@ LCD2004I2C::LCD2004I2C() {
entry_mode = 0;
}
void LCD2004I2C::init(uint8_t addr) {
esp_err_t LCD2004I2C::init(uint8_t addr, bool initial_backlight) {
if (initialized) return ESP_ERR_INVALID_STATE;
i2c_device_config_t dev_config = {
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
.device_address = addr,
@@ -60,42 +61,52 @@ void LCD2004I2C::init(uint8_t addr) {
}
};
// 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_err_t err = i2c_master_bus_add_device(i2c_main_bus_handle, &dev_config, &lcd_device_handle);
if (err != ESP_OK) return err;
ESP_LOGD(TAG, "LCD2004 device added to bus");
backlight = initial_backlight ? BACKLIGHT_MASK : 0;
// 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);
err = send4_cmd(CMD_FUNCTION_SET | FUNCTION_SET_DL);
if (err != ESP_OK) return err;
vTaskDelay(pdMS_TO_TICKS(5));
}
// set to 4-bit mode, while in 8 bit mode
send4_cmd(CMD_FUNCTION_SET);
err = send4_cmd(CMD_FUNCTION_SET);
if (err != ESP_OK) return err;
// use all 4 rows
send_cmd(CMD_FUNCTION_SET | FUNCTION_SET_N);
err = send_cmd(CMD_FUNCTION_SET | FUNCTION_SET_N);
if (err != ESP_OK) return err;
// display, set cursor visible
send_cmd(CMD_DISPLAY_CONTROL | DISPLAY_CONTROL_SHOW);
err = send_cmd(CMD_DISPLAY_CONTROL | DISPLAY_CONTROL_SHOW);
if (err != ESP_OK) return err;
// clear display
send_cmd(CMD_CLEAR_DISPLAY);
err = send_cmd(CMD_CLEAR_DISPLAY);
if (err != ESP_OK) return err;
esp_rom_delay_us(1600);
// set cursor to increment to the right
send_cmd(CMD_ENTRY_MODE_SET | ENTRY_MODE_INC_DEC);
err = send_cmd(CMD_ENTRY_MODE_SET | ENTRY_MODE_INC_DEC);
if (err != ESP_OK) return err;
display_control = DISPLAY_CONTROL_SHOW;
entry_mode = ENTRY_MODE_INC_DEC;
initialized = true;
ESP_LOGI(TAG, "LCD2004 initialized!");
return ESP_OK;
}
// i2c wrappers
/// Sends `cmd` to the device as a command.
void LCD2004I2C::send_cmd(uint8_t cmd) {
esp_err_t LCD2004I2C::send_cmd(uint8_t cmd) {
uint8_t control_bits = backlight;
uint8_t high = control_bits | (cmd & 0xF0);
@@ -110,10 +121,10 @@ void LCD2004I2C::send_cmd(uint8_t cmd) {
low
};
ESP_ERROR_CHECK(i2c_master_transmit(lcd_device_handle, i2c_data, sizeof(i2c_data), I2C_MASTER_TIMEOUT_MS));
return i2c_master_transmit(lcd_device_handle, i2c_data, sizeof(i2c_data), I2C_MASTER_TIMEOUT_MS);
}
void LCD2004I2C::send_data(uint8_t data) {
esp_err_t LCD2004I2C::send_data(uint8_t data) {
uint8_t control_bits = DATA_MASK | backlight;
uint8_t high = control_bits | (data & 0xF0);
@@ -129,11 +140,11 @@ void LCD2004I2C::send_data(uint8_t data) {
low
};
ESP_ERROR_CHECK(i2c_master_transmit(lcd_device_handle, i2c_data, sizeof(i2c_data), I2C_MASTER_TIMEOUT_MS));
return 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) {
esp_err_t LCD2004I2C::send4_cmd(uint8_t cmd) {
uint8_t control_bits = backlight;
uint8_t high = control_bits | (cmd & 0xF0);
@@ -145,7 +156,7 @@ void LCD2004I2C::send4_cmd(uint8_t cmd) {
high
};
ESP_ERROR_CHECK(i2c_master_transmit(lcd_device_handle, i2c_data, sizeof(i2c_data), I2C_MASTER_TIMEOUT_MS));
return i2c_master_transmit(lcd_device_handle, i2c_data, sizeof(i2c_data), I2C_MASTER_TIMEOUT_MS);
}
// LCD2004 class implementations
@@ -154,35 +165,41 @@ bool LCD2004I2C::get_backlight() {
return backlight != 0;
}
void LCD2004I2C::set_backlight(bool backlight) {
esp_err_t LCD2004I2C::set_backlight(bool backlight) {
if (get_backlight() == backlight) {
return;
return ESP_OK;
}
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));
uint8_t data = backlight ? BACKLIGHT_MASK : 0;
esp_err_t err = i2c_master_transmit(lcd_device_handle, &data, 1, I2C_MASTER_TIMEOUT_MS);
if (err == ESP_OK) {
this->backlight = data;
}
return err;
}
void LCD2004I2C::clear() {
send_cmd(CMD_CLEAR_DISPLAY);
esp_err_t LCD2004I2C::clear() {
esp_err_t err = send_cmd(CMD_CLEAR_DISPLAY);
if (err != ESP_OK) return err;
esp_rom_delay_us(1600);
entry_mode |= ENTRY_MODE_INC_DEC;
return ESP_OK;
}
void LCD2004I2C::move_cursor(uint8_t row, uint8_t col) {
esp_err_t LCD2004I2C::move_cursor(uint8_t row, uint8_t col) {
const uint8_t ROW_OFFSETS[4] = {0x00, 0x40, 0x14, 0x54};
if (row >= LCD2004_ROWS || col >= LCD2004_COLS) return ESP_ERR_INVALID_ARG;
uint8_t addr = ROW_OFFSETS[row] + col;
send_cmd(CMD_SET_DDRAM_ADDRESS | addr);
return 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) {
esp_err_t LCD2004I2C::show_hide(bool show) {
if (get_show_hide() == show) {
return;
return ESP_OK;
}
if (show) {
@@ -190,16 +207,16 @@ void LCD2004I2C::show_hide(bool show) {
} else {
display_control &= ~DISPLAY_CONTROL_SHOW;
}
send_cmd(CMD_DISPLAY_CONTROL | display_control);
return 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) {
esp_err_t LCD2004I2C::show_cursor(bool show_cursor) {
if (get_show_cursor() == show_cursor) {
return;
return ESP_OK;
}
if (show_cursor) {
@@ -207,16 +224,16 @@ void LCD2004I2C::show_cursor(bool show_cursor) {
} else {
display_control &= ~DISPLAY_CONTROL_CURSOR;
}
send_cmd(CMD_DISPLAY_CONTROL | display_control);
return 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) {
esp_err_t LCD2004I2C::blink_cursor(bool blink_cursor) {
if (get_blink_cursor() == blink_cursor) {
return;
return ESP_OK;
}
if (blink_cursor) {
@@ -224,12 +241,12 @@ void LCD2004I2C::blink_cursor(bool blink_cursor) {
} else {
display_control &= ~DISPLAY_CONTROL_BLINK;
}
send_cmd(CMD_DISPLAY_CONTROL | display_control);
return send_cmd(CMD_DISPLAY_CONTROL | display_control);
}
void LCD2004I2C::show_blink_cursor(bool show_cursor, bool blink_cursor) {
esp_err_t LCD2004I2C::show_blink_cursor(bool show_cursor, bool blink_cursor) {
if (get_show_cursor() == show_cursor && get_blink_cursor() == blink_cursor) {
return;
return ESP_OK;
}
if (blink_cursor) {
@@ -242,16 +259,16 @@ void LCD2004I2C::show_blink_cursor(bool show_cursor, bool blink_cursor) {
} else {
display_control &= ~DISPLAY_CONTROL_CURSOR;
}
send_cmd(CMD_DISPLAY_CONTROL | display_control);
return 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) {
esp_err_t LCD2004I2C::scroll_direction(bool left_to_right) {
if (get_scroll_direction() == left_to_right) {
return;
return ESP_OK;
}
if (left_to_right) {
@@ -259,16 +276,16 @@ void LCD2004I2C::scroll_direction(bool left_to_right) {
} else {
entry_mode &= ~ENTRY_MODE_INC_DEC;
}
send_cmd(CMD_ENTRY_MODE_SET | entry_mode);
return 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) {
esp_err_t LCD2004I2C::shift_display(bool shift_display) {
if (get_display_shift() == shift_display) {
return;
return ESP_OK;
}
if (shift_display) {
@@ -276,53 +293,60 @@ void LCD2004I2C::shift_display(bool shift_display) {
} else {
entry_mode &= ~ENTRY_MODE_SHIFT;
}
send_cmd(CMD_ENTRY_MODE_SET | entry_mode);
return send_cmd(CMD_ENTRY_MODE_SET | entry_mode);
}
void LCD2004I2C::shift_cursor_left() {
send_cmd(CMD_CURSOR_OR_DISPLAY_SHIFT);
esp_err_t LCD2004I2C::shift_cursor_left() {
return send_cmd(CMD_CURSOR_OR_DISPLAY_SHIFT);
}
void LCD2004I2C::shift_cursor_right() {
send_cmd(CMD_CURSOR_OR_DISPLAY_SHIFT | CURSOR_OR_DISPLAY_SHIFT_RL);
esp_err_t LCD2004I2C::shift_cursor_right() {
return 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);
esp_err_t LCD2004I2C::shift_display_left() {
return 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);
esp_err_t LCD2004I2C::shift_display_right() {
return 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]) {
esp_err_t LCD2004I2C::create_custom_char(uint8_t location, const uint8_t char_map[8]) {
if (char_map == nullptr) return ESP_ERR_INVALID_ARG;
uint8_t loc = location % 8;
send_cmd(CMD_SET_CGRAM_ADDRESS | (loc << 3));
esp_err_t err = send_cmd(CMD_SET_CGRAM_ADDRESS | (loc << 3));
if (err != ESP_OK) return err;
for (int i = 0; i < 8; i++) {
send_data(char_map[i]);
err = send_data(char_map[i]);
if (err != ESP_OK) return err;
}
return ESP_OK;
}
void LCD2004I2C::print_char(char c) {
esp_err_t 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<uint8_t>(c));
return send_data(static_cast<uint8_t>(c));
}
void LCD2004I2C::print_u8(uint8_t b) {
esp_err_t 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);
return send_data(b);
}
void LCD2004I2C::write_str(const char* s) {
esp_err_t LCD2004I2C::write_str(const char* s) {
// TODO: replace this with a single I2C transmission.
if (s == nullptr) return ESP_ERR_INVALID_ARG;
while (*s) {
print_char(*s);
esp_err_t err = print_char(*s);
if (err != ESP_OK) return err;
s++;
}
return ESP_OK;
}
+26 -22
View File
@@ -2,15 +2,19 @@
#define LCD2004_HPP
#include "driver/i2c_master.h"
#include "esp_err.h"
#include <stdint.h>
#define LCD2004_CLK_SPEED_HZ 100000
#define I2C_MASTER_TIMEOUT_MS 1000
#define LCD2004_ROWS 4
#define LCD2004_COLS 20
class LCD2004I2C {
private:
i2c_master_dev_handle_t lcd_device_handle;
i2c_master_dev_handle_t lcd_device_handle = nullptr;
bool initialized = false;
/// Weather or not the backlight is on.
/// `BACKLIGHT_MASK` if backlight is on,
/// `0` if backlight is off.
@@ -19,9 +23,9 @@ private:
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);
esp_err_t send_cmd(uint8_t cmd);
esp_err_t send_data(uint8_t data);
esp_err_t send4_cmd(uint8_t cmd);
public:
/// Constructs a new LCD2004 device.
@@ -30,31 +34,31 @@ public:
/// Initializes the LCD2004 device. Must be called before any other methods.
///
/// The address is usually `0x27`.
void init(uint8_t addr);
esp_err_t init(uint8_t addr, bool initial_backlight);
/// 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);
esp_err_t set_backlight(bool backlight);
/// Clears the display, and resets the cursor to
void clear();
esp_err_t clear();
/// Moves the cursor to `row` and `col`.
void move_cursor(uint8_t row, uint8_t col);
esp_err_t 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);
esp_err_t 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);
esp_err_t show_cursor(bool show_cursor);
/// Returns `true` iff the cursor is blinking.
///
@@ -62,16 +66,16 @@ public:
bool get_blink_cursor();
/// Blinks or stops blinking the cursor.
void blink_cursor(bool blink_cursor);
esp_err_t blink_cursor(bool blink_cursor);
/// Sets the cursor show state and blink state.
void show_blink_cursor(bool show_cursor, bool blink_cursor);
esp_err_t 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);
esp_err_t scroll_direction(bool left_to_right);
/// Returns `true` iff the display is shifting with the cursor.
bool get_display_shift();
@@ -79,34 +83,34 @@ public:
/// Sets the display to shift with the cursor.
///
/// This is like autoscrolling.
void shift_display(bool shift_display);
esp_err_t shift_display(bool shift_display);
/// Shifts the cursor left 1
void shift_cursor_left();
esp_err_t shift_cursor_left();
/// Shifts the cursor right 1
void shift_cursor_right();
esp_err_t shift_cursor_right();
/// Shifts the display left 1
void shift_display_left();
esp_err_t shift_display_left();
/// Shifts the display right 1
void shift_display_right();
esp_err_t 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]);
esp_err_t create_custom_char(uint8_t location, const uint8_t char_map[8]);
/// Prints a single character to the display.
void print_char(char c);
esp_err_t print_char(char c);
/// Prints a single byte to the display.
void print_u8(uint8_t b);
esp_err_t print_u8(uint8_t b);
/// Writes a string to the display.
void write_str(const char* s);
esp_err_t write_str(const char* s);
};
#endif // LCD2004_HPP
+2 -3
View File
@@ -2,11 +2,10 @@
#define BLK_BOX_H
struct BlkBoxInitConfig {
// nothing for now...
/// Initial character LCD backlight state.
bool lcd_backlight = true;
};
void init_blk_box(BlkBoxInitConfig cfg);
#endif // BLK_BOX_H
+1 -1
View File
@@ -18,7 +18,7 @@ enum class CursorMode: uint8_t {
};
/// Initializes the 2004 Character LCD
void init_lcd();
void init_lcd(bool initial_backlight);
class LCDController {
public: