鸿蒙南向开发教程 Day 11:GPIO 按键中断与 LED 状态机

目标 :掌握 OpenHarmony 轻量系统的 GPIO 中断机制,实现按键控制 LED 多状态切换

前置条件:已完成 Day 10 的 GPIO 基础控制


一、工程结构

复制代码
app/
├── BUILD.gn
└── 10_gpio_interrupt/            # 模块目录
    ├── BUILD.gn
    └── demo.c                    # 按键中断 + LED 状态机代码

1.1 app/BUILD.gn

gn 复制代码
import("//build/lite/config/component/lite_component.gni")

lite_component("app") {
  features = [
    "10_gpio_interrupt:gpio_isr_demo",
  ]
}

1.2 10_gpio_interrupt/BUILD.gn

gn 复制代码
static_library("gpio_isr_demo") {
    sources = [
        "demo.c"
    ]

    include_dirs = [
        "//utils/native/lite/include",
        "//kernel/liteos_m/components/cmsis/2.0",
        "//base/iot_hardware/peripheral/interfaces/kits",
        "//device/hisilicon/hispark_pegasus/sdk_liteos/include",  # hi_io.h 路径
    ]
}

二、完整代码详解

2.1 头文件与宏定义

c 复制代码
#include <stdio.h>
#include <unistd.h>
#include "ohos_init.h"
#include "cmsis_os2.h"
#include "iot_gpio.h"      # OpenHarmony GPIO 标准接口
#include "hi_io.h"         # Hi3861 芯片特有 IO 配置接口

#define LED_TASK_GPIO       (9)     # LED 连接引脚
#define IOT_GPIO_KEY        (5)     # 按键连接引脚
#define DELAY_TICKS_30      (30)    # 30 tick 延时
#define LED_TASK_STACK_SIZE (512)   # LED 任务栈大小
#define LED_TASK_PRIO       (25)    # 任务优先级
#define GPIO_HIGH           (1)
#define GPIO_LOW            (0)

# LED 状态枚举
enum LedState {
    LED_ON = 0,     # 常亮
    LED_OFF,        # 熄灭
    LED_SPARK,      # 闪烁
};

enum LedState g_ledState = LED_SPARK;   # 全局状态,默认闪烁
static int g_count = 100;               # 循环计数,按键 100 次后停止

2.2 LED 控制任务

c 复制代码
static void *LedTask(void)
{
    while (g_count) {
        switch (g_ledState) {
            case LED_ON:
                # 常亮:输出低电平(假设低电平点亮)
                IoTGpioSetOutputVal(LED_TASK_GPIO, GPIO_LOW);
                osDelay(DELAY_TICKS_30);
                break;

            case LED_OFF:
                # 熄灭:输出高电平
                IoTGpioSetOutputVal(LED_TASK_GPIO, GPIO_HIGH);
                osDelay(DELAY_TICKS_30);
                break;

            case LED_SPARK:
                # 闪烁:翻转电平
                IoTGpioSetOutputVal(LED_TASK_GPIO, GPIO_LOW);
                osDelay(DELAY_TICKS_30);
                IoTGpioSetOutputVal(LED_TASK_GPIO, GPIO_HIGH);
                osDelay(DELAY_TICKS_30);
                break;

            default:
                osDelay(DELAY_TICKS_30);
                break;
        }
    }

    return NULL;
}

状态机设计

当前状态 按键后状态 行为
LED_ON LED_OFF 熄灭
LED_OFF LED_ON 常亮
LED_SPARK LED_OFF 熄灭

2.3 按键中断回调

c 复制代码
static void OnButtonPressed(void)
{
    enum LedState nextState = LED_SPARK;

    # 状态切换逻辑
    switch (g_ledState) {
        case LED_ON:
            nextState = LED_OFF;
            break;
        case LED_OFF:
            nextState = LED_ON;
            break;
        case LED_SPARK:
            nextState = LED_OFF;
            break;
        default:
            break;
    }

    g_ledState = nextState;   # 更新全局状态
    g_count--;                # 按键次数递减
}

注意:中断回调函数应尽量简短,不要执行阻塞操作。这里只做状态切换和计数。

2.4 系统入口

c 复制代码
static void LedExampleEntry(void)
{
    osThreadAttr_t attr;

    # 1. 初始化 LED GPIO
    IoTGpioInit(LED_TASK_GPIO);
    IoTGpioSetDir(LED_TASK_GPIO, IOT_GPIO_DIR_OUT);

    # 2. 初始化按键 GPIO
    IoTGpioInit(IOT_GPIO_KEY);

    # 3. 配置引脚复用功能为 GPIO
    hi_io_set_func(IOT_GPIO_KEY, GPIO_LOW);

    # 4. 设置按键方向为输入
    IoTGpioSetDir(IOT_GPIO_KEY, IOT_GPIO_DIR_IN);

    # 5. 设置内部上拉(按键未按下时为高电平)
    hi_io_set_pull(IOT_GPIO_KEY, GPIO_HIGH);

    # 6. 注册中断回调
    IoTGpioRegisterIsrFunc(
        IOT_GPIO_KEY,                   # 引脚号
        IOT_INT_TYPE_EDGE,              # 中断类型:边沿触发
        IOT_GPIO_EDGE_FALL_LEVEL_LOW,   # 触发方式:下降沿(高→低)
        OnButtonPressed,                # 回调函数
        NULL                            # 回调参数
    );

    # 7. 创建 LED 控制任务
    attr.name = "LedTask";
    attr.attr_bits = 0U;
    attr.cb_mem = NULL;
    attr.cb_size = 0U;
    attr.stack_mem = NULL;
    attr.stack_size = LED_TASK_STACK_SIZE;
    attr.priority = LED_TASK_PRIO;

    if (osThreadNew((osThreadFunc_t)LedTask, NULL, &attr) == NULL) {
        printf("[LedExample] Failed to create LedTask!\n");
    }
}

