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

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