版本信息: ESP-ADF v2.7-65-gcf908721
本文档详细分析ESP-ADF中的显示/输出类外设实现机制,包括LCD、LED、WS2812、IS31FL3216和AW2013等外设的设计模式、接口规范、初始化流程和事件处理机制。ESP-ADF显示/输出类外设基于统一的外设框架设计,通过事件驱动模型实现显示和指示功能,为音频应用提供了丰富的视觉反馈能力和用户界面支持。
ESP-ADF显示/输出类外设主要负责提供视觉反馈和用户界面显示功能,将应用程序的状态和数据以可视化方式呈现给用户。主要功能包括:
显示/输出类外设是ESP-ADF外设子系统的重要组成部分,位于硬件驱动层和应用层之间:
LCD外设基于ESP-IDF的LCD驱动框架实现,支持多种LCD控制器和接口类型,主要用于显示文本、图形和用户界面。LCD外设通过SPI或I2C接口与LCD控制器通信,提供了显示初始化、显示控制和显示内容更新等功能。
LCD外设实现仅在ESP-IDF 4.4.0及以上版本可用,它主要依赖于ESP-IDF的esp_lcd
组件,该组件提供了统一的LCD面板驱动接口。
LCD外设实现主要包含一个层次:
components/esp_peripherals/include/periph_lcd.h
components/esp_peripherals/periph_lcd.c
源文件:components/esp_peripherals/include/periph_lcd.h
和components/esp_peripherals/periph_lcd.c
// LCD外设初始化函数
esp_periph_handle_t periph_lcd_init(periph_lcd_cfg_t *config);
// 获取LCD面板句柄
esp_lcd_panel_handle_t periph_lcd_get_panel_handle(esp_periph_handle_t handle);
// LCD面板IO总线获取回调函数类型
typedef esp_err_t (*get_lcd_io_bus)(void *bus, esp_lcd_panel_io_spi_config_t *io_config, esp_lcd_panel_io_handle_t *out_panel_io);
// LCD面板获取回调函数类型
typedef esp_err_t (*get_lcd_panel)(const esp_lcd_panel_io_handle_t panel_io, const esp_lcd_panel_dev_config_t *panel_dev_config,
esp_lcd_panel_handle_t *ret_panel);
// LCD供应商初始化回调函数类型
typedef esp_err_t (*lcd_vender_init_func)(const esp_lcd_panel_io_handle_t panel_io);
// LCD复位回调函数类型
typedef esp_err_t (*perph_lcd_rest)(esp_periph_handle_t self, void *ctx);
// LCD配置结构体
typedef struct {
void *io_bus; // IO总线句柄
get_lcd_io_bus new_panel_io; // 面板IO创建函数
esp_lcd_panel_io_spi_config_t *lcd_io_cfg; // LCD IO配置
get_lcd_panel new_lcd_panel; // 面板创建函数
lcd_vender_init_func vendor_init; // 供应商初始化函数
esp_lcd_panel_dev_config_t *lcd_dev_cfg; // LCD设备配置
perph_lcd_rest rest_cb; // 复位回调函数
void *rest_cb_ctx; // 复位回调上下文
bool lcd_swap_xy; // 是否交换XY坐标
bool lcd_mirror_x; // 是否X轴镜像
bool lcd_mirror_y; // 是否Y轴镜像
bool lcd_color_invert; // 是否颜色反转
} periph_lcd_cfg_t;
// LCD外设内部结构体 (定义在periph_lcd.c中)
typedef struct periph_lcd {
void *io_bus; // IO总线句柄
get_lcd_io_bus new_panel_io; // 面板IO创建函数
esp_lcd_panel_io_spi_config_t lcd_io_cfg; // LCD IO配置
get_lcd_panel new_lcd_panel; // 面板创建函数
esp_lcd_panel_dev_config_t lcd_dev_cfg; // LCD设备配置
esp_lcd_panel_io_handle_t lcd_io_handle; // LCD IO句柄
esp_lcd_panel_handle_t lcd_panel_handle; // LCD面板句柄
perph_lcd_rest rest_cb; // 复位回调函数
lcd_vender_init_func vendor_init; // 供应商初始化函数
void *rest_cb_ctx; // 复位回调上下文
bool lcd_swap_xy; // 是否交换XY坐标
bool lcd_mirror_x; // 是否X轴镜像
bool lcd_mirror_y; // 是否Y轴镜像
bool lcd_color_invert; // 是否颜色反转
} periph_lcd_t;
LCD外设的初始化流程主要在外设层完成,涉及到ESP-IDF的LCD驱动接口。下面详细介绍初始化过程。
外设层初始化主要通过periph_lcd_init
函数(位于periph_lcd.c
)完成,主要包括以下步骤:
periph_lcd_t
结构体内存esp_periph_create
函数创建外设句柄_setup_lcd
函数设置LCD IO总线_lcd_init
函数初始化LCD面板// 文件:components/esp_peripherals/periph_lcd.c
esp_periph_handle_t periph_lcd_init(periph_lcd_cfg_t *config)
{
// 1. 创建LCD外设结构体
periph_lcd_t *periph_lcd = audio_calloc(1, sizeof(periph_lcd_t));
AUDIO_MEM_CHECK(TAG, periph_lcd, return NULL);
// 2. 复制配置参数
periph_lcd->io_bus = config->io_bus;
memcpy(&periph_lcd->lcd_io_cfg, config->lcd_io_cfg, sizeof(esp_lcd_panel_io_spi_config_t));
memcpy(&periph_lcd->lcd_dev_cfg, config->lcd_dev_cfg, sizeof(esp_lcd_panel_dev_config_t));
periph_lcd->new_panel_io = config->new_panel_io;
periph_lcd->new_lcd_panel = config->new_lcd_panel;
periph_lcd->lcd_swap_xy = config->lcd_swap_xy;
periph_lcd->lcd_mirror_x = config->lcd_mirror_x;
periph_lcd->lcd_mirror_y = config->lcd_mirror_y;
periph_lcd->lcd_color_invert = config->lcd_color_invert;
periph_lcd->rest_cb = config->rest_cb;
periph_lcd->vendor_init = config->vendor_init;
// 3. 创建外设句柄
esp_periph_handle_t periph = esp_periph_create(PERIPH_ID_LCD, "periph_lcd");
AUDIO_MEM_CHECK(TAG, periph, {
audio_free(periph_lcd);
return NULL;
});
// 4. 设置回调函数
if (periph_lcd->rest_cb == NULL) {
periph_lcd->rest_cb = _lcd_rest_default;
}
periph_lcd->rest_cb_ctx = config->rest_cb_ctx;
esp_periph_set_data(periph, periph_lcd);
esp_periph_set_function(periph, NULL, _lcd_run, _lcd_destroy);
// 5. 设置LCD IO总线
_setup_lcd(periph);
// 6. 初始化LCD面板
_lcd_init(periph);
return periph;
}
_setup_lcd
函数负责设置LCD IO总线和创建LCD面板,主要包括以下步骤:
new_panel_io
回调函数创建LCD IO句柄new_lcd_panel
回调函数创建LCD面板句柄// 文件:components/esp_peripherals/periph_lcd.c
esp_err_t _setup_lcd(esp_periph_handle_t self)
{
// 1. 获取LCD外设数据
periph_lcd_t *periph_lcd = esp_periph_get_data(self);
// 2. 创建LCD IO句柄
ESP_ERROR_CHECK(periph_lcd->new_panel_io(periph_lcd->io_bus,
&periph_lcd->lcd_io_cfg, &periph_lcd->lcd_io_handle));
// 3. 创建LCD面板句柄
ESP_ERROR_CHECK(periph_lcd->new_lcd_panel(periph_lcd->lcd_io_handle,
&periph_lcd->lcd_dev_cfg, &periph_lcd->lcd_panel_handle));
return ESP_OK;
}
_lcd_init
函数负责初始化LCD面板和设置显示参数,主要包括以下步骤:
esp_lcd_panel_init
函数初始化LCD面板esp_lcd_panel_disp_on_off
函数打开显示// 文件:components/esp_peripherals/periph_lcd.c
static esp_err_t _lcd_init(esp_periph_handle_t self)
{
// 1. 获取LCD外设数据
periph_lcd_t *periph_lcd = esp_periph_get_data(self);
// 2. 执行LCD复位
if (periph_lcd->rest_cb) {
periph_lcd->rest_cb(self, periph_lcd->rest_cb_ctx);
}
// 3. 初始化LCD面板
ESP_ERROR_CHECK(esp_lcd_panel_init(periph_lcd->lcd_panel_handle));
// 4. 执行供应商特定初始化
if (periph_lcd->vendor_init) {
ESP_ERROR_CHECK(periph_lcd->vendor_init(periph_lcd->lcd_io_handle));
}
// 5. 配置LCD显示参数
ESP_ERROR_CHECK(esp_lcd_panel_invert_color(periph_lcd->lcd_panel_handle, periph_lcd->lcd_color_invert));
ESP_ERROR_CHECK(esp_lcd_panel_set_gap(periph_lcd->lcd_panel_handle, 0, 0));
ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(periph_lcd->lcd_panel_handle, periph_lcd->lcd_swap_xy));
ESP_ERROR_CHECK(esp_lcd_panel_mirror(periph_lcd->lcd_panel_handle, periph_lcd->lcd_mirror_x, periph_lcd->lcd_mirror_y));
// 6. 打开显示
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(periph_lcd->lcd_panel_handle, true));
return ESP_OK;
}
如果用户没有提供复位回调函数,则使用默认的复位函数:
// 文件:components/esp_peripherals/periph_lcd.c
esp_err_t _lcd_rest_default(esp_periph_handle_t self, void *ctx)
{
periph_lcd_t *periph_lcd = esp_periph_get_data(self);
ESP_ERROR_CHECK(esp_lcd_panel_reset(periph_lcd->lcd_panel_handle));
return ESP_OK;
}
下图展示了LCD外设从应用程序调用到ESP-IDF LCD驱动完成初始化的完整流程:
通过上述初始化流程,LCD外设完成了从创建外设句柄到初始化LCD面板的全过程,使应用程序能够通过获取LCD面板句柄来控制LCD显示。
LCD外设的销毁流程相对简单,主要通过_lcd_destroy
函数(位于periph_lcd.c
)完成,主要包括以下步骤:
esp_lcd_panel_del
函数删除LCD面板esp_lcd_panel_io_del
函数删除LCD IO// 文件:components/esp_peripherals/periph_lcd.c
static esp_err_t _lcd_destroy(esp_periph_handle_t self)
{
// 1. 获取LCD外设数据
periph_lcd_t *periph_lcd = esp_periph_get_data(self);
// 2. 删除LCD面板
esp_lcd_panel_del(periph_lcd->lcd_panel_handle);
// 3. 删除LCD IO
esp_lcd_panel_io_del(periph_lcd->lcd_io_handle);
// 注意:这里没有释放periph_lcd结构体内存,因为这个操作由esp_periph库负责
return ESP_OK;
}
下图展示了LCD外设销毁的完整流程:
内存管理:_lcd_destroy
函数不负责释放periph_lcd
结构体内存,这个操作由esp_periph
库负责。这是因为该结构体是通过esp_periph_set_data
函数设置给外设句柄的,由外设框架统一管理。
资源释放顺序:先删除LCD面板,再删除LCD IO,这个顺序确保了资源的正确释放,避免了悬挂指针和内存泄漏问题。
错误处理:销毁函数没有进行错误检查,这是因为销毁操作通常是在应用程序退出或者资源清理阶段执行的,即使出现错误也不会影响程序的正常退出。
通过上述销毁流程,LCD外设能够正确释放所有分配的资源,确保应用程序不会出现资源泄漏问题。
LCD外设的事件处理机制相对简单,主要通过_lcd_run
函数(位于periph_lcd.c
)实现,该函数在外设框架接收到事件时被调用。
// 文件:components/esp_peripherals/periph_lcd.c
static esp_err_t _lcd_run(esp_periph_handle_t self, audio_event_iface_msg_t *msg)
{
return ESP_OK;
}
与按键外设不同,LCD外设的_lcd_run
函数实现非常简单,仅返回ESP_OK
,不处理任何事件。这是因为LCD外设主要是一个输出设备,不会主动产生事件,而是被动接收应用程序的控制命令进行显示更新。
被动设备:LCD外设是一个典型的被动设备,不会主动产生事件,而是由应用程序控制其显示内容和状态。
无事件类型定义:与按键外设不同,LCD外设没有定义特定的事件类型,因为它不需要向应用程序报告状态变化。
控制方式:应用程序通过以下方式控制LCD显示:
periph_lcd_init
periph_lcd_get_panel_handle
esp_lcd_panel_draw_bitmap
等#include "esp_peripherals.h"
#include "periph_lcd.h"
#include "esp_lcd_panel_ops.h"
void app_lcd_demo(void)
{
// 1. 初始化esp_periph库
esp_periph_config_t periph_cfg = DEFAULT_ESP_PERIPH_SET_CONFIG();
esp_periph_set_handle_t set = esp_periph_set_init(&periph_cfg);
// 2. 初始化LCD外设
periph_lcd_cfg_t lcd_cfg = {
// 填写LCD配置参数
};
esp_periph_handle_t lcd_periph = periph_lcd_init(&lcd_cfg);
esp_periph_start(set, lcd_periph);
// 3. 获取LCD面板句柄
esp_lcd_panel_handle_t panel = periph_lcd_get_panel_handle(lcd_periph);
// 4. 使用ESP-IDF LCD API绘制内容
uint16_t color_data[320 * 240] = {0}; // 示例:320x240分辨率
esp_lcd_panel_draw_bitmap(panel, 0, 0, 320, 240, color_data);
// ... 其他应用逻辑
// 5. 销毁LCD外设
esp_periph_stop(set, lcd_periph);
esp_periph_destroy(set);
}
下图展示了LCD外设与应用程序的交互流程:
通过上述交互流程,应用程序可以控制LCD显示内容,而无需关心事件处理机制。这种设计简化了LCD外设的使用,使开发者可以专注于显示内容的设计和实现。