当前位置: 首页 > news >正文

ESP-ADF外设子系统深度解析:esp_peripherals组件架构与核心设计(系列开篇)

目录

  • ESP-ADF外设子系统深度解析:esp_peripherals组件架构与核心设计(系列开篇)
    • 简介
    • 模块概述
      • 功能定义
      • 架构位置
      • 核心特性
    • 接口分析
      • 公共API概述
        • 1. 外设集合管理API
        • 2. 单个外设管理API
        • 3. 事件通信API
        • 4. 定时器管理API
      • 数据结构
        • 关键数据结构分析
        • 枚举类型
      • 配置选项
        • 外设集合初始化时序图
    • 实现原理
      • 初始化流程
      • 核心算法
      • 状态管理
    • 事件处理
      • 事件类型
      • 事件流向
      • 回调机制
    • 与其他模块交互
      • 依赖模块
      • 被依赖关系
      • 交互流程
    • 使用示例
      • 基础使用
      • 高级场景
    • 最佳实践
      • 性能优化
      • 常见问题
      • 注意事项
    • 总结
      • 设计评估

ESP-ADF外设子系统深度解析:esp_peripherals组件架构与核心设计(系列开篇)

文档版本: 1.0.0
ESP-ADF版本: v2.7-65-gcf908721

简介

ESP-ADF (ESP Audio Development Framework) 的 esp_peripherals 子系统是一个用于管理和控制各种外设的框架,它提供了一个统一的接口和事件处理机制,使应用程序能够方便地与按钮、触摸板、SD卡、WiFi、LED等外部设备进行交互。本文档全面分析了esp_peripherals组件的架构设计、核心数据结构、事件处理机制以及其与ESP-ADF其他部分的交互方式,帮助开发者深入理解该组件的内部工作原理。

模块概述

功能定义

esp_peripherals组件在ESP-ADF中扮演着外设管理者的角色,主要功能包括:

  1. 外设初始化和管理:提供统一的接口创建、初始化、启动和销毁各种外设
  2. 事件驱动机制:使用基于事件的方式处理各类外设事件
  3. 异步处理架构:使用独立任务处理外设操作,避免阻塞主应用流程
  4. 统一的API接口:为不同类型的外设提供一致的抽象层接口
  5. 扩展性设计:支持自定义外设的集成

架构位置

esp_peripherals组件在ESP-ADF架构中属于硬件抽象层,位于应用层和硬件驱动之间:

应用层
esp_peripherals
ESP-IDF驱动层
硬件

esp_peripherals组件包含两个主要部分:

  1. 核心框架:提供统一的外设管理机制和事件处理系统
  2. 具体外设实现:每种外设的特定实现,如按钮、触摸、SD卡等

核心特性

  • 动态外设注册:支持运行时动态添加和移除外设
  • 事件驱动模型:使用事件队列进行异步通信
  • 状态管理:跟踪外设的运行状态(初始化、运行中、暂停等)
  • 自动生命周期管理:负责外设的创建、初始化、运行和销毁
  • 定时器支持:为外设提供定时器功能
  • 线程安全设计:使用互斥锁保证多线程环境下的安全访问

接口分析

本章接口分析基于以下源文件:

  • /components/esp_peripherals/esp_peripherals.c:实现了外设管理的核心功能
  • /components/esp_peripherals/include/esp_peripherals.h:定义了外设管理的公共API接口

公共API概述

esp_peripherals组件提供了一系列API用于创建、管理和控制外设,可以分为以下几类:

1. 外设集合管理API

用于初始化、销毁和管理外设集合的API函数:

// 初始化外设集合
esp_periph_set_handle_t esp_periph_set_init(esp_periph_config_t *config);// 销毁外设集合
esp_err_t esp_periph_set_destroy(esp_periph_set_handle_t periph_set_handle);// 停止所有外设
esp_err_t esp_periph_set_stop_all(esp_periph_set_handle_t periph_set_handle);// 通过ID查找外设
esp_periph_handle_t esp_periph_set_get_by_id(esp_periph_set_handle_t periph_set_handle, int periph_id);// 获取外设集合的事件接口
audio_event_iface_handle_t esp_periph_set_get_event_iface(esp_periph_set_handle_t periph_set_handle);// 注册外设集合事件回调函数
esp_err_t esp_periph_set_register_callback(esp_periph_set_handle_t periph_set_handle, esp_periph_event_handle_t cb, void *user_context);// 获取外设集合的队列句柄
QueueHandle_t esp_periph_set_get_queue(esp_periph_set_handle_t periph_set_handle);// 初始化外设列表(无任务模式)
esp_err_t esp_periph_set_list_init(esp_periph_set_handle_t periph_set_handle);// 运行外设列表(无任务模式)
esp_err_t esp_periph_set_list_run(esp_periph_set_handle_t periph_set_handle, audio_event_iface_msg_t msg);// 销毁外设列表(无任务模式)
esp_err_t esp_periph_set_list_destroy(esp_periph_set_handle_t periph_set_handle);// 从外设集合中移除外设
esp_err_t esp_periph_remove_from_set(esp_periph_set_handle_t periph_set_handle, esp_periph_handle_t periph);// 分配外设ID
esp_err_t esp_periph_alloc_periph_id(esp_periph_set_handle_t periph_set_handle, int *periph_id);// 更改外设集合等待时间
esp_err_t esp_periph_set_change_waiting_time(esp_periph_set_handle_t periph_set_handle, int time_ms);
2. 单个外设管理API

创建和管理单个外设的API函数:

