概述
PWM(Pulse Width Modulation,脉宽调制)通过调节输出方波的占空比控制平均电平,是 LED 调光、蜂鸣器、电机调速的基础。本教程使用官方示例 三通道 PWM 驱动 RGB LED:红、绿、蓝三路分别输出不同占空比的波形。
用大白话讲:PWM 就像快速开关灯的手:开的时间长、关的时间短,灯就显得亮;反过来就暗。这个「开的时间占总时间的比例」就是占空比(占空比 50% = 一半时间开、一半时间关)。因为切换速度快到肉眼反应不过来,灯看起来就是稳定的明暗。
本教程基于安信可官方 SDK(Ai-Thinker-Open/Ai-Thinker-WB2,版本
release_bl_iot_sdk_1.6.40)的官方示例applications/peripherals/demo_pwm编写,代码可在本地 SDK 中直接找到。
按官方示例接线(见 SDK applications/peripherals/demo_pwm/README.md):
| Ai-WB2 引脚 | RGB LED 引脚 |
|---|---|
| IO14 | Red(红) |
| IO17 | Green(绿) |
| IO3 | Blue(蓝) |
| 3V3 | VCC |
| GND | GND |
💡 示例将三路 GPIO 复用为 PWM 功能(复用 = 一根针脚可切换成不同功能,
gpioFun = 8就是指定 PWM 功能),同一引脚不能同时用于普通 GPIO 输出。
打开终端,进入官方 demo_pwm 示例工程目录:
cd ~/Ai-Thinker-WB2/applications/peripherals/demo_pwm
说明:
cd是「进入目录」命令,这里进入 demo_pwm 示例工程目录;后续的make编译、make flash烧录命令都必须先在这个目录里执行。
打开 demo_pwm/main.c,本步完整代码已移至文末,见:
📜 完整代码 — 位于本页「完整代码」章节,默认折叠,点击展开,与官方示例(
applications/peripherals/demo_pwm/demo_pwm/main.c)完全一致。
代码要点:
| 代码 | 作用 |
|---|---|
GLB_GPIO_Cfg_Type cfg[] |
配置 GPIO 复用为 PWM 功能:gpioPin 指定引脚,gpioFun = 8 指定复用功能;引脚或功能号错则无波形输出 |
GLB_GPIO_Init(cfg + i) |
初始化三路 GPIO 复用配置,不调用则引脚仍处于普通 GPIO 状态,不会出波形 |
PWM_Smart_Configure2(ch, clkDiv, period, threshold2) |
配置 PWM 通道:频率 = 80MHz ÷ Divider ÷ Period,占空比 = threshold2 ÷ Period;算错灯就不亮或亮度不对 |
PWM_Channel_Enable(ch) |
使能 PWM 通道,开始输出;不使能引脚永远是低电平,灯不亮 |
三路 PWM 输出参数汇总:
| 通道 | 引脚 | 占空比 | 频率 |
|---|---|---|---|
| 2 | IO14(红) | 50%(1/2) | 40MHz |
| 3 | IO17(绿) | 75%(6/8) | 5MHz |
| 4 | IO3(蓝) | 10%(1/10) | 1MHz |
在工程目录执行编译:
make -j8
说明:
make是「编译工程」命令,把代码变成开发板能运行的固件(程序文件);-j8表示用 8 个核并行编译,速度更快。
编译成功后生成固件 build_out/demo_pwm.bin。
开发板保持 USB 连接,确认串口设备号后执行烧录:
make flash p=/dev/ttyUSB0 b=921600
说明:
make flash是「烧录」命令,把编译好的固件写进开发板芯片。p=后面是串口设备号(改成你电脑上实际的串口,可用ls /dev/ttyUSB*查看),b=是烧录波特率(传输速度)。
⏳ 烧录过程中按提示长按开发板 EN 键进入下载模式,等待进度条完成即烧录成功。
烧录完成后开发板自动重启运行,RGB LED 三色灯珠以各自占空比点亮,整体呈混合色;用逻辑分析仪可观察到三路不同频率、不同占空比的方波(见官方示例 img/logic_analyzer.jpg)。

