directory i2c_manager -> lvgl_i2c

This commit is contained in:
Rop Gonggrijp 2021-07-14 12:43:19 +02:00
parent 93125f3e00
commit befa5d0730
12 changed files with 12 additions and 9 deletions

96
lvgl_i2c/Kconfig Normal file
View file

@ -0,0 +1,96 @@
menu "I2C Port 0"
config I2C_MANAGER_0_ENABLED
bool "Enable I2C port 0"
if I2C_MANAGER_0_ENABLED
config I2C_MANAGER_0_SDA
int "SDA (GPIO pin)"
config I2C_MANAGER_0_SCL
int "SCL (GPIO pin)"
config I2C_MANAGER_0_FREQ_HZ
int "Frequency (Hz)"
default 400000
range 100000 5000000
help
The clock speed in Hz. Ranges from 100000 (100 kHz) to
5000000 (5 Mhz). I2C busses that involve external wires may
have to be slower, and the real maximum speed the bus will
support depends on the value of the pullup resistors and the
design of the overall circuit.
config I2C_MANAGER_0_TIMEOUT
int "R/W timeout (ms)"
default 20
range 10 1000
help
Timeout for I2C read and write operations. This does not
include the time waiting for a lock.
config I2C_MANAGER_0_LOCK_TIMEOUT
int "Stale lock override (ms)"
default 50
range 10 1000
help
Timeout at which point an operation waiting for its turn on
the port will assume that whatever set the lock has died and
overrides it. Set this somewhat larger than the previous
timeout.
config I2C_MANAGER_0_PULLUPS
bool "Use ESP32 built-in bus pull-up resistors"
help
The I2C bus needs resistors to make sure it's in a defined
state when nobody is talking. Many circuits have external
pullup resistors already and turning these on will increase
power consumption slightly and may limit the speed your bus
can attain. Try with these off first if you don't know.
endif
endmenu
menu "I2C Port 1"
config I2C_MANAGER_1_ENABLED
bool "Enable I2C port 1"
if I2C_MANAGER_1_ENABLED
config I2C_MANAGER_1_SDA
int "SDA (GPIO pin)"
config I2C_MANAGER_1_SCL
int "SCL (GPIO pin)"
config I2C_MANAGER_1_FREQ_HZ
int "Frequency (Hz)"
default 1000000
range 100000 5000000
help
The clock speed in Hz. Ranges from 100000 (100 kHz) to
5000000 (5 Mhz). I2C busses that involve external wires may
have to be slower, and the real maximum speed the bus will
support depends on the value of the pullup resistors and the
design of the overall circuit.
config I2C_MANAGER_1_TIMEOUT
int "R/W timeout (ms)"
default 20
range 10 1000
help
Timeout for I2C read and write operations. This does not
include the time waiting for a lock. Default should be fine.
config I2C_MANAGER_1_LOCK_TIMEOUT
int "Stale lock override (ms)"
default 50
help
Timeout at which point an operation waiting for its turn on
the port will assume that whatever set the lock has died and
overrides it. Set this somewhat larger than the previous
timeout. Default should be fine.
range 30 1000
config I2C_MANAGER_1_PULLUPS
bool "Use ESP32 built-in bus pull-up resistors"
help
The I2C bus needs resistors to make sure it's in a defined
state when nobody is talking. Many circuits have external
pullup resistors already and turning these on will increase
power consumption slightly and may limit the speed your bus
can attain. Try with these off first if you don't know.
endif
endmenu

90
lvgl_i2c/README.md Normal file
View file

@ -0,0 +1,90 @@
# I2C in `lvgl_esp32_drivers`
 
