1. 简介
PWM(Pulse Width Modulation),全称脉宽调制,通过对一系列脉冲的宽度进行调制,等效出所需波形。即对模拟信号电平进行数字编码,通过调节频率、占空比的变化来调节信号的变化。
一个 PWM 周期内由一段高电平和一段低电平区间组成,通过调整高低电平的时间,即占空比,可以等效输出不同的电压。
比如,周期为 100ms,高电平时间为 50ms,低电平时间为 50ms,即占空比为 50%,VCC 假设为 5V,那么等效输出的电压为 1.15V。
2. 硬件特性
Hi2821 一共有 12 路 PWM 输出通道;支持PWM分组,一个组可以分配多个通道,同一个通道不能同时位于不同的组里。
3. 例程
3.1 Kconfig
SDK 是默认打开 PWM 的编译的,配置项不多,基本上也不需要配置。唯一可能要配置的是最后一个“Using PWM PRELOAD”,它的作用是可以预配置一系列占空比,这样 PWM 输出完一个周期后,驱动会自动加载下一个周期的配置。
3.2 代码
#include "soc_osal.h"
#include "app_init.h"
#include "pwm.h"
#include "hal_pwm.h"
#include "pinctrl.h"#include <string.h>
#include <stdint.h>#define PWM_GROUP_ID 0static uint8_t pwm_chn = 0;
static pwm_config_t pwm_cfg = {1000,0,0,100,false
};static errcode_t pwm_callback(uint8_t channel)
{static uint8_t dir = 0;if (dir) {pwm_cfg.low_time++;pwm_cfg.high_time--;} else {pwm_cfg.low_time--;pwm_cfg.high_time++;}/* 更新占空比 */hal_pwm_funcs_t *func = hal_pwm_get_funcs();func->set_time(PWM_SET_LOW_TIME, (pwm_channel_t)channel, pwm_cfg.low_time);func->set_time(PWM_SET_HIGH_TIME, (pwm_channel_t)channel, pwm_cfg.high_time);func->set_action(PWM_GROUP_ID, PWM_ACTION_START);osal_printk("duty cycle, high: %u, low: %u\r\n", pwm_cfg.high_time, pwm_cfg.low_time);if (pwm_cfg.low_time == 0) {dir = 1;} else if (pwm_cfg.low_time == 1000) {dir = 0;}return 0;
}void app_main(void *unused)
{(void)(unused);int ret = 0;/* 初始化管脚 */uapi_pin_set_mode(31, HAL_PIO_PWM0);/* 初始化PWM */uapi_pwm_deinit();uapi_pwm_init();ret = uapi_pwm_open(pwm_chn, &pwm_cfg);if (ret) {osal_printk("PWM init failed, err: %X");return;}uapi_pwm_register_interrupt(pwm_chn, pwm_callback);osal_printk("\r\nPWM freq: %d Hz\r\n", uapi_pwm_get_frequency(pwm_chn));uapi_pwm_set_group(PWM_GROUP_ID, &pwm_chn, 1);uapi_pwm_start_group(PWM_GROUP_ID);
}
1. 初始化管脚
我使用的是开发板上自带的 LED 灯,对应 31 号管脚,只需要调用 uapi_pin_set_mode 函数配置复用到对应的 PWM 通道即可。
2. 初始化 PWM
先调用 uapi_pwm_init 初始化驱动;接着调用 uapi_pwm_open 配置通道运行参数,参数一为通道号,参数二为运行参数,定义如下:
typedef struct pwm_config {uint32_t low_time; /*!< @if Eng PWM working clock cycle count low level part,PWM work frequency @ref uapi_pwm_get_frequency().If the PWM operating cycle is Tus,the actual low-level time = low_time * Tus.@else PWM工作时钟周期计数个数低电平部分,频率参考 @ref uapi_pwm_get_frequency()。如果PWM工作周期为Tus, 实际低电平时间 = low_time * Tus @endif */uint32_t high_time; /*!< @if Eng PWM working clock cycle count high level part,PWM work frequency @ref uapi_pwm_get_frequency().If the PWM operating cycle is Tus,the actual high-level time = high_time * Tus.@else PWM工作时钟周期计数个数高电平部分,频率参考 @ref uapi_pwm_get_frequency()。如果PWM工作周期为Tus, 实际高电平时间 = high_time * Tus @endif */uint32_t offset_time; /*!< @if Eng PWM offset time.@else PWM相位。 @endif */uint16_t cycles; /*!< @if Eng PWM cycles, range:0~32767 (15bit).@else PWM重复周期,范围:0~32767 (15bit)。 @endif */bool repeat; /*!< @if Eng Flag to indicate PWM should output continuously.@else 指示PWM应连续输出的标志。 @endif */
} pwm_config_t;
- low_time:低电平时间;
- high_time:高电平时间;
- offset_time:相位偏移;
- cycles:重复周期;
- repeat:是否连续周期。
PWM 的时钟默认是 32MHz,也可以调 uapi_pwm_get_frequency 去获取。low_time 和 high_time 的值是 PWM 时钟的周期数,那么可以得出一个 PWM 周期的时间为:。
cycles 和 repeat 是互斥的,如果设置了 repeat,那么驱动会忽略 cycles 的值。
然后调用 uapi_pwm_register_interrupt 注册中断回调,当 PWM 完成了一个输出任务时,这个回调就会调用。
如果设置了 repeat 这个中断就永远不会调用,因为输出的任务永远不会结束。
另外,中断回调注册一定要放在 uapi_pwm_open 之前,因为后者会使用驱动默认的中断回调去覆盖回调函数。
调用 uapi_pwm_set_group 设置 PWM 组,最多可设置 6 个组;参数一为组号,参数二为通道号列表,参数三为通道数。最后调用 uapi_pwm_start_group 去使能对应组的输出任务。
3. 中断回调函数
在中断回调函数里面去调整占空比,目前的 SDK 比较 low,还没有移植调整占空比的函数,所以只能调用 HAL 层的函数接口去调整。
调用 hal_pwm_get_funcs 函数可以获取 PWM 底层的操作函数,调用 set_time 函数可以调整 low_time 和 high_time 的值,调整完成后需要调用 set_action 去使能 PWM 输出任务,参数一为组号,参数二填 PWM_ACTION_START。
3.3 测试
编译并烧录固件,此时就能看到呼吸灯的效果了,下面为部分系统 log。