GPIO 输出
LED 闪烁 — GPIO 驱动 | sample: blinky
学习目标
- 掌握 GPIO 输出的完整配置流程:
uapi_pin_set_mode()→uapi_gpio_init()→uapi_gpio_set_dir()→uapi_gpio_toggle() - 理解 GPIO 电平控制——高电平驱动 vs 低电平驱动的硬件差异
- 能够在 WS63 上通过 GPIO 控制 LED 闪烁
基本概念
GPIO 输出做什么
GPIO(General Purpose Input/Output)输出是 MCU 控制外设的最基本方式——CPU 写一个寄存器位,引脚输出高电平或低电平,驱动 LED 亮灭、继电器通断、三极管开关。
flowchart LR
C[CPU] -->|写寄存器| G[GPIO 控制器]
G -->|输出电平| P[物理引脚]
P -->|高/低电平| L[LED / 继电器 / 三极管]
三个步骤
WS63 的 GPIO 输出需要三步配置:
| 步骤 | API | 说明 |
|---|---|---|
| 1. 引脚复用 | uapi_pin_set_mode(pin, HAL_PIO_FUNC_GPIO) |
WS63 引脚多功能——先选 GPIO 模式 |
| 2. 方向 | uapi_gpio_set_dir(pin, GPIO_DIRECTION_OUTPUT) |
设置为输出 |
| 3. 操作 | uapi_gpio_toggle(pin) / uapi_gpio_set_val(pin, level) |
翻转或写指定电平 |
引脚复用在 pinctrl 文档中有详细说明。本篇聚焦 GPIO 输出操作本身。
驱动能力
GPIO 的驱动电流有限(通常几 mA)。LED 必须串联限流电阻,继电器/电机必须通过三极管/MOSFET 驱动——不能直接用 GPIO 引脚驱动大电流负载。
涉及 API
| API | 用途 | 头文件 |
|---|---|---|
uapi_pin_set_mode(pin, HAL_PIO_FUNC_GPIO) |
设置引脚为 GPIO 功能 | pinctrl.h |
uapi_gpio_init() |
初始化 GPIO 子系统 | gpio.h |
uapi_gpio_set_dir(pin, GPIO_DIRECTION_OUTPUT) |
设置引脚为输出方向 | gpio.h |
uapi_gpio_set_val(pin, GPIO_LEVEL_LOW) |
写指定电平(高/低) | gpio.h |
uapi_gpio_toggle(pin) |
翻转当前电平 | gpio.h |
案例说明
做什么
配置一个 GPIO 引脚为输出,以 500ms 间隔翻转电平,驱动 LED 闪烁。这是嵌入式世界的 "Hello World"。
规格与功能
| 规格项 | 说明 |
|---|---|
| 引脚 | CONFIG_BLINKY_PIN(Kconfig 可配) |
| 模式 | GPIO 输出 |
| 初始电平 | 低电平(LED 灭) |
| 闪烁周期 | 500ms(osal_msleep(500) + toggle) |
| 任务优先级 | 24 |
| 任务栈 | 0x1000(4096 字节) |
程序运行流程:引脚复用 → GPIO 初始化 → 设方向 → 设初始电平 → 死循环翻转 + 延时。
案例流程
sequenceDiagram
participant E as 入口
participant G as GPIO
participant L as LED
E->>G: uapi_pin_set_mode — GPIO 功能
E->>G: uapi_gpio_init
E->>G: uapi_gpio_set_dir — 输出
E->>G: uapi_gpio_set_val — 低电平
Note over L: LED 灭
loop 每 500ms
E->>G: uapi_gpio_toggle
Note over L: 翻转亮/灭
E->>E: osal_msleep(500)
end
案例操作指导
第一步:编译
第二步:烧录
第三步:验证
LED 以 1Hz 频率闪烁(500ms 亮 + 500ms 灭)。串口周期输出 "Blinky working."。
关键配置
| 参数 | 值 | 说明 |
|---|---|---|
BLINKY_DURATION_MS |
500 | 翻转间隔,越小闪得越快 |
| 任务优先级 | 24 | 中优先级,不影响 SLE 等关键任务 |
| 任务栈 | 4096 | 简单的 GPIO 操作,4096 足够 |
| 初始电平 | GPIO_LEVEL_LOW |
LED 灭——根据电路选择低或高 |
代码详解
完整代码参考 src/application/samples/peripheral/blinky/blinky_demo.c:
#include "pinctrl.h"
#include "gpio.h"
#include "soc_osal.h"
#include "app_init.h"
#define BLINKY_DURATION_MS 500
#define BLINKY_TASK_PRIO 24
#define BLINKY_TASK_STACK_SIZE 0x1000
static int blinky_task(const char *arg)
{
(void)arg;
/* 第1步: 引脚复用为 GPIO */
uapi_pin_set_mode(CONFIG_BLINKY_PIN, HAL_PIO_FUNC_GPIO);
/* 第2步: 设置方向为输出 */
uapi_gpio_set_dir(CONFIG_BLINKY_PIN, GPIO_DIRECTION_OUTPUT);
/* 第3步: 设置初始电平——低电平,LED 灭 */
uapi_gpio_set_val(CONFIG_BLINKY_PIN, GPIO_LEVEL_LOW);
while (1) {
osal_msleep(BLINKY_DURATION_MS); // 等 500ms
uapi_gpio_toggle(CONFIG_BLINKY_PIN); // 翻转电平
osal_printk("Blinky working.\r\n"); // 打印状态
}
return 0;
}
static void blinky_entry(void)
{
osal_task *task_handle = NULL;
osal_kthread_lock();
task_handle = osal_kthread_create(
(osal_kthread_handler)blinky_task, 0,
"BlinkyTask", BLINKY_TASK_STACK_SIZE);
if (task_handle != NULL) {
osal_kthread_set_priority(task_handle, BLINKY_TASK_PRIO);
osal_kfree(task_handle); // 任务句柄可释放
}
osal_kthread_unlock();
}
app_run(blinky_entry);
osal_kfree(task_handle)释放的是创建任务返回的句柄(osal_task*),不是任务本身。任务创建后独立运行,句柄不再需要即可释放。