概述
上一节 GPIO输入(按键检测) 采用轮询方式检测按键,CPU 需要不断查询电平状态。本教程改用外部中断方式:按键触发中断时自动进入回调函数翻转 LED,CPU 无需轮询即可响应,这是嵌入式开发的常用方式。
用大白话讲:轮询像门卫一直盯着门口看有没有人按门铃,很费精力;中断像在门铃上装了个自动装置——没人按的时候 CPU 安心做别的事(甚至睡觉),一有人按门铃(引脚电平变化),装置自动打断当前工作去开门(执行回调函数)。本教程就用这个「门铃」检测按键,按一下 LED 翻转一次。
本教程基于安信可官方 SDK(Ai-Thinker-Open/Ai-Thinker-WB2,版本
release_bl_iot_sdk_1.6.40)的中断 API 编写。官方 SDK 中 GPIO 中断能力由components/platform/hosal/include/hosal_gpio.h提供,官方实际用法可参考applications/iot-solution/qcloud_demo(key_irq按键中断回调)。
与按键检测教程接线一致(IO8 按键、IO14 LED):
| Ai-WB2 引脚 | 外设 |
|---|---|
| IO14 | LED 正极(串联 330Ω 限流电阻),LED 负极接 GND |
| IO8 | 按键一端,按键另一端接 3V3 |
| 3V3 / GND | 供电 |
💡 本教程按键配置为内部下拉输入(
INPUT_PULL_DOWN,引脚悬空时默认读低电平),按键另一端接 3V3,按下瞬间 IO8 由低变高产生上升沿(电平从低跳到高的那个瞬间),触发外部中断。与轮询教程的外部上拉接法相反,接线时请注意。
官方 SDK 未单独提供外部中断示例,本教程在官方 blink 示例工程骨架(main/main.c + Makefile)上改写:
cd ~/Ai-Thinker-WB2/applications/get-started/blink
说明:
cd是「进入目录」命令,这里借用官方 blink 示例工程骨架作为本教程的工程;后续的make编译、make flash烧录命令都必须先在这个目录里执行。
将 main/main.c 替换为以下内容,本步完整代码已移至文末,见:
📜 完整代码 — 位于本页「完整代码」章节,默认折叠,点击展开。本教程改写代码,基于官方 blink 工程骨架(
applications/get-started/blink)编写,API 风格与官方示例(applications/iot-solution/qcloud_demo)的key_irq用法完全一致。
代码要点:
| 代码 | 作用 |
|---|---|
led.config = OUTPUT_PUSH_PULL |
LED 引脚配置为推挽输出模式(能输出高/低电平),不配置就没法点亮 LED |
key.config = INPUT_PULL_DOWN |
按键引脚配置为内部下拉输入(悬空默认低电平),不配置就检测不到按下 |
hosal_gpio_irq_set(&key, HOSAL_IRQ_TRIG_POS_PULSE, key_irq, NULL) |
注册中断:按键上升沿(由低变高的瞬间)触发 key_irq 回调,不注册按下也没反应 |
key_irq(void *arg) |
中断回调(触发时系统自动执行的函数),内部翻转 LED 输出电平,实现按一下翻一次 |
blog_info("[key irq]") |
打印中断日志(LOG 等级为 INFO),方便确认每次按键都触发了中断 |
⚠️ 中断回调中不要执行阻塞操作(延时、打印大量日志等),应尽快处理并返回;需要唤醒任务时使用
vTaskNotifyGiveFromISR()(见官方 qcloud_demo 的key_irq)。
在工程目录执行编译:
make -j8
说明:
make是「编译工程」命令,把代码变成开发板能运行的固件(程序文件);-j8表示用 8 个核并行编译,速度更快。
编译成功后生成固件 build_out/blink.bin。
开发板保持 USB 连接,确认串口设备号后执行烧录:
make flash p=/dev/ttyUSB0 b=921600
说明:
make flash是「烧录」命令,把编译好的固件写进开发板芯片。p=后面是串口设备号(改成你电脑上实际的串口,可用ls /dev/ttyUSB*查看),b=是烧录波特率(传输速度)。
⏳ 烧录过程中按提示长按开发板 EN 键进入下载模式,等待进度条完成即烧录成功。
烧录完成后开发板自动重启运行,每次按下按键,LED 亮灭状态翻转一次。
打开串口助手(波特率 921600)查看日志,每次按键输出一行中断日志:
[key irq]
[key irq]
...
💡 与轮询方式不同,中断方式下 CPU 平时处于休眠等待状态,只有按键触发才进入回调,功耗与响应效率更高。
看到「每次按键 LED 亮灭翻转一次」且串口每按一次打印一行 [key irq] 即为成功;如果按键无反应或按一次出多行日志,说明还没成功,对照文末「常见问题与踩坑提示」排查。
代码执行流程
例程从启动到运行的完整流程如下(图中的循环箭头表示反复执行):
本文 API 汇总
hosal_gpio_init(gpio)
按 dev 结构体中的 port(引脚)与 config(方向/模式)配置引脚(本教程配置 LED 输出与按键输入)。
参数:
gpio:hosal_gpio_dev_t结构体指针,必填。其中port选 GPIO 引脚号(0~22),config为方向/模式枚举,可选值:OUTPUT_PUSH_PULL(推挽输出)/INPUT_PULL_DOWN(内部下拉输入)/INPUT_HIGH_IMPEDANCE(高阻输入)
返回值:成功返回 0;失败返回负值错误码
hosal_gpio_output_set(gpio, value)
向已配置为输出的引脚输出高或低电平。
参数:
gpio:hosal_gpio_dev_t结构体指针(port指定引脚号)value:输出电平,可选值:0低电平(0V)/ 非0(如1)高电平(3.3V)
返回值:成功返回 0;失败返回负值错误码
hosal_gpio_irq_set(gpio, trigger, handler, arg)
为引脚注册中断处理函数,电平变化满足触发条件时自动调用(本教程按键上升沿触发)。
参数:
gpio:hosal_gpio_dev_t结构体指针trigger:触发类型,可选值:HOSAL_IRQ_TRIG_POS_PULSE(上升沿触发)/HOSAL_IRQ_TRIG_NEG_PULSE(下降沿触发)/HOSAL_IRQ_TRIG_POS_LEVEL(高电平触发)/HOSAL_IRQ_TRIG_NEG_LEVEL(低电平触发)handler:中断回调函数指针,形如void handler(void *arg),在中断上下文中执行,需快速返回arg:回调参数指针,无参传NULL
返回值:成功返回 0;失败返回负值错误码
blog_info(fmt, ...)
输出一条 INFO 级日志(UART0,受 blog_set_level 级别过滤)。
参数:
fmt:格式化字符串,同printf用法,必填...:变参,与fmt占位符对应,可省略
返回值:无
完整代码
以下为改写后的 main/main.c 完整源码。官方 SDK 未提供独立的外部中断示例,本代码基于官方 blink 工程骨架(applications/get-started/blink)编写,API 风格与官方示例(applications/iot-solution/qcloud_demo)的 key_irq 用法完全一致:
📜 点击展开 main/main.c 完整代码
#include <stdio.h>
#include <string.h>
#include <FreeRTOS.h>
#include <task.h>
#include <hosal_gpio.h>
#include <blog.h>
#define GPIO_BUTTON_PIN 8
#define GPIO_LED_PIN 14
static hosal_gpio_dev_t led;
static hosal_gpio_dev_t key;
/* 按键中断回调:翻转 LED 状态 */
static void key_irq(void *arg)
{
static uint8_t value = 0;
value = !value;
hosal_gpio_output_set(&led, value);
blog_info("[key irq]");
}
void main(void)
{
/* LED:推挽输出 */
led.port = GPIO_LED_PIN;
led.config = OUTPUT_PUSH_PULL;
hosal_gpio_init(&led);
hosal_gpio_output_set(&led, 0);
/* 按键:内部下拉输入,上升沿触发中断 */
key.port = GPIO_BUTTON_PIN;
key.config = INPUT_PULL_DOWN;
hosal_gpio_init(&key);
hosal_gpio_irq_set(&key, HOSAL_IRQ_TRIG_POS_PULSE, key_irq, NULL);
for (;;) {
vTaskDelay(pdMS_TO_TICKS(1000));
}
}常见问题与踩坑提示
⚠️ 按下按键无反应,LED 不翻转
原因:按键接法与中断触发类型不匹配(下拉输入须配上升沿 HOSAL_IRQ_TRIG_POS_PULSE,上拉输入须配下降沿 HOSAL_IRQ_TRIG_NEG_PULSE)
解决:确认按键一端接 IO8、另一端接 3V3(不是 GND);确认使用 INPUT_PULL_DOWN + HOSAL_IRQ_TRIG_POS_PULSE
⚠️ 一次按键触发多次翻转
原因:机械按键按下/松开瞬间电平抖动,产生多次边沿中断
解决:中断回调中做软件防抖(记录按键时间戳,间隔小于 20ms 忽略),或按键并联 100nF 电容硬件去抖
⚠️ 编译报错 hosal_gpio.h: No such file or directory
原因:工程 proj_config.mk 中未开启 hosal 组件
解决:确认工程 proj_config.mk 的 CONFIG_COMPONENT_* 开关包含 HOSAL(官方默认开启,修改过工程配置的需检查)
⚠️ 中断回调里调用 printf 导致系统卡死
原因:printf 在中断上下文可能阻塞
解决:中断回调中用 blog_info(官方示例用法)或仅置标志位,主循环中再打印
⚠️ 烧录一直卡住等待,进度条不动
原因:未进入下载模式,或数据线只能充电不能传数据
解决:烧录时按提示长按 EN 键进入下载模式;换一根能传数据的 Type-C 数据线后重试
⚠️ 找不到串口设备或提示无权限
原因:Linux 下 /dev/ttyUSB0 不存在或权限不足,Windows 下未安装 USB 转串口驱动
解决:Linux 用 ls /dev/ttyUSB* 确认设备号,权限不足执行 sudo usermod -aG dialout $USER 后重新登录;Windows 在设备管理器安装驱动并确认 COM 口号
⚠️ 执行 make 报找不到 Makefile
原因:在错误的目录执行了编译命令(必须在示例工程目录内)
解决:先执行 cd ~/Ai-Thinker-WB2/applications/get-started/blink 进入工程目录,再执行 make -j8
运行自检
每次按下按键,LED 亮灭状态翻转一次,且串口每按一次输出一行 [key irq] 日志,即外部中断功能验证通过。
遇到问题?
如有其他问题,请到统一的提问与讨论区:Ai-Thinker Discussions

