ESP32开发系列

开发前言

环境配置参考:

Ubuntu20.04下ESP32环境搭建

VSCode开发配置(使用本地已经存在的配置)

搭建环境、编译烧写

VSCode运行 hello world

常用的插件命令:

Show Examples Projects :查找例子
Build your project:编译
Select port to use:选择USB端口
Flash:下载
Monitor your device:串口监视

窗口快捷键直接运行

项目工程结构分析

工程结构学习,从新建工程到烧写程序
项目:特指一个目录,其中包含了构建可执行应用程序所需的全部文件和配置,以及其他支持型文件,例如分区表、数据/文件系统分区和引导程序。
项目配置:保存在项目根目录下名为 sdkconfig 的文件中,可以通过 idf.py menuconfig 进行修改,且一个项目只能包含一个项目配置。
应用程序:是由 ESP-IDF 构建得到的可执行文件。一个项目通常会构建两个应用程序:项目应用程序(可执行的主文件,即用户自定义的固件)和引导程序(启动并初始化项目应用程序)。
组件:是模块化且独立的代码,会被编译成静态库(.a 文件)并链接到应用程序。部分组件由 ESP-IDF 官方提供,其他组件则来源于其它开源项目。
目标:特指运行构建后应用程序的硬件设备。ESP-IDF 当前仅支持 esp32 和 esp32s2 以及 esp32c3 这三个硬件目标。

idf.py 工具的常用命令:

idf.py set-target :设置构建项目的目标(芯片)
idf.py menuconfig:运行 menuconfig 工具来配置项目
idf.py build:构建在当前目录下找到的项目
idf.py clean:把构建输出的文件从构建目录中删除
idf.py fullclean:会将整个 build 目录下的内容全部删除
idf.py flash:会在必要时自动构建项目,并生成烧录进目标 设备中。-p 和 -b 选项可分别设置串口的设备名和烧录时的波特率
idf.py monitor:用于显示目标 ESP32 设备的串口输出。-p 选项可用于设置主机端串口的设备名,按下 Ctrl+] 可退出监视器

多个 idf.py 命令可合并成一个,例如,idf.py -p COM4 clean flash monitor 会依次清理源码树,构建项目,烧录进目标 ESP32 设备,最后运行串口监视器

新建工程:idf.py create-project template_prj在当前路径创建名为template_prj的工程,也可以用 –p 选项可以指定路径。

创建的即是[IDF_PATH]\examples\get-started\sample_project中的模板工程。

一个相对完整的工程目录可能为

myProject/
├── CMakeLists.txt
├── sdkconfig
├── components/
│ ├── component1/
│ │ ├── CMakeLists.txt
│ │ ├── Kconfig
│ │ └── src1.c
│ └── component2/
│ . ├── CMakeLists.txt
│ . ├── Kconfig
│ . └── src1.c
│ . └── include/
│ . . └── component2.h
├── main/
│ ├── CMakeLists.txt
│ ├── src1.c
│ └── src2.c

└── build/

顶层 CMakeLists.txt 文件,包含整个项目的构建设置。
为了让cmake正常工作,项目的cmakelist中必须有以下三行代码:

cmake_minimum_required(VERSION 3.5) # 必须放在 CMakeLists.txt 文件的第一行,它会告诉 CMake 构建该项目所需要的最小版本号。ESP-IDF 支持 CMake 3.5 或更高的版本。
include($ENV{IDF_PATH}/tools/cmake/project.cmake) # 导入 CMake 的其余功能来完成配置项目、检索组件等任务。
project(template_prj) # 创建项目本身,并指定项目名称。该名称会作为最终输出的二进制文件的名字,即 template_prj.elf 和 template_prj.bin。每个 CMakeLists 文件只能定义一个项目。

sdkconfig

项目配置文件,执行 idf.py menuconfig 时会创建或更新此文件,文件中保存了项目中所有组件(包括 ESP-IDF 本身)的配置信息

components(option)

