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。