// 创建外设实例
esp_periph_handle_t esp_periph_create(int periph_id, const char *tag);// 设置外设功能函数
esp_err_t esp_periph_set_function(esp_periph_handle_t periph,esp_periph_func init,esp_periph_run_func run,esp_periph_func destroy);// 启动外设
esp_err_t esp_periph_start(esp_periph_set_handle_t periph_set_handle, esp_periph_handle_t periph);// 停止外设
esp_err_t esp_periph_stop(esp_periph_handle_t periph);// 设置外设数据
esp_err_t esp_periph_set_data(esp_periph_handle_t periph, void *data);// 获取外设数据
void *esp_periph_get_data(esp_periph_handle_t periph);// 获取外设状态
esp_periph_state_t esp_periph_get_state(esp_periph_handle_t periph);// 获取外设ID
esp_periph_id_t esp_periph_get_id(esp_periph_handle_t periph);// 设置外设ID
esp_err_t esp_periph_set_id(esp_periph_handle_t periph, esp_periph_id_t periph_id);// 初始化外设
esp_err_t esp_periph_init(esp_periph_handle_t periph);// 运行外设
esp_err_t esp_periph_run(esp_periph_handle_t periph);// 销毁外设
esp_err_t esp_periph_destroy(esp_periph_handle_t periph);
3. 事件通信API

处理外设事件和命令的API函数:

// 发送外设事件
esp_err_t esp_periph_send_event(esp_periph_handle_t periph, int event_id, void *data, int data_len);// 发送命令到外设
esp_err_t esp_periph_send_cmd(esp_periph_handle_t periph, int cmd, void *data, int data_len);// 从中断发送命令到外设
esp_err_t esp_periph_send_cmd_from_isr(esp_periph_handle_t periph, int cmd, void *data, int data_len);// 注册事件回调
esp_err_t esp_periph_register_on_events(esp_periph_handle_t periph, esp_periph_event_t *evts);
4. 定时器管理API

管理外设定时器的API函数:

// 启动外设定时器
esp_err_t esp_periph_start_timer(esp_periph_handle_t periph, TickType_t interval_tick, timer_callback callback);// 停止外设定时器
esp_err_t esp_periph_stop_timer(esp_periph_handle_t periph);

数据结构

关键数据结构分析
  1. esp_periph:表示单个外设的结构体

    struct esp_periph {char                       *tag;           // 外设标签bool                        disabled;      // 是否禁用esp_periph_id_t             periph_id;     // 外设IDesp_periph_func             init;          // 初始化函数esp_periph_run_func         run;           // 运行函数esp_periph_func             destroy;       // 销毁函数esp_periph_state_t          state;         // 外设状态void                       *source;        // 源指针void                       *periph_data;   // 外设私有数据esp_periph_event_t         *on_evt;        // 事件处理TimerHandle_t               timer;         // 定时器句柄STAILQ_ENTRY(esp_periph)    entries;       // 链表项
    };
    
  2. esp_periph_sets:管理多个外设的集合

    typedef struct esp_periph_sets {EventGroupHandle_t                              state_event_bits;  // 状态事件位xSemaphoreHandle                                lock;              // 互斥锁int                                             task_stack;        // 任务栈大小int                                             task_prio;         // 任务优先级int                                             task_core;         // 任务核心audio_thread_t                                  audio_thread;      // 音频线程bool                                            ext_stack;         // 外部栈标志bool                                            run;               // 运行标志esp_periph_event_t                              event_handle;      // 事件处理int                                             periph_dynamic_id; // 动态IDSTAILQ_HEAD(esp_periph_list_item, esp_periph)   periph_list;       // 外设链表
    } esp_periph_set_t;
    
  3. esp_periph_event:外设事件结构

    // 外设事件回调函数类型定义
    typedef esp_err_t (*esp_periph_event_handle_t)(audio_event_iface_msg_t *event, void *context);// 外设事件结构
    typedef struct esp_periph_event {void                           *user_ctx;   // 用户上下文esp_periph_event_handle_t       cb;         // 回调函数audio_event_iface_handle_t      iface;      // 事件接口
    } esp_periph_event_t;
    

    外设事件回调函数接收两个参数:

    • event:事件消息,包含事件类型、消息数据等
    • context:用户自定义上下文

    返回值类型为esp_err_t,表示事件处理的结果码

