pwm开发指南
本文档以 pwm_demo.c 为例,带你在 HiDiTing 开发板上快速跑通第一个 PWM(脉冲宽度调制)控制应用,并了解如何基于它构建自己的应用。
PWM 驱动背景知识
PWM工作原理
PWM(脉冲宽度调制)是一种通过调节脉冲宽度(占空比)来控制输出功率的数字调制技术。PWM 信号由高电平和低电平两部分组成,通过调整高电平时间与周期的比例(即占空比)来控制输出效果。
| 参数 | 说明 | 应用场景 |
|---|---|---|
| 占空比 | 高电平时间与总周期的比例 | 调节 LED 亮度、电机转速、蜂鸣器音量 |
| 频率 | 每秒脉冲周期数 | 决定 PWM 信号的基频 |
| 相位偏移 | 信号的起始相位偏移量 | 多通道 PWM 同步控制 |
| 连续/非连续模式 | 是否持续输出 PWM 信号 | 连续模式用于持续控制,非连续模式用于单次触发 |
占空比说明:
- 占空比 = high_time / (high_time + low_time) × 100%
- 本示例中,1KHz 频率对应的时钟周期数为 32000,因此:
- 80% 占空比:high_time = 25600,low_time = 6400
- 20% 占空比:high_time = 6400,low_time = 25600
操作流程
本示例通过 AT 命令触发 PWM 输出测试,演示了完整的 PWM 操作流程:
PWM 初始化流程:
- 引脚配置:调用 uapi_pin_set_mode() 将引脚配置为 PWM 功能模式。
- PWM 初始化:调用 uapi_pwm_init() 初始化 PWM 模块。
- 打开通道:调用 uapi_pwm_open() 打开指定 PWM 通道并配置初始参数。
- 配置组:调用 uapi_pwm_set_group() 将通道添加到指定组。
PWM 输出流程(循环 5 次):
- 预加载配置:调用 uapi_pwm_config_preload() 设置新的占空比参数。
- 启动输出:调用 uapi_pwm_start_group() 启动 PWM 组输出。
- 延时输出:调用 uapi_tcxo_delay_ms(500) 持续输出 500ms。
- 停止输出:调用 uapi_pwm_stop_group() 停止 PWM 组输出。
PWM 资源清理流程:
- 关闭通道:调用 uapi_pwm_close() 关闭 PWM 通道。
- 去初始化:调用 uapi_pwm_deinit() 释放 PWM 资源。
关键参数说明
| 宏定义 | 值 | 说明 |
|---|---|---|
PWM_FREQ_1KHZ |
32000 | 1KHz 对应的时钟周期数 |
PWM_DUTY_80PCT |
25600 | 80% 占空比的高电平时间 |
PWM_DUTY_20PCT |
6400 | 20% 占空比的高电平时间 |
PWM_OFFSET_0 |
0 | 相位偏移为 0 |
PWM_CYCLES_CONTINUOUS |
0 | 0 表示连续输出模式 |
CONFIG_PWM_CHANNEL |
0 | PWM 通道号 |
CONFIG_PWM_GROUP_ID |
0 | PWM 组 ID |
CONFIG_PWM_PIN |
34 | PWM 输出引脚 |
CONFIG_PWM_PIN_MODE |
1 | PWM 功能对应的引脚复用模式 |
API 接口列表
本文档示例代码中使用的接口:
| 接口函数 | 说明 |
|---|---|
| uapi_pwm_init | 初始化 PWM 模块 |
| uapi_pwm_deinit | 去初始化 PWM |
| uapi_pwm_open | 打开 PWM 通道 |
| uapi_pwm_close | 关闭 PWM 通道 |
| uapi_pwm_set_group | 将通道添加到 PWM 组 |
| uapi_pwm_start_group | 启动 PWM 组输出 |
| uapi_pwm_stop_group | 停止 PWM 组输出 |
| uapi_pwm_config_preload | 预加载 PWM 配置 |
完整 API 列表
更多 PWM 接口请参考:PWM API 参考
注意事项:
- 在调用uapi_pwm_deinit接口之前,需要先调用uapi_pwm_close接口。
- PWM调用uapi_pwm_stop/uapi_pwm_close时不支持在中断中调用。
- PWM不支持占空比为0。
- PWM不支持多路互补输出。
- 深睡唤醒之后需要先配置复用关系,再调用uapi_pwm_deinit接口去初始化,之后调用uapi_pwm_init接口初始化。
- PWM分为M_PWM和A_PWM两种。M_PWM的使用请参考如下,A_PWM通过调用“/src/drivers/chips/3322/pwm_aon/drivers/pwm_aon.h”文件中的接口实现,虽命名存在差异,但用法相同。
M_PWM的使用请参考(PWM利用微处理器的数字输出对模拟电路进行控制):
- 将IO复用为PWM功能。
- 调用uapi_pwm_init对PWM进行初始化。
- 调用uapi_pwm_open,配置PWM参数,打开指定通道。
- 调用uapi_pwm_register_interrupt接口,注册PWM中断的回调函数。
- 调用uapi_pwm_start接口,开启指定ID的PWM信号输出。
- 调用uapi_pwm_close接口,停止指定ID的PWM信号输出。
- 调用uapi_pwm_deinit接口,去初始化指定ID的PWM。
快速跑通 PWM Demo
功能说明
PWM Demo 通过 AT 命令方式提供交互式控制,支持以下命令:
| AT 命令 | 功能 | 参数说明 |
|---|---|---|
AT+PWMSAMPLE=0 |
执行 PWM 输出测试 | 参数固定为 0,无需实际解析 |
编译
完成一站式 CLI 环境配置后执行:
# 编译固件,编译生成的固件从 output/3322/fwpkg 中获取 diting-community.fwpkg
fbb set-target pack_diting_community
fbb build
构建成功后,使用一站式 CLI 烧写固件并打开 UART2 串口监视器。以下为 Windows USB DFU 示例;将 COM3 替换为实际日志串口,其他平台和串口烧写参数参见一站式 CLI 开发环境使用指南。
fbb flash -f "$env:FBB_SDK_DIR\output\3322\fwpkg\diting-community.fwpkg" --chip 3322 -d --timeout 180
fbb monitor --port COM3 --baud 750000
使用方式
- 烧录固件并启动设备
-
通过串口发送 AT 命令,格式如下:

