From 9dd10f196a6d7259cc2f38026234165bbedd1127 Mon Sep 17 00:00:00 2001 From: Alexey Zholtikov Date: Sat, 24 May 2025 10:35:46 +0300 Subject: [PATCH] feat: initial --- .gitignore | 6 +- CMakeLists.txt | 2 +- README.md | 117 ++++++++++++++++++++++++++++- include/main.h | 0 include/zh_1602a_i2c.h | 93 +++++++++++++++++++++++ main.c | 0 version.txt | 1 + zh_1602a_i2c.c | 165 +++++++++++++++++++++++++++++++++++++++++ 8 files changed, 376 insertions(+), 8 deletions(-) delete mode 100644 include/main.h create mode 100644 include/zh_1602a_i2c.h delete mode 100644 main.c create mode 100644 zh_1602a_i2c.c diff --git a/.gitignore b/.gitignore index 65225d8..496ee2c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1 @@ -.DS_Store -.vscode -build -sdkconfig -sdkconfig.old \ No newline at end of file +.DS_Store \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 9ab5c51..ed68433 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1 +1 @@ -idf_component_register(SRCS "main.c" INCLUDE_DIRS "include") \ No newline at end of file +idf_component_register(SRCS "zh_1602a_i2c.c" INCLUDE_DIRS "include" REQUIRES zh_pcf8574) \ No newline at end of file diff --git a/README.md b/README.md index 3df3e4d..7a6c4cb 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,116 @@ -# esp_component_template +# ESP32 ESP-IDF and ESP8266 RTOS SDK component for liquid crystal display module 1602A via I2C connection (PCF8574) -esp_component_template \ No newline at end of file +## Tested on + +1. [ESP8266 RTOS_SDK v3.4](https://docs.espressif.com/projects/esp8266-rtos-sdk/en/latest/index.html#) +2. [ESP32 ESP-IDF v5.4](https://docs.espressif.com/projects/esp-idf/en/release-v5.4/esp32/index.html) + +## Features + +1. Support of 16 LCD 1602A on one bus. + +## Connection + +| 1602A | PCF8574 | +| ----- | ------- | +| RS | P0 | +| E | P2 | +| D4 | P4 | +| D5 | P5 | +| D6 | P6 | +| D7 | P7 | + +## Dependencies + +1. [zh_vector](http://git.zh.com.ru/alexey.zholtikov/zh_vector) +2. [zh_pcf8574](http://git.zh.com.ru/alexey.zholtikov/zh_pcf8574) + +## Using + +In an existing project, run the following command to install the components: + +```text +cd ../your_project/components +git clone http://git.zh.com.ru/alexey.zholtikov/zh_1602a_i2c +git clone http://git.zh.com.ru/alexey.zholtikov/zh_pcf8574 +git clone http://git.zh.com.ru/alexey.zholtikov/zh_vector +``` + +In the application, add the component: + +```c +#include "zh_1602a_i2c.h" +``` + +## Examples + +One LCD on bus: + +```c +#include "zh_1602a_i2c.h" + +#define I2C_PORT (I2C_NUM_MAX - 1) + +#ifndef CONFIG_IDF_TARGET_ESP8266 +i2c_master_bus_handle_t i2c_bus_handle = NULL; +#endif + +zh_pcf8574_handle_t lcd_1602a_handle = {0}; + +void app_main(void) +{ + esp_log_level_set("zh_1602a_i2c", ESP_LOG_NONE); // For ESP8266 first enable "Component config -> Log output -> Enable log set level" via menuconfig. + esp_log_level_set("zh_pcf8574", ESP_LOG_NONE); // For ESP8266 first enable "Component config -> Log output -> Enable log set level" via menuconfig. + esp_log_level_set("zh_vector", ESP_LOG_NONE); // For ESP8266 first enable "Component config -> Log output -> Enable log set level" via menuconfig. +#ifdef CONFIG_IDF_TARGET_ESP8266 + i2c_config_t i2c_config = { + .mode = I2C_MODE_MASTER, + .sda_io_num = GPIO_NUM_4, // In accordance with used chip. + .sda_pullup_en = GPIO_PULLUP_ENABLE, + .scl_io_num = GPIO_NUM_5, // In accordance with used chip. + .scl_pullup_en = GPIO_PULLUP_ENABLE, + }; + i2c_driver_install(I2C_PORT, i2c_config.mode); + i2c_param_config(I2C_PORT, &i2c_config); +#else + i2c_master_bus_config_t i2c_bus_config = { + .clk_source = I2C_CLK_SRC_DEFAULT, + .i2c_port = I2C_PORT, + .scl_io_num = GPIO_NUM_22, // In accordance with used chip. + .sda_io_num = GPIO_NUM_21, // In accordance with used chip. + .glitch_ignore_cnt = 7, + .flags.enable_internal_pullup = true, + }; + i2c_new_master_bus(&i2c_bus_config, &i2c_bus_handle); +#endif + zh_pcf8574_init_config_t pcf8574_init_config = ZH_PCF8574_INIT_CONFIG_DEFAULT(); +#ifdef CONFIG_IDF_TARGET_ESP8266 + pcf8574_init_config.i2c_port = I2C_PORT; +#else + pcf8574_init_config.i2c_handle = i2c_bus_handle; +#endif + pcf8574_init_config.i2c_address = 0x38; + zh_pcf8574_init(&pcf8574_init_config, &lcd_1602a_handle); + zh_1602a_init(&lcd_1602a_handle); + for (;;) + { + zh_1602a_set_cursor(&lcd_1602a_handle, 0, 0); + zh_1602a_print_char(&lcd_1602a_handle, "LCD 1602A"); + zh_1602a_set_cursor(&lcd_1602a_handle, 1, 0); + zh_1602a_print_char(&lcd_1602a_handle, "Hello World!"); + vTaskDelay(5000 / portTICK_PERIOD_MS); + zh_1602a_clear_row(&lcd_1602a_handle, 0); + zh_1602a_print_char(&lcd_1602a_handle, "Progress: "); + for (uint8_t i = 0; i <= 100; ++i) + { + zh_1602a_set_cursor(&lcd_1602a_handle, 0, 10); + zh_1602a_print_int(&lcd_1602a_handle, i); + zh_1602a_print_char(&lcd_1602a_handle, "%"); + zh_1602a_print_progress_bar(&lcd_1602a_handle, 1, i); + } + vTaskDelay(5000 / portTICK_PERIOD_MS); + zh_1602a_lcd_clear(&lcd_1602a_handle); + vTaskDelay(5000 / portTICK_PERIOD_MS); + } +} +``` diff --git a/include/main.h b/include/main.h deleted file mode 100644 index e69de29..0000000 diff --git a/include/zh_1602a_i2c.h b/include/zh_1602a_i2c.h new file mode 100644 index 0000000..7fa266e --- /dev/null +++ b/include/zh_1602a_i2c.h @@ -0,0 +1,93 @@ +#pragma once + +#include "zh_pcf8574.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + + /** + * @brief Initializes the LCD 1602A. + * + * @param[in] handle Pointer to unique PCF8574 handle. + * + * @return ESP_OK if success or an error code otherwise. + */ + esp_err_t zh_1602a_init(zh_pcf8574_handle_t *handle); + + /** + * @brief Clears the LCD screen. + * + * @param[in] handle Pointer to unique PCF8574 handle. + * + * @return ESP_OK if success or an error code otherwise. + */ + esp_err_t zh_1602a_lcd_clear(zh_pcf8574_handle_t *handle); + + /** + * @brief Sets the cursor to a specific position on the LCD. + * + * @param[in] handle Pointer to unique PCF8574 handle. + * @param[in] row The row number (0 or 1). + * @param[in] col The column number (0–15). + * + * @return ESP_OK if success or an error code otherwise. + */ + esp_err_t zh_1602a_set_cursor(zh_pcf8574_handle_t *handle, uint8_t row, uint8_t col); + + /** + * @brief Prints a string to the LCD. + * + * @param[in] handle Pointer to unique PCF8574 handle. + * @param[in] str Pointer to the string to be displayed. + * + * @return ESP_OK if success or an error code otherwise. + */ + esp_err_t zh_1602a_print_char(zh_pcf8574_handle_t *handle, const char *str); + + /** + * @brief Prints an integer to the LCD. + * + * @param[in] handle Pointer to unique PCF8574 handle. + * @param[in] num The integer to be displayed. + * + * @return ESP_OK if success or an error code otherwise.. + */ + esp_err_t zh_1602a_print_int(zh_pcf8574_handle_t *handle, int num); + + /** + * @brief Prints a floating-point number to the LCD. + * + * @param[in] handle Pointer to unique PCF8574 handle. + * @param[in] num The floating-point number to be displayed. + * @param[in] precision The number of decimal places to display. + * + * @return ESP_OK if success or an error code otherwise. + */ + esp_err_t zh_1602a_print_float(zh_pcf8574_handle_t *handle, float num, uint8_t precision); + + /** + * @brief Displays a progress bar on a specific row of the LCD. + * + * @param[in] handle Pointer to unique PCF8574 handle. + * @param[in] row The row number (0 or 1). + * @param[in] progress The progress percentage (0–100). + * + * @return ESP_OK if success or an error code otherwise. + */ + esp_err_t zh_1602a_print_progress_bar(zh_pcf8574_handle_t *handle, uint8_t row, uint8_t progress); + + /** + * @brief Clears a specific row on the LCD. + * + * @param[in] handle Pointer to unique PCF8574 handle. + * @param[in] row The row number (0 or 1). + * + * @return ESP_OK if success or an error code otherwise. + */ + esp_err_t zh_1602a_clear_row(zh_pcf8574_handle_t *handle, uint8_t row); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/main.c b/main.c deleted file mode 100644 index e69de29..0000000 diff --git a/version.txt b/version.txt index e69de29..afaf360 100644 --- a/version.txt +++ b/version.txt @@ -0,0 +1 @@ +1.0.0 \ No newline at end of file diff --git a/zh_1602a_i2c.c b/zh_1602a_i2c.c new file mode 100644 index 0000000..f614fa2 --- /dev/null +++ b/zh_1602a_i2c.c @@ -0,0 +1,165 @@ +#include "zh_1602a_i2c.h" + +static const char *TAG = "zh_1602a_i2c"; + +#define ZH_1602A_I2C_LOGI(msg, ...) ESP_LOGI(TAG, msg, ##__VA_ARGS__) +#define ZH_1602A_I2C_LOGW(msg, ...) ESP_LOGW(TAG, msg, ##__VA_ARGS__) +#define ZH_1602A_I2C_LOGE(msg, ...) ESP_LOGE(TAG, msg, ##__VA_ARGS__) +#define ZH_1602A_I2C_LOGE_ERR(msg, err, ...) ESP_LOGE(TAG, "[%s:%d:%s] " msg, __FILE__, __LINE__, esp_err_to_name(err), ##__VA_ARGS__) + +#define ZH_1602A_I2C_CHECK(cond, err, msg, ...) \ + if (!(cond)) \ + { \ + ZH_1602A_I2C_LOGE_ERR(msg, err); \ + return err; \ + } + +#define LCD_1602A_PULSE \ + zh_pcf8574_write_gpio(handle, 2, true); \ + vTaskDelay(10 / portTICK_PERIOD_MS); \ + zh_pcf8574_write_gpio(handle, 2, false); \ + vTaskDelay(10 / portTICK_PERIOD_MS); + +static void _zh_1602a_lcd_init(zh_pcf8574_handle_t *handle); +static void _zh_1602a_send_command(zh_pcf8574_handle_t *handle, uint8_t command); +static void _zh_1602a_send_data(zh_pcf8574_handle_t *handle, uint8_t data); + +esp_err_t zh_1602a_init(zh_pcf8574_handle_t *handle) +{ + ZH_1602A_I2C_LOGI("1602A initialization started."); + ZH_1602A_I2C_CHECK(handle != NULL, ESP_ERR_INVALID_ARG, "1602A initialization failed. Invalid argument."); + ZH_1602A_I2C_CHECK(handle->is_initialized == true, ESP_ERR_INVALID_STATE, "1602A initialization failed. PCF8574 not initialized."); + _zh_1602a_lcd_init(handle); + ZH_1602A_I2C_LOGI("1602A initialization completed successfully."); + return ESP_OK; +} + +esp_err_t zh_1602a_lcd_clear(zh_pcf8574_handle_t *handle) +{ + ZH_1602A_I2C_LOGI("1602A display cleaning started."); + ZH_1602A_I2C_CHECK(handle != NULL, ESP_ERR_INVALID_ARG, "1602A display cleaning failed. Invalid argument."); + ZH_1602A_I2C_CHECK(handle->is_initialized == true, ESP_ERR_INVALID_STATE, "1602A display cleaning failed. PCF8574 not initialized."); + _zh_1602a_send_command(handle, 0x01); + ZH_1602A_I2C_LOGI("1602A display cleaning completed successfully."); + return ESP_OK; +} + +esp_err_t zh_1602a_set_cursor(zh_pcf8574_handle_t *handle, uint8_t row, uint8_t col) +{ + ZH_1602A_I2C_LOGI("1602A set cursor started."); + ZH_1602A_I2C_CHECK(row < 2 && col < 16 && handle != NULL, ESP_ERR_INVALID_ARG, "1602A set cursor failed. Invalid argument."); + ZH_1602A_I2C_CHECK(handle->is_initialized == true, ESP_ERR_INVALID_STATE, "1602A set cursor failed. PCF8574 not initialized."); + uint8_t address = (row == 0) ? col : (0x40 + col); + _zh_1602a_send_command(handle, 0x80 | address); + ZH_1602A_I2C_LOGI("1602A set cursor completed successfully."); + return ESP_OK; +} + +esp_err_t zh_1602a_print_char(zh_pcf8574_handle_t *handle, const char *str) +{ + ZH_1602A_I2C_LOGI("1602A print char started."); + ZH_1602A_I2C_CHECK(str != NULL && handle != NULL, ESP_ERR_INVALID_ARG, "1602A print char failed. Invalid argument."); + ZH_1602A_I2C_CHECK(handle->is_initialized == true, ESP_ERR_INVALID_STATE, "1602A print char failed. PCF8574 not initialized."); + while (*str != 0) + { + _zh_1602a_send_data(handle, (uint8_t)*str++); + } + ZH_1602A_I2C_LOGI("1602A print char completed successfully."); + return ESP_OK; +} + +esp_err_t zh_1602a_print_int(zh_pcf8574_handle_t *handle, int num) +{ + ZH_1602A_I2C_LOGI("1602A print int started."); + ZH_1602A_I2C_CHECK(handle != NULL, ESP_ERR_INVALID_ARG, "1602A print int failed. Invalid argument."); + ZH_1602A_I2C_CHECK(handle->is_initialized == true, ESP_ERR_INVALID_STATE, "1602A print int failed. PCF8574 not initialized."); + char buffer[12]; + sprintf(buffer, "%d", num); + zh_1602a_print_char(handle, buffer); + ZH_1602A_I2C_LOGI("1602A print int completed successfully."); + return ESP_OK; +} + +esp_err_t zh_1602a_print_float(zh_pcf8574_handle_t *handle, float num, uint8_t precision) +{ + ZH_1602A_I2C_LOGI("1602A print float started."); + ZH_1602A_I2C_CHECK(handle != NULL, ESP_ERR_INVALID_ARG, "1602A print float failed. Invalid argument."); + ZH_1602A_I2C_CHECK(handle->is_initialized == true, ESP_ERR_INVALID_STATE, "1602A print float failed. PCF8574 not initialized."); + char buffer[16]; + sprintf(buffer, "%.*f", precision, num); + zh_1602a_print_char(handle, buffer); + ZH_1602A_I2C_LOGI("1602A print float completed successfully."); + return ESP_OK; +} + +esp_err_t zh_1602a_print_progress_bar(zh_pcf8574_handle_t *handle, uint8_t row, uint8_t progress) +{ + ZH_1602A_I2C_LOGI("1602A print progress bar started."); + ZH_1602A_I2C_CHECK(row < 2 && progress <= 100 && handle != NULL, ESP_ERR_INVALID_ARG, "1602A print progress bar failed. Invalid argument."); + ZH_1602A_I2C_CHECK(handle->is_initialized == true, ESP_ERR_INVALID_STATE, "1602A print progress bar failed. PCF8574 not initialized."); + uint8_t blocks = (progress * 16) / 100; + zh_1602a_set_cursor(handle, row, 0); + for (uint8_t i = 0; i < 16; ++i) + { + if (i < blocks) + { + zh_1602a_print_char(handle, "\xFF"); + } + else + { + zh_1602a_print_char(handle, " "); + } + } + ZH_1602A_I2C_LOGI("1602A print progress bar completed successfully."); + return ESP_OK; +} + +esp_err_t zh_1602a_clear_row(zh_pcf8574_handle_t *handle, uint8_t row) +{ + ZH_1602A_I2C_LOGI("1602A clear row started."); + ZH_1602A_I2C_CHECK(row < 2 && handle != NULL, ESP_ERR_INVALID_ARG, "1602A clear row failed. Invalid argument."); + ZH_1602A_I2C_CHECK(handle->is_initialized == true, ESP_ERR_INVALID_STATE, "1602A clear row failed. PCF8574 not initialized."); + zh_1602a_set_cursor(handle, row, 0); + for (uint8_t i = 0; i < 16; ++i) + { + zh_1602a_print_char(handle, " "); + } + zh_1602a_set_cursor(handle, row, 0); + ZH_1602A_I2C_LOGI("1602A clear row completed successfully."); + return ESP_OK; +} + +static void _zh_1602a_lcd_init(zh_pcf8574_handle_t *handle) +{ + vTaskDelay(20 / portTICK_PERIOD_MS); + zh_pcf8574_write(handle, 0x30); + LCD_1602A_PULSE; + zh_pcf8574_write(handle, 0x30); + LCD_1602A_PULSE; + zh_pcf8574_write(handle, 0x30); + LCD_1602A_PULSE; + zh_pcf8574_write(handle, 0x20); + LCD_1602A_PULSE; + _zh_1602a_send_command(handle, 0x28); + _zh_1602a_send_command(handle, 0x28); + _zh_1602a_send_command(handle, 0x28); + _zh_1602a_send_command(handle, 0x0C); + _zh_1602a_send_command(handle, 0x01); + _zh_1602a_send_command(handle, 0x06); +} + +static void _zh_1602a_send_command(zh_pcf8574_handle_t *handle, uint8_t command) +{ + zh_pcf8574_write(handle, (command & 0xF0)); + LCD_1602A_PULSE; + zh_pcf8574_write(handle, command << 4); + LCD_1602A_PULSE; +} + +static void _zh_1602a_send_data(zh_pcf8574_handle_t *handle, uint8_t data) +{ + zh_pcf8574_write(handle, (data & 0xF0) | 0x01); + LCD_1602A_PULSE; + zh_pcf8574_write(handle, (data << 4) | 0x01); + LCD_1602A_PULSE; +}