可选的 “components” 目录中包含了项目的部分自定义组件,并不是每个项目都需要这种自定义组件,但它有助于构建可复用的代码或者导入第三方(不属于 ESP-IDF)的组件。
或者,您也可以在顶层 CMakeLists.txt 中设置 EXTRA_COMPONENT_DIRS 变量以查找其他指定位置处的组件。
如果项目中源文件较多,建议将其归于组件中,而不是全部放在 “main” 中。

main目录

目录是一个特殊的组件,它包含项目本身的源代码。”main” 是默认名称,CMake 变量 COMPONENT_DIRS 默认包含此组件,但您可以修改此变量。

build目录

存放构建输出的地方,如果没有此目录,idf.py 会自动创建。CMake 会配置项目,并在此目录下生成临时的构建文件。随后,在主构建进程的运行期间,该目录还会保存临时目标文件、库文件以及最终输出的二进制文件。

每个组件目录都包含一个 CMakeLists.txt 文件

里面会定义一些变量以控制该组件的构建过程,以及其与整个项目的集成。

每个组件还可以包含一个 Kconfig 文件

它用于定义 menuconfig 时展示的 组件配置 选项。某些组件可能还会包含 Kconfig.projbuild 和 project_include.cmake 特殊文件,它们用于 覆盖项目的部分设置。

添加头文件路径
打开工程下main/xxx.c,可以看到程序入口函数app_main以及一些头文件

此时因为没有为VScode指定头文件路径,所以显示找不到源文件,可能会报红

可以使用vscode命令 Edit Configurations (JSON)添加c_cpp_properties.json,并在上面添加你的ESP-IDF路径(如: xxxx/xxx/Espressif)

上述cmakelists文件分别为:
顶层 CMakeLists.txt 文件

cmake_minimum_required(VERSION 3.5) 

#设置外部自定义组件的位置
set(EXTRA_COMPONENT_DIRS ./components)

include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(template_prj)

main文件夹的 CMakeLists.txt 文件

#idf快速组建注册
idf_component_register(SRCS "src1.c" "src2.c"
                   INCLUDE_DIRS ".")

component1文件夹的 CMakeLists.txt 文件

#添加源文件列表
set(COMPONENT_SRCS
 src1.c
 )

#添加头文件路径
set(COMPONENT_ADD_INCLUDEDIRS
 ./
 )

#若src1中有include其他头文件,则需要添加依赖项
#设置依赖项 (其他组件)
#set(COMPONENT_PRIV_INCLUDEDIRS
#  driver/private_include
#  )
#设置依赖项 (其他组件)
#set(COMPONENT_REQUIRES driver)
#set(COMPONENT_PRIV_REQUIRES freertos nvs_flash)

#注册组件
register_component()

component2文件夹的 CMakeLists.txt 文件

#添加源文件列表
set(COMPONENT_SRCS
 src1.c
 )

#添加头文件路径
set(COMPONENT_ADD_INCLUDEDIRS
 ./include
 )

#若src1中有include其他头文件,则需要添加依赖项
#设置依赖项 (其他组件)
#set(COMPONENT_PRIV_INCLUDEDIRS
#  driver/private_include
#  )
#设置依赖项 (其他组件)
#set(COMPONENT_REQUIRES driver)
#set(COMPONENT_PRIV_REQUIRES freertos nvs_flash)

#注册组件
register_component()

然后你就可以编译项目和运行了

ESP32-添加多目录的自定义组件

自定义模块和组件

我们在开发时,可能需要构建自己的工程和组件库

其实就是为了方便程序的管理与后续的移植,就有将特定驱动相关的文件放在单独文件夹的需求。
输入VSCode插件命令 configure esp-idf extension,使用 ADVANCED 安装,一通操作下来,等他自动安装扩展插件完成

新建工程与组件 - 修改CMakeList

给工程新增文件夹与源文件

快速构建系统与组件

生成并使用.a静态库

在自定义 CMake 项目中使用 ESP-IDF

基本开发

GPIO基本概念

ESP32 芯片有 34 个物理 GPIO

