13 KiB
zh_160x_i2c - ESP32 ESP-IDF Component for LCD 1602A/1604A via I2C (PCF8574)
Table of Contents
- Overview
- Features
- Installation
- API Reference
- Usage Examples
- Technical Specifications
- Error Codes
- Connection
- Contributing
- License
Overview
zh_160x_i2c is a lightweight ESP-IDF component for controlling LCD 1602A and 1604A displays via I2C interface using PCF8574(A) I/O expanders. This component simplifies the process of displaying text, numbers, and progress bars on character LCD modules by providing a simple and intuitive API.
The component is designed specifically for ESP32 microcontrollers and uses ESP-IDF's I2C master driver and FreeRTOS for optimal performance in embedded systems.
Features
- Simple API: Easy-to-use functions for common LCD operations
- I2C Interface: Uses only 2 GPIO pins (SCL and SDA) via PCF8574 expander
- Support for 1602A and 1604A: Supports both 16x2 and 16x4 LCD modules
- Multiple LCD Support: Up to 8 LCD modules on one I2C bus (PCF8574) or 4 (PCF8574A)
- Text Display: Print strings, integers, and floating-point numbers
- Cursor Control: Enable/disable cursor with optional blink
- Progress Bar: Display progress bars on LCD
- Row Operations: Clear specific rows
- ESP-IDF Optimized: Uses ESP-IDF's I2C master driver and FreeRTOS
Installation
- Navigate to your project's components directory:
cd ../your_project/components
- Clone the repository:
git clone https://github.com/aZholtikov/zh_160x_i2c
- In your application, include the header:
#include "zh_160x_i2c.h"
- The component will be automatically built with your project.
Required menuconfig Settings
For correct operation, enable the following settings in menuconfig:
CONFIG_FREERTOS_HZ=1000
CONFIG_I2C_ISR_IRAM_SAFE
CONFIG_I2C_MASTER_ISR_HANDLER_IN_IRAM
API Reference
Configuration Constants
#define ZH_LCD_16X2 1 // LCD size 16x2
#define ZH_LCD_16X4 0 // LCD size 16x4
zh_160x_init()
Initializes the LCD 160X module.
Parameters:
handle- Pointer to unique PCF8574 handle (must be already initialized)size- LCD size (ZH_LCD_16X2 or ZH_LCD_16X4)
Returns:
ESP_OK- SuccessESP_ERR_INVALID_ARG- Invalid argument (NULL handle or handle not initialized)ESP_FAIL- Initialization failed
Example:
zh_pcf8574_handle_t lcd_handle = {0};
// Initialize PCF8574 first
zh_pcf8574_init(&config, &lcd_handle);
// Then initialize LCD
zh_160x_init(&lcd_handle, ZH_LCD_16X2);
zh_160x_lcd_clear()
Clears the entire LCD screen.
Parameters:
handle- Pointer to unique PCF8574 handle
Returns:
ESP_OK- SuccessESP_ERR_INVALID_ARG- Invalid argument (NULL handle)ESP_FAIL- Operation failed
zh_160x_set_cursor()
Sets the cursor to a specific position on the LCD.
Parameters:
handle- Pointer to unique PCF8574 handlerow- Row number (0-1 for 16x2, 0-3 for 16x4)col- Column number (0-15)
Returns:
ESP_OK- SuccessESP_ERR_INVALID_ARG- Invalid argument (NULL handle, invalid row/col)ESP_FAIL- Operation failed
zh_160x_print_char()
Prints a string to the LCD.
Parameters:
handle- Pointer to unique PCF8574 handlestr- Pointer to the null-terminated string to display
Returns:
ESP_OK- SuccessESP_ERR_INVALID_ARG- Invalid argument (NULL handle or str)ESP_FAIL- Operation failed
zh_160x_print_int()
Prints an integer to the LCD.
Parameters:
handle- Pointer to unique PCF8574 handlenum- Integer value to display
Returns:
ESP_OK- SuccessESP_ERR_INVALID_ARG- Invalid argument (NULL handle)ESP_FAIL- Operation failed
zh_160x_print_float()
Prints a floating-point number to the LCD.
Parameters:
handle- Pointer to unique PCF8574 handlenum- Floating-point value to displayprecision- Number of decimal places (0-9)
Returns:
ESP_OK- SuccessESP_ERR_INVALID_ARG- Invalid argument (NULL handle)ESP_FAIL- Operation failed
zh_160x_print_progress_bar()
Displays a progress bar on a specific row of the LCD.
Parameters:
handle- Pointer to unique PCF8574 handlerow- Row number (0-1 for 16x2, 0-3 for 16x4)progress- Progress percentage (0-100)
Returns:
ESP_OK- SuccessESP_ERR_INVALID_ARG- Invalid argument (NULL handle, invalid row or progress)ESP_FAIL- Operation failed
zh_160x_clear_row()
Clears a specific row on the LCD.
Parameters:
handle- Pointer to unique PCF8574 handlerow- Row number (0-1 for 16x2, 0-3 for 16x4)
Returns:
ESP_OK- SuccessESP_ERR_INVALID_ARG- Invalid argument (NULL handle, invalid row)ESP_FAIL- Operation failed
zh_160x_on_cursor()
Enables the cursor with optional blink mode.
Parameters:
handle- Pointer to unique PCF8574 handleblink- If true, cursor blinks; if false, cursor is steady
Returns:
ESP_OK- SuccessESP_ERR_INVALID_ARG- Invalid argument (NULL handle)ESP_FAIL- Operation failed
zh_160x_off_cursor()
Disables the cursor.
Parameters:
handle- Pointer to unique PCF8574 handle
Returns:
ESP_OK- SuccessESP_ERR_INVALID_ARG- Invalid argument (NULL handle)ESP_FAIL- Operation failed
Usage Examples
Basic Example: Single LCD Display
#include "zh_160x_i2c.h"
#define I2C_PORT (I2C_NUM_MAX - 1)
zh_pcf8574_handle_t lcd_160x_handle = {0};
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,
.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;
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);
// Display text
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!");
}
Example: Progress Bar
#include "zh_160x_i2c.h"
void app_main(void)
{
zh_pcf8574_handle_t lcd_handle = {0};
// ... initialization code ...
for (uint8_t i = 0; i <= 100; ++i)
{
zh_160x_set_cursor(&lcd_handle, 0, 10);
zh_160x_print_int(&lcd_handle, i);
zh_160x_print_char(&lcd_handle, "%");
zh_160x_print_progress_bar(&lcd_handle, 1, i);
vTaskDelay(100 / portTICK_PERIOD_MS);
}
}
Example: Displaying Various Data Types
#include "zh_160x_i2c.h"
void app_main(void)
{
zh_pcf8574_handle_t lcd_handle = {0};
// ... initialization code ...
// Display string
zh_160x_set_cursor(&lcd_handle, 0, 0);
zh_160x_print_char(&lcd_handle, "String: ");
zh_160x_print_char(&lcd_handle, "Hello");
// Display integer
zh_160x_set_cursor(&lcd_handle, 1, 0);
zh_160x_print_int(&lcd_handle, 42);
// Display floating-point number
zh_160x_set_cursor(&lcd_handle, 0, 0);
zh_160x_print_float(&lcd_handle, 3.14159, 2); // 3.14
// Enable cursor
zh_160x_on_cursor(&lcd_handle, true); // Blinking cursor
vTaskDelay(2000 / portTICK_PERIOD_MS);
// Disable cursor
zh_160x_off_cursor(&lcd_handle);
}
Example: Multiple LCDs on One Bus
#include "zh_160x_i2c.h"
#define I2C_PORT (I2C_NUM_MAX - 1)
zh_pcf8574_handle_t lcd1_handle = {0};
zh_pcf8574_handle_t lcd2_handle = {0};
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,
.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;
i2c_new_master_bus(&i2c_bus_config, &i2c_bus_handle);
// Initialize first LCD at address 0x27
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, &lcd1_handle);
zh_160x_init(&lcd1_handle, ZH_LCD_16X2);
// Initialize second LCD at address 0x26
config.i2c_address = 0x26;
zh_pcf8574_init(&config, &lcd2_handle);
zh_160x_init(&lcd2_handle, ZH_LCD_16X2);
// Display on first LCD
zh_160x_set_cursor(&lcd1_handle, 0, 0);
zh_160x_print_char(&lcd1_handle, "LCD 1");
// Display on second LCD
zh_160x_set_cursor(&lcd2_handle, 0, 0);
zh_160x_print_char(&lcd2_handle, "LCD 2");
}
Technical Specifications
| Parameter | Value |
|---|---|
| LCD Types | 1602A (16x2), 1604A (16x4) |
| Interface | I2C via PCF8574(A) |
| I2C Address Range | 0x20-0x27 (PCF8574), 0x38-0x3F (PCF8574A) |
| Max LCDs per Bus | 16 |
| GPIO Usage | 2 (SCL, SDA) |
| ESP-IDF Version | >= 5.0 |
| Platform | ESP32 family |
| Language | C (C99) |
Error Codes
| Error Code | Description |
|---|---|
ESP_OK |
Operation completed successfully |
ESP_ERR_INVALID_ARG |
Invalid argument (NULL pointer or invalid value) |
ESP_ERR_INVALID_STATE |
Handle not initialized |
ESP_FAIL |
General failure (I2C communication error) |
Connection
1602A/1604A to PCF8574 Connection
| LCD Pin | PCF8574 Pin | Description |
|---|---|---|
| RS | P0 | Register select |
| E | P2 | Enable |
| D4 | P4 | Data bit 4 |
| D5 | P5 | Data bit 5 |
| D6 | P6 | Data bit 6 |
| D7 | P7 | Data bit 7 |
I2C Connection
| LCD Module | PCF8574 | ESP32 |
|---|---|---|
| VCC | VCC | 3.3V |
| GND | GND | GND |
| SDA | SDA | GPIO21 |
| SCL | SCL | GPIO22 |
| RW | GND | GND |
Note: RW pin must be connected to GND for write-only operation.
Contributing
Contributions are welcome! To contribute:
- Fork the repository
- Create a feature branch (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
Please ensure your code follows the existing style and includes appropriate documentation.
License
This project is licensed under the Apache License, Version 2.0 - see the LICENSE file for details.
Apache License, Version 2.0
Copyright (c) 2026 Alexey Zholtikov
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at:
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
Additional Notes
- I2C Pull-up Resistors: Use 4.7kΩ resistors on SDA and SCL lines for reliable communication
- Power Supply: Ensure stable 5V power for LCD module (PCF8574 is 5V tolerant)
- Contrast Adjustment: Use the built-in potentiometer to adjust LCD contrast for optimal visibility
- Backlight: LCD backlight is typically connected to VCC through a current-limiting resistor
- Performance: Operations are blocking and use FreeRTOS delays for proper timing
- I2C_ISR_IRAM_SAFE: For proper operation, enable
I2C_ISR_IRAM_SAFEandI2C_MASTER_ISR_HANDLER_IN_IRAMin menuconfig - FreeRTOS: For correct operation, configure
CONFIG_FREERTOS_HZ=1000in menuconfig - Best Practices:
- Always initialize PCF8574 before initializing LCD
- Check I2C address using a scanner if LCD does not respond
- Consider I2C bus speed limitations when using multiple devices