266 lines
13 KiB
C
266 lines
13 KiB
C
#include "zh_160x_i2c.h"
|
|
|
|
static const char *TAG = "zh_160x_i2c";
|
|
|
|
#define ZH_LOGI(msg, ...) ESP_LOGI(TAG, msg, ##__VA_ARGS__)
|
|
#define ZH_LOGE(msg, err, ...) ESP_LOGE(TAG, "[%s:%d:%s] " msg, __FILE__, __LINE__, esp_err_to_name(err), ##__VA_ARGS__)
|
|
|
|
#define ZH_ERROR_CHECK(cond, err, cleanup, msg, ...) \
|
|
if (!(cond)) \
|
|
{ \
|
|
ZH_LOGE(msg, err, ##__VA_ARGS__); \
|
|
cleanup; \
|
|
return err; \
|
|
}
|
|
|
|
/**
|
|
* @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) */
|
|
};
|
|
|
|
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 && 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_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 && *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_160x_i2c_handle_t **handle, uint8_t row, uint8_t col)
|
|
{
|
|
ZH_LOGI("160X set cursor started.");
|
|
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_160x_i2c_handle_t **handle, const char *str)
|
|
{
|
|
ZH_LOGI("160X print char started.");
|
|
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_LOGI("160X print char completed successfully.");
|
|
return ESP_OK;
|
|
}
|
|
|
|
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 && *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_160x_i2c_handle_t **handle, float num, uint8_t precision)
|
|
{
|
|
ZH_LOGI("160X print float started.");
|
|
ZH_ERROR_CHECK(handle != NULL && *handle != NULL, ESP_ERR_INVALID_ARG, NULL, "160X print float failed. Invalid argument.");
|
|
char buffer[16];
|
|
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_160x_i2c_handle_t **handle, uint8_t row, uint8_t progress)
|
|
{
|
|
ZH_LOGI("160X print progress bar started.");
|
|
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)
|
|
{
|
|
if (i < blocks)
|
|
{
|
|
ZH_ERROR_CHECK(zh_160x_print_char(handle, "\xFF") == ESP_OK, ESP_FAIL, NULL, "160X print progress bar failed. PCF8574 error.");
|
|
}
|
|
else
|
|
{
|
|
ZH_ERROR_CHECK(zh_160x_print_char(handle, " ") == ESP_OK, ESP_FAIL, NULL, "160X print progress bar failed. PCF8574 error.");
|
|
}
|
|
}
|
|
ZH_LOGI("160X print progress bar completed successfully.");
|
|
return ESP_OK;
|
|
}
|
|
|
|
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 && *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)
|
|
{
|
|
ZH_ERROR_CHECK(zh_160x_print_char(handle, " ") == ESP_OK, ESP_FAIL, NULL, "160X clear row failed. PCF8574 error.");
|
|
}
|
|
ZH_ERROR_CHECK(zh_160x_set_cursor(handle, row, 0) == ESP_OK, ESP_FAIL, NULL, "160X clear row failed. PCF8574 error.");
|
|
ZH_LOGI("160X clear row completed successfully.");
|
|
return ESP_OK;
|
|
}
|
|
|
|
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 && *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_160x_i2c_handle_t **handle)
|
|
{
|
|
ZH_LOGI("160X disable cursor started.");
|
|
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_160x_i2c_handle_t *handle)
|
|
{
|
|
vTaskDelay(20 / portTICK_PERIOD_MS);
|
|
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->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->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->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, 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_160x_i2c_handle_t *handle, uint8_t command)
|
|
{
|
|
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->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_160x_i2c_handle_t *handle, uint8_t data)
|
|
{
|
|
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->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_160x_i2c_handle_t *handle)
|
|
{
|
|
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_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;
|
|
}
|