pwm - PWM 模块
作者:马梦阳
一、概述
脉冲宽度调制(Pulse Width Modulation,简称 PWM)是一种通过调节数字信号的占空比来模拟模拟信号输出的技术,广泛应用于电机控制、LED 亮度调节、蜂鸣器驱动、电源管理等嵌入式场景。
LuatOS 提供了统一的 PWM 核心库(pwm),对底层硬件 PWM 模块进行抽象封装。开发者只需调用简洁的 API 接口,即可实现 PWM 输出控制,显著降低开发门槛,提升代码复用性与系统可维护性。
该库提供基础的 PWM 通道配置与控制功能,包括频率设置、占空比调节、启停控制等。
该库支持两种 API 风格,详细说明请看第二章核心示例;
二、核心示例
1、核心示例是指:使用本库文件提供的核心 API,开发的基础业务逻辑的演示代码;
2、核心示例的作用是:帮助开发者快速理解如何使用本库,所以核心示例的逻辑都比较简单;
3、更加完整和详细的 demo,请参考 LuatOS 仓库 中各个产品目录下的 demo/pwm;
pwm(main.lua)
-- LuaTools需要PROJECT和VERSION这两个信息
PROJECT = "pwm_demo"
VERSION = "001.000.000"
--[[
注意1:部分 PWM 通道通常与 GPIO 引脚绑定,在使用前需要进行复用操作;
参考文档:https://docs.openluat.com/air780epm/common/luatio/;
注意2:PWM 核心库有两种 API 风格:
旧风格(基于pwm.open()):一步完成配置与启动,适合简单固定输出场景;
新风格(基于pwm.setup()):分步操作,支持动态调节占空比和频率,适合需要灵活控制的场景;
]]
log.info("main", PROJECT, VERSION)
-- Task1:旧 API 风格 PWM 输出示例;
local function task1_old_pwm()
log.info("Task1", "启动旧风格 PWM 示例(通道 4)")
local ch = 4
-- 打开 PWM4,并设置信号频率为 1 kHz,分频精度为 100,占空比为 100/100=100%,持续输出;
local pwm_success = pwm.open(ch, 1000, 100, 0, 2147483647) -- 2147483647
-- 判断通道开启情况;
if pwm_success then
log.info("Task1", "PWM4 通道开启成功")
else
log.info("Task1", "PWM4 通道开启失败")
end
-- 等待 3 秒;
sys.wait(3000)
-- 关闭 PWM4;
pwm.close(ch)
log.info("Task1", "PWM4 已关闭")
log.info("Task1", "Task1 结束")
end
-- Task 2:新 API 风格 PWM 输出示例(动态调节);
local function task2_new_pwm()
log.info("Task2", "启动新风格 PWM 示例(通道 4)")
local ch = 4
-- 配置 PWM4 通道:信号频率为 1 kHz,分频精度为 100,占空比为 100/100=100%,持续输出;
local setup_success = pwm.setup(ch, 1000, 100, 0, 100)
-- 判断 PWM 通道参数是否配置成功;
if setup_success then
log.info("Task2", "PWM4 配置成功,稍后可启动")
else
log.info("Task2", "PWM4 配置失败,检查通道或内存")
end
-- 启动 PWM4;
local pwm_success = pwm.start(ch)
if pwm_success then
log.info("Task2", "PWM4 启动成功,稍后可输出信号")
else
log.info("Task2", "PWM4 启动失败,检查通道或内存")
end
-- 等待 2 秒后调整占空比;
-- pwm.setDuty()接口的第二个参数依赖配置时的分频精度;
-- 若分频精度为 100,则此处设置的占空比为 25/100=25%;
sys.wait(2000)
local setduty_success = pwm.setDuty(ch, 25)
if setduty_success then
log.info("Task2", "PWM4 占空比更新为 25%")
else
log.info("Task2", "PWM4 占空比设置失败")
end
-- 再等 2 秒后调整频率;
sys.wait(2000)
local setfreq_success = pwm.setFreq(ch, 2000)
if setfreq_success then
log.info("Task2", "PWM4 频率更新为 2000 Hz")
else
log.error("Task2", "PWM4 频率设置失败")
end
-- 再等 2 秒后停止;
sys.wait(2000)
local pwm_success = pwm.stop(ch)
if pwm_success then
log.info("Task2", "PWM4 停止成功,通道资源已释放")
else
log.info("Task2", "PWM4 停止失败,检查通道或内存")
end
log.info("Task2", "Task2 结束")
end
-- 主演示任务;
local function pwm_demo_task()
log.info("MAIN", "启动 PWM 综合演示任务")
task1_old_pwm() -- 调用旧 API 演示
sys.wait(500) -- 短暂间隔
task2_new_pwm() -- 调用新 API 演示
sys.wait(500) -- 短暂间隔
log.info("MAIN", "PWM 综合演示任务结束")
end
-- 启动任务
sys.taskInit(pwm_demo_task)
-- 用户代码已结束---------------------------------------------
-- 结尾总是这一句
sys.run()
-- sys.run()之后后面不要加任何语句!!!!!
三、常量详解
核心库常量,顾名思义是由合宙 LuatOS 内核固件中定义的、不可重新赋值或修改的固定值,在脚本代码中不需要声明,可直接调用;
每个常量对应的常量取值仅做日志打印时查询使用,不要将这个常量取值用做具体的业务逻辑判断,因为LuatOS内核固件可能会变更每个常量对应的常量取值;
如果用做具体的业务逻辑判断,一旦常量取值发生改变,业务逻辑就会出错;
pwm 库没有常量;
四、函数详解
4.1 pwm.open(channel, period, pulse, pnum, precision)
功能
开启指定的 PWM 通道,配置其输出频率、占空比、输出模式及分频精度,并启动 PWM 信号输出;
注意事项
1. 调用前需确保所选通道在当前硬件平台上可用,部分模组的 PWM 通道与 GPIO 引脚绑定,需要先配置对应引脚复用功能,可参考该文档资料:https://docs.openluat.com/air780epm/common/luatio/;
2. pnum = 0 表示持续输出;pnum = 1 表示仅输出一个完整周期后自动停止;pnum > 1 表示输出指定数量的完整周期后停止(需要硬件支持脉冲计数功能);
3. 修改已开启通道的参数前,先调用 pwm.close(channel) 关闭通道,避免配置冲突;
参数
channel
参数含义:PWM 通道编号;
数据类型:number;
取值范围:取决于硬件平台,例如 Air8000 支持 PWM0 ~ PWM5;
是否必选:必须传入此参数;
注意事项:不同模组之间通道编号不同,需参考所选模组的硬件文档;
参数示例:channel = 0
period
参数含义:PWM 信号频率,单位为 Hz;
数据类型:number;
取值范围:1 ~ 13MHz;
是否必选:必须传入此参数;
注意事项:无;
参数示例:period = 1000
pulse
参数含义:占空比的量化值,基于分频精度计算实际占空比 = pulse/precision;
数据类型:number;
取值范围:0 ~ precision(包含 0 和 precision);
是否必选:必须传入此参数;
注意事项:无;
参数示例:pulse = 50
pnum
参数含义:输出脉冲周期数;
数据类型:number;
取值范围:0:持续输出(默认);
1:单次输出(一个完整周期后停止);
≥2:输出指定数量的完整周期后停止;
是否必选:可选传入此参数(默认为 0);
注意事项:脉冲计数功能依赖硬件支持,仅 Air700系列、Air780系列、Air8000系列模组支持;
参数示例:pnum = 0
precision
参数含义:分频精度,用于定义占空比的分辨率;
数据类型:number;
取值范围:100/256/1000,其他精度若设备不支持会开启失败;
是否必选:可选传入此参数(默认为 100);
注意事项:若设置的分频精度不被设备支持,此时会开启失败;
参数示例:precision = 256
返回值
local pwm_success = pwm.open(channel, period, pulse, pnum, precision)
有一个返回值 pwm_success;
pwm_success
含义说明:表示 PWM 通道开启是否成功;
数值类型:boolean;
取值范围:true/false;
注意事项:返回 false 时建议检查日志输出以定位具体原因;
返回示例:true
示例
-- 打开 PWM4通道,并设置信号频率为 1 kHz,分频精度为 100,占空比为 100/100=100%,持续输出;
local pwm_success = pwm.open(4, 1000, 100, 0, 100)
-- 判断通道是否开启情况;
if pwm_success then
log.info("PWM4 通道开启成功")
else
log.info("PWM4 通道开启失败")
end
4.2 pwm.close(channel)
功能
关闭指定的 PWM 通道;
注意事项
1. 调用后,对应通道的 PWM 输出将立即停止,引脚电平状态取决于暂停时的电平,可在 close 后通过 gpio.setup 配置成需要的电平;
2. 若指定通道未开启或不存在,函数仍会正常返回,不会报错;
3. 该函数无返回值,无法直接判断操作是否作用于有效通道,建议结合日志或硬件状态进行验证;
参数
channel
参数含义:需要关闭的 PWM 通道编号;
数据类型:number;
取值范围:取决于硬件平台,例如 Air8000 支持 PWM0 ~ PWM5;
是否必选:必须传入此参数;
注意事项:不同模组之间通道编号不同,需参考所选模组的硬件文档;
参数示例:channel = 0
返回值
该接口无返回值,只需调用该接口执行相关操作,无需处理返回结果;
如果一定要把接口调用的结果赋值给一个变量,则这个变量就是一个 nil 值;
示例
-- 关闭 PWM4 通道
pwm.close(4)
4.3 pwm.setup(channel, period, pulse, pnum, precision)
功能
初始化并配置指定 PWM 通道的参数(包括频率、占空比、输出模式和分频精度),但不立即启动 PWM 输出;
配置结果会被缓存,供后续 pwm.start(channel) 使用;
注意事项
1. 调用前需确保所选通道在当前硬件平台上可用,部分模组的 PWM 通道与 GPIO 引脚绑定,需要先配置对应引脚复用功能,可参考该文档资料:https://docs.openluat.com/air780epm/common/luatio/;
2. 该接口仅完成参数配置和内存缓存,不会使能硬件 PWM 输出,实际输出需调用 pwm.start(channel);
3. 每个通道只需调用一次 setup,重复调用会覆盖之前的配置;
4. 通道编号范围在当前实现中被硬编码限制为 0 ~ 5,实际可用通道由各个硬件平台决定;
5. 若内存分配失败(如系统内存不足),会打印错误日志 "pwm_setup malloc fail" 并返回失败;
6. 后续若调用依赖已配置参数的接口(如 pwm.start(channel)),但未先调用 pwm.setup,系统会通过 check_channel 检查并报错 "请先调用 pwm.setup!!";
参数
channel
参数含义:PWM 通道编号;
数据类型:number;
取值范围:0 ~ 5,实际可用通道取决于硬件平台,例如 Air8000 支持 PWM0 ~ PWM5;
是否必选:必须传入此参数;
注意事项:不同模组之间通道编号不同,需参考所选模组的硬件文档;
在当前实现中编号范围被硬编码限制为 0 ~ 5,实际可用通道由各个硬件平台决定;
参数示例:channel = 0
period
参数含义:PWM 信号频率,单位为 Hz;
数据类型:number;
取值范围:1 ~ 13MHz;
是否必选:必须传入此参数;
注意事项:无;
参数示例:period = 1000
pulse
参数含义:占空比的量化值,基于分频精度计算实际占空比 = pulse/precision;
数据类型:number;
取值范围:0 ~ precision(包含 0 和 precision);
是否必选:必须传入此参数;
注意事项:无;
参数示例:pulse = 50
pnum
参数含义:输出脉冲周期数;
数据类型:number;
取值范围:0:持续输出(默认);
1:单次输出(一个完整周期后停止);
≥2:输出指定数量的完整周期后停止;
是否必选:可选传入此参数(默认为 0);
注意事项:脉冲计数功能依赖硬件支持,仅 Air700系列、Air780系列、Air8000系列模组支持;
参数示例:pnum = 0
precision
参数含义:分频精度,用于定义占空比的分辨率;
数据类型:number;
取值范围:100/256/1000,其他精度若设备不支持会有日志提示;
是否必选:可选传入此参数(默认为 100);
注意事项:若设置的分频精度不被设备支持,此时会开启失败;
参数示例:precision = 256
返回值
local setup_success = pwm.setup(channel, period, pulse, pnum, precision)
有一个返回值 setup_success;
setup_success
含义说明:表示 PWM 通道参数配置是否成功缓存;
数值类型:boolean;
取值范围:true/false;
注意事项:返回 true 仅表示配置缓存成功,不代表硬件已生效;
返回示例:true
示例
-- 配置 PWM4 通道:信号频率为 1 kHz,分频精度为 100,占空比为 100/100=100%,持续输出;
local setup_success = pwm.setup(4, 1000, 100, 0, 100)
-- 判断 PWM 通道参数是否配置成功;
if setup_success then
log.info("PWM", "配置成功,稍后可启动")
-- 注意:此时 PWM 并未输出,需额外调用启动接口 pwm.start;
else
log.info("PWM", "配置失败,检查通道或内存")
end
4.4 pwm.start(channel)
功能
启动已通过 pwm.setup() 配置好的指定 PWM 通道,使硬件开始按照预设参数输出 PWM 信号;
注意事项
1. 必须先调用 pwm.setup() 完成通道配置,否则会触发错误日志 "请先调用 pwm.setup!!" 并返回失败;
2. 通道编号范围当前实现仅支持 0 ~ 5,超出范围将返回失败;
参数
channel
参数含义:PWM 通道编号;
数据类型:number;
取值范围:0 ~ 5,实际可用通道取决于硬件平台,例如 Air8000 支持 PWM0 ~ PWM5;
是否必选:必须传入此参数;
注意事项:不同模组之间通道编号不同,需参考所选模组的硬件文档;
通道编号范围当前实现仅支持 0 ~ 5,超出范围将返回失败;
该通道必须已通过 pwm.setup() 成功配置,否则启动失败;
参数示例:channel = 0
返回值
local pwm_success = pwm.start(channel)
有一个返回值 pwm_success;
pwm_success
含义说明:表示 PWM 通道开启是否成功;
数值类型:boolean;
取值范围:true/false;
注意事项:返回 false 时建议检查日志输出以定位具体原因;
返回示例:true
示例
-- 配置 PWM4 通道:信号频率为 1 kHz,分频精度为 100,占空比为 100/100=100%,持续输出;
pwm.setup(4, 1000, 100, 0, 100)
-- 启动 PWM4;
local pwm_success = pwm.start(4)
if pwm_success then
log.info("PWM", "启动成功,稍后可输出信号")
else
log.info("PWM", "启动失败,检查通道或内存")
end
4.5 pwm.stop(channel)
功能
停止指定 PWM 通道的输出;
注意事项
1. 该接口会先关闭硬件 PWM 输出,然后释放该通道在 pwm.setup() 中分配的配置内存,并将内部配置指针置为 NULL;
2. 必须先调用过 pwm.setup() 且通道处于已配置状态,否则会返回错误;
3. 停止后,该通道的配置信息将被清除,如需再次使用,必须重新调用 pwm.setup() 进行配置;
4. 停止后,对应引脚电平状态取决于暂停时的电平,可在 close 后通过 gpio.setup 配置成需要的电平;
5. 通道编号范围当前实现仅支持 0 ~ 5,超出范围将返回失败;
6. 返回值结果仅表示该接口调用情况,不严格反映硬件状态;
参数
channel
参数含义:要停止的 PWM 通道编号;
数据类型:number;
取值范围:0 ~ 5,实际可用通道取决于硬件平台,例如 Air8000 支持 PWM0 ~ PWM5;
是否必选:必须传入此参数;
注意事项:不同模组之间通道编号不同,需参考所选模组的硬件文档;
通道编号范围当前实现仅支持 0 ~ 5,超出范围将返回失败;
该通道必须已通过 pwm.setup() 成功配置,否则返回失败;
参数示例:channel = 0
返回值
local stop_success = pwm.start(channel)
有一个返回值 stop_success;
stop_success
含义说明:表示 PWM 通道停止及资源释放流程是否执行成功;
数值类型:boolean;
取值范围:true/false;
注意事项:返回 true 表示软件层面已清理资源;
对应引脚电平状态取决于暂停时的电平,可在 close 后通过 gpio.setup 配置成需要的电平;
返回示例:true
示例
-- 配置 PWM4 通道:信号频率为 1 kHz,分频精度为 100,占空比为 100/100=100%,持续输出;
pwm.setup(4, 1000, 100, 0, 100)
-- 启动 PWM4;
pwm.start(4)
-- 停止 PWM4;
local pwm_success = pwm.stop(4)
if pwm_success then
log.info("PWM", "停止成功,通道资源已释放")
else
log.info("PWM", "停止失败,检查通道或内存")
end
4.6 pwm.setDuty(channel, duty)
功能
动态修改已配置 PWM 通道的占空比;
注意事项
1. 必须先调用 pwm.setup() 对该通道完成初始化配置,否则会失败,接口返回 false 并打印日志 "请先调用 pwm.setup!!"。
2. 该接口会直接更新缓存配置中的 pulse 值(即占空比量化值),然后调用底层重新应用整个配置(包括频率、精度等),从而实现占空比的动态调整;
3. duty 的实际含义依赖于该通道在 pwm.setup() 中设置的 precision(分频精度):
若 precision = 100,则 duty = 25 表示 25% 占空比;
若 precision = 256,则 duty = 64 表示约 25% 占空比(64/256);
因此,duty 必须在 0 ~ precision 范围内,超出会导致硬件行为异常或设置失败;
4. 通道编号范围限制为 0 ~ 5,超出范围将返回失败;
参数
channel
参数含义:目标 PWM 通道编号;
数据类型:number;
取值范围:0 ~ 5,实际可用通道取决于硬件平台,例如 Air8000 支持 PWM0 ~ PWM5;
是否必选:必须传入此参数;
注意事项:不同模组之间通道编号不同,需参考所选模组的硬件文档;
通道编号范围当前实现仅支持 0 ~ 5,超出范围将返回失败;
该通道必须已通过 pwm.setup() 成功配置,否则返回失败;
参数示例:channel = 0
duty
参数含义:新的占空比量化值(基于当前通道的 precision);
数据类型:number;
取值范围:0 ~ precision(包含 0 和 precision);
是否必选:必须传入此参数;
注意事项:无;
参数示例:duty = 50
返回值
local setduty_success = pwm.setDuty(channel, duty)
有一个返回值 setduty_success;
setduty_success
含义说明:表示新占空比设置是否成功;
数值类型:boolean;
取值范围:true/false;
注意事项:返回 true 仅表示配置已经下发,实际占空比需结合硬件能力验证;
返回示例:true
示例
-- 配置 PWM4 通道:信号频率为 1 kHz,分频精度为 100,占空比为 100/100=100%,持续输出;
pwm.setup(4, 1000, 100, 0, 100)
-- 启动 PWM4;
pwm.start(4)
-- 定义调整占空比的函数;
local function updatePwmDuty()
local setduty_success = pwm.setDuty(4, 25)
if setduty_success then
log.info("PWM", "占空比更新为 25%")
else
log.info("PWM", "占空比设置失败")
end
end
-- 2秒后调用该函数
sys.timerStart(updatePwmDuty, 2000)
4.7 pwm.setFreq(channel, freq)
功能
动态修改已配置 PWM 通道的输出频率;
注意事项
1. 必须先调用 pwm.setup() 对该通道完成初始化配置,否则会失败,接口返回 false 并打印日志 "请先调用 pwm.setup!!";
2. 该接口会更新缓存配置中的 period(频率)字段,然后调用底层重新配置整个 PWM 通道,从而生效新的频率;
3. 修改频率时,占空比的绝对时间(高电平持续时间)可能发生变化,因为占空比是按比例(pulse / precision)计算的;例如:
原频率 1kHz(周期 1ms),占空比 50% → 高电平 0.5ms;
改为 2kHz(周期 0.5ms),占空比仍为 50% → 高电平变为 0.25ms;
若需保持高电平时间不变,需同步调整 pulse 值;
4. 通道编号范围限制为 0 ~ 5,超出范围将返回失败;
参数
channel
参数含义:目标 PWM 通道编号;
数据类型:number;
取值范围:0 ~ 5,实际可用通道取决于硬件平台,例如 Air8000 支持 PWM0 ~ PWM5;
是否必选:必须传入此参数;
注意事项:不同模组之间通道编号不同,需参考所选模组的硬件文档;
通道编号范围当前实现仅支持 0 ~ 5,超出范围将返回失败;
该通道必须已通过 pwm.setup() 成功配置,否则返回失败;
参数示例:channel = 0
freq
参数含义:新的 PWM 输出频率,单位为 Hz;
数据类型:number;
取值范围:1 ~ 13MHz;
是否必选:必须传入此参数;
注意事项:无;
参数示例:freq = 1000
返回值
local setfreq_success = pwm.setFreq(channel, freq)
有一个返回值 setfreq_success;
setfreq_success
含义说明:表示新频率设置是否成功;
数值类型:boolean;
取值范围:true/false;
注意事项:返回 true 仅表示配置已经下发,实际输出频率需结合硬件能力验证;
返回示例:true
示例
-- 配置 PWM4 通道:信号频率为 1 kHz,分频精度为 100,占空比为 100/100=100%,持续输出;
pwm.setup(4, 1000, 100, 0, 100)
-- 启动 PWM4;
pwm.start(4)
-- 定义频率更新函数
local function updatePwmFreq()
local setfreq_success = pwm.setFreq(4, 2000)
if setfreq_success then
log.info("PWM", "PWM4 频率更新为 2000 Hz")
else
log.info("PWM", "PWM4 频率设置失败")
end
end
-- 2秒后调用该函数
sys.timerStart(updatePwmFreq, 2000)
五、产品支持说明
支持 LuatOS 开发的所有产品都支持 pwm 核心库。