GPIO PAD号:0­-19, 21-­23, 25­-27, 32­-39。其中 GPIO 34­-39 仅用作输入管脚,其他的既可以作为输入又可以作为输出管脚。

每个 pad 都可用作一个通用 IO,或连接一个内部的外设信号

IO_MUX、RTC IO_MUX 和 GPIO 交换矩阵用于将信号从外设传输至 GPIO pad。这些模块共同组成了芯片的 IO 控

ESP32开发系列_第1张图片

IO_MUX 选择GPIO pad配置为GPIO(与交换矩阵连接)或者直连(更好的高频数字特性,用于高速信号)

GPIO 交换矩阵 进行【外设输入输出】与【pad信号】之间的全交换。其实就是完pad的输入与输出信号的选择

RTC IO_MUX 控制GPIO pad的低功耗和模拟功能。

外设输入 by GPIO

GPIO中所有34个GPIO(X: 0-19,21-23,25-27,32-39)都支持输入

外设Y中有162个带输入信号,176个带输出信号,可通过交换矩阵配置和哪个GPIO连接

索引号(Y:0-18,23-36,39-58,61-90,95-124,140-155,164-181,190-195,198-206)
ESP32开发系列_第2张图片

把某个外设信号 Y 绑定到某个 GPIO pad X 的配置过程为

1、在 GPIO 交换矩阵中配置外设信号Y的 GPIO_FUNCy_IN_SEL_CFG 寄存器:

设置 GPIO_FUNCy_IN_SEL 字段为要读取的 GPIO pad X 的值。清零其他 GPIO pad 的其他字段。

2、在 GPIO 交换矩阵中配置 GPIO pad X 的 GPIO_FUNCx_OUT_SEL_CFG 寄存器、清零 GPIO_ENABLE_DATA[x] 字段:

【1】 要强制管脚的输出状态始终由 GPIO_ENABLE_DATA[x] 字段决定,则将 GPIO_FUNCx_OUT_SEL_CFG 寄存器的 GPIO_FUNCx_OEN_SEL 字段位置为 1。
【2】 GPIO_ENABLE_DATA[x] 字段在 GPIO_ENABLE_REG (GPIOs 0-31) 或 GPIO_ENABLE1_REG (GPIOs 32-39) 中,清零此位可以关闭 GPIO pad 的输出。

3、配置 IO_MUX 寄存器来选择 GPIO 交换矩阵。配置 GPIO pad X 的 IO_MUX_x_REG 的过程如下:

【1】 设置功能字段 (MCU_SEL) 为 GPIO X 的 IO_MUX 功能(所有管脚的 Function 2,数值为 2)。
【2】置位 FUN_IE 使能输入。
【3】置位或清零 FUN_WPU 和 FUN_WPD 位,使能或关闭内部上拉/下拉电阻器。

需要注意的是:

1、同一个输入 pad 上可以同时绑定多个内部 input_signals。
2、置位 GPIO_FUNCy_IN_INV_SEL 可以把输入的信号取反。
3、无需将输入信号绑定到一个 pad 也可以使外设读取恒低或恒高电平的输入值。实现方式为选择特定的GPIO_FUNCy_IN_SEL 输入值而不是一个 GPIO 序号:

【1】当 GPIO_FUNCy_IN_SEL 是 0x30 时, input_signal_x 始终为 0。
【2】 当 GPIO_FUNCy_IN_SEL 是 0x38 时, input_signal_x 始终为 1。

例:把 RMT 外设通道 0 的输入信号 RMT_SIG_IN0_IDX(信号索引号 83)绑定到 GPIO15,请按照以下步骤操作(请注意 GPIO15 也叫做 MTDO 管脚):

【1】将 GPIO_FUNC83_IN_SEL_CFG 寄存器的 GPIO_FUNC83_IN_SEL 字段设置为 15。
【2】因为此信号是纯输入信号,置位 GPIO_FUNC15_OUT_SEL_CFG_REG 寄存器中的 GPIO_FUNC15_OEN_SEL 位。
【3】清零 GPIO_ENABLE_REG 寄存器的 bit 15(GPIO_ENABLE_DATA[15] 字段)。
【4】配置 IO_MUX_GPIO15 寄存器的 MCU_SEL 字段为 2 (GPIO function),同时置位 FUN_IE(使能输入模式)。

