跳转至

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 核心库。