枚举类型
  1. esp_periph_id_t:定义外设的唯一标识符。每个外设类型都有一个唯一的ID,用于在创建和查找外设时标识它们。

    typedef enum {PERIPH_ID_BUTTON      = AUDIO_ELEMENT_TYPE_PERIPH + 1,  // 按钮外设PERIPH_ID_TOUCH       = AUDIO_ELEMENT_TYPE_PERIPH + 2,  // 触摸外设PERIPH_ID_SDCARD      = AUDIO_ELEMENT_TYPE_PERIPH + 3,  // SD卡外设PERIPH_ID_WIFI        = AUDIO_ELEMENT_TYPE_PERIPH + 4,  // WiFi外设PERIPH_ID_FLASH       = AUDIO_ELEMENT_TYPE_PERIPH + 5,  // Flash存储外设PERIPH_ID_AUXIN       = AUDIO_ELEMENT_TYPE_PERIPH + 6,  // 辅助输入外设PERIPH_ID_ADC         = AUDIO_ELEMENT_TYPE_PERIPH + 7,  // ADC外设PERIPH_ID_CONSOLE     = AUDIO_ELEMENT_TYPE_PERIPH + 8,  // 控制台外设PERIPH_ID_BLUETOOTH   = AUDIO_ELEMENT_TYPE_PERIPH + 9,  // 蓝牙外设PERIPH_ID_LED         = AUDIO_ELEMENT_TYPE_PERIPH + 10, // LED外设PERIPH_ID_SPIFFS      = AUDIO_ELEMENT_TYPE_PERIPH + 11, // SPIFFS文件系统外设PERIPH_ID_ADC_BTN     = AUDIO_ELEMENT_TYPE_PERIPH + 12, // ADC按钮外设PERIPH_ID_IS31FL3216  = AUDIO_ELEMENT_TYPE_PERIPH + 13, // IS31FL3216 LED驱动器外设PERIPH_ID_GPIO_ISR    = AUDIO_ELEMENT_TYPE_PERIPH + 14, // GPIO中断服务外设PERIPH_ID_WS2812      = AUDIO_ELEMENT_TYPE_PERIPH + 15, // WS2812 LED驱动器外设PERIPH_ID_AW2013      = AUDIO_ELEMENT_TYPE_PERIPH + 16, // AW2013 LED驱动器外设PERIPH_ID_LCD         = AUDIO_ELEMENT_TYPE_PERIPH + 17, // LCD显示器外设PERIPH_ID_CUSTOM_BASE = AUDIO_ELEMENT_TYPE_PERIPH + 18  // 自定义外设的基础ID
    } esp_periph_id_t;
    
  2. esp_periph_state_t:定义外设的状态,用于跟踪外设在生命周期中的不同阶段。

    typedef enum {PERIPH_STATE_NULL,     // 初始状态,外设未初始化PERIPH_STATE_INIT,     // 外设已创建但未初始化PERIPH_STATE_RUNNING,  // 外设运行中PERIPH_STATE_PAUSE,    // 外设暂停PERIPH_STATE_STOPPING, // 外设正在停止PERIPH_STATE_ERROR,    // 外设错误状态PERIPH_STATE_STATUS_MAX, // 状态值上限,用于验证
    } esp_periph_state_t;
    

配置选项

esp_peripherals组件提供了以下配置选项:

typedef struct {int                         task_stack;    // 服务任务栈大小int                         task_prio;     // 服务任务优先级int                         task_core;     // 服务任务运行核心bool                        extern_stack;  // 是否使用外部RAM分配栈
} esp_periph_config_t;

默认配置为:

#define DEFAULT_ESP_PERIPH_SET_CONFIG() {.task_stack         = DEFAULT_ESP_PERIPH_STACK_SIZE,   // 4KB.task_prio          = DEFAULT_ESP_PERIPH_TASK_PRIO,    // 5.task_core          = DEFAULT_ESP_PERIPH_TASK_CORE,    // 0.extern_stack       = false,
}
外设集合初始化时序图

下面的时序图展示了外设集合的初始化、外设添加和启动过程:

应用程序 外设集合API 外设 API 具体外设实现 esp_periph_config_t periph_cfg = DEFAULT_ESP_PERIPH_SET_CONFIG() esp_periph_set_init(&periph_cfg) 返回 esp_periph_set_handle_t periph_button_init(&button_cfg) esp_periph_create() 返回 esp_periph_handle_t 返回 button_handle esp_periph_set_register(set, button_handle, "button") 返回 ESP_OK esp_periph_start(button_handle) button->>init(button_handle) 返回 ESP_OK esp_periph_set_state(PERIPH_STATE_RUNNING) 返回 ESP_OK esp_periph_set_start(set) 创建外设事件处理任务 返回 ESP_OK 应用程序 外设集合API 外设 API 具体外设实现

实现原理

初始化流程

esp_peripherals组件的初始化流程如下:

应用程序 外设集合 外设 esp_periph_set_init() 返回periph_set_handle esp_periph_create() 返回periph_handle esp_periph_set_function() esp_periph_start() 添加到外设列表 创建任务处理外设事件 调用init函数 初始化完成 外设启动完成 应用程序 外设集合 外设
  1. 外设集合初始化

    • 通过esp_periph_set_init()创建外设集合
    • 初始化内部数据结构,如事件组、互斥锁等
    • 安装GPIO中断服务
    • 初始化事件接口
  2. 外设创建与注册

    • 使用esp_periph_create()创建外设实例
    • 通过esp_periph_set_function()设置外设的初始化、运行和销毁函数
    • 通过esp_periph_start()将外设添加到外设集合并启动
  3. 外设初始化

    • 外设任务会遍历外设列表
    • 对状态为PERIPH_STATE_INIT的外设调用其初始化函数
    • 初始化成功后,将状态更新为PERIPH_STATE_RUNNING

核心算法

esp_peripherals的核心是一个事件驱动的循环,在独立任务中运行:

/*** @brief 外设集合的主任务函数* * 这个函数作为一个独立任务运行,负责管理外设集合中所有外设的生命周期。* 它会初始化新添加的外设,处理外设事件,并在任务结束时清理资源。* * @param[in] pv  传入的外设集合句柄(esp_periph_set_handle_t),在任务创建时作为参数传入*/
static void esp_periph_task(void *pv)
{esp_periph_handle_t periph;esp_periph_set_handle_t periph_set_handle = (esp_periph_set_handle_t)pv;// 记录日志并更新事件组标志位,标记任务已开始运行ESP_LOGD(TAG, "esp_periph_task is running, handle:%p", periph_set_handle);xEventGroupSetBits(periph_set_handle->state_event_bits, STARTED_BIT);xEventGroupClearBits(periph_set_handle->state_event_bits, STOPPED_BIT);// 主循环,直到收到停止信号(periph_set_handle->run = false)while (periph_set_handle->run) {// 遍历并初始化所有处于初始状态的外设mutex_lock(periph_set_handle->lock);  // 加锁保护外设列表STAILQ_FOREACH(periph, &periph_set_handle->periph_list, entries) {// 跳过已禁用的外设if (periph->disabled) {continue;}// 只初始化处于初始状态且有初始化函数的外设if (periph->state == PERIPH_STATE_INIT && periph->init) {ESP_LOGD(TAG, "PERIPH[%s]->init", periph->tag);// 调用外设的初始化函数并更新状态if (periph->init(periph) == ESP_OK) {periph->state = PERIPH_STATE_RUNNING;  // 初始化成功} else {periph->state = PERIPH_STATE_ERROR;    // 初始化失败}}}mutex_unlock(periph_set_handle->lock);  // 解锁// 阻塞等待命令消息,直到收到新命令或外设事件audio_event_iface_waiting_cmd_msg(esp_periph_set_get_event_iface(periph_set_handle));}// 任务结束前的清理工作STAILQ_FOREACH(periph, &periph_set_handle->periph_list, entries) {// 停止所有外设的定时器esp_periph_stop_timer(periph);// 调用外设的销毁函数释放资源if (periph->destroy) {periph->destroy(periph);}}// 更新事件组标志位,标记任务已停止xEventGroupClearBits(periph_set_handle->state_event_bits, STARTED_BIT);xEventGroupSetBits(periph_set_handle->state_event_bits, STOPPED_BIT);// 删除当前任务vTaskDelete(NULL);
}

