前言
闲来无事,再开一坑,说是说从零学起,实际上就是分析官方例子。顺便帮助大家总结一波,在此分析一下,不要拿我的博文作为自己的学习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掉了,然后再次绑定。
这里我删掉了这个迷惑行为。还有就是为了大家看起来简洁,我直接使用结构体声明的时候赋值。其余的部分均参照官方例子
解析官方例子
实际上,这个最简单的代码,在没有接触过嵌入式系统的人来说会有点迷惑。我们如果玩裸机编程玩惯了,下意识的情况会选择去直接写中断服务函数。实际上,中断服务函数的作用是再外界的信号来的时候,打断你手头做的事情,去执行它的服务函数,如果该函数的时间很长,则会导致整个程序的其它东西被耽搁。而我们通过全局变量将变量保存后,丢个主循环去处理,则会导致处理的不够及时,整个系统的实时性会变得很差。于是乎,上了嵌入式系统后,我们就结合二者的情况,专门创建一个线程去做。当我们的中断来临的时候,把中断信息打包好,然后交给中断处理的线程去处理这个信息。由于线程之间的并发性,所以不会去耽搁别的线程,这个是不同于裸机的中断编程思维。
总结
相关官方资料链接
gpio例程:https://github.com/espressif/esp-idf/tree/master/examples/peripherals/gpio/generic_gpio
gpio相关api:https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/peripherals/gpio.html
freertos相关链接:https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/system/freertos.html