ESP-IDF学习笔记-GPIO的简单使用

ESP32中的GPIO可以分为简单的GPIO和专用GPIO,这里记录普通GPIO的使用。每个管脚都可用作一个通用 IO,或连接一个内部外设信号。通过 GPIO 交换矩阵、IO MUX 和 RTC IO MUX,可配置外设模块的输入信号来源于任何的 GPIO 管脚,并且外设模块的输出信号也可连接到任意 GPIO 管脚。这些模块共同组成了芯片的输入输出控制。

官方文档

参考文章

一,GPIO配置

头文件:driver/gpio.h

GPIO配置分为结构体配置和函数配置。

结构体配置

函数原型:

1
esp_err_t gpio_config(const gpio_config_t *pGPIOConfig);

可配置GPIO‘的输入输出模式、上下拉、中断类型、引脚。

使用的gpio_config_t结构体:

成员 类型 作用 典型值
pin_bit_mask uint64_t 选择引脚,以位为单位选择 如(1 << 18)|(1 << 19)
mode gpio_mode_t 选择输入输出模式 GPIO_MODE_DISABLE
pull_up_en gpio_pullup_t 选择是否上拉 GPIO_PULLUP_DISABLE/GPIO_PULLUP_ENABLE
pull_down_en gpio_pulldown_t 选择是否下拉 GPIO_PULLDOWN_DISABLE/GPIO_PULLDOWN_ENABLE
intr_type gpio_int_type_t 中断触发模式 GPIO_INTR_DISABLE

详细参数解析:

gpio_mode_t:

  • GPIO_MODE_DISABLE:失能输入输出
  • GPIO_MODE_INPUT:输入模式
  • GPIO_MODE_OUTPUT:输出模式
  • GPIO_MODE_OUTPUT_OD:开漏输出模式
  • GPIO_MODE_INPUT_OUTPUT_OD:开漏输入输出模式
  • GPIO_MODE_INPUT_OUTPUT:输入输出模式

gpio_int_type_t:

  • GPIO_INTR_DISABLE:禁止中断
  • GPIO_INTR_POSEDGE:上升沿中断
  • GPIO_INTR_NEGEDGE:下降沿中断
  • GPIO_INTR_ANYEDGE:边缘中断
  • GGPIO_INTR_LOW_LEVEL:低电平中断
  • GPIO_INTR_HIGH_LEVEL:高电平中断

pin_bit_mask:

该参数使用每个bit表示要操作的GPIO口,典型使用值如下:

1
2
3
4
5
6
7
8
9
10
// 表示此配置对 GPIO2生效,因为这个64位数的第2位(从零开始)为1
pin_bit_mask = 0b0100

// 表示此配置对 GPIO0 和 GPIO5 生效,因为第0位和第5位为1
pin_bit_mask = 0b100001

// 表示此配置对 GPIO16 和 GPIO 18 生效,采用位运算
pin_bit_mask = (1ull << 16) | (1ull << 18)


使用例:

1
2
3
4
5
6
7
8
gpio_config_t gpio={
.pin_bit_mask=1<<GPIO_NUM_3,
.mode=GPIO_MODE_OUTPUT,
.pull_down_en=0,
.pull_up_en=1,
.intr_type=GPIO_INTR_DISABLE,
};
gpio_config(&gpio);

使用gpio_reset_pin()重置IO口。

函数配置

就是将上面的结构体分成了多个函数进行配置,这里不多讲,请自行参考官方文档。

1
2
3
4
5
6
7
8
9
10
11
12
esp_err_t gpio_set_intr_type(gpio_num_t gpio_num, gpio_int_type_t intr_type);
esp_err_t gpio_intr_enable(gpio_num_t gpio_num);
esp_err_t gpio_intr_disable(gpio_num_t gpio_num);
esp_err_t gpio_set_direction(gpio_num_t gpio_num, gpio_mode_t mode);
esp_err_t gpio_set_pull_mode(gpio_num_t gpio_num, gpio_pull_mode_t pull);
esp_err_t gpio_pullup_en(gpio_num_t gpio_num);
esp_err_t gpio_pullup_dis(gpio_num_t gpio_num);
esp_err_t gpio_set_drive_capability(gpio_num_t gpio_num, gpio_drive_cap_t strength);//设置IO口驱动能力
esp_err_t gpio_get_drive_capability(gpio_num_t gpio_num, gpio_drive_cap_t *strength);
esp_err_t gpio_hold_en(gpio_num_t gpio_num);//保持IO口状态
esp_err_t gpio_hold_dis(gpio_num_t gpio_num);