预期结果
PWM 输出测试(AT+PWMSAMPLE)
执行后,设备会依次输出 5 次不同占空比的 PWM 信号,每次持续 500ms:

文件结构与代码走读
文件职责
| 文件 | 作用 |
|---|---|
| samples/native_samples/pwm/CMakeLists.txt | 定义 pwm_sample 组件及驱动依赖。 |
| samples/native_samples/pwm/pwm_demo.h | 集中定义通道、组、引脚、周期和占空比等 Sample 参数。 |
| samples/native_samples/pwm/pwm_demo.c | 实现 PWM 初始化、预加载、分组启停和清理。 |
| samples/native_samples/pwm/README.md | 给出完整命令和预期波形。 |
核心数据与常量
| 名称 | 作用 | 开发注意事项 |
|---|---|---|
pwm_config_t |
描述高、低电平时间、相位偏移、周期数和重复方式。 | high_time + low_time 决定周期,占空比由两者比例决定。 |
CONFIG_PWM_PIN / CONFIG_PWM_PIN_MODE |
选择 PWM 输出引脚及复用模式。 | 必须与板级连线和目标通道匹配。 |
CONFIG_PWM_CHANNEL / CONFIG_PWM_GROUP_ID |
选择通道并将其加入输出组。 | 预加载和启停均应使用同一组、通道组合。 |
PWM_DUTY_80PCT / PWM_DUTY_20PCT |
Sample 使用的两组高低电平时间。 | 这些数值基于当前 PWM 时钟,修改时钟后需重新计算。 |
PWM_CYCLES_CONTINUOUS |
取 0 时表示连续输出。 | 业务需要有限脉冲时应设置明确周期数。 |
核心业务流程
- 配置 PWM 引脚复用并调用
uapi_pwm_init()。 - 填写
pwm_config_t,调用uapi_pwm_open()打开通道,再用uapi_pwm_set_group()建立通道组。 - 需要无毛刺更新参数时,先调用
uapi_pwm_config_preload(),再启动对应组。 - 调用
uapi_pwm_start_group()输出波形;达到业务条件后调用uapi_pwm_stop_group()。 - 退出时关闭通道并去初始化 PWM,错误分支同样执行资源回收。
现有 AT Sample 只是依次切换 80% 和 20% 占空比并统计执行结果,完整日志和异常处理可直接查看源文件。自定义应用应通过业务入口驱动同一流程,下一节保留了可构建的 app_run 示例,不再重复 AT 处理代码。
基于 PWM Demo 开发自己的应用
上面的demo是使用AT指令触发运行,HiDiTing还支持app_run方式触发应用在系统启动时自动运行,以下示例将以app_run的方式开发一个开发者自己的应用
- app_run(func) 是HiDiTing中应用层注册应用函数的宏,基于 GCC 编译器属性和自定义段区(section)自动注册来实现集中调用应用函数,系统启动时会自动遍历所有用 app_run 注册过的函数并执行,无需在 系统 main 函数里逐个调用函数。
代码清单
新建一个 PWM 控制应用(以 my_pwm_demo 为例)通常只需要以下改动:
- 新建
my_pwm_demo.c源文件 - 新建
my_pwm_demo.h头文件 - 新建
CMakeLists.txt源文件 - 在
my_pwm_demo.c中实现 PWM 操作函数 - 在
my_pwm_demo.h中定义宏常量和函数声明 - 在
CMakeLists.txt源文件编译规则
my_pwm_demo文件结构:
CMakeLists.txt 修改示例
# 在新建的 CMakeLists.txt 中添加应用的源文件
set(SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/my_pwm_demo.c
)
set(PUBLIC_HEADER
${CMAKE_CURRENT_SOURCE_DIR}
)
set(PRIVATE_HEADER
)
set(PUBLIC_DEFINES
MY_PWM_DEMO_ENABLE
)
build_component()
关键代码片段
my_pwm_demo.c —— PWM 控制实现
#include "my_pwm_demo.h"
#include "pwm.h"
#include "pinctrl.h"
#include "pinctrl_porting.h"
#include "errcode.h"
#include "debug_print.h"
#include "driver/pinctrl.h"
#include "soc_osal.h"
#include "app_init.h"
#include "tcxo.h"
#define MY_PWM_PIN 34 // PWM 输出引脚
#define MY_PWM_PIN_MODE 1 // PWM 功能对应的引脚复用模式
#define MY_PWM_CHANNEL 0 // PWM 通道号
#define MY_PWM_GROUP_ID 0 // PWM 组 ID
#define TASK_PRIO 26
#define TASK_STACK_SIZE 0x1000
static void *my_pwm_task(const char *arg)
{
unused(arg);
printf("my_pwm_demo start\n");
// 初始化
uapi_pin_set_mode(MY_PWM_PIN, MY_PWM_PIN_MODE);
uapi_pwm_init();
// 配置 PWM 参数(50% 占空比)
pwm_config_t pwm_cfg = {
.low_time = 16000, // 50% 占空比
.high_time = 16000, // 50% 占空比
.offset_time = 0,
.cycles = 0, // 连续输出
.repeat = true
};
// 打开通道
uapi_pwm_open(MY_PWM_CHANNEL, &pwm_cfg);
// 配置组
uint8_t channel_id = MY_PWM_CHANNEL;
uapi_pwm_set_group(MY_PWM_GROUP_ID, &channel_id, 1);
// 启动输出
uapi_pwm_start_group(MY_PWM_GROUP_ID);
printf("pwm started with 50%% duty cycle\n");
// 输出 2 秒
uapi_tcxo_delay_ms(2000);
// 停止输出
uapi_pwm_stop_group(MY_PWM_GROUP_ID);
printf("pwm stopped\n");
// 清理资源
uapi_pwm_close(MY_PWM_CHANNEL);
uapi_pwm_deinit();
printf("my_pwm_demo done\n");
return NULL;
}
static void my_pwm_entry(void)
{
osal_task *task_handle = NULL;
osal_kthread_lock();
task_handle = osal_kthread_create((osal_kthread_handler)my_pwm_task, 0, "MyPwmTask", TASK_STACK_SIZE);
if (task_handle != NULL) {
osal_kthread_set_priority(task_handle, TASK_PRIO);
}
osal_kthread_unlock();
}
app_run(my_pwm_entry);
测试验证
完成一站式 CLI 环境配置后执行:
# 编译固件,编译生成的固件从 output/3322/fwpkg 中获取 diting-community.fwpkg
fbb set-target pack_diting_community
fbb build

