Compare commits

...
10 Commits
6 changed files with 703 additions and 188 deletions

No files matched your search

+164 -68
View File
@@ -1,53 +1,26 @@
# ESP32 ESP-IDF component for liquid crystal display module 1602(4)A via I2C connection (PCF8574(A))
## Tested on
1. [ESP32 ESP-IDF v6.0.0](https://docs.espressif.com/projects/esp-idf/en/v6.0/esp32/index.html)
## SAST Tools
[PVS-Studio](https://pvs-studio.com/pvs-studio/?utm_source=website&utm_medium=github&utm_campaign=open_source) - static analyzer for C, C++, C#, and Java code.
# ESP32 ESP-IDF component for LCD 1602A/1604A via I2C (PCF8574)
## Features
1. Support of 16 LCD 160X on one bus.
1. Support for HD44780-compatible 16x2 and 16x4 LCD displays via I2C.
2. 4-bit interface mode for efficient data transfer through PCF8574 expander.
3. Print strings, integers, and floating-point numbers to the display.
4. Progress bar display with custom characters on any row.
5. Cursor visibility and blink control.
6. Row-specific clear operations in addition to full screen clear.
## Attention
For correct operation, please enable the following settings in the menuconfig:
```text
CONFIG_FREERTOS_HZ=1000
CONFIG_GPIO_CTRL_FUNC_IN_IRAM
CONFIG_I2C_ISR_IRAM_SAFE
CONFIG_I2C_MASTER_ISR_HANDLER_IN_IRAM
```
## Connection
| 1602(4)A | PCF8574 |
| -------- | ------- |
| RS | P0 |
| E | P2 |
| D4 | P4 |
| D5 | P5 |
| D6 | P6 |
| D7 | P7 |
## Dependencies
1. [zh_vector](https://github.com/aZholtikov/zh_vector)
2. [zh_pcf8574](https://github.com/aZholtikov/zh_pcf8574)
1. The PCF8574 expander handle must remain valid until the LCD handle is deinitialized.
2. DDRAM location 0xFF must be configured as a custom character in CGRAM for progress bar to display correctly.
## Using
In an existing project, run the following command to install the components:
In an existing project, run the following command to install the component:
```text
```bash
cd ../your_project/components
git clone https://github.com/aZholtikov/zh_160x_i2c
git clone https://github.com/aZholtikov/zh_pcf8574
git clone https://github.com/aZholtikov/zh_vector
```
In the application, add the component:
@@ -58,20 +31,20 @@ In the application, add the component:
## Examples
One LCD on bus:
### Basic Text Output Example
```c
#include "zh_160x_i2c.h"
#define I2C_PORT (I2C_NUM_MAX - 1)
zh_pcf8574_handle_t lcd_160x_handle = {0};
zh_pcf8574_handle_t *pcf8574_handle = NULL;
zh_160x_i2c_handle_t *lcd_handle = NULL;
void app_main(void)
{
esp_log_level_set("zh_160x_i2c", ESP_LOG_ERROR);
esp_log_level_set("zh_pcf8574", ESP_LOG_ERROR);
esp_log_level_set("zh_vector", ESP_LOG_ERROR);
i2c_master_bus_config_t i2c_bus_config = {
.clk_source = I2C_CLK_SRC_DEFAULT,
.i2c_port = I2C_PORT,
@@ -81,34 +54,157 @@ void app_main(void)
.flags.enable_internal_pullup = true,
};
i2c_master_bus_handle_t i2c_bus_handle = NULL;
i2c_new_master_bus(&i2c_bus_config, &i2c_bus_handle);
zh_pcf8574_init_config_t config = ZH_PCF8574_INIT_CONFIG_DEFAULT();
config.i2c_handle = i2c_bus_handle;
config.i2c_address = 0x27;
zh_pcf8574_init(&config, &lcd_160x_handle);
zh_160x_init(&lcd_160x_handle, ZH_LCD_16X2);
for (;;)
esp_err_t ret = i2c_new_master_bus(&i2c_bus_config, &i2c_bus_handle);
if (ret != ESP_OK)
{
zh_160x_on_cursor(&lcd_160x_handle, true);
zh_160x_set_cursor(&lcd_160x_handle, 0, 0);
zh_160x_print_char(&lcd_160x_handle, "LCD 160X");
zh_160x_set_cursor(&lcd_160x_handle, 1, 0);
zh_160x_print_char(&lcd_160x_handle, "Hello World!");
vTaskDelay(5000 / portTICK_PERIOD_MS);
zh_160x_off_cursor(&lcd_160x_handle);
zh_160x_set_cursor(&lcd_160x_handle, 0, 0);
zh_160x_print_char(&lcd_160x_handle, "Progress: ");
for (uint8_t i = 0; i <= 100; ++i)
{
zh_160x_set_cursor(&lcd_160x_handle, 0, 10);
zh_160x_print_int(&lcd_160x_handle, i);
zh_160x_print_char(&lcd_160x_handle, "%");
zh_160x_print_progress_bar(&lcd_160x_handle, 1, i);
vTaskDelay(100 / portTICK_PERIOD_MS);
}
vTaskDelay(5000 / portTICK_PERIOD_MS);
zh_160x_lcd_clear(&lcd_160x_handle);
vTaskDelay(5000 / portTICK_PERIOD_MS);
printf("I2C bus initialization error\n");
return;
}
zh_pcf8574_init_config_t pcf_config = ZH_PCF8574_INIT_CONFIG_DEFAULT();
pcf_config.i2c_handle = i2c_bus_handle;
pcf_config.i2c_address = 0x27;
ret = zh_pcf8574_init(&pcf_config, &pcf8574_handle);
if (ret != ESP_OK)
{
printf("PCF8574 initialization error\n");
i2c_del_master_bus(i2c_bus_handle);
return;
}
ret = zh_160x_init(&pcf8574_handle, &lcd_handle, ZH_LCD_16X2);
if (ret != ESP_OK)
{
printf("LCD initialization error\n");
zh_pcf8574_deinit(&pcf8574_handle);
i2c_del_master_bus(i2c_bus_handle);
return;
}
zh_160x_set_cursor(&lcd_handle, 0, 0);
zh_160x_print_char(&lcd_handle, "LCD 160X");
zh_160x_set_cursor(&lcd_handle, 1, 0);
zh_160x_print_int(&lcd_handle, 42);
zh_160x_off_cursor(&lcd_handle);
zh_160x_deinit(&lcd_handle);
zh_pcf8574_deinit(&pcf8574_handle);
i2c_del_master_bus(i2c_bus_handle);
}
```
### Float and Cursor Control Example
```c
#include "zh_160x_i2c.h"
#define I2C_PORT (I2C_NUM_MAX - 1)
zh_pcf8574_handle_t *pcf8574_handle = NULL;
zh_160x_i2c_handle_t *lcd_handle = NULL;
void app_main(void)
{
esp_log_level_set("zh_160x_i2c", ESP_LOG_ERROR);
esp_log_level_set("zh_pcf8574", ESP_LOG_ERROR);
i2c_master_bus_config_t i2c_bus_config = {
.clk_source = I2C_CLK_SRC_DEFAULT,
.i2c_port = I2C_PORT,
.scl_io_num = GPIO_NUM_22,
.sda_io_num = GPIO_NUM_21,
.glitch_ignore_cnt = 7,
.flags.enable_internal_pullup = true,
};
i2c_master_bus_handle_t i2c_bus_handle = NULL;
esp_err_t ret = i2c_new_master_bus(&i2c_bus_config, &i2c_bus_handle);
if (ret != ESP_OK)
{
printf("I2C bus initialization error\n");
return;
}
zh_pcf8574_init_config_t pcf_config = ZH_PCF8574_INIT_CONFIG_DEFAULT();
pcf_config.i2c_handle = i2c_bus_handle;
pcf_config.i2c_address = 0x27;
ret = zh_pcf8574_init(&pcf_config, &pcf8574_handle);
if (ret != ESP_OK)
{
printf("PCF8574 initialization error\n");
i2c_del_master_bus(i2c_bus_handle);
return;
}
ret = zh_160x_init(&pcf8574_handle, &lcd_handle, ZH_LCD_16X2);
if (ret != ESP_OK)
{
printf("LCD initialization error\n");
zh_pcf8574_deinit(&pcf8574_handle);
i2c_del_master_bus(i2c_bus_handle);
return;
}
zh_160x_on_cursor(&lcd_handle, true);
zh_160x_set_cursor(&lcd_handle, 0, 0);
zh_160x_print_char(&lcd_handle, "Temp: ");
zh_160x_print_float(&lcd_handle, 23.67f, 2);
zh_160x_clear_row(&lcd_handle, 1);
zh_160x_set_cursor(&lcd_handle, 1, 0);
zh_160x_print_char(&lcd_handle, "Row cleared");
zh_160x_off_cursor(&lcd_handle);
zh_160x_deinit(&lcd_handle);
zh_pcf8574_deinit(&pcf8574_handle);
i2c_del_master_bus(i2c_bus_handle);
}
```
---
### Progress Bar Example
```c
#include "zh_160x_i2c.h"
#define I2C_PORT (I2C_NUM_MAX - 1)
zh_pcf8574_handle_t *pcf8574_handle = NULL;
zh_160x_i2c_handle_t *lcd_handle = NULL;
void app_main(void)
{
esp_log_level_set("zh_160x_i2c", ESP_LOG_ERROR);
esp_log_level_set("zh_pcf8574", ESP_LOG_ERROR);
i2c_master_bus_config_t i2c_bus_config = {
.clk_source = I2C_CLK_SRC_DEFAULT,
.i2c_port = I2C_PORT,
.scl_io_num = GPIO_NUM_22,
.sda_io_num = GPIO_NUM_21,
.glitch_ignore_cnt = 7,
.flags.enable_internal_pullup = true,
};
i2c_master_bus_handle_t i2c_bus_handle = NULL;
esp_err_t ret = i2c_new_master_bus(&i2c_bus_config, &i2c_bus_handle);
if (ret != ESP_OK)
{
printf("I2C bus initialization error\n");
return;
}
zh_pcf8574_init_config_t pcf_config = ZH_PCF8574_INIT_CONFIG_DEFAULT();
pcf_config.i2c_handle = i2c_bus_handle;
pcf_config.i2c_address = 0x27;
ret = zh_pcf8574_init(&pcf_config, &pcf8574_handle);
if (ret != ESP_OK)
{
printf("PCF8574 initialization error\n");
i2c_del_master_bus(i2c_bus_handle);
return;
}
ret = zh_160x_init(&pcf8574_handle, &lcd_handle, ZH_LCD_16X2);
if (ret != ESP_OK)
{
printf("LCD initialization error\n");
zh_pcf8574_deinit(&pcf8574_handle);
i2c_del_master_bus(i2c_bus_handle);
return;
}
zh_160x_set_cursor(&lcd_handle, 0, 0);
zh_160x_print_char(&lcd_handle, "Progress: ");
zh_160x_print_progress_bar(&lcd_handle, 1, 75);
zh_160x_lcd_clear(&lcd_handle);
zh_160x_deinit(&lcd_handle);
zh_pcf8574_deinit(&pcf8574_handle);
i2c_del_master_bus(i2c_bus_handle);
}
```
+10
View File
@@ -0,0 +1,10 @@
description: ESP32 ESP-IDF component for LCD 1602A/1604A displays via I2C using PCF8574(A) expanders
url: https://github.com/aZholtikov/zh_160x_i2c
version: "5.0.1"
license: "Apache-2.0"
maintainers:
- "Alexey Zholtikov <alexey.zholtikov@yandex.ru>"
dependencies:
idf: ">=5.0"
zh_pcf8574:
git: "https://github.com/aZholtikov/zh_pcf8574.git"
+163 -54
View File
@@ -1,119 +1,228 @@
/**
* @file zh_160x_i2c.h
*
* @brief Driver for 16x2 and 16x4 LCD displays via I2C PCF8574 expander.
*
* Provides a high-level API for controlling HD44780-compatible LCD modules
* connected through a PCF8574 I/O expander. Supports text output, cursor
* control, and progress bar display.
*
* Key features:
* - 4-bit interface mode for efficient data transfer
* - Support for 16x2 and 16x4 LCD configurations
* - Text, integer, and floating-point number printing
* - Custom progress bar rendering
* - Cursor visibility and blink control
*
* @note All display operations require an initialized handle obtained through zh_160x_init().
* @note The text position counter of the HD44780 controller is shared by all rows,
* so row addresses are remapped internally.
*/
#pragma once
#include "zh_pcf8574.h"
#define ZH_LCD_16X2 1 /*!< LCD size 16x2. */
#define ZH_LCD_16X4 0 /*!< LCD size 16x4. */
#ifdef __cplusplus
extern "C"
{
#endif
/**
* @brief Initializes the LCD 160X.
* @brief Opaque handle for LCD 160x I2C display.
*
* @param[in] handle Pointer to unique PCF8574 handle.
* @param[in] size LCD size (ZH_LCD_16X2 or ZH_LCD_16X4).
*
* @return ESP_OK if success or an error code otherwise.
* Internal structure containing the PCF8574 expander handle and
* current LCD size configuration.
*/
esp_err_t zh_160x_init(zh_pcf8574_handle_t *handle, bool size);
typedef struct _zh_160x_i2c_handle_t zh_160x_i2c_handle_t;
/**
* @brief Clears the LCD screen.
*
* @param[in] handle Pointer to unique PCF8574 handle.
*
* @return ESP_OK if success or an error code otherwise.
* @brief Supported LCD display sizes.
*/
esp_err_t zh_160x_lcd_clear(zh_pcf8574_handle_t *handle);
typedef enum
{
ZH_LCD_16X2, /*!< LCD size 16x2 */
ZH_LCD_16X4, /*!< LCD size 16x4 */
ZH_LCD_NUM_MAX /*!< Number of supported LCD sizes */
} zh_160x_i2c_lcd_size_t;
/**
* @brief Initializes the LCD 160x display.
*
* Allocates and configures the LCD handle, then sends the initialization
* sequence to the LCD controller via the PCF8574 I2C expander.
* Configures 4-bit mode and sets default display parameters.
*
* @param[in] expander Pointer to the PCF8574 expander handle pointer (must not be NULL and must point to a non-NULL handle)
* @param[out] handle Pointer to the output LCD handle pointer (must not be NULL and must point to a NULL pointer)
* @param[in] size LCD size configuration (must be < ZH_LCD_NUM_MAX)
*
* @return ESP_OK on success
* @return ESP_ERR_INVALID_ARG if any parameter pointer is NULL or size is invalid
* @return ESP_ERR_INVALID_STATE if the output pointer already holds an initialized handle
* @return ESP_ERR_NO_MEM if memory allocation fails
* @return ESP_FAIL if LCD initialization sequence fails
*/
esp_err_t zh_160x_init(zh_pcf8574_handle_t **expander, zh_160x_i2c_handle_t **handle, zh_160x_i2c_lcd_size_t size);
/**
* @brief Deinitializes the LCD 160x display and frees the handle.
*
* Releases the allocated memory for the LCD handle and sets the output
* pointer to NULL to prevent use-after-free.
*
* @param[in,out] handle Pointer to the LCD handle (must not be NULL)
*
* @return ESP_OK on success
* @return ESP_ERR_INVALID_ARG if handle pointer is NULL or points to a NULL handle
*/
esp_err_t zh_160x_deinit(zh_160x_i2c_handle_t **handle);
/**
* @brief Clears the LCD screen and moves cursor to home position.
*
* Sends the LCD clear command (0x01) to erase all displayed text
* and reset the cursor to the upper-left corner (row 0, column 0).
*
* @param[in] handle Pointer to the LCD handle pointer (must not be NULL and must point to a non-NULL handle)
*
* @return ESP_OK on success
* @return ESP_ERR_INVALID_ARG if handle pointer is NULL or points to a NULL handle
* @return ESP_FAIL if sending the command fails
*/
esp_err_t zh_160x_lcd_clear(zh_160x_i2c_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.
* @param[in] col The column number (0–15).
* Moves the cursor to the specified row and column.
* For 16x2 displays: rows 0–1, columns 0–15.
* For 16x4 displays: rows 0–3, columns 0–15.
*
* @return ESP_OK if success or an error code otherwise.
* @param[in] handle Pointer to the LCD handle pointer (must not be NULL and must point to a non-NULL handle)
* @param[in] row Row number (must be < 2 for 16x2 and < 4 for 16x4)
* @param[in] col Column number (must be < 16)
*
* @return ESP_OK on success
* @return ESP_ERR_INVALID_ARG if any parameter pointer is NULL or coordinates are out of range
* @return ESP_FAIL if sending the command fails
*
* @note Row address mapping: row 0 = 0x00, row 1 = 0x40,
* row 2 = 0x10, row 3 = 0x50.
*/
esp_err_t zh_160x_set_cursor(zh_pcf8574_handle_t *handle, uint8_t row, uint8_t col);
esp_err_t zh_160x_set_cursor(zh_160x_i2c_handle_t **handle, uint8_t row, uint8_t col);
/**
* @brief Prints a string to the LCD.
* @brief Prints a string to the LCD at the current cursor position.
*
* @param[in] handle Pointer to unique PCF8574 handle.
* @param[in] str Pointer to the string to be displayed.
* Transmits each character of the null-terminated string to the LCD
* in 4-bit mode. The cursor advances automatically after each character.
*
* @return ESP_OK if success or an error code otherwise.
* @param[in] handle Pointer to the LCD handle pointer (must not be NULL and must point to a non-NULL handle)
* @param[in] str Null-terminated string to display (must not be NULL)
*
* @return ESP_OK on success
* @return ESP_ERR_INVALID_ARG if any parameter is NULL or handle points to a NULL handle
* @return ESP_FAIL if any character transmission fails
*/
esp_err_t zh_160x_print_char(zh_pcf8574_handle_t *handle, const char *str);
esp_err_t zh_160x_print_char(zh_160x_i2c_handle_t **handle, const char *str);
/**
* @brief Prints an integer to the LCD.
* @brief Prints an integer to the LCD at the current cursor position.
*
* @param[in] handle Pointer to unique PCF8574 handle.
* @param[in] num The integer to be displayed.
* Converts the integer to a decimal string and displays it.
* Supports the full range of signed 32-bit values
* (maximum 10 digits plus an optional sign).
*
* @return ESP_OK if success or an error code otherwise.
* @param[in] handle Pointer to the LCD handle pointer (must not be NULL and must point to a non-NULL handle)
* @param[in] num Integer value to display
*
* @return ESP_OK on success
* @return ESP_ERR_INVALID_ARG if handle pointer is NULL or points to a NULL handle
* @return ESP_FAIL if string conversion or printing fails
*/
esp_err_t zh_160x_print_int(zh_pcf8574_handle_t *handle, int num);
esp_err_t zh_160x_print_int(zh_160x_i2c_handle_t **handle, int num);
/**
* @brief Prints a floating-point number to the LCD.
* @brief Prints a floating-point number to the LCD at the current cursor position.
*
* @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.
* Converts the floating-point value to a decimal string with the
* specified number of decimal places and displays it. The converted
* text must fit into an internal 16-character buffer.
*
* @return ESP_OK if success or an error code otherwise.
* @param[in] handle Pointer to the LCD handle pointer (must not be NULL and must point to a non-NULL handle)
* @param[in] num Floating-point value to display
* @param[in] precision Number of decimal places passed to snprintf (0–6 recommended)
*
* @return ESP_OK on success
* @return ESP_ERR_INVALID_ARG if handle pointer is NULL or points to a NULL handle
* @return ESP_FAIL if string conversion or printing fails
*/
esp_err_t zh_160x_print_float(zh_pcf8574_handle_t *handle, float num, uint8_t precision);
esp_err_t zh_160x_print_float(zh_160x_i2c_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.
* @param[in] progress The progress percentage (0–100).
* Renders a 16-block progress bar where filled blocks represent the
* progress percentage and empty blocks show the remainder. Filled
* blocks are printed as byte 0xFF, which displays as a solid block
* only when DDRAM location 0xFF is configured as a custom character
* (CGRAM pattern 7 on HD44780 controllers).
* Leaves the cursor at the end of the row.
*
* @return ESP_OK if success or an error code otherwise.
* @param[in] handle Pointer to the LCD handle pointer (must not be NULL and must point to a non-NULL handle)
* @param[in] row Row number (must be < 2 for 16x2 and < 4 for 16x4)
* @param[in] progress Progress percentage (must be <= 100)
*
* @return ESP_OK on success
* @return ESP_ERR_INVALID_ARG if any parameter pointer is NULL, row is out of range, or progress exceeds 100
* @return ESP_FAIL if cursor positioning or character printing fails
*/
esp_err_t zh_160x_print_progress_bar(zh_pcf8574_handle_t *handle, uint8_t row, uint8_t progress);
esp_err_t zh_160x_print_progress_bar(zh_160x_i2c_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.
* Moves the cursor to the beginning of the specified row and fills
* all 16 positions with spaces to erase existing content.
* Leaves the cursor at the start of the cleared row.
*
* @return ESP_OK if success or an error code otherwise.
* @param[in] handle Pointer to the LCD handle pointer (must not be NULL and must point to a non-NULL handle)
* @param[in] row Row number (must be < 2 for 16x2 and < 4 for 16x4)
*
* @return ESP_OK on success
* @return ESP_ERR_INVALID_ARG if any parameter pointer is NULL or row is out of range
* @return ESP_FAIL if cursor positioning or character printing fails
*/
esp_err_t zh_160x_clear_row(zh_pcf8574_handle_t *handle, uint8_t row);
esp_err_t zh_160x_clear_row(zh_160x_i2c_handle_t **handle, uint8_t row);
/**
* @brief Enable the cursor.
* @brief Enables the cursor with optional blink mode.
*
* @param[in] handle Pointer to unique PCF8574 handle.
* @param[in] blink Blink mode.
* Turns on the cursor and optionally enables blinking.
* Command 0x0F enables cursor with blink, 0x0E enables cursor only.
*
* @return ESP_OK if success or an error code otherwise.
* @param[in] handle Pointer to the LCD handle pointer (must not be NULL and must point to a non-NULL handle)
* @param[in] blink Enable cursor blink mode
*
* @return ESP_OK on success
* @return ESP_ERR_INVALID_ARG if handle pointer is NULL or points to a NULL handle
* @return ESP_FAIL if sending the command fails
*/
esp_err_t zh_160x_on_cursor(zh_pcf8574_handle_t *handle, bool blink);
esp_err_t zh_160x_on_cursor(zh_160x_i2c_handle_t **handle, bool blink);
/**
* @brief Disable the cursor.
* @brief Hides the cursor and disables blinking.
*
* @param[in] handle Pointer to unique PCF8574 handle.
* Sends command 0x0C to turn off both cursor and cursor blink.
*
* @return ESP_OK if success or an error code otherwise.
* @param[in] handle Pointer to the LCD handle pointer (must not be NULL and must point to a non-NULL handle)
*
* @return ESP_OK on success
* @return ESP_ERR_INVALID_ARG if handle pointer is NULL or points to a NULL handle
* @return ESP_FAIL if sending the command fails
*/
esp_err_t zh_160x_off_cursor(zh_pcf8574_handle_t *handle);
esp_err_t zh_160x_off_cursor(zh_160x_i2c_handle_t **handle);
#ifdef __cplusplus
}
+1 -1
View File
@@ -1 +1 @@
4.1.0
5.0.1
+231
View File
@@ -0,0 +1,231 @@
# Описание компонента zh_160x_i2c
## Общие сведения
**zh_160x_i2c** — это драйвер для ESP-IDF, реализующий управление 16x2 и 16x4 LCD-дисплеями (HD44780-совместимыми) через I2C-расширитель PCF8574.
Компонент предоставляет высокоуровневый API для вывода текста, управления курсором, отображения индикатора прогресса и работы с числовыми данными на жидкокристаллических дисплеях.
## Основные возможности
1. **Поддержка дисплеев 16x2 и 16x4** — автоматическое определение конфигурации экрана.
2. **4-битный режим интерфейса** — эффективная передача данных через линии D4–D7 (пин P4–P7 PCF8574).
3. **Вывод строк, целых и вещественных чисел** — функции для форматированного вывода числовых данных.
4. **Отображение индикатора прогресса** — 16-блочный прогресс-бар с настраиваемым процентом.
5. **Управление курсором** — включение/выключение курсора и режима мерцания (blink).
6. **Построчная очистка** — очистка произвольной строки дисплея.
## Внутреннее устройство
### Структура `zh_160x_i2c_handle_t`
| Поле | Тип | Описание |
|------|-----|----------|
| `zh_pcf8574_handle` | `zh_pcf8574_handle_t *` | Указатель на управляющую структуру расширителя PCF8574 |
| `zh_160x_i2c_lcd_size` | `zh_160x_i2c_lcd_size_t` | Конфигурация размера LCD (16x2 или 16x4) |
Управляющая структура содержит ссылку на расширитель PCF8574 (который принадлежит вызывающей стороне) и текущую конфигурацию размера дисплея для расчёта адресов строк.
### Управление памятью
Память под структуру `zh_160x_i2c_handle_t` выделяется функцией `heap_caps_calloc()` с флагом `MALLOC_CAP_8BIT` при вызове `zh_160x_init()`. Освобождение происходит через `heap_caps_free()` в функции `zh_160x_deinit()`, после чего указатель устанавливается в `NULL` для предотвращения use-after-free.
### Адресация строк HD44780
HD44780-совместимые контроллеры используют нестандартную адресную карту строк:
| Физическая строка | Адрес DDRAM |
| ------------------- | ------------- |
| 0 | `0x00` |
| 1 | `0x40` |
| 2 | `0x10` |
| 3 | `0x50` |
Массив `_lcd_row_addresses` содержит эти адреса и используется функцией `zh_160x_set_cursor()` для корректного позиционирования курсора.
### Протокол инициализации LCD
Инициализация follows HD44780 initialization protocol:
1. Задержка 20 мс для стабилизации питания.
2. Три команды `0x30` с импульсами Enable — перевод в 4-битный режим (последовательность «warm-up»).
3. Команда `0x20` — установка 4-битного режима.
4. Команда `0x28` — функция set: 4-бит, 2 строки, 5x8 точки.
5. Команда `0x0C` — display on, cursor off, blink off.
6. Команда `0x01` — clear display.
7. Команда `0x06` — entry mode: cursor increment, no shift.
## API (интерфейс)
### Инициализация и освобождение
| Функция | Описание |
|---------|----------|
| `zh_160x_init()` | Инициализирует LCD-дисплей, выделяет и настраивает управляющую структуру, отправляет последовательность инициализации контроллеру LCD через расширитель PCF8574. |
| `zh_160x_deinit()` | Освобождает выделенную память управляющей структуры и устанавливает указатель в `NULL`. |
### Позиционирование и очистка
| Функция | Описание |
| --------- | ---------- |
| `zh_160x_lcd_clear()` | Очищает весь экран LCD и перемещает курсор в домашнюю позицию (строка 0, столбец 0). |
| `zh_160x_set_cursor()` | Устанавливает курсор в указанную позицию (строка и столбец). Для 16x2: строки 0–1, столбцы 0–15. Для 16x4: строки 0–3, столбцы 0–15. |
| `zh_160x_clear_row()` | Очищает указанную строку (заполняет 16 пробелами) и возвращает курсор в начало строки. |
### Вывод данных
| Функция | Описание |
| --------- | ---------- |
| `zh_160x_print_char()` | Выводит строку символов в текущую позицию курсора. Курсор автоматически перемещается после каждого символа. |
| `zh_160x_print_int()` | Преобразует целое число (signed 32-bit) в десятичную строку и выводит на дисплей. |
| `zh_160x_print_float()` | Преобразует число с плавающей точкой в строку с указанным количеством знаков после запятой и выводит на дисплей. |
### Прогресс-бар и курсор
| Функция | Описание |
| --------- | ---------- |
| `zh_160x_print_progress_bar()` | Отображает 16-блочный прогресс-бар на указанной строке. Заполненные блоки отображаются как символ с кодом `0xFF` (требует настройки CGRAM). |
| `zh_160x_on_cursor()` | Включает курсор с опциональным режимом мерцания (blink). |
| `zh_160x_off_cursor()` | Выключает курсор и режим мерцания. |
## Потокобезопасность
Компонент **не является потокобезопасным**. Все операции с LCD-дисплеем требуют внешних механизмов синхронизации (мьютексов, критических секций) при доступе из нескольких задач FreeRTOS. Управляющая структура `zh_160x_i2c_handle_t` не содержит внутренних примитивов синхронизации.
## Алгоритмы
### Отправка данных в 4-битном режиме
Команды и данные передаются в два этапа (старший и младший нибблы) через линии D4–D7:
1. **Отправка команды (`_zh_160x_send_command`)** — старший ниббл команды записывается на шины с маской `0x08` (RS=0, P3=1), затем генерируется импульс Enable. Затем младший ниббл (сдвинутый на 4 бита) записывается с той же маской и снова импульс Enable.
2. **Отправка данных (`_zh_160x_send_data`)** — аналогично, но с маской `0x09` (RS=1 для режима данных, P3=1).
3. **Импульс Enable (`_zh_160x_pulse`)** — установка пина P2 в HIGH, задержка 1 мс, установка в LOW, задержка 1 мс. Этот импульс Latch-ит данные в LCD-контроллер.
### Прогресс-бар
Процент прогресса конвертируется в количество заполненных блоков по формуле:
```
blocks = (progress * 16) / 100
```
Где `progress` — значение от 0 до 100. Заполненные блоки выводятся как `0xFF` (сплошной блок), пустые — как пробелы.
## Примеры использования
### Базовый вывод текста
```c
#include "zh_160x_i2c.h"
zh_pcf8574_handle_t *pcf8574_handle = NULL;
zh_160x_i2c_handle_t *lcd_160x_handle = NULL;
void app_main(void)
{
// Настройка I2C-шины и инициализация PCF8574...
zh_pcf8574_init_config_t config = ZH_PCF8574_INIT_CONFIG_DEFAULT();
config.i2c_handle = i2c_bus_handle;
config.i2c_address = 0x27;
zh_pcf8574_init(&config, &pcf8574_handle);
// Инициализация LCD 16x2
zh_160x_init(&pcf8574_handle, &lcd_160x_handle, ZH_LCD_16X2);
// Вывод текста на дисплей
zh_160x_set_cursor(&lcd_160x_handle, 0, 0);
zh_160x_print_char(&lcd_160x_handle, "Hello, LCD!");
zh_160x_set_cursor(&lcd_160x_handle, 1, 0);
zh_160x_print_char(&lcd_160x_handle, "ESP32 Ready");
// Очистка и освобождение ресурсов
vTaskDelay(5000 / portTICK_PERIOD_MS);
zh_160x_lcd_clear(&lcd_160x_handle);
zh_160x_deinit(&lcd_160x_handle);
}
```
### Вывод числовых данных
```c
// Вывод целого числа
int temperature = 25;
zh_160x_set_cursor(&lcd_160x_handle, 0, 0);
zh_160x_print_char(&lcd_160x_handle, "Temp: ");
zh_160x_print_int(&lcd_160x_handle, temperature);
zh_160x_print_char(&lcd_160x_handle, " C");
// Вывод числа с плавающей точкой (2 знака после запятой)
float voltage = 3.315f;
zh_160x_set_cursor(&lcd_160x_handle, 1, 0);
zh_160x_print_char(&lcd_160x_handle, "VCC: ");
zh_160x_print_float(&lcd_160x_handle, voltage, 2);
zh_160x_print_char(&lcd_160x_handle, " V");
```
### Отображение прогресс-бара
```c
// Прогресс-бар требует настройки CGRAM:
// DDRAM адрес 0xFF должен быть сопоставлен с пользовательским символом (CGRAM pattern 7)
for (uint8_t i = 0; i <= 100; i += 10)
{
zh_160x_set_cursor(&lcd_160x_handle, 0, 0);
zh_160x_print_char(&lcd_160x_handle, "Progress: ");
zh_160x_print_int(&lcd_160x_handle, i);
zh_160x_print_char(&lcd_160x_handle, "%");
zh_160x_print_progress_bar(&lcd_160x_handle, 1, i);
vTaskDelay(500 / portTICK_PERIOD_MS);
}
zh_160x_lcd_clear(&lcd_160x_handle);
zh_160x_deinit(&lcd_160x_handle);
```
## Логирование
Компонент использует ESP-IDF логирование с тегом `"zh_160x_i2c"`.
```c
esp_log_level_set("zh_160x_i2c", ESP_LOG_ERROR);
```
Для включения подробного логирования установите уровень `ESP_LOG_DEBUG`.
## Ошибки
| Код | Описание |
| ----- | ---------- |
| `ESP_OK` | Успешное выполнение |
| `ESP_ERR_INVALID_ARG` | Неверный аргумент (NULL-указатель, недопустимый размер дисплея, координаты вне диапазона) |
| `ESP_ERR_INVALID_STATE` | LCD уже инициализирован (повторная инициализация без деинициализации) |
| `ESP_ERR_NO_MEM` | Не удалось выделить память под управляющую структуру |
| `ESP_FAIL` | Ошибка записи/чтения через PCF8574 или сбой последовательности инициализации |
## Ограничения
1. **Зависимость от компонента `zh_pcf8574`** — требуется установка и инициализация драйвера PCF8574 перед использованием LCD.
2. **Буфер 16 символов** — функция `zh_160x_print_float()` использует внутренний буфер фиксированной длины; длинные числа могут быть усечены.
3. **Отсутствие потокобезопасности** — все операции с LCD требуют внешней синхронизации при работе из нескольких задач FreeRTOS.
4. **Прогресс-бар требует CGRAM** — для отображения заполненных блоков символ с кодом `0xFF` должен быть настроен как пользовательский символ в CGRAM (паттерн 7 HD44780).
5. **Один I2C-адрес** — компонент рассчитан на один LCD-модуль на шину; множественные дисплеи требуют дополнительных расширителей или мультиплексирования.
## Требования для сборки
- ESP-IDF v5.0 или новее
- Компонент `zh_pcf8574` (зависимость, устанавливается автоматически через idf_component_manager)
- FreeRTOS (входит в состав ESP-IDF)
- Настройка `CONFIG_FREERTOS_HZ=1000` в menuconfig
## Установка
```bash
cd ваш_проект/components
git clone https://github.com/aZholtikov/zh_160x_i2c
```
```c
#include "zh_160x_i2c.h"
```
+134 -65
View File
@@ -13,89 +13,163 @@ static const char *TAG = "zh_160x_i2c";
return err; \
}
static esp_err_t _zh_160x_lcd_init(zh_pcf8574_handle_t *handle);
static esp_err_t _zh_160x_send_command(zh_pcf8574_handle_t *handle, uint8_t command);
static esp_err_t _zh_160x_send_data(zh_pcf8574_handle_t *handle, uint8_t data);
static esp_err_t _zh_160x_pulse(zh_pcf8574_handle_t *handle);
/**
* @brief LCD 160x I2C handle structure.
*
* Internal handle containing the PCF8574 expander pointer and
* the configured LCD size for row address calculations.
*
* @warning The expander handle is owned by the caller and must remain valid
* until the LCD handle is deinitialized.
*/
struct _zh_160x_i2c_handle_t
{
zh_pcf8574_handle_t *zh_pcf8574_handle; /*!< PCF8574 expander handle */
zh_160x_i2c_lcd_size_t zh_160x_i2c_lcd_size; /*!< Configured LCD size (16x2 or 16x4) */
};
esp_err_t zh_160x_init(zh_pcf8574_handle_t *handle, bool size) // -V2008
static const uint8_t _lcd_row_addresses[] = {0x00, 0x40, 0x10, 0x50};
/**
* @brief Initialize the LCD 160x display.
*
* Sends initialization sequence to the LCD controller via PCF8574 expander.
* Configures 4-bit mode and sets default display parameters.
* Follows HD44780 initialization protocol with function set commands.
*
* @param handle Pointer to the 160x I2C handle
*
* @return ESP_OK on success
* @return ESP_FAIL if any expander write or pulse operation fails
*/
static esp_err_t _zh_160x_lcd_init(zh_160x_i2c_handle_t *handle);
/**
* @brief Send a command to the LCD.
*
* Transmits a command byte to the LCD in 4-bit mode. The high and low
* nibbles are sent sequentially on data lines D4-D7 (PCF8574 pins P4-P7),
* each followed by an enable pulse. Bit mask 0x08 keeps pin P3 high;
* RS (pin P0) and E (pin P2) are held low between pulses.
*
* @param handle Pointer to the 160x I2C handle
* @param command Command byte to send
*
* @return ESP_OK on success
* @return ESP_FAIL if any expander write or pulse operation fails
*/
static esp_err_t _zh_160x_send_command(zh_160x_i2c_handle_t *handle, uint8_t command);
/**
* @brief Send data to the LCD.
*
* Transmits a data byte to the LCD in 4-bit mode. The high and low
* nibbles are sent sequentially on data lines D4-D7 (PCF8574 pins P4-P7),
* each followed by an enable pulse. Bit mask 0x09 sets RS (pin P0) high
* for data mode and keeps pin P3 high; E (pin P2) is held low between pulses.
*
* @param handle Pointer to the 160x I2C handle
* @param data Data byte to send
*
* @return ESP_OK on success
* @return ESP_FAIL if any expander write or pulse operation fails
*/
static esp_err_t _zh_160x_send_data(zh_160x_i2c_handle_t *handle, uint8_t data);
/**
* @brief Generate an enable pulse for the LCD.
*
* Toggles the enable line (PCF8574 pin P2) to latch data into the LCD.
* Sets the line high, delays 1ms, then sets it low with another 1ms delay.
* This pulse signals the LCD to process the data on the D4-D7 pins.
*
* @param handle Pointer to the 160x I2C handle
*
* @return ESP_OK on success
* @return ESP_FAIL if any GPIO write operation fails
*/
static esp_err_t _zh_160x_pulse(zh_160x_i2c_handle_t *handle);
esp_err_t zh_160x_init(zh_pcf8574_handle_t **expander, zh_160x_i2c_handle_t **handle, zh_160x_i2c_lcd_size_t size)
{
ZH_LOGI("160X initialization started.");
ZH_ERROR_CHECK(handle != NULL, ESP_ERR_INVALID_ARG, NULL, "160X initialization failed. Invalid argument.");
ZH_ERROR_CHECK(handle->is_initialized == true, ESP_ERR_INVALID_STATE, NULL, "160X initialization failed. PCF8574 not initialized.");
handle->system = heap_caps_calloc(1, sizeof(uint8_t), MALLOC_CAP_8BIT);
ZH_ERROR_CHECK(handle->system != NULL, ESP_ERR_INVALID_ARG, NULL, "160X initialization failed. Memory allocation fail.");
*(uint8_t *)handle->system = size;
ZH_ERROR_CHECK(_zh_160x_lcd_init(handle) == ESP_OK, ESP_FAIL, NULL, "160X initialization failed. PCF8574 error.");
ZH_ERROR_CHECK(handle != NULL && expander != NULL && *expander != NULL && size < ZH_LCD_NUM_MAX, ESP_ERR_INVALID_ARG, NULL, "160X initialization failed. Invalid argument.");
ZH_ERROR_CHECK(*handle == NULL, ESP_ERR_INVALID_STATE, NULL, "160X initialization failed. 160X is already initialized.");
*handle = heap_caps_calloc(1, sizeof(zh_160x_i2c_handle_t), MALLOC_CAP_8BIT);
ZH_ERROR_CHECK(*handle != NULL, ESP_ERR_NO_MEM, NULL, "160X initialization failed. Failed to allocate 160X handle.");
(*handle)->zh_pcf8574_handle = *expander;
(*handle)->zh_160x_i2c_lcd_size = size;
ZH_ERROR_CHECK(_zh_160x_lcd_init(*handle) == ESP_OK, ESP_FAIL, heap_caps_free(*handle); *handle = NULL, "160X initialization failed. PCF8574 error.");
ZH_LOGI("160X initialization completed successfully.");
return ESP_OK;
}
esp_err_t zh_160x_lcd_clear(zh_pcf8574_handle_t *handle)
esp_err_t zh_160x_deinit(zh_160x_i2c_handle_t **handle)
{
ZH_LOGI("160X deinitialization started.");
ZH_ERROR_CHECK(handle != NULL && *handle != NULL, ESP_ERR_INVALID_ARG, NULL, "160X deinitialization failed. Invalid argument.");
heap_caps_free(*handle);
*handle = NULL;
ZH_LOGI("160X deinitialization completed successfully.");
return ESP_OK;
}
esp_err_t zh_160x_lcd_clear(zh_160x_i2c_handle_t **handle)
{
ZH_LOGI("160X display cleaning started.");
ZH_ERROR_CHECK(handle != NULL, ESP_ERR_INVALID_ARG, NULL, "160X display cleaning failed. Invalid argument.");
ZH_ERROR_CHECK(handle->is_initialized == true, ESP_ERR_INVALID_STATE, NULL, "160X display cleaning failed. PCF8574 not initialized.");
ZH_ERROR_CHECK(_zh_160x_send_command(handle, 0x01) == ESP_OK, ESP_FAIL, NULL, "160X display cleaning failed. PCF8574 error.");
ZH_ERROR_CHECK(handle != NULL && *handle != NULL, ESP_ERR_INVALID_ARG, NULL, "160X display cleaning failed. Invalid argument.");
ZH_ERROR_CHECK(_zh_160x_send_command(*handle, 0x01) == ESP_OK, ESP_FAIL, NULL, "160X display cleaning failed. PCF8574 error.");
ZH_LOGI("160X display cleaning completed successfully.");
return ESP_OK;
}
esp_err_t zh_160x_set_cursor(zh_pcf8574_handle_t *handle, uint8_t row, uint8_t col) // -V2008
esp_err_t zh_160x_set_cursor(zh_160x_i2c_handle_t **handle, uint8_t row, uint8_t col)
{
ZH_LOGI("160X set cursor started.");
ZH_ERROR_CHECK(handle != NULL && row < ((*(uint8_t *)handle->system == ZH_LCD_16X2) ? 2 : 4) && col < 16, ESP_ERR_INVALID_ARG, NULL, "160X set cursor failed. Invalid argument.");
ZH_ERROR_CHECK(handle->is_initialized == true, ESP_ERR_INVALID_STATE, NULL, "160X set cursor failed. PCF8574 not initialized.");
ZH_ERROR_CHECK(_zh_160x_send_command(handle, 0x80 | ((row == 0) ? col : (row == 1) ? (0x40 + col)
: (row == 2) ? (0x10 + col)
: (0x50 + col))) == ESP_OK,
ESP_FAIL, NULL, "160X set cursor failed. PCF8574 error.");
ZH_ERROR_CHECK(handle != NULL && *handle != NULL && row < (((*handle)->zh_160x_i2c_lcd_size == ZH_LCD_16X2) ? 2 : 4) && col < 16, ESP_ERR_INVALID_ARG, NULL, "160X set cursor failed. Invalid argument.");
ZH_ERROR_CHECK(_zh_160x_send_command(*handle, 0x80 | (_lcd_row_addresses[row] + col)) == ESP_OK, ESP_FAIL, NULL, "160X set cursor failed. PCF8574 error.");
ZH_LOGI("160X set cursor completed successfully.");
return ESP_OK;
}
esp_err_t zh_160x_print_char(zh_pcf8574_handle_t *handle, const char *str)
esp_err_t zh_160x_print_char(zh_160x_i2c_handle_t **handle, const char *str)
{
ZH_LOGI("160X print char started.");
ZH_ERROR_CHECK(str != NULL && handle != NULL, ESP_ERR_INVALID_ARG, NULL, "160X print char failed. Invalid argument.");
ZH_ERROR_CHECK(handle->is_initialized == true, ESP_ERR_INVALID_STATE, NULL, "160X print char failed. PCF8574 not initialized.");
ZH_ERROR_CHECK(str != NULL && handle != NULL && *handle != NULL, ESP_ERR_INVALID_ARG, NULL, "160X print char failed. Invalid argument.");
while (*str != 0)
{
ZH_ERROR_CHECK(_zh_160x_send_data(handle, (uint8_t)*str++) == ESP_OK, ESP_FAIL, NULL, "160X print char failed. PCF8574 error.");
ZH_ERROR_CHECK(_zh_160x_send_data(*handle, (uint8_t)*str++) == ESP_OK, ESP_FAIL, NULL, "160X print char failed. PCF8574 error.");
}
ZH_LOGI("160X print char completed successfully.");
return ESP_OK;
}
esp_err_t zh_160x_print_int(zh_pcf8574_handle_t *handle, int num)
esp_err_t zh_160x_print_int(zh_160x_i2c_handle_t **handle, int num)
{
ZH_LOGI("160X print int started.");
ZH_ERROR_CHECK(handle != NULL, ESP_ERR_INVALID_ARG, NULL, "160X print int failed. Invalid argument.");
ZH_ERROR_CHECK(handle->is_initialized == true, ESP_ERR_INVALID_STATE, NULL, "160X print int failed. PCF8574 not initialized.");
char buffer[12];
sprintf(buffer, "%d", num);
ZH_ERROR_CHECK(handle != NULL && *handle != NULL, ESP_ERR_INVALID_ARG, NULL, "160X print int failed. Invalid argument.");
char buffer[16];
snprintf(buffer, sizeof(buffer), "%d", num);
ZH_ERROR_CHECK(zh_160x_print_char(handle, buffer) == ESP_OK, ESP_FAIL, NULL, "160X print int failed. PCF8574 error.");
ZH_LOGI("160X print int completed successfully.");
return ESP_OK;
}
esp_err_t zh_160x_print_float(zh_pcf8574_handle_t *handle, float num, uint8_t precision)
esp_err_t zh_160x_print_float(zh_160x_i2c_handle_t **handle, float num, uint8_t precision)
{
ZH_LOGI("160X print float started.");
ZH_ERROR_CHECK(handle != NULL, ESP_ERR_INVALID_ARG, NULL, "160X print float failed. Invalid argument.");
ZH_ERROR_CHECK(handle->is_initialized == true, ESP_ERR_INVALID_STATE, NULL, "160X print float failed. PCF8574 not initialized.");
ZH_ERROR_CHECK(handle != NULL && *handle != NULL, ESP_ERR_INVALID_ARG, NULL, "160X print float failed. Invalid argument.");
char buffer[16];
sprintf(buffer, "%.*f", precision, num);
snprintf(buffer, sizeof(buffer), "%.*f", precision, num);
ZH_ERROR_CHECK(zh_160x_print_char(handle, buffer) == ESP_OK, ESP_FAIL, NULL, "160X print float failed. PCF8574 error.");
ZH_LOGI("160X print float completed successfully.");
return ESP_OK;
}
esp_err_t zh_160x_print_progress_bar(zh_pcf8574_handle_t *handle, uint8_t row, uint8_t progress) // -V2008
esp_err_t zh_160x_print_progress_bar(zh_160x_i2c_handle_t **handle, uint8_t row, uint8_t progress)
{
ZH_LOGI("160X print progress bar started.");
ZH_ERROR_CHECK(handle != NULL && row < ((*(uint8_t *)handle->system == ZH_LCD_16X2) ? 2 : 4) && progress <= 100, ESP_ERR_INVALID_ARG, NULL, "160X print progress bar failed. Invalid argument.");
ZH_ERROR_CHECK(handle->is_initialized == true, ESP_ERR_INVALID_STATE, NULL, "160X print progress bar failed. PCF8574 not initialized.");
ZH_ERROR_CHECK(handle != NULL && *handle != NULL && row < (((*handle)->zh_160x_i2c_lcd_size == ZH_LCD_16X2) ? 2 : 4) && progress <= 100, ESP_ERR_INVALID_ARG, NULL, "160X print progress bar failed. Invalid argument.");
uint8_t blocks = (progress * 16) / 100;
ZH_ERROR_CHECK(zh_160x_set_cursor(handle, row, 0) == ESP_OK, ESP_FAIL, NULL, "160X print progress bar failed. PCF8574 error.");
for (uint8_t i = 0; i < 16; ++i)
@@ -113,11 +187,10 @@ esp_err_t zh_160x_print_progress_bar(zh_pcf8574_handle_t *handle, uint8_t row, u
return ESP_OK;
}
esp_err_t zh_160x_clear_row(zh_pcf8574_handle_t *handle, uint8_t row) // -V2008
esp_err_t zh_160x_clear_row(zh_160x_i2c_handle_t **handle, uint8_t row)
{
ZH_LOGI("160X clear row started.");
ZH_ERROR_CHECK(handle != NULL && row < ((*(uint8_t *)handle->system == ZH_LCD_16X2) ? 2 : 4), ESP_ERR_INVALID_ARG, NULL, "160X clear row failed. Invalid argument.");
ZH_ERROR_CHECK(handle->is_initialized == true, ESP_ERR_INVALID_STATE, NULL, "160X clear row failed. PCF8574 not initialized.");
ZH_ERROR_CHECK(handle != NULL && *handle != NULL && row < (((*handle)->zh_160x_i2c_lcd_size == ZH_LCD_16X2) ? 2 : 4), ESP_ERR_INVALID_ARG, NULL, "160X clear row failed. Invalid argument.");
ZH_ERROR_CHECK(zh_160x_set_cursor(handle, row, 0) == ESP_OK, ESP_FAIL, NULL, "160X clear row failed. PCF8574 error.");
for (uint8_t i = 0; i < 16; ++i)
{
@@ -128,69 +201,65 @@ esp_err_t zh_160x_clear_row(zh_pcf8574_handle_t *handle, uint8_t row) // -V2008
return ESP_OK;
}
esp_err_t zh_160x_on_cursor(zh_pcf8574_handle_t *handle, bool blink)
esp_err_t zh_160x_on_cursor(zh_160x_i2c_handle_t **handle, bool blink)
{
ZH_LOGI("160X enable cursor started.");
ZH_ERROR_CHECK(handle != NULL, ESP_ERR_INVALID_ARG, NULL, "160X enable cursor failed. Invalid argument.");
ZH_ERROR_CHECK(handle->is_initialized == true, ESP_ERR_INVALID_STATE, NULL, "160X enable cursor failed. PCF8574 not initialized.");
ZH_ERROR_CHECK(_zh_160x_send_command(handle, (blink == true) ? 0x0f : 0x0e) == ESP_OK, ESP_FAIL, NULL, "160X enable cursor failed. PCF8574 error.");
ZH_ERROR_CHECK(handle != NULL && *handle != NULL, ESP_ERR_INVALID_ARG, NULL, "160X enable cursor failed. Invalid argument.");
ZH_ERROR_CHECK(_zh_160x_send_command(*handle, (blink == true) ? 0x0f : 0x0e) == ESP_OK, ESP_FAIL, NULL, "160X enable cursor failed. PCF8574 error.");
ZH_LOGI("160X enable cursor completed successfully.");
return ESP_OK;
}
esp_err_t zh_160x_off_cursor(zh_pcf8574_handle_t *handle)
esp_err_t zh_160x_off_cursor(zh_160x_i2c_handle_t **handle)
{
ZH_LOGI("160X disable cursor started.");
ZH_ERROR_CHECK(handle != NULL, ESP_ERR_INVALID_ARG, NULL, "160X disable cursor failed. Invalid argument.");
ZH_ERROR_CHECK(handle->is_initialized == true, ESP_ERR_INVALID_STATE, NULL, "160X disable cursor failed. PCF8574 not initialized.");
ZH_ERROR_CHECK(_zh_160x_send_command(handle, 0x0c) == ESP_OK, ESP_FAIL, NULL, "160X disable cursor failed. PCF8574 error.");
ZH_ERROR_CHECK(handle != NULL && *handle != NULL, ESP_ERR_INVALID_ARG, NULL, "160X disable cursor failed. Invalid argument.");
ZH_ERROR_CHECK(_zh_160x_send_command(*handle, 0x0c) == ESP_OK, ESP_FAIL, NULL, "160X disable cursor failed. PCF8574 error.");
ZH_LOGI("160X disable cursor completed successfully.");
return ESP_OK;
}
static esp_err_t _zh_160x_lcd_init(zh_pcf8574_handle_t *handle) // -V2008
static esp_err_t _zh_160x_lcd_init(zh_160x_i2c_handle_t *handle)
{
vTaskDelay(20 / portTICK_PERIOD_MS);
ZH_ERROR_CHECK(zh_pcf8574_write(handle, 0x30) == ESP_OK, ESP_FAIL, NULL, "PCF8574 error.");
ZH_ERROR_CHECK(zh_pcf8574_write(&handle->zh_pcf8574_handle, 0x30) == ESP_OK, ESP_FAIL, NULL, "PCF8574 error.");
ZH_ERROR_CHECK(_zh_160x_pulse(handle) == ESP_OK, ESP_FAIL, NULL, "PCF8574 error.");
ZH_ERROR_CHECK(zh_pcf8574_write(handle, 0x30) == ESP_OK, ESP_FAIL, NULL, "PCF8574 error.");
ZH_ERROR_CHECK(zh_pcf8574_write(&handle->zh_pcf8574_handle, 0x30) == ESP_OK, ESP_FAIL, NULL, "PCF8574 error.");
ZH_ERROR_CHECK(_zh_160x_pulse(handle) == ESP_OK, ESP_FAIL, NULL, "PCF8574 error.");
ZH_ERROR_CHECK(zh_pcf8574_write(handle, 0x30) == ESP_OK, ESP_FAIL, NULL, "PCF8574 error.");
ZH_ERROR_CHECK(zh_pcf8574_write(&handle->zh_pcf8574_handle, 0x30) == ESP_OK, ESP_FAIL, NULL, "PCF8574 error.");
ZH_ERROR_CHECK(_zh_160x_pulse(handle) == ESP_OK, ESP_FAIL, NULL, "PCF8574 error.");
ZH_ERROR_CHECK(zh_pcf8574_write(handle, 0x20) == ESP_OK, ESP_FAIL, NULL, "PCF8574 error.");
ZH_ERROR_CHECK(zh_pcf8574_write(&handle->zh_pcf8574_handle, 0x20) == ESP_OK, ESP_FAIL, NULL, "PCF8574 error.");
ZH_ERROR_CHECK(_zh_160x_pulse(handle) == ESP_OK, ESP_FAIL, NULL, "PCF8574 error.");
ZH_ERROR_CHECK(_zh_160x_send_command(handle, 0x28) == ESP_OK, ESP_FAIL, NULL, "PCF8574 error.");
ZH_ERROR_CHECK(_zh_160x_send_command(handle, 0x28) == ESP_OK, ESP_FAIL, NULL, "PCF8574 error.");
ZH_ERROR_CHECK(_zh_160x_send_command(handle, 0x28) == ESP_OK, ESP_FAIL, NULL, "PCF8574 error.");
ZH_ERROR_CHECK(_zh_160x_send_command(handle, 0x0C) == ESP_OK, ESP_FAIL, NULL, "PCF8574 error.");
ZH_ERROR_CHECK(_zh_160x_send_command(handle, 0x01) == ESP_OK, ESP_FAIL, NULL, "PCF8574 error.");
ZH_ERROR_CHECK(_zh_160x_send_command(handle, 0x06) == ESP_OK, ESP_FAIL, NULL, "PCF8574 error.");
return ESP_OK;
}
static esp_err_t _zh_160x_send_command(zh_pcf8574_handle_t *handle, uint8_t command)
static esp_err_t _zh_160x_send_command(zh_160x_i2c_handle_t *handle, uint8_t command)
{
ZH_ERROR_CHECK(zh_pcf8574_write(handle, (command & 0xF0) | 0x08) == ESP_OK, ESP_FAIL, NULL, "PCF8574 error.");
ZH_ERROR_CHECK(zh_pcf8574_write(&handle->zh_pcf8574_handle, (command & 0xF0) | 0x08) == ESP_OK, ESP_FAIL, NULL, "PCF8574 error.");
ZH_ERROR_CHECK(_zh_160x_pulse(handle) == ESP_OK, ESP_FAIL, NULL, "PCF8574 error.");
ZH_ERROR_CHECK(zh_pcf8574_write(handle, (command << 4) | 0x08) == ESP_OK, ESP_FAIL, NULL, "PCF8574 error.");
ZH_ERROR_CHECK(zh_pcf8574_write(&handle->zh_pcf8574_handle, (command << 4) | 0x08) == ESP_OK, ESP_FAIL, NULL, "PCF8574 error.");
ZH_ERROR_CHECK(_zh_160x_pulse(handle) == ESP_OK, ESP_FAIL, NULL, "PCF8574 error.");
return ESP_OK;
}
static esp_err_t _zh_160x_send_data(zh_pcf8574_handle_t *handle, uint8_t data)
static esp_err_t _zh_160x_send_data(zh_160x_i2c_handle_t *handle, uint8_t data)
{
ZH_ERROR_CHECK(zh_pcf8574_write(handle, (data & 0xF0) | 0x09) == ESP_OK, ESP_FAIL, NULL, "PCF8574 error.");
ZH_ERROR_CHECK(zh_pcf8574_write(&handle->zh_pcf8574_handle, (data & 0xF0) | 0x09) == ESP_OK, ESP_FAIL, NULL, "PCF8574 error.");
ZH_ERROR_CHECK(_zh_160x_pulse(handle) == ESP_OK, ESP_FAIL, NULL, "PCF8574 error.");
ZH_ERROR_CHECK(zh_pcf8574_write(handle, (data << 4) | 0x09) == ESP_OK, ESP_FAIL, NULL, "PCF8574 error.");
ZH_ERROR_CHECK(zh_pcf8574_write(&handle->zh_pcf8574_handle, (data << 4) | 0x09) == ESP_OK, ESP_FAIL, NULL, "PCF8574 error.");
ZH_ERROR_CHECK(_zh_160x_pulse(handle) == ESP_OK, ESP_FAIL, NULL, "PCF8574 error.");
return ESP_OK;
}
static esp_err_t _zh_160x_pulse(zh_pcf8574_handle_t *handle)
static esp_err_t _zh_160x_pulse(zh_160x_i2c_handle_t *handle)
{
ZH_ERROR_CHECK(zh_pcf8574_write_gpio(handle, ZH_PCF8574_GPIO_NUM_P2, ZH_PCF8574_GPIO_HIGH) == ESP_OK, ESP_FAIL, NULL, "PCF8574 error.");
ZH_ERROR_CHECK(zh_pcf8574_write_gpio(&handle->zh_pcf8574_handle, ZH_PCF8574_GPIO_NUM_P2, ZH_PCF8574_GPIO_HIGH) == ESP_OK, ESP_FAIL, NULL, "PCF8574 error.");
vTaskDelay(1 / portTICK_PERIOD_MS);
ZH_ERROR_CHECK(zh_pcf8574_write_gpio(handle, ZH_PCF8574_GPIO_NUM_P2, ZH_PCF8574_GPIO_LOW) == ESP_OK, ESP_FAIL, NULL, "PCF8574 error.");
ZH_ERROR_CHECK(zh_pcf8574_write_gpio(&handle->zh_pcf8574_handle, ZH_PCF8574_GPIO_NUM_P2, ZH_PCF8574_GPIO_LOW) == ESP_OK, ESP_FAIL, NULL, "PCF8574 error.");
vTaskDelay(1 / portTICK_PERIOD_MS);
return ESP_OK;
}