简单的GOIO输入:

【1】 GPIO_IN_REG/GPIO_IN1_REG 寄存器存储着每一个 GPIO pad 的输入值。
【2】 任意 GPIO pin 的输入值都可以随时读取而无需为某一个外设信号配置 GPIO 交换矩阵。但是需要为 pad X 的
【3】IO_MUX_x_REG 寄存器配置 FUN_IE 位以使能输入。

外设输出 by GPIO

GPIO中仅有28 个 GPIO (X: 0-19, 21-23, 25-27, 32-33)支持输出
外设信号(Y: 0-18, 23-37, 61-121,140-215, 224-228)
ESP32开发系列_第3张图片
输出外设信号 Y 到某一 GPIO pad X 的步骤为

1、在 GPIO 交换矩阵里配置 GPIO X 的 GPIO_FUNCx_OUT_SEL_CFG 寄存器和 GPIO_ENABLE_DATA[x] 字段:

【1】设置 GPIO_FUNCx_OUT_SEL_CFG 寄存器的 GPIO_FUNCx_OUT_SEL 字段为外设输出信号 Y 的索引号 (Y)。
【2】要将信号强制使能为输出模式,将 GPIO pad X 的 GPIO_FUNCx_OUT_SEL_CFG 寄存器的GPIO_FUNCx_OEN_SEL 置位,并且将 GPIO_ENABLE_REG 寄存器的 GPIO_ENABLE_DATA[x] 字段置位。或者,将 GPIO_FUNCx_OEN_SEL 清零,此时输出使能信号由内部逻辑功能决定。
【3】GPIO_ENABLE_DATA[x] 字段在 GPIO_ENABLE_REG (GPIOs 0-31) 或 GPIO_ENABLE1_REG (GPIOs32-39) 中,清零此位可以关闭 GPIO pad 的输出。

2、要选择以开漏方式输出,可以设置 GPIO X 的 GPIO_PINx 寄存器中的 GPIO_PINx_PAD_DRIVER 位。
3、配置 IO_MUX 寄存器来选择 GPIO 交换矩阵。配置 GPIO pad X 的 IO_MUX_x_REG 的过程如下:

【1】 设置功能字段 (MCU_SEL) 为 GPIO X 的 IO_MUX 功能(所有管脚的 Function 2,数值为 2)。
【2】设置 FUN_DRV 字段为特定的输出强度值 (0-3),值越大,输出驱动能力越强。
【3】在开漏模式下,通过置位/清零 FUN_WPU 和 FUN_WPD 使能或关闭上拉/下拉电阻器。

需要注意的是:

1、某一个外设的输出信号可以同时从多个 pad 输出。
2、置位 GPIO_FUNCx_OUT_INV_SEL 可以把输出的信号取反。

简单的GOIO输出:

1、GPIO 交换矩阵也可以用于简单 GPIO 输出。设置 GPIO_OUT_DATA 寄存器中某一位的值可以写入对应的 GPIO pad。
2、为实现某一 pad 的 GPIO 输出,设置 GPIO 交换矩阵 GPIO_FUNCx_OUT_SEL 寄存器为特定的外设索引值 256(0x100)。

直接 I/O

从之前的总体输入输出结构框图可以看出,IO_MUX还有一些直接I/O信号。快速信号如以太网、 SDIO、 SPI、 JTAG、 UART 等会旁路绕过 GPIO 交换矩阵,直连以实现更好的高频数字特性。

RTC IO_MUX 输入输出

18 个 GPIO 管脚具有低功耗(低功耗 RTC)性能和模拟功能,这些功能不经过IO_MUX 和 GPIO 交换矩阵,而是使用 RTC_MUX 将 I/O 指向 RTC 子系统。

