当前位置:网站首页>STM32笔记之 PWM(脉宽调制)

STM32笔记之 PWM(脉宽调制)

2022-06-21 11:58:00 夏沫の浅雨

写在前面:
本文章旨在总结备份、方便以后查询,由于是个人总结,如有不对,欢迎指正;另外,内容大部分来自网络、书籍、和各类手册,如若侵权请告知,马上删帖致歉。

 

目录

一、PWM简介

二、STM32F1中对 PWM的支持

三、PWM计数模式

四、工作原理

五、PWM输出的模式

六、与 PWM调控相关的函数

七、例程演示

八、其他


 

一、PWM简介

脉冲宽度调制(英语:Pulse Width Modulation,缩写:PWM),简称脉宽调制,是将模拟信号变换为脉冲的一种技术,一般变换后脉冲的周期固定,但脉冲的工作周期会依模拟信号的大小而改变。

一般我们常用的 PWM技术是用来调光或者调速的,这也是最最常见的。

 

二、STM32F1中对 PWM的支持

在 STM32F1里面,PWM的生成是由 Timer输出的,但并不是所有的 Timer都支持;注意,这里说的是利用硬件技术产生 PWM,其制造出来的 PWM频率是非常可观,至于用软件定时控制 IO输出脚,即所谓的软件 PWM技术,这里就不阐述了。

从上面得知,STM32F1的 Timer是可以产生 PWM的,但并不是所有的 Timer都支持;所以,翻看一下手册,可以得知,支持 PWM硬件输出技术的 Timer有 “通用定时器” 和 “高级控制定时” 这两款类型,而对于 “基本定时器” 是并没有 PWM硬件输出技术的,所以在实际应用中要稍微注意一下,以免造成资源分配不合理。

1、通用定时器(TIM2 ~ TIM5)

每个定时都有 4个独立通道作为输出:

2、高级控制定时器(TIM1 & TIM8)

该款定时器可产生7路 PWM输出:

3、基本定时器(TIM6 & TIM7)

有关定时器的讲解,可以看之前的篇章:STM32笔记之 Timer(定时器)

 

三、PWM计数模式

  • 边沿对齐模式

1、向上计数

2、向下计数

  • 中央对齐模式

 

四、工作原理

以向上计数为例:

计数值以每周期固定从 Bottom开始计数,一直累加到 Top,然后再重新计数;只要不停止运行,便如此往复循环;从 Bottom累加到 Top为一个周期,为了制造出 PWM脉冲,而我们要做的就是设置 Compare的值,这样一来,就多了个 Compare参考值,也可以说是分割值;在计数的时候,每每累加一次计数,都跟 Compare值作比较,当计数值与 Compare值相同时,就控制输出状态与之相反,从而得到上图中 Output的图线。

 

五、PWM输出的模式

上面提到,Compare值作比较,当计数值与 Compare值相同时,就控制输出状态与之相反;因此,就会出现两种占空比的状态,一种是上图的当 Compare到 Top之间输出为高电平,另一种则是与之相反的 Bottom至 Compare之间为高电平.

那么对应在 STM32中,其状态确定在 TIMx_CCMRx寄存器位 6:4 的 OCxM[2:0]中:

注意STM32中的 PWM模式只是区别什么时候是有效电平,但并没有确定是高电平有效还是低电平有效。这需要结合 CCER寄存器的 CCxP位的值来确定。

 

六、与 PWM调控相关的函数

函数功能

void TIM_SetClockDivision(TIM_TypeDef* TIMx, uint16_t TIM_CKD)

时钟分频因子设置

void TIM_PrescalerConfig(TIM_TypeDef* TIMx, uint16_t Prescaler, uint16_t TIM_PSCReloadMode)

TIM_x预分频设置

void TIM_SetAutoreload(TIM_TypeDef* TIMx, uint16_t Autoreload)

设置定时器的自动加载周期

void TIM_SetCompare1(TIM_TypeDef* TIMx, uint16_t Compare1)

设置对应各通道的比较值(可以理解为占空比)

void TIM_SetCompare2(TIM_TypeDef* TIMx, uint16_t Compare2)

void TIM_SetCompare3(TIM_TypeDef* TIMx, uint16_t Compare3)

void TIM_SetCompare4(TIM_TypeDef* TIMx, uint16_t Compare4)

一般来说,计算需要的 PWM频率 = System clock / 时钟分频因子 / TIM_x预分频 / 自动加载周期。

 

七、例程演示

下面以 TIM3的 ch3、4作为例子:

#define TIME3_ARR			624			// 自动重装载寄存器周期值
#define TIME3_PSC			71			// TIMx时钟频率预分频值
#define TIME3_PULSE			(uint16_t)((TIME3_ARR+1) / 2 - 1)		// 比较寄存器值

#define TIME3_CH3_PIN       GPIO_Pin_0
#define TIME3_CH4_PIN       GPIO_Pin_1

uint16_t Time3_CCR3_Val = TIME3_PULSE;
uint16_t Time3_CCR4_Val = TIME3_PULSE;

