目标 :掌握 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 点击 Build → Upload ,串口波特率 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) ------ 外设总线通信实战。