mirror of
https://github.com/espressif/esp-idf.git
synced 2025-08-14 14:06:54 +00:00
lcd: add esp_lcd component
* Support intel 8080 LCD panel IO on ESP32-S3 * Support RGB LCD panel on ESP32-S3 * Support SPI && I2C LCD panel IO on all esp chips
This commit is contained in:
230
components/esp_lcd/src/esp_lcd_panel_io_spi.c
Normal file
230
components/esp_lcd/src/esp_lcd_panel_io_spi.c
Normal file
@@ -0,0 +1,230 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
// #define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/cdefs.h>
|
||||
#include "esp_lcd_panel_io_interface.h"
|
||||
#include "esp_lcd_panel_io.h"
|
||||
#include "driver/spi_master.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_check.h"
|
||||
|
||||
static const char *TAG = "lcd_panel.io.spi";
|
||||
|
||||
static esp_err_t panel_io_spi_tx_param(esp_lcd_panel_io_t *io, int lcd_cmd, int lcd_cmd_bits, const void *param, size_t param_size);
|
||||
static esp_err_t panel_io_spi_tx_color(esp_lcd_panel_io_t *io, int lcd_cmd, int lcd_cmd_bits, const void *color, size_t color_size);
|
||||
static esp_err_t panel_io_spi_del(esp_lcd_panel_io_t *io);
|
||||
static void lcd_spi_pre_trans_cb(spi_transaction_t *trans);
|
||||
static void lcd_spi_post_trans_color_cb(spi_transaction_t *trans);
|
||||
|
||||
typedef struct {
|
||||
spi_transaction_t base;
|
||||
struct {
|
||||
unsigned int dc_gpio_level: 1;
|
||||
unsigned int trans_is_color: 1;
|
||||
} flags;
|
||||
} lcd_spi_trans_descriptor_t;
|
||||
|
||||
typedef struct {
|
||||
esp_lcd_panel_io_t base; // Base class of generic lcd panel io
|
||||
spi_device_handle_t spi_dev; // SPI device handle
|
||||
int dc_gpio_num; // D/C line GPIO number
|
||||
bool (*on_color_trans_done)(esp_lcd_panel_io_handle_t panel_io, void *user_data, void *event_data); // User register's callback, invoked when color data trans done
|
||||
void *user_data; // User's private data, passed directly to callback on_color_trans_done
|
||||
size_t queue_size; // Size of transaction queue
|
||||
size_t num_trans_working; // Number of transactions that are undergoing (the descriptor not recycled yet)
|
||||
struct {
|
||||
int dc_as_cmd_phase: 1; // D/C line value is encoded into SPI transaction command phase
|
||||
int dc_data_level: 1; // Indicates the level of DC line when tranfering data
|
||||
} flags;
|
||||
lcd_spi_trans_descriptor_t trans_pool[0]; // Transaction pool
|
||||
} esp_lcd_panel_io_spi_t;
|
||||
|
||||
esp_err_t esp_lcd_new_panel_io_spi(esp_lcd_spi_bus_handle_t bus, const esp_lcd_panel_io_spi_config_t *io_config, esp_lcd_panel_io_handle_t *ret_io)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
esp_lcd_panel_io_spi_t *spi_panel_io = NULL;
|
||||
ESP_GOTO_ON_FALSE(bus && io_config && ret_io, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
|
||||
ESP_GOTO_ON_FALSE(!(io_config->flags.dc_as_cmd_phase && io_config->dc_gpio_num >= 0),
|
||||
ESP_ERR_INVALID_ARG, err, TAG, "invalid DC mode");
|
||||
spi_panel_io = calloc(1, sizeof(esp_lcd_panel_io_spi_t) + sizeof(lcd_spi_trans_descriptor_t) * io_config->trans_queue_depth);
|
||||
ESP_GOTO_ON_FALSE(spi_panel_io, ESP_ERR_NO_MEM, err, TAG, "no mem for spi panel io");
|
||||
|
||||
spi_device_interface_config_t devcfg = {
|
||||
.clock_speed_hz = io_config->pclk_hz,
|
||||
.mode = io_config->spi_mode,
|
||||
.spics_io_num = io_config->cs_gpio_num,
|
||||
.queue_size = io_config->trans_queue_depth,
|
||||
.command_bits = io_config->flags.dc_as_cmd_phase ? 1 : 0, // whether to encode DC line into command transaction
|
||||
.pre_cb = lcd_spi_pre_trans_cb, // pre-transaction callback, mainly control DC gpio level
|
||||
.post_cb = io_config->on_color_trans_done ? lcd_spi_post_trans_color_cb : NULL, // post-transaction, where we invoke user registered "on_color_trans_done()"
|
||||
};
|
||||
ret = spi_bus_add_device((spi_host_device_t)bus, &devcfg, &spi_panel_io->spi_dev);
|
||||
ESP_GOTO_ON_ERROR(ret, err, TAG, "adding spi device to bus failed");
|
||||
|
||||
// if the DC line is not encoded into any spi transaction phase or it's not controlled by SPI peripheral
|
||||
if (io_config->dc_gpio_num >= 0) {
|
||||
gpio_config_t io_conf = {
|
||||
.mode = GPIO_MODE_OUTPUT,
|
||||
.pin_bit_mask = 1ULL << io_config->dc_gpio_num,
|
||||
};
|
||||
ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, TAG, "configure GPIO for D/C line failed");
|
||||
}
|
||||
|
||||
spi_panel_io->flags.dc_as_cmd_phase = io_config->flags.dc_as_cmd_phase;
|
||||
spi_panel_io->flags.dc_data_level = !io_config->flags.dc_low_on_data;
|
||||
spi_panel_io->on_color_trans_done = io_config->on_color_trans_done;
|
||||
spi_panel_io->user_data = io_config->user_data;
|
||||
spi_panel_io->dc_gpio_num = io_config->dc_gpio_num;
|
||||
spi_panel_io->queue_size = io_config->trans_queue_depth;
|
||||
spi_panel_io->base.tx_param = panel_io_spi_tx_param;
|
||||
spi_panel_io->base.tx_color = panel_io_spi_tx_color;
|
||||
spi_panel_io->base.del = panel_io_spi_del;
|
||||
*ret_io = &(spi_panel_io->base);
|
||||
ESP_LOGD(TAG, "new spi lcd panel io @%p", spi_panel_io);
|
||||
|
||||
return ESP_OK;
|
||||
|
||||
err:
|
||||
if (spi_panel_io) {
|
||||
if (io_config->dc_gpio_num >= 0) {
|
||||
gpio_reset_pin(io_config->dc_gpio_num);
|
||||
}
|
||||
free(spi_panel_io);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static esp_err_t panel_io_spi_del(esp_lcd_panel_io_t *io)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
spi_transaction_t *spi_trans = NULL;
|
||||
esp_lcd_panel_io_spi_t *spi_panel_io = __containerof(io, esp_lcd_panel_io_spi_t, base);
|
||||
|
||||
// wait all pending transaction to finish
|
||||
for (size_t i = 0; i < spi_panel_io->num_trans_working; i++) {
|
||||
ret = spi_device_get_trans_result(spi_panel_io->spi_dev, &spi_trans, portMAX_DELAY);
|
||||
ESP_GOTO_ON_ERROR(ret, err, TAG, "recycle spi transactions failed");
|
||||
}
|
||||
spi_bus_remove_device(spi_panel_io->spi_dev);
|
||||
if (spi_panel_io->dc_gpio_num >= 0) {
|
||||
gpio_reset_pin(spi_panel_io->dc_gpio_num);
|
||||
}
|
||||
ESP_LOGD(TAG, "del lcd panel io spi @%p", spi_panel_io);
|
||||
free(spi_panel_io);
|
||||
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static esp_err_t panel_io_spi_tx_param(esp_lcd_panel_io_t *io, int lcd_cmd, int lcd_cmd_bits, const void *param, size_t param_size)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
spi_transaction_t *spi_trans = NULL;
|
||||
lcd_spi_trans_descriptor_t *lcd_trans = NULL;
|
||||
esp_lcd_panel_io_spi_t *spi_panel_io = __containerof(io, esp_lcd_panel_io_spi_t, base);
|
||||
|
||||
// before issue a polling transaction, need to wait queued transactions finished
|
||||
for (size_t i = 0; i < spi_panel_io->num_trans_working; i++) {
|
||||
ret = spi_device_get_trans_result(spi_panel_io->spi_dev, &spi_trans, portMAX_DELAY);
|
||||
ESP_GOTO_ON_ERROR(ret, err, TAG, "recycle spi transactions failed");
|
||||
}
|
||||
spi_panel_io->num_trans_working = 0;
|
||||
lcd_trans = &spi_panel_io->trans_pool[0];
|
||||
memset(lcd_trans, 0, sizeof(lcd_spi_trans_descriptor_t));
|
||||
lcd_trans->base.user = spi_panel_io;
|
||||
lcd_trans->flags.dc_gpio_level = !spi_panel_io->flags.dc_data_level; // set D/C line to command mode
|
||||
lcd_trans->base.length = lcd_cmd_bits;
|
||||
lcd_trans->base.tx_buffer = &lcd_cmd;
|
||||
if (spi_panel_io->flags.dc_as_cmd_phase) { // encoding DC value to SPI command phase when necessary
|
||||
lcd_trans->base.cmd = !spi_panel_io->flags.dc_data_level;
|
||||
}
|
||||
// command is short, using polling mode
|
||||
ret = spi_device_polling_transmit(spi_panel_io->spi_dev, &lcd_trans->base);
|
||||
ESP_GOTO_ON_ERROR(ret, err, TAG, "spi transmit (polling) command failed");
|
||||
|
||||
if (param && param_size) {
|
||||
lcd_trans->flags.dc_gpio_level = spi_panel_io->flags.dc_data_level; // set D/C line to data mode
|
||||
lcd_trans->base.length = param_size * 8; // transaction length is in bits
|
||||
lcd_trans->base.tx_buffer = param;
|
||||
if (spi_panel_io->flags.dc_as_cmd_phase) { // encoding DC value to SPI command phase when necessary
|
||||
lcd_trans->base.cmd = spi_panel_io->flags.dc_data_level;
|
||||
}
|
||||
// parameter is usually short, using polling mode
|
||||
ret = spi_device_polling_transmit(spi_panel_io->spi_dev, &lcd_trans->base);
|
||||
ESP_GOTO_ON_ERROR(ret, err, TAG, "spi transmit (polling) param failed");
|
||||
}
|
||||
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static esp_err_t panel_io_spi_tx_color(esp_lcd_panel_io_t *io, int lcd_cmd, int lcd_cmd_bits, const void *color, size_t color_size)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
spi_transaction_t *spi_trans = NULL;
|
||||
lcd_spi_trans_descriptor_t *lcd_trans = NULL;
|
||||
esp_lcd_panel_io_spi_t *spi_panel_io = __containerof(io, esp_lcd_panel_io_spi_t, base);
|
||||
|
||||
// before issue a polling transaction, need to wait queued transactions finished
|
||||
for (size_t i = 0; i < spi_panel_io->num_trans_working; i++) {
|
||||
ret = spi_device_get_trans_result(spi_panel_io->spi_dev, &spi_trans, portMAX_DELAY);
|
||||
ESP_GOTO_ON_ERROR(ret, err, TAG, "recycle spi transactions failed");
|
||||
}
|
||||
spi_panel_io->num_trans_working = 0;
|
||||
lcd_trans = &spi_panel_io->trans_pool[0];
|
||||
memset(lcd_trans, 0, sizeof(lcd_spi_trans_descriptor_t));
|
||||
lcd_trans->base.user = spi_panel_io;
|
||||
lcd_trans->flags.dc_gpio_level = !spi_panel_io->flags.dc_data_level; // set D/C line to command mode
|
||||
lcd_trans->base.length = lcd_cmd_bits;
|
||||
lcd_trans->base.tx_buffer = &lcd_cmd;
|
||||
if (spi_panel_io->flags.dc_as_cmd_phase) { // encoding DC value to SPI command phase when necessary
|
||||
lcd_trans->base.cmd = !spi_panel_io->flags.dc_data_level;
|
||||
}
|
||||
// command is short, using polling mode
|
||||
ret = spi_device_polling_transmit(spi_panel_io->spi_dev, &lcd_trans->base);
|
||||
ESP_GOTO_ON_ERROR(ret, err, TAG, "spi transmit (polling) command failed");
|
||||
|
||||
// sending LCD color data
|
||||
lcd_trans->flags.trans_is_color = 1;
|
||||
lcd_trans->flags.dc_gpio_level = spi_panel_io->flags.dc_data_level; // set D/C line to data mode
|
||||
lcd_trans->base.length = color_size * 8; // transaction length is in bits
|
||||
lcd_trans->base.tx_buffer = color;
|
||||
if (spi_panel_io->flags.dc_as_cmd_phase) { // encoding DC value to SPI command phase when necessary
|
||||
lcd_trans->base.cmd = spi_panel_io->flags.dc_data_level;
|
||||
}
|
||||
// color data is usually large, using queue+blocking mode
|
||||
ret = spi_device_queue_trans(spi_panel_io->spi_dev, &lcd_trans->base, portMAX_DELAY);
|
||||
ESP_GOTO_ON_ERROR(ret, err, TAG, "spi transmit (queue) color failed");
|
||||
spi_panel_io->num_trans_working++;
|
||||
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void lcd_spi_pre_trans_cb(spi_transaction_t *trans)
|
||||
{
|
||||
esp_lcd_panel_io_spi_t *spi_panel_io = trans->user;
|
||||
lcd_spi_trans_descriptor_t *lcd_trans = __containerof(trans, lcd_spi_trans_descriptor_t, base);
|
||||
if (spi_panel_io->dc_gpio_num >= 0) { // set D/C line level if necessary
|
||||
gpio_set_level(spi_panel_io->dc_gpio_num, lcd_trans->flags.dc_gpio_level);
|
||||
}
|
||||
}
|
||||
|
||||
static void lcd_spi_post_trans_color_cb(spi_transaction_t *trans)
|
||||
{
|
||||
esp_lcd_panel_io_spi_t *spi_panel_io = trans->user;
|
||||
lcd_spi_trans_descriptor_t *lcd_trans = __containerof(trans, lcd_spi_trans_descriptor_t, base);
|
||||
if (lcd_trans->flags.trans_is_color) {
|
||||
if (spi_panel_io->on_color_trans_done) {
|
||||
spi_panel_io->on_color_trans_done(&spi_panel_io->base, spi_panel_io->user_data, NULL);
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user