STM32 Timer (3) PWM代码的实现
理论知识基于上一篇文章
STM32 Timer (2) 定时器中断代码框架
定时器周期公式T = (arr+1)(psc+1)/f; f = (APB1 *2).
//定时器基本参数:
// ARR: 自动重装载值;
// ftim: 频率
//PWM参数:
// CCRx: 比较值
对于定时器参数更深入的理解
从(APB1 * 2)来的时钟, 经过psc参数进行预分频,会是这样的效果:(假设psc = 108000)
(54 *2)MHz/psc = 108Mhz/108000 = 1Khz, 转化为周期T1 = 1/1khz(s) = 1ms,
也就是说,每一个时钟跳动,对于定时器输入来说,都是1ms,当然这是建立在psc = 108000的情况下.
也就是说, psc=108000的时候,定时器每经过1ms,计数器就++ 或 -- 一次.
ARR是自动重装载值,也是定时器的设定值, 假设arr = 500; 就代表着当计数器的值到达500的时候, 就触发更新中断,一次定时器的任务就结束.
这两个参数一起使用的时候,就能得到完整的一次定时任务的时间, 每1ms更新一次技术, 更新500次的时候触发中断, 就等于
1ms * 500 = 500ms; 每500ms触发一次定时中断.
在这个理论的基础上,我们来分析pwm的频率是一种什么概念.
假设定时器触发中断的总时间为T, 定时器频率为f1; 计数器设置为500不变, 那么分析psc对定时器的影响.
当psc= 108 时, f1 = 108Mhz /108 = 1Mhz, 1Mhz 是什么概念, T1 = 1/f1(s) = 1us; 也就是定时器每1us更新一次计数器. 这样子触发一个完整的中断T只要 500us;,下面我将数据对比一下
psc(分频系数) | f1(定时器频率) | T1(计数器更新时间) | arr计数次数 | T(定时器完整中断时间) |
108 | 108Mhz/108 = 1MHz | 0.000001s = 1us | 500 | 500us |
1080 | 108Mhz/1080 = 100KHz | 0.00001s = 10us | 500 | 5ms |
10800 | 108Mhz/10800 = 10KHz | 0.0001s = 100us | 500 | 50ms |
108000 | 108Mhz.108000 = 1Khz | 0.001s = 1ms | 500 | 500ms |
从图片个表格可以看出来, 预分频系数直接关系着频率, 也就是直观感受上的 快 和 慢. 当预分频系数越小, 一个定时周期就越短, 因为计数器 咻的一下就把500次心跳更新完了. 当预分频系数越大, 一个定时周期就越长, 因为缓慢的心跳让计数器的更新动作也变得缓慢. 重装载值控制控制着计数器的阈值然后影响着 一个完整的定时器中断周期, 在预分频系数一定时,假设每1ms更新一次计数器 arr = 500 , T = 500ms; arr = 1000, T = 1000ms; 结合CCrx来看是什么效果. 假设CCRx分别是arr值的一半, 当arr = 500的时候, 每250ms电平翻转一次; 当arr = 1000的时候, 每500ms电平翻转一次; 肉眼看到的led效果就是: 一个快速地flash, 另一个慢慢地闪烁.
所以呢,在pwm中, 有三个重要的参数,一个是频率,一个是arr重装载值,一个是CCRx比较值
频率控制着计数器的变化速度,频率越快,计数器变化就越快, 周期就越短,一下子就走到了重装载值的尽头.
arr重装载值同样起着跟频率一样的作用.
CCRx比较值呢就是改变占空比的作用.
这个放在led上的效果就是,
占空比越大, led越亮.
占空比越小, led越暗.
将两种效果结合起来就是:
频率越高, led迅速地从暗到亮
频率越低, led缓慢地从暗到亮
放在蜂鸣器上的效果就是:
频率改变声音的音调(Hz), 占空比改变声音的响度.
//1. 开启TIM时钟和配置PWM输出GPIO,配置GPIO时选择复用功能 __HAL_RCC_TIM3_CLK_ENABLE(); //使能定时器 3 __HAL_RCC_GPIOB_CLK_ENABLE(); //开启 GPIOB 时钟 HAL_GPIO_Init(GPIOB,&GPIO_Initure); //配置GPIO //2. 初始化 TIM3,设置 TIM3 的 ARR 和 PSC 等参数。 HAL_TIM_PWM_Init(); //3. 设置 TIM3_CH4 的 PWM 模式,输出比较极性,比较值等参数。 HAL_TIM_PWM_ConfigChannel(); //4. 能 TIM3,使能 TIM3 的 CH4 输出 HAL_TIM_PWM_Start(); //5. 修改 TIM3_CCR4 来控制占空比。
#include <rtthread.h> #include <rtdevice.h> #include <board.h> #define THREAD_STACKSIZE 200 #define THREAD_PRORITY 5 #define THREAD_TASK 5 TIM_HandleTypeDef hpwm; TIM_OC_InitTypeDef TIM3_CH4Handler; void TIM_SetCompare4(uint32_t compare) { TIM3->CCR4=compare; } void TIM3_PWM_Init(uint16_t arr, uint16_t psc) { hpwm.Instance = TIM3; hpwm.Init.Prescaler = psc; //设置psc预分频系数 hpwm.Init.CounterMode = TIM_COUNTERMODE_UP; //设置计数方式 hpwm.Init.Period = arr; //设置重装载值 hpwm.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; HAL_TIM_PWM_Init(&hpwm); //初始化PWM定时器 TIM3_CH4Handler.OCMode = TIM_OCMODE_PWM1; //设置为小于比较值为有效电平模式 TIM3_CH4Handler.OCPolarity = TIM_OCPOLARITY_LOW; //设置有效电平为低电平(LED需要) TIM3_CH4Handler.Pulse = arr/2; //设置比较值 HAL_TIM_PWM_ConfigChannel(&hpwm, &TIM3_CH4Handler,TIM_CHANNEL_4); //配置信道 HAL_TIM_PWM_Start(&hpwm, TIM_CHANNEL_4); //开启pwm定时器输出 } static void pwm_timer_entry(void *arg) { uint8_t dir = 1; uint16_t led0pwmval = 0; //psc = 108 表示每次心跳为1us, 500次更新一次计数器, 共500us一个周期 //这种效果就等于疯狂刷新定时器, 不停监听占空比的变化 //总觉得有点丧心病狂 TIM3_PWM_Init(500-1, 108-1); while(1) { rt_thread_mdelay(10); //每10ms更新一次占空比. if(dir) { led0pwmval ++; //每次更新的精度为1,越小表示越平滑 }else { led0pwmval --; } if(led0pwmval > 500) //500 和 arr设置为一样,表示占空比从0到最大 { dir = 0; rt_thread_mdelay(10); } if(led0pwmval == 0) { dir =1; //rt_thread_mdelay(10); } TIM_SetCompare4(led0pwmval); //更新占空比的值 } } int pwm_timer() { rt_thread_t tid; tid = rt_thread_create("pwm_timer", pwm_timer_entry, RT_NULL, THREAD_STACKSIZE, THREAD_PRORITY,THREAD_TASK); if(tid) rt_thread_startup(tid); return 0; } MSH_CMD_EXPORT(pwm_timer, kis_pwm_timer);
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef* htim_pwm) { if(htim_pwm->Instance==TIM2) { __HAL_RCC_TIM2_CLK_ENABLE(); }else if(htim_pwm->Instance == TIM3) { GPIO_InitTypeDef GPIO_InitStruck; __HAL_RCC_TIM3_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE(); //gpio GPIO_InitStruck.Pin = GPIO_PIN_1; GPIO_InitStruck.Mode = GPIO_MODE_AF_PP; GPIO_InitStruck.Pull = GPIO_PULLUP; GPIO_InitStruck.Speed = GPIO_SPEED_HIGH; GPIO_InitStruck.Alternate = GPIO_AF2_TIM3; HAL_GPIO_Init(GPIOB,&GPIO_InitStruck); } }