当这些管脚被配置为 RTC GPIO 管脚,作为输出管脚时仍然能够在芯片处于 Deep-sleep 睡眠模式下保持电平值输出或者作为输入管脚使用时可以将芯片从 Deep-sleep 中唤醒

代码实现

简单 GPIO 输入输出实现可以通过函数:gpio_config

@path: gpio.h  
@brief: GPIO通用配置函数,配置 GPIO 的模式,上拉,下拉,中断   
@param: 一个指向** GPIO 配置结构体**的指针 pGPIOConfig   
@return: ESP_OK:成功;ESP_ERR_INVALID_ARG:参数错误    

esp_err_t gpio_config(const gpio_config_t *pGPIOConfig);

/**
 * @brief Configuration parameters of GPIO pad for gpio_config function
 */
typedef struct {
    uint64_t pin_bit_mask;          /*!< GPIO pin: set with bit mask, each bit maps to a GPIO */
    gpio_mode_t mode;               /*!< GPIO mode: set input/output mode                     */
    gpio_pullup_t pull_up_en;       /*!< GPIO pull-up                                         */
    gpio_pulldown_t pull_down_en;   /*!< GPIO pull-down                                       */
    gpio_int_type_t intr_type;      /*!< GPIO interrupt type                                  */
} gpio_config_t;

GPIO 配置结构体为gpio_config_t,以配置GPIO2为输出、GPIO4为输入为例。代码为:

#include 
#include 
#include 
#include "driver/gpio.h"  
#include "freertos/FreeRTOS.h" //portTICK_RATE_MS
#include "freertos/task.h" //vTaskDelay
/**
 * Brief:
 * 基本的GPIO输入输出学习
 *
 * GPIO status:
 * GPIO2: output
 * GPIO4:  input
 *
 * Test:
 * Connect GPIO2 with GPIO4
 * Generate pulses on GPIO4, that connect to GPIO4
 *
 */ 
#define GPIO_2   2
#define GPIO_4   4

void app_main(void)
{
    // GPIO1
    gpio_config_t io_conf = {};                 //新建配置GPIO pad的gpio_config功能参数的结构体  
    io_conf.pin_bit_mask = (1ULL << GPIO_2);    //设置GPIO2的掩码为1
    io_conf.mode = GPIO_MODE_OUTPUT;            //设置GPIO2 为输出模式
    io_conf.pull_up_en = 0;                     //不上拉
    io_conf.pull_down_en = 0;                   //不下拉  
    io_conf.intr_type = 0;                      //禁用GPIO2中断  
    esp_err_t result;
    result = gpio_config(&io_conf);             //配置GPIO2  
    if (result == ESP_OK)
        printf("gpio2_config succeed \n"); 
    else 
        printf("gpio2_config failed \n"); 
    
    // GPIO3  
    io_conf.pin_bit_mask = (1ULL << GPIO_4);    //设置GPIO4的掩码为1
    io_conf.mode = GPIO_MODE_INPUT;             //设置GPIO4 为输入模式  
    result = gpio_config(&io_conf);             //配置GPIO4
    if (result == ESP_OK)
        printf("gpio4_config succeed \n"); 
    else 
        printf("gpio4_config failed \n");  

    int cnt = 0;
    int value = 0;
    while(1)
    {   
        cnt++;
        vTaskDelay(1000 / portTICK_RATE_MS); 
        gpio_set_level(GPIO_2, cnt % 2); 
        printf("GPIO_2_output: %d\n", cnt % 2);
        value = gpio_get_level(GPIO_4);
        printf("GPIO_4_input: %d\n", value);
    }
}

RTC GPIO 输入输出实现

以DAC1通过GPIO25输出、ADC1通过GPIO33输入为例。

首先查找乐鑫的 《esp32技术参考手册》 的 4.10 章节找到ADC与DAC的管脚映射:
ESP32开发系列_第4张图片可以看到 DAC_1 对应 GPIO25, ADC1_CH5 对应 GPIO33 。

