From a458b721fde208a0dc0983c208368a7decbbfc55 Mon Sep 17 00:00:00 2001 From: Alexey Zholtikov Date: Sat, 14 Jun 2025 17:39:45 +0300 Subject: [PATCH] feat!: added lcd 16x4 support --- CMakeLists.txt | 2 +- include/{zh_1602a.h => zh_160x.h} | 48 ++--- version.txt | 2 +- zh_1602a.c | 291 ----------------------------- zh_160x.c | 294 ++++++++++++++++++++++++++++++ 5 files changed, 322 insertions(+), 315 deletions(-) rename include/{zh_1602a.h => zh_160x.h} (72%) delete mode 100644 zh_1602a.c create mode 100644 zh_160x.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 0b27796..008d0fe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1 +1 @@ -idf_component_register(SRCS "zh_1602a.c" INCLUDE_DIRS "include" REQUIRES driver) \ No newline at end of file +idf_component_register(SRCS "zh_160x.c" INCLUDE_DIRS "include" REQUIRES driver) \ No newline at end of file diff --git a/include/zh_1602a.h b/include/zh_160x.h similarity index 72% rename from include/zh_1602a.h rename to include/zh_160x.h index 1c61083..3e04b2d 100644 --- a/include/zh_1602a.h +++ b/include/zh_160x.h @@ -6,40 +6,44 @@ #include "freertos/task.h" #include "driver/gpio.h" +#define ZH_LCD_16X2 1 +#define ZH_LCD_16X4 0 + #ifdef __cplusplus extern "C" { #endif - typedef struct // Structure for initial initialization of LCD 1602A. + typedef struct // Structure for initial initialization of LCD 160X. { - uint8_t rs_gpio_number; // GPIO connected to RS of LCD 1602A. - uint8_t e_gpio_number; // GPIO connected to E of LCD 1602A. - uint8_t d0_gpio_number; // GPIO connected to D0 of LCD 1602A. @note Required for 8 bit work mode only. - uint8_t d1_gpio_number; // GPIO connected to D1 of LCD 1602A. @note Required for 8 bit work mode only. - uint8_t d2_gpio_number; // GPIO connected to D2 of LCD 1602A. @note Required for 8 bit work mode only. - uint8_t d3_gpio_number; // GPIO connected to D3 of LCD 1602A. @note Required for 8 bit work mode only. - uint8_t d4_gpio_number; // GPIO connected to D4 of LCD 1602A. - uint8_t d5_gpio_number; // GPIO connected to D5 of LCD 1602A. - uint8_t d6_gpio_number; // GPIO connected to D6 of LCD 1602A. - uint8_t d7_gpio_number; // GPIO connected to D7 of LCD 1602A. - } zh_1602a_init_config_t; + bool lcd_size; // LCD size (ZH_LCD_16X2 or ZH_LCD_16X4). + uint8_t rs_gpio_number; // GPIO connected to RS of LCD 160X. + uint8_t e_gpio_number; // GPIO connected to E of LCD 160X. + uint8_t d0_gpio_number; // GPIO connected to D0 of LCD 160X. @note Required for 8 bit work mode only. + uint8_t d1_gpio_number; // GPIO connected to D1 of LCD 160X. @note Required for 8 bit work mode only. + uint8_t d2_gpio_number; // GPIO connected to D2 of LCD 160X. @note Required for 8 bit work mode only. + uint8_t d3_gpio_number; // GPIO connected to D3 of LCD 160X. @note Required for 8 bit work mode only. + uint8_t d4_gpio_number; // GPIO connected to D4 of LCD 160X. + uint8_t d5_gpio_number; // GPIO connected to D5 of LCD 160X. + uint8_t d6_gpio_number; // GPIO connected to D6 of LCD 160X. + uint8_t d7_gpio_number; // GPIO connected to D7 of LCD 160X. + } zh_160x_init_config_t; /** - * @brief Initializes the LCD 1602A. + * @brief Initializes the LCD 160X. * - * @param[in] config Pointer to 1602A initialized configuration structure. Can point to a temporary variable. + * @param[in] config Pointer to 160X initialized configuration structure. Can point to a temporary variable. * * @return ESP_OK if success or an error code otherwise. */ - esp_err_t zh_1602a_init(const zh_1602a_init_config_t *config); + esp_err_t zh_160x_init(const zh_160x_init_config_t *config); /** * @brief Clears the LCD screen. * * @return ESP_OK if success or an error code otherwise. */ - esp_err_t zh_1602a_lcd_clear(void); + esp_err_t zh_160x_lcd_clear(void); /** * @brief Sets the cursor to a specific position on the LCD. @@ -49,7 +53,7 @@ extern "C" * * @return ESP_OK if success or an error code otherwise. */ - esp_err_t zh_1602a_set_cursor(uint8_t row, uint8_t col); + esp_err_t zh_160x_set_cursor(uint8_t row, uint8_t col); /** * @brief Prints a string to the LCD. @@ -58,7 +62,7 @@ extern "C" * * @return ESP_OK if success or an error code otherwise. */ - esp_err_t zh_1602a_print_char(const char *str); + esp_err_t zh_160x_print_char(const char *str); /** * @brief Prints an integer to the LCD. @@ -67,7 +71,7 @@ extern "C" * * @return ESP_OK if success or an error code otherwise.. */ - esp_err_t zh_1602a_print_int(int num); + esp_err_t zh_160x_print_int(int num); /** * @brief Prints a floating-point number to the LCD. @@ -77,7 +81,7 @@ extern "C" * * @return ESP_OK if success or an error code otherwise. */ - esp_err_t zh_1602a_print_float(float num, uint8_t precision); + esp_err_t zh_160x_print_float(float num, uint8_t precision); /** * @brief Displays a progress bar on a specific row of the LCD. @@ -87,7 +91,7 @@ extern "C" * * @return ESP_OK if success or an error code otherwise. */ - esp_err_t zh_1602a_print_progress_bar(uint8_t row, uint8_t progress); + esp_err_t zh_160x_print_progress_bar(uint8_t row, uint8_t progress); /** * @brief Clears a specific row on the LCD. @@ -96,7 +100,7 @@ extern "C" * * @return ESP_OK if success or an error code otherwise. */ - esp_err_t zh_1602a_clear_row(uint8_t row); + esp_err_t zh_160x_clear_row(uint8_t row); #ifdef __cplusplus } diff --git a/version.txt b/version.txt index afaf360..359a5b9 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.0.0 \ No newline at end of file +2.0.0 \ No newline at end of file diff --git a/zh_1602a.c b/zh_1602a.c deleted file mode 100644 index c4ce3e2..0000000 --- a/zh_1602a.c +++ /dev/null @@ -1,291 +0,0 @@ -#include "zh_1602a.h" - -static const char *TAG = "zh_1602a"; - -#define ZH_1602A_LOGI(msg, ...) ESP_LOGI(TAG, msg, ##__VA_ARGS__) -#define ZH_1602A_LOGW(msg, ...) ESP_LOGW(TAG, msg, ##__VA_ARGS__) -#define ZH_1602A_LOGE(msg, ...) ESP_LOGE(TAG, msg, ##__VA_ARGS__) -#define ZH_1602A_LOGE_ERR(msg, err, ...) ESP_LOGE(TAG, "[%s:%d:%s] " msg, __FILE__, __LINE__, esp_err_to_name(err), ##__VA_ARGS__) - -#define ZH_1602A_CHECK(cond, err, msg, ...) \ - if (!(cond)) \ - { \ - ZH_1602A_LOGE_ERR(msg, err); \ - return err; \ - } - -#ifdef CONFIG_IDF_TARGET_ESP8266 -#define esp_delay_us(x) os_delay_us(x) -#else -#define esp_delay_us(x) esp_rom_delay_us(x) -#endif - -static esp_err_t _zh_1602a_gpio_init(const zh_1602a_init_config_t *config); -static void _zh_1602a_lcd_init(void); -static bool _zh_1602a_8bit_gpio_check(uint8_t rs, uint8_t e, uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3, uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7); -static bool _zh_1602a_4bit_gpio_check(uint8_t rs, uint8_t e, uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7); -static void _zh_1602a_send_command(uint8_t command); -static void _zh_1602a_send_data(uint8_t data); -static void _zh_1602a_pulse_enable(void); -static void _zh_1602a_send_8bit(uint8_t data); -static void _zh_1602a_send_4bit(uint8_t data); - -static uint8_t _rs_pin = 0; -static uint8_t _e_pin = 0; -static uint8_t _gpio_matrix[8] = {0}; -static bool _is_initialized = false; -static bool _is_8bit_work_mode = true; - -esp_err_t zh_1602a_init(const zh_1602a_init_config_t *config) -{ - ZH_1602A_LOGI("1602A initialization started."); - ZH_1602A_CHECK(config != NULL, ESP_ERR_INVALID_ARG, "1602A initialization failed. Invalid argument."); - ZH_1602A_CHECK(_is_initialized == false, ESP_ERR_INVALID_STATE, "1602A initialization failed. 1602A is already initialized."); - esp_err_t err = _zh_1602a_gpio_init(config); - ZH_1602A_CHECK(err == ESP_OK, ESP_FAIL, "1602A initialization failed. GPIO initialization failed."); - _zh_1602a_lcd_init(); - _is_initialized = true; - ZH_1602A_LOGI("1602A initialization completed successfully in %d bit mode.", (_is_8bit_work_mode == true) ? 8 : 4); - return ESP_OK; -} - -esp_err_t zh_1602a_lcd_clear(void) -{ - ZH_1602A_LOGI("1602A display cleaning started."); - ZH_1602A_CHECK(_is_initialized == true, ESP_ERR_INVALID_STATE, "1602A display cleaning failed. 1602A not initialized."); - _zh_1602a_send_command(0x01); - esp_delay_us(1600); - ZH_1602A_LOGI("1602A display cleaning completed successfully."); - return ESP_OK; -} - -esp_err_t zh_1602a_set_cursor(uint8_t row, uint8_t col) -{ - ZH_1602A_LOGI("1602A set cursor started."); - ZH_1602A_CHECK(row < 2 && col < 16, ESP_ERR_INVALID_ARG, "1602A set cursor failed. Invalid argument."); - ZH_1602A_CHECK(_is_initialized == true, ESP_ERR_INVALID_STATE, "1602A set cursor failed. 1602A not initialized."); - uint8_t address = (row == 0) ? col : (0x40 + col); - _zh_1602a_send_command(0x80 | address); - ZH_1602A_LOGI("1602A set cursor completed successfully."); - return ESP_OK; -} - -esp_err_t zh_1602a_print_char(const char *str) -{ - ZH_1602A_LOGI("1602A print char started."); - ZH_1602A_CHECK(str != NULL, ESP_ERR_INVALID_ARG, "1602A print char failed. Invalid argument."); - ZH_1602A_CHECK(_is_initialized == true, ESP_ERR_INVALID_STATE, "1602A print char failed. 1602A not initialized."); - while (*str != 0) - { - _zh_1602a_send_data((uint8_t)*str++); - } - ZH_1602A_LOGI("1602A print char completed successfully."); - return ESP_OK; -} - -esp_err_t zh_1602a_print_int(int num) -{ - ZH_1602A_LOGI("1602A print int started."); - ZH_1602A_CHECK(_is_initialized == true, ESP_ERR_INVALID_STATE, "1602A print int failed. 1602A not initialized."); - char buffer[12]; - sprintf(buffer, "%d", num); - zh_1602a_print_char(buffer); - ZH_1602A_LOGI("1602A print int completed successfully."); - return ESP_OK; -} - -esp_err_t zh_1602a_print_float(float num, uint8_t precision) -{ - ZH_1602A_LOGI("1602A print float started."); - ZH_1602A_CHECK(_is_initialized == true, ESP_ERR_INVALID_STATE, "1602A print float failed. 1602A not initialized."); - char buffer[16]; - sprintf(buffer, "%.*f", precision, num); - zh_1602a_print_char(buffer); - ZH_1602A_LOGI("1602A print float completed successfully."); - return ESP_OK; -} - -esp_err_t zh_1602a_print_progress_bar(uint8_t row, uint8_t progress) -{ - ZH_1602A_LOGI("1602A print progress bar started."); - ZH_1602A_CHECK(row < 2 && progress <= 100, ESP_ERR_INVALID_ARG, "1602A print progress bar failed. Invalid argument."); - ZH_1602A_CHECK(_is_initialized == true, ESP_ERR_INVALID_STATE, "1602A print progress bar failed. 1602A not initialized."); - uint8_t blocks = (progress * 16) / 100; - zh_1602a_set_cursor(row, 0); - for (uint8_t i = 0; i < 16; ++i) - { - if (i < blocks) - { - zh_1602a_print_char("\xFF"); - } - else - { - zh_1602a_print_char(" "); - } - } - ZH_1602A_LOGI("1602A print progress bar completed successfully."); - return ESP_OK; -} - -esp_err_t zh_1602a_clear_row(uint8_t row) -{ - ZH_1602A_LOGI("1602A clear row started."); - ZH_1602A_CHECK(row < 2, ESP_ERR_INVALID_ARG, "1602A clear row failed. Invalid argument."); - ZH_1602A_CHECK(_is_initialized == true, ESP_ERR_INVALID_STATE, "1602A clear row failed. 1602A not initialized."); - zh_1602a_set_cursor(row, 0); - for (uint8_t i = 0; i < 16; ++i) - { - zh_1602a_print_char(" "); - } - zh_1602a_set_cursor(row, 0); - ZH_1602A_LOGI("1602A clear row completed successfully."); - return ESP_OK; -} - -static esp_err_t _zh_1602a_gpio_init(const zh_1602a_init_config_t *config) -{ - ZH_1602A_CHECK(config->rs_gpio_number < GPIO_NUM_MAX || config->e_gpio_number < GPIO_NUM_MAX || config->d0_gpio_number < GPIO_NUM_MAX || - config->d1_gpio_number < GPIO_NUM_MAX || config->d2_gpio_number < GPIO_NUM_MAX || config->d3_gpio_number < GPIO_NUM_MAX || - config->d4_gpio_number < GPIO_NUM_MAX || config->d5_gpio_number < GPIO_NUM_MAX || config->d6_gpio_number < GPIO_NUM_MAX || config->d7_gpio_number < GPIO_NUM_MAX, - ESP_ERR_INVALID_ARG, "Invalid GPIO number."); - bool gpio_check = _zh_1602a_8bit_gpio_check(config->rs_gpio_number, config->e_gpio_number, config->d0_gpio_number, config->d1_gpio_number, config->d2_gpio_number, - config->d3_gpio_number, config->d4_gpio_number, config->d5_gpio_number, config->d6_gpio_number, config->d7_gpio_number); - if (gpio_check == false) - { - gpio_check = _zh_1602a_4bit_gpio_check(config->rs_gpio_number, config->e_gpio_number, config->d4_gpio_number, config->d5_gpio_number, config->d6_gpio_number, config->d7_gpio_number); - ZH_1602A_CHECK(gpio_check == true, ESP_FAIL, "Invalid GPIO number."); - _is_8bit_work_mode = false; - } - gpio_config_t pin_config = { - .mode = GPIO_MODE_OUTPUT, - .pull_down_en = GPIO_PULLDOWN_ENABLE, - .pin_bit_mask = (1ULL << config->rs_gpio_number) | (1ULL << config->e_gpio_number) | (1ULL << config->d0_gpio_number) | (1ULL << config->d1_gpio_number) | - (1ULL << config->d2_gpio_number) | (1ULL << config->d3_gpio_number) | (1ULL << config->d4_gpio_number) | (1ULL << config->d5_gpio_number) | - (1ULL << config->d6_gpio_number) | (1ULL << config->d7_gpio_number), - }; - esp_err_t err = gpio_config(&pin_config); - ZH_1602A_CHECK(err == ESP_OK, err, "GPIO initialization failed.") - _rs_pin = config->rs_gpio_number; - _e_pin = config->e_gpio_number; - _gpio_matrix[0] = config->d0_gpio_number; - _gpio_matrix[1] = config->d1_gpio_number; - _gpio_matrix[2] = config->d2_gpio_number; - _gpio_matrix[3] = config->d3_gpio_number; - _gpio_matrix[4] = config->d4_gpio_number; - _gpio_matrix[5] = config->d5_gpio_number; - _gpio_matrix[6] = config->d6_gpio_number; - _gpio_matrix[7] = config->d7_gpio_number; - return ESP_OK; -} - -static void _zh_1602a_lcd_init(void) -{ - vTaskDelay(20 / portTICK_PERIOD_MS); - _zh_1602a_send_4bit(0x03); - vTaskDelay(10 / portTICK_PERIOD_MS); - _zh_1602a_send_4bit(0x03); - vTaskDelay(10 / portTICK_PERIOD_MS); - _zh_1602a_send_4bit(0x03); - esp_delay_us(150); - if (_is_8bit_work_mode == false) - { - _zh_1602a_send_4bit(0x02); - } - _zh_1602a_send_command((_is_8bit_work_mode == true) ? 0x38 : 0x28); - vTaskDelay(10 / portTICK_PERIOD_MS); - _zh_1602a_send_command((_is_8bit_work_mode == true) ? 0x38 : 0x28); - esp_delay_us(150); - _zh_1602a_send_command((_is_8bit_work_mode == true) ? 0x38 : 0x28); - _zh_1602a_send_command(0x0C); - _zh_1602a_send_command(0x01); - _zh_1602a_send_command(0x06); - vTaskDelay(10 / portTICK_PERIOD_MS); -} - -static bool _zh_1602a_8bit_gpio_check(uint8_t rs, uint8_t e, uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3, uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7) -{ - uint8_t matrix[] = {rs, e, d0, d1, d2, d3, d4, d5, d6, d7}; - for (uint8_t i = 0; i < sizeof(matrix); ++i) - { - for (uint8_t j = i + 1; j < sizeof(matrix); ++j) - { - if (matrix[i] == matrix[j]) - { - return false; - } - } - } - return true; -} - -static bool _zh_1602a_4bit_gpio_check(uint8_t rs, uint8_t e, uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7) -{ - uint8_t matrix[] = {rs, e, d4, d5, d6, d7}; - for (uint8_t i = 0; i < sizeof(matrix); ++i) - { - for (uint8_t j = i + 1; j < sizeof(matrix); ++j) - { - if (matrix[i] == matrix[j]) - { - return false; - } - } - } - return true; -} - -static void _zh_1602a_send_command(uint8_t command) -{ - gpio_set_level(_rs_pin, 0); - gpio_set_level(_e_pin, 0); - if (_is_8bit_work_mode == true) - { - _zh_1602a_send_8bit(command); - } - else - { - _zh_1602a_send_4bit(command >> 4); - _zh_1602a_send_4bit(command); - } -} - -static void _zh_1602a_send_data(uint8_t data) -{ - gpio_set_level(_rs_pin, 1); - gpio_set_level(_e_pin, 0); - if (_is_8bit_work_mode == true) - { - _zh_1602a_send_8bit(data); - } - else - { - _zh_1602a_send_4bit(data >> 4); - _zh_1602a_send_4bit(data); - } -} - -static void _zh_1602a_pulse_enable(void) -{ - gpio_set_level(_e_pin, 1); - esp_delay_us(1); - gpio_set_level(_e_pin, 0); - esp_delay_us(40); -} - -static void _zh_1602a_send_8bit(uint8_t data) -{ - for (uint8_t i = 0; i <= 7; ++i) - { - gpio_set_level(_gpio_matrix[i], (data >> i) & 0x01); - } - _zh_1602a_pulse_enable(); -} - -static void _zh_1602a_send_4bit(uint8_t data) -{ - for (uint8_t i = 0; i <= 3; ++i) - { - gpio_set_level(_gpio_matrix[i + 4], (data >> i) & 0x01); - } - _zh_1602a_pulse_enable(); -} \ No newline at end of file diff --git a/zh_160x.c b/zh_160x.c new file mode 100644 index 0000000..e856c74 --- /dev/null +++ b/zh_160x.c @@ -0,0 +1,294 @@ +#include "zh_160x.h" + +static const char *TAG = "zh_160x"; + +#define ZH_160X_LOGI(msg, ...) ESP_LOGI(TAG, msg, ##__VA_ARGS__) +#define ZH_160X_LOGW(msg, ...) ESP_LOGW(TAG, msg, ##__VA_ARGS__) +#define ZH_160X_LOGE(msg, ...) ESP_LOGE(TAG, msg, ##__VA_ARGS__) +#define ZH_160X_LOGE_ERR(msg, err, ...) ESP_LOGE(TAG, "[%s:%d:%s] " msg, __FILE__, __LINE__, esp_err_to_name(err), ##__VA_ARGS__) + +#define ZH_160X_CHECK(cond, err, msg, ...) \ + if (!(cond)) \ + { \ + ZH_160X_LOGE_ERR(msg, err); \ + return err; \ + } + +#ifdef CONFIG_IDF_TARGET_ESP8266 +#define esp_delay_us(x) os_delay_us(x) +#else +#define esp_delay_us(x) esp_rom_delay_us(x) +#endif + +static esp_err_t _zh_160x_gpio_init(const zh_160x_init_config_t *config); +static void _zh_160x_lcd_init(void); +static bool _zh_160x_8bit_gpio_check(uint8_t rs, uint8_t e, uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3, uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7); +static bool _zh_160x_4bit_gpio_check(uint8_t rs, uint8_t e, uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7); +static void _zh_160x_send_command(uint8_t command); +static void _zh_160x_send_data(uint8_t data); +static void _zh_160x_pulse_enable(void); +static void _zh_160x_send_8bit(uint8_t data); +static void _zh_160x_send_4bit(uint8_t data); + +static bool _lcd_size = 0; +static uint8_t _rs_pin = 0; +static uint8_t _e_pin = 0; +static uint8_t _gpio_matrix[8] = {0}; +static bool _is_initialized = false; +static bool _is_8bit_work_mode = true; + +esp_err_t zh_160x_init(const zh_160x_init_config_t *config) +{ + ZH_160X_LOGI("160X initialization started."); + ZH_160X_CHECK(config != NULL, ESP_ERR_INVALID_ARG, "160X initialization failed. Invalid argument."); + ZH_160X_CHECK(_is_initialized == false, ESP_ERR_INVALID_STATE, "160X initialization failed. 160X is already initialized."); + esp_err_t err = _zh_160x_gpio_init(config); + ZH_160X_CHECK(err == ESP_OK, ESP_FAIL, "160X initialization failed. GPIO initialization failed."); + _zh_160x_lcd_init(); + _lcd_size = config->lcd_size; + _is_initialized = true; + ZH_160X_LOGI("160X initialization completed successfully in %d bit mode.", (_is_8bit_work_mode == true) ? 8 : 4); + return ESP_OK; +} + +esp_err_t zh_160x_lcd_clear(void) +{ + ZH_160X_LOGI("160X display cleaning started."); + ZH_160X_CHECK(_is_initialized == true, ESP_ERR_INVALID_STATE, "160X display cleaning failed. 160X not initialized."); + _zh_160x_send_command(0x01); + esp_delay_us(1600); + ZH_160X_LOGI("160X display cleaning completed successfully."); + return ESP_OK; +} + +esp_err_t zh_160x_set_cursor(uint8_t row, uint8_t col) +{ + ZH_160X_LOGI("160X set cursor started."); + ZH_160X_CHECK(row < ((_lcd_size == ZH_LCD_16X2) ? 2 : 4) && col < 16, ESP_ERR_INVALID_ARG, "160X set cursor failed. Invalid argument."); + ZH_160X_CHECK(_is_initialized == true, ESP_ERR_INVALID_STATE, "160X set cursor failed. 160X not initialized."); + _zh_160x_send_command(0x80 | ((row == 0) ? col : (row == 1) ? (0x40 + col) + : (row == 2) ? (0x10 + col) + : (0x50 + col))); + ZH_160X_LOGI("160X set cursor completed successfully."); + return ESP_OK; +} + +esp_err_t zh_160x_print_char(const char *str) +{ + ZH_160X_LOGI("160X print char started."); + ZH_160X_CHECK(str != NULL, ESP_ERR_INVALID_ARG, "160X print char failed. Invalid argument."); + ZH_160X_CHECK(_is_initialized == true, ESP_ERR_INVALID_STATE, "160X print char failed. 160X not initialized."); + while (*str != 0) + { + _zh_160x_send_data((uint8_t)*str++); + } + ZH_160X_LOGI("160X print char completed successfully."); + return ESP_OK; +} + +esp_err_t zh_160x_print_int(int num) +{ + ZH_160X_LOGI("160X print int started."); + ZH_160X_CHECK(_is_initialized == true, ESP_ERR_INVALID_STATE, "160X print int failed. 160X not initialized."); + char buffer[12]; + sprintf(buffer, "%d", num); + zh_160x_print_char(buffer); + ZH_160X_LOGI("160X print int completed successfully."); + return ESP_OK; +} + +esp_err_t zh_160x_print_float(float num, uint8_t precision) +{ + ZH_160X_LOGI("160X print float started."); + ZH_160X_CHECK(_is_initialized == true, ESP_ERR_INVALID_STATE, "160X print float failed. 160X not initialized."); + char buffer[16]; + sprintf(buffer, "%.*f", precision, num); + zh_160x_print_char(buffer); + ZH_160X_LOGI("160X print float completed successfully."); + return ESP_OK; +} + +esp_err_t zh_160x_print_progress_bar(uint8_t row, uint8_t progress) +{ + ZH_160X_LOGI("160X print progress bar started."); + ZH_160X_CHECK(row < ((_lcd_size == ZH_LCD_16X2) ? 2 : 4) && progress <= 100, ESP_ERR_INVALID_ARG, "160X print progress bar failed. Invalid argument."); + ZH_160X_CHECK(_is_initialized == true, ESP_ERR_INVALID_STATE, "160X print progress bar failed. 160X not initialized."); + uint8_t blocks = (progress * 16) / 100; + zh_160x_set_cursor(row, 0); + for (uint8_t i = 0; i < 16; ++i) + { + if (i < blocks) + { + zh_160x_print_char("\xFF"); + } + else + { + zh_160x_print_char(" "); + } + } + ZH_160X_LOGI("160X print progress bar completed successfully."); + return ESP_OK; +} + +esp_err_t zh_160x_clear_row(uint8_t row) +{ + ZH_160X_LOGI("160X clear row started."); + ZH_160X_CHECK(row < ((_lcd_size == ZH_LCD_16X2) ? 2 : 4), ESP_ERR_INVALID_ARG, "160X clear row failed. Invalid argument."); + ZH_160X_CHECK(_is_initialized == true, ESP_ERR_INVALID_STATE, "160X clear row failed. 160X not initialized."); + zh_160x_set_cursor(row, 0); + for (uint8_t i = 0; i < 16; ++i) + { + zh_160x_print_char(" "); + } + zh_160x_set_cursor(row, 0); + ZH_160X_LOGI("160X clear row completed successfully."); + return ESP_OK; +} + +static esp_err_t _zh_160x_gpio_init(const zh_160x_init_config_t *config) +{ + ZH_160X_CHECK(config->rs_gpio_number < GPIO_NUM_MAX || config->e_gpio_number < GPIO_NUM_MAX || config->d0_gpio_number < GPIO_NUM_MAX || + config->d1_gpio_number < GPIO_NUM_MAX || config->d2_gpio_number < GPIO_NUM_MAX || config->d3_gpio_number < GPIO_NUM_MAX || + config->d4_gpio_number < GPIO_NUM_MAX || config->d5_gpio_number < GPIO_NUM_MAX || config->d6_gpio_number < GPIO_NUM_MAX || config->d7_gpio_number < GPIO_NUM_MAX, + ESP_ERR_INVALID_ARG, "Invalid GPIO number."); + bool gpio_check = _zh_160x_8bit_gpio_check(config->rs_gpio_number, config->e_gpio_number, config->d0_gpio_number, config->d1_gpio_number, config->d2_gpio_number, + config->d3_gpio_number, config->d4_gpio_number, config->d5_gpio_number, config->d6_gpio_number, config->d7_gpio_number); + if (gpio_check == false) + { + gpio_check = _zh_160x_4bit_gpio_check(config->rs_gpio_number, config->e_gpio_number, config->d4_gpio_number, config->d5_gpio_number, config->d6_gpio_number, config->d7_gpio_number); + ZH_160X_CHECK(gpio_check == true, ESP_FAIL, "Invalid GPIO number."); + _is_8bit_work_mode = false; + } + gpio_config_t pin_config = { + .mode = GPIO_MODE_OUTPUT, + .pull_down_en = GPIO_PULLDOWN_ENABLE, + .pin_bit_mask = (1ULL << config->rs_gpio_number) | (1ULL << config->e_gpio_number) | (1ULL << config->d0_gpio_number) | (1ULL << config->d1_gpio_number) | + (1ULL << config->d2_gpio_number) | (1ULL << config->d3_gpio_number) | (1ULL << config->d4_gpio_number) | (1ULL << config->d5_gpio_number) | + (1ULL << config->d6_gpio_number) | (1ULL << config->d7_gpio_number), + }; + esp_err_t err = gpio_config(&pin_config); + ZH_160X_CHECK(err == ESP_OK, err, "GPIO initialization failed.") + _rs_pin = config->rs_gpio_number; + _e_pin = config->e_gpio_number; + _gpio_matrix[0] = config->d0_gpio_number; + _gpio_matrix[1] = config->d1_gpio_number; + _gpio_matrix[2] = config->d2_gpio_number; + _gpio_matrix[3] = config->d3_gpio_number; + _gpio_matrix[4] = config->d4_gpio_number; + _gpio_matrix[5] = config->d5_gpio_number; + _gpio_matrix[6] = config->d6_gpio_number; + _gpio_matrix[7] = config->d7_gpio_number; + return ESP_OK; +} + +static void _zh_160x_lcd_init(void) +{ + vTaskDelay(20 / portTICK_PERIOD_MS); + _zh_160x_send_4bit(0x03); + vTaskDelay(10 / portTICK_PERIOD_MS); + _zh_160x_send_4bit(0x03); + vTaskDelay(10 / portTICK_PERIOD_MS); + _zh_160x_send_4bit(0x03); + esp_delay_us(150); + if (_is_8bit_work_mode == false) + { + _zh_160x_send_4bit(0x02); + } + _zh_160x_send_command((_is_8bit_work_mode == true) ? 0x38 : 0x28); + vTaskDelay(10 / portTICK_PERIOD_MS); + _zh_160x_send_command((_is_8bit_work_mode == true) ? 0x38 : 0x28); + esp_delay_us(150); + _zh_160x_send_command((_is_8bit_work_mode == true) ? 0x38 : 0x28); + _zh_160x_send_command(0x0C); + _zh_160x_send_command(0x01); + _zh_160x_send_command(0x06); + vTaskDelay(10 / portTICK_PERIOD_MS); +} + +static bool _zh_160x_8bit_gpio_check(uint8_t rs, uint8_t e, uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3, uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7) +{ + uint8_t matrix[] = {rs, e, d0, d1, d2, d3, d4, d5, d6, d7}; + for (uint8_t i = 0; i < sizeof(matrix); ++i) + { + for (uint8_t j = i + 1; j < sizeof(matrix); ++j) + { + if (matrix[i] == matrix[j]) + { + return false; + } + } + } + return true; +} + +static bool _zh_160x_4bit_gpio_check(uint8_t rs, uint8_t e, uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7) +{ + uint8_t matrix[] = {rs, e, d4, d5, d6, d7}; + for (uint8_t i = 0; i < sizeof(matrix); ++i) + { + for (uint8_t j = i + 1; j < sizeof(matrix); ++j) + { + if (matrix[i] == matrix[j]) + { + return false; + } + } + } + return true; +} + +static void _zh_160x_send_command(uint8_t command) +{ + gpio_set_level(_rs_pin, 0); + gpio_set_level(_e_pin, 0); + if (_is_8bit_work_mode == true) + { + _zh_160x_send_8bit(command); + } + else + { + _zh_160x_send_4bit(command >> 4); + _zh_160x_send_4bit(command); + } +} + +static void _zh_160x_send_data(uint8_t data) +{ + gpio_set_level(_rs_pin, 1); + gpio_set_level(_e_pin, 0); + if (_is_8bit_work_mode == true) + { + _zh_160x_send_8bit(data); + } + else + { + _zh_160x_send_4bit(data >> 4); + _zh_160x_send_4bit(data); + } +} + +static void _zh_160x_pulse_enable(void) +{ + gpio_set_level(_e_pin, 1); + esp_delay_us(1); + gpio_set_level(_e_pin, 0); + esp_delay_us(40); +} + +static void _zh_160x_send_8bit(uint8_t data) +{ + for (uint8_t i = 0; i <= 7; ++i) + { + gpio_set_level(_gpio_matrix[i], (data >> i) & 0x01); + } + _zh_160x_pulse_enable(); +} + +static void _zh_160x_send_4bit(uint8_t data) +{ + for (uint8_t i = 0; i <= 3; ++i) + { + gpio_set_level(_gpio_matrix[i + 4], (data >> i) & 0x01); + } + _zh_160x_pulse_enable(); +} \ No newline at end of file