提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 前言
- 一、ESP32 + FreeRTOS 的经典启动模板
- 二、app_main函数
- 1.代码
- 2.应用GPIO配置
- 3.创建队列(中断->任务 的桥梁)
- 4.创建GPIO任务
- 5.安装GPIO中断服务
- 6.给GPIO0绑定中断函数
- 7. 小结
- 三、队列句柄,任务函数,中断服务函数
前言
立创开发板ESP32-S3的boot按键学习记录
一、ESP32 + FreeRTOS 的经典启动模板
1️⃣ 配置 GPIO0 为下降沿中断
2️⃣ 创建一个队列
3️⃣ 启动一个任务,并注册中断处理函数。
二、app_main函数
1.代码
代码如下:
voidapp_main(void){gpio_config_tio0_conf={.intr_type=GPIO_INTR_NEGEDGE,// 下降沿中断.mode=GPIO_MODE_INPUT,// 输入模式.pin_bit_mask=1<<GPIO_NUM_0,// 选择GPIO0.pull_down_en=0,// 禁能内部下拉.pull_up_en=1// 使能内部上拉};// 根据上面的配置 设置GPIOgpio_config(&io0_conf);// 创建一个队列处理GPIO事件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(0);// 给GPIO0添加中断处理gpio_isr_handler_add(GPIO_NUM_0,gpio_isr_handler,(void*)GPIO_NUM_0);}2.应用GPIO配置
代码如下:
gpio_config(&io0_conf);✅ 把上面的配置真正写到硬件寄存器。
这里的 & 表示取地址,即将前面的 io0_conf 结构体的地址给到gpio_config( )函数;
还有 * 表示解地址,即去对应的地址里取值;
3.创建队列(中断->任务 的桥梁)
代码如下:
gpio_evt_queue=xQueueCreate(10,sizeof(uint32_t));✅ 在中断里发送GPIO的编号。
4.创建GPIO任务
代码如下:
xTaskCreate(gpio_task_example,// 任务函数"gpio_task_example",// 任务名2048,// 栈大小NULL,// 参数10,// 优先级NULL// 任务句柄);✅ 启动一个 FreeRTOS 任务
| 参数 | 含义 |
|---|---|
| gpio_task_example//任务函数 | 任务入口函数 |
| 2048 | 栈空间 |
| 10 | 优先级(越高越先跑) |
👉 任务负责处理 GPIO 中断事件,任务函数参数需要与后续的任务函数保持同名,而任务名是用来打印输出的名字可以自己定义,但一般会与任务函数保持同名。
❗注意:GPIO任务创建后就开始执行GPIO任务函数,但是为什么程序没有卡在for循环(见下面的实际任务函数)中,这是因为程序执行到
xQueueReceive(gpio_evt_queue,&io_num,portMAX_DELAY)如果队列为空,那么任务的状态将会变成Blocked(阻塞),CPU会释放,而不是卡死在循环中.相当于这个任务"睡着了",CPU可以相应下一个任务.
当队列有数据了(中断来了)
xQueueSendFromISR(gpio_evt_queue,&gpio_num,NULL);✅ gpio_evt_queue 队列不为空
✅ FreeRTOS 唤醒 gpio_task_example
✅ 任务从 xQueueReceive()返回
✅ 执行 printf()
✅ 再次回到 for(;😉
✅ 再次阻塞在 xQueueReceive()
实际的任务函数是:
staticvoidgpio_task_example(void*arg){uint32_tio_num;for(;;)// 等同于 while(1){if(xQueueReceive(gpio_evt_queue,&io_num,portMAX_DELAY)){printf("GPIO[%"PRIu32"] intr, val: %d\n",io_num,gpio_get_level(io_num));}}}5.安装GPIO中断服务
代码如下:
gpio_install_isr_service(0);✅ 初始化 ESP32 的 GPIO 中断管理系统。
📌 只需调用一次
📌 为所有 GPIO 中断做准备
6.给GPIO0绑定中断函数
代码如下:
gpio_isr_handler_add(GPIO_NUM_0,gpio_isr_handler,(void*)GPIO_NUM_0);✅ 把 GPIO0 的中断“挂”到指定函数。
| 参数 | 含义 |
|---|---|
| GPIO_NUM_0 | 哪个 GPIO |
| gpio_isr_handler | 中断服务函数 |
| (void*)GPIO_NUM_0 | 传给中断的参数 |
7. 小结
整个系统的工作流程
上电 ↓app_main()↓ GPIO 配置 ↓ 创建队列 ↓ 创建任务 ↓ 安装中断服务 ↓ 绑定 GPIO 中断 ↓ 系统运行 ↓ GPIO0 按下 ↓gpio_isr_handler()↓xQueueSendFromISR()↓ gpio_task_example 被唤醒 ↓ 处理 GPIO 事件注意,这里的GPIO任务和GPIO中断函数并不是中断服务函数,中断服务函数是gpio_isr_handler,中断服务函数只干了一件事,就是将对应的触发中断的GPIO序号添加到队列中.
代码如下:
// GPIO中断服务函数staticvoidIRAM_ATTRgpio_isr_handler(void*arg){uint32_tgpio_num=(uint32_t)arg;// 获取入口参数xQueueSendFromISR(gpio_evt_queue,&gpio_num,NULL);// 把入口参数值发送到队列}三、队列句柄,任务函数,中断服务函数
代码如下:
staticQueueHandle_t gpio_evt_queue=NULL;// 定义队列句柄// GPIO中断服务函数staticvoidIRAM_ATTRgpio_isr_handler(void*arg){uint32_tgpio_num=(uint32_t)arg;// 获取入口参数xQueueSendFromISR(gpio_evt_queue,&gpio_num,NULL);// 把入口参数值发送到队列}// GPIO任务函数staticvoidgpio_task_example(void*arg){uint32_tio_num;// 定义变量 表示哪个GPIOfor(;;){if(xQueueReceive(gpio_evt_queue,&io_num,portMAX_DELAY)){// 死等队列消息printf("GPIO[%"PRIu32"] intr, val: %d\n",io_num,gpio_get_level(io_num));// 打印相关内容}}}