## Information for users
### I2C Manager support
`lvgl_esp32_drivers` comes with built-in I2C support by integrating I2C Manager, which is used in case you select a touch sensor or screen that uses the I2C bus. If you're just using LVGL you don't need to do anything special.
I2C Manager can help if you are in a situation where you want to avoid "bus conflicts" on the I2C bus. Suppose you use LVGL with a touch sensor that uses I2C, and your device also has another I2C device that needs to be read frequently, such as a 3D-accelerometer. ESP-IDF is not inherently "thread-safe". So if you read that from another task than the one LVGL uses to read the touch data, you need some kind of mechanism to keep these communications from interfering.
If you have (or write) a driver for that 3D-accelerometer that can use I2C Manager (or the I2C HAL and i2cdev abstraction layers that I2C Manager is compatible with) then put I2C Manager in your components directory by cloning the repository from below and in your main program do:
```c
#include "i2c_manager.h"
#include "lvgl_helpers.h"
[...]
lvgl_i2c_locking(i2c_manager_locking());
lv_init();
lvgl_driver_init();
```
The `lvgl_i2c_locking` part will cause the LVGL I2C driver to play nice with anything else that uses the I2C port(s) through I2C Manager.
Refer to the [I2C Manager GitHub repository](https://github.com/ropg/i2c_manager) for much more information.
 
## Information for LVGL driver developers
I2C support in the LVGL ESP drivers is provided exclusively by the files in this directory. Code from all over the project that was talking to the I2C hardware directly has been replaced by code that communicates through the functions provided in `i2c_manager.h`. I2C is handled by the I2C Manager that was built into `lvlg_esp32_drivers`, but the code would be the same if it was routed through I2C Manager as a separate component. If you are providing a driver, you need not worry about any of this.
 
### Using I2C in an LVGL driver, a multi-step guide
<dl>
<dt>Step 1</dt>
<dd>
The Kconfig entries for your driver only need to specify that you will be using I2C. This is done by adding `select LV_I2C_DISPLAY` or `select LV_I2C_TOUCH`.
</dd>
<dt>Step 2</dt>
<dd>
To use the I2C port in your code you would do something like:
```c
#include "lvgl_i2c/i2c_manager.h"
uint8_t data[2];
lvgl_i2c_read(CONFIG_LV_I2C_TOUCH_PORT, 0x23, 0x42, &data, 2);
```
This causes a touch driver to read two bytes at register `0x42` from the IC at address `0x23`. Replace `CONFIG_LV_I2C_TOUCH_PORT` by `CONFIG_LV_I2C_DISPLAY_PORT` when this is a display instead of a touch driver. `lvgl_i2c_write` works much the same way, except it writes the bytes from the buffer instead of reading them. _(It's ignored above but these functions return `esp_err_t` so you can check if the I2C communication worked.)_
</dd>
<dt>Step 3</dt>
<dd>
There is no step 3, you are already done.
</dd>
</dl>
### Meanwhile, behind the scenes ...
If any of the drivers selected by the user uses I2C, the menuconfig system will show an extra menu to select I2C port(s) for screen and/or touch sensor. An additional menu allows for setting of GPIO pins and bus speed of any port selected for use with LVGL. It's perfectly fine for a display and a touch sensor to be on the same I2C port or different ones.
&nbsp;
## More information
If you need more documentation, please refer to the [I2C Manager GitHub repository](https://github.com/ropg/i2c_manager) for more detailed information on how I2C manager works. There are features not in the simple example above, such as reads and writes without specifying a register, 16-bit registers, 10-bit I2C addressing and more.

368
lvgl_i2c/i2c_manager.c Normal file
View file

@ -0,0 +1,368 @@
/*
SPDX-License-Identifier: MIT
MIT License
Copyright (c) 2021 Rop Gonggrijp. Based on esp_i2c_helper by Mika Tuupola.
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.
*/
#include <stdint.h>
#include <stddef.h>
#include <esp_log.h>
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "freertos/task.h"
#include <driver/i2c.h>
#include "sdkconfig.h"
#include "i2c_manager.h"
#if defined __has_include
#if __has_include ("esp_idf_version.h")
#include "esp_idf_version.h"
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 3, 0)
#define HAS_CLK_FLAGS
#endif
#endif
#endif
static const char* TAG = I2C_TAG;
static SemaphoreHandle_t I2C_FN(_local_mutex)[2] = { NULL, NULL };
static SemaphoreHandle_t* I2C_FN(_mutex) = &I2C_FN(_local_mutex)[0];
static const uint8_t ACK_CHECK_EN = 1;
#if defined (I2C_NUM_0) && defined (CONFIG_I2C_MANAGER_0_ENABLED)
#define I2C_ZERO I2C_NUM_0
#if defined (CONFIG_I2C_MANAGER_0_PULLUPS)
#define I2C_MANAGER_0_PULLUPS true
#else
#define I2C_MANAGER_0_PULLUPS false
#endif
#define I2C_MANAGER_0_TIMEOUT ( CONFIG_I2C_MANAGER_0_TIMEOUT / portTICK_RATE_MS )
#define I2C_MANAGER_0_LOCK_TIMEOUT ( CONFIG_I2C_MANAGER_0_LOCK_TIMEOUT / portTICK_RATE_MS )
#endif
#if defined (I2C_NUM_1) && defined (CONFIG_I2C_MANAGER_1_ENABLED)
#define I2C_ONE I2C_NUM_1
#if defined (CONFIG_I2C_MANAGER_1_PULLUPS)
#define I2C_MANAGER_1_PULLUPS true
#else
#define I2C_MANAGER_1_PULLUPS false
#endif
#define I2C_MANAGER_1_TIMEOUT ( CONFIG_I2C_MANAGER_1_TIMEOUT / portTICK_RATE_MS )
#define I2C_MANAGER_1_LOCK_TIMEOUT ( CONFIG_I2C_MANAGER_1_LOCK_TIMEOUT / portTICK_RATE_MS )
#endif
#define ERROR_PORT(port, fail) { \
ESP_LOGE(TAG, "Invalid port or not configured for I2C Manager: %d", (int)port); \
return fail; \
}
#if defined(I2C_ZERO) && defined (I2C_ONE)
#define I2C_PORT_CHECK(port, fail) \
if (port != I2C_NUM_0 && port != I2C_NUM_1) ERROR_PORT(port, fail);
#else
#if defined(I2C_ZERO)
#define I2C_PORT_CHECK(port, fail) \
if (port != I2C_NUM_0) ERROR_PORT(port, fail);
#elif defined(I2C_ONE)
#define I2C_PORT_CHECK(port, fail) \
if (port != I2C_NUM_1) ERROR_PORT(port, fail);
#else
#define I2C_PORT_CHECK(port, fail) \
ERROR_PORT(port, fail);
#endif
#endif
static void i2c_send_address(i2c_cmd_handle_t cmd, uint16_t addr, i2c_rw_t rw) {
if (addr & I2C_ADDR_10) {
i2c_master_write_byte(cmd, 0xF0 | ((addr & 0x3FF) >> 7) | rw, ACK_CHECK_EN);
i2c_master_write_byte(cmd, addr & 0xFF, ACK_CHECK_EN);
} else {
i2c_master_write_byte(cmd, (addr << 1) | rw, ACK_CHECK_EN);
}
}
static void i2c_send_register(i2c_cmd_handle_t cmd, uint32_t reg) {
if (reg & I2C_REG_16) {
i2c_master_write_byte(cmd, (reg & 0xFF00) >> 8, ACK_CHECK_EN);
}
i2c_master_write_byte(cmd, reg & 0xFF, ACK_CHECK_EN);
}
esp_err_t I2C_FN(_init)(i2c_port_t port) {
I2C_PORT_CHECK(port, ESP_FAIL);
esp_err_t ret = ESP_OK;
if (I2C_FN(_mutex)[port] == 0) {
ESP_LOGI(TAG, "Starting I2C master at port %d.", (int)port);
I2C_FN(_mutex)[port] = xSemaphoreCreateMutex();
i2c_config_t conf = {0};
#ifdef HAS_CLK_FLAGS
conf.clk_flags = 0;
#endif
#if defined (I2C_ZERO)
if (port == I2C_NUM_0) {
conf.sda_io_num = CONFIG_I2C_MANAGER_0_SDA;
conf.scl_io_num = CONFIG_I2C_MANAGER_0_SCL;
conf.sda_pullup_en = I2C_MANAGER_0_PULLUPS ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE;
conf.scl_pullup_en = conf.sda_pullup_en;
conf.master.clk_speed = CONFIG_I2C_MANAGER_0_FREQ_HZ;
}
#endif
#if defined (I2C_ONE)
if (port == I2C_NUM_1) {
conf.sda_io_num = CONFIG_I2C_MANAGER_1_SDA;
conf.scl_io_num = CONFIG_I2C_MANAGER_1_SCL;
conf.sda_pullup_en = I2C_MANAGER_1_PULLUPS ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE;
conf.scl_pullup_en = conf.sda_pullup_en;
conf.master.clk_speed = CONFIG_I2C_MANAGER_1_FREQ_HZ;
}
#endif
conf.mode = I2C_MODE_MASTER;
ret = i2c_param_config(port, &conf);
ret |= i2c_driver_install(port, conf.mode, 0, 0, 0);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to initialise I2C port %d.", (int)port);
ESP_LOGW(TAG, "If it was already open, we'll use it with whatever settings were used "
"to open it. See I2C Manager README for details.");
} else {
ESP_LOGI(TAG, "Initialised port %d (SDA: %d, SCL: %d, speed: %d Hz.)",
port, conf.sda_io_num, conf.scl_io_num, conf.master.clk_speed);
}
}
return ret;
}
esp_err_t I2C_FN(_read)(i2c_port_t port, uint16_t addr, uint32_t reg, uint8_t *buffer, uint16_t size) {
I2C_PORT_CHECK(port, ESP_FAIL);
esp_err_t result;
// May seem weird, but init starts with a check if it's needed, no need for that check twice.
I2C_FN(_init)(port);
ESP_LOGV(TAG, "Reading port %d, addr 0x%03x, reg 0x%04x", port, addr, reg);
TickType_t timeout = 0;
#if defined (I2C_ZERO)
if (port == I2C_NUM_0) {
timeout = I2C_MANAGER_0_TIMEOUT;
}
#endif
#if defined (I2C_ONE)
if (port == I2C_NUM_1) {
timeout = I2C_MANAGER_1_TIMEOUT;
}
#endif
if (I2C_FN(_lock)((int)port) == ESP_OK) {
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
if (!(reg & I2C_NO_REG)) {
/* When reading specific register set the addr pointer first. */
i2c_master_start(cmd);
i2c_send_address(cmd, addr, I2C_MASTER_WRITE);
i2c_send_register(cmd, reg);
}
/* Read size bytes from the current pointer. */
i2c_master_start(cmd);
i2c_send_address(cmd, addr, I2C_MASTER_READ);
i2c_master_read(cmd, buffer, size, I2C_MASTER_LAST_NACK);
i2c_master_stop(cmd);
result = i2c_master_cmd_begin(port, cmd, timeout);
i2c_cmd_link_delete(cmd);
I2C_FN(_unlock)((int)port);
} else {
ESP_LOGE(TAG, "Lock could not be obtained for port %d.", (int)port);
return ESP_ERR_TIMEOUT;
}
if (result != ESP_OK) {
ESP_LOGW(TAG, "Error: %d", result);
}
ESP_LOG_BUFFER_HEX_LEVEL(TAG, buffer, size, ESP_LOG_VERBOSE);
return result;
}
esp_err_t I2C_FN(_write)(i2c_port_t port, uint16_t addr, uint32_t reg, const uint8_t *buffer, uint16_t size) {
I2C_PORT_CHECK(port, ESP_FAIL);
esp_err_t result;
// May seem weird, but init starts with a check if it's needed, no need for that check twice.
I2C_FN(_init)(port);
ESP_LOGV(TAG, "Writing port %d, addr 0x%03x, reg 0x%04x", port, addr, reg);
TickType_t timeout = 0;
#if defined (I2C_ZERO)
if (port == I2C_NUM_0) {
timeout = (CONFIG_I2C_MANAGER_0_TIMEOUT) / portTICK_RATE_MS;
}
#endif
#if defined (I2C_ONE)
if (port == I2C_NUM_1) {
timeout = (CONFIG_I2C_MANAGER_1_TIMEOUT) / portTICK_RATE_MS;
}
#endif
if (I2C_FN(_lock)((int)port) == ESP_OK) {
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_send_address(cmd, addr, I2C_MASTER_WRITE);
if (!(reg & I2C_NO_REG)) {
i2c_send_register(cmd, reg);
}
i2c_master_write(cmd, (uint8_t *)buffer, size, ACK_CHECK_EN);
i2c_master_stop(cmd);
result = i2c_master_cmd_begin( port, cmd, timeout);
i2c_cmd_link_delete(cmd);
I2C_FN(_unlock)((int)port);
} else {
ESP_LOGE(TAG, "Lock could not be obtained for port %d.", (int)port);
return ESP_ERR_TIMEOUT;
}
if (result != ESP_OK) {
ESP_LOGW(TAG, "Error: %d", result);
}
ESP_LOG_BUFFER_HEX_LEVEL(TAG, buffer, size, ESP_LOG_VERBOSE);
return result;
}
esp_err_t I2C_FN(_close)(i2c_port_t port) {
I2C_PORT_CHECK(port, ESP_FAIL);
vSemaphoreDelete(I2C_FN(_mutex)[port]);
I2C_FN(_mutex)[port] = NULL;
ESP_LOGI(TAG, "Closing I2C master at port %d", port);
return i2c_driver_delete(port);
}
esp_err_t I2C_FN(_lock)(i2c_port_t port) {
I2C_PORT_CHECK(port, ESP_FAIL);
ESP_LOGV(TAG, "Mutex lock set for %d.", (int)port);
TickType_t timeout;
#if defined (I2C_ZERO)
if (port == I2C_NUM_0) {
timeout = (CONFIG_I2C_MANAGER_0_LOCK_TIMEOUT) / portTICK_RATE_MS;
}
#endif
#if defined (I2C_ONE)
if (port == I2C_NUM_1) {
timeout = (CONFIG_I2C_MANAGER_1_LOCK_TIMEOUT) / portTICK_RATE_MS;
}
#endif
if (xSemaphoreTake(I2C_FN(_mutex)[port], timeout) == pdTRUE) {
return ESP_OK;
} else {
ESP_LOGE(TAG, "Removing stale mutex lock from port %d.", (int)port);
I2C_FN(_force_unlock)(port);
return (xSemaphoreTake(I2C_FN(_mutex)[port], timeout) == pdTRUE ? ESP_OK : ESP_FAIL);
}
}
esp_err_t I2C_FN(_unlock)(i2c_port_t port) {
I2C_PORT_CHECK(port, ESP_FAIL);
ESP_LOGV(TAG, "Mutex lock removed for %d.", (int)port);
return (xSemaphoreGive(I2C_FN(_mutex)[port]) == pdTRUE) ? ESP_OK : ESP_FAIL;
}
esp_err_t I2C_FN(_force_unlock)(i2c_port_t port) {
I2C_PORT_CHECK(port, ESP_FAIL);
if (I2C_FN(_mutex)[port]) {
vSemaphoreDelete(I2C_FN(_mutex)[port]);
}
I2C_FN(_mutex)[port] = xSemaphoreCreateMutex();
return ESP_OK;
}
#ifdef I2C_OEM
void I2C_FN(_locking)(void* leader) {
if (leader) {
ESP_LOGI(TAG, "Now following I2C Manager for locking");
I2C_FN(_mutex) = (SemaphoreHandle_t*)leader;
}
}
#else
void* i2c_manager_locking() {
return (void*)i2c_manager_mutex;
}
int32_t i2c_hal_read(void *handle, uint8_t address, uint8_t reg, uint8_t *buffer, uint16_t size) {
return i2c_manager_read(*(i2c_port_t*)handle, address, reg, buffer, size);
}
int32_t i2c_hal_write(void *handle, uint8_t address, uint8_t reg, const uint8_t *buffer, uint16_t size) {
return i2c_manager_write(*(i2c_port_t*)handle, address, reg, buffer, size);
}
static i2c_port_t port_zero = (i2c_port_t)0;
static i2c_port_t port_one = (i2c_port_t)1;
static i2c_hal_t _i2c_hal[2] = {
{&i2c_hal_read, &i2c_hal_write, &port_zero},
{&i2c_hal_read, &i2c_hal_write, &port_one}
};
void* i2c_hal(i2c_port_t port) {
I2C_PORT_CHECK(port, NULL);
return (void*)&_i2c_hal[port];
}
#endif

