ESP-IDF 完全编程指南

有关 ESP32 的所有资源

适用于 Espressif SoC模型和开发套件的 KiCAD 库

撰写本文时使用的是 esp-idf-v5.3.1

  1. 启动模式设置电路BOOT

    启动模式 GPIO0 GPIO46 功能
    SPI Boot 1 任意 芯片正常读取程序并启动
    Download Boot 0 0 下载代码至 Flash

通用输入输出GPIO

GPIO 概述

GPIOGeneral Purpose Input/Output通用输入输出是微控制器中最基础最核心的外设之一在 ESP32 中可以通过程序控制 GPIO 引脚输出或者是输入即读取相应的电平或信号

在 ESP32 中 GPIO 的内部结构如下

GPIO 内部结构

  • IE输入使能当 IE = 1 时输入有效
  • OE输出使能当 OE = 1时输出有效
  • WPU内部若上拉weak pull-up
  • WPD内部弱下拉weak pull-down

使用 GPIO 相关函数前需要包含 driver/gpio.h #include "driver/gpio.h"

  1. 初始化 GPIO

    在初始化 GPIO 前要先创建一个结构体变量用于储存 GPIO 配置

    1
    2
    3
    4
    5
    6
    7
    8
    9

    gpio_config_t gpio_10_config = {
    .pin_bit_mask = 1ull << GPIO_NUM_10, // 表示引脚掩码用掩码移位方式控制 GPIO
    .mode = GPIO_MODE_INPUT_OUTPUT, // 设置为输入输出模式
    .pull_up_en = GPIO_PULLUP_DISABLE, // 设置上拉电阻
    .pull_down_en = GPIO_PULLDOWN_DISABLE, // 设置下拉电阻
    .intr_type = GPIO_INTR_DISABLE, // 设置 GPIO 中断
    };

    1
    2
    3
    4
    5
    6
    7
    8
    9

    typedef enum {
    GPIO_MODE_DISABLE = GPIO_MODE_DEF_DISABLE, /*!< GPIO mode : disable input and output */
    GPIO_MODE_INPUT = GPIO_MODE_DEF_INPUT, /*!< GPIO mode : input only */
    GPIO_MODE_OUTPUT = GPIO_MODE_DEF_OUTPUT, /*!< GPIO mode : output only mode */
    GPIO_MODE_OUTPUT_OD = ((GPIO_MODE_DEF_OUTPUT) | (GPIO_MODE_DEF_OD)), /*!< GPIO mode : output only with open-drain mode */
    GPIO_MODE_INPUT_OUTPUT_OD = ((GPIO_MODE_DEF_INPUT) | (GPIO_MODE_DEF_OUTPUT) | (GPIO_MODE_DEF_OD)), /*!< GPIO mode : output and input with open-drain mode*/
    GPIO_MODE_INPUT_OUTPUT = ((GPIO_MODE_DEF_INPUT) | (GPIO_MODE_DEF_OUTPUT)), /*!< GPIO mode : output and input mode */
    } gpio_mode_t;

    接下来使用 gpio_config 函数初始化该 GPIO

    1
    gpio_config(&gpio_10_config);

    该函数的的原型为

    1
    esp_err_t gpio_config(const gpio_config_t *pGPIOConfig);

    该函数的返回类型是 esp_err_t 若返回 ESP_OK代表初始化成功

    若要查看 ESP-IDF 所有错误类型请参考 %IDF_PATH/components/esp_common/include/esp_err.h

  2. 控制 GPIO 输出电平

    1
    esp_err_t gpio_set_level(gpio_num_t gpio_num, uint32_t level);

    其中参数 gpio_num 代表的是 GPIO 号GPIO_NUM_48 参数 level 代表的是电平状态1为高电平2为低电平

    例如 gpio_set_level(GPIO_NUM_48, 1); 将 GPIO48 引脚设置为高电平

  3. 读取 GPIO 电平

    1
    int gpio_get_level(gpio_num_t gpio_num);

    其中参数 gpio_num 代表的是 GPIO号gpio_num(GPIO_NUM_48); 该函数会返回 1 或 0 1高电平0低电平

  4. 示例

    按下按钮切换 GPIO_NUM_47 上 LED 灯的亮灭状态同时 GPIO_NUM_48 引脚上的 500 毫秒闪烁一次

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    #include "freertos/FreeRTOS.h"
    #include "freertos/task.h"
    #include "driver/gpio.h"

    void app_main(void) {
    gpio_config_t gpio_LED_config = {
    .pin_bit_mask = (1ull << GPIO_NUM_47) | (1ull << GPIO_NUM_48),
    .mode = GPIO_MODE_OUTPUT,
    .pull_up_en = GPIO_PULLUP_DISABLE,
    .pull_down_en = GPIO_PULLDOWN_DISABLE,
    .intr_type = GPIO_INTR_DISABLE
    };
    gpio_config_t gpio_10_config = {
    .pin_bit_mask = 1ull << GPIO_NUM_10,
    .mode = GPIO_MODE_INPUT,
    .pull_up_en = GPIO_PULLUP_DISABLE,
    .pull_down_en = GPIO_PULLDOWN_DISABLE,
    .intr_type = GPIO_INTR_DISABLE
    };

    gpio_config(&gpio_LED_config);
    gpio_config(&gpio_10_config);
    gpio_set_level(GPIO_NUM_48, 1);

    while(1) {
    if( gpio_get_level(GPIO_NUM_10) == 1 ) {
    vTaskDelay( pdMS_TO_TICKS(20) );
    if( gpio_get_level(GPIO_NUM_10) == 1 ) {
    gpio_set_level(GPIO_NUM_48, !gpio_get_level(GPIO_NUM_48));
    }
    while( gpio_get_level(GPIO_NUM_10) == 1 );
    }
    vTaskDelay( pdMS_TO_TICKS(500) );
    gpio_set_level(GPIO_NUM_48, !gpio_get_level(GPIO_NUM_48));

    }
    }

    这种方法有个致命的缺陷要是按下按钮时整个程序就阻塞了

    我们可以用 FreeRTOS 的方法写如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    #include "freertos/FreeRTOS.h"
    #include "freertos/task.h"
    #include "driver/gpio.h"

    void Task_Blink(void *param) {
    gpio_set_level(GPIO_NUM_48, !gpio_get_level(GPIO_NUM_48));
    vTaskDelay( pdMS_TO_TICKS(500) );
    }

    void Task_Switch(void *param) {
    if( gpio_get_level(GPIO_NUM_10) == 1 ) {
    vTaskDelay( pdMS_TO_TICKS(20) );
    if( gpio_get_level(GPIO_NUM_10) == 1 ) {
    gpio_set_level(GPIO_NUM_48, !gpio_get_level(GPIO_NUM_48));
    }
    while( gpio_get_level(GPIO_NUM_10) == 1 );
    }
    }

    void app_main(void) {
    gpio_config_t gpio_LED_config = {
    .pin_bit_mask = (1ull << GPIO_NUM_47) | (1ull << GPIO_NUM_48),
    .mode = GPIO_MODE_OUTPUT,
    .pull_up_en = GPIO_PULLUP_DISABLE,
    .pull_down_en = GPIO_PULLDOWN_DISABLE,
    .intr_type = GPIO_INTR_DISABLE
    };
    gpio_config_t gpio_10_config = {
    .pin_bit_mask = 1ull << GPIO_NUM_10,
    .mode = GPIO_MODE_INPUT,
    .pull_up_en = GPIO_PULLUP_DISABLE,
    .pull_down_en = GPIO_PULLDOWN_DISABLE,
    .intr_type = GPIO_INTR_DISABLE
    };
    gpio_config(&gpio_LED_config);
    gpio_config(&gpio_10_config);
    gpio_set_level(GPIO_NUM_47, 1);
    gpio_set_level(GPIO_NUM_48, 1);

    while(1) {
    xTaskCreatePinnedToCore( Task_Blink, "Task_Blink", 1024, NULL, 1, NULL, 1 );
    xTaskCreatePinnedToCore( Task_Switch, "Task_Switch", 1024, NULL, 1, NULL, 1 );
    }
    }

    有关 FreeRTOS 的详细教程在后续章节我们可以学到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void Task_A(void *param) {
/* code... */
}
BaseType_t xTaskCreatePinnedToCore( TaskFunction_t pxTaskCode,
const char * const pcName,
const uint32_t usStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pxCreatedTask,
const BaseType_t xCoreID );

