ESP-ADF外设子系统深度解析:esp_peripherals组件架构与核心设计(输入类外设之按键Button)
ESP-ADF外设子系统深度解析:esp_peripherals组件架构与核心设计(输入类外设之按键Button)
版本信息: ESP-ADF v2.7-65-gcf908721
简介
本文档详细分析ESP-ADF中的输入类外设实现机制,包括按键(button)、触摸(touch)和ADC按键(adc_button)等输入类外设的设计模式、接口规范、初始化流程和事件处理机制。ESP-ADF输入类外设基于统一的外设框架设计,通过事件驱动模型实现用户输入的检测和处理,为应用程序提供了灵活且易用的输入接口。
模块概述
功能定义
ESP-ADF输入类外设主要负责检测和处理用户的物理输入操作,将物理信号转换为应用程序可处理的事件。主要功能包括:
- 物理输入信号检测(按键按下/释放、触摸触发/释放、ADC电平变化等)
- 输入事件生成(短按、长按、触摸等事件)
- 事件过滤和防抖处理
- 向应用程序传递输入事件
架构位置
输入类外设是ESP-ADF外设子系统的重要组成部分,位于硬件驱动层和应用层之间:
核心特性
- 多种输入类型支持:支持GPIO按键、电容触摸、ADC按键等多种输入方式
- 统一事件模型:所有输入外设使用统一的事件模型和接口
- 丰富的事件类型:支持按下、释放、长按、长按释放等多种事件类型
- 防抖处理:内置输入信号防抖处理机制
- 可配置参数:支持灵活配置长按时间、触发阈值等参数
- 中断和轮询结合:结合中断和定时器轮询提高响应速度和可靠性
按键(Button)外设
按键外设概述
按键外设基于GPIO实现,支持多个按键同时使用,可以检测按下、释放、长按等多种按键事件。按键外设通过中断和定时器相结合的方式检测按键状态变化,并生成相应的事件。
按键外设实现分为两个层次:
-
外设层:负责将按键集成到ESP-ADF外设系统中,处理事件分发和生命周期管理。
- 头文件:
components/esp_peripherals/include/periph_button.h
- 实现文件:
components/esp_peripherals/periph_button.c
- 头文件:
-
底层驱动层:提供底层按键驱动,负责GPIO配置、中断处理和按键状态检测。
- 头文件:
components/esp_peripherals/lib/button/button.h
- 实现文件:
components/esp_peripherals/lib/button/button.c
- 头文件:
按键外设层次架构图
按键外设API和数据结构
外设层API
源文件:components/esp_peripherals/include/periph_button.h
和components/esp_peripherals/periph_button.c
公共API
// 按键外设初始化函数
esp_periph_handle_t periph_button_init(periph_button_cfg_t* but_cfg);// 按键配置结构体
typedef struct {uint64_t gpio_mask; // GPIO掩码,使用BIT(GPIO_NUM)表示,如GPIO_SEL_36int long_press_time_ms; // 长按时间阈值,默认为2000ms
} periph_button_cfg_t;// 按键事件类型
typedef enum {PERIPH_BUTTON_UNCHANGE = 0, // 无事件PERIPH_BUTTON_PRESSED, // 按键按下PERIPH_BUTTON_RELEASE, // 按键释放PERIPH_BUTTON_LONG_PRESSED, // 长按PERIPH_BUTTON_LONG_RELEASE, // 长按后释放
} periph_button_event_id_t;
内部数据结构
// 按键外设内部结构体 (定义在periph_button.c中)
typedef struct {esp_button_handle_t btn; // 底层按键驱动句柄uint64_t gpio_mask; // GPIO掩码int long_press_time_ms; // 长按时间阈值
} periph_button_t;// 按键结果结构体 (定义在底层按键驱动中)
typedef struct {uint64_t press_mask; // 按下的按键掩码uint64_t release_mask; // 释放的按键掩码uint64_t long_press_mask; // 长按的按键掩码uint64_t long_release_mask; // 长按后释放的按键掩码
} button_result_t;
底层按键驱动API
源文件:components/esp_peripherals/lib/button/button.h
和components/esp_peripherals/lib/button/button.c
公开API (button.h)
// 按键状态枚举
typedef enum {BTN_UNCHANGE = 0,BTN_PRESSED,BTN_RELEASE,BTN_LONG_PRESS,BTN_LONG_RELEASE,
} button_status_t;// 按键结果结构体
typedef struct {uint64_t press_mask;uint64_t release_mask;uint64_t long_press_mask;uint64_t long_release_mask;
} button_result_t;// 按键驱动句柄
typedef struct esp_button *esp_button_handle_t;// GPIO中断处理函数类型
typedef void (*gpio_intr_handler)(void *);// 按键配置结构体
typedef struct {int long_press_time_ms; // 长按时间阈值uint64_t gpio_mask; // GPIO掩码gpio_intr_handler button_intr_handler; // 中断处理函数void *intr_context; // 中断上下文
} button_config_t;// 默认长按时间
#define DEFAULT_LONG_PRESS_TIME_MS (2*1000)// 初始化按键驱动
esp_button_handle_t button_init(button_config_t *config);// 读取按键状态
bool button_read(esp_button_handle_t button, button_result_t *result);// 销毁按键驱动
esp_err_t button_destroy(esp_button_handle_t button);
内部实现 (button.c)
// 按键项目结构体(单个按键,内部实现)
typedef struct esp_button_item {int gpio_num; // GPIO编号long long last_press_tick; // 上次按下时间bool long_pressed; // 长按标志STAILQ_ENTRY(esp_button_item) entry; // 链表项
} esp_button_item_t;// 按键控制结构体(内部实现)
struct esp_button {int long_press_time_ms; // 长按时间阈值uint64_t gpio_mask; // GPIO掩码STAILQ_HEAD(esp_button_list, esp_button_item) btn_list; // 按键链表
};// 获取按键状态(内部函数)
static button_status_t button_get_state(esp_button_handle_t button, esp_button_item_t *btn_item);
按键外设配置选项
- gpio_mask: 指定使用的GPIO引脚,可以同时配置多个引脚
- long_press_time_ms: 长按时间阈值,默认为2000ms
底层驱动配置选项
- long_press_time_ms: 长按时间阈值,与外设层相同
- gpio_mask: 指定使用的GPIO引脚掩码,与外设层相同
- button_intr_handler: GPIO中断处理函数,当GPIO状态变化时调用
- intr_context: 中断处理函数的上下文参数,通常传入外设句柄
注意:外设层的配置选项会传递给底层驱动,并自动设置中断处理函数和上下文参数。应用程序只需要配置外设层的参数即可。
按键外设初始化流程
按键外设的初始化流程涉及两个层次:外设层(Peripheral Layer)和底层驱动(Driver Layer)。下面分别介绍这两个层次的初始化过程。
外设层初始化过程(periph_button.c)
外设层初始化主要通过periph_button_init
函数(位于periph_button.c
)完成,主要包括以下步骤:
- 创建外设句柄:调用
esp_periph_create
函数创建外设句柄 - 分配内部数据结构:分配
periph_button_t
结构体内存 - 设置配置参数:设置GPIO掩码和长按时间阈值
- 注册回调函数:设置初始化、运行和销毁回调函数
// 文件:components/esp_peripherals/periph_button.c
esp_periph_handle_t periph_button_init(periph_button_cfg_t *config)
{// 1. 创建外设句柄esp_periph_handle_t periph = esp_periph_create(PERIPH_ID_BUTTON, "periph_btn");AUDIO_MEM_CHECK(TAG, periph, return NULL);// 2. 分配内部数据结构periph_button_t *periph_btn = audio_calloc(1, sizeof(periph_button_t));AUDIO_MEM_CHECK(TAG, periph_btn, {audio_free(periph);return NULL;});// 3. 设置配置参数periph_btn->gpio_mask = config->gpio_mask;periph_btn->long_press_time_ms = config->long_press_time_ms;// 4. 注册回调函数esp_periph_set_data(periph, periph_btn);esp_periph_set_function(periph, _button_init, _button_run, _button_destroy);return periph;
}
当外设被添加到外设集合并启动时,会调用_button_init
函数(位于periph_button.c
),该函数负责初始化底层按键驱动并启动定时器:
// 文件:components/esp_peripherals/periph_button.c
static esp_err_t _button_init(esp_periph_handle_t self)
{// 验证按键外设VALIDATE_BTN(self, ESP_FAIL);// 获取按键外设数据periph_button_t *periph_btn = esp_periph_get_data(self);// 准备底层驱动配置button_config_t btn_config = {.gpio_mask = periph_btn->gpio_mask,.long_press_time_ms = periph_btn->long_press_time_ms,.button_intr_handler = button_intr_handler,.intr_context = self,};// 调用底层驱动初始化函数periph_btn->btn = button_init(&btn_config);// 启动定时器用于按键状态检测esp_periph_start_timer(self, 50/portTICK_RATE_MS, button_timer_handler);return ESP_OK;
}
底层驱动初始化过程(button.c)
底层按键驱动初始化通过button_init
函数(位于button.c
)完成,主要包括以下步骤:
- 分配按键驱动结构体:分配
esp_button
结构体内存 - 设置按键参数:设置GPIO掩码和长按时间阈值
- 配置GPIO:设置GPIO为输入模式,启用上拉电阻,设置中断类型
- 初始化按键列表:初始化按键项目链表
- 为每个GPIO创建按键项:遍历GPIO掩码,为每个启用的GPIO创建按键项
- 配置中断处理:如果提供了中断处理函数,则为每个GPIO配置中断
// 文件:components/esp_peripherals/lib/button/button.c
esp_button_handle_t button_init(button_config_t *config)
{// 1. 分配按键驱动结构体esp_button_handle_t btn = audio_calloc(1, sizeof(struct esp_button));AUDIO_MEM_CHECK(TAG, btn, return NULL);// 验证GPIO掩码if (config->gpio_mask <= 0) {ESP_LOGE(TAG, "required at least 1 gpio");return NULL;}// 2. 设置按键参数btn->gpio_mask = config->gpio_mask;btn->long_press_time_ms = config->long_press_time_ms;if (btn->long_press_time_ms == 0) {btn->long_press_time_ms = DEFAULT_LONG_PRESS_TIME_MS;}// 3. 配置GPIOgpio_config_t gpiocfg = {.pin_bit_mask = btn->gpio_mask,.mode = GPIO_MODE_INPUT,.pull_up_en = GPIO_PULLUP_ENABLE,.pull_down_en = GPIO_PULLDOWN_DISABLE,.intr_type = GPIO_INTR_ANYEDGE,};gpio_config(&gpiocfg);uint64_t gpio_mask = btn->gpio_mask;int gpio_num = 0;// 4. 初始化按键列表STAILQ_INIT(&btn->btn_list);// 5. 为每个GPIO创建按键项while (gpio_mask) {if (gpio_mask & 0x01) {ESP_LOGD(TAG, "Mask = %llx, current_mask = %llx, idx=%d", btn->gpio_mask, gpio_mask, gpio_num);// 分配按键项内存esp_button_item_t *new_btn = audio_calloc(1, sizeof(esp_button_item_t));AUDIO_MEM_CHECK(TAG, new_btn, {button_destroy(btn);return NULL;});new_btn->gpio_num = gpio_num;// 6. 配置中断处理if (config->button_intr_handler) {gpio_set_intr_type(gpio_num, GPIO_INTR_ANYEDGE);gpio_isr_handler_add(gpio_num, config->button_intr_handler, config->intr_context);gpio_intr_enable(gpio_num);}// 将按键项添加到列表STAILQ_INSERT_TAIL(&btn->btn_list, new_btn, entry);}gpio_mask >>= 1;gpio_num ++;}return btn;
}
按键外设完整初始化时序图
下图展示了按键外设从应用程序调用到底层驱动完成初始化的完整流程:
按键外设销毁流程
按键外设的销毁流程同样涉及两个层次:外设层(Peripheral Layer)和底层驱动(Driver Layer)。下面分别介绍这两个层次的销毁过程。
外设层销毁过程(periph_button.c)
外设层销毁主要通过_button_destroy
函数(位于periph_button.c
)完成,主要包括以下步骤:
- 获取外设数据:获取按键外设的内部数据结构
- 停止定时器:停止按键状态检测定时器
- 释放底层资源:调用底层驱动的销毁函数释放资源
- 释放内部数据结构:释放按键外设的内部数据结构
// 文件:components/esp_peripherals/periph_button.c
static esp_err_t _button_destroy(esp_periph_handle_t self)
{// 1. 获取按键外设数据periph_button_t *periph_btn = esp_periph_get_data(self);// 2. 停止定时器esp_periph_stop_timer(self);// 3. 释放底层按键驱动资源button_destroy(periph_btn->btn);// 4. 释放按键外设数据结构audio_free(periph_btn);return ESP_OK;
}
底层驱动销毁过程(button.c)
底层按键驱动销毁通过button_destroy
函数(位于button.c
)完成,主要包括以下步骤:
- 参数检查:检查按键驱动句柄是否有效
- 释放按键项资源:遍历按键列表,释放每个按键项的资源
- 释放驱动结构体:释放按键驱动结构体内存
// 文件:components/esp_peripherals/lib/button/button.c
esp_err_t button_destroy(esp_button_handle_t button)
{// 1. 参数检查AUDIO_NULL_CHECK(TAG, button, return ESP_FAIL);// 2. 释放按键项资源esp_button_item_t *item, *tmp;STAILQ_FOREACH_SAFE(item, &button->btn_list, entry, tmp) {if (item) {STAILQ_REMOVE(&button->btn_list, item, esp_button_item, entry);audio_free(item);}}// 3. 释放驱动结构体audio_free(button);return ESP_OK;
}
按键外设完整销毁时序图
下图展示了按键外设从应用程序调用到底层驱动完成销毁的完整流程:
按键检测算法
按键检测算法结合了中断和定时器轮询机制,涉及外设层(Peripheral Layer)和底层驱动(Driver Layer)两个层次。下面分别介绍这两个层次的实现。
外设层检测实现(periph_button.c)
外设层通过中断和定时器触发按键状态检测,并将按键事件分发到ESP-ADF的事件系统中:
- 中断处理:按键状态变化时触发GPIO中断,发送命令到外设任务
- 定时器处理:定期检查按键状态,防止中断丢失
- 状态读取:调用底层驱动读取按键状态
- 事件分发:将按键事件分发到ESP-ADF的事件系统
// 文件:components/esp_peripherals/periph_button.c
static void IRAM_ATTR button_intr_handler(void* param)
{// 从中断上下文发送命令到外设任务esp_periph_handle_t periph = (esp_periph_handle_t)param;esp_periph_send_cmd_from_isr(periph, 0, NULL, 0);
}static void button_timer_handler(xTimerHandle tmr)
{// 从定时器上下文发送命令到外设任务esp_periph_handle_t periph = (esp_periph_handle_t) pvTimerGetTimerID(tmr);esp_periph_send_cmd_from_isr(periph, 0, NULL, 0);
}static esp_err_t _button_run(esp_periph_handle_t self, audio_event_iface_msg_t *msg)
{// 读取按键状态并生成事件button_result_t result;periph_button_t *periph_btn = esp_periph_get_data(self);// 调用底层驱动读取按键状态if (button_read(periph_btn->btn, &result)) {ESP_LOGD(TAG, "Button event, press_mask %llx, release_mask: %llx, long_press_mask: %llx, long_release_mask: %llx",result.press_mask, result.release_mask, result.long_press_mask, result.long_release_mask);// 发送各类按键事件到ESP-ADF事件系统button_send_event(self, PERIPH_BUTTON_PRESSED, result.press_mask);button_send_event(self, PERIPH_BUTTON_RELEASE, result.release_mask);button_send_event(self, PERIPH_BUTTON_LONG_PRESSED, result.long_press_mask);button_send_event(self, PERIPH_BUTTON_LONG_RELEASE, result.long_release_mask);}return ESP_OK;
}
底层驱动检测实现(button.c)
底层按键驱动实现在button.c
中,负责具体的按键状态检测和事件生成:
- 状态判断:根据GPIO电平和按下时长判断按键状态
- 事件生成:生成按下、释放、长按、长按释放事件
- 结果汇总:将多个按键的状态汇总到结果结构体中
// 文件:components/esp_peripherals/lib/button/button.c
/*** @brief 获取按键当前状态* * 该函数是按键状态检测的核心,通过检测GPIO电平和按键按下时长,判断按键的当前状态。* 按键状态机如下:* 1. 初始状态:按键未按下,last_press_tick = 0* 2. 按下状态:检测到低电平且之前未按下,记录按下时间,返回BTN_PRESSED* 3. 短按释放:检测到高电平且按下时间小于长按阈值,返回BTN_RELEASE* 4. 长按状态:按下持续时间超过长按阈值,返回BTN_LONG_PRESS(只触发一次)* 5. 长按释放:长按后检测到高电平,返回BTN_LONG_RELEASE* * @param button 按键驱动句柄,包含长按时间阈值等配置* @param btn_item 单个按键项,包含GPIO编号和按下时间记录* @return button_status_t 返回按键状态(无变化/按下/释放/长按/长按释放)*/
static button_status_t button_get_state(esp_button_handle_t button, esp_button_item_t *btn_item)
{// 获取当前GPIO电平int level = gpio_get_level(btn_item->gpio_num);int active_level = 0; // 按下为低电平(按键接地时为有效)int deactive_level = 1; // 释放为高电平(通过上拉电阻保持高电平)// 情况1:按键刚被按下(从未按下状态变为按下状态)// 条件:last_press_tick为0(表示之前未按下)且当前为低电平(按下状态)if (btn_item->last_press_tick == 0 && level == active_level) {btn_item->last_press_tick = tick_get(); // 记录当前按下时间戳btn_item->long_pressed = false; // 重置长按标志return BTN_PRESSED; // 返回按下事件}// 情况2:长按后释放(按下时间超过阈值后释放)// 条件:当前为高电平(释放状态)且之前有按下记录且按下时间超过长按阈值if (level == deactive_level && btn_item->last_press_tick && tick_get() - btn_item->last_press_tick > button->long_press_time_ms) {btn_item->last_press_tick = 0; // 清除按下时间记录btn_item->long_pressed = false; // 重置长按标志return BTN_LONG_RELEASE; // 返回长按释放事件}// 情况3:短按释放(按下时间未超过阈值就释放)// 条件:当前为高电平(释放状态)且之前有按下记录if (level == deactive_level && btn_item->last_press_tick) {btn_item->last_press_tick = 0; // 清除按下时间记录btn_item->long_pressed = false; // 重置长按标志return BTN_RELEASE; // 返回释放事件}// 情况4:长按事件(按下持续时间超过阈值)// 条件:长按标志为false(确保长按事件只触发一次)且当前为低电平(按下状态)// 且按下时间超过长按阈值if (btn_item->long_pressed == false && level == active_level && tick_get() - btn_item->last_press_tick > button->long_press_time_ms) {btn_item->long_pressed = true; // 设置长按标志,防止重复触发return BTN_LONG_PRESS; // 返回长按事件}// 情况5:无状态变化(继续保持之前的状态)return BTN_UNCHANGE;
}/*** @brief 读取所有按键的状态并汇总结果* * 该函数遍历所有注册的按键,获取每个按键的当前状态,并将状态信息汇总到结果结构体中。* 每种按键状态(按下、释放、长按、长按释放)都使用一个位掩码来表示,掩码中的每一位* 对应一个GPIO编号。例如,如果GPIO 5按下,则press_mask的第5位会被设置为1。* * 工作流程:* 1. 清空结果结构体,准备接收新的状态信息* 2. 遍历按键链表中的每个按键项* 3. 调用button_get_state获取单个按键的状态* 4. 根据返回的状态,设置对应掩码中的相应位* 5. 如果至少有一个按键状态发生变化,返回true* * @param button 按键驱动句柄,包含按键链表和配置信息* @param result 输出参数,用于存储汇总的按键状态信息* @return bool 如果有任何按键状态发生变化返回true,否则返回false*/
bool button_read(esp_button_handle_t button, button_result_t *result)
{esp_button_item_t *btn_item; // 当前处理的按键项button_status_t btn_status; // 按键状态bool changed = false; // 状态变化标志// 步骤1: 清空结果结构体,确保没有旧状态信息memset(result, 0, sizeof(button_result_t));uint64_t tmp; // 临时变量,用于构建掩码位// 步骤2: 遍历所有按键项STAILQ_FOREACH(btn_item, &button->btn_list, entry) {// 步骤3: 获取当前按键状态btn_status = button_get_state(button, btn_item);// 步骤4: 根据状态设置对应的掩码位switch (btn_status) {case BTN_UNCHANGE:// 无状态变化,不需要设置掩码break;case BTN_PRESSED:// 按键按下状态changed = true; // 标记有状态变化tmp = 0x01; // 准备掩码位tmp <<= btn_item->gpio_num; // 将位移动到对应GPIO位置result->press_mask |= tmp; // 设置按下掩码的对应位break;case BTN_RELEASE:// 按键释放状态(短按后释放)changed = true;tmp = 0x01;tmp <<= btn_item->gpio_num;result->release_mask |= tmp; // 设置释放掩码的对应位break;case BTN_LONG_RELEASE:// 长按后释放状态changed = true;tmp = 0x01;tmp <<= btn_item->gpio_num;result->long_release_mask |= tmp; // 设置长按释放掩码的对应位break;case BTN_LONG_PRESS:// 长按状态changed = true;tmp = 0x01;tmp <<= btn_item->gpio_num;result->long_press_mask |= tmp; // 设置长按掩码的对应位break;}}// 步骤5: 返回是否有状态变化// 如果任何按键的状态发生了变化,changed会被设置为truereturn changed;
}
按键状态转换时序图
下图展示了按键状态的转换过程,包括按下、释放、长按和长按释放的完整状态流转:
按键状态检测时间轴图
下图展示了按键状态随时间变化的过程,包括不同按键操作的时间轴:
按键检测流程时序图
下图展示了按键检测的完整流程,包括中断触发、定时器轮询和状态判断:
按键事件处理
按键外设产生以下事件类型:
- PERIPH_BUTTON_PRESSED:按键按下事件
- PERIPH_BUTTON_RELEASE:按键释放事件
- PERIPH_BUTTON_LONG_PRESSED:按键长按事件
- PERIPH_BUTTON_LONG_RELEASE:长按后释放事件
事件数据为按键对应的GPIO编号。
按键外设使用示例
#include "esp_peripherals.h"
#include "periph_button.h"void app_main()
{// 初始化外设管理器esp_periph_config_t periph_cfg = DEFAULT_ESP_PERIPH_SET_CONFIG();esp_periph_set_handle_t set = esp_periph_set_init(&periph_cfg);// 配置按键periph_button_cfg_t btn_cfg = {.gpio_mask = GPIO_SEL_36 | GPIO_SEL_39, // 使用GPIO36和GPIO39作为按键.long_press_time_ms = 2000, // 长按阈值2秒};// 初始化按键外设并添加到外设集合esp_periph_handle_t button_handle = periph_button_init(&btn_cfg);esp_periph_start(button_handle);esp_periph_set_add_periph(set, button_handle);// 注册事件回调esp_periph_set_register_callback(set, button_event_callback, NULL);// 主循环while (1) {vTaskDelay(1000 / portTICK_RATE_MS);}
}// 按键事件回调函数
static esp_err_t button_event_callback(audio_event_iface_msg_t *event, void *context)
{switch (event->source_type) {case PERIPH_ID_BUTTON:if (event->cmd == PERIPH_BUTTON_PRESSED) {printf("Button %d pressed\n", (int)event->data);} else if (event->cmd == PERIPH_BUTTON_RELEASE) {printf("Button %d released\n", (int)event->data);} else if (event->cmd == PERIPH_BUTTON_LONG_PRESSED) {printf("Button %d long pressed\n", (int)event->data);} else if (event->cmd == PERIPH_BUTTON_LONG_RELEASE) {printf("Button %d long released\n", (int)event->data);}break;}return ESP_OK;
}
相关文章:
ESP-ADF外设子系统深度解析:esp_peripherals组件架构与核心设计(输入类外设之按键Button)
ESP-ADF外设子系统深度解析:esp_peripherals组件架构与核心设计(输入类外设之按键Button) 版本信息: ESP-ADF v2.7-65-gcf908721 简介 本文档详细分析ESP-ADF中的输入类外设实现机制,包括按键(button)、触摸(touch)和ADC按键(a…...
HOW - 企业团队自建 npm 仓库
文章目录 一、明确需求二、选型:常用方案三、Verdaccio 搭建步骤1. 安装 Node.js 环境2. 全局安装 verdaccio3. 启动服务4. 配置(可选)5. 用户登录与发布四、团队使用方式1. 使用 `.npmrc` 文件统一配置2. 发布范围包(Scoped packages)五、权限控制六、进阶集成七、测试和…...
键值对和Map的区别
数组里存储键值对和使用Map(在不同语言里也被叫做字典、哈希表等)存在多方面的区别,下面从多个维度进行分析,同时给出C#和C的代码示例。 区别分析 1. 查找效率 数组存储键值对:查找特定键的值时,通常需要…...
CS61A:STRING REPRESENTATION
Python 规定所有对象都应该产生两种不同的字符串表示形式:一种是人类可解释的文本,另一种是 Python 可解释的表达式。字符串的构造函数 str 返回一个人类可读的字符串。在可能的情况下,repr 函数会返回一个计算结果相等的 Python 表达式。rep…...
AI编程新纪元:GitHub Copilot、CodeGeeX与VS2022的联合开发实践
引言:AI编程时代的到来 在软件开发领域,我们正站在一个历史性的转折点上。GitHub Copilot、CodeGeeX等AI编程助手的出现,结合Visual Studio 2022的强大功能,正在重塑代码编写的本质。这不仅是工具层面的革新,更是开发范式的根本转变。能够有效利用这些AI工具的开发者将跨…...
iOS崩溃堆栈分析
文章目录 一、背景二、获取崩溃日志三、使用 dSYM 文件符号化堆栈信息1. 准备 dSYM 文件2. 符号化方法使用 Xcode使用 atos 命令 一、背景 在 iOS 开发中,分析崩溃日志和堆栈信息是调试的重要环节。上线APP往往只能获取到堆栈信息无法获取到具体的崩溃日志…...
kafka服务端和springboot中使用
kafka服务端和springboot中使用 一、kafka-sever安装使用 下载kafka-server https://kafka.apache.org/downloads.html 启动zookeeper zookeeper-server-start.bat config\zookeeper.properties 启动kafka-server kafka-server-start.bat config\server.properties创建主…...
05-DevOps-Jenkins自动拉取构建代码
新建Gitlab仓库 先在Gitab上创建一个代码仓库,选择创建空白项目 安装说明进行填写,然后点击创建项目 创建好的仓库是空的,什么都没有 新建一个springboot项目,用于代码上传使用。 只是为了测试代码上传功能,所以代码…...
win7/win10/macos如何切换DNS,提升网络稳定性
本篇教程教您如何在Windows10、Windows8.1、Windows7、MacOS操作系统切换DNS,以提升系统的稳定性,获得更好的操作体验。 Windows10及Windows8.1 1、右键单击“此计算机”,然后选择“属性”。进入Windows系统界面后,选择左侧的“…...
【正点原子STM32MP257连载】第四章 ATK-DLMP257B功能测试——A35M33异核通信测试
1)实验平台:正点原子ATK-DLMP257B开发板 2)浏览产品:https://www.alientek.com/Product_Details/135.html 3)全套实验源码手册视频下载:正点原子资料下载中心 第四章 ATK-DLMP257B功能测试——A35&M33…...
maven如何解决jar包依赖冲突
maven如何解决jar包依赖冲突 1.背景2.报错信息3.解决思路3.1.查找jsqlparser冲突3.2.发现冲突3.2.解决冲突 4.Dromara Warm-Flow 1.背景 在ruoyi-vue项目集成Warm-Flow过程中,需要把mybatis升级为mybatis-plus,按照Warm-Flow常见问题中升级过程…...
过往记录系列 篇六:国家队护盘历史规律梳理
文章目录 系列文章护盘触发条件与时间规律护盘信号识别特征市场反应规律退出策略历史演变系列文章 过往记录系列 篇一:牛市板块轮动顺序梳理 过往记录系列 篇二:新年1月份(至春节前)行情历史梳理 过往记录系列 篇三:春节行情历史梳理 过往记录系列 篇四:年报月行情历史梳…...
string的模拟实现 (6)
目录 1.string.h 2.string.cpp 3.test.cpp 4.一些注意点 本篇博客就学习下如何模拟实现简易版的string类,学好string类后面学习其他容器也会更轻松些。 代码实现如下: 1.string.h #define _CRT_SECURE_NO_WARNINGS 1 #pragma once #include <…...
多模态思维链AI医疗编程:从计算可持续性到开放域推理的系统性解决方案
多模态思维链AI医疗编程:从计算可持续性到开放域推理的系统性解决方案 医疗AI领域的多模态思维链技术正在重塑临床决策支持、医学影像分析和医疗流程优化的范式。本指南从计算可持续性、错误传播控制、伦理安全防护和通用性扩展四大维度,系统解析医疗大模型落地落地的关键要…...
BTS7960 直流电机控制程序
/*************正转逻辑*****************/ LEN1 REN1 while() { LPWN0 DELAY LPWM1 DELAY } /************反转逻辑******************/ LEN1 REN1 while() { RPWN0 DELAY RPWM1 DELAY } /******************************/ /***2025 测试直流电机正反转past…...
vue3 uniapp vite 配置之定义指令
动态引入指令 // src/directives/index.js import trim from ./trim;const directives {trim, };export default {install(app) {console.log([✔] 自定义指令插件 install 触发了!);Object.entries(directives).forEach(([key, directive]) > {app.directive(…...
Mysql-JDBC
JDBCUtils public class JDBCUtils {/*** 工具类的构造方法一般写成私有*/private JDBCUtils(){}//静态代码块再类加载的时候执行,且执行一次static{try {Class.forName("com.mysql.cj.jdbc.Driver");} catch (ClassNotFoundException e) {e.printStackT…...
如何在爬虫中合理使用海外代理?在爬虫中合理使用海外ip
我们都知道,爬虫工作就是在各类网页中游走,快速而高效地采集数据。然而如果目标网站分布在多个国家或者存在区域性限制,那靠普通的网络访问可能会带来诸多阻碍。而这时,“海外代理”俨然成了爬虫工程师们的得力帮手! …...
安卓环境搭建开发工具下载Gradle下载
1.安装jdk(使用java语言开发安卓app) 核心库 java.lang java.util java.sq; java.io 2.安装开发工具(IDE)android studio https://r3---sn-2x3elnel.gvt1-cn.com/edgedl/android/studio/install/2023.3.1.18/android-studio-2023.3.1.18-windows.exe下载完成后一步一步安装即…...
k8s+helm部署tongweb7云容器版(by lqw)
安装准备 1.联系销售获取安装包和授权(例如:tongweb-cloud-7.0.C.6_P3.tar.gz)。 2.已安装docker和k8s集群,参考: k8s集群搭建 3.有对应的docker私库,没有的可以参考: harbor搭建 4.docker已经…...
关于DApp、DeFi、IDO私募及去中心化应用开发的综合解析
一、DApp(去中心化应用)技术开发 1. 技术架构与开发流程 分层架构 : 前端层 :使用React/Vue.js构建用户界面,通过Web3.js或Ethers.js与区块链交互。 智能合约层 :以太坊系常用Solidity,Solana…...
招贤纳士|Walrus 亚太地区招聘高级开发者关系工程师
职位介绍: 开发者关系团队(Developer Relations)通过线上线下方式与开发者社区互动,提供专业支持和指导,帮助他们在 Sui 和 Walrus 上构建下一代 Web3 应用。团队通过与社区对话,了解开发者的痛点…...
Qt实现文件传输客户端(图文详解+代码详细注释)
Qt实现文件传输客户端 1、 客户端UI界面设计2、客户端2.1 添加网络模块和头文件2.2 创建Tcp对象2.3 连接按钮2.3.1 连接按钮连接信号与槽2.3.2 连接按钮实现 2.4 读取文件2.4.1 连接读取文件的信号与槽2.4.2 读取文件槽函数实现2.5 进度条2.5.1 设置进度条初始值2.5.2 初始化进…...
STL详解 - list的模拟实现
目录 1. list 的基本结构 1.1 构造函数 2. 迭代器的实现 2.1 构造函数 2.2 自增和自减操作符 2.3 比较操作符 2.4 解引用和箭头操作符 3. list 容器的实现 3.1 构造函数 3.2 拷贝构造 3.3 赋值运算符重载 3.4 析构函数 3.5 迭代器相关函数 3.6 插入和删除函数 3.…...
ROS 2 的bag
ROS 1 和 ROS 2 的bag包互转方法 1. 安装rosbags工具: 使用pip安装最新版本的rosbags库(确保版本大于等于0.9.15) pip install rosbags --upgrade 2. db3文件bag包互转:使用rosbags-convert命令进行转换 rosbags-convert --sr…...
微软承认Win11出现极端错误,只能强制关机或重装系统
最近,不少使用 Windows 11 的用户反映,在系统更新后,“Windows Hello”突然失效,原本便捷的人脸识别和PIN登录功能统统无法使用。更糟的是,有人在重置系统后直接被挡在系统门外,这让人不禁发问:…...
bininote: 使用AI将视频转换了Markdown笔记
GitHub:https://github.com/JefferyHcool/BiliNote 更多AI开源软件:发现分享好用的AI工具、AI开源软件、AI模型、AI变现 - 小众AI BiliNote 是一个开源的 AI 视频笔记助手,支持通过哔哩哔哩、YouTube 等视频链接,自动提取内容并生…...
Python自动化办公
第五篇:Python自动化办公:10行代码搞定重复性工作 适合读者:职场人士、数据分析师 | 阅读时长:12分钟 引言 每天重复处理Excel、PDF或邮件?Python可以帮你自动化这些枯燥任务,节省90%的时间。本文通过实际…...
使用 tcpdump 工具,捕获并分析
一、 文章概述 使用 tcpdump 工具,捕获并分析了与 SM-DP(Subscription Management Data Preparation) 服务器之间进行 TLS(Transport Layer Security) 握手的过程的数据包,并对其进行了详细解读。 二、 主…...
【LInux网络】socket 编程 - 从ip端口到接口详解
📢博客主页:https://blog.csdn.net/2301_779549673 📢博客仓库:https://gitee.com/JohnKingW/linux_test/tree/master/lesson 📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正! &…...
MCP:构建大型语言模型与外部系统无缝交互的标准协议架构
目录 引言MCP概述 2.1 MCP的定义2.2 MCP的起源与发展MCP的核心设计原理 3.1 上下文传递与交互3.2 数据安全与隐私保护3.3 统一通信协议MCP架构设计详解 4.1 模块化设计4.2 组件解析 4.2.1 Client Agent4.2.2 Context Manager4.2.3 API Adapter4.2.4 Security Layer4.3 设计原则…...
50常用控件_QPushButton
目录 QPushButton添加图标 QPushButton添加快捷键 代码示例: 按钮的重复触发 使用 QPushButton 表示一个按钮.这也是当前我们最熟悉的一个控件了 OPushButton 继承自 QAbstractButton .这个类是一个抽象类是其他按钮的父类 抽象类 这个类包含了 纯虚函数无法创建出实例(对象…...
Java的ForkJoinPool:深入理解并发编程的利器
在现代软件开发中,多核处理器的普及使得并发编程成为提升应用性能的关键。Java作为一门广泛使用的编程语言,提供了丰富的并发工具,其中ForkJoinPool是Java 7引入的一个强大组件,专为处理可递归分解的任务设计。它通过分治算法和工…...
结合 Python 与 MySQL 构建你的 GenBI Agent_基于 MCP Server
写在前面 商业智能(BI)正在经历一场由大型语言模型(LLM)驱动的深刻变革。传统的 BI 工具通常需要用户学习复杂的界面或查询语言,而生成式商业智能 (Generative BI, GenBI) 则旨在让用户通过自然语言与数据交互,提出问题,并获得由 AI 生成的数据洞察、可视化建议甚至完整…...
道路运输安全员企业负责人考试内容与范围
道路运输企业主要负责人(安全员)考证要求 的详细说明,适用于企业法定代表人、分管安全负责人等需取得的 《道路运输企业主要负责人和安全生产管理人员安全考核合格证明》(交通运输部要求)。 考试内容与范围 1. 法律法…...
一体化安全管控平台:消防“一张图”与APP统一管理的创新模式
在科技飞速发展的当下,智慧消防已成为消防救援行业不可阻挡的发展趋势。随着城市化进程的加速,城市规模不断扩大,建筑结构愈发复杂,传统的消防管理模式逐渐暴露出诸多弊端,难以满足现代社会对消防安全的高标准要求。 智…...
利用pnpm patch给第三方库打补丁
如果在使用第三方库的时候, 发现bug, 但是等不了官方补丁, 可以使用pnpm patch给第三方库打补丁来解决, 类似 git diff, 操作如下: 在package.json所在目录的命令行执行 pnpm patch jiaminghi/data-view执行完这个命令后会生成临时文件夹供你编辑, 然后开始编辑这个临时文件夹…...
Spark-SQL(三)
一. 数据加载与保存 1. 数据加载: spark.read.load 是加载数据的通用方法。 spark.read.format("…")[.option("…")].load("…") 1)format("…"):指定加载的数据类型。 2)load("…"…...
centosu7 二进制安装mysql5.7
一、准备工作 1. 卸载原有MariaDB(如有) sudo yum remove -y mariadb-libs sudo rm -rf /var/lib/mysql 2. 安装依赖 sudo yum install -y libaio numactl openssl-devel 3. 创建MySQL用户和目录 sudo groupadd mysql sudo useradd -r -g mysql -s…...
生物信息与自动化控制1 - 传感器数据采集与PID 算法的应用
1. 生物过程自动化控制 在生物制药、发酵工程等生物过程中,可以利用生物信息学技术分析生物反应的机理和代谢网络,然后通过自动化控制系统对生物过程进行实时监测和优化控制,以提高生物产品的产量和质量。例如,在发酵过程中&…...
npm包管理工具理解
一、当前维护者:GitHub(微软旗下) 2018 年,npm 公司被 GitHub 收购; 2020 年,GitHub 被微软收购。 因此,目前 npm 公共仓库由 GitHub 团队负责运维,微软提供底层基础设施支持&#…...
Uniapp 使用Android studio进行离线打包
一.需求 开发Uniapp项目时,使用HBuilderX进行云打包,会经常遇到两个方面的问题,当天的打包的次数受到了限制和打包的时间会比较长,因此,对于离线打包其需求还是比较常见的,这篇文章记录一下对Uniapp的项目…...
mcp和API区别
MCP(Model Context Protocol,模型上下文协议)与传统API(Application Programming Interface,应用程序编程接口)在技术架构、集成方式和应用场景等方面存在显著差异,以下是主要区别的总结&#x…...
ASP.NET 中 Cache 的常规使用方法
在 ASP.NET 中,Cache 类提供了一种在服务器内存中存储数据的方法,可以显著提高应用程序性能。以下是 Cache 的常规使用方法: 1. 基本缓存操作 添加缓存项 // 最简单的添加方式 Cache["key"] "value";// 使用 Insert …...
TextIn ParseX文档解析参数使用指南(第一期)
TextIn ParseX通用文档解析作为一款适配多样化场景的PDF解析工具,在基础识别能力以上,还提供了便捷、完善的参数配置功能,便于用户根据自身需求调整,获得所需输出结果。在TextIn技术社群,我们的产品团队也经常接到关于…...
[图论]生成树 引言
生成树 引言 生成树:一个连通图的生成树是该图的一个极小连通子图。生成树中含有图中全部(设 V V V个)顶点及构成一棵树的 V − 1 V-1 V−1条边,且生成树中不应有环。最小生成树(MST):图的所有生成树中,边权之和最小的生成树。显…...
生信小白学Rust-02
基本类型 Rust 每个值都有其确切的数据类型,总的来说可以分为两类:基本类型和复合类型。 基本类型意味着它们往往是一个最小化原子类型,无法解构为其它类型(一般意义上来说),由以下组成: 数值…...
Docker Compose 中配置 Host 网络模式
在 Docker Compose 中配置 Host 网络模式时,需通过 network_mode 参数直接指定容器使用宿主机的网络栈。以下是具体配置方法及注意事项: 1. 基础配置示例 在 docker-compose.yml 文件中,为需要启用 Host 模式的服务添加 network_mode: "…...
SQL Server 2022 安装常见问题及解决方法
一、系统要求不满足 1. 硬件配置不足 SQL Server 2022 对硬件有一定要求,若内存、磁盘空间不足,安装可能失败。例如,32 位系统至少需要 1GB 内存,64 位系统至少 2GB,且安装过程需预留足够磁盘空间。 解决方法&a…...
解决 AWS RDS MySQL mysqldump 导入sql SET @@GLOBAL 权限不足问题
在使用 mysqldump 导出数据库时,导出的 SQL 文件通常会包含一些 SET 语句,例如 SET MYSQLDUMP, SET SESSION, SET GLOBAL 等,这些语句用于设置会话或全局变量以确保数据一致性和兼容性。然而,在 AWS RDS MySQL 环境中,…...