void TIM3_PWM_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
    TIM_OCInitTypeDef  TIM_OCInitStructure;

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);
    GPIO_InitStructure.GPIO_Pin = TIME3_CH3_PIN | TIME3_CH4_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);				// IO口配置

    /* Time base configuration */
    TIM_TimeBaseStructure.TIM_Period = TIME3_ARR;
    TIM_TimeBaseStructure.TIM_Prescaler = TIME3_PSC;	// 1600Hz
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;        // 时钟分频因子
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);		// 定时器基本配置

    /* PWM1 Mode configuration: Channel3 */
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;   // PWM mode 1
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStructure.TIM_Pulse = Time3_CCR3_Val;     // 占空比设置
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;    // 极性为高电平

    TIM_OC3Init(TIM3, &TIM_OCInitStructure);

    TIM_OC3PreloadConfig(TIM3, TIM_OCPreload_Enable);

    /* PWM1 Mode configuration: Channel4 */
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStructure.TIM_Pulse = Time3_CCR4_Val;

    TIM_OC4Init(TIM3, &TIM_OCInitStructure);

    TIM_OC4PreloadConfig(TIM3, TIM_OCPreload_Enable); // 使能预装载(使能 CCR4的预装载)

    TIM_ARRPreloadConfig(TIM3, ENABLE); // 使能自动重载寄存器 ARR的预装载

    /* TIM3 enable counter */
    TIM_Cmd(TIM3, ENABLE);
}

在这里,要注意一下 TIM_ARRPreloadConfig()函数,它的作用只是允许或禁止在定时器工作时向 ARR的缓冲器中写入新值后是否立刻更新,操作的是 TIMx->CR1中的 “APRE” 位,其位解释如下:

从上图的解释中我们并不能得到任何有用的信息,需要结合以下两个示例才明白:

图 1:APRE = 0(默认值),当 ARR值被修改时,同时马上更新影子寄存器的值

可以看到之前参数是 ARR = FF,当给 ARR重新赋值为 36后,马上就生效了,并在等于 36时发生了溢出,并产生一个事件。

图 2:APRE = 1,当 ARR值被修改时,必须在下一次事件UEV发生后才能更新影子寄存器的值

在这里可以看到之前参数是 ARR = F5,当去修改 ARR的值为 36,只有表面的重装载寄存器值更改了,但是真正起作用的影子寄存器并没有更改。这时候需要等到上一个周期结束,发生更新事件,影子寄存器才会进行修改。

而在程序中,TIM_ARRPreloadConfig(TIM3, ENABLE);则是为了在我们更改数值时,避免在切换的时候出现一个不符合修改前和修改后参数的触发事件(以上面图 2为例,如果是没有使能 APRE,那么给 ARR重新赋值为 36后,数据会马上就生效;而由于我们是在当时计数值为 F1的时候修改的,同时,因为 APRE = 0,所以在计数到 F5的时候并不会产生一个事件并复位计数值(这时触发值变成 36了),会一直跑完到 0xFFFF(以 16bit定时器为例)然后寄存器溢出才重新从 0开始计数,最后才在计数到 36的时候产生事件,那么就会造成,在这次切换的过程中总的计数为: 0xFFFF - 0x00F5 + 36;如果在 PWM模式下,这样一来就变成了在写入新的 ARR值时,有可能在切换的时候产生一个高(低)电平的不规则的脉冲(既不符合修改前又不符合修改后))。

同样的,对于 TIM_OCxPreloadConfig()函数也是一样的道理。

 

八、其他

1、值得注意的是,在使用高级定时器 TIM8的时候(可能其他的高级定时器也需要添加,但是在 TIM1中已经默认打开了),需要再添加 TIM_CtrlPWMOutputs(TIM8, ENABLE); 执行函数才能输出 PWM波。针对 TIM_CtrlPWMOutputs函数,可以看以下说明:

/**
  * @brief  Enables or disables the TIM peripheral Main Outputs.
  * @param  TIMx: where x can be 1, 8, 15, 16 or 17 to select the TIMx peripheral.
  * @param  NewState: new state of the TIM peripheral Main Outputs.
  *   This parameter can be: ENABLE or DISABLE.
  * @retval None
  */
void TIM_CtrlPWMOutputs(TIM_TypeDef* TIMx, FunctionalState NewState);

2、对于用 Keil来软件仿真,利用内置的逻辑分析器来看波形,就得设置时钟频率啊:

然后还得需要在以下这里设置一下(记得对应回去你的芯片):

然后就进入 DEBUG模式,设置 PWM输出端口(这里随便设了一个):

最后,重点来了:Keil的软件波形仿真,相对有点不靠谱,能用示波器观察的就不要用这个了,或者弄个逻辑分析仪也行,并且,好像 Keil并不支持 TIM5以上的定时器输出观察。

原网站

版权声明
本文为[夏沫の浅雨]所创,转载请带上原文链接,感谢
https://blog.csdn.net/qq_42992084/article/details/108547321