对于这类外设输入输出,官方给定的外设驱动文件给出了相关的配置函数,我们使用起来也很方便,这里以当前的dac、adc应用为例,介绍一下相关的函数

esp_adc_cal_check_efuse
检查当前参考电压是否熔断到对应位置。

adc1_config_width
设置ADC1采集位宽

adc1_config_channel_atten
配置ADC1的通道,即对应的 GPIO 。以及这个通道的衰减。

esp_adc_cal_characterize
使用输入的参数配置ADC

esp_adc_cal_raw_to_voltage
根据采样值以及adc的配置特性计算电压值。

测试代码

#include   
#include 
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

#include "esp_adc_cal.h"      //esp_adc_cal_check_efuse
#include "driver/adc.h"  

#include "driver/dac.h"

#define DEFAULT_VREF    1100  // 参考电压  
#define NO_OF_SAMPLES   64    // 采样次数  

#define AMP_DAC         255   // DAC输出幅度 

static const adc_unit_t unit = ADC_UNIT_1;              // ADC1
static const adc_channel_t channel = ADC_CHANNEL_5;     // ADC1 GPIO33
static const adc_bits_width_t width = ADC_WIDTH_BIT_12; // ESP32 集成 12-bit SAR ADC
static const adc_atten_t atten = ADC_ATTEN_DB_11;       // 衰减 11 dB (3.55 x)
static esp_adc_cal_characteristics_t *adc_chars;        // ADC的结构存储特性
static const dac_channel_t dac_chan = DAC_CHANNEL_1;

