Skip to content

概述

上一节 GPIO输入(按键检测) 采用轮询方式检测按键,CPU 需要不断查询电平状态。本教程改用外部中断方式:按键触发中断时自动进入回调函数翻转 LED,CPU 无需轮询即可响应,这是嵌入式开发的常用方式。

用大白话讲:轮询像门卫一直盯着门口看有没有人按门铃,很费精力;中断像在门铃上装了个自动装置——没人按的时候 CPU 安心做别的事(甚至睡觉),一有人按门铃(引脚电平变化),装置自动打断当前工作去开门(执行回调函数)。本教程就用这个「门铃」检测按键,按一下 LED 翻转一次。

本教程基于安信可官方 SDKAi-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_demokey_irq 按键中断回调)。

🎯本页目标通过 GPIO 外部中断检测按键,按下按键触发中断翻转 LED 亮灭,掌握中断配置与回调函数编写。
🧰前置条件① Ai-WB2 开发板、按键、LED、330Ω 限流电阻、杜邦线 ② 已按 [SDK 安装](../sdk/sdk_intro) 完成环境搭建,并完成 [GPIO输入(按键检测)](./gpio_button)。
🔗相关章节轮询方式见 [GPIO输入(按键检测)](./gpio_button);GPIO 基础输出见 [GPIO输出(点亮LED)](./gpio_led)。

硬件接线

与按键检测教程接线一致(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 输出与按键输入)。

参数

  • gpiohosal_gpio_dev_t 结构体指针,必填。其中 port 选 GPIO 引脚号(0~22),config 为方向/模式枚举,可选值:OUTPUT_PUSH_PULL(推挽输出)/ INPUT_PULL_DOWN(内部下拉输入)/ INPUT_HIGH_IMPEDANCE(高阻输入)

返回值:成功返回 0;失败返回负值错误码

hosal_gpio_output_set(gpio, value)

向已配置为输出的引脚输出高或低电平。

参数

  • gpiohosal_gpio_dev_t 结构体指针(port 指定引脚号)
  • value:输出电平,可选值:0 低电平(0V)/ 非 0(如 1)高电平(3.3V)

返回值:成功返回 0;失败返回负值错误码

hosal_gpio_irq_set(gpio, trigger, handler, arg)

为引脚注册中断处理函数,电平变化满足触发条件时自动调用(本教程按键上升沿触发)。

参数

  • gpiohosal_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 占位符对应,可省略

返回值:无

vTaskDelay(ms)

让当前任务挂起指定毫秒数,期间让出 CPU 给其他任务。

参数

  • ms:延时毫秒数,可选值:任意非负整数(内部经 pdMS_TO_TICKS 换算为系统节拍)

返回值:无


完整代码

以下为改写后的 main/main.c 完整源码。官方 SDK 未提供独立的外部中断示例,本代码基于官方 blink 工程骨架(applications/get-started/blink)编写,API 风格与官方示例(applications/iot-solution/qcloud_demo)的 key_irq 用法完全一致:

📜 点击展开 main/main.c 完整代码
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.mkCONFIG_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

Released under the MIT License. Build Time 2026-09-11 14:52:23