SYS_RUN(LedExampleEntry);

三、GPIO 中断 API 详解

3.1 hi_io_set_func --- 引脚复用配置

c 复制代码
hi_io_set_func(unsigned int gpio, unsigned char func);
参数 说明
gpio 引脚号
func 复用功能编号,0 通常为 GPIO 模式

Hi3861 引脚支持多种复用功能(UART、I2C、SPI、PWM 等),使用前需切换到 GPIO 模式。

3.2 hi_io_set_pull --- 上下拉配置

c 复制代码
hi_io_set_pull(unsigned int gpio, unsigned int pull);
参数值 含义
0 / GPIO_LOW 下拉
1 / GPIO_HIGH 上拉
2 浮空

本例使用上拉:按键未按下时引脚为高电平,按下后变为低电平,产生下降沿。

3.3 IoTGpioRegisterIsrFunc --- 注册中断

c 复制代码
unsigned int IoTGpioRegisterIsrFunc(
    unsigned int gpio,                      # 引脚号
    IotGpioIntType intType,                 # 中断类型
    IotGpioIntPolarity intPolarity,         # 触发极性
    GpioIsrCallbackFunc func,               # 回调函数
    char *arg                               # 用户参数
);

中断类型

类型 说明
IOT_INT_TYPE_LEVEL 电平触发
IOT_INT_TYPE_EDGE 边沿触发

触发极性

极性 说明
IOT_GPIO_EDGE_RISE_LEVEL_HIGH 上升沿 / 高电平
IOT_GPIO_EDGE_FALL_LEVEL_LOW 下降沿 / 低电平

本例配置:边沿触发 + 下降沿,即按键按下瞬间触发。


四、按键消抖说明

机械按键按下时会产生抖动(多个高低电平跳变),可能导致中断多次触发。

复制代码
理想波形:  ───┐      ┌──────
              └──────┘

实际波形:  ───┐ ┌─┐  ┌─┐ ┌─┐ ┌──────
              └─┘ └─┘ └─┘ └─┘ └──────
                ↑ 抖动(5~20ms)

简单消抖方法(中断回调内):

c 复制代码
static void OnButtonPressed(void)
{
    static uint32_t lastTick = 0;
    uint32_t now = osKernelGetTickCount();

    # 忽略 200ms 内的重复触发
    if (now - lastTick < 20) {
        return;
    }
    lastTick = now;

    # ... 状态切换逻辑
}

本例代码未加消抖,实际项目中建议添加。


五、硬件连接

5.1 按键连接方式

复制代码
Hi3861 GPIO 5 ──┬── 按键 ──┬── GND
                │          │
            内部上拉      按下导通
            (高电平)    (低电平)
  • 未按下:上拉电阻拉高 → 高电平
  • 按下:导通到 GND → 低电平(下降沿触发中断)

5.2 LED 连接

同 Day 10,GPIO 9 连接 LED(低电平点亮)。


六、编译与验证

6.1 编译烧录

VSCode 点击 BuildUpload ,串口波特率 115200

6.2 预期现象

阶段 LED 现象 操作
初始 快速闪烁 无需操作
按第 1 次 熄灭 按下 GPIO 5 按键
按第 2 次 常亮 再次按下
按第 3 次 熄灭 再次按下
按第 100 次 停止 任务结束

七、总结

要点 内容
引脚复用 hi_io_set_func(gpio, 0) 切换到 GPIO 模式
上下拉 hi_io_set_pull(gpio, 1) 内部上拉
中断注册 IoTGpioRegisterIsrFunc 配置边沿/电平触发
中断回调 简短快速,禁止阻塞操作
状态机 枚举定义状态,switch 处理切换
全局变量 中断与任务共享,注意原子性(本例单字节变量无需额外保护)

八、下一步

Day 12 预告:I2C 传感器(DHT11 / OLED) ------ 外设总线通信实战。

相关推荐
LEO111101 小时前
HarmonyOS应用开发实战:猫猫大作战-HUD 拆成独立子组件、用 `@Prop` 接收父组件得分/时间为锚点,把 @Prop 声明与传递、单向只
harmonyos·鸿蒙
yaoyaoxingzhe1 小时前
HarmonyOS应用开发实战:猫猫大作战-`ForEach` 的双层嵌套、`keyGenerator` 密钥、`width` 百分比响应式格子
harmonyos·鸿蒙
echohelloworld112 小时前
HarmonyOS应用开发实战:猫猫大作战-Row 横向布局、Spacer 占位、Button 双态样式(暂停灰 / 重新开始红)、底部 paddin
harmonyos·鸿蒙
爱写代码的森3 小时前
鸿蒙三方库 | harmony-utils之ObjectUtil对象拷贝与合并详解
华为·harmonyos·鸿蒙·huawei
fiona20264 小时前
HarmonyOS应用开发实战:猫猫大作战-Scroll 容器、Scroller 控制器、滚动监听与性能
harmonyos·鸿蒙
GitCode官方4 小时前
开源鸿蒙跨平台直播| 快手KRN鸿蒙适配与性能优化
华为·开源·harmonyos·atomgit
爱写代码的森4 小时前
鸿蒙三方库 | harmony-utils之TypeUtil类型检查工具详解
华为·harmonyos·鸿蒙·huawei
萌新源4 小时前
【鸿蒙开发实战】HarmonyOS 跨设备分享教程:隔空传送与碰一碰分享
华为·harmonyos
用户0934077735144 小时前
HarmonyOS WPS Open SDK 二开周回顾:从注册到关窗回传怎么串
typescript·harmonyos