xTaskCreatePinnedToCore( Task_A, // 任务函数
"Task_A", // 任务名字
1024, //
NULL, // 传参
1, // 任务优先级
NULL, // 创建任务的句柄
1 ); // 核心 ID

##通用硬件定时器GPTimer

1
2
3
4
5
6
7
8
9
10
11
12
gptimer_handle_t gptimer = NULL;
gptimer_config_t timer_config = {
.clk_src = GPTIMER_CLK_SRC_DEFAULT, // 选择默认的时钟源
.direction = GPTIMER_COUNT_UP, // 计数方向为向上计数
.resolution_hz = 1 * 1000 * 1000, // 分辨率为 1 MHz即 1 次滴答为 1 微秒
};
// 创建定时器实例
ESP_ERROR_CHECK(gptimer_new_timer(&timer_config, &gptimer));
// 使能定时器
ESP_ERROR_CHECK(gptimer_enable(gptimer));
// 启动定时器
ESP_ERROR_CHECK(gptimer_start(gptimer));

##中断Interrupt

外部中断EXTI

定时器中断

##FreeRTOS

现有一个需求在 GPIO1上的 LED 灯需要每 500毫秒闪烁一次GPIO2上的灯每 70毫秒闪烁一次

我们有以下常规写法

  1. 周期任务调度为节约篇幅忽略了初始化代码若想查看完整代码请参考附录 FreeRTOS 周期任务调度

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40

    #include "freertos/FreeRTOS.h"
    #include "freertos/task.h"
    #include "driver/gpio.h"
    #include "driver/timer.h"

    volatile uint8_t LED_1_Blink_Flag = 0;
    volatile uint8_t LED_2_Blink_Flag = 0;
    bool IRAM_ATTR timer_callback(void *args);

    void setup() {
    /* 初始化 GPIO 引脚 */
    /* ...此部分略... */

    /* 初始化定时器 */
    /* ...此部分略... */
    }

    void app_main(void) {
    while(1) {

    if(LED_1_Blink_Flag) {
    gpio_set_level(GPIO_NUM_1, !gpio_get_level(GPIO_NUM_1));
    LED_1_Blink_Flag = 0;
    }
    if(LED_2_Blink_Flag) {
    gpio_set_level(GPIO_NUM_2, !gpio_get_level(GPIO_NUM_2));
    LED_2_Blink_Flag = 0;
    }
    }
    }

    bool IRAM_ATTR timer_callback(void *args) {
    static uint16_t counter = 0;
    counter ++;
    if(counter % 500 == 0) LED_1_Blink_Flag = 1;
    if(counter % 1000 == 0) LED_2_Blink_Flag = 1;
    if(counter >= 60000) counter = 0;
    return true;
    }