外设任务执行时序图

应用程序 esp_periph_set_init esp_periph_task 外设列表 事件队列 外设函数(init/destroy) 创建外设集合 创建任务(xTaskCreate) 任务启动 设置 STARTED_BIT 加锁并遍历外设列表 调用 periph->>init(periph) 返回初始化结果 periph->>state = PERIPH_STATE_RUNNING periph->>state = PERIPH_STATE_ERROR alt [初始化成功] [初始化失败] alt [外设处于初始状态且有init函数] loop [遍历每个外设] 解锁 阻塞等待命令消息 收到命令或事件 loop [主循环 (periph_set_handle->>run == true)] 任务结束清理 停止定时器 调用 periph->>destroy(periph) 返回销毁结果 alt [外设有destroy函数] loop [遍历每个外设] 设置 STOPPED_BIT vTaskDelete(NULL) 应用程序 esp_periph_set_init esp_periph_task 外设列表 事件队列 外设函数(init/destroy)

这个算法的关键点包括:

  1. 使用一个无限循环不断处理外设事件
  2. 初始化新添加的外设
  3. 等待命令消息(阻塞模式)
  4. 在接收到停止信号时,清理所有外设资源

状态管理

esp_peripherals组件使用状态机来管理外设的生命周期:

创建外设
初始化成功
初始化失败
暂停
恢复
停止请求
销毁
PERIPH_STATE_NULL
PERIPH_STATE_INIT
PERIPH_STATE_RUNNING
PERIPH_STATE_ERROR
PERIPH_STATE_PAUSE
PERIPH_STATE_STOPPING

状态转换通过以下方式实现:

  • 初始化函数成功返回时,状态从INIT转为RUNNING
  • 初始化失败时,状态变为ERROR
  • 调用esp_periph_stop()时,状态变为STOPPING

事件处理

事件类型

esp_peripherals组件中的事件分为两种类型:

  1. 命令事件(CMD):发送给外设的命令,例如控制外设行为
  2. 普通事件(EVENT):外设产生的事件,报告状态变化或响应

每个外设可以定义自己的事件类型和ID,通常在头文件中声明。

事件流向

事件在esp_peripherals组件中的流向如下图所示:

产生事件
发送命令
分发事件
处理事件
外设
事件队列
应用程序
事件处理回调

事件处理流程:

  1. 外设通过esp_periph_send_event()函数发送事件
  2. 事件被放入事件队列
  3. 外设任务从队列中取出事件
  4. 根据事件类型调用相应的处理函数
  5. 将处理结果通知应用程序

回调机制

esp_peripherals提供了两种回调机制:

  1. 外设级回调:通过esp_periph_register_on_events()注册,仅处理特定外设的事件
  2. 全局回调:通过esp_periph_set_register_callback()注册,处理所有外设的事件

回调函数原型:

typedef esp_err_t (*esp_periph_event_handle_t)(audio_event_iface_msg_t *event, void *context);

与其他模块交互

依赖模块

esp_peripherals组件依赖以下ESP-ADF和ESP-IDF模块:

  1. ESP-ADF核心模块

    • audio_event_iface:事件接口
    • audio_mutex:互斥锁
    • audio_thread:线程管理
    • audio_mem:内存管理
  2. ESP-IDF驱动模块

    • gpio:GPIO控制
    • freertos:任务和队列管理
    • esp_log:日志系统

被依赖关系

esp_peripherals组件被以下ESP-ADF模块依赖:

  1. 播放器组件:使用外设控制播放
  2. 管道组件:与外设集成实现数据流控制
  3. 应用层示例:直接使用外设实现用户交互

交互流程

一个典型的esp_peripherals使用流程如下:

应用程序 外设子系统 硬件驱动 创建外设集合(esp_periph_set_init) 创建外设(esp_periph_create) 设置外设函数(esp_periph_set_function) 注册全局回调(esp_periph_set_register_callback) 启动外设(esp_periph_start) 初始化硬件 硬件就绪 硬件事件(中断) 事件通知(回调) 发送命令(esp_periph_send_cmd) 控制硬件 停止外设(esp_periph_stop) 销毁外设集合(esp_periph_set_destroy) 应用程序 外设子系统 硬件驱动

使用示例

基础使用

以下是esp_peripherals的基本使用示例:

