125 changed files with 32129 additions and 5422 deletions
@ -0,0 +1,2 @@ |
|||
idf_component_register(SRC_DIRS "." |
|||
INCLUDE_DIRS "include" ) |
@ -0,0 +1,19 @@ |
|||
menu "Bus Options" |
|||
|
|||
menu "I2C Bus Options" |
|||
config I2C_BUS_DYNAMIC_CONFIG |
|||
bool "enable dynamic configuration" |
|||
default y |
|||
help |
|||
If enable, i2c_bus will dynamically check configs and re-install i2c driver before each transfer, |
|||
hence multiple devices with different configs on a single bus can be supported. |
|||
|
|||
config I2C_MS_TO_WAIT |
|||
int "mutex block time" |
|||
default 200 |
|||
range 50 5000 |
|||
help |
|||
task block time when try to take the bus, unit:milliseconds |
|||
endmenu |
|||
|
|||
endmenu |
@ -0,0 +1,7 @@ |
|||
#
|
|||
# "main" pseudo-component makefile.
|
|||
#
|
|||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
|||
|
|||
COMPONENT_ADD_INCLUDEDIRS := include |
|||
COMPONENT_SRCDIRS := . |
@ -0,0 +1,487 @@ |
|||
// Copyright 2020-2021 Espressif Systems (Shanghai) PTE LTD
|
|||
//
|
|||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
// you may not use this file except in compliance with the License.
|
|||
// You may obtain a copy of the License at
|
|||
|
|||
// http://www.apache.org/licenses/LICENSE-2.0
|
|||
//
|
|||
// Unless required by applicable law or agreed to in writing, software
|
|||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
// See the License for the specific language governing permissions and
|
|||
// limitations under the License.
|
|||
#include <stdio.h> |
|||
#include <string.h> |
|||
|
|||
#include "freertos/FreeRTOS.h" |
|||
#include "freertos/semphr.h" |
|||
|
|||
#include "esp_log.h" |
|||
#include "i2c_bus.h" |
|||
|
|||
#define I2C_ACK_CHECK_EN 0x1 /*!< I2C master will check ack from slave*/ |
|||
#define I2C_ACK_CHECK_DIS 0x0 /*!< I2C master will not check ack from slave */ |
|||
#define I2C_BUS_FLG_DEFAULT (0) |
|||
#define I2C_BUS_MASTER_BUF_LEN (0) |
|||
#define I2C_BUS_MS_TO_WAIT CONFIG_I2C_MS_TO_WAIT |
|||
#define I2C_BUS_TICKS_TO_WAIT (I2C_BUS_MS_TO_WAIT/portTICK_RATE_MS) |
|||
#define I2C_BUS_MUTEX_TICKS_TO_WAIT (I2C_BUS_MS_TO_WAIT/portTICK_RATE_MS) |
|||
|
|||
typedef struct { |
|||
i2c_port_t i2c_port; /*!<I2C port number */ |
|||
bool is_init; /*if bus is initialized*/ |
|||
i2c_config_t conf_active; /*!<I2C active configuration */ |
|||
SemaphoreHandle_t mutex; /* mutex to achive thread-safe*/ |
|||
int32_t ref_counter; /*reference count*/ |
|||
} i2c_bus_t; |
|||
|
|||
typedef struct { |
|||
uint8_t dev_addr; /*device address*/ |
|||
i2c_config_t conf; /*!<I2C active configuration */ |
|||
i2c_bus_t *i2c_bus; /*!<I2C bus*/ |
|||
} i2c_bus_device_t; |
|||
|
|||
static const char *TAG = "i2c_bus"; |
|||
static i2c_bus_t s_i2c_bus[I2C_NUM_MAX]; |
|||
|
|||
#define I2C_BUS_CHECK(a, str, ret) if(!(a)) { \ |
|||
ESP_LOGE(TAG,"%s:%d (%s):%s", __FILE__, __LINE__, __FUNCTION__, str); \ |
|||
return (ret); \ |
|||
} |
|||
|
|||
#define I2C_BUS_CHECK_GOTO(a, str, lable) if(!(a)) { \ |
|||
ESP_LOGE(TAG,"%s:%d (%s):%s", __FILE__, __LINE__, __FUNCTION__, str); \ |
|||
goto lable; \ |
|||
} |
|||
|
|||
#define I2C_BUS_INIT_CHECK(is_init, ret) if(!is_init) { \ |
|||
ESP_LOGE(TAG,"%s:%d (%s):i2c_bus has not inited", __FILE__, __LINE__, __FUNCTION__); \ |
|||
return (ret); \ |
|||
} |
|||
|
|||
#define I2C_BUS_MUTEX_TAKE(mutex, ret) if (!xSemaphoreTake(mutex, I2C_BUS_MUTEX_TICKS_TO_WAIT)) { \ |
|||
ESP_LOGE(TAG, "i2c_bus take mutex timeout, max wait = %d ms", I2C_BUS_MUTEX_TICKS_TO_WAIT); \ |
|||
return (ret); \ |
|||
} |
|||
|
|||
#define I2C_BUS_MUTEX_TAKE_MAX_DELAY(mutex, ret) if (!xSemaphoreTake(mutex, portMAX_DELAY)) { \ |
|||
ESP_LOGE(TAG, "i2c_bus take mutex timeout, max wait = %d ms", portMAX_DELAY); \ |
|||
return (ret); \ |
|||
} |
|||
|
|||
#define I2C_BUS_MUTEX_GIVE(mutex, ret) if (!xSemaphoreGive(mutex)) { \ |
|||
ESP_LOGE(TAG, "i2c_bus give mutex failed"); \ |
|||
return (ret); \ |
|||
} |
|||
|
|||
static esp_err_t i2c_driver_reinit(i2c_port_t port, const i2c_config_t *conf); |
|||
static esp_err_t i2c_driver_deinit(i2c_port_t port); |
|||
static esp_err_t i2c_bus_write_reg8(i2c_bus_device_handle_t dev_handle, uint8_t mem_address, size_t data_len, const uint8_t *data); |
|||
static esp_err_t i2c_bus_read_reg8(i2c_bus_device_handle_t dev_handle, uint8_t mem_address, size_t data_len, uint8_t *data); |
|||
inline static bool i2c_config_compare(i2c_port_t port, const i2c_config_t *conf); |
|||
/**************************************** Public Functions (Application level)*********************************************/ |
|||
|
|||
i2c_bus_handle_t i2c_bus_create(i2c_port_t port, const i2c_config_t *conf) |
|||
{ |
|||
I2C_BUS_CHECK(port < I2C_NUM_MAX, "I2C port error", NULL); |
|||
I2C_BUS_CHECK(conf != NULL, "pointer = NULL error", NULL); |
|||
I2C_BUS_CHECK(conf->mode == I2C_MODE_MASTER, "i2c_bus only supports master mode", NULL); |
|||
|
|||
if (s_i2c_bus[port].is_init) { |
|||
/**if i2c_bus has been inited and configs not changed, return the handle directly**/ |
|||
if (i2c_config_compare(port, conf)) { |
|||
ESP_LOGW(TAG, "i2c%d has been inited, return handle directly, ref_counter=%d", port, s_i2c_bus[port].ref_counter); |
|||
return (i2c_bus_handle_t)&s_i2c_bus[port]; |
|||
} |
|||
} else { |
|||
s_i2c_bus[port].mutex = xSemaphoreCreateMutex(); |
|||
I2C_BUS_CHECK(s_i2c_bus[port].mutex != NULL, "i2c_bus xSemaphoreCreateMutex failed", NULL); |
|||
s_i2c_bus[port].ref_counter = 0; |
|||
} |
|||
|
|||
esp_err_t ret = i2c_driver_reinit(port, conf); |
|||
I2C_BUS_CHECK(ret == ESP_OK, "init error", NULL); |
|||
s_i2c_bus[port].conf_active = *conf; |
|||
s_i2c_bus[port].i2c_port = port; |
|||
return (i2c_bus_handle_t)&s_i2c_bus[port]; |
|||
} |
|||
|
|||
esp_err_t i2c_bus_delete(i2c_bus_handle_t *p_bus) |
|||
{ |
|||
I2C_BUS_CHECK(p_bus != NULL && *p_bus != NULL, "pointer = NULL error", ESP_ERR_INVALID_ARG); |
|||
i2c_bus_t *i2c_bus = (i2c_bus_t *)(*p_bus); |
|||
I2C_BUS_INIT_CHECK(i2c_bus->is_init, ESP_FAIL); |
|||
I2C_BUS_MUTEX_TAKE_MAX_DELAY(i2c_bus->mutex, ESP_ERR_TIMEOUT); |
|||
|
|||
/** if ref_counter == 0, de-init the bus**/ |
|||
if ((i2c_bus->ref_counter) > 0) { |
|||
ESP_LOGW(TAG, "i2c%d is also handled by others ref_counter=%u, won't be de-inited", i2c_bus->i2c_port, i2c_bus->ref_counter); |
|||
return ESP_OK; |
|||
} |
|||
|
|||
esp_err_t ret = i2c_driver_deinit(i2c_bus->i2c_port); |
|||
I2C_BUS_CHECK(ret == ESP_OK, "deinit error", ret); |
|||
vSemaphoreDelete(i2c_bus->mutex); |
|||
*p_bus = NULL; |
|||
return ESP_OK; |
|||
} |
|||
|
|||
uint8_t i2c_bus_scan(i2c_bus_handle_t bus_handle, uint8_t *buf, uint8_t num) |
|||
{ |
|||
I2C_BUS_CHECK(bus_handle != NULL, "Handle error", 0); |
|||
i2c_bus_t *i2c_bus = (i2c_bus_t *)bus_handle; |
|||
I2C_BUS_INIT_CHECK(i2c_bus->is_init, 0); |
|||
uint8_t device_count = 0; |
|||
I2C_BUS_MUTEX_TAKE_MAX_DELAY(i2c_bus->mutex, 0); |
|||
for (uint8_t dev_address = 1; dev_address < 127; dev_address++) { |
|||
i2c_cmd_handle_t cmd = i2c_cmd_link_create(); |
|||
i2c_master_start(cmd); |
|||
i2c_master_write_byte(cmd, (dev_address << 1) | I2C_MASTER_WRITE, I2C_ACK_CHECK_EN); |
|||
i2c_master_stop(cmd); |
|||
esp_err_t ret = i2c_master_cmd_begin(i2c_bus->i2c_port, cmd, I2C_BUS_TICKS_TO_WAIT); |
|||
|
|||
if (ret == ESP_OK) { |
|||
ESP_LOGI(TAG, "found i2c device address = 0x%02x", dev_address); |
|||
if (buf != NULL && device_count < num) { |
|||
*(buf + device_count) = dev_address; |
|||
} |
|||
device_count++; |
|||
} |
|||
|
|||
i2c_cmd_link_delete(cmd); |
|||
} |
|||
I2C_BUS_MUTEX_GIVE(i2c_bus->mutex, 0); |
|||
return device_count; |
|||
} |
|||
|
|||
uint32_t i2c_bus_get_current_clk_speed(i2c_bus_handle_t bus_handle) |
|||
{ |
|||
I2C_BUS_CHECK(bus_handle != NULL, "Null Bus Handle", 0); |
|||
i2c_bus_t *i2c_bus = (i2c_bus_t *)bus_handle; |
|||
I2C_BUS_INIT_CHECK(i2c_bus->is_init, 0); |
|||
return i2c_bus->conf_active.master.clk_speed; |
|||
} |
|||
|
|||
uint8_t i2c_bus_get_created_device_num(i2c_bus_handle_t bus_handle) |
|||
{ |
|||
I2C_BUS_CHECK(bus_handle != NULL, "Null Bus Handle", 0); |
|||
i2c_bus_t *i2c_bus = (i2c_bus_t *)bus_handle; |
|||
I2C_BUS_INIT_CHECK(i2c_bus->is_init, 0); |
|||
return i2c_bus->ref_counter; |
|||
} |
|||
|
|||
i2c_bus_device_handle_t i2c_bus_device_create(i2c_bus_handle_t bus_handle, uint8_t dev_addr, uint32_t clk_speed) |
|||
{ |
|||
I2C_BUS_CHECK(bus_handle != NULL, "Null Bus Handle", NULL); |
|||
I2C_BUS_CHECK(clk_speed <= 400000, "clk_speed must <= 400000", NULL); |
|||
i2c_bus_t *i2c_bus = (i2c_bus_t *)bus_handle; |
|||
I2C_BUS_INIT_CHECK(i2c_bus->is_init, NULL); |
|||
i2c_bus_device_t *i2c_device = calloc(1, sizeof(i2c_bus_device_t)); |
|||
I2C_BUS_CHECK(i2c_device != NULL, "calloc memory failed", NULL); |
|||
I2C_BUS_MUTEX_TAKE_MAX_DELAY(i2c_bus->mutex, NULL); |
|||
i2c_device->dev_addr = dev_addr; |
|||
i2c_device->conf = i2c_bus->conf_active; |
|||
|
|||
/*if clk_speed == 0, current active clock speed will be used, else set a specified value*/ |
|||
if (clk_speed != 0) { |
|||
i2c_device->conf.master.clk_speed = clk_speed; |
|||
} |
|||
|
|||
i2c_device->i2c_bus = i2c_bus; |
|||
i2c_bus->ref_counter++; |
|||
I2C_BUS_MUTEX_GIVE(i2c_bus->mutex, NULL); |
|||
return (i2c_bus_device_handle_t)i2c_device; |
|||
} |
|||
|
|||
esp_err_t i2c_bus_device_delete(i2c_bus_device_handle_t *p_dev_handle) |
|||
{ |
|||
I2C_BUS_CHECK(p_dev_handle != NULL && *p_dev_handle != NULL, "Null Device Handle", ESP_ERR_INVALID_ARG); |
|||
i2c_bus_device_t *i2c_device = (i2c_bus_device_t *)(*p_dev_handle); |
|||
I2C_BUS_MUTEX_TAKE_MAX_DELAY(i2c_device->i2c_bus->mutex, ESP_ERR_TIMEOUT); |
|||
i2c_device->i2c_bus->ref_counter--; |
|||
I2C_BUS_MUTEX_GIVE(i2c_device->i2c_bus->mutex, ESP_FAIL); |
|||
free(i2c_device); |
|||
*p_dev_handle = NULL; |
|||
return ESP_OK; |
|||
} |
|||
|
|||
uint8_t i2c_bus_device_get_address(i2c_bus_device_handle_t dev_handle) |
|||
{ |
|||
I2C_BUS_CHECK(dev_handle != NULL, "device handle error", NULL_I2C_DEV_ADDR); |
|||
i2c_bus_device_t *i2c_device = (i2c_bus_device_t *)dev_handle; |
|||
return i2c_device->dev_addr; |
|||
} |
|||
|
|||
esp_err_t i2c_bus_read_bytes(i2c_bus_device_handle_t dev_handle, uint8_t mem_address, size_t data_len, uint8_t *data) |
|||
{ |
|||
return i2c_bus_read_reg8(dev_handle, mem_address, data_len, data); |
|||
} |
|||
|
|||
esp_err_t i2c_bus_read_byte(i2c_bus_device_handle_t dev_handle, uint8_t mem_address, uint8_t *data) |
|||
{ |
|||
return i2c_bus_read_reg8(dev_handle, mem_address, 1, data); |
|||
} |
|||
|
|||
esp_err_t i2c_bus_read_bit(i2c_bus_device_handle_t dev_handle, uint8_t mem_address, uint8_t bit_num, uint8_t *data) |
|||
{ |
|||
uint8_t byte = 0; |
|||
esp_err_t ret = i2c_bus_read_reg8(dev_handle, mem_address, 1, &byte); |
|||
*data = byte & (1 << bit_num); |
|||
*data = (*data != 0) ? 1 : 0; |
|||
return ret; |
|||
} |
|||
|
|||
esp_err_t i2c_bus_read_bits(i2c_bus_device_handle_t dev_handle, uint8_t mem_address, uint8_t bit_start, uint8_t length, uint8_t *data) |
|||
{ |
|||
uint8_t byte = 0; |
|||
esp_err_t ret = i2c_bus_read_byte(dev_handle, mem_address, &byte); |
|||
|
|||
if (ret != ESP_OK) { |
|||
return ret; |
|||
} |
|||
|
|||
uint8_t mask = ((1 << length) - 1) << (bit_start - length + 1); |
|||
byte &= mask; |
|||
byte >>= (bit_start - length + 1); |
|||
*data = byte; |
|||
return ret; |
|||
} |
|||
|
|||
esp_err_t i2c_bus_write_byte(i2c_bus_device_handle_t dev_handle, uint8_t mem_address, uint8_t data) |
|||
{ |
|||
return i2c_bus_write_reg8(dev_handle, mem_address, 1, &data); |
|||
} |
|||
|
|||
esp_err_t i2c_bus_write_bytes(i2c_bus_device_handle_t dev_handle, uint8_t mem_address, size_t data_len, const uint8_t *data) |
|||
{ |
|||
return i2c_bus_write_reg8(dev_handle, mem_address, data_len, data); |
|||
} |
|||
|
|||
esp_err_t i2c_bus_write_bit(i2c_bus_device_handle_t dev_handle, uint8_t mem_address, uint8_t bit_num, uint8_t data) |
|||
{ |
|||
uint8_t byte = 0; |
|||
esp_err_t ret = i2c_bus_read_byte(dev_handle, mem_address, &byte); |
|||
|
|||
if (ret != ESP_OK) { |
|||
return ret; |
|||
} |
|||
|
|||
byte = (data != 0) ? (byte | (1 << bit_num)) : (byte & ~(1 << bit_num)); |
|||
return i2c_bus_write_byte(dev_handle, mem_address, byte); |
|||
} |
|||
|
|||
esp_err_t i2c_bus_write_bits(i2c_bus_device_handle_t dev_handle, uint8_t mem_address, uint8_t bit_start, uint8_t length, uint8_t data) |
|||
{ |
|||
uint8_t byte = 0; |
|||
esp_err_t ret = i2c_bus_read_byte(dev_handle, mem_address, &byte); |
|||
|
|||
if (ret != ESP_OK) { |
|||
return ret; |
|||
} |
|||
|
|||
uint8_t mask = ((1 << length) - 1) << (bit_start - length + 1); |
|||
data <<= (bit_start - length + 1); // shift data into correct position
|
|||
data &= mask; // zero all non-important bits in data
|
|||
byte &= ~(mask); // zero all important bits in existing byte
|
|||
byte |= data; // combine data with existing byte
|
|||
return i2c_bus_write_byte(dev_handle, mem_address, byte); |
|||
} |
|||
|
|||
/**
|
|||
* @brief I2C master send queued commands. |
|||
* This function will trigger sending all queued commands. |
|||
* The task will be blocked until all the commands have been sent out. |
|||
* If I2C_BUS_DYNAMIC_CONFIG enable, i2c_bus will dynamically check configs and re-install i2c driver before each transfer, |
|||
* hence multiple devices with different configs on a single bus can be supported. |
|||
* @note |
|||
* Only call this function in I2C master mode |
|||
* |
|||
* @param i2c_num I2C port number |
|||
* @param cmd_handle I2C command handler |
|||
* @param ticks_to_wait maximum wait ticks. |
|||
* @param conf pointer to I2C parameter settings |
|||
* @return esp_err_t |
|||
*/ |
|||
inline static esp_err_t i2c_master_cmd_begin_with_conf(i2c_port_t i2c_num, i2c_cmd_handle_t cmd_handle, TickType_t ticks_to_wait, const i2c_config_t *conf) |
|||
{ |
|||
esp_err_t ret; |
|||
#ifdef CONFIG_I2C_BUS_DYNAMIC_CONFIG |
|||
/*if configs changed, i2c driver will reinit with new configuration*/ |
|||
if (conf != NULL && false == i2c_config_compare(i2c_num, conf)) { |
|||
ret = i2c_driver_reinit(i2c_num, conf); |
|||
I2C_BUS_CHECK(ret == ESP_OK, "reinit error", ret); |
|||
s_i2c_bus[i2c_num].conf_active = *conf; |
|||
} |
|||
#endif |
|||
ret = i2c_master_cmd_begin(i2c_num, cmd_handle, ticks_to_wait); |
|||
return ret; |
|||
} |
|||
|
|||
/**************************************** Public Functions (Low level)*********************************************/ |
|||
|
|||
esp_err_t i2c_bus_cmd_begin(i2c_bus_device_handle_t dev_handle, i2c_cmd_handle_t cmd) |
|||
{ |
|||
I2C_BUS_CHECK(dev_handle != NULL, "device handle error", ESP_ERR_INVALID_ARG); |
|||
I2C_BUS_CHECK(cmd != NULL, "I2C command error", ESP_ERR_INVALID_ARG); |
|||
i2c_bus_device_t *i2c_device = (i2c_bus_device_t *)dev_handle; |
|||
I2C_BUS_INIT_CHECK(i2c_device->i2c_bus->is_init, ESP_ERR_INVALID_STATE); |
|||
I2C_BUS_MUTEX_TAKE(i2c_device->i2c_bus->mutex, ESP_ERR_TIMEOUT); |
|||
esp_err_t ret = i2c_master_cmd_begin_with_conf(i2c_device->i2c_bus->i2c_port, cmd, I2C_BUS_TICKS_TO_WAIT, &i2c_device->conf); |
|||
I2C_BUS_MUTEX_GIVE(i2c_device->i2c_bus->mutex, ESP_FAIL); |
|||
return ret; |
|||
} |
|||
|
|||
static esp_err_t i2c_bus_read_reg8(i2c_bus_device_handle_t dev_handle, uint8_t mem_address, size_t data_len, uint8_t *data) |
|||
{ |
|||
I2C_BUS_CHECK(dev_handle != NULL, "device handle error", ESP_ERR_INVALID_ARG); |
|||
I2C_BUS_CHECK(data != NULL, "data pointer error", ESP_ERR_INVALID_ARG); |
|||
i2c_bus_device_t *i2c_device = (i2c_bus_device_t *)dev_handle; |
|||
I2C_BUS_INIT_CHECK(i2c_device->i2c_bus->is_init, ESP_ERR_INVALID_STATE); |
|||
I2C_BUS_MUTEX_TAKE(i2c_device->i2c_bus->mutex, ESP_ERR_TIMEOUT); |
|||
i2c_cmd_handle_t cmd = i2c_cmd_link_create(); |
|||
|
|||
if (mem_address != NULL_I2C_MEM_ADDR) { |
|||
i2c_master_start(cmd); |
|||
i2c_master_write_byte(cmd, (i2c_device->dev_addr << 1) | I2C_MASTER_WRITE, I2C_ACK_CHECK_EN); |
|||
i2c_master_write_byte(cmd, mem_address, I2C_ACK_CHECK_EN); |
|||
} |
|||
|
|||
i2c_master_start(cmd); |
|||
i2c_master_write_byte(cmd, (i2c_device->dev_addr << 1) | I2C_MASTER_READ, I2C_ACK_CHECK_EN); |
|||
i2c_master_read(cmd, data, data_len, I2C_MASTER_LAST_NACK); |
|||
i2c_master_stop(cmd); |
|||
esp_err_t ret = i2c_master_cmd_begin_with_conf(i2c_device->i2c_bus->i2c_port, cmd, I2C_BUS_TICKS_TO_WAIT, &i2c_device->conf); |
|||
i2c_cmd_link_delete(cmd); |
|||
I2C_BUS_MUTEX_GIVE(i2c_device->i2c_bus->mutex, ESP_FAIL); |
|||
return ret; |
|||
} |
|||
|
|||
esp_err_t i2c_bus_read_reg16(i2c_bus_device_handle_t dev_handle, uint16_t mem_address, size_t data_len, uint8_t *data) |
|||
{ |
|||
I2C_BUS_CHECK(dev_handle != NULL, "device handle error", ESP_ERR_INVALID_ARG); |
|||
I2C_BUS_CHECK(data != NULL, "data pointer error", ESP_ERR_INVALID_ARG); |
|||
i2c_bus_device_t *i2c_device = (i2c_bus_device_t *)dev_handle; |
|||
I2C_BUS_INIT_CHECK(i2c_device->i2c_bus->is_init, ESP_ERR_INVALID_STATE); |
|||
uint8_t memAddress8[2]; |
|||
memAddress8[0] = (uint8_t)((mem_address >> 8) & 0x00FF); |
|||
memAddress8[1] = (uint8_t)(mem_address & 0x00FF); |
|||
I2C_BUS_MUTEX_TAKE(i2c_device->i2c_bus->mutex, ESP_ERR_TIMEOUT); |
|||
i2c_cmd_handle_t cmd = i2c_cmd_link_create(); |
|||
|
|||
if (mem_address != NULL_I2C_MEM_ADDR) { |
|||
i2c_master_start(cmd); |
|||
i2c_master_write_byte(cmd, (i2c_device->dev_addr << 1) | I2C_MASTER_WRITE, I2C_ACK_CHECK_EN); |
|||
i2c_master_write(cmd, memAddress8, 2, I2C_ACK_CHECK_EN); |
|||
} |
|||
|
|||
i2c_master_start(cmd); |
|||
i2c_master_write_byte(cmd, (i2c_device->dev_addr << 1) | I2C_MASTER_READ, I2C_ACK_CHECK_EN); |
|||
i2c_master_read(cmd, data, data_len, I2C_MASTER_LAST_NACK); |
|||
i2c_master_stop(cmd); |
|||
esp_err_t ret = i2c_master_cmd_begin_with_conf(i2c_device->i2c_bus->i2c_port, cmd, I2C_BUS_TICKS_TO_WAIT, &i2c_device->conf); |
|||
i2c_cmd_link_delete(cmd); |
|||
I2C_BUS_MUTEX_GIVE(i2c_device->i2c_bus->mutex, ESP_FAIL); |
|||
return ret; |
|||
} |
|||
|
|||
static esp_err_t i2c_bus_write_reg8(i2c_bus_device_handle_t dev_handle, uint8_t mem_address, size_t data_len, const uint8_t *data) |
|||
{ |
|||
I2C_BUS_CHECK(dev_handle != NULL, "device handle error", ESP_ERR_INVALID_ARG); |
|||
I2C_BUS_CHECK(data != NULL, "data pointer error", ESP_ERR_INVALID_ARG); |
|||
i2c_bus_device_t *i2c_device = (i2c_bus_device_t *)dev_handle; |
|||
I2C_BUS_INIT_CHECK(i2c_device->i2c_bus->is_init, ESP_ERR_INVALID_STATE); |
|||
I2C_BUS_MUTEX_TAKE(i2c_device->i2c_bus->mutex, ESP_ERR_TIMEOUT); |
|||
i2c_cmd_handle_t cmd = i2c_cmd_link_create(); |
|||
i2c_master_start(cmd); |
|||
i2c_master_write_byte(cmd, (i2c_device->dev_addr << 1) | I2C_MASTER_WRITE, I2C_ACK_CHECK_EN); |
|||
|
|||
if (mem_address != NULL_I2C_MEM_ADDR) { |
|||
i2c_master_write_byte(cmd, mem_address, I2C_ACK_CHECK_EN); |
|||
} |
|||
|
|||
i2c_master_write(cmd, (uint8_t *)data, data_len, I2C_ACK_CHECK_EN); |
|||
i2c_master_stop(cmd); |
|||
esp_err_t ret = i2c_master_cmd_begin_with_conf(i2c_device->i2c_bus->i2c_port, cmd, I2C_BUS_TICKS_TO_WAIT, &i2c_device->conf); |
|||
i2c_cmd_link_delete(cmd); |
|||
I2C_BUS_MUTEX_GIVE(i2c_device->i2c_bus->mutex, ESP_FAIL); |
|||
return ret; |
|||
} |
|||
|
|||
esp_err_t i2c_bus_write_reg16(i2c_bus_device_handle_t dev_handle, uint16_t mem_address, size_t data_len, const uint8_t *data) |
|||
{ |
|||
I2C_BUS_CHECK(dev_handle != NULL, "device handle error", ESP_ERR_INVALID_ARG); |
|||
I2C_BUS_CHECK(data != NULL, "data pointer error", ESP_ERR_INVALID_ARG); |
|||
i2c_bus_device_t *i2c_device = (i2c_bus_device_t *)dev_handle; |
|||
I2C_BUS_INIT_CHECK(i2c_device->i2c_bus->is_init, ESP_ERR_INVALID_STATE); |
|||
uint8_t memAddress8[2]; |
|||
memAddress8[0] = (uint8_t)((mem_address >> 8) & 0x00FF); |
|||
memAddress8[1] = (uint8_t)(mem_address & 0x00FF); |
|||
I2C_BUS_MUTEX_TAKE(i2c_device->i2c_bus->mutex, ESP_ERR_TIMEOUT); |
|||
i2c_cmd_handle_t cmd = i2c_cmd_link_create(); |
|||
i2c_master_start(cmd); |
|||
i2c_master_write_byte(cmd, (i2c_device->dev_addr << 1) | I2C_MASTER_WRITE, I2C_ACK_CHECK_EN); |
|||
|
|||
if (mem_address != NULL_I2C_MEM_ADDR) { |
|||
i2c_master_write(cmd, memAddress8, 2, I2C_ACK_CHECK_EN); |
|||
} |
|||
|
|||
i2c_master_write(cmd, (uint8_t *)data, data_len, I2C_ACK_CHECK_EN); |
|||
i2c_master_stop(cmd); |
|||
esp_err_t ret = i2c_master_cmd_begin_with_conf(i2c_device->i2c_bus->i2c_port, cmd, I2C_BUS_TICKS_TO_WAIT, &i2c_device->conf); |
|||
i2c_cmd_link_delete(cmd); |
|||
I2C_BUS_MUTEX_GIVE(i2c_device->i2c_bus->mutex, ESP_FAIL); |
|||
return ret; |
|||
} |
|||
|
|||
/**************************************** Private Functions*********************************************/ |
|||
static esp_err_t i2c_driver_reinit(i2c_port_t port, const i2c_config_t *conf) |
|||
{ |
|||
I2C_BUS_CHECK(port < I2C_NUM_MAX, "i2c port error", ESP_ERR_INVALID_ARG); |
|||
I2C_BUS_CHECK(conf != NULL, "pointer = NULL error", ESP_ERR_INVALID_ARG); |
|||
|
|||
if (s_i2c_bus[port].is_init) { |
|||
i2c_driver_delete(port); |
|||
s_i2c_bus[port].is_init = false; |
|||
ESP_LOGI(TAG, "i2c%d bus deinited", port); |
|||
} |
|||
|
|||
esp_err_t ret = i2c_param_config(port, conf); |
|||
I2C_BUS_CHECK(ret == ESP_OK, "i2c param config failed", ret); |
|||
ret = i2c_driver_install(port, conf->mode, I2C_BUS_MASTER_BUF_LEN, I2C_BUS_MASTER_BUF_LEN, I2C_BUS_FLG_DEFAULT); |
|||
I2C_BUS_CHECK(ret == ESP_OK, "i2c driver install failed", ret); |
|||
s_i2c_bus[port].is_init = true; |
|||
ESP_LOGI(TAG, "i2c%d bus inited", port); |
|||
return ESP_OK; |
|||
} |
|||
|
|||
static esp_err_t i2c_driver_deinit(i2c_port_t port) |
|||
{ |
|||
I2C_BUS_CHECK(port < I2C_NUM_MAX, "i2c port error", ESP_ERR_INVALID_ARG); |
|||
I2C_BUS_CHECK(s_i2c_bus[port].is_init == true, "i2c not inited", ESP_ERR_INVALID_STATE); |
|||
i2c_driver_delete(port); //always return ESP_OK
|
|||
s_i2c_bus[port].is_init = false; |
|||
ESP_LOGI(TAG,"i2c%d bus deinited",port); |
|||
return ESP_OK; |
|||
} |
|||
|
|||
/**
|
|||
* @brief compare with active i2c_bus configuration |
|||
* |
|||
* @param port choose which i2c_port's configuration will be compared |
|||
* @param conf new configuration |
|||
* @return true new configuration is equal to active configuration |
|||
* @return false new configuration is not equal to active configuration |
|||
*/ |
|||
inline static bool i2c_config_compare(i2c_port_t port, const i2c_config_t *conf) |
|||
{ |
|||
if (s_i2c_bus[port].conf_active.master.clk_speed == conf->master.clk_speed |
|||
&& s_i2c_bus[port].conf_active.sda_io_num == conf->sda_io_num |
|||
&& s_i2c_bus[port].conf_active.scl_io_num == conf->scl_io_num |
|||
&& s_i2c_bus[port].conf_active.scl_pullup_en == conf->scl_pullup_en |
|||
&& s_i2c_bus[port].conf_active.sda_pullup_en == conf->sda_pullup_en) { |
|||
return true; |
|||
} |
|||
|
|||
return false; |
|||
} |
@ -0,0 +1,596 @@ |
|||
// Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD
|
|||
//
|
|||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
// you may not use this file except in compliance with the License.
|
|||
// You may obtain a copy of the License at
|
|||
|
|||
// http://www.apache.org/licenses/LICENSE-2.0
|
|||
//
|
|||
// Unless required by applicable law or agreed to in writing, software
|
|||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
// See the License for the specific language governing permissions and
|
|||
// limitations under the License.
|
|||
|
|||
#include "sdkconfig.h" |
|||
#if CONFIG_IDF_TARGET_ESP32 |
|||
|
|||
#include <stdio.h> |
|||
#include <string.h> |
|||
#include "freertos/FreeRTOS.h" |
|||
#include "freertos/task.h" |
|||
#include "freertos/queue.h" |
|||
#include "esp_heap_caps.h" |
|||
#include "esp32/rom/lldesc.h" |
|||
#include "soc/dport_access.h" |
|||
#include "soc/dport_reg.h" |
|||
#include "soc/i2s_struct.h" |
|||
#include "hal/gpio_ll.h" |
|||
#include "esp_log.h" |
|||
#include "i2s_lcd_driver.h" |
|||
|
|||
static const char *TAG = "ESP32_I2S_LCD"; |
|||
|
|||
#define I2S_CHECK(a, str, ret) if (!(a)) { \ |
|||
ESP_LOGE(TAG,"%s:%d (%s):%s", __FILE__, __LINE__, __FUNCTION__, str); \ |
|||
return (ret); \ |
|||
} |
|||
|
|||
#define LCD_CAM_DMA_NODE_BUFFER_MAX_SIZE (4000) // 4-byte aligned
|
|||
#define LCD_DATA_MAX_WIDTH (24) /*!< Maximum width of LCD data bus */ |
|||
|
|||
typedef struct { |
|||
uint32_t dma_buffer_size; |
|||
uint32_t dma_half_buffer_size; |
|||
uint32_t dma_node_buffer_size; |
|||
uint32_t dma_node_cnt; |
|||
uint32_t dma_half_node_cnt; |
|||
lldesc_t *dma; |
|||
uint8_t *dma_buffer; |
|||
QueueHandle_t event_queue; |
|||
uint8_t width; |
|||
bool swap_data; |
|||
intr_handle_t lcd_cam_intr_handle; |
|||
i2s_dev_t *i2s_dev; |
|||
} i2s_lcd_obj_t; |
|||
|
|||
typedef struct { |
|||
void (*i2s_write_data_func)(i2s_lcd_obj_t *i2s_lcd_obj, uint8_t *data, size_t len); |
|||
int rs_io_num; |
|||
i2s_lcd_obj_t *i2s_lcd_obj; |
|||
SemaphoreHandle_t mutex; |
|||
} i2s_lcd_driver_t; |
|||
|
|||
static void IRAM_ATTR i2s_isr(void *arg) |
|||
{ |
|||
BaseType_t HPTaskAwoken = pdFALSE; |
|||
i2s_lcd_obj_t *i2s_lcd_obj = (i2s_lcd_obj_t *)arg; |
|||
i2s_dev_t *i2s_dev = i2s_lcd_obj->i2s_dev; |
|||
|
|||
typeof(i2s_dev->int_st) status = i2s_dev->int_st; |
|||
i2s_dev->int_clr.val = status.val; |
|||
if (status.val == 0) { |
|||
return; |
|||
} |
|||
|
|||
if (status.out_eof) { |
|||
xQueueSendFromISR(i2s_lcd_obj->event_queue, (void *)&status.val, &HPTaskAwoken); |
|||
} |
|||
|
|||
if (HPTaskAwoken == pdTRUE) { |
|||
portYIELD_FROM_ISR(); |
|||
} |
|||
} |
|||
|
|||
static void lcd_dma_set_int(i2s_lcd_obj_t *i2s_lcd_obj) |
|||
{ |
|||
// Generate a data DMA linked list
|
|||
for (int x = 0; x < i2s_lcd_obj->dma_node_cnt; x++) { |
|||
i2s_lcd_obj->dma[x].size = i2s_lcd_obj->dma_node_buffer_size; |
|||
i2s_lcd_obj->dma[x].length = i2s_lcd_obj->dma_node_buffer_size; |
|||
i2s_lcd_obj->dma[x].buf = (i2s_lcd_obj->dma_buffer + i2s_lcd_obj->dma_node_buffer_size * x); |
|||
i2s_lcd_obj->dma[x].eof = !((x + 1) % i2s_lcd_obj->dma_half_node_cnt); |
|||
i2s_lcd_obj->dma[x].empty = (uint32_t)&i2s_lcd_obj->dma[(x + 1) % i2s_lcd_obj->dma_node_cnt]; |
|||
} |
|||
i2s_lcd_obj->dma[i2s_lcd_obj->dma_half_node_cnt - 1].empty = (uint32_t)NULL; |
|||
i2s_lcd_obj->dma[i2s_lcd_obj->dma_node_cnt - 1].empty = (uint32_t)NULL; |
|||
} |
|||
|
|||
static void lcd_dma_set_left(i2s_lcd_obj_t *i2s_lcd_obj, int pos, size_t len) |
|||
{ |
|||
int end_pos = 0, size = 0; |
|||
// Processing data length is an integer multiple of i2s_lcd_obj->dma_node_buffer_size
|
|||
if (len % i2s_lcd_obj->dma_node_buffer_size) { |
|||
end_pos = (pos % 2) * i2s_lcd_obj->dma_half_node_cnt + len / i2s_lcd_obj->dma_node_buffer_size; |
|||
size = len % i2s_lcd_obj->dma_node_buffer_size; |
|||
} else { |
|||
end_pos = (pos % 2) * i2s_lcd_obj->dma_half_node_cnt + len / i2s_lcd_obj->dma_node_buffer_size - 1; |
|||
size = i2s_lcd_obj->dma_node_buffer_size; |
|||
} |
|||
// Process the tail node to make it a DMA tail
|
|||
i2s_lcd_obj->dma[end_pos].size = size; |
|||
i2s_lcd_obj->dma[end_pos].length = size; |
|||
i2s_lcd_obj->dma[end_pos].eof = 1; |
|||
i2s_lcd_obj->dma[end_pos].empty = (uint32_t)NULL; |
|||
} |
|||
|
|||
static void lcd_i2s_start(i2s_dev_t *i2s_dev, uint8_t fifo_mode, uint32_t addr, size_t len) |
|||
{ |
|||
while (!i2s_dev->state.tx_idle); |
|||
i2s_dev->fifo_conf.tx_fifo_mod = fifo_mode; |
|||
i2s_dev->conf.tx_start = 0; |
|||
i2s_dev->conf.tx_reset = 1; |
|||
i2s_dev->conf.tx_reset = 0; |
|||
i2s_dev->lc_conf.out_rst = 1; |
|||
i2s_dev->lc_conf.out_rst = 0; |
|||
i2s_dev->conf.tx_fifo_reset = 1; |
|||
i2s_dev->conf.tx_fifo_reset = 0; |
|||
i2s_dev->out_link.addr = addr; |
|||
i2s_dev->out_link.start = 1; |
|||
ets_delay_us(1); |
|||
i2s_dev->conf.tx_start = 1; |
|||
} |
|||
|
|||
static void i2s_write_8bit_data(i2s_lcd_obj_t *i2s_lcd_obj, uint8_t *data, size_t len) |
|||
{ |
|||
int event = 0; |
|||
int x = 0, y = 0, left = 0, cnt = 0; |
|||
if (len <= 0) { |
|||
ESP_LOGE(TAG, "wrong len!"); |
|||
return; |
|||
} |
|||
len = len * 2; |
|||
lcd_dma_set_int(i2s_lcd_obj); |
|||
uint8_t fifo_mode = 1; |
|||
// Start signal
|
|||
xQueueSend(i2s_lcd_obj->event_queue, &event, 0); |
|||
cnt = len / i2s_lcd_obj->dma_half_buffer_size; |
|||
// Process a complete piece of data, ping-pong operation
|
|||
for (x = 0; x < cnt; x++) { |
|||
uint8_t *out = (uint8_t *)i2s_lcd_obj->dma[(x % 2) * i2s_lcd_obj->dma_half_node_cnt].buf; |
|||
uint8_t *in = data; |
|||
if (!i2s_lcd_obj->swap_data) { // data will be swapped when fifo_mode=1, so negate the lcd.swap_data
|
|||
for (y = 0; y < i2s_lcd_obj->dma_half_buffer_size; y += 4) { |
|||
out[y + 3] = in[(y >> 1) + 0]; |
|||
out[y + 1] = in[(y >> 1) + 1]; |
|||
} |
|||
} else { |
|||
for (y = 0; y < i2s_lcd_obj->dma_half_buffer_size; y += 4) { |
|||
out[y + 1] = in[(y >> 1) + 0]; |
|||
out[y + 3] = in[(y >> 1) + 1]; |
|||
} |
|||
} |
|||
data += i2s_lcd_obj->dma_half_buffer_size >> 1; |
|||
xQueueReceive(i2s_lcd_obj->event_queue, (void *)&event, portMAX_DELAY); |
|||
lcd_i2s_start(i2s_lcd_obj->i2s_dev, fifo_mode, ((uint32_t)&i2s_lcd_obj->dma[(x % 2) * i2s_lcd_obj->dma_half_node_cnt]) & 0xfffff, i2s_lcd_obj->dma_half_buffer_size); |
|||
} |
|||
left = len % i2s_lcd_obj->dma_half_buffer_size; |
|||
// Process remaining incomplete segment data
|
|||
while (left) { |
|||
uint8_t *out = (uint8_t *)i2s_lcd_obj->dma[(x % 2) * i2s_lcd_obj->dma_half_node_cnt].buf; |
|||
uint8_t *in = data; |
|||
if (left > 2) { |
|||
cnt = left - left % 4; |
|||
left = left % 4; |
|||
data += cnt >> 1; |
|||
if (!i2s_lcd_obj->swap_data) { // data will be swapped when fifo_mode=1, so negate the lcd.swap_data
|
|||
for (y = 0; y < cnt; y += 4) { |
|||
out[y + 3] = in[(y >> 1) + 0]; |
|||
out[y + 1] = in[(y >> 1) + 1]; |
|||
} |
|||
} else { |
|||
for (y = 0; y < cnt; y += 4) { |
|||
out[y + 1] = in[(y >> 1) + 0]; |
|||
out[y + 3] = in[(y >> 1) + 1]; |
|||
} |
|||
} |
|||
} else { |
|||
cnt = 4; |
|||
left = 0; |
|||
fifo_mode = 3; |
|||
out[3] = in[0]; |
|||
} |
|||
// printf("[");
|
|||
// for (size_t i = 0; i < cnt; i++) {
|
|||
// printf("%02x, ", out[i]);
|
|||
// } printf("]\n");
|
|||
lcd_dma_set_left(i2s_lcd_obj, x, cnt); |
|||
xQueueReceive(i2s_lcd_obj->event_queue, (void *)&event, portMAX_DELAY); |
|||
lcd_i2s_start(i2s_lcd_obj->i2s_dev, fifo_mode, ((uint32_t)&i2s_lcd_obj->dma[(x % 2) * i2s_lcd_obj->dma_half_node_cnt]) & 0xfffff, cnt); |
|||
x++; |
|||
} |
|||
xQueueReceive(i2s_lcd_obj->event_queue, (void *)&event, portMAX_DELAY); |
|||
} |
|||
|
|||
static void i2s_write_16bit_data(i2s_lcd_obj_t *i2s_lcd_obj, uint8_t *data, size_t len) |
|||
{ |
|||
int event = 0; |
|||
int x = 0, y = 0, left = 0, cnt = 0; |
|||
if (len <= 0 || len % 2 != 0) { |
|||
ESP_LOGE(TAG, "wrong len!"); |
|||
return; |
|||
} |
|||
lcd_dma_set_int(i2s_lcd_obj); |
|||
uint8_t fifo_mode = 1; |
|||
// Start signal
|
|||
xQueueSend(i2s_lcd_obj->event_queue, &event, 0); |
|||
cnt = len / i2s_lcd_obj->dma_half_buffer_size; |
|||
// Process a complete piece of data, ping-pong operation
|
|||
for (x = 0; x < cnt; x++) { |
|||
uint8_t *out = (uint8_t *)i2s_lcd_obj->dma[(x % 2) * i2s_lcd_obj->dma_half_node_cnt].buf; |
|||
uint8_t *in = data; |
|||
if (i2s_lcd_obj->swap_data) { |
|||
for (y = 0; y < i2s_lcd_obj->dma_half_buffer_size; y += 4) { |
|||
out[y + 3] = in[y + 0]; |
|||
out[y + 2] = in[y + 1]; |
|||
out[y + 1] = in[y + 2]; |
|||
out[y + 0] = in[y + 3]; |
|||
} |
|||
} else { |
|||
for (y = 0; y < i2s_lcd_obj->dma_half_buffer_size; y += 4) { |
|||
out[y + 2] = in[y + 0]; |
|||
out[y + 3] = in[y + 1]; |
|||
out[y + 0] = in[y + 2]; |
|||
out[y + 1] = in[y + 3]; |
|||
} |
|||
} |
|||
data += i2s_lcd_obj->dma_half_buffer_size; |
|||
xQueueReceive(i2s_lcd_obj->event_queue, (void *)&event, portMAX_DELAY); |
|||
lcd_i2s_start(i2s_lcd_obj->i2s_dev, fifo_mode, ((uint32_t)&i2s_lcd_obj->dma[(x % 2) * i2s_lcd_obj->dma_half_node_cnt]) & 0xfffff, i2s_lcd_obj->dma_half_buffer_size); |
|||
} |
|||
left = len % i2s_lcd_obj->dma_half_buffer_size; |
|||
// Process remaining incomplete segment data
|
|||
while (left) { |
|||
uint8_t *out = (uint8_t *)i2s_lcd_obj->dma[(x % 2) * i2s_lcd_obj->dma_half_node_cnt].buf; |
|||
uint8_t *in = data; |
|||
if (left > 2) { |
|||
cnt = left - left % 4; |
|||
left = left % 4; |
|||
data += cnt; |
|||
if (i2s_lcd_obj->swap_data) { |
|||
for (y = 0; y < cnt; y += 4) { |
|||
out[y + 3] = in[y + 0]; |
|||
out[y + 2] = in[y + 1]; |
|||
out[y + 1] = in[y + 2]; |
|||
out[y + 0] = in[y + 3]; |
|||
} |
|||
} else { |
|||
for (y = 0; y < cnt; y += 4) { |
|||
out[y + 2] = in[y + 0]; |
|||
out[y + 3] = in[y + 1]; |
|||
out[y + 0] = in[y + 2]; |
|||
out[y + 1] = in[y + 3]; |
|||
} |
|||
} |
|||
} else { |
|||
cnt = 4; |
|||
left = 0; |
|||
fifo_mode = 3; |
|||
if (i2s_lcd_obj->swap_data) { |
|||
out[3] = in[0]; |
|||
out[2] = in[1]; |
|||
} else { |
|||
out[2] = in[0]; |
|||
out[3] = in[1]; |
|||
} |
|||
} |
|||
lcd_dma_set_left(i2s_lcd_obj, x, cnt); |
|||
xQueueReceive(i2s_lcd_obj->event_queue, (void *)&event, portMAX_DELAY); |
|||
lcd_i2s_start(i2s_lcd_obj->i2s_dev, fifo_mode, ((uint32_t)&i2s_lcd_obj->dma[(x % 2) * i2s_lcd_obj->dma_half_node_cnt]) & 0xfffff, cnt); |
|||
x++; |
|||
} |
|||
xQueueReceive(i2s_lcd_obj->event_queue, (void *)&event, portMAX_DELAY); |
|||
} |
|||
|
|||
static esp_err_t i2s_lcd_reg_config(i2s_dev_t *i2s_dev, uint16_t data_width, uint32_t clk_freq) |
|||
{ |
|||
// Configure the clock
|
|||
i2s_dev->clkm_conf.clkm_div_num = 2; // 160MHz / 2 = 80MHz
|
|||
i2s_dev->clkm_conf.clkm_div_b = 0; |
|||
i2s_dev->clkm_conf.clkm_div_a = 10; |
|||
i2s_dev->clkm_conf.clk_en = 1; |
|||
|
|||
i2s_dev->conf.val = 0; |
|||
i2s_dev->fifo_conf.val = 0; |
|||
i2s_dev->fifo_conf.dscr_en = 1; |
|||
|
|||
i2s_dev->conf2.lcd_en = 1; |
|||
i2s_dev->conf2.camera_en = 1; |
|||
|
|||
i2s_dev->lc_conf.ahbm_fifo_rst = 1; |
|||
i2s_dev->lc_conf.ahbm_fifo_rst = 0; |
|||
i2s_dev->lc_conf.ahbm_rst = 1; |
|||
i2s_dev->lc_conf.ahbm_rst = 0; |
|||
i2s_dev->lc_conf.check_owner = 0; |
|||
i2s_dev->lc_conf.out_loop_test = 0; |
|||
i2s_dev->lc_conf.out_auto_wrback = 0; |
|||
i2s_dev->lc_conf.out_data_burst_en = 1; |
|||
i2s_dev->lc_conf.out_no_restart_clr = 0; |
|||
i2s_dev->lc_conf.indscr_burst_en = 0; |
|||
i2s_dev->lc_conf.out_eof_mode = 1; |
|||
|
|||
i2s_dev->timing.val = 0; |
|||
|
|||
i2s_dev->int_ena.val = 0; |
|||
i2s_dev->int_clr.val = ~0; |
|||
|
|||
// Configure sampling rate
|
|||
i2s_dev->sample_rate_conf.tx_bck_div_num = 40000000 / clk_freq; // Fws = Fbck / 2
|
|||
i2s_dev->sample_rate_conf.tx_bits_mod = (data_width == 8) ? 0 : 1; |
|||
// Configuration data format
|
|||
i2s_dev->conf.tx_start = 0; |
|||
i2s_dev->conf.tx_reset = 1; |
|||
i2s_dev->conf.tx_reset = 0; |
|||
i2s_dev->conf.tx_fifo_reset = 1; |
|||
i2s_dev->conf.tx_fifo_reset = 0; |
|||
i2s_dev->conf.tx_slave_mod = 0; |
|||
i2s_dev->conf.tx_right_first = 1; // Must be set to 1, otherwise the clock line will change during reset
|
|||
i2s_dev->conf.tx_msb_right = 0; |
|||
i2s_dev->conf.tx_short_sync = 0; |
|||
i2s_dev->conf.tx_mono = 0; |
|||
i2s_dev->conf.tx_msb_shift = 0; |
|||
|
|||
i2s_dev->conf1.tx_pcm_bypass = 1; |
|||
i2s_dev->conf1.tx_stop_en = 1; |
|||
|
|||
i2s_dev->conf_chan.tx_chan_mod = 1; |
|||
|
|||
i2s_dev->fifo_conf.tx_fifo_mod_force_en = 1; |
|||
i2s_dev->fifo_conf.tx_data_num = 32; |
|||
i2s_dev->fifo_conf.tx_fifo_mod = 1; |
|||
|
|||
i2s_dev->lc_conf.out_rst = 1; |
|||
i2s_dev->lc_conf.out_rst = 0; |
|||
|
|||
i2s_dev->int_ena.out_eof = 1; |
|||
|
|||
return ESP_OK; |
|||
} |
|||
|
|||
static esp_err_t lcd_set_pin(const i2s_lcd_config_t *config) |
|||
{ |
|||
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[config->pin_num_wr], PIN_FUNC_GPIO); |
|||
gpio_set_direction(config->pin_num_wr, GPIO_MODE_OUTPUT); |
|||
gpio_set_pull_mode(config->pin_num_wr, GPIO_FLOATING); |
|||
gpio_matrix_out(config->pin_num_wr, I2S0O_WS_OUT_IDX, true, false); |
|||
|
|||
for (int i = 0; i < config->data_width; i++) { |
|||
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[config->pin_data_num[i]], PIN_FUNC_GPIO); |
|||
gpio_set_direction(config->pin_data_num[i], GPIO_MODE_OUTPUT); |
|||
gpio_set_pull_mode(config->pin_data_num[i], GPIO_FLOATING); |
|||
// High bit aligned, OUT23 is always the highest bit
|
|||
gpio_matrix_out(config->pin_data_num[i], I2S0O_DATA_OUT0_IDX + (LCD_DATA_MAX_WIDTH - config->data_width) + i, false, false); |
|||
} |
|||
|
|||
return ESP_OK; |
|||
} |
|||
|
|||
static esp_err_t lcd_dma_config(i2s_lcd_obj_t *i2s_lcd_obj, uint32_t max_dma_buffer_size) |
|||
{ |
|||
int cnt = 0; |
|||
if (LCD_CAM_DMA_NODE_BUFFER_MAX_SIZE % 2 != 0) { |
|||
ESP_LOGE(TAG, "ESP32 only supports 2-byte aligned data length"); |
|||
return ESP_FAIL; |
|||
} |
|||
if (max_dma_buffer_size >= LCD_CAM_DMA_NODE_BUFFER_MAX_SIZE * 2) { |
|||
i2s_lcd_obj->dma_node_buffer_size = LCD_CAM_DMA_NODE_BUFFER_MAX_SIZE; |
|||
for (cnt = 0; cnt < max_dma_buffer_size - 8; cnt++) { // Find a buffer size that can divide dma_size
|
|||
if ((max_dma_buffer_size - cnt) % (i2s_lcd_obj->dma_node_buffer_size * 2) == 0) { |
|||
break; |
|||
} |
|||
} |
|||
i2s_lcd_obj->dma_buffer_size = max_dma_buffer_size - cnt; |
|||
} else { |
|||
i2s_lcd_obj->dma_node_buffer_size = max_dma_buffer_size / 2; |
|||
i2s_lcd_obj->dma_buffer_size = i2s_lcd_obj->dma_node_buffer_size * 2; |
|||
} |
|||
|
|||
i2s_lcd_obj->dma_half_buffer_size = i2s_lcd_obj->dma_buffer_size / 2; |
|||
i2s_lcd_obj->dma_node_cnt = (i2s_lcd_obj->dma_buffer_size) / i2s_lcd_obj->dma_node_buffer_size; // Number of DMA nodes
|
|||
i2s_lcd_obj->dma_half_node_cnt = i2s_lcd_obj->dma_node_cnt / 2; |
|||
|
|||
ESP_LOGI(TAG, "lcd_buffer_size: %d, lcd_dma_size: %d, lcd_dma_node_cnt: %d", i2s_lcd_obj->dma_buffer_size, i2s_lcd_obj->dma_node_buffer_size, i2s_lcd_obj->dma_node_cnt); |
|||
|
|||
i2s_lcd_obj->dma = (lldesc_t *)heap_caps_calloc(i2s_lcd_obj->dma_node_cnt, sizeof(lldesc_t), MALLOC_CAP_DMA | MALLOC_CAP_8BIT); |
|||
i2s_lcd_obj->dma_buffer = (uint8_t *)heap_caps_calloc(i2s_lcd_obj->dma_buffer_size, sizeof(uint8_t), MALLOC_CAP_DMA | MALLOC_CAP_8BIT); |
|||
return ESP_OK; |
|||
} |
|||
|
|||
esp_err_t lcd_cam_deinit(i2s_lcd_driver_t *drv) |
|||
{ |
|||
if (!drv->i2s_lcd_obj) { |
|||
return ESP_FAIL; |
|||
} |
|||
|
|||
if (drv->i2s_lcd_obj->event_queue) { |
|||
vQueueDelete(drv->i2s_lcd_obj->event_queue); |
|||
} |
|||
if (drv->i2s_lcd_obj->dma) { |
|||
heap_caps_free(drv->i2s_lcd_obj->dma); |
|||
} |
|||
if (drv->i2s_lcd_obj->dma_buffer) { |
|||
heap_caps_free(drv->i2s_lcd_obj->dma_buffer); |
|||
} |
|||
|
|||
if (drv->i2s_lcd_obj->lcd_cam_intr_handle) { |
|||
esp_intr_free(drv->i2s_lcd_obj->lcd_cam_intr_handle); |
|||
} |
|||
|
|||
heap_caps_free(drv->i2s_lcd_obj); |
|||
drv->i2s_lcd_obj = NULL; |
|||
return ESP_OK; |
|||
} |
|||
|
|||
static esp_err_t lcd_cam_init(i2s_lcd_driver_t *drv, const i2s_lcd_config_t *config) |
|||
{ |
|||
esp_err_t ret = ESP_OK; |
|||
|
|||
i2s_lcd_obj_t *i2s_lcd_obj = (i2s_lcd_obj_t *)heap_caps_calloc(1, sizeof(i2s_lcd_obj_t), MALLOC_CAP_DMA); |
|||
if (i2s_lcd_obj == NULL) { |
|||
ESP_LOGE(TAG, "lcd_cam object malloc failed"); |
|||
return ESP_ERR_NO_MEM; |
|||
} |
|||
drv->i2s_lcd_obj = i2s_lcd_obj; |
|||
|
|||
if (I2S_NUM_0 == config->i2s_port) { |
|||
i2s_lcd_obj->i2s_dev = &I2S0; |
|||
periph_module_enable(PERIPH_I2S0_MODULE); |
|||
ESP_LOGI(TAG, "Enable I2S0"); |
|||
} else if (I2S_NUM_1 == config->i2s_port) { |
|||
i2s_lcd_obj->i2s_dev = &I2S1; |
|||
periph_module_enable(PERIPH_I2S1_MODULE); |
|||
ESP_LOGI(TAG, "Enable I2S1"); |
|||
} else { |
|||
ESP_LOGE(TAG, "Designated I2S peripheral not found"); |
|||
} |
|||
|
|||
do { |
|||
ret |= i2s_lcd_reg_config(i2s_lcd_obj->i2s_dev, config->data_width, config->clk_freq); |
|||
if (ret != ESP_OK) { |
|||
ESP_LOGE(TAG, "lcd_cam config fail!"); |
|||
break; |
|||
} |
|||
|
|||
ret |= lcd_set_pin(config); |
|||
ret |= lcd_dma_config(i2s_lcd_obj, config->buffer_size); |
|||
|
|||
if (ret != ESP_OK) { |
|||
ESP_LOGE(TAG, "lcd config fail!"); |
|||
break; |
|||
} |
|||
|
|||
i2s_lcd_obj->event_queue = xQueueCreate(1, sizeof(int)); |
|||
i2s_lcd_obj->width = config->data_width; |
|||
i2s_lcd_obj->swap_data = config->swap_data;; |
|||
|
|||
if (i2s_lcd_obj->event_queue == NULL) { |
|||
ESP_LOGE(TAG, "lcd config fail!"); |
|||
break; |
|||
} |
|||
|
|||
if (I2S_NUM_0 == config->i2s_port) { |
|||
ret |= esp_intr_alloc(ETS_I2S0_INTR_SOURCE, ESP_INTR_FLAG_LOWMED | ESP_INTR_FLAG_IRAM, i2s_isr, i2s_lcd_obj, &i2s_lcd_obj->lcd_cam_intr_handle); |
|||
} else if (I2S_NUM_1 == config->i2s_port) { |
|||
ret |= esp_intr_alloc(ETS_I2S1_INTR_SOURCE, ESP_INTR_FLAG_LOWMED | ESP_INTR_FLAG_IRAM, i2s_isr, i2s_lcd_obj, &i2s_lcd_obj->lcd_cam_intr_handle); |
|||
} |
|||
if (ret != ESP_OK) { |
|||
ESP_LOGE(TAG, "lcd_cam intr alloc fail!"); |
|||
break; |
|||
} |
|||
ESP_LOGI(TAG, "i2s lcd driver init ok"); |
|||
return ESP_OK; |
|||
} while (0); |
|||
|
|||
lcd_cam_deinit(drv); |
|||
return ESP_FAIL; |
|||
} |
|||
|
|||
/**< Public functions */ |
|||
|
|||
i2s_lcd_handle_t i2s_lcd_driver_init(const i2s_lcd_config_t *config) |
|||
{ |
|||
I2S_CHECK(NULL != config, "config pointer invalid", NULL); |
|||
I2S_CHECK(GPIO_IS_VALID_OUTPUT_GPIO(config->pin_num_wr), "GPIO WR invalid", NULL); |
|||
I2S_CHECK(GPIO_IS_VALID_OUTPUT_GPIO(config->pin_num_rs), "GPIO RS invalid", NULL); |
|||
I2S_CHECK(config->data_width > 0 && config->data_width <= 16, "Bit width out of range", NULL); |
|||
I2S_CHECK(0 == (config->data_width % 8), "Bit width must be a multiple of 8", NULL); |
|||
uint64_t pin_mask = 0; |
|||
for (size_t i = 0; i < config->data_width; i++) { |
|||
uint64_t mask = 1ULL << config->pin_data_num[i]; |
|||
I2S_CHECK(!(pin_mask & mask), "Data bus GPIO has a duplicate", NULL); |
|||
I2S_CHECK(GPIO_IS_VALID_OUTPUT_GPIO(config->pin_data_num[i]), "Data bus gpio invalid", NULL); |
|||
pin_mask |= mask; |
|||
} |
|||
|
|||
i2s_lcd_driver_t *i2s_lcd_drv = (i2s_lcd_driver_t *)heap_caps_malloc(sizeof(i2s_lcd_driver_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); |
|||
I2S_CHECK(NULL != i2s_lcd_drv, "Error malloc handle of i2s lcd driver", NULL); |
|||
|
|||
esp_err_t ret = lcd_cam_init(i2s_lcd_drv, config); |
|||
if (ESP_OK != ret) { |
|||
ESP_LOGE(TAG, "%s:%d (%s):%s", __FILE__, __LINE__, __FUNCTION__, "i2s lcd driver initialize failed"); |
|||
heap_caps_free(i2s_lcd_drv); |
|||
return NULL; |
|||
} |
|||
|
|||
i2s_lcd_drv->mutex = xSemaphoreCreateMutex(); |
|||
if (i2s_lcd_drv->mutex == NULL) { |
|||
ESP_LOGE(TAG, "%s:%d (%s):%s", __FILE__, __LINE__, __FUNCTION__, "lcd create mutex failed"); |
|||
lcd_cam_deinit(i2s_lcd_drv); |
|||
heap_caps_free(i2s_lcd_drv); |
|||
return NULL; |
|||
} |
|||
|
|||
if (8 == config->data_width) { |
|||
i2s_lcd_drv->i2s_write_data_func = i2s_write_8bit_data; |
|||
} else if (16 == config->data_width) { |
|||
i2s_lcd_drv->i2s_write_data_func = i2s_write_16bit_data; |
|||
} |
|||
|
|||
if (config->pin_num_cs >= 0) { |
|||
gpio_pad_select_gpio(config->pin_num_cs); |
|||
gpio_set_direction(config->pin_num_cs, GPIO_MODE_OUTPUT); |
|||
gpio_set_level(config->pin_num_cs, 0); |
|||
} |
|||
|
|||
gpio_pad_select_gpio(config->pin_num_rs); |
|||
gpio_set_direction(config->pin_num_rs, GPIO_MODE_OUTPUT); |
|||
i2s_lcd_drv->rs_io_num = config->pin_num_rs; |
|||
return (i2s_lcd_handle_t)i2s_lcd_drv; |
|||
} |
|||
|
|||
esp_err_t i2s_lcd_driver_deinit(i2s_lcd_handle_t handle) |
|||
{ |
|||
i2s_lcd_driver_t *i2s_lcd_drv = (i2s_lcd_driver_t *)handle; |
|||
I2S_CHECK(NULL != i2s_lcd_drv, "handle pointer invalid", ESP_ERR_INVALID_ARG); |
|||
lcd_cam_deinit(i2s_lcd_drv); |
|||
vSemaphoreDelete(i2s_lcd_drv->mutex); |
|||
heap_caps_free(handle); |
|||
return ESP_OK; |
|||
} |
|||
|
|||
esp_err_t i2s_lcd_write_data(i2s_lcd_handle_t handle, uint16_t data) |
|||
{ |
|||
i2s_lcd_driver_t *i2s_lcd_drv = (i2s_lcd_driver_t *)handle; |
|||
I2S_CHECK(NULL != i2s_lcd_drv, "handle pointer invalid", ESP_ERR_INVALID_ARG); |
|||
i2s_lcd_drv->i2s_write_data_func(i2s_lcd_drv->i2s_lcd_obj, (uint8_t *)&data, i2s_lcd_drv->i2s_lcd_obj->width == 16 ? 2 : 1); |
|||
return ESP_OK; |
|||
} |
|||
|
|||
esp_err_t i2s_lcd_write_cmd(i2s_lcd_handle_t handle, uint16_t cmd) |
|||
{ |
|||
i2s_lcd_driver_t *i2s_lcd_drv = (i2s_lcd_driver_t *)handle; |
|||
I2S_CHECK(NULL != i2s_lcd_drv, "handle pointer invalid", ESP_ERR_INVALID_ARG); |
|||
gpio_set_level(i2s_lcd_drv->rs_io_num, LCD_CMD_LEV); |
|||
i2s_lcd_drv->i2s_write_data_func(i2s_lcd_drv->i2s_lcd_obj, (uint8_t *)&cmd, i2s_lcd_drv->i2s_lcd_obj->width == 16 ? 2 : 1); |
|||
gpio_set_level(i2s_lcd_drv->rs_io_num, LCD_DATA_LEV); |
|||
return ESP_OK; |
|||
} |
|||
|
|||
esp_err_t i2s_lcd_write(i2s_lcd_handle_t handle, const uint8_t *data, uint32_t length) |
|||
{ |
|||
i2s_lcd_driver_t *i2s_lcd_drv = (i2s_lcd_driver_t *)handle; |
|||
I2S_CHECK(NULL != i2s_lcd_drv, "handle pointer invalid", ESP_ERR_INVALID_ARG); |
|||
i2s_lcd_drv->i2s_write_data_func(i2s_lcd_drv->i2s_lcd_obj, (uint8_t *)data, length); |
|||
|
|||
return ESP_OK; |
|||
} |
|||
|
|||
esp_err_t i2s_lcd_acquire(i2s_lcd_handle_t handle) |
|||
{ |
|||
i2s_lcd_driver_t *i2s_lcd_drv = (i2s_lcd_driver_t *)handle; |
|||
I2S_CHECK(NULL != i2s_lcd_drv, "handle pointer invalid", ESP_ERR_INVALID_ARG); |
|||
BaseType_t ret = xSemaphoreTake(i2s_lcd_drv->mutex, portMAX_DELAY); |
|||
I2S_CHECK(pdTRUE == ret, "Take semaphore failed", ESP_FAIL); |
|||
return ESP_OK; |
|||
} |
|||
|
|||
esp_err_t i2s_lcd_release(i2s_lcd_handle_t handle) |
|||
{ |
|||
i2s_lcd_driver_t *i2s_lcd_drv = (i2s_lcd_driver_t *)handle; |
|||
I2S_CHECK(NULL != i2s_lcd_drv, "handle pointer invalid", ESP_ERR_INVALID_ARG); |
|||
BaseType_t ret = xSemaphoreGive(i2s_lcd_drv->mutex); |
|||
I2S_CHECK(pdTRUE == ret, "Give semaphore failed", ESP_FAIL); |
|||
return ESP_OK; |
|||
} |
|||
|
|||
#endif // CONFIG_IDF_TARGET_ESP32
|
@ -0,0 +1,469 @@ |
|||
// Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD
|
|||
//
|
|||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
// you may not use this file except in compliance with the License.
|
|||
// You may obtain a copy of the License at
|
|||
|
|||
// http://www.apache.org/licenses/LICENSE-2.0
|
|||
//
|
|||
// Unless required by applicable law or agreed to in writing, software
|
|||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
// See the License for the specific language governing permissions and
|
|||
// limitations under the License.
|
|||
|
|||
#include "sdkconfig.h" |
|||
#if CONFIG_IDF_TARGET_ESP32S2 |
|||
|
|||
#include <stdio.h> |
|||
#include <string.h> |
|||
#include "freertos/FreeRTOS.h" |
|||
#include "freertos/task.h" |
|||
#include "freertos/semphr.h" |
|||
#include "esp_log.h" |
|||
#include "driver/gpio.h" |
|||
#include "driver/i2s.h" |
|||
#include "esp_heap_caps.h" |
|||
#include "esp32s2/rom/lldesc.h" |
|||
#include "soc/system_reg.h" |
|||
#include "i2s_lcd_driver.h" |
|||
|
|||
|
|||
static const char *TAG = "ESP32S2_I2S_LCD"; |
|||
|
|||
#define I2S_CHECK(a, str, ret) if (!(a)) { \ |
|||
ESP_LOGE(TAG,"%s:%d (%s):%s", __FILE__, __LINE__, __FUNCTION__, str); \ |
|||
return (ret); \ |
|||
} |
|||
|
|||
#define LCD_CAM_DMA_NODE_BUFFER_MAX_SIZE (4000) // 4-byte aligned
|
|||
#define LCD_DATA_MAX_WIDTH (24) /*!< Maximum width of LCD data bus */ |
|||
|
|||
typedef struct { |
|||
uint32_t dma_buffer_size; |
|||
uint32_t dma_half_buffer_size; |
|||
uint32_t dma_node_buffer_size; |
|||
uint32_t dma_node_cnt; |
|||
uint32_t dma_half_node_cnt; |
|||
lldesc_t *dma; |
|||
uint8_t *dma_buffer; |
|||
QueueHandle_t event_queue; |
|||
uint8_t width; |
|||
bool swap_data; |
|||
intr_handle_t lcd_cam_intr_handle; |
|||
i2s_dev_t *i2s_dev; |
|||
} i2s_lcd_obj_t; |
|||
|
|||
typedef struct { |
|||
int rs_io_num; |
|||
i2s_lcd_obj_t *i2s_lcd_obj; |
|||
SemaphoreHandle_t mutex; |
|||
} i2s_lcd_driver_t; |
|||
|
|||
static void IRAM_ATTR i2s_isr(void *arg) |
|||
{ |
|||
BaseType_t HPTaskAwoken = pdFALSE; |
|||
i2s_lcd_obj_t *i2s_lcd_obj = (i2s_lcd_obj_t*)arg; |
|||
i2s_dev_t *i2s_dev = i2s_lcd_obj->i2s_dev; |
|||
|
|||
typeof(i2s_dev->int_st) status = i2s_dev->int_st; |
|||
i2s_dev->int_clr.val = status.val; |
|||
if (status.val == 0) { |
|||
return; |
|||
} |
|||
|
|||
if (status.out_eof) { |
|||
xQueueSendFromISR(i2s_lcd_obj->event_queue, (void*)&status.val, &HPTaskAwoken); |
|||
} |
|||
|
|||
if (HPTaskAwoken == pdTRUE) { |
|||
portYIELD_FROM_ISR(); |
|||
} |
|||
} |
|||
|
|||
static void lcd_dma_set_int(i2s_lcd_obj_t *i2s_lcd_obj) |
|||
{ |
|||
// Generate a data DMA linked list
|
|||
for (int x = 0; x < i2s_lcd_obj->dma_node_cnt; x++) { |
|||
i2s_lcd_obj->dma[x].size = i2s_lcd_obj->dma_node_buffer_size; |
|||
i2s_lcd_obj->dma[x].length = i2s_lcd_obj->dma_node_buffer_size; |
|||
i2s_lcd_obj->dma[x].buf = (i2s_lcd_obj->dma_buffer + i2s_lcd_obj->dma_node_buffer_size * x); |
|||
i2s_lcd_obj->dma[x].eof = !((x + 1) % i2s_lcd_obj->dma_half_node_cnt); |
|||
i2s_lcd_obj->dma[x].empty = (uint32_t)&i2s_lcd_obj->dma[(x + 1) % i2s_lcd_obj->dma_node_cnt]; |
|||
} |
|||
i2s_lcd_obj->dma[i2s_lcd_obj->dma_half_node_cnt - 1].empty = (uint32_t)NULL; |
|||
i2s_lcd_obj->dma[i2s_lcd_obj->dma_node_cnt - 1].empty = (uint32_t)NULL; |
|||
} |
|||
|
|||
static void lcd_dma_set_left(i2s_lcd_obj_t *i2s_lcd_obj, int pos, size_t len) |
|||
{ |
|||
int end_pos = 0, size = 0; |
|||
// Processing data length is an integer multiple of i2s_lcd_obj->dma_node_buffer_size
|
|||
if (len % i2s_lcd_obj->dma_node_buffer_size) { |
|||
end_pos = (pos % 2) * i2s_lcd_obj->dma_half_node_cnt + len / i2s_lcd_obj->dma_node_buffer_size; |
|||
size = len % i2s_lcd_obj->dma_node_buffer_size; |
|||
} else { |
|||
end_pos = (pos % 2) * i2s_lcd_obj->dma_half_node_cnt + len / i2s_lcd_obj->dma_node_buffer_size - 1; |
|||
size = i2s_lcd_obj->dma_node_buffer_size; |
|||
} |
|||
// Process the tail node to make it a DMA tail
|
|||
i2s_lcd_obj->dma[end_pos].size = size; |
|||
i2s_lcd_obj->dma[end_pos].length = size; |
|||
i2s_lcd_obj->dma[end_pos].eof = 1; |
|||
i2s_lcd_obj->dma[end_pos].empty = (uint32_t)NULL; |
|||
} |
|||
|
|||
static void lcd_i2s_start(i2s_dev_t *i2s_dev, uint32_t addr, size_t len) |
|||
{ |
|||
while (!i2s_dev->state.tx_idle); |
|||
i2s_dev->conf.tx_start = 0; |
|||
i2s_dev->conf.tx_reset = 1; |
|||
i2s_dev->conf.tx_reset = 0; |
|||
i2s_dev->conf.tx_fifo_reset = 1; |
|||
i2s_dev->conf.tx_fifo_reset = 0; |
|||
i2s_dev->out_link.addr = addr; |
|||
i2s_dev->out_link.start = 1; |
|||
ets_delay_us(1); |
|||
i2s_dev->conf.tx_start = 1; |
|||
} |
|||
|
|||
static void i2s_write_data(i2s_lcd_obj_t *i2s_lcd_obj, uint8_t *data, size_t len) |
|||
{ |
|||
int event = 0; |
|||
int x = 0, y = 0, left = 0, cnt = 0; |
|||
if (len <= 0) { |
|||
ESP_LOGE(TAG, "wrong len!"); |
|||
return; |
|||
} |
|||
lcd_dma_set_int(i2s_lcd_obj); |
|||
uint32_t half_buffer_size = i2s_lcd_obj->dma_half_buffer_size; |
|||
cnt = len / half_buffer_size; |
|||
// Start signal
|
|||
xQueueSend(i2s_lcd_obj->event_queue, &event, 0); |
|||
// Process a complete piece of data, ping-pong operation
|
|||
for (x = 0; x < cnt; x++) { |
|||
uint8_t *out = (uint8_t*)i2s_lcd_obj->dma[(x % 2) * i2s_lcd_obj->dma_half_node_cnt].buf; |
|||
uint8_t *in = data; |
|||
if (i2s_lcd_obj->swap_data) { |
|||
uint8_t *out1 = out + 1; |
|||
uint8_t *in1 = in + 1; |
|||
for (y = 0; y < half_buffer_size;) { |
|||
out1[y] = in[y]; |
|||
out[y] = in1[y]; |
|||
y += 2; |
|||
out1[y] = in[y]; |
|||
out[y] = in1[y]; |
|||
y += 2; |
|||
} |
|||
} else { |
|||
memcpy(out, in, half_buffer_size); |
|||
} |
|||
data += half_buffer_size; |
|||
xQueueReceive(i2s_lcd_obj->event_queue, (void *)&event, portMAX_DELAY); |
|||
lcd_i2s_start(i2s_lcd_obj->i2s_dev, ((uint32_t)&i2s_lcd_obj->dma[(x % 2) * i2s_lcd_obj->dma_half_node_cnt]) & 0xfffff, half_buffer_size); |
|||
} |
|||
left = len % half_buffer_size; |
|||
// Process remaining incomplete segment data
|
|||
if (left) { |
|||
uint8_t *out = (uint8_t*)i2s_lcd_obj->dma[(x % 2) * i2s_lcd_obj->dma_half_node_cnt].buf; |
|||
uint8_t *in = data; |
|||
cnt = left - left % 2; |
|||
if (cnt) { |
|||
if (i2s_lcd_obj->swap_data) { |
|||
for (y = 0; y < cnt; y+=2) { |
|||
out[y+1] = in[y+0]; |
|||
out[y+0] = in[y+1]; |
|||
} |
|||
} else { |
|||
memcpy(out, in, cnt); |
|||
} |
|||
} |
|||
|
|||
if (left % 2) { |
|||
out[cnt] = in[cnt]; |
|||
} |
|||
lcd_dma_set_left(i2s_lcd_obj, x, left); |
|||
xQueueReceive(i2s_lcd_obj->event_queue, (void *)&event, portMAX_DELAY); |
|||
lcd_i2s_start(i2s_lcd_obj->i2s_dev, ((uint32_t)&i2s_lcd_obj->dma[(x % 2) * i2s_lcd_obj->dma_half_node_cnt]) & 0xfffff, left); |
|||
} |
|||
xQueueReceive(i2s_lcd_obj->event_queue, (void *)&event, portMAX_DELAY); |
|||
} |
|||
|
|||
static esp_err_t i2s_lcd_reg_config(i2s_dev_t *i2s_dev, uint16_t data_width, uint32_t clk_freq) |
|||
{ |
|||
// Configure the clock
|
|||
i2s_dev->clkm_conf.val = 0; |
|||
i2s_dev->clkm_conf.clkm_div_num = 2; // 160MHz / 2 = 80MHz
|
|||
i2s_dev->clkm_conf.clkm_div_b = 0; |
|||
i2s_dev->clkm_conf.clkm_div_a = 63; |
|||
i2s_dev->clkm_conf.clk_sel = 2; |
|||
i2s_dev->clkm_conf.clk_en = 1; |
|||
|
|||
// Configure sampling rate
|
|||
i2s_dev->sample_rate_conf.tx_bck_div_num = 40000000 / clk_freq; // Fws = Fbck / 2
|
|||
i2s_dev->sample_rate_conf.tx_bits_mod = data_width; |
|||
|
|||
i2s_dev->timing.val = 0; |
|||
|
|||
i2s_dev->int_ena.val = 0; |
|||
i2s_dev->int_clr.val = ~0; |
|||
|
|||
i2s_dev->conf2.val = 0; |
|||
i2s_dev->conf2.lcd_en = 1; |
|||
|
|||
// Configuration data format
|
|||
i2s_dev->conf.val = 0; |
|||
i2s_dev->conf.tx_right_first = 1; |
|||
i2s_dev->conf.tx_msb_right = 1; |
|||
i2s_dev->conf.tx_dma_equal = 1; |
|||
|
|||
i2s_dev->conf1.tx_pcm_bypass = 1; |
|||
i2s_dev->conf1.tx_stop_en = 1; |
|||
|
|||
i2s_dev->fifo_conf.val = 0; |
|||
i2s_dev->fifo_conf.dscr_en = 1; |
|||
i2s_dev->fifo_conf.tx_fifo_mod_force_en = 1; |
|||
i2s_dev->fifo_conf.tx_data_num = 32; |
|||
i2s_dev->fifo_conf.tx_fifo_mod = 2; |
|||
i2s_dev->fifo_conf.tx_24msb_en = 0; |
|||
|
|||
i2s_dev->conf_chan.tx_chan_mod = 0;//remove
|
|||
i2s_dev->int_ena.out_eof = 1; |
|||
return ESP_OK; |
|||
} |
|||
|
|||
static esp_err_t lcd_set_pin(const i2s_lcd_config_t *config) |
|||
{ |
|||
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[config->pin_num_wr], PIN_FUNC_GPIO); |
|||
gpio_set_direction(config->pin_num_wr, GPIO_MODE_OUTPUT); |
|||
gpio_set_pull_mode(config->pin_num_wr, GPIO_FLOATING); |
|||
gpio_matrix_out(config->pin_num_wr, I2S0O_WS_OUT_IDX, true, false); |
|||
|
|||
for (int i = 0; i < config->data_width; i++) { |
|||
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[config->pin_data_num[i]], PIN_FUNC_GPIO); |
|||
gpio_set_direction(config->pin_data_num[i], GPIO_MODE_OUTPUT); |
|||
gpio_set_pull_mode(config->pin_data_num[i], GPIO_FLOATING); |
|||
// High bit aligned, OUT23 is always the highest bit
|
|||
gpio_matrix_out(config->pin_data_num[i], I2S0O_DATA_OUT0_IDX + (LCD_DATA_MAX_WIDTH - config->data_width) + i, false, false); |
|||
} |
|||
|
|||
return ESP_OK; |
|||
} |
|||
|
|||
static esp_err_t lcd_dma_config(i2s_lcd_obj_t *i2s_lcd_obj, uint32_t max_dma_buffer_size) |
|||
{ |
|||
int cnt = 0; |
|||
if (LCD_CAM_DMA_NODE_BUFFER_MAX_SIZE % 2 != 0) { |
|||
ESP_LOGE(TAG, "ESP32 only supports 2-byte aligned data length"); |
|||
return ESP_FAIL; |
|||
} |
|||
if (max_dma_buffer_size >= LCD_CAM_DMA_NODE_BUFFER_MAX_SIZE * 2) { |
|||
i2s_lcd_obj->dma_node_buffer_size = LCD_CAM_DMA_NODE_BUFFER_MAX_SIZE; |
|||
for (cnt = 0; cnt < max_dma_buffer_size - 8; cnt++) { // Find a buffer size that can divide dma_size
|
|||
if ((max_dma_buffer_size - cnt) % (i2s_lcd_obj->dma_node_buffer_size * 2) == 0) { |
|||
break; |
|||
} |
|||
} |
|||
i2s_lcd_obj->dma_buffer_size = max_dma_buffer_size - cnt; |
|||
} else { |
|||
i2s_lcd_obj->dma_node_buffer_size = max_dma_buffer_size / 2; |
|||
i2s_lcd_obj->dma_buffer_size = i2s_lcd_obj->dma_node_buffer_size * 2; |
|||
} |
|||
|
|||
i2s_lcd_obj->dma_half_buffer_size = i2s_lcd_obj->dma_buffer_size / 2; |
|||
i2s_lcd_obj->dma_node_cnt = (i2s_lcd_obj->dma_buffer_size) / i2s_lcd_obj->dma_node_buffer_size; // Number of DMA nodes
|
|||
i2s_lcd_obj->dma_half_node_cnt = i2s_lcd_obj->dma_node_cnt / 2; |
|||
|
|||
ESP_LOGI(TAG, "lcd_buffer_size: %d, lcd_dma_size: %d, lcd_dma_node_cnt: %d", i2s_lcd_obj->dma_buffer_size, i2s_lcd_obj->dma_node_buffer_size, i2s_lcd_obj->dma_node_cnt); |
|||
|
|||
i2s_lcd_obj->dma = (lldesc_t *)heap_caps_malloc(i2s_lcd_obj->dma_node_cnt * sizeof(lldesc_t), MALLOC_CAP_DMA | MALLOC_CAP_8BIT); |
|||
i2s_lcd_obj->dma_buffer = (uint8_t *)heap_caps_malloc(i2s_lcd_obj->dma_buffer_size * sizeof(uint8_t), MALLOC_CAP_DMA | MALLOC_CAP_8BIT); |
|||
return ESP_OK; |
|||
} |
|||
|
|||
static esp_err_t lcd_cam_deinit(i2s_lcd_driver_t *drv) |
|||