鸿蒙南向开发教程 Day 12:PWM 呼吸灯控制
·
目标:掌握 OpenHarmony 轻量系统的 PWM 接口,实现 LED 亮度渐变(呼吸灯效果)
前置条件:已完成 Day 11 的 GPIO 按键中断
一、工程结构
app/
├── BUILD.gn
└── 11_pwm/ # 模块目录
├── BUILD.gn
└── demo.c # PWM 测试代码
1.1 app/BUILD.gn
import("//build/lite/config/component/lite_component.gni")
lite_component("app") {
features = [
"11_pwm:pwm_demo", # 引用 11_pwm 模块
]
}
1.2 11_pwm/BUILD.gn
static_library("pwm_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",
]
}
二、完整代码详解
2.1 头文件与宏定义
#include <stdio.h>
#include <unistd.h>
#include "ohos_init.h"
#include "cmsis_os2.h"
#include "iot_gpio.h" # GPIO 基础接口
#include "iot_pwm.h" # PWM 接口
#include "iot_errno.h" # 错误码定义
#include "hi_io.h" # 引脚复用配置
#define RED_LED_PIN_NAME (10) # 红灯连接引脚 GPIO 10
#define RED_LED_PIN_FUNCTION WIFI_IOT_IO_FUNC_GPIO_10_GPIO
#define IOT_PWM_DUTY_MAX (100) # 最大占空比(百分比)
#define PWM_FREQ_DIVITION (64000) # PWM 频率分频值
#define DELAY_US (250000) # 延时 250ms(微秒)
#define STACK_SIZE (4096) # 任务栈大小
#define PWM_PORT_NUM (1) # PWM 通道号 1
#define DOUBLE (2) # 倍增系数
PWM 通道与引脚对应:
| PWM 通道 | 默认引脚 | 复用功能 |
|---|---|---|
| PWM 0 | GPIO 7 | HI_IO_FUNC_GPIO_7_PWM0_OUT |
| PWM 1 | GPIO 10 | HI_IO_FUNC_GPIO_10_PWM1_OUT |
| PWM 2 | GPIO 9 | HI_IO_FUNC_GPIO_9_PWM2_OUT |
| PWM 3 | GPIO 8 | HI_IO_FUNC_GPIO_8_PWM3_OUT |
不同开发板引脚定义可能不同,需查看原理图确认。
2.2 PWM 任务线程
static void PWMLedDemoTask(void)
{
static int g_count = 100; # 循环 100 次
# 1. 设置引脚复用为 PWM 输出
hi_io_set_func(RED_LED_PIN_NAME, HI_IO_FUNC_GPIO_10_PWM1_OUT);
# 2. 初始化 PWM 通道
IoTPwmInit(PWM_PORT_NUM);
while (g_count) {
# 3. 占空比从 1% 递增到 100%,每次翻倍
for (int i = 1; i < IOT_PWM_DUTY_MAX; i *= DOUBLE) {
# 启动 PWM:通道号,占空比,频率分频
IoTPwmStart(PWM_PORT_NUM, i, PWM_FREQ_DIVITION);
usleep(DELAY_US); # 延时 250ms 观察亮度
# 停止 PWM(准备下一次设置)
IoTPwmStop(PWM_PORT_NUM);
}
g_count--;
}
}
呼吸灯效果原理:
占空比 1% → LED 微亮(几乎看不见)
占空比 2% → 稍亮
占空比 4% → 更亮
占空比 8% → 明显变亮
...
占空比 64% → 很亮
占空比 100% → 最亮
视觉效果:LED 从暗到亮快速渐变,形成呼吸效果
2.3 系统入口
static void PWMLedDemo(void)
{
osThreadAttr_t attr;
# 初始化 GPIO(部分驱动要求先初始化 GPIO 再设置复用)
IoTGpioInit(RED_LED_PIN_NAME);
attr.name = "PWMLedDemoTask";
attr.attr_bits = 0U;
attr.cb_mem = NULL;
attr.cb_size = 0U;
attr.stack_mem = NULL;
attr.stack_size = STACK_SIZE;
attr.priority = osPriorityNormal;
if (osThreadNew(PWMLedDemoTask, NULL, &attr) == NULL) {
printf("[ColorfulLightDemo] Failed to create PWMLedDemoTask!\n");
}
}
APP_FEATURE_INIT(PWMLedDemo);
三、PWM 原理详解
3.1 什么是 PWM?
PWM(Pulse Width Modulation,脉冲宽度调制) 通过快速开关数字信号,利用人眼视觉暂留实现"模拟"亮度控制。
高占空比(100%): 低占空比(25%):
┌────┐ ┌────┐ ┌┐ ┌┐ ┌┐
│ │ │ │ ││ ││ ││
│ │ │ │ ││ ││ ││
└────┘ └────┘ └┘────┘┘────┘┘
亮 暗
实际 LED 在快速闪烁(通常 kHz 级别),人眼看到的是平均亮度
3.2 PWM 三要素
| 参数 | 说明 | 本例设置 |
|---|---|---|
| 频率 | 每秒脉冲次数 | PWM_FREQ_DIVITION = 64000(分频值) |
| 占空比 | 高电平时间 / 周期 | 1% ~ 100% 变化 |
| 极性 | 高电平有效 / 低电平有效 | 默认高电平有效 |
3.3 频率与分频值
Hi3861 PWM 时钟源通常为 160MHz,实际频率计算:
PWM 频率 = 时钟源 / 分频值 / 计数周期
本例分频值 64000,若计数周期 256:
频率 ≈ 160MHz / 64000 / 256 ≈ 9.77 Hz(较低,肉眼可见闪烁)
实际驱动实现可能不同,分频值直接决定频率,需根据现象调整。
若 LED 明显闪烁而非平滑渐变,说明 PWM 频率太低,应减小分频值提高频率。
四、PWM API 详解
4.1 IoTPwmInit — 初始化 PWM
unsigned int IoTPwmInit(unsigned int port);
| 参数 | 说明 |
|---|---|
port |
PWM 通道号(0 ~ 3) |
4.2 IoTPwmStart — 启动 PWM 输出
unsigned int IoTPwmStart(unsigned int port, unsigned short duty, unsigned int freq);
| 参数 | 说明 |
|---|---|
port |
PWM 通道号 |
duty |
占空比,范围 1 ~ 65535,或驱动实现的百分比 |
freq |
频率分频值(驱动相关) |
注意:不同驱动版本
duty单位可能不同,有的用百分比(1~100),有的用绝对值。本例使用百分比。
4.3 IoTPwmStop — 停止 PWM 输出
unsigned int IoTPwmStop(unsigned int port);
4.4 IoTPwmDeinit — 反初始化
unsigned int IoTPwmDeinit(unsigned int port);
五、引脚复用配置
PWM 功能需要把 GPIO 引脚切换到 PWM 模式:
hi_io_set_func(RED_LED_PIN_NAME, HI_IO_FUNC_GPIO_10_PWM1_OUT);
Hi3861 常用 PWM 复用宏:
| 宏定义 | 功能 |
|---|---|
HI_IO_FUNC_GPIO_7_PWM0_OUT |
GPIO 7 复用为 PWM0 |
HI_IO_FUNC_GPIO_10_PWM1_OUT |
GPIO 10 复用为 PWM1 |
HI_IO_FUNC_GPIO_9_PWM2_OUT |
GPIO 9 复用为 PWM2 |
HI_IO_FUNC_GPIO_8_PWM3_OUT |
GPIO 8 复用为 PWM3 |
六、编译与验证
6.1 编译烧录
VSCode 点击 Build → Upload,串口波特率 115200。
6.2 预期现象
- 开发板红灯从暗到亮渐变(呼吸效果)
- 每次亮度变化间隔 250ms
- 共循环 100 次后停止
6.3 若出现闪烁而非渐变
说明 PWM 频率过低,修改分频值:
#define PWM_FREQ_DIVITION (640) // 改为更小的分频值,提高频率
七、练习:平滑呼吸灯
当前代码占空比按指数增长(1, 2, 4, 8…),步进不均匀。改为线性增长:
for (int i = 1; i <= IOT_PWM_DUTY_MAX; i++) {
IoTPwmStart(PWM_PORT_NUM, i, PWM_FREQ_DIVITION);
usleep(10000); // 每 10ms 增加 1%
}
IoTPwmStop(PWM_PORT_NUM);
for (int i = IOT_PWM_DUTY_MAX; i >= 1; i--) {
IoTPwmStart(PWM_PORT_NUM, i, PWM_FREQ_DIVITION);
usleep(10000); // 每 10ms 减少 1%
}
IoTPwmStop(PWM_PORT_NUM);
效果:LED 缓慢由暗到亮,再由亮到暗,循环呼吸。
八、总结
| 要点 | 内容 |
|---|---|
| PWM 原理 | 快速开关,占空比控制平均亮度 |
| 引脚复用 | hi_io_set_func 切换到 PWM 模式 |
| 初始化 | IoTPwmInit(port) |
| 启动 | IoTPwmStart(port, duty, freq) |
| 停止 | IoTPwmStop(port) |
| 占空比 | 1~100,值越大越亮 |
| 频率 | 分频值越小频率越高,需避免肉眼可见闪烁 |
九、下一步
Day 13 预告:I2C 传感器(DHT11 / OLED) —— 外设总线通信实战。
更多推荐

所有评论(0)