// 初始化外设集合
esp_periph_config_t periph_cfg = DEFAULT_ESP_PERIPH_SET_CONFIG();
esp_periph_set_handle_t periph_set = esp_periph_set_init(&periph_cfg);// 创建和启动按钮外设
audio_board_key_init(periph_set);// 注册回调函数
esp_periph_set_register_callback(periph_set, periph_callback, NULL);// 在应用程序中使用事件循环
while (1) {audio_event_iface_msg_t msg;esp_err_t ret = audio_event_iface_listen(esp_periph_set_get_event_iface(periph_set), &msg, portMAX_DELAY);if (ret != ESP_OK) {continue;}// 处理消息if (msg.source_type == AUDIO_ELEMENT_TYPE_PERIPH) {// 处理外设消息}
}// 清理资源
esp_periph_set_destroy(periph_set);

高级场景

以下是组合多个外设的高级示例:

// 初始化外设集合
esp_periph_config_t periph_cfg = DEFAULT_ESP_PERIPH_SET_CONFIG();
esp_periph_set_handle_t periph_set = esp_periph_set_init(&periph_cfg);// 创建和启动多个外设
audio_board_key_init(periph_set);         // 按钮
audio_board_sdcard_init(periph_set, SD_MODE_1_LINE); // SD卡
periph_wifi_cfg_t wifi_cfg = { /*配置*/ };
periph_wifi_init(&wifi_cfg, periph_set);  // WiFi// 注册回调函数
esp_periph_set_register_callback(periph_set, periph_callback, NULL);// 在回调函数中实现外设联动
esp_err_t periph_callback(audio_event_iface_msg_t *event, void *context)
{if (event->source_type == AUDIO_ELEMENT_TYPE_PERIPH) {if (event->source == (void*)periph_set_get_by_id(periph_set, PERIPH_ID_BUTTON)) {// 按钮事件处理if (event->cmd == PERIPH_BUTTON_PRESSED) {// 可以控制其他外设periph_wifi_connect(periph_set_get_by_id(periph_set, PERIPH_ID_WIFI));}} else if (event->source == (void*)periph_set_get_by_id(periph_set, PERIPH_ID_WIFI)) {// WiFi事件处理}}return ESP_OK;
}

最佳实践

性能优化

  1. 合理配置任务参数

    • 根据外设数量和处理复杂度调整任务栈大小
    • 设置适当的任务优先级,避免优先级过高抢占关键任务
  2. 避免长时间处理

    • 外设事件处理函数应该尽量简短
    • 复杂处理应放在单独的任务中进行
  3. 减少事件频率

    • 对高频事件进行去抖动或合并处理
    • 使用定时器减少不必要的事件触发

常见问题

  1. 外设初始化失败

    • 检查硬件连接
    • 确认驱动是否正确加载
    • 检查配置参数是否正确
  2. 事件回调不触发

    • 确认外设已正确启动
    • 检查事件回调是否已注册
    • 验证事件队列是否正常工作
  3. 外设状态错误

    • 检查外设生命周期管理
    • 确保正确调用启动和停止函数
    • 避免重复初始化或销毁

注意事项

  1. 线程安全考虑

    • 外设回调可能在不同线程中执行
    • 访问共享资源时需使用互斥锁
  2. 资源管理

    • 确保所有创建的外设最终都被销毁
    • 使用esp_periph_set_destroy自动清理所有外设资源
  3. 事件处理顺序

    • 事件处理顺序不保证,不应依赖特定顺序
    • 如有顺序依赖,应在应用层实现

总结

设计评估

esp_peripherals组件采用了以下设计模式和技术:

  1. 观察者模式:使用事件回调机制实现外设状态变化通知
  2. 工厂模式:统一的外设创建接口
  3. 状态模式:使用状态机管理外设生命周期
  4. 命令模式:通过命令队列控制外设行为

设计优点:

  • 统一的接口简化了不同外设的使用
  • 事件驱动模型提高了系统响应性
  • 良好的扩展性支持自定义外设开发

设计不足:

  • 事件处理模型增加了系统复杂度
  • 异步处理可能导致调试困难
  • 对资源受限的系统,任务和队列开销较大

相关文章:

ESP-ADF外设子系统深度解析:esp_peripherals组件架构与核心设计(系列开篇)

目录 ESP-ADF外设子系统深度解析:esp_peripherals组件架构与核心设计(系列开篇)简介模块概述功能定义架构位置核心特性 接口分析公共API概述1. 外设集合管理API2. 单个外设管理API3. 事件通信API4. 定时器管理API 数据结构关键数据结构分析枚…...

供应链管理:供应链管理的边界

一、追根溯源,什么是真正的财富 序号财富解释1土地作为生产资料,土地是农业、工业、商业的基础 城市中心的土地因稀缺性而价值连城,农业土地的肥沃程度直接影响粮食产量。2资源、矿产提供能源和原材料,支撑工业生产和经济发展。 …...

【Linux网络编程】TCP Echo Server的实现

本文专栏:linux网络编程 本文的基础知识是基于上篇文章:UDP Echo Server的实现 传送门: 【Linux网络编程】UDP Echo Server的实现 -CSDN博客 目录 一,InetAddr类的编写 二,客户端代码编写 创建套接字(s…...

信奥赛CSP-J复赛集训(数学思维专题)(11):P9585 「MXOI Round 2」酒店

信奥赛CSP-J复赛集训(数学思维专题)(11):P9585 「MXOI Round 2」酒店 题目描述 小 C 开了一家酒店,叫做 CC Hotel。 一天,CC Hotel 来了 n n n 位客人。小 C 需要把他们都安排在酒店的某一层…...

python: audioFlux XXCC 提取梅尔频率倒谱系数 MFCC

承上一篇:python:audioFlux 使用教程 XXCC: 倒谱系数,支持所有频谱类型. 可以提取梅尔频率倒谱系数(MFCC) Cepstrum coefficients, supports all spectrum types. 以下是使用 audioflux 库中 XXCC 类计算倒谱系数…...

PHP + Go 如何协同打造高并发微服务?

为什么需要 PHP Go 协同? 在微服务架构中,PHP 和 Go 看似是“两个世界”的语言,但它们的互补性极强: PHP:开发效率高、生态成熟,适合快速实现复杂业务逻辑(如电商订单、用户系统)…...

k8s工具使用

Kubectl Cheat Sheet k8s的命令级别 1.基础命令(初级) 2.基础命令(中级) 3.部署命令 4.集群管理命令 5.故障排查和调试命令 6.高级命令 7.设置命令 8.其它命令 命令行提示 为了使用kubectl命令更加高效,我们可以选择安装一下开源软件来增加操作kubectl命令的快捷方式,同…...

uml制做参考-以代码画UML图

【PlantUML系列】类图(一)_plantuml skin-CSDN博客 UML入门以及Plant UML工具介绍_plantuml-CSDN博客 UML类图详解-CSDN博客 【PlantUML】-类图-CSDN博客 【掌握绘图艺术】用PlantUML绘制完美UML图表,编程开发者的福音 - 知乎 如何优化P…...

深入解析B站androidApp接口:从bilibili.api.ticket.v1.Ticket/GetTicket到SendMsg的技术分析

前言 最近一段时间,我对B站的App接口进行了深入分析,特别是关注了认证机制和私信功能的实现。通过逆向工程和网络抓包,发现了B站移动端API的底层工作原理,包括设备标识生成机制、认证流程和消息传输协议。本文将分享这些研究成果…...

[AI ][Dify] 构建一个自动化新闻编辑助手:Dify 工作流实战指南

在内容创作行业中,自动化辅助工具已成为提升编辑效率的重要利器。本文将通过 Dify 平台,演示如何构建一个**“新闻编辑助手”**,实现从网页抓取、文本翻译、标题生成,到新闻配图的全流程自动化。 🎯 目标概览 这个工作流旨在实现如下功能: 从指定网页抓取新闻内容; 使…...

Unity中国战略调整简讯:Unity6下架 团结引擎接棒

Unity中国战略调整简讯:Unity6下架 团结引擎接棒 免费版 2025年4月9日 —— Unity中国宣布自即日起,中国大陆及港澳地区停止提供Unity 6及后续版本下载与服务,相关功能由国产引擎“团结引擎”承接。国际版2022 LTS及更早版本仍由Unity中国维护…...

司美格鲁肽用SNAC市场报告:2024年全球市场销售额达到了0.14亿美元

引言:了解司美格鲁肽与SNAC的重要性 在当前的医药领域,司美格鲁肽(Semaglutide)作为一种创新性的治疗2型糖尿病和肥胖症的药物,受到了广泛关注。而SNAC(N-(8-(2-羟苯基)…...

自动驾驶第一性原理

所谓的第一性原理: 就是指从最基本的物理规律,数据逻辑及工程约束条件出发,剥离所有的非本质的假设,直接推导出自动驾驶最核心的要素。 自动驾驶核心框架分解: 1、根本目标: 安全高效的将人/物从A地运送…...

《UE5_C++多人TPS完整教程》学习笔记36 ——《P37 拾取组件(Pickup Widget)》

本文为B站系列教学视频 《UE5_C多人TPS完整教程》 —— 《P37 拾取组件(Pickup Widget)》 的学习笔记,该系列教学视频为计算机工程师、程序员、游戏开发者、作家(Engineer, Programmer, Game Developer, Author) Steph…...

Uniswap V2/V3/V4 流动性与价格计算详解

Uniswap V2/V3/V4 流动性与价格计算详解 一、核心概念对比 Uniswap V2 流动性模型: 恒定乘积公式 (x * y = k)价格决定: 完全由池子中的代币数量决定 (price = y/x)流动性衡量: 总储备量代表流动性,直接通过 getReserves() 获取流动性分布: 均匀分布在所有价格点交易费用: 固…...

yum安装MySQL数据库

yum安装方式 优点:操作简单易用。不用单独下载,服务器可以联网且yum源没有问题即可(可以选择国内的163/阿里源) 安装步骤: 1.关闭防火墙和selinux: systemctl stop firewalld ##关闭防火墙 systemctl disable firewalld …...

大联盟(特别版)双端互动平台完整套件分享:含多模块源码+本地部署环境

这是一套结构清晰、功能完整的互动平台组件,适合有开发经验的技术人员进行模块参考、结构研究或本地部署实验使用。 该平台覆盖前端展示、后端服务、移动端资源以及完整数据库,采用模块化架构,整体部署流程简单清晰,适合自研团队参…...

【Code】《代码整洁之道》笔记-Chapter15-JUnit内幕

第15章 JUnit内幕 JUnit是最有名的Java框架之一。就像别的框架一样,它概念简单,定义精确,实现优雅。但它的代码是怎样的呢?本章将研判来自JUnit框架的一个代码例子。 15.1 JUnit框架 JUnit有很多位作者,但它始于K…...

【Java八股】

JVM JVM中有哪些引用 在Java中,引用(Reference)是指向对象的一个变量。Java中的引用不仅仅有常规的直接引用,还有不同类型的引用,用于控制垃圾回收(GC)的行为和优化性能。JVM中有四种引用类型…...

深入探究AI编程能力:ChatGPT及其大规模模型的实现原理

📢 友情提示: 本文由银河易创AI(https://ai.eaigx.com)平台gpt-4-turbo模型辅助创作完成,旨在提供灵感参考与技术分享,文中关键数据、代码与结论建议通过官方渠道验证。 随着人工智能的快速发展&#xff0c…...

高德地图 JS-SDK 实现教程

高德地图 JS-SDK 实现教程:定位、地图选点、地址解析等 适用地点选择、地址显示、表单填写等场景,全面支持移动端、手机浏览器和 PC端环境 一、创建应用&Key 前端(JS-SDK、地图组件) 登陆 高德开放平台创建应用,…...

【信息系统项目管理师】高分论文:论信息系统项目的整合管理(银行数据仓库项目)

更多内容请见: 备考信息系统项目管理师-专栏介绍和目录 文章目录 正文一、制定项目章程二、制定项目管理计划三、指导和管理项目的实施四、管理项目知识五、监控项目工作六、实施整体变更控制七、结束项目或阶段正文 2023年6月,我以项目经理的身份,参加了 xx银行xx省分行数…...

dev中使用auto的方法

在dev-c中使用新特性是一样的道理,在他启动编译器来编译代码的时候我们让他加上这个参数就行了,设置方法是:在Tools里面找到Compiler Options打开它,然后把那个Add the following commands when calling compiler:选上勾,在里面加…...

C 语言中经典的数据结构

在 C 语言中,经典的数据结构通常包括以下几种,每种都有其特定的应用场景和实现方式: 1. 数组(Array) 定义:连续内存空间存储相同类型的数据。 特点:随机访问快(O(1))&am…...

小白学习java第12天(下):网络编程

上面我们了解TCP就是三次握手!!! 下面我们就详细介绍一下UDP,就是进行发包(TCP协议类似于就是打电话,你必须进行连接才能进行传输,但是UDP类似于发消息,连不连接我都是可以的&#…...

论文精度:双分支图Transformer网络:视频驱动的3D人体网格重建新突破

论文地址:https://arxiv.org/pdf/2412.01179 目录 一、背景与问题定义 1.1 3D人体网格重建的意义 1.2 现有方法的困境 二、核心创新:DGTR网络架构 2.1 整体框架设计 2.2 全局运动感知分支(GMA) 2.3 局部细节优化分支(LDR) 2.3.1 局部信息聚合 2.3.2 调制图卷积…...

华三IRF堆叠技术

IRF(Intelligent Resilient Framework,智能弹性架构)是华三通信(H3C)自主研发的网络设备虚拟化技术,通过将多台物理设备整合为单一逻辑设备,实现统一管理、高可靠性和灵活扩展。以下是其核心要点…...

第一阶段补充知识

目录 书写脚本使用的相关知识? 备份和冗灾的区别?什么叫DD备份,什么叫DD冗灾? 关于Linux系统优化以及Linux的安全加固? 系统优化 硬件系统优化: 内核参数优化: 网络性能优化: 进程管…...

STM32 HAL库 L298N电机驱动模块实现

一、引言 在机器人、自动化设备等众多应用场景中,电机驱动是一个关键的部分。L298N 是一款常用的电机驱动模块,它可以驱动两个直流电机或一个步进电机。STM32F407 是一款高性能的 ARM Cortex-M4 内核微控制器,结合 HAL 库可以方便地实现对 L…...

Redis的Key的过期策略

我们都知道Redis的键值对是可以设置过期时间的,那么就会涉及到一个问题,Redis到底是如何做到响应快的同时有能快速地释放掉过期的键值对的呢?不卖关子了,直接说答案,那就是Redis两个策略:定期删除和惰性删除…...

ubuntu桌面版使用root账号进行登录

这里写自定义目录标题 第一步:给root账户设置密码,并切换至root账户第二步:注释gdm-autologin文件内的相关内容第三步:注释gdm-password文件内的相关内容第四步:重启系统即可使用root账户登录 第一步:给roo…...

贪心算法(18)(java)距离相等的条形码

在一个仓库里,有一排条形码,其中第 i 个条形码为 barcodes[i]。 请你重新排列这些条形码,使其中任意两个相邻的条形码不能相等。 你可以返回任何满足该要求的答案,此题保证存在答案。 示例 1: 输入:barco…...

CentOS服务器能ping通却无法yum install:指定镜像源解决

文章目录 前言一、问题记录二、解决过程1.修改DNS无效2.指定镜像源 总结 前言 今天有一个项目现场要在一个远程centos服务器上部署产品服务,发现能ping通百度,但是无法yum install 安装基础软件包,开始以为DNS服务器的问题,结果配…...

WebSocket与MQTT

在物联网(IoT)领域,​WebSocket和MQTT确实都可以实现实时通信,但它们的核心设计目标、适用场景和角色存在显著差异。以下是两者的对比分析: ​1. 协议设计初衷​ ​WebSocket​ ​目标​:提供浏览器与服务器…...

【论文解读】MSVM-UNet: Multi-Scale Vision Mamba UNet for Medical Image Segmentation

MSVM-UNet: Multi-Scale Vision Mamba UNet for Medical Image Segmentation 论文链接: https://arxiv.org/abs/2408.13735 Code: https://github.com/gndlwch2w/msvm-unet 来源: 2024 IEEE International Conference on Bioinformatics an…...

Vue表单组件el-form校验规则rules,条件判断rules表单验证显示必填或非必填

在使用 Element UI&#xff08;一个基于 Vue 的前端框架&#xff09;的表单验证功能时&#xff0c;你可能想要实现一个规则&#xff0c;使得某些字段在特定条件下成为必填项&#xff0c;或者在满足某些条件时不允许为空。这通常通过自定义校验规则来实现。 <template>&l…...

手动关闭ArcGIS与ArcGIS Online连接的方法

【关闭软件启动时ArcGIS与ArcGIS Online连接方法】 打开C盘找到文件夹“C:\Program Files (x86)\Common Files\ArcGIS\bin”&#xff0c;如下图&#xff0c;删除“ArcGISConnection.exe”与“ArcGISConnectionTest.exe”文件&#xff0c;软件下次启动的时候就不会建立与ArcGIS …...

(二十五)安卓开发一个完整的登录页面-支持密码登录和手机验证码登录

下面将详细讲解如何在 Android 中开发一个完整的登录页面&#xff0c;支持密码登录和手机验证码登录。以下是实现过程的详细步骤&#xff0c;从布局设计到逻辑实现&#xff0c;再到用户体验优化&#xff0c;逐步展开。 1. 设计登录页面布局 首先&#xff0c;我们需要设计一个用…...

【过程控制系统】PID算式实现,控制系统分类,工程应用中控制系统应该注意的问题

目录 1-1 试简述过程控制的发展概况及各个阶段的主要特点。 1-2 与其它自动控制相比&#xff0c;过程控制有哪些优点&#xff1f;为什么说过程控制的控制过程多属慢过程&#xff1f; 1-3 什么是过程控制系统&#xff0c;其基本分类是什么&#xff1f; 1-4 何为集散控制系统…...

测试第三课-------自动化测试相关

作者前言 &#x1f382; ✨✨✨✨✨✨&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f382; ​&#x1f382; 作者介绍&#xff1a; &#x1f382;&#x1f382; &#x1f382; &#x1f389;&#x1f389;&#x1f389…...

关于数据清洗和数据处理实践学习笔记

一些可能想要知道的&#xff1a; pandas是一个模板&#xff0c;它读取的数据都是dataframe的格式&#xff0c;即df Matplotlib是一个用于数据可视化的Python库&#xff0c;能够生成各种静态、动态和交互式图表 pyplot&#xff1a;Matplotlib 的核心接口模块&#xff0c;提供类…...

ubuntu学习day2

linux常用命令 3.文件查看及处理命令 3.1查看文件内容 cat[选项][文件] -b 对非空输出行编号 -E 在每行结束处显示$ -n 对输出的所有行编号 -s 不输出多行空行 标准输入、标准输出和标准错误 在 Linux 中&#xff0c;每个进程默认有三个文件描述符&#xff1a; 标准输入&…...

JavaScript `new Date()` 方法移动端 `兼容 ios`,ios环境new Date()返回NaN

在 iOS 环境下&#xff0c;new Date() 方法会返回 NaN&#xff0c;这通常是由于时间字符串的格式问题。iOS 的 Date 构造函数对时间字符串的格式要求比其他平台更严格。 原因&#xff1a;ios端不兼容“-”为连接符的时间。 解决办法&#xff1a; 替换时间格式 IOS 不支持某…...

考研408参考用书:计算机组成原理(唐朔飞)介绍,附pdf

我用夸克网盘分享了「《计算机组成原理》第2,3版 唐朔飞」&#xff0c; 链接&#xff1a;https://pan.quark.cn/s/6a87d10274a3 1. 书籍定位与适用对象 定位&#xff1a;计算机组成原理是计算机科学与技术、软件工程等专业的核心基础课程&#xff0c;涉及计算机硬件的底层工作原…...

案例-索引对于并发Insert性能优化测试

前言 最近因业务并发量上升&#xff0c;开发反馈对订单表Insert性能降低。应开发要求对涉及Insert的表进行分析并提供优化方案。   一般对Insert 影响基本都在索引&#xff0c;涉及表已按创建日期做了分区表&#xff0c;索引全部为普通索引未做分区索引。 优化建议&#xff…...

@Async 为什么要自定义线程池,使用默认线程池风险

为什么要自定义线程池而非使用默认线程池 使用Spring的Async注解时&#xff0c;如果不自定义线程池而使用默认线程池&#xff0c;可能会带来一些风险和问题。以下是主要原因&#xff1a; 默认线程池的风险 无限制的资源消耗 默认线程池使用SimpleAsyncTaskExecutor&#xff0…...

Spark-SQL简介与编程

1. Spark-SQL是什么 Spark SQL 是 Spark 用于结构化数据(structured data)处理的 Spark 模块。 Hadoop与Spark的对比 Hadoop的局限性 Hadoop无法处理结构化数据&#xff0c;导致一些项目无法推进。 例如&#xff0c;MySQL中的数据是结构化的&#xff0c;Hadoop无法直接处理。…...

如何分析 JVM OOM 内存溢出 Dump 快照日志

文章目录 1、需求背景2、OOM 触发3、Dump 日志分析 1、需求背景 企业开发过程中&#xff0c;如果系统服务客户量比较大&#xff0c;偶尔会出现OOM内存溢出问题&#xff0c;导致服务发生宕机&#xff0c;停止对外提供访问。 这种情况就需要排查定位内存溢出的原因&#xff08;…...

系统监控 | 简易多个内网服务器的CPU和内存使用率监控 system_moniter

效果图 原理 一台主机A上运行mysql数据库&#xff0c;接收数据。 其他主机设置定时任务&#xff0c;每6分钟发送一次自己的CPU和内存使用百分数到主机A。 主机A上提供flask为后台的可视化网页&#xff0c;见上图。 源码库 https://github.com/BioMooc/system_moniterhttps:/…...

【神经网络】python实现神经网络(四)——误差反向传播的基础理论

一.反向传播 本章将介绍能够高效计算权重参数的梯度的方法——误差反向传播法,这里简单介绍一下什么是反向传播,加入有个函数y = f(x),那么它的反向传播为图下这个样子: 反向传播的计算顺序是,将输入信号E乘以节点的局部导数,然后将结果传递给下一个节点。这里所…...