From 824247de0852f3fdb9d5c3ff184de8313be7416f Mon Sep 17 00:00:00 2001 From: Arthur Tavares Date: Sun, 26 Mar 2023 15:33:32 +0200 Subject: [PATCH 1/5] change portTICK_RATE_MS to portTICK_PERIOD_MS --- main/app_main.c | 79 +++++++++++++++++++++++++------------------------ 1 file changed, 40 insertions(+), 39 deletions(-) diff --git a/main/app_main.c b/main/app_main.c index 3ad1b36..e9ed6cb 100644 --- a/main/app_main.c +++ b/main/app_main.c @@ -43,25 +43,25 @@ #define TAG "app" // LCD1602 -#define LCD_NUM_ROWS 2 -#define LCD_NUM_COLUMNS 32 -#define LCD_NUM_VISIBLE_COLUMNS 16 +#define LCD_NUM_ROWS 2 +#define LCD_NUM_COLUMNS 32 +#define LCD_NUM_VISIBLE_COLUMNS 16 // LCD2004 -//#define LCD_NUM_ROWS 4 -//#define LCD_NUM_COLUMNS 40 -//#define LCD_NUM_VISIBLE_COLUMNS 20 +// #define LCD_NUM_ROWS 4 +// #define LCD_NUM_COLUMNS 40 +// #define LCD_NUM_VISIBLE_COLUMNS 20 // Undefine USE_STDIN if no stdin is available (e.g. no USB UART) - a fixed delay will occur instead of a wait for a keypress. -#define USE_STDIN 1 -//#undef USE_STDIN +#define USE_STDIN 1 +// #undef USE_STDIN -#define I2C_MASTER_NUM I2C_NUM_0 -#define I2C_MASTER_TX_BUF_LEN 0 // disabled -#define I2C_MASTER_RX_BUF_LEN 0 // disabled -#define I2C_MASTER_FREQ_HZ 100000 -#define I2C_MASTER_SDA_IO CONFIG_I2C_MASTER_SDA -#define I2C_MASTER_SCL_IO CONFIG_I2C_MASTER_SCL +#define I2C_MASTER_NUM I2C_NUM_0 +#define I2C_MASTER_TX_BUF_LEN 0 // disabled +#define I2C_MASTER_RX_BUF_LEN 0 // disabled +#define I2C_MASTER_FREQ_HZ 100000 +#define I2C_MASTER_SDA_IO CONFIG_I2C_MASTER_SDA +#define I2C_MASTER_SCL_IO CONFIG_I2C_MASTER_SCL static void i2c_master_init(void) { @@ -69,9 +69,9 @@ static void i2c_master_init(void) i2c_config_t conf; conf.mode = I2C_MODE_MASTER; conf.sda_io_num = I2C_MASTER_SDA_IO; - conf.sda_pullup_en = GPIO_PULLUP_DISABLE; // GY-2561 provides 10kΩ pullups + conf.sda_pullup_en = GPIO_PULLUP_DISABLE; // GY-2561 provides 10kΩ pullups conf.scl_io_num = I2C_MASTER_SCL_IO; - conf.scl_pullup_en = GPIO_PULLUP_DISABLE; // GY-2561 provides 10kΩ pullups + conf.scl_pullup_en = GPIO_PULLUP_DISABLE; // GY-2561 provides 10kΩ pullups conf.master.clk_speed = I2C_MASTER_FREQ_HZ; i2c_param_config(i2c_master_port, &conf); i2c_driver_install(i2c_master_port, conf.mode, @@ -88,11 +88,12 @@ static uint8_t _wait_for_user(void) #ifdef USE_STDIN while (!c) { - STATUS s = uart_rx_one_char(&c); - if (s == OK) { - printf("%c", c); - } - vTaskDelay(1); + STATUS s = uart_rx_one_char(&c); + if (s == OK) + { + printf("%c", c); + } + vTaskDelay(1); } #else vTaskDelay(1000 / portTICK_RATE_MS); @@ -100,7 +101,7 @@ static uint8_t _wait_for_user(void) return c; } -void lcd1602_task(void * pvParameter) +void lcd1602_task(void *pvParameter) { // Set up I2C i2c_master_init(); @@ -108,12 +109,12 @@ void lcd1602_task(void * pvParameter) uint8_t address = CONFIG_LCD1602_I2C_ADDRESS; // Set up the SMBus - smbus_info_t * smbus_info = smbus_malloc(); + smbus_info_t *smbus_info = smbus_malloc(); ESP_ERROR_CHECK(smbus_init(smbus_info, i2c_num, address)); - ESP_ERROR_CHECK(smbus_set_timeout(smbus_info, 1000 / portTICK_RATE_MS)); + ESP_ERROR_CHECK(smbus_set_timeout(smbus_info, 1000 / portTICK_PERIOD_MS)); // Set up the LCD1602 device with backlight off - i2c_lcd1602_info_t * lcd_info = i2c_lcd1602_malloc(); + i2c_lcd1602_info_t *lcd_info = i2c_lcd1602_malloc(); ESP_ERROR_CHECK(i2c_lcd1602_init(lcd_info, smbus_info, true, LCD_NUM_ROWS, LCD_NUM_COLUMNS, LCD_NUM_VISIBLE_COLUMNS)); @@ -148,7 +149,7 @@ void lcd1602_task(void * pvParameter) i2c_lcd1602_move_cursor(lcd_info, 15, 1); i2c_lcd1602_write_char(lcd_info, 'C'); - ESP_LOGI(TAG, "move to 0,1 and blink"); // cursor should still be on + ESP_LOGI(TAG, "move to 0,1 and blink"); // cursor should still be on _wait_for_user(); i2c_lcd1602_move_cursor(lcd_info, 0, 1); i2c_lcd1602_set_blink(lcd_info, true); @@ -175,13 +176,13 @@ void lcd1602_task(void * pvParameter) ESP_LOGI(TAG, "disable blink"); _wait_for_user(); - i2c_lcd1602_set_blink(lcd_info, false); // cursor should still be on + i2c_lcd1602_set_blink(lcd_info, false); // cursor should still be on ESP_LOGI(TAG, "disable cursor"); _wait_for_user(); i2c_lcd1602_set_cursor(lcd_info, false); - ESP_LOGI(TAG, "display alphabet from 0,0"); // should overflow to second line at "ABC..." + ESP_LOGI(TAG, "display alphabet from 0,0"); // should overflow to second line at "ABC..." _wait_for_user(); i2c_lcd1602_home(lcd_info); i2c_lcd1602_write_string(lcd_info, "abcdefghijklmnopqrstuvwxyz0123456789.,-+ABCDEFGHIJKLMNOPQRSTUVWXYZ"); @@ -191,7 +192,7 @@ void lcd1602_task(void * pvParameter) for (int i = 0; i < 8; ++i) { i2c_lcd1602_scroll_display_left(lcd_info); - vTaskDelay(200 / portTICK_RATE_MS); + vTaskDelay(200 / portTICK_PERIOD_MS); } ESP_LOGI(TAG, "scroll display right 8 places quickly"); @@ -226,7 +227,7 @@ void lcd1602_task(void * pvParameter) for (int i = 0; i < 5; ++i) { i2c_lcd1602_write_char(lcd_info, '>'); - vTaskDelay(200 / portTICK_RATE_MS); + vTaskDelay(200 / portTICK_PERIOD_MS); } ESP_LOGI(TAG, "change address counter to decrement (right to left) and display <<<<<"); @@ -235,7 +236,7 @@ void lcd1602_task(void * pvParameter) for (int i = 0; i < 5; ++i) { i2c_lcd1602_write_char(lcd_info, '<'); - vTaskDelay(200 / portTICK_RATE_MS); + vTaskDelay(200 / portTICK_PERIOD_MS); } ESP_LOGI(TAG, "disable auto-scroll and display +++++"); @@ -244,7 +245,7 @@ void lcd1602_task(void * pvParameter) for (int i = 0; i < 5; ++i) { i2c_lcd1602_write_char(lcd_info, '+'); - vTaskDelay(200 / portTICK_RATE_MS); + vTaskDelay(200 / portTICK_PERIOD_MS); } ESP_LOGI(TAG, "set left_to_right and display >>>>>"); @@ -253,7 +254,7 @@ void lcd1602_task(void * pvParameter) for (int i = 0; i < 5; ++i) { i2c_lcd1602_write_char(lcd_info, '>'); - vTaskDelay(200 / portTICK_RATE_MS); + vTaskDelay(200 / portTICK_PERIOD_MS); } ESP_LOGI(TAG, "clear display and disable cursor"); @@ -264,14 +265,14 @@ void lcd1602_task(void * pvParameter) ESP_LOGI(TAG, "create and display custom characters"); _wait_for_user(); // https://github.com/agnunez/ESP8266-I2C-LCD1602/blob/master/examples/CustomChars/CustomChars.ino - uint8_t bell[8] = {0x4, 0xe, 0xe, 0xe, 0x1f, 0x0, 0x4}; - uint8_t note[8] = {0x2, 0x3, 0x2, 0xe, 0x1e, 0xc, 0x0}; + uint8_t bell[8] = {0x4, 0xe, 0xe, 0xe, 0x1f, 0x0, 0x4}; + uint8_t note[8] = {0x2, 0x3, 0x2, 0xe, 0x1e, 0xc, 0x0}; uint8_t clock[8] = {0x0, 0xe, 0x15, 0x17, 0x11, 0xe, 0x0}; uint8_t heart[8] = {0x0, 0xa, 0x1f, 0x1f, 0xe, 0x4, 0x0}; - uint8_t duck[8] = {0x0, 0xc, 0x1d, 0xf, 0xf, 0x6, 0x0}; - uint8_t check[8] = {0x0, 0x1 ,0x3, 0x16, 0x1c, 0x8, 0x0}; + uint8_t duck[8] = {0x0, 0xc, 0x1d, 0xf, 0xf, 0x6, 0x0}; + uint8_t check[8] = {0x0, 0x1, 0x3, 0x16, 0x1c, 0x8, 0x0}; uint8_t cross[8] = {0x0, 0x1b, 0xe, 0x4, 0xe, 0x1b, 0x0}; - uint8_t retarrow[8] = { 0x1, 0x1, 0x5, 0x9, 0x1f, 0x8, 0x4}; + uint8_t retarrow[8] = {0x1, 0x1, 0x5, 0x9, 0x1f, 0x8, 0x4}; i2c_lcd1602_define_char(lcd_info, I2C_LCD1602_INDEX_CUSTOM_0, bell); i2c_lcd1602_define_char(lcd_info, I2C_LCD1602_INDEX_CUSTOM_1, note); i2c_lcd1602_define_char(lcd_info, I2C_LCD1602_INDEX_CUSTOM_2, clock); @@ -320,7 +321,7 @@ void lcd1602_task(void * pvParameter) while (1) { i2c_lcd1602_write_char(lcd_info, c); - vTaskDelay(100 / portTICK_RATE_MS); + vTaskDelay(100 / portTICK_PERIOD_MS); ESP_LOGD(TAG, "col %d, row %d, char 0x%02x", col, row, c); ++c; ++col; From f7d453a3c389ab9d58bcfccf7fe28fc60c7722ad Mon Sep 17 00:00:00 2001 From: Arthur Tavares Date: Sun, 26 Mar 2023 15:47:14 +0200 Subject: [PATCH 2/5] add components manually --- .../esp32-i2c-lcd1602/i2c-lcd1602.c | 624 ++++++++++++++++++ .../esp32-i2c-lcd1602/i2c-lcd1602.h | 336 ++++++++++ main/components/esp32-smbus/smbus.c | 401 +++++++++++ main/components/esp32-smbus/smbus.h | 210 ++++++ 4 files changed, 1571 insertions(+) create mode 100644 main/components/esp32-i2c-lcd1602/i2c-lcd1602.c create mode 100644 main/components/esp32-i2c-lcd1602/i2c-lcd1602.h create mode 100644 main/components/esp32-smbus/smbus.c create mode 100644 main/components/esp32-smbus/smbus.h diff --git a/main/components/esp32-i2c-lcd1602/i2c-lcd1602.c b/main/components/esp32-i2c-lcd1602/i2c-lcd1602.c new file mode 100644 index 0000000..317ac7d --- /dev/null +++ b/main/components/esp32-i2c-lcd1602/i2c-lcd1602.c @@ -0,0 +1,624 @@ +/* + * MIT License + * + * Copyright (c) 2018 David Antliff + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * @file + * + * @brief + * The LCD1602 controller is an HD44780-compatible controller that normally operates + * via an 8-bit or 4-bit wide parallel bus. + * + * https://www.sparkfun.com/datasheets/LCD/HD44780.pdf + * + * The LCD1602 controller is connected to a PCF8574A I/O expander via the I2C bus. + * Only the top four bits are connected to the controller's data lines. The lower + * four bits are used as control lines: + * + * - B7: data bit 3 + * - B6: data bit 2 + * - B5: data bit 1 + * - B4: data bit 0 + * - B3: backlight (BL): off = 0, on = 1 + * - B2: enable (EN): change from 1 to 0 to clock data into controller + * - B1: read/write (RW): write = 0, read = 1 + * - B0: register select (RS): command = 0, data = 1 + * + * Therefore to send a command byte requires the following operations: + * + * // First nibble: + * val = command & 0xf0 // extract top nibble + * val |= 0x04 // RS = 0 (command), RW = 0 (write), EN = 1 + * i2c_write_byte(i2c_address, val) + * sleep(2ms) + * val &= 0xfb // EN = 0 + * i2c_write_byte(i2c_address, val) + * + * // Second nibble: + * val = command & 0x0f // extract bottom nibble + * val |= 0x04 // RS = 0 (command), RW = 0 (write), EN = 1 + * i2c_write_byte(i2c_address, val) + * sleep(2ms) + * val &= 0xfb // EN = 0 + * i2c_write_byte(i2c_address, val) + * + * Sending a data byte is very similar except that RS = 1 (data) + * + * When the controller powers up, it defaults to: + * + * - display cleared + * - 8-bit interface, 1 line display, 5x8 dots per character + * - increment by 1 set + * - no shift + * + * The controller must be set to 4-bit operation before proper communication can commence. + * The initialisation sequence for 4-bit operation is: + * + * 0. wait > 15ms after Vcc rises to 4.5V, or > 40ms after Vcc rises to 2.7V + * 1. send nibble 0x03 // select 8-bit interface + * 2. wait > 4.1ms + * 3. send nibble 0x03 // select 8-bit interface again + * 4. wait > 100us + * 5. send command 0x32 // select 4-bit interface + * 6. send command 0x28 // set 2 lines and 5x7(8?) dots per character + * 7. send command 0x0c // display on, cursor off + * 8. send command 0x06 // move cursor right when writing, no scroll + * 9. send command 0x80 // set cursor to home position (row 1, column 1) + */ + +#include +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_system.h" +#include "esp_log.h" + +#include "i2c-lcd1602.h" + +#define TAG "i2c-lcd1602" + +// Delays (microseconds) +#define DELAY_POWER_ON 50000 // wait at least 40us after VCC rises to 2.7V +#define DELAY_INIT_1 4500 // wait at least 4.1ms (fig 24, page 46) +#define DELAY_INIT_2 4500 // wait at least 4.1ms (fig 24, page 46) +#define DELAY_INIT_3 120 // wait at least 100us (fig 24, page 46) + +#define DELAY_CLEAR_DISPLAY 2000 +#define DELAY_RETURN_HOME 2000 + +#define DELAY_ENABLE_PULSE_WIDTH 1 // enable pulse must be at least 450ns wide +#define DELAY_ENABLE_PULSE_SETTLE 50 // command requires > 37us to settle (table 6 in datasheet) + +// Commands +#define COMMAND_CLEAR_DISPLAY 0x01 +#define COMMAND_RETURN_HOME 0x02 +#define COMMAND_ENTRY_MODE_SET 0x04 +#define COMMAND_DISPLAY_CONTROL 0x08 +#define COMMAND_SHIFT 0x10 +#define COMMAND_FUNCTION_SET 0x20 +#define COMMAND_SET_CGRAM_ADDR 0x40 +#define COMMAND_SET_DDRAM_ADDR 0x80 + +// COMMAND_ENTRY_MODE_SET flags +#define FLAG_ENTRY_MODE_SET_ENTRY_INCREMENT 0x02 +#define FLAG_ENTRY_MODE_SET_ENTRY_DECREMENT 0x00 +#define FLAG_ENTRY_MODE_SET_ENTRY_SHIFT_ON 0x01 +#define FLAG_ENTRY_MODE_SET_ENTRY_SHIFT_OFF 0x00 + +// COMMAND_DISPLAY_CONTROL flags +#define FLAG_DISPLAY_CONTROL_DISPLAY_ON 0x04 +#define FLAG_DISPLAY_CONTROL_DISPLAY_OFF 0x00 +#define FLAG_DISPLAY_CONTROL_CURSOR_ON 0x02 +#define FLAG_DISPLAY_CONTROL_CURSOR_OFF 0x00 +#define FLAG_DISPLAY_CONTROL_BLINK_ON 0x01 +#define FLAG_DISPLAY_CONTROL_BLINK_OFF 0x00 + +// COMMAND_SHIFT flags +#define FLAG_SHIFT_MOVE_DISPLAY 0x08 +#define FLAG_SHIFT_MOVE_CURSOR 0x00 +#define FLAG_SHIFT_MOVE_LEFT 0x04 +#define FLAG_SHIFT_MOVE_RIGHT 0x00 + +// COMMAND_FUNCTION_SET flags +#define FLAG_FUNCTION_SET_MODE_8BIT 0x10 +#define FLAG_FUNCTION_SET_MODE_4BIT 0x00 +#define FLAG_FUNCTION_SET_LINES_2 0x08 +#define FLAG_FUNCTION_SET_LINES_1 0x00 +#define FLAG_FUNCTION_SET_DOTS_5X10 0x04 +#define FLAG_FUNCTION_SET_DOTS_5X8 0x00 + +// Control flags +#define FLAG_BACKLIGHT_ON 0b00001000 // backlight enabled (disabled if clear) +#define FLAG_BACKLIGHT_OFF 0b00000000 // backlight disabled +#define FLAG_ENABLE 0b00000100 +#define FLAG_READ 0b00000010 // read (write if clear) +#define FLAG_WRITE 0b00000000 // write +#define FLAG_RS_DATA 0b00000001 // data (command if clear) +#define FLAG_RS_COMMAND 0b00000000 // command + +static bool _is_init(const i2c_lcd1602_info_t *i2c_lcd1602_info) +{ + bool ok = false; + if (i2c_lcd1602_info != NULL) + { + if (i2c_lcd1602_info->init) + { + ok = true; + } + else + { + ESP_LOGE(TAG, "i2c_lcd1602_info is not initialised"); + } + } + else + { + ESP_LOGE(TAG, "i2c_lcd1602_info is NULL"); + } + return ok; +} + +// Set or clear the specified flag depending on condition +static uint8_t _set_or_clear(uint8_t flags, bool condition, uint8_t flag) +{ + if (condition) + { + flags |= flag; + } + else + { + flags &= ~flag; + } + return flags; +} + +// send data to the I/O Expander +static esp_err_t _write_to_expander(const i2c_lcd1602_info_t *i2c_lcd1602_info, uint8_t data) +{ + // backlight flag must be included with every write to maintain backlight state + ESP_LOGD(TAG, "_write_to_expander 0x%02x", data | i2c_lcd1602_info->backlight_flag); + return smbus_send_byte(i2c_lcd1602_info->smbus_info, data | i2c_lcd1602_info->backlight_flag); +} + +// IMPORTANT - for the display to stay "in sync" it is important that errors do not interrupt the +// 2 x nibble sequence. + +// clock data from expander to LCD by causing a falling edge on Enable +static esp_err_t _strobe_enable(const i2c_lcd1602_info_t *i2c_lcd1602_info, uint8_t data) +{ + esp_err_t err1 = _write_to_expander(i2c_lcd1602_info, data | FLAG_ENABLE); + esp_rom_delay_us(DELAY_ENABLE_PULSE_WIDTH); + esp_err_t err2 = _write_to_expander(i2c_lcd1602_info, data & ~FLAG_ENABLE); + esp_rom_delay_us(DELAY_ENABLE_PULSE_SETTLE); + return err1 ? err1 : err2; +} + +// send top nibble to the LCD controller +static esp_err_t _write_top_nibble(const i2c_lcd1602_info_t *i2c_lcd1602_info, uint8_t data) +{ + ESP_LOGD(TAG, "_write_top_nibble 0x%02x", data); + esp_err_t err1 = _write_to_expander(i2c_lcd1602_info, data); + esp_err_t err2 = _strobe_enable(i2c_lcd1602_info, data); + return err1 ? err1 : err2; +} + +// send command or data to controller +static esp_err_t _write(const i2c_lcd1602_info_t *i2c_lcd1602_info, uint8_t value, uint8_t register_select_flag) +{ + ESP_LOGD(TAG, "_write 0x%02x | 0x%02x", value, register_select_flag); + esp_err_t err1 = _write_top_nibble(i2c_lcd1602_info, (value & 0xf0) | register_select_flag); + esp_err_t err2 = _write_top_nibble(i2c_lcd1602_info, ((value & 0x0f) << 4) | register_select_flag); + return err1 ? err1 : err2; +} + +// send command to controller +static esp_err_t _write_command(const i2c_lcd1602_info_t *i2c_lcd1602_info, uint8_t command) +{ + ESP_LOGD(TAG, "_write_command 0x%02x", command); + return _write(i2c_lcd1602_info, command, FLAG_RS_COMMAND); +} + +// send data to controller +static esp_err_t _write_data(const i2c_lcd1602_info_t *i2c_lcd1602_info, uint8_t data) +{ + ESP_LOGD(TAG, "_write_data 0x%02x", data); + return _write(i2c_lcd1602_info, data, FLAG_RS_DATA); +} + +// Public API + +i2c_lcd1602_info_t *i2c_lcd1602_malloc(void) +{ + i2c_lcd1602_info_t *i2c_lcd1602_info = malloc(sizeof(*i2c_lcd1602_info)); + if (i2c_lcd1602_info != NULL) + { + memset(i2c_lcd1602_info, 0, sizeof(*i2c_lcd1602_info)); + ESP_LOGD(TAG, "malloc i2c_lcd1602_info_t %p", i2c_lcd1602_info); + } + else + { + ESP_LOGE(TAG, "malloc i2c_lcd1602_info_t failed"); + } + return i2c_lcd1602_info; +} + +void i2c_lcd1602_free(i2c_lcd1602_info_t **i2c_lcd1602_info) +{ + if (i2c_lcd1602_info != NULL && (*i2c_lcd1602_info != NULL)) + { + ESP_LOGD(TAG, "free i2c_lcd1602_info_t %p", *i2c_lcd1602_info); + free(*i2c_lcd1602_info); + *i2c_lcd1602_info = NULL; + } + else + { + ESP_LOGE(TAG, "free i2c_lcd1602_info_t failed"); + } +} + +esp_err_t i2c_lcd1602_init(i2c_lcd1602_info_t *i2c_lcd1602_info, smbus_info_t *smbus_info, + bool backlight, uint8_t num_rows, uint8_t num_columns, uint8_t num_visible_columns) +{ + esp_err_t err = ESP_FAIL; + if (i2c_lcd1602_info != NULL) + { + i2c_lcd1602_info->smbus_info = smbus_info; + i2c_lcd1602_info->backlight_flag = backlight ? FLAG_BACKLIGHT_ON : FLAG_BACKLIGHT_OFF; + i2c_lcd1602_info->num_rows = num_rows; + i2c_lcd1602_info->num_columns = num_columns; + i2c_lcd1602_info->num_visible_columns = num_visible_columns; + + // display on, no cursor, no blinking + i2c_lcd1602_info->display_control_flags = FLAG_DISPLAY_CONTROL_DISPLAY_ON | FLAG_DISPLAY_CONTROL_CURSOR_OFF | FLAG_DISPLAY_CONTROL_BLINK_OFF; + + // left-justified left-to-right text + i2c_lcd1602_info->entry_mode_flags = FLAG_ENTRY_MODE_SET_ENTRY_INCREMENT | FLAG_ENTRY_MODE_SET_ENTRY_SHIFT_OFF; + + i2c_lcd1602_info->init = true; + + // See page 45/46 of HD44780 data sheet for the initialisation procedure. + + // Wait at least 40ms after power rises above 2.7V before sending commands. + esp_rom_delay_us(DELAY_POWER_ON); + + err = i2c_lcd1602_reset(i2c_lcd1602_info); + } + else + { + ESP_LOGE(TAG, "i2c_lcd1602_info is NULL"); + err = ESP_FAIL; + } + return err; +} + +esp_err_t i2c_lcd1602_reset(const i2c_lcd1602_info_t *i2c_lcd1602_info) +{ + esp_err_t first_err = ESP_OK; + esp_err_t last_err = ESP_FAIL; + + // put Expander into known state - Register Select and Read/Write both low + if ((last_err = _write_to_expander(i2c_lcd1602_info, 0)) != ESP_OK) + { + if (first_err == ESP_OK) + first_err = last_err; + ESP_LOGE(TAG, "reset: _write_to_expander 1 failed: %d", last_err); + } + + esp_rom_delay_us(1000); + + // select 4-bit mode on LCD controller - see datasheet page 46, figure 24. + if ((last_err = _write_top_nibble(i2c_lcd1602_info, 0x03 << 4)) != ESP_OK) + { + if (first_err == ESP_OK) + first_err = last_err; + ESP_LOGE(TAG, "reset: _write_top_nibble 1 failed: %d", last_err); + } + + esp_rom_delay_us(DELAY_INIT_1); + + // repeat + if ((last_err = _write_top_nibble(i2c_lcd1602_info, 0x03 << 4)) != ESP_OK) + { + if (first_err == ESP_OK) + first_err = last_err; + ESP_LOGE(TAG, "reset: _write_top_nibble 2 failed: %d", last_err); + } + + esp_rom_delay_us(DELAY_INIT_2); + + // repeat + if ((last_err = _write_top_nibble(i2c_lcd1602_info, 0x03 << 4)) != ESP_OK) + { + if (first_err == ESP_OK) + first_err = last_err; + ESP_LOGE(TAG, "reset: _write_top_nibble 3 failed: %d", last_err); + } + + esp_rom_delay_us(DELAY_INIT_3); + + // select 4-bit mode + if ((last_err = _write_top_nibble(i2c_lcd1602_info, 0x02 << 4)) != ESP_OK) + { + if (first_err == ESP_OK) + first_err = last_err; + ESP_LOGE(TAG, "reset: _write_top_nibble 4 failed: %d", last_err); + } + + // now we can use the command()/write() functions + if ((last_err = _write_command(i2c_lcd1602_info, COMMAND_FUNCTION_SET | FLAG_FUNCTION_SET_MODE_4BIT | FLAG_FUNCTION_SET_LINES_2 | FLAG_FUNCTION_SET_DOTS_5X8)) != ESP_OK) + { + if (first_err == ESP_OK) + first_err = last_err; + ESP_LOGE(TAG, "reset: _write_command 1 failed: %d", last_err); + } + + if ((last_err = _write_command(i2c_lcd1602_info, COMMAND_DISPLAY_CONTROL | i2c_lcd1602_info->display_control_flags)) != ESP_OK) + { + if (first_err == ESP_OK) + first_err = last_err; + ESP_LOGE(TAG, "reset: _write_command 2 failed: %d", last_err); + } + + if ((last_err = i2c_lcd1602_clear(i2c_lcd1602_info)) != ESP_OK) + { + if (first_err == ESP_OK) + first_err = last_err; + ESP_LOGE(TAG, "reset: i2c_lcd1602_clear failed: %d", last_err); + } + + if ((last_err = _write_command(i2c_lcd1602_info, COMMAND_ENTRY_MODE_SET | i2c_lcd1602_info->entry_mode_flags)) != ESP_OK) + { + if (first_err == ESP_OK) + first_err = last_err; + ESP_LOGE(TAG, "reset: _write_command 3 failed: %d", last_err); + } + + if ((last_err = i2c_lcd1602_home(i2c_lcd1602_info)) != ESP_OK) + { + if (first_err == ESP_OK) + first_err = last_err; + ESP_LOGE(TAG, "reset: i2c_lcd1602_home failed: %d", last_err); + } + + return first_err; +} + +esp_err_t i2c_lcd1602_clear(const i2c_lcd1602_info_t *i2c_lcd1602_info) +{ + esp_err_t err = ESP_FAIL; + if (_is_init(i2c_lcd1602_info)) + { + err = _write_command(i2c_lcd1602_info, COMMAND_CLEAR_DISPLAY); + if (err == ESP_OK) + { + esp_rom_delay_us(DELAY_CLEAR_DISPLAY); + } + } + return err; +} + +esp_err_t i2c_lcd1602_home(const i2c_lcd1602_info_t *i2c_lcd1602_info) +{ + esp_err_t err = ESP_FAIL; + if (_is_init(i2c_lcd1602_info)) + { + err = _write_command(i2c_lcd1602_info, COMMAND_RETURN_HOME); + if (err == ESP_OK) + { + esp_rom_delay_us(DELAY_RETURN_HOME); + } + } + return err; +} + +esp_err_t i2c_lcd1602_move_cursor(const i2c_lcd1602_info_t *i2c_lcd1602_info, uint8_t col, uint8_t row) +{ + esp_err_t err = ESP_FAIL; + if (_is_init(i2c_lcd1602_info)) + { + const int row_offsets[] = {0x00, 0x40, 0x14, 0x54}; + if (row > i2c_lcd1602_info->num_rows) + { + row = i2c_lcd1602_info->num_rows - 1; + } + if (col > i2c_lcd1602_info->num_columns) + { + col = i2c_lcd1602_info->num_columns - 1; + } + err = _write_command(i2c_lcd1602_info, COMMAND_SET_DDRAM_ADDR | (col + row_offsets[row])); + } + return err; +} + +esp_err_t i2c_lcd1602_set_backlight(i2c_lcd1602_info_t *i2c_lcd1602_info, bool enable) +{ + esp_err_t err = ESP_FAIL; + if (_is_init(i2c_lcd1602_info)) + { + i2c_lcd1602_info->backlight_flag = _set_or_clear(i2c_lcd1602_info->backlight_flag, enable, FLAG_BACKLIGHT_ON); + err = _write_to_expander(i2c_lcd1602_info, 0); + } + return err; +} + +esp_err_t i2c_lcd1602_set_display(i2c_lcd1602_info_t *i2c_lcd1602_info, bool enable) +{ + esp_err_t err = ESP_FAIL; + if (_is_init(i2c_lcd1602_info)) + { + i2c_lcd1602_info->display_control_flags = _set_or_clear(i2c_lcd1602_info->display_control_flags, enable, FLAG_DISPLAY_CONTROL_DISPLAY_ON); + err = _write_command(i2c_lcd1602_info, COMMAND_DISPLAY_CONTROL | i2c_lcd1602_info->display_control_flags); + } + return err; +} + +esp_err_t i2c_lcd1602_set_cursor(i2c_lcd1602_info_t *i2c_lcd1602_info, bool enable) +{ + esp_err_t err = ESP_FAIL; + if (_is_init(i2c_lcd1602_info)) + { + i2c_lcd1602_info->display_control_flags = _set_or_clear(i2c_lcd1602_info->display_control_flags, enable, FLAG_DISPLAY_CONTROL_CURSOR_ON); + err = _write_command(i2c_lcd1602_info, COMMAND_DISPLAY_CONTROL | i2c_lcd1602_info->display_control_flags); + } + return err; +} + +esp_err_t i2c_lcd1602_set_blink(i2c_lcd1602_info_t *i2c_lcd1602_info, bool enable) +{ + esp_err_t err = ESP_FAIL; + if (_is_init(i2c_lcd1602_info)) + { + i2c_lcd1602_info->display_control_flags = _set_or_clear(i2c_lcd1602_info->display_control_flags, enable, FLAG_DISPLAY_CONTROL_BLINK_ON); + err = _write_command(i2c_lcd1602_info, COMMAND_DISPLAY_CONTROL | i2c_lcd1602_info->display_control_flags); + } + return err; +} + +esp_err_t i2c_lcd1602_set_left_to_right(i2c_lcd1602_info_t *i2c_lcd1602_info) +{ + esp_err_t err = ESP_FAIL; + if (_is_init(i2c_lcd1602_info)) + { + i2c_lcd1602_info->entry_mode_flags |= FLAG_ENTRY_MODE_SET_ENTRY_INCREMENT; + err = _write_command(i2c_lcd1602_info, COMMAND_ENTRY_MODE_SET | i2c_lcd1602_info->entry_mode_flags); + } + return err; +} + +esp_err_t i2c_lcd1602_set_right_to_left(i2c_lcd1602_info_t *i2c_lcd1602_info) +{ + esp_err_t err = ESP_FAIL; + if (_is_init(i2c_lcd1602_info)) + { + i2c_lcd1602_info->entry_mode_flags &= ~FLAG_ENTRY_MODE_SET_ENTRY_INCREMENT; + err = _write_command(i2c_lcd1602_info, COMMAND_ENTRY_MODE_SET | i2c_lcd1602_info->entry_mode_flags); + } + return err; +} + +esp_err_t i2c_lcd1602_set_auto_scroll(i2c_lcd1602_info_t *i2c_lcd1602_info, bool enable) +{ + esp_err_t err = ESP_FAIL; + if (_is_init(i2c_lcd1602_info)) + { + i2c_lcd1602_info->entry_mode_flags = _set_or_clear(i2c_lcd1602_info->entry_mode_flags, enable, FLAG_ENTRY_MODE_SET_ENTRY_SHIFT_ON); + err = _write_command(i2c_lcd1602_info, COMMAND_ENTRY_MODE_SET | i2c_lcd1602_info->entry_mode_flags); + } + return err; +} + +esp_err_t i2c_lcd1602_scroll_display_left(const i2c_lcd1602_info_t *i2c_lcd1602_info) +{ + esp_err_t err = ESP_FAIL; + if (_is_init(i2c_lcd1602_info)) + { + // RAM is not changed + err = _write_command(i2c_lcd1602_info, COMMAND_SHIFT | FLAG_SHIFT_MOVE_DISPLAY | FLAG_SHIFT_MOVE_LEFT); + } + return err; +} + +esp_err_t i2c_lcd1602_scroll_display_right(const i2c_lcd1602_info_t *i2c_lcd1602_info) +{ + esp_err_t err = ESP_FAIL; + if (_is_init(i2c_lcd1602_info)) + { + // RAM is not changed + err = _write_command(i2c_lcd1602_info, COMMAND_SHIFT | FLAG_SHIFT_MOVE_DISPLAY | FLAG_SHIFT_MOVE_RIGHT); + } + return err; +} + +esp_err_t i2c_lcd1602_move_cursor_left(const i2c_lcd1602_info_t *i2c_lcd1602_info) +{ + esp_err_t err = ESP_FAIL; + if (_is_init(i2c_lcd1602_info)) + { + // RAM is not changed. Shift direction is inverted. + err = _write_command(i2c_lcd1602_info, COMMAND_SHIFT | FLAG_SHIFT_MOVE_CURSOR | FLAG_SHIFT_MOVE_RIGHT); + } + return err; +} + +esp_err_t i2c_lcd1602_move_cursor_right(const i2c_lcd1602_info_t *i2c_lcd1602_info) +{ + esp_err_t err = ESP_FAIL; + if (_is_init(i2c_lcd1602_info)) + { + // RAM is not changed. Shift direction is inverted. + err = _write_command(i2c_lcd1602_info, COMMAND_SHIFT | FLAG_SHIFT_MOVE_CURSOR | FLAG_SHIFT_MOVE_LEFT); + } + return err; +} + +esp_err_t i2c_lcd1602_define_char(const i2c_lcd1602_info_t *i2c_lcd1602_info, i2c_lcd1602_custom_index_t index, const uint8_t pixelmap[]) +{ + esp_err_t err = ESP_FAIL; + if (_is_init(i2c_lcd1602_info)) + { + index &= 0x07; // only the first 8 indexes can be used for custom characters + err = _write_command(i2c_lcd1602_info, COMMAND_SET_CGRAM_ADDR | (index << 3)); + for (int i = 0; err == ESP_OK && i < 8; ++i) + { + err = _write_data(i2c_lcd1602_info, pixelmap[i]); + } + } + return err; +} + +esp_err_t i2c_lcd1602_write_char(const i2c_lcd1602_info_t *i2c_lcd1602_info, uint8_t chr) +{ + esp_err_t err = ESP_FAIL; + if (_is_init(i2c_lcd1602_info)) + { + err = _write_data(i2c_lcd1602_info, chr); + } + return err; +} + +esp_err_t i2c_lcd1602_write_string(const i2c_lcd1602_info_t *i2c_lcd1602_info, const char *string) +{ + esp_err_t err = ESP_FAIL; + if (_is_init(i2c_lcd1602_info)) + { + // ESP_LOGI(TAG, "i2c_lcd1602_write_string: %s", string); + err = ESP_OK; + for (int i = 0; err == ESP_OK && string[i]; ++i) + { + err = _write_data(i2c_lcd1602_info, string[i]); + } + } + return err; +} + +// TEMPLATE +#if 0 +esp_err_t i2c_lcd1602_XXX(i2c_lcd1602_info_t * i2c_lcd1602_info) +{ + esp_err_t err = ESP_FAIL; + if (_is_init(i2c_lcd1602_info)) + { + } + return err; +} +#endif diff --git a/main/components/esp32-i2c-lcd1602/i2c-lcd1602.h b/main/components/esp32-i2c-lcd1602/i2c-lcd1602.h new file mode 100644 index 0000000..d13db2a --- /dev/null +++ b/main/components/esp32-i2c-lcd1602/i2c-lcd1602.h @@ -0,0 +1,336 @@ +/* + * MIT License + * + * Copyright (c) 2018 David Antliff + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * @file + * @brief Interface definitions for the ESP32-compatible I2C LCD1602 component. + * + * This component provides structures and functions that are useful for communicating with the device. + * + * Technically, the LCD1602 device is an I2C not SMBus device, however some SMBus protocols can be used + * to communicate with the device, so it makes sense to use an SMBus interface to manage communication. + */ + +#ifndef I2C_LCD1602_H +#define I2C_LCD1602_H + +#include "stdbool.h" +#include "../esp32-smbus/smbus.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + + /** + * @brief Structure containing information related to the I2C-LCD1602 device. + */ + typedef struct + { + bool init; ///< True if struct has been initialised, otherwise false + smbus_info_t *smbus_info; ///< Pointer to associated SMBus info + uint8_t backlight_flag; ///< Non-zero if backlight is to be enabled, otherwise zero + uint8_t num_rows; ///< Number of configured columns + uint8_t num_columns; ///< Number of configured columns, including offscreen columns + uint8_t num_visible_columns; ///< Number of visible columns + uint8_t display_control_flags; ///< Currently active display control flags + uint8_t entry_mode_flags; ///< Currently active entry mode flags + } i2c_lcd1602_info_t; + +// Special characters for ROM Code A00 + +// Use the second set (0bxxxx1xxx) to avoid placing the null character within a string +#define I2C_LCD1602_CHARACTER_CUSTOM_0 0b00001000 ///< User-defined custom symbol in index 0 +#define I2C_LCD1602_CHARACTER_CUSTOM_1 0b00001001 ///< User-defined custom symbol in index 1 +#define I2C_LCD1602_CHARACTER_CUSTOM_2 0b00001010 ///< User-defined custom symbol in index 2 +#define I2C_LCD1602_CHARACTER_CUSTOM_3 0b00001011 ///< User-defined custom symbol in index 3 +#define I2C_LCD1602_CHARACTER_CUSTOM_4 0b00001100 ///< User-defined custom symbol in index 4 +#define I2C_LCD1602_CHARACTER_CUSTOM_5 0b00001101 ///< User-defined custom symbol in index 5 +#define I2C_LCD1602_CHARACTER_CUSTOM_6 0b00001110 ///< User-defined custom symbol in index 6 +#define I2C_LCD1602_CHARACTER_CUSTOM_7 0b00001111 ///< User-defined custom symbol in index 7 + +#define I2C_LCD1602_CHARACTER_ALPHA 0b11100000 ///< Lower-case alpha symbol +#define I2C_LCD1602_CHARACTER_BETA 0b11100010 ///< Lower-case beta symbol +#define I2C_LCD1602_CHARACTER_THETA 0b11110010 ///< Lower-case theta symbol +#define I2C_LCD1602_CHARACTER_PI 0b11110111 ///< Lower-case pi symbol +#define I2C_LCD1602_CHARACTER_OMEGA 0b11110100 ///< Upper-case omega symbol +#define I2C_LCD1602_CHARACTER_SIGMA 0b11110110 ///< Upper-case sigma symbol +#define I2C_LCD1602_CHARACTER_INFINITY 0b11110011 ///< Infinity symbol +#define I2C_LCD1602_CHARACTER_DEGREE 0b11011111 ///< Degree symbol +#define I2C_LCD1602_CHARACTER_ARROW_RIGHT 0b01111110 ///< Arrow pointing right symbol +#define I2C_LCD1602_CHARACTER_ARROW_LEFT 0b01111111 ///< Arrow pointing left symbol +#define I2C_LCD1602_CHARACTER_SQUARE 0b11011011 ///< Square outline symbol +#define I2C_LCD1602_CHARACTER_DOT 0b10100101 ///< Centred dot symbol +#define I2C_LCD1602_CHARACTER_DIVIDE 0b11111101 ///< Division sign symbol +#define I2C_LCD1602_CHARACTER_BLOCK 0b11111111 ///< 5x8 filled block + + /** + * @brief Enum of valid indexes for definitions of user-defined characters. + */ + typedef enum + { + I2C_LCD1602_INDEX_CUSTOM_0 = 0, ///< Index of first user-defined custom symbol + I2C_LCD1602_INDEX_CUSTOM_1, ///< Index of second user-defined custom symbol + I2C_LCD1602_INDEX_CUSTOM_2, ///< Index of third user-defined custom symbol + I2C_LCD1602_INDEX_CUSTOM_3, ///< Index of fourth user-defined custom symbol + I2C_LCD1602_INDEX_CUSTOM_4, ///< Index of fifth user-defined custom symbol + I2C_LCD1602_INDEX_CUSTOM_5, ///< Index of sixth user-defined custom symbol + I2C_LCD1602_INDEX_CUSTOM_6, ///< Index of seventh user-defined custom symbol + I2C_LCD1602_INDEX_CUSTOM_7, ///< Index of eighth user-defined custom symbol + } i2c_lcd1602_custom_index_t; + +#define I2C_LCD1602_ERROR_CHECK(x) \ + do \ + { \ + esp_err_t rc = (x); \ + if (rc != ESP_OK) \ + { \ + ESP_LOGW(TAG, "I2C error %d at %s:%d", rc, __FILE__, __LINE__); \ + } \ + } while (0); + + /** + * @brief Construct a new I2C-LCD1602 info instance. + * New instance should be initialised before calling other functions. + * + * @return Pointer to new device info instance, or NULL if it cannot be created. + */ + i2c_lcd1602_info_t *i2c_lcd1602_malloc(void); + + /** + * @brief Delete an existing I2C-LCD1602 info instance. + * + * @param[in,out] tsl2561_info Pointer to I2C-LCD1602 info instance that will be freed and set to NULL. + */ + void i2c_lcd1602_free(i2c_lcd1602_info_t **tsl2561_info); + + /** + * @brief Initialise a I2C-LCD1602 info instance with the specified SMBus information. + * + * @param[in] i2c_lcd1602_info Pointer to I2C-LCD1602 info instance. + * @param[in] smbus_info Pointer to SMBus info instance. + * @param[in] backlight Initial backlight state. + * @param[in] num_rows Maximum number of supported rows for this device. Typical values include 2 (1602) or 4 (2004). + * @param[in] num_columns Maximum number of supported columns for this device. Typical values include 40 (1602, 2004). + * @param[in] num_visible_columns Number of columns visible at any one time. Typical values include 16 (1602) or 20 (2004). + * @return ESP_OK if successful, otherwise an error constant. + */ + esp_err_t i2c_lcd1602_init(i2c_lcd1602_info_t *i2c_lcd1602_info, smbus_info_t *smbus_info, + bool backlight, uint8_t num_rows, uint8_t num_columns, uint8_t num_visible_columns); + + /** + * @brief Reset the display. Custom characters will be cleared. + * + * @param[in] i2c_lcd1602_info Pointer to I2C-LCD1602 info instance. + * @return ESP_OK if successful, otherwise an error constant. + */ + esp_err_t i2c_lcd1602_reset(const i2c_lcd1602_info_t *i2c_lcd1602_info); + + /** + * @brief Clears entire display (clears DDRAM) and returns cursor to home position. + * DDRAM content is cleared, CGRAM content is not changed. + * + * @param[in] i2c_lcd1602_info Pointer to initialised I2C-LCD1602 info instance. + * @return ESP_OK if successful, otherwise an error constant. + */ + esp_err_t i2c_lcd1602_clear(const i2c_lcd1602_info_t *i2c_lcd1602_info); + + /** + * @brief Move cursor to home position. Also resets any display shift that may have occurred. + * DDRAM content is not changed. CGRAM content is not changed. + * + * @param[in] i2c_lcd1602_info Pointer to initialised I2C-LCD1602 info instance. + * @return ESP_OK if successful, otherwise an error constant. + */ + esp_err_t i2c_lcd1602_home(const i2c_lcd1602_info_t *i2c_lcd1602_info); + + /** + * @brief Move cursor to specified column and row position. This is where a new character will appear. + * + * @param[in] i2c_lcd1602_info Pointer to initialised I2C-LCD1602 info instance. + * @param[in] col Zero-based horizontal index of intended cursor position. Column 0 is the left column. + * @param[in] row Zero-based vertical index of intended cursor position. Row 0 is the top row. + * @return ESP_OK if successful, otherwise an error constant. + */ + esp_err_t i2c_lcd1602_move_cursor(const i2c_lcd1602_info_t *i2c_lcd1602_info, uint8_t col, uint8_t row); + + /** + * @brief Enable or disable the LED backlight. + * + * @param[in] i2c_lcd1602_info Pointer to initialised I2C-LCD1602 info instance. + * @param[in] enable True to enable, false to disable. + * @return ESP_OK if successful, otherwise an error constant. + */ + esp_err_t i2c_lcd1602_set_backlight(i2c_lcd1602_info_t *i2c_lcd1602_info, bool enable); + + /** + * @brief Enable or disable the display. When disabled, the backlight is not affected, but any + * contents of the DDRAM is not displayed, nor is the cursor. The display is "blank". + * Re-enabling the display does not affect the contents of DDRAM or the state or position of the cursor. + * + * @param[in] i2c_lcd1602_info Pointer to initialised I2C-LCD1602 info instance. + * @param[in] enable True to enable, false to disable. + * @return ESP_OK if successful, otherwise an error constant. + */ + esp_err_t i2c_lcd1602_set_display(i2c_lcd1602_info_t *i2c_lcd1602_info, bool enable); + + /** + * @brief Enable or disable display of the underline cursor. + * If enabled, this visually indicates where the next character written to the display will appear. + * It may be enabled alongside the blinking cursor, however the visual result is inelegant. + * + * @param[in] i2c_lcd1602_info Pointer to initialised I2C-LCD1602 info instance. + * @param[in] enable True to enable, false to disable. + * @return ESP_OK if successful, otherwise an error constant. + */ + esp_err_t i2c_lcd1602_set_cursor(i2c_lcd1602_info_t *i2c_lcd1602_info, bool enable); + + /** + * @brief Enable or disable display of the blinking block cursor. + * If enabled, this visually indicates where the next character written to the display will appear. + * It may be enabled alongside the underline cursor, however the visual result is inelegant. + * + * @param[in] i2c_lcd1602_info Pointer to initialised I2C-LCD1602 info instance. + * @param[in] enable True to enable, false to disable. + * @return ESP_OK if successful, otherwise an error constant. + */ + esp_err_t i2c_lcd1602_set_blink(i2c_lcd1602_info_t *i2c_lcd1602_info, bool enable); + + /** + * @brief Set cursor movement direction following each character write to produce left-to-right text. + * + * @param[in] i2c_lcd1602_info Pointer to initialised I2C-LCD1602 info instance. + * @return ESP_OK if successful, otherwise an error constant. + */ + esp_err_t i2c_lcd1602_set_left_to_right(i2c_lcd1602_info_t *i2c_lcd1602_info); + + /** + * @brief Set cursor movement direction following each character write to produce right-to-left text. + * + * @param[in] i2c_lcd1602_info Pointer to initialised I2C-LCD1602 info instance. + * @return ESP_OK if successful, otherwise an error constant. + */ + esp_err_t i2c_lcd1602_set_right_to_left(i2c_lcd1602_info_t *i2c_lcd1602_info); + + /** + * @brief Enable or disable auto-scroll of display. + * When enabled, the display will scroll as characters are written to maintain the cursor position on-screen. + * Left-to-right text will appear to be right-justified from the cursor position. + * When disabled, the display will not scroll and the cursor will move on-screen. + * Left-to-right text will appear to be left-justified from the cursor position. + * + * @param[in] i2c_lcd1602_info Pointer to initialised I2C-LCD1602 info instance. + * @param[in] enable True to enable, false to disable. + * @return ESP_OK if successful, otherwise an error constant. + */ + esp_err_t i2c_lcd1602_set_auto_scroll(i2c_lcd1602_info_t *i2c_lcd1602_info, bool enable); + + /** + * @brief Scroll the display one position to the left. On-screen text will appear to move to the right. + * + * @param[in] i2c_lcd1602_info Pointer to initialised I2C-LCD1602 info instance. + * @return ESP_OK if successful, otherwise an error constant. + */ + esp_err_t i2c_lcd1602_scroll_display_left(const i2c_lcd1602_info_t *i2c_lcd1602_info); + + /** + * @brief Scroll the display one position to the right. On-screen text will appear to move to the left. + * @param[in] i2c_lcd1602_info Pointer to initialised I2C-LCD1602 info instance. + * @return ESP_OK if successful, otherwise an error constant. + */ + esp_err_t i2c_lcd1602_scroll_display_right(const i2c_lcd1602_info_t *i2c_lcd1602_info); + + /** + * @brief Move the cursor one position to the left, even if it is invisible. + * This affects where the next character written to the display will appear. + * + * @param[in] i2c_lcd1602_info Pointer to initialised I2C-LCD1602 info instance. + * @return ESP_OK if successful, otherwise an error constant. + */ + esp_err_t i2c_lcd1602_move_cursor_left(const i2c_lcd1602_info_t *i2c_lcd1602_info); + + /** + * @brief Move the cursor one position to the right, even if it is invisible. + * This affects where the next character written to the display will appear. + * + * @param[in] i2c_lcd1602_info Pointer to initialised I2C-LCD1602 info instance. + * @return ESP_OK if successful, otherwise an error constant. + */ + esp_err_t i2c_lcd1602_move_cursor_right(const i2c_lcd1602_info_t *i2c_lcd1602_info); + + /** + * @brief Define a custom character from an array of pixel data. + * + * There are eight possible custom characters, and the zero-based index is used + * to select a character to define. Any existing character definition at that index will be lost. + * Characters are 5 pixels wide and 8 pixels tall. + * The pixelmap array consists of up to eight bytes, each byte representing the pixel states per row. + * The first byte represents the top row. The eighth byte is often left as zero (to leave space for the underline cursor). + * For each row, the lowest five bits represent pixels that are to be illuminated. The least significant bit represents + * the right-most pixel. Empty rows will be zero. + * + * NOTE: After calling this function, the DDRAM will not be selected and the cursor position will be undefined. Therefore it + * is important that the DDRAM address is set following this function, if text is to be written to the display. + * This can be performed with a call to i2c_lcd1602_home() or i2c_lcd1602_move_cursor(). + * + * Custom characters are written using the I2C_LCD1602_CHARACTER_CUSTOM_X definitions. + * + * @param[in] i2c_lcd1602_info Pointer to initialised I2C-LCD1602 info instance. + * @param[in] index Zero-based index of the character to define. Only values 0-7 are valid. + * @param[in] pixelmap An 8-byte array defining the pixel map for the new character definition. + * @return ESP_OK if successful, otherwise an error constant. + */ + esp_err_t i2c_lcd1602_define_char(const i2c_lcd1602_info_t *i2c_lcd1602_info, i2c_lcd1602_custom_index_t index, const uint8_t pixelmap[]); + + /** + * @brief Write a single character to the display at the current position of the cursor. + * Depending on the active mode, the cursor may move left or right, or the display may shift left or right. + * Custom characters can be written using the I2C_LCD1602_CHARACTER_CUSTOM_X definitions. + * + * The display is I2C_LCD1602_NUM_COLUMNS wide, and upon encountering the end of the first line, the cursor + * will automatically move to the beginning of the second line. + * + * @param[in] i2c_lcd1602_info Pointer to initialised I2C-LCD1602 info instance. + * @return ESP_OK if successful, otherwise an error constant. + */ + esp_err_t i2c_lcd1602_write_char(const i2c_lcd1602_info_t *i2c_lcd1602_info, uint8_t chr); + + /** + * @brief Write a string of characters to the display, starting at the current position of the cursor. + * Depending on the active mode, the cursor may move left or right, or the display may shift left or right, + * after each character is written. + * + * The display is I2C_LCD1602_NUM_COLUMNS wide, and upon encountering the end of the first line, the cursor + * will automatically move to the beginning of the second line. + * + * @param[in] i2c_lcd1602_info Pointer to initialised I2C-LCD1602 info instance. + * @return ESP_OK if successful, otherwise an error constant. + */ + esp_err_t i2c_lcd1602_write_string(const i2c_lcd1602_info_t *i2c_lcd1602_info, const char *string); + +#ifdef __cplusplus +} +#endif + +#endif // I2C_LCD1602_H diff --git a/main/components/esp32-smbus/smbus.c b/main/components/esp32-smbus/smbus.c new file mode 100644 index 0000000..40419fb --- /dev/null +++ b/main/components/esp32-smbus/smbus.c @@ -0,0 +1,401 @@ +/* + * MIT License + * + * Copyright (c) 2017 David Antliff + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * @file smbus.c + * + * SMBus diagrams are represented as a chain of "piped" symbols, using the following symbols: + * + * - ADDR : the 7-bit I2C address of a bus slave. + * - S : the START condition sent by a bus master. + * - Sr : the REPEATED START condition sent by a master. + * - P : the STOP condition sent by a master. + * - Wr : bit 0 of the address byte indicating a write operation. Value is 0. + * - Rd : bit 0 of the address byte indicating a read operation. Value is 1. + * - R/W : bit 0 of the address byte, indicating a read or write operation. + * - A : ACKnowledge bit sent by a master. + * - N : Not ACKnowledge bit set by a master. + * - As : ACKnowledge bit sent by a slave. + * - Ns : Not ACKnowledge bit set by a slave. + * - DATA : data byte sent by a master. + * - DATAs : data byte sent by a slave. + */ + +// TODO: add proper error checking around all i2c functions + +#include +#include +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_system.h" +#include "esp_log.h" + +#include "smbus.h" + +static const char * TAG = "smbus"; + +#define WRITE_BIT I2C_MASTER_WRITE +#define READ_BIT I2C_MASTER_READ +#define ACK_CHECK true +#define NO_ACK_CHECK false +#define ACK_VALUE 0x0 +#define NACK_VALUE 0x1 +#define MAX_BLOCK_LEN 255 // SMBus v3.0 increases this from 32 to 255 +//#define MEASURE // enable measurement and reporting of I2C transaction duration + +static bool _is_init(const smbus_info_t * smbus_info) +{ + bool ok = false; + if (smbus_info != NULL) + { + if (smbus_info->init) + { + ok = true; + } + else + { + ESP_LOGE(TAG, "smbus_info is not initialised"); + } + } + else + { + ESP_LOGE(TAG, "smbus_info is NULL"); + } + return ok; +} + +static esp_err_t _check_i2c_error(esp_err_t err) +{ + switch (err) + { + case ESP_OK: // Success + break; + case ESP_ERR_INVALID_ARG: // Parameter error + ESP_LOGE(TAG, "I2C parameter error"); + break; + case ESP_FAIL: // Sending command error, slave doesn't ACK the transfer. + ESP_LOGE(TAG, "I2C no slave ACK"); + break; + case ESP_ERR_INVALID_STATE: // I2C driver not installed or not in master mode. + ESP_LOGE(TAG, "I2C driver not installed or not master"); + break; + case ESP_ERR_TIMEOUT: // Operation timeout because the bus is busy. + ESP_LOGE(TAG, "I2C timeout"); + break; + default: + ESP_LOGE(TAG, "I2C error %d", err); + } + return err; +} + +esp_err_t _write_bytes(const smbus_info_t * smbus_info, uint8_t command, uint8_t * data, size_t len) +{ + // Protocol: [S | ADDR | Wr | As | COMMAND | As | (DATA | As){*len} | P] + esp_err_t err = ESP_FAIL; + if (_is_init(smbus_info) && data) + { + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, smbus_info->address << 1 | WRITE_BIT, ACK_CHECK); + i2c_master_write_byte(cmd, command, ACK_CHECK); + i2c_master_write(cmd, data, len, ACK_CHECK); + i2c_master_stop(cmd); +#ifdef MEASURE + uint64_t start_time = esp_timer_get_time(); +#endif + err = _check_i2c_error(i2c_master_cmd_begin(smbus_info->i2c_port, cmd, smbus_info->timeout)); +#ifdef MEASURE + ESP_LOGI(TAG, "_write_bytes: i2c_master_cmd_begin took %"PRIu64" us", esp_timer_get_time() - start_time); +#endif + i2c_cmd_link_delete(cmd); + } + return err; +} + +esp_err_t _read_bytes(const smbus_info_t * smbus_info, uint8_t command, uint8_t * data, size_t len) +{ + // Protocol: [S | ADDR | Wr | As | COMMAND | As | Sr | ADDR | Rd | As | (DATAs | A){*len-1} | DATAs | N | P] + esp_err_t err = ESP_FAIL; + if (_is_init(smbus_info) && data) + { + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, smbus_info->address << 1 | WRITE_BIT, ACK_CHECK); + i2c_master_write_byte(cmd, command, ACK_CHECK); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, smbus_info->address << 1 | READ_BIT, ACK_CHECK); + if (len > 1) + { + i2c_master_read(cmd, data, len - 1, ACK_VALUE); + } + i2c_master_read_byte(cmd, &data[len - 1], NACK_VALUE); + i2c_master_stop(cmd); +#ifdef MEASURE + uint64_t start_time = esp_timer_get_time(); +#endif + err = _check_i2c_error(i2c_master_cmd_begin(smbus_info->i2c_port, cmd, smbus_info->timeout)); +#ifdef MEASURE + ESP_LOGI(TAG, "_read_bytes: i2c_master_cmd_begin took %"PRIu64" us", esp_timer_get_time() - start_time); +#endif + i2c_cmd_link_delete(cmd); + } + return err; +} + + +// Public API + +smbus_info_t * smbus_malloc(void) +{ + smbus_info_t * smbus_info = malloc(sizeof(*smbus_info)); + if (smbus_info != NULL) + { + memset(smbus_info, 0, sizeof(*smbus_info)); + ESP_LOGD(TAG, "malloc smbus_info_t %p", smbus_info); + } + else + { + ESP_LOGE(TAG, "malloc smbus_info_t failed"); + } + return smbus_info; +} + +void smbus_free(smbus_info_t ** smbus_info) +{ + if (smbus_info != NULL && (*smbus_info != NULL)) + { + ESP_LOGD(TAG, "free smbus_info_t %p", *smbus_info); + free(*smbus_info); + *smbus_info = NULL; + } + else + { + ESP_LOGE(TAG, "free smbus_info_t failed"); + } +} + +esp_err_t smbus_init(smbus_info_t * smbus_info, i2c_port_t i2c_port, i2c_address_t address) +{ + if (smbus_info != NULL) + { + smbus_info->i2c_port = i2c_port; + smbus_info->address = address; + smbus_info->timeout = SMBUS_DEFAULT_TIMEOUT; + smbus_info->init = true; + } + else + { + ESP_LOGE(TAG, "smbus_info is NULL"); + return ESP_FAIL; + } + return ESP_OK; +} + +esp_err_t smbus_set_timeout(smbus_info_t * smbus_info, portBASE_TYPE timeout) +{ + esp_err_t err = ESP_FAIL; + if (_is_init(smbus_info)) + { + smbus_info->timeout = timeout; + err = ESP_OK; + } + return err; +} + +esp_err_t smbus_quick(const smbus_info_t * smbus_info, bool bit) +{ + // Protocol: [S | ADDR | R/W | As | P] + esp_err_t err = ESP_FAIL; + if (_is_init(smbus_info)) + { + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, smbus_info->address << 1 | bit, ACK_CHECK); + i2c_master_stop(cmd); + err = _check_i2c_error(i2c_master_cmd_begin(smbus_info->i2c_port, cmd, smbus_info->timeout)); + i2c_cmd_link_delete(cmd); + } + return err; +} + +esp_err_t smbus_send_byte(const smbus_info_t * smbus_info, uint8_t data) +{ + // Protocol: [S | ADDR | Wr | As | DATA | As | P] + esp_err_t err = ESP_FAIL; + if (_is_init(smbus_info)) + { + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, smbus_info->address << 1 | WRITE_BIT, ACK_CHECK); + i2c_master_write_byte(cmd, data, ACK_CHECK); + i2c_master_stop(cmd); + err = _check_i2c_error(i2c_master_cmd_begin(smbus_info->i2c_port, cmd, smbus_info->timeout)); + i2c_cmd_link_delete(cmd); + } + return err; +} + +esp_err_t smbus_receive_byte(const smbus_info_t * smbus_info, uint8_t * data) +{ + // Protocol: [S | ADDR | Rd | As | DATAs | N | P] + esp_err_t err = ESP_FAIL; + if (_is_init(smbus_info)) + { + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, smbus_info->address << 1 | READ_BIT, ACK_CHECK); + i2c_master_read_byte(cmd, data, NACK_VALUE); + i2c_master_stop(cmd); + err = _check_i2c_error(i2c_master_cmd_begin(smbus_info->i2c_port, cmd, smbus_info->timeout)); + i2c_cmd_link_delete(cmd); + } + return err; +} + +esp_err_t smbus_write_byte(const smbus_info_t * smbus_info, uint8_t command, uint8_t data) +{ + // Protocol: [S | ADDR | Wr | As | COMMAND | As | DATA | As | P] + return _write_bytes(smbus_info, command, &data, 1); +} + +esp_err_t smbus_write_word(const smbus_info_t * smbus_info, uint8_t command, uint16_t data) +{ + // Protocol: [S | ADDR | Wr | As | COMMAND | As | DATA-LOW | As | DATA-HIGH | As | P] + uint8_t temp[2] = { data & 0xff, (data >> 8) & 0xff }; + return _write_bytes(smbus_info, command, temp, 2); +} + +esp_err_t smbus_read_byte(const smbus_info_t * smbus_info, uint8_t command, uint8_t * data) +{ + // Protocol: [S | ADDR | Wr | As | COMMAND | As | Sr | ADDR | Rd | As | DATA | N | P] + return _read_bytes(smbus_info, command, data, 1); +} + +esp_err_t smbus_read_word(const smbus_info_t * smbus_info, uint8_t command, uint16_t * data) +{ + // Protocol: [S | ADDR | Wr | As | COMMAND | As | Sr | ADDR | Rd | As | DATA-LOW | A | DATA-HIGH | N | P] + esp_err_t err = ESP_FAIL; + uint8_t temp[2] = { 0 }; + if (data) + { + err = _read_bytes(smbus_info, command, temp, 2); + if (err == ESP_OK) + { + *data = (temp[1] << 8) + temp[0]; + } + else + { + *data = 0; + } + } + return err; +} + +esp_err_t smbus_write_block(const smbus_info_t * smbus_info, uint8_t command, uint8_t * data, uint8_t len) +{ + // Protocol: [S | ADDR | Wr | As | COMMAND | As | LEN | As | DATA-1 | As | DATA-2 | As ... | DATA-LEN | As | P] + esp_err_t err = ESP_FAIL; + if (_is_init(smbus_info) && data) + { + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, smbus_info->address << 1 | WRITE_BIT, ACK_CHECK); + i2c_master_write_byte(cmd, command, ACK_CHECK); + i2c_master_write_byte(cmd, len, ACK_CHECK); + for (size_t i = 0; i < len; ++i) + { + i2c_master_write_byte(cmd, data[i], ACK_CHECK); + } + i2c_master_stop(cmd); + err = _check_i2c_error(i2c_master_cmd_begin(smbus_info->i2c_port, cmd, smbus_info->timeout)); + i2c_cmd_link_delete(cmd); + } + return err; +} + +esp_err_t smbus_read_block(const smbus_info_t * smbus_info, uint8_t command, uint8_t * data, uint8_t * len) +{ + // Protocol: [S | ADDR | Wr | As | COMMAND | As | Sr | ADDR | Rd | As | LENs | A | DATA-1 | A | DATA-2 | A ... | DATA-LEN | N | P] + esp_err_t err = ESP_FAIL; + + if (_is_init(smbus_info) && data && len) + { + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, smbus_info->address << 1 | WRITE_BIT, ACK_CHECK); + i2c_master_write_byte(cmd, command, ACK_CHECK); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, smbus_info->address << 1 | READ_BIT, ACK_CHECK); + uint8_t slave_len = 0; + i2c_master_read_byte(cmd, &slave_len, ACK_VALUE); + err = _check_i2c_error(i2c_master_cmd_begin(smbus_info->i2c_port, cmd, smbus_info->timeout)); + i2c_cmd_link_delete(cmd); + + if (err != ESP_OK) + { + *len = 0; + return err; + } + + if (slave_len > *len) + { + ESP_LOGW(TAG, "slave data length %d exceeds data len %d bytes", slave_len, *len); + slave_len = *len; + } + + cmd = i2c_cmd_link_create(); + for (size_t i = 0; i < slave_len - 1; ++i) + { + i2c_master_read_byte(cmd, &data[i], ACK_VALUE); + } + i2c_master_read_byte(cmd, &data[slave_len - 1], NACK_VALUE); + i2c_master_stop(cmd); + err = _check_i2c_error(i2c_master_cmd_begin(smbus_info->i2c_port, cmd, smbus_info->timeout)); + i2c_cmd_link_delete(cmd); + + if (err == ESP_OK) + { + *len = slave_len; + } + else + { + *len = 0; + } + } + return err; +} + +esp_err_t smbus_i2c_write_block(const smbus_info_t * smbus_info, uint8_t command, uint8_t * data, size_t len) +{ + // Protocol: [S | ADDR | Wr | As | COMMAND | As | (DATA | As){*len} | P] + return _write_bytes(smbus_info, command, data, len); +} + +esp_err_t smbus_i2c_read_block(const smbus_info_t * smbus_info, uint8_t command, uint8_t * data, size_t len) +{ + // Protocol: [S | ADDR | Wr | As | COMMAND | As | Sr | ADDR | Rd | As | (DATAs | A){*len-1} | DATAs | N | P] + return _read_bytes(smbus_info, command, data, len); +} diff --git a/main/components/esp32-smbus/smbus.h b/main/components/esp32-smbus/smbus.h new file mode 100644 index 0000000..10b6f3e --- /dev/null +++ b/main/components/esp32-smbus/smbus.h @@ -0,0 +1,210 @@ +/* + * MIT License + * + * Copyright (c) 2017 David Antliff + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * @file smbus.h + * @brief Interface definitions for the ESP32-compatible SMBus Protocol component. + * + * This component provides structures and functions that are useful for communicating + * with SMBus-compatible I2C slave devices. + */ + +#ifndef SMBUS_H +#define SMBUS_H + +#include "stdbool.h" +#include "driver/i2c.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +#define SMBUS_DEFAULT_TIMEOUT (1000 / portTICK_PERIOD_MS) ///< Default transaction timeout in ticks + /** + * @brief 7-bit or 10-bit I2C slave address. + */ + typedef uint16_t i2c_address_t; + + /** + * @brief Structure containing information related to the SMBus protocol. + */ + typedef struct + { + bool init; ///< True if struct has been initialised, otherwise false + i2c_port_t i2c_port; ///< ESP-IDF I2C port number + i2c_address_t address; ///< I2C address of slave device + portBASE_TYPE timeout; ///< Number of ticks until I2C operation timeout + } smbus_info_t; + + /** + * @brief Construct a new SMBus info instance. + * New instance should be initialised before calling other functions. + * @return Pointer to new device info instance, or NULL if it cannot be created. + */ + smbus_info_t *smbus_malloc(void); + + /** + * @brief Delete an existing SMBus info instance. + * @param[in,out] smbus_info Pointer to SMBus info instance that will be freed and set to NULL. + */ + void smbus_free(smbus_info_t **smbus_info); + + /** + * @brief Initialise a SMBus info instance with the specified I2C information. + * The I2C timeout defaults to approximately 1 second. + * @param[in] smbus_info Pointer to SMBus info instance. + * @param[in] i2c_port I2C port to associate with this SMBus instance. + * @param[in] address Address of I2C slave device. + */ + esp_err_t smbus_init(smbus_info_t *smbus_info, i2c_port_t i2c_port, i2c_address_t address); + + /** + * @brief Set the I2C timeout. + * I2C transactions that do not complete within this period are considered an error. + * @param[in] smbus_info Pointer to initialised SMBus info instance. + * @param[in] timeout Number of ticks to wait until the transaction is considered in error. + * @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred. + */ + esp_err_t smbus_set_timeout(smbus_info_t *smbus_info, portBASE_TYPE timeout); + + /** + * @brief Send a single bit to a slave device in the place of the read/write bit. + * May be used to simply turn a device function on or off, or enable or disable + * a low-power standby mode. There is no data sent or received. + * @param[in] smbus_info Pointer to initialised SMBus info instance. + * @param[in] bit Data bit to send. + * @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred. + */ + esp_err_t smbus_quick(const smbus_info_t *smbus_info, bool bit); + + /** + * @brief Send a single byte to a slave device. + * @param[in] smbus_info Pointer to initialised SMBus info instance. + * @param[in] data Data byte to send to slave. + * @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred. + */ + esp_err_t smbus_send_byte(const smbus_info_t *smbus_info, uint8_t data); + + /** + * @brief Receive a single byte from a slave device. + * @param[in] smbus_info Pointer to initialised SMBus info instance. + * @param[out] data Data byte received from slave. + * @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred. + */ + esp_err_t smbus_receive_byte(const smbus_info_t *smbus_info, uint8_t *data); + + /** + * @brief Write a single byte to a slave device with a command code. + * @param[in] smbus_info Pointer to initialised SMBus info instance. + * @param[in] command Device-specific command byte. + * @param[in] data Data byte to send to slave. + * @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred. + */ + esp_err_t smbus_write_byte(const smbus_info_t *smbus_info, uint8_t command, uint8_t data); + + /** + * @brief Write a single word (two bytes) to a slave device with a command code. + * The least significant byte is transmitted first. + * @param[in] smbus_info Pointer to initialised SMBus info instance. + * @param[in] command Device-specific command byte. + * @param[in] data Data word to send to slave. + * @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred. + */ + esp_err_t smbus_write_word(const smbus_info_t *smbus_info, uint8_t command, uint16_t data); + + /** + * @brief Read a single byte from a slave device with a command code. + * @param[in] smbus_info Pointer to initialised SMBus info instance. + * @param[in] command Device-specific command byte. + * @param[out] data Data byte received from slave. + * @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred. + */ + esp_err_t smbus_read_byte(const smbus_info_t *smbus_info, uint8_t command, uint8_t *data); + + /** + * @brief Read a single word (two bytes) from a slave device with a command code. + * The first byte received is the least significant byte. + * @param[in] smbus_info Pointer to initialised SMBus info instance. + * @param[in] command Device-specific command byte. + * @param[out] data Data byte received from slave. + * @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred. + */ + esp_err_t smbus_read_word(const smbus_info_t *smbus_info, uint8_t command, uint16_t *data); + + /** + * @brief Write up to 255 bytes to a slave device with a command code. + * This uses a byte count to negotiate the length of the transaction. + * The first byte in the data array is transmitted first. + * @param[in] smbus_info Pointer to initialised SMBus info instance. + * @param[in] command Device-specific command byte. + * @param[in] data Data bytes to send to slave. + * @param[in] len Number of bytes to send to slave. + * @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred. + */ + esp_err_t smbus_write_block(const smbus_info_t *smbus_info, uint8_t command, uint8_t *data, uint8_t len); + + /** + * @brief Read up to 255 bytes from a slave device with a command code. + * This uses a byte count to negotiate the length of the transaction. + * The first byte received is placed in the first array location. + * @param[in] smbus_info Pointer to initialised SMBus info instance. + * @param[in] command Device-specific command byte. + * @param[out] data Data bytes received from slave. + * @param[in/out] len Size of data array, and number of bytes actually received. + * @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred. + */ + esp_err_t smbus_read_block(const smbus_info_t *smbus_info, uint8_t command, uint8_t *data, uint8_t *len); + + /** + * @brief Write bytes to a slave device with a command code. + * No byte count is used - the transaction lasts as long as the master requires. + * The first byte in the data array is transmitted first. + * This operation is not defined by the SMBus specification. + * @param[in] smbus_info Pointer to initialised SMBus info instance. + * @param[in] command Device-specific command byte. + * @param[in] data Data bytes to send to slave. + * @param[in] len Number of bytes to send to slave. + * @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred. + */ + esp_err_t smbus_i2c_write_block(const smbus_info_t *smbus_info, uint8_t command, uint8_t *data, size_t len); + + /** + * @brief Read bytes from a slave device with a command code (combined format). + * No byte count is used - the transaction lasts as long as the master requires. + * The first byte received is placed in the first array location. + * This operation is not defined by the SMBus specification. + * @param[in] smbus_info Pointer to initialised SMBus info instance. + * @param[in] command Device-specific command byte. + * @param[out] data Data bytes received from slave. + * @param[in/out] len Size of data array. If the slave fails to provide sufficient bytes, ESP_ERR_TIMEOUT will be returned. + * @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred. + */ + esp_err_t smbus_i2c_read_block(const smbus_info_t *smbus_info, uint8_t command, uint8_t *data, size_t len); + +#ifdef __cplusplus +} +#endif + +#endif // SMBUS_H From 8ec38f772572f18dbc6d06eec07c5833585f174b Mon Sep 17 00:00:00 2001 From: Arthur Tavares Date: Sun, 26 Mar 2023 15:47:29 +0200 Subject: [PATCH 3/5] change components path --- main/app_main.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main/app_main.c b/main/app_main.c index e9ed6cb..ebcf5f2 100644 --- a/main/app_main.c +++ b/main/app_main.c @@ -37,8 +37,8 @@ #include "sdkconfig.h" #include "rom/uart.h" -#include "smbus.h" -#include "i2c-lcd1602.h" +#include "components/esp32-smbus/smbus.h" +#include "components/esp32-i2c-lcd1602/i2c-lcd1602.h" #define TAG "app" From 7554be1f4ef9147d5088017cef0f95fb54419254 Mon Sep 17 00:00:00 2001 From: Arthur Tavares Date: Sun, 26 Mar 2023 15:47:52 +0200 Subject: [PATCH 4/5] update idf_component_register --- main/CMakeLists.txt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index bfb7ef6..706c85b 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -1,5 +1,7 @@ set(COMPONENT_SRCDIRS ".") set(COMPONENT_ADD_INCLUDEDIRS ".") -register_component() - +idf_component_register(SRCS "app_main.c" + "components/esp32-smbus/smbus.c" + "components/esp32-i2c-lcd1602/i2c-lcd1602.c" + INCLUDE_DIRS ".") \ No newline at end of file From b472f3a1dcd1420b98b2c11bca5a45f95e8396dc Mon Sep 17 00:00:00 2001 From: Arthur Tavares Date: Sun, 26 Mar 2023 15:48:25 +0200 Subject: [PATCH 5/5] add .vscode --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 3fab7aa..8a83082 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ build/ sdkconfig sdkconfig.old +.vscode/