二,IO口的使用

设置IO口输出:

1
esp_err_t gpio_set_level(gpio_num_t gpio_num, uint32_t level);

读取IO口状态:

1
int gpio_get_level(gpio_num_t gpio_num);

当IO口没有设置为输入,则读取的值恒为0。

三,中断的使用

1,设置中断类型

通过结构体配置,或者使用gpio_set_intr_type()函数设置触发中断的条件。

2,创建中断服务函数

创建形如以下的中断服务函数

1
2
3
4
5
6
7
8
static QueueHandle_t 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);
}

这里使用的FreeRTOS的消息队列传递中断信息。因为GPIO中断在IRAM中工作,好处是在flash禁用的情况下也可以响应中断。且速度更快,对于这种频繁触发的中断是有利的。但是这个中断也因此无法使用printf等串口打印工作,需要转入其他Task中执行。详见官网API参考—— 中断分配Interrupt Allocation

3,创建中断处理任务函数

1
2
3
4
5
6
7
8
9
static void gpio_task_example(void* arg)
{
uint32_t io_num;
for(;;) {
if(xQueueReceive(gpio_evt_queue, &io_num, portMAX_DELAY)) {
printf("GPIO[%"PRIu32"] intr, val: %d\n", io_num, gpio_get_level(io_num));
}
}
}

4,安装中断服务

1
esp_err_t gpio_install_isr_service(int intr_alloc_flags);

参数:

intr_alloc_flags – Flags used to allocate the interrupt. One or multiple (ORred) ESP_INTR_FLAG_* values. See esp_intr_alloc.h for more info.

该函数与gpio_isr_register() 不兼容。gpio_isr_register() 会创建一个中断服务函数处理全部的中断用户需要在这个处理函数中分辨IO口分别处理。而使用这个函数,会用gpio_isr_register() 创建一个设置好的中断服务函数分辨IO口的任务已经写好,使用gpio_isr_handler_add()添加每个中断的服务函数。

相应的有移除函数。

1
void gpio_uninstall_isr_service(void);

5,为每个IO口添加handle

1
esp_err_t gpio_isr_handler_add(gpio_num_t gpio_num, gpio_isr_t isr_handler, void *args);

其中**args是上面创建的中断服务函数传入**的参数。

该函数需要在gpio_install_isr_service()后调用。使用该函数添加的服务函数不在需要使用IRAM_ATTR,除非在使用gpio_install_isr_service()时设置了ESP_INTR_FLAG_IRAM

删除函数:

1
esp_err_t gpio_isr_handler_remove(gpio_num_t gpio_num);

6,开启任务

1
xTaskCreate(gpio_task_example, "gpio_task_example", 2048, NULL, 10, NULL);

注意,上面用到的FreeRTOS的API需要引入相应头文件。

1
2
3
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"

一个例子:

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
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/gpio.h"

static QueueHandle_t gpioevent=NULL;
static void IRAM_ATTR gpio_intr(void *arg)
{
uint32_t gpio_num=(uint32_t )arg;
xQueueSendFromISR(gpioevent,&gpio_num,NULL);
}

static void gpio_task(void *arg)
{
uint32_t io_num;
for(;;){
if(xQueueReceive(gpioevent,&io_num,portMAX_DELAY))
{
printf("GPIO[%"PRIu32"] intr, val: %d\n", io_num, gpio_get_level(io_num));
}
}
}

void app_main(void)
{
gpio_config_t gpio;

gpio.pull_down_en=0;
gpio.mode=GPIO_MODE_INPUT;
gpio.pin_bit_mask=1<<GPIO_NUM_8;
gpio.intr_type=GPIO_INTR_POSEDGE;
gpio.pull_up_en=1;
gpio_config(&gpio);

gpioevent=xQueueCreate(10, sizeof(uint32_t));
xTaskCreate(gpio_task, "gpio_task", 2048, NULL, 10, NULL);

gpio_install_isr_service(0);
gpio_isr_handler_add(GPIO_NUM_8, gpio_intr, (void*) GPIO_NUM_8);

while(1)
{
...
}
}