附录

FreeRTOS 周期任务调度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "driver/timer.h"

volatile uint8_t LED_1_Blink_Flag = 0;
volatile uint8_t LED_2_Blink_Flag = 0;
bool IRAM_ATTR timer_callback(void *args);

void setup() {
/* 初始化 GPIO 引脚 */
gpio_config_t gpio_LED_config = {
.pin_bit_mask = (1ull << GPIO_NUM_1) | (1ull << GPIO_NUM_2),
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE
};

gpio_config(&gpio_LED_config);
gpio_set_level(GPIO_NUM_1, 1);
gpio_set_level(GPIO_NUM_2, 1);

/* 初始化定时器 */
timer_config_t _1ms_timer_config = {
.alarm_en = TIMER_ALARM_EN,
.counter_en = TIMER_PAUSE,
.intr_type = TIMER_INTR_LEVEL,
.counter_dir = TIMER_COUNT_UP,
.auto_reload = TIMER_AUTORELOAD_EN,
.divider = 80
};

timer_init(TIMER_GROUP_0, TIMER_0, &_1ms_timer_config);
timer_set_counter_value(TIMER_GROUP_0, TIMER_0, 0);
timer_set_alarm_value(TIMER_GROUP_0, TIMER_0, 1000);
timer_isr_callback_add(TIMER_GROUP_0, TIMER_0, timer_callback, NULL, 0);
timer_enable_intr(TIMER_GROUP_0, TIMER_0);
timer_start(TIMER_GROUP_0, TIMER_0);
}

void app_main(void) {
while(1) {

if(LED_1_Blink_Flag) {
gpio_set_level(GPIO_NUM_1, !gpio_get_level(GPIO_NUM_1));
LED_1_Blink_Flag = 0;
}
if(LED_2_Blink_Flag) {
gpio_set_level(GPIO_NUM_2, !gpio_get_level(GPIO_NUM_2));
LED_2_Blink_Flag = 0;
}
}
}

bool IRAM_ATTR timer_callback(void *args) {
static uint16_t counter = 0;
counter ++;
if(counter % 500 == 0) LED_1_Blink_Flag = 1;
if(counter % 1000 == 0) LED_2_Blink_Flag = 1;
if(counter >= 60000) counter = 0;
return true;
}