说明
-
因为
app_run是在系统启动时运行应用,所以应用的运行日志会被夹在开机启动日志中。 -
为使该
pwm应用参与编译,还需在上层CMakeLists.txt文件中以宏MY_PWM_DEMO_ENABLE为条件添加该my_pwm_demo文件。 - 宏
MY_PWM_DEMO_ENABLE默认关闭。如需启用新建PWM示例功能,需要在config.py中的diting-community配置项下将其打开。
app_run运行配置
app_run应用默认是关闭的,如需启用此应用,需用户手动在acore.prelds文件中添加 KEEP (*(SORT(.zinitcall.app_run*.init))) 具体参考示意图如下:

注意事项
引脚配置注意事项:
- PWM 输出引脚为 34,具体引脚对应的物理引脚请参考芯片手册。
- 使用前必须调用
uapi_pin_set_mode()将引脚配置为 PWM 功能模式。 - 引脚复用模式
CONFIG_PWM_PIN_MODE设置为 1。
PWM 初始化注意事项:
- 必须依次调用
uapi_pwm_init()和uapi_pwm_open()完成初始化。 - 打开通道时必须提供有效的
pwm_config_t配置结构体。 - 配置组时,通道 ID 必须与打开时使用的通道号一致。
PWM 输出注意事项:
- 每次改变占空比前,必须先调用
uapi_pwm_config_preload()预加载新配置。 - 启动输出后,需要通过延时函数控制输出时长。
- 输出完成后,必须调用
uapi_pwm_stop_group()停止输出。
资源清理注意事项:
- 使用完成后,必须调用
uapi_pwm_close()关闭通道。 - 最后调用
uapi_pwm_deinit()释放 PWM 资源。
AT 命令注意事项:
- 命令名称为大写字母,如
PWMSAMPLE。 - 参数固定为 0,无需实际解析。
常见错误
| 错误现象 | 原因 | 解决方法 |
|---|---|---|
undefined reference to 'uapi_pwm_init' |
未链接 PWM 驱动库 | 检查 CMakeLists.txt 中是否正确添加了头文件路径 |
undefined reference to 'uapi_at_cmd_table_register' |
未链接 AT 命令库 | 检查 CMakeLists.txt 中是否正确添加了 AT 相关头文件路径 |
编译报 at_ret_t 未定义 |
未包含 at.h 头文件 |
添加 #include "at.h" |
| 串口无输出 | AT 命令未注册 | 确认调用了 at_diting_pwm_example_cmd_register() |
| 引脚无响应 | 引脚未正确配置 | 确认调用了 uapi_pin_set_mode() 配置为 PWM 模式 |
| PWM 输出异常 | 占空比参数配置错误 | 确认 high_time 和 low_time 之和等于时钟周期数 |
| 资源泄漏 | 未调用资源清理函数 | 确认调用了 uapi_pwm_close() 和 uapi_pwm_deinit() |