76
lvgl_i2c/i2c_manager.h Normal file
View file

@ -0,0 +1,76 @@
#ifndef _I2C_MANAGER_H
#define _I2C_MANAGER_H
#ifdef __cplusplus
extern "C" {
#endif
/*
If you copy the i2c_manager files to your own component instead of
depending on i2c_manager, you MUST uncomment the define below
and put in some short string that identifies your component (such
as 'xyz'). This will cause i2c_manager to create functions named
xyz_i2c_* instead of i2c_manager_*. See README.md for details.
*/
#define I2C_OEM lvgl
// Only here to get the I2C_NUM_0 and I2C_NUM_1 defines.
#include <driver/i2c.h>
#define CONCATX(A, B) A ## B
#define CONCAT(A, B) CONCATX(A, B)
#define STR_LITERAL(s) # s
#define STR_EXPAND(s) STR_LITERAL(s)
#define STR_QUOTE(s) STR_EXPAND(STR_EXPAND(s))
#ifdef I2C_OEM
#define I2C_NAME_PREFIX CONCAT(I2C_OEM, _i2c)
#else
#define I2C_NAME_PREFIX i2c_manager
#endif
#define I2C_TAG STR_EXPAND(I2C_NAME_PREFIX)
#define I2C_FN(s) CONCAT(I2C_NAME_PREFIX, s)
#define I2C_ADDR_10 ( 1 << 15 )
#define I2C_REG_16 ( 1 << 31 )
#define I2C_NO_REG ( 1 << 30 )
esp_err_t I2C_FN(_init)(i2c_port_t port);
esp_err_t I2C_FN(_read)(i2c_port_t port, uint16_t addr, uint32_t reg, uint8_t *buffer, uint16_t size);
esp_err_t I2C_FN(_write)(i2c_port_t port, uint16_t addr, uint32_t reg, const uint8_t *buffer, uint16_t size);
esp_err_t I2C_FN(_close)(i2c_port_t port);
esp_err_t I2C_FN(_lock)(i2c_port_t port);
esp_err_t I2C_FN(_unlock)(i2c_port_t port);
esp_err_t I2C_FN(_force_unlock)(i2c_port_t port);
#ifdef I2C_OEM
void I2C_FN(_locking)(void* leader);
#else
void* i2c_manager_locking();
typedef struct {
int32_t (* read)(void *handle, uint8_t address, uint8_t reg, uint8_t *buffer, uint16_t size);
int32_t (* write)(void *handle, uint8_t address, uint8_t reg, const uint8_t *buffer, uint16_t size);
void *handle;
} i2c_hal_t;
void* i2c_hal(i2c_port_t port);
#endif
#ifdef __cplusplus
}
#endif
#endif