void app_main(void)
{
    //Check if TP is burned into eFuse
    if (esp_adc_cal_check_efuse(ESP_ADC_CAL_VAL_EFUSE_TP) == ESP_OK) {
        printf("eFuse Two Point: Supported\n");
    } else {
        printf("eFuse Two Point: NOT supported\n");
    }

    //Check Vref is burned into eFuse
    if (esp_adc_cal_check_efuse(ESP_ADC_CAL_VAL_EFUSE_VREF) == ESP_OK) {
        printf("eFuse Vref: Supported\n");
    } else {
        printf("eFuse Vref: NOT supported\n");
    }  

    // 配置位宽
    if (adc1_config_width(width) == ESP_OK ) {   
        printf("adc1_config_width: ESP_OK\n");
    } else {
        printf("adc1_config_width: ESP_ERR_INVALID_ARG\n");
    }  

    // 在ADC1上设置特定通道的衰减,并配置其相关的GPIO RTC_MUX。
    if (adc1_config_channel_atten(channel, atten) == ESP_OK ) { 
        printf("adc1_config_channel_atten: ESP_OK\n");
    } else {
        printf("adc1_config_channel_atten: ESP_ERR_INVALID_ARG\n");
    }  

    // 构造ADC的结构存储特性
    adc_chars = calloc(1, sizeof(esp_adc_cal_characteristics_t)); //分配一个内存块。
    esp_adc_cal_value_t val_type = esp_adc_cal_characterize(unit, atten, width, DEFAULT_VREF, adc_chars);

    // 使能DAC输出  
    if (dac_output_enable(dac_chan) == ESP_OK ) { 
        printf("dac_output_enable: ESP_OK\n");
    } else {
        printf("dac_output_enable: ESP_ERR_INVALID_ARG\n");
    } 

    int cnt = 0;
    while(1)
    {
        uint32_t adc_reading = 0;  

        cnt++; 

        // dac 输出方波 
        dac_output_voltage(dac_chan, ((cnt % 2) * 200));  
        printf("dac Voltage: %d mV\n", ((cnt % 2) * 200) * 3300 / 255 ); 

        // 采样64次取平均值  
        for (int i = 0; i < NO_OF_SAMPLES; i++)
        {
            adc_reading += adc1_get_raw((adc1_channel_t)channel);
        }
        adc_reading /= NO_OF_SAMPLES;  

        // 采样值转换为电平值  
        uint32_t voltage = esp_adc_cal_raw_to_voltage(adc_reading, adc_chars); 
        printf("Raw: %d\tVoltage: %dmV\n", adc_reading, voltage);
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}


GPIO外部中断

ESP32 中断矩阵将任一外部中断源单独分配到每个 CPU 的任一外部中断上。
ESP32开发系列_第5张图片
中断矩阵:

输入:71个外部中断
输出:两个 CPU 分别生成 26 个外部中断
屏蔽 CPU 的 NMI 类型中断
查询外部中断源当前的中断状态

关于外部中断源,没必要都去学习,我们只要知道一些常用的外设都可以产生中断就行,比如gpio、串口、spi、定时器等。

GPIO 信号作为中断源的实现参考examples\peripherals\gpio\generic_gpio中的示例工程。

基本步骤

1、通过 esp_err_t gpio_config(const gpio_config_t *pGPIOConfig) 函数配置GPIO的基本输入输出,同样的 gpio_config_t 结构体的 gpio_int_type_t类型 intr_type 字段可以设置中断类型:

GPIO_INTR_DISABLE : 禁用GPIO中断
GPIO_INTR_POSEDGE : 上升沿触发
GPIO_INTR_NEGEDGE : 下降沿触发
GPIO_INTR_ANYEDGE : 任意沿触发
GPIO_INTR_LOW_LEVEL : 低电平触发
GPIO_INTR_HIGH_LEVEL : 高电平触发

2、配置完成后,调用 esp_err_t gpio_install_isr_service(int intr_alloc_flags)。

3、调用 esp_err_t gpio_isr_handler_add(gpio_num_t gpio_num, gpio_isr_t isr_handler, void *args); 为相应的GPIO引脚添加ISR handler。

/定义回调函数
//注意IRAM_ATTR ,是将定义的中断回调函数定义在iram区
static void IRAM_ATTR isr_handler(void* arg)
{
   ....
}
//install gpio isr service
esp_err_t gpio_install_isr_service(int intr_alloc_flags)//注册中断号
//hook isr handler for specific gpio pin
//将中断回调函数与中断号关联起来,当中断发生时,会触发中断回调函数。
esp_err_t gpio_isr_handler_add(gpio_num_t gpio_num, gpio_isr_t isr_handler, void* args)

更多可以参考esp32 外设开发GPIO,官方例程如下:

/* GPIO Example

   This example code is in the Public Domain (or CC0 licensed, at your option.)

   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/
#include 
#include 
#include 
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/gpio.h"

/**
 * Brief:
 * This test code shows how to configure gpio and how to use gpio interrupt.
 *
 * GPIO status:
 * GPIO18: output
 * GPIO19: output
 * GPIO4:  input, pulled up, interrupt from rising edge and falling edge
 * GPIO5:  input, pulled up, interrupt from rising edge.
 *
 * Test:
 * Connect GPIO18 with GPIO4
 * Connect GPIO19 with GPIO5
 * Generate pulses on GPIO18/19, that triggers interrupt on GPIO4/5
 *
 */

#define GPIO_OUTPUT_IO_0    18//要定义为输出的的io编号
#define GPIO_OUTPUT_IO_1    19//要定义为输出的的io编号
#define GPIO_OUTPUT_PIN_SEL  ((1ULL<<GPIO_OUTPUT_IO_0) | (1ULL<<GPIO_OUTPUT_IO_1))//打开引脚标志  1为打开 0为关闭
#define GPIO_INPUT_IO_0     4    //要定义为输入的的io编号
#define GPIO_INPUT_IO_1     5    //要定义为输入的的io编号
#define GPIO_INPUT_PIN_SEL  ((1ULL<<GPIO_INPUT_IO_0) | (1ULL<<GPIO_INPUT_IO_1))//打开引脚标志
#define ESP_INTR_FLAG_DEFAULT 0   //设置中断编号

static xQueueHandle gpio_evt_queue = NULL;//定义gpio消息队,用于传输消息
/*定义gpio 中断回调函数*/
static void IRAM_ATTR gpio_isr_handler(void* arg)
{
    uint32_t gpio_num = (uint32_t) arg;
    //在中断中向消息队列发送数据,
    //注意freeRTOS中断操作都是用带有ISR的函数
    xQueueSendFromISR(gpio_evt_queue, &gpio_num, NULL);
}

static void gpio_task_example(void* arg)
{
    uint32_t io_num;
    for(;;) {
        if(xQueueReceive(gpio_evt_queue, &io_num, portMAX_DELAY)) {//接收队列消息,如果成功接收到数据 ,打印消息
            printf("GPIO[%d] intr, val: %d\n", io_num, gpio_get_level(io_num));
        }
    }
}

void app_main(void)
{
    gpio_config_t io_conf;
    //disable interrupt
    io_conf.intr_type = GPIO_PIN_INTR_DISABLE;//关闭中断
    //set as output mode
    io_conf.mode = GPIO_MODE_OUTPUT;//设置成输出引脚
    //bit mask of the pins that you want to set,e.g.GPIO18/19
    io_conf.pin_bit_mask = GPIO_OUTPUT_PIN_SEL;//打开gpio18/19
    //disable pull-down mode
    io_conf.pull_down_en = 0;//关闭下拉
    //disable pull-up mode
    io_conf.pull_up_en = 0;//关闭上拉
    //configure GPIO with the given settings
    gpio_config(&io_conf);//执行配置

    //interrupt of rising edge
    io_conf.intr_type = GPIO_PIN_INTR_POSEDGE;//设置中断为上升沿触发
    //bit mask of the pins, use GPIO4/5 here
    io_conf.pin_bit_mask = GPIO_INPUT_PIN_SEL;
    //set as input mode    
    io_conf.mode = GPIO_MODE_INPUT;
    //enable pull-up mode
    io_conf.pull_up_en = 1;
    gpio_config(&io_conf);

    //change gpio intrrupt type for one pin
    //改变中断触发方式为双边沿触发
    gpio_set_intr_type(GPIO_INPUT_IO_0, GPIO_INTR_ANYEDGE);

    //create a queue to handle gpio event from isr
    //创建长度为10的队列
    gpio_evt_queue = xQueueCreate(10, sizeof(uint32_t));
    //start gpio task
    //创建gpio测试任务函数
    xTaskCreate(gpio_task_example, "gpio_task_example", 2048, NULL, 10, NULL);

    //install gpio isr service
    //注册中断
    gpio_install_isr_service(ESP_INTR_FLAG_DEFAULT);
    //hook isr handler for specific gpio pin
    //添加中断回调函数
    gpio_isr_handler_add(GPIO_INPUT_IO_0, gpio_isr_handler, (void*) GPIO_INPUT_IO_0);
    //hook isr handler for specific gpio pin
    //添加中断回调函数
    gpio_isr_handler_add(GPIO_INPUT_IO_1, gpio_isr_handler, (void*) GPIO_INPUT_IO_1);

    //remove isr handler for gpio number.
    //移除中断
    gpio_isr_handler_remove(GPIO_INPUT_IO_0);
    //hook isr handler for specific gpio pin again
    //重新添加中断
    gpio_isr_handler_add(GPIO_INPUT_IO_0, gpio_isr_handler, (void*) GPIO_INPUT_IO_0);

    int cnt = 0;
    while(1) {
        printf("cnt: %d\n", cnt++);
        vTaskDelay(1000 / portTICK_RATE_MS);
        gpio_set_level(GPIO_OUTPUT_IO_0, cnt % 2);//设置电平信号
        gpio_set_level(GPIO_OUTPUT_IO_1, cnt % 2);//设置电平信号
    }
}

GPIO接口快速使用

UART串口使用

高分辨率定时器接口使用

WiFi接口使用(STA和AP模式)

Camera使用

其他硬件模块

安可信ESP32-CAM电路图和说明

ESP32-CAM AI THINKER 引脚排列:GPIO 用法说明

ESP32-CAM上手第一步——资料不能少之我的手记

Git源码仓库

你可能感兴趣的:(vscode,ESP32)