第8讲 自定义工程架构及添加组件components翻译组件partitions-16MiB.csv 分区表文件sdkconfig 项目配置文件components文件下cmake文件解析#用来批量管理多个外设驱动MQ2、XL955、SPI 屏幕、IIC 等 #作用定义变量 src_dirs存放所有包含.c 源文件的子文件夹 #含义告诉编译器BSP 组件下这 5 个文件夹里的全部 .c 驱动代码都要参与编译 set(src_dirs LED #灯光驱动 MYIIC #I2C 总线底层 XL9555 #I/O 扩展芯片驱动 MYSPI #硬件 SPI 驱动SD 卡 SPILCD) #SPI 屏幕驱动 #作用定义头文件搜索目录变量 #含义工程任意 .c 文件写 #include xxx.h 时编译器会自动去这 5 个文件夹找头文件不用手动写相对路径 set(include_dirs LED MYIIC XL9555 MYSPI SPILCD) #作用声明当前 BSP 组件依赖的 ESP-IDF 官方内置组件 #1.driver提供 adc/gpio/spi/i2c/uart 等所有外设底层驱动头文件MQ2、XL955、MYSPI 全部依赖它 #2.esp_lcdESP-IDF 官方 LCD 屏幕驱动库SPILCD 屏幕驱动依赖该组件 #效果编译时自动加载依赖组件的头文件、库不会报 xxx.h: No such file or directory set(requires driver esp_lcd) #ESP-IDF 固定 API把上面定义的三组变量注册为一个完整组件 #SRC_DIRS ${src_dirs}批量编译 5 个子文件夹的全部源码 #INCLUDE_DIRS ${include_dirs}开放 5 个文件夹的头文件给整个工程调用 #REQUIRES ${requires}绑定依赖库解决头文件找不到的编译报错 idf_component_register(SRC_DIRS ${src_dirs} INCLUDE_DIRS ${include_dirs} REQUIRES ${requires}) #component_compile_options(...) 编译优化参数 #给当前 BSP 组件单独设置编译参数仅作用于 BSP 下所有驱动代码 #-O3最高等级代码优化程序运行速度更快、占用 Flash 更小工控 / 屏幕刷屏必备 #-ffast-math浮点运算加速适合 ADC 电压换算、屏幕坐标计算、传感器数值运算 #-Wno-format关闭格式化打印警告如ESP_LOGI打印变量类型不匹配提示 #-Wno-errorformat打印格式警告不升级为编译错误即使打印格式不规范也能正常编译烧录 component_compile_options(-ffast-math -O3 -Wno-errorformat-Wno-format)main文件下cmake文件解析#component翻译:组件 #这是当前组件文件夹下 CMakeLists.txt 的核心注册语句用来告诉编译系统 #1.去哪里找 .c 源码进行编译 #1.头文件去哪里搜索支持 #include xxx.h。 #.代表当前 CMakeLists.txt 所在的根目录会编译本级目录所有 .c 文件比如 main.c、组件根目录的驱动文件 #APP代表同层级下 APP 子文件夹会编译 ./APP/ 里面全部 .c 源码 idf_component_register( SRC_DIRS . APP INCLUDE_DIRS . APP) #编译范围本级目录 APP 子文件夹下所有 C 代码 #头文件检索范围本级目录 APP 子文件夹项目级cmake文件解析# The following lines of boilerplate have to be in your projects # CMakeLists in this exact order for cmake to work correctly #这是ESP-IDF 项目最顶层的 CMake 配置文件控制整个工程的编译框架顺序不能随意调换。 #作用声明当前工程最低需要 CMake 3.16 版本才能正常解析构建脚本 #ESP-IDF v5.x 强制要求 CMake 版本≥3.16版本过低会直接报错退出。 cmake_minimum_required(VERSION 3.16) #EXTRA_COMPONENT_DIRS 是 ESP-IDF 内置特殊变量用来告诉编译系统 #除了默认的 components/ 根目录外额外去哪里扫描自定义组件。 #本例追加 components/Middlewares 文件夹为自定义组件目录 #ESP-IDF 会自动遍历该目录下所有带CMakeLists.txt的子文件夹当作独立组件编译。 set(EXTRA_COMPONENT_DIRS components/Middlewares) #给整个工程所有 C/C 代码统一添加编译参数 #-fdiagnostics-coloralways强制编译器输出彩色报错日志 #效果串口 / 终端里警告、错误信息分红色 / 黄色高亮方便快速定位代码问题 #不加该参数时Windows 终端经常是黑白文字报错难以分辨。 add_compile_options(-fdiagnostics-coloralways) #ESP-IDF 核心模板脚本不可删除、不能调换顺序 #1.$ENV{IDF_PATH}读取系统环境变量指向你的 ESP-IDF 框架根目录 #2.加载官方预制 CMake 逻辑组件扫描、工具链配置、分区表、bootloader 编译、烧录规则、idf.py指令支持等 #3.硬性规则这一行必须在 project(xxx) 之前执行顺序颠倒直接编译失败。 include($ENV{IDF_PATH}/tools/cmake/project.cmake) #定义项目名称01_Trends_task_creation_and_deletion #作用 #1.生成同名工程可执行文件、固件 bin #2.日志、编译产物文件夹会关联该项目名 #3.分区表、版本信息、APP 编译标识以此命名。 project(01_Trends_task_creation_and_deletion)第11讲 GPIO功能及函数简介GPIO (General Purpose Input/Output)通用输入/输出端口ESP32的GPIO可高度复用每个引脚可映射不同外设功能注意:GPIO并非都可用作输出/输入一些引脚用于连接模块上的FLASH和PSRAMGPIO的初始化配置:esp_err_t gpio_config(const gpio_config_t *pGPIOConfig);typedef struct{uint64_t pin_bit_mask;gpio_mode_t mode;gpio_pullup_t pull_up_en;gpio_pulldown_t pull_down_en;gpio_int_type_t intr_type;}gpio_config_t;设置GPIO的输出电平:esp_err_t gpio_set_level(gpio_num_t gpio_num, uint32_t level);gpio_num:要设置的引脚编号如GPIO_NUM_1表示GPIO1level:输出电平值0表示低电平1表示高电平读取输入电平:int gpio_get_level(gpio_num_t gpio_num);gpio_num:要设置的引脚编号如GPIO_NUM_1表示GPIO1第12讲 LED控制原理与实战LED (Light Emitting Diode发光二极管)单向导通器件只有在正向导通时才能发光第13讲 KEY控制原理KEY按键:机械弹性开关按下时开关接通松开时开关断开连通原理:轻触按键内部的金属弹片受力弹动来实现接通和断开软件消抖:1.延迟法:延时一段时间再进行确认2.计数法:连续检测一定次数如果状态没有发生变化则确认为有效状态。硬件消抖:通过电路实现如应用施密特电路的回差特性配合积分电路,应用锁存器的保持功能定时器按键消抖:按键按下触发中断启动一个10ms定时器)定时结束后再次读取按键状态若仍为按下则判定为有效按键//作用防止头文件被重复 #include 多次避免编译报重复定义错误是 C 语言标准头文件写法。 #ifndef __LED_H #define __LED_H //导入驱动头文件 ESP-IDF GPIO 操作底层库提供 gpio_set_level、gpio_config 等引脚操作函数。 #include driver/gpio.h /* 引脚定义 */ //后续代码只修改此处就能更换硬件引脚便于维护。 #define LED0_GPIO_PIN GPIO_NUM_43 /* LED0连接的GPIO端口 LED0 接芯片 43 号 GPIO */ #define LED1_GPIO_PIN GPIO_NUM_44 /* LED1连接的GPIO端口 LED1 接芯片 44 号 GPIO*/ /* LED 电平控制宏 LED0(x) */ //do{}while(0)把多行逻辑封装为完整单语句if/for 后使用不会出现语法 bug #define LED0(x) do { x ? \ gpio_set_level(LED0_GPIO_PIN, 1): \ gpio_set_level(LED0_GPIO_PIN, 0); \ } while(0) /* LED 电平控制宏 LED1(x) */ #define LED1(x) do { x ? \ gpio_set_level(LED1_GPIO_PIN, 1): \ gpio_set_level(LED1_GPIO_PIN, 0); \ } while(0) /* LED 自动翻转宏 LED0_TOGGLE() / LED1_TOGGLE() */ //gpio_get_level(引脚)读取当前引脚电平1 高 / 0 低 #define LED0_TOGGLE() do { gpio_set_level(LED0_GPIO_PIN, !gpio_get_level(LED0_GPIO_PIN)); } while(0) /* LED0翻转 */ #define LED1_TOGGLE() do { gpio_set_level(LED1_GPIO_PIN, !gpio_get_level(LED1_GPIO_PIN)); } while(0) /* LED1翻转 */ /* 函数声明*/ void led_init(void); /* 初始化LED */ #endif /* __LED_H */#include led.h void led_init(void) { //gpio_config_tESP-IDF 标准 GPIO 配置结构体统一设置引脚模式、上下拉、中断。 gpio_config_t gpio_init_struct {0}; gpio_init_struct.intr_type GPIO_INTR_DISABLE; /* 失能引脚中断 */ gpio_init_struct.mode GPIO_MODE_INPUT_OUTPUT; /* 输入输出模式 */ gpio_init_struct.pull_up_en GPIO_PULLUP_DISABLE; /* 失能上拉 */ gpio_init_struct.pull_down_en GPIO_PULLDOWN_DISABLE; /* 失能下拉 */ /* 配置LED0引脚 */ gpio_init_struct.pin_bit_mask 1ull LED0_GPIO_PIN; // 选中43号引脚 ESP_ERROR_CHECK(gpio_config(gpio_init_struct)); // 写入配置出错直接终止程序并打印日志 /* 配置LED1引脚 */ gpio_init_struct.pin_bit_mask 1ull LED1_GPIO_PIN;// 选中44号引脚 ESP_ERROR_CHECK(gpio_config(gpio_init_struct)); // 写入配置出错直接终止程序并打印日志 //硬件逻辑高电平熄灭 LED低电平点亮 LED上电默认输出高电平开机 LED 不亮。 LED0(1); /* 关闭LED0 */ LED1(1); /* 关闭LED1 */ }#include freertos/FreeRTOS.h #include freertos/task.h #include nvs_flash.h #include stdio.h #include led.h /* 1.freertos/FreeRTOS.h / freertos/task.h ESP32 底层操作系统 FreeRTOS 核心头文件提供任务、延时函数vTaskDelay、时间转换pdMS_TO_TICKS。 2.nvs_flash.h NVS 非易失性存储驱动用来读写 Flash 保存参数WiFi 密码、设备配置等断电不丢失。 3.stdio.h C 标准输入输出库支持printf串口打印日志。 4.led.h 自己的 LED 驱动头文件导入 LED 初始化、控制宏定义。 */ /** * brief 程序入口 * param 无 * retval 无 */ void app_main(void)//ESP-IDF 程序唯一入口上电自动执行。 { esp_err_t ret;//esp_err_t乐鑫统一错误码类型用来接收函数执行状态 ret nvs_flash_init(); /* 初始化NVS */ //初始化 Flash 分区的 NVS 存储系统网络、配网功能都依赖它。 if (ret ESP_ERR_NVS_NO_FREE_PAGES || ret ESP_ERR_NVS_NEW_VERSION_FOUND) { //ESP_ERR_NVS_NO_FREE_PAGESNVS 分区损坏、空间满、分区表异常 //ESP_ERR_NVS_NEW_VERSION_FOUND旧版本 NVS 数据和当前工具版本不兼容 ESP_ERROR_CHECK(nvs_flash_erase()); ESP_ERROR_CHECK(nvs_flash_init()); /* 修复逻辑 1.nvs_flash_erase()擦除整个 NVS 分区清空所有存储数据 2.重新执行nvs_flash_init()完成初始化 3.ESP_ERROR_CHECK()函数执行失败直接打印错误日志并重启芯片方便调试。 作用解决刷机、换固件后 NVS 分区兼容报错是 ESP32 标准 NVS 容错写法。 */ } led_init(); /* 初始化LED */ //调用led.c里的 GPIO 初始化函数配置 GPIO43、GPIO44 为输出上电默认熄灭两个 LED。 while(1) { LED0_TOGGLE(); /* 切换LED0状态 */ LED1_TOGGLE(); /* 切换LED1状态 */ vTaskDelay(pdMS_TO_TICKS(500)); /* 延时500ms */ /* 1.while(1)无限循环单片机程序不会退出持续重复内部逻辑 2.LED0_TOGGLE() / LED1_TOGGLE()翻转 LED 亮灭状态 3.pdMS_TO_TICKS(500)把毫秒 500转换成 FreeRTOS 系统时钟节拍 4.vTaskDelay()任务阻塞延时 500ms这段时间 CPU 释放给其他任务不占用资源。 */ } }第13讲 KEY控制原理KEY按键机械单性开关按下时开关接通松开时开关断开连通原理轻触按键内部的金属弹片受力弹动来实现接通和断开软件消抖:1.延迟法:延时一段时间再进行确认2.计数法:连续检测一定次数如果状态没有发生变化则确认为有效状态。第14讲 KEY实战#ifndef __KEY_H #define __KEY_H // 头文件保护防止重复包含报错 #include freertos/FreeRTOS.h #include freertos/task.h #include driver/gpio.h /* 引脚定义 *///ESP32开发板BOOT按键固定GPIO0 #define BOOT_GPIO_PIN GPIO_NUM_0 /*IO操作*//* IO读取宏BOOT 等价于读取GPIO0电平 */ #define BOOT gpio_get_level(BOOT_GPIO_PIN) /* 按键按下返回值 */ #define BOOT_PRES 1 /* 代表BOOT按键按下 */ /* 对外函数声明 */ void key_init(void); /* GPIO0按键初始化 */ uint8_t key_scan(uint8_t mode); /* 按键扫描mode1支持连按0单次触发 */ #endif#include key.h void key_init(void) { gpio_config_t gpio_init_struct {0}; gpio_init_struct.intr_type GPIO_INTR_DISABLE; // 关闭引脚中断采用轮询扫描 gpio_init_struct.mode GPIO_MODE_INPUT; // 设置GPIO0为输入模式读取按键电平 gpio_init_struct.pull_up_en GPIO_PULLUP_ENABLE; // 开启内部上拉电阻 gpio_init_struct.pull_down_en GPIO_PULLDOWN_DISABLE; // 关闭下拉 gpio_init_struct.pin_bit_mask 1ull BOOT_GPIO_PIN; /* 设置的引脚的位掩码 */ ESP_ERROR_CHECK(gpio_config(gpio_init_struct)); // 写入GPIO配置失败直接报错停机 } /* 硬件逻辑: BOOT 按键一端接 GPIO0一端接 GND 松开内部上拉 → BOOT 1高电平 按下引脚接地 → BOOT 0低电平 */ uint8_t key_scan(uint8_t mode) { static uint8_t key_up 1; // 静态变量记录按键松开状态程序运行全程保留值 uint8_t keyval 0; // 键值缓存无按键默认0 if (mode) key_up 1; /* 支持连按 */// mode1时强制重置松开标志开启连续长按功能 // 判断当前按键处于松开状态 引脚检测到低电平按下 if (key_up (BOOT 0)) /* 按键松开标志为1, 且有任意一个按键按下了 */ { esp_rom_delay_us(20000); // 20ms软件延时消抖过滤机械按键抖动杂波 key_up 0; // 标记按键已按下避免重复触发 if (BOOT 0) keyval BOOT_PRES;// 消抖后确认按下返回键值1 } else if (BOOT 1) // 引脚回到高电平按键松开 { key_up 1; // 恢复松开标志等待下次按下 } return keyval; // 0无按键1BOOT按下 } /* 关键逻辑拆解: 1.static uint8_t key_up 1静态局部变量只初始化一次保存按键松开状态(重点) 2.esp_rom_delay_us(20000)机械按键按下瞬间会有 20ms 电平抖动延时过滤误判 3.参数mode作用 mode0单次触发按住不放只会触发一次 LED 翻转 mode1支持连按每次调用都会重置标志长按会持续返回按键值 4.返回值0 无按键BOOT_PRES(1) 检测到有效按下。 */ /* 问esp_rom_delay_us(20000)能否换成vTaskDelay(pdMS_TO_TICKS(20)) 答不能 1、核心本质区别 esp_rom_delay_us(20000)忙等阻塞延时死循环耗 CPU不会让出任务 vTaskDelay()任务阻塞延时会主动释放 CPU切换其他任务运行 2、替换后会出现的致命 bug 当执行 vTaskDelay 时当前任务会挂起 20msCPU 去跑别的任务 在这 20ms 内如果手指松开按键等 20ms 后任务切回来BOOT 0 条件已经不成立消抖判断失效按键按下识别不到。 3、两种延时适用场景区分 ✅ esp_rom_delay_us (20000) 适合这里按键消抖 短时间微秒级忙等全程卡在当前判断分支不切换任务消抖期间电平保持稳定读取专门用于按键、IO 瞬时消抖。 ❌ vTaskDelay 不适合短消抖 只适合任务级长时间等待比如主循环 10ms 轮询、定时 500ms 闪灯不能用于 IO 消抖阻塞。 4、如果不想用忙等标准 FreeRTOS 改良消抖方案推荐无阻塞 */第15讲 EXIT简介与应用作用让MCU在“事件发生”时立即响应,(而不是不停地轮询)节约CPU资源 轮询方式主动检测状态缺点CPU空转、效率低 中断方式被动响应事件优点实时性高、节能高效 解释外部中断是一种由外部事件触发的硬件中断 过程特定引脚检测到事件-》MCU立即暂停当前程序-》转去执行中断服务程序(ISR)-》返回原程序继续执行 外部中断类型:电平触发、边沿触发 高电平/低电平触发需保持电平状态直到CPU响应 上升沿/下降沿触发触发瞬间即可响应 当系统中有多个中断同时发生时CPU执行任务具有先后顺序。优先级高的先执行。 步骤 gpio_config()-》gpio_install_isr_service()-》gpio_isr_handler_add()-》gpio_intr_enable() 配置外部中断引脚-》注册中断服务-》添加中断服务函数-》使能中断