前言

闲来无事,再开一坑,说是说从零学起,实际上就是分析官方例子。顺便帮助大家总结一波,在此分析一下,不要拿我的博文作为自己的学习esp-idf的”教参“。

官方例程详细注释

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/gpio.h"

/**
 * 摘要:
 * 此代码展示了如何配置 gpio 以及如何使用 gpio 中断。
 *
 * GPIO 状态:
 * GPIO18: 推挽输出
 * GPIO19: 推挽输出
 * GPIO4:  上拉输入, 上升沿和下降沿中断
 * GPIO5:  上拉输入, 下降沿中断
 *
 * 实验操作:
 * GPIO18 连接到 GPIO4
 * GPIO19 连接到 GPIO5
 * 利用GPIO18/19产生信号,触发GPIO4/5上的中断
 *
 */

#define GPIO_OUTPUT_IO_0    18
#define GPIO_OUTPUT_IO_1    19
#define GPIO_OUTPUT_PIN_SEL  ((1ULL<<GPIO_OUTPUT_IO_0) | (1ULL<<GPIO_OUTPUT_IO_1))
#define GPIO_INPUT_IO_0     4
#define GPIO_INPUT_IO_1     5
#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; // 设置消息队列,用于传递中断的信息

// 真正的中断服务函数,这里只干一件事,通过队列把中断信息打包发送出去
static void IRAM_ATTR gpio_isr_handler(void* arg) 
{
    uint32_t gpio_num = (uint32_t) arg;
    xQueueSendFromISR(gpio_evt_queue, &gpio_num, NULL); // 中断专属发送信息给队列的函数
}

// 实际起作用的gpio中断处理函数,一直等待中断发送信息然后到这里处理信息
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_conf1 = {
    	.intr_type = GPIO_INTR_DISABLE, //不启用gpio中断
    	.mode = GPIO_MODE_OUTPUT,//推挽输出模式
    	.pin_bit_mask = GPIO_OUTPUT_PIN_SEL,//设置goio,可以同时设置多个
    	.pull_down_en = 0,// 不下拉
    	.pull_up_en = 0,// 不上拉
    };
    gpio_config(&io_conf1);
	
	gpio_config_t io_conf2 = {
    	.intr_type = GPIO_INTR_POSEDGE, //启用上升沿中断
    	.mode = GPIO_MODE_INPUT,//输入模式
    	.pin_bit_mask = GPIO_INPUT_PIN_SEL,//设置goio,可以同时设置多个
    	.pull_down_en = 0,// 不下拉
    	.pull_up_en = 1,// 上拉
    };
    gpio_config(&io_conf2);

    //改变gpio中断模式为任意边沿中断(上升沿和下降沿中断)
    gpio_set_intr_type(GPIO_INPUT_IO_0, GPIO_INTR_ANYEDGE);

    //创建用于传递中断信息的消息队列
    gpio_evt_queue = xQueueCreate(10, sizeof(uint32_t));
    //开始gpio中断处理线程
    xTaskCreate(gpio_task_example, "gpio_task_example", 2048, NULL, 10, NULL);

    //安装gpio中断驱动(参数为优先级)
    gpio_install_isr_service(ESP_INTR_FLAG_DEFAULT);
    //给指定的gpio绑定中断服务函数
    gpio_isr_handler_add(GPIO_INPUT_IO_0, gpio_isr_handler, (void*) GPIO_INPUT_IO_0);
    gpio_isr_handler_add(GPIO_INPUT_IO_1, gpio_isr_handler, (void*) GPIO_INPUT_IO_1);
    
    // 打印内存使用情况
    printf("Minimum free heap size: %d bytes\n", esp_get_minimum_free_heap_size());

    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电平
        gpio_set_level(GPIO_OUTPUT_IO_1, cnt % 2);
    }
}

吐槽

本例程总的来说还不错,只不过官方为了尽可能的展示自己的api在绑定好gpio的中断服务函数后,多此一举的remove掉了,然后再次绑定。

esp01 GPIO esp01 gpio0_esp32


这里我删掉了这个迷惑行为。还有就是为了大家看起来简洁,我直接使用结构体声明的时候赋值。其余的部分均参照官方例子

解析官方例子

实际上,这个最简单的代码,在没有接触过嵌入式系统的人来说会有点迷惑。我们如果玩裸机编程玩惯了,下意识的情况会选择去直接写中断服务函数。实际上,中断服务函数的作用是再外界的信号来的时候,打断你手头做的事情,去执行它的服务函数,如果该函数的时间很长,则会导致整个程序的其它东西被耽搁。而我们通过全局变量将变量保存后,丢个主循环去处理,则会导致处理的不够及时,整个系统的实时性会变得很差。于是乎,上了嵌入式系统后,我们就结合二者的情况,专门创建一个线程去做。当我们的中断来临的时候,把中断信息打包好,然后交给中断处理的线程去处理这个信息。由于线程之间的并发性,所以不会去耽搁别的线程,这个是不同于裸机的中断编程思维。

总结

esp01 GPIO esp01 gpio0_嵌入式_02