💡 将某通道
threshold2改为period的 1/2 即为 50% 占空比;改为0则恒为低电平。修改后重新make -j8 && make flash即可验证。
看到三色灯珠同时点亮、亮度不同(呈混合色)即为成功;如果某个灯不亮或颜色不对,说明还没成功,对照文末「常见问题与踩坑提示」排查。
代码执行流程
例程从启动到运行的完整流程如下(图中的循环箭头表示反复执行):
本文 API 汇总
GLB_GPIO_Init(cfg)
把指定引脚复用为 PWM 输出等外设功能(本教程配置三路 GPIO 复用为 PWM 功能)。
参数:
cfg:GLB_GPIO_Cfg_Type结构体指针,必填。关键字段:gpioPin(引脚号,可选值:GPIO_PIN_0~GPIO_PIN_22,需为 PWM 支持的引脚)、gpioFun(复用功能号,可选值:GPIO_FUN_PWM0~GPIO_FUN_PWM5,本教程8)、gpioMode(方向模式,本教程GPIO_MODE_OUTPUT)、pullType(上下拉,本教程GPIO_PULL_DOWN)
返回值:无
PWM_Channel_Enable(ch)
打开 PWM 通道,开始输出配置好的波形。
参数:
ch:PWM 通道号,可选值:PWM_CH0~PWM_CH5(需与 GPIO 复用功能号对应,如PWM_CH0↔GPIO_FUN_PWM0)
返回值:无
PWM_Smart_Configure2(ch, clkDiv, period, threshold2)
设置分频、周期与阈值,确定输出频率和占空比(示例自定义封装,源自官方 PWM_Smart_Configure)。
参数:
ch:PWM 通道号,可选值:PWM_CH0~PWM_CH5clkDiv:时钟分频系数,可选值:1~255(频率 = 80MHz ÷ Divider ÷ Period)period:周期计数(决定频率),可选值:1~0xFFFF,如4000(占空比分母)threshold2:阈值 2(决定占空比),可选值:0~period,占空比 = threshold2 ÷ period,如2000(50% 占空比)
返回值:无
完整代码
以下为 demo_pwm/main.c 完整源码,与官方示例(applications/peripherals/demo_pwm/demo_pwm/main.c)完全一致:
📜 点击展开 demo_pwm/main.c 完整代码
#include <FreeRTOS.h>
#include <task.h>
#include <blog.h>
#include <bl602.h>
#include <bl602_gpio.h>
#include <bl602_glb.h>
#include <bl_pwm.h>
#define PWM_Get_Channel_Reg(ch) (PWM_BASE+PWM_CHANNEL_OFFSET+(ch)*0x20)
#define PWM_STOP_TIMEOUT_COUNT (160*1000)
/// @brief Copy from PWM_Smart_Configure. Use Bus Clock instead of External Crystal Clock for PWM Timer
/// @param ch PWM Channel
/// @param clkDiv PWM clock divider
/// @param period PWM period
/// @param threshold2
/// @return
BL_Err_Type PWM_Smart_Configure2(PWM_CH_ID_Type ch, uint16_t clkDiv, uint16_t period,uint16_t threshold2)
{
uint32_t tmpVal;
uint32_t timeoutCnt = PWM_STOP_TIMEOUT_COUNT;
/* Get channel register */
uint32_t PWMx = PWM_Get_Channel_Reg(ch);
tmpVal = BL_RD_REG(PWMx, PWM_CONFIG);
// if(BL_GET_REG_BITS_VAL(tmpVal, PWM_REG_CLK_SEL) != PWM_CLK_XCLK){
if(BL_GET_REG_BITS_VAL(tmpVal, PWM_REG_CLK_SEL) != PWM_CLK_BCLK){
BL_WR_REG(PWMx, PWM_CONFIG, BL_SET_REG_BIT(tmpVal, PWM_STOP_EN));
while(!BL_IS_REG_BIT_SET(BL_RD_REG(PWMx, PWM_CONFIG), PWM_STS_TOP)){
timeoutCnt--;
if(timeoutCnt == 0){
return TIMEOUT;
}
}
// tmpVal = BL_SET_REG_BITS_VAL(tmpVal, PWM_REG_CLK_SEL, PWM_CLK_XCLK);
tmpVal = BL_SET_REG_BITS_VAL(tmpVal, PWM_REG_CLK_SEL, PWM_CLK_BCLK);
}
tmpVal = BL_SET_REG_BITS_VAL(tmpVal, PWM_OUT_INV, PWM_POL_NORMAL);
tmpVal = BL_SET_REG_BITS_VAL(tmpVal, PWM_STOP_MODE, PWM_STOP_GRACEFUL);
BL_WR_REG(PWMx, PWM_CONFIG, tmpVal);
/* Config pwm division */
BL_WR_REG(PWMx, PWM_CLKDIV, clkDiv);
/* Config pwm period and duty */
BL_WR_REG(PWMx, PWM_PERIOD, period);
BL_WR_REG(PWMx, PWM_THRE1, 0);
BL_WR_REG(PWMx, PWM_THRE2, threshold2);
return SUCCESS;
}
int main(void)
{
GLB_GPIO_Cfg_Type cfg[3] = {
{
.drive = 0,
.smtCtrl = 1,
.gpioMode = GPIO_MODE_OUTPUT,
.pullType = GPIO_PULL_DOWN,
.gpioPin = 14, /// red
.gpioFun = 8,
},
{
.drive = 0,
.smtCtrl = 1,
.gpioMode = GPIO_MODE_OUTPUT,
.pullType = GPIO_PULL_DOWN,
.gpioPin = 17, /// green
.gpioFun = 8,
},
{
.drive = 0,
.smtCtrl = 1,
.gpioMode = GPIO_MODE_OUTPUT,
.pullType = GPIO_PULL_DOWN,
.gpioPin = 3, /// blue
.gpioFun = 8,
},
};
for (int i = 0; i < 3; i++) {
GLB_GPIO_Init(cfg + i);
PWM_CH_ID_Type ch = cfg[i].gpioPin % PWM_CH_MAX;
PWM_Channel_Disable(ch);
}
/// PWM Frequency = 80 Hz / Divider / Period
/// set pwm channel 2 for 50% duty, 40MHz
/// Divider = 1
/// Period = 2
PWM_Smart_Configure2(2, 1, 2, 1);
PWM_Channel_Enable(2);
/// set pwm channel 3 for 75% duty, 5MHz
/// Divider = 2
/// Period = 8
PWM_Smart_Configure2(3, 2, 8, 6);
PWM_Channel_Enable(3);
/// set pwm channel 3 for 10% duty, 1MHz
/// Divider = 8
/// Period = 10
PWM_Smart_Configure2(4, 8, 10, 1);
PWM_Channel_Enable(4);
for (;;) {
vTaskDelay(pdMS_TO_TICKS(1000));
}
return 0;
}常见问题与踩坑提示
⚠️ RGB LED 不亮或颜色异常
原因:RGB LED 模块引脚顺序与示例不符,或共阳/共阴类型不匹配
解决:对照模块丝印确认 R/G/B 引脚分别接 IO14/IO17/IO3;共阳 LED 需将 PWM_OUT_INV 反相或将 3V3/GND 对调
⚠️ 同一引脚先做 GPIO 后配 PWM 不生效
原因:引脚复用功能未切换干净
解决:重新上电或复位后再烧录;确认 gpioFun = 8 为 PWM 复用功能号(见 SDK 引脚复用表)
⚠️ 占空比计算公式容易记反
原因:threshold2 与 Period 的角色混淆
解决:占空比 = threshold2 / Period,例如 Period=10、threshold2=1 即 10% 占空比
⚠️ 烧录一直卡住等待,进度条不动
原因:未进入下载模式,或数据线只能充电不能传数据
解决:烧录时按提示长按 EN 键进入下载模式;换一根能传数据的 Type-C 数据线后重试
⚠️ 找不到串口设备或提示无权限
原因:Linux 下 /dev/ttyUSB0 不存在或权限不足,Windows 下未安装 USB 转串口驱动
解决:Linux 用 ls /dev/ttyUSB* 确认设备号,权限不足执行 sudo usermod -aG dialout $USER 后重新登录;Windows 在设备管理器安装驱动并确认 COM 口号
⚠️ 执行 make 报找不到 Makefile
原因:在错误的目录执行了编译命令(必须在示例工程目录内)
解决:先执行 cd ~/Ai-Thinker-WB2/applications/peripherals/demo_pwm 进入工程目录,再执行 make -j8
运行自检
RGB LED 三色同时点亮且亮度不同(混合色),或逻辑分析仪观察到三路不同频率方波,即 PWM 功能验证通过。
遇到问题?
如有其他问题,请到统一的提问与讨论区:Ai-Thinker Discussions

