打印
[应用相关]

解析PWM在STM32中的应用

[复制链接]
665|0
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
tpgf|  楼主 | 2024-6-13 17:12 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
PWM,即Pulse Width Modulation,中文意思为脉冲宽度调制,简称脉宽调制。

一、PWM的定义和工作原理
PWM是通过改变脉冲信号的占空比(高电平时间与总周期时间的比值)(一般为百分数)

例如:信号周期为10,信号占空比为5/10——>高电平时间占5,低电平时间占5。

来控制模拟电路的输出功率。具体来说,PWM系统使用一个频率恒定的脉冲信号作为输出电压,并通过改变每个脉冲的宽度(占空比)来调节输出电压的大小。

所以当我们的占空比越来越大时,我们的高电平时间也会越来越多,输出的电压也会越高。

二、PWM的组成部分
脉冲信号发生器:负责根据用户设置的频率生成一系列脉冲信号。
模拟信号处理模块:负责将脉冲信号的宽度按照用户设定的比例进行调整。                      ADC(模拟数字转换器):将调整后的脉冲信号转换为模拟输出,以驱动模拟负载。(今天就不做讲解了哈)
大致总结一下就是:当我输出电源有10V时,我的脉冲占空比为5 /10时,输出电源只能输出5V

而我们的脉冲占空比为1时,输出电源可以输出完整的10V。

三、PWM的应用
电源管理:通过PWM控制电源的输出电压和电流,实现对电能的高效利用和设备的稳定运行。(调节输出电压的大小)
无线通信:在通信系统中,PWM可用于调制和解调信号,实现数据的传输和接收。(以后探讨)
电机控制:PWM技术广泛应用于各种类型的电机控制中,如伺服电机、步进电机等。通过精确控制电机的转速和位置,实现高精度的运动控制。
音频处理:在音频放大器中,PWM可用于调节音频信号的幅度和频率,实现高质量的音频输出。
四、代码讲解
#include "stm32f10x.h"               

void PWM_Init_1(void){
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

        GPIO_InitTypeDef GPIO_InitStructure;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(GPIOA, &GPIO_InitStructure);
       
        TIM_InternalClockConfig(TIM2);
       
        TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
        TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
        TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
        TIM_TimeBaseInitStructure.TIM_Period = 100 - 1;  //ARR  Reso=1/(ARR+1)
        TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1;  //PSC  Freq=72M/(PSC+1)/(ARR+1)
        TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
        TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
       
        TIM_OCInitTypeDef TIM_OCInitStructure;
        TIM_OCStructInit(&TIM_OCInitStructure);
        TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
        TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
        TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
        TIM_OCInitStructure.TIM_Pulse = 0;  //CCR  Duty=CCR/(ARR+1)
        TIM_OC1Init(TIM2, &TIM_OCInitStructure);
       
        TIM_Cmd(TIM2, ENABLE);
}

void PWM_Init_2(void){
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

        GPIO_InitTypeDef GPIO_InitStructure;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(GPIOA, &GPIO_InitStructure);
       
        TIM_InternalClockConfig(TIM3);
       
        TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
        TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
        TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
        TIM_TimeBaseInitStructure.TIM_Period = 100 - 1;  //ARR  Reso=1/(ARR+1)
        TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1;  //PSC  Freq=72M/(PSC+1)/(ARR+1)
        TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
        TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);
       
        TIM_OCInitTypeDef TIM_OCInitStructure;
        TIM_OCStructInit(&TIM_OCInitStructure);
        TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
        TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
        TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
        TIM_OCInitStructure.TIM_Pulse = 0;  //CCR  Duty=CCR/(ARR+1)
        TIM_OC2Init(TIM3, &TIM_OCInitStructure);
       
        TIM_Cmd(TIM3, ENABLE);
}

void PWM_SetDuty_1(uint16_t duty){
        TIM_SetCompare1(TIM2, duty);
}

void PWM_SetDuty_2(uint16_t duty){
        TIM_SetCompare2(TIM3, duty);
}

void PWM_SetFreq_1(uint16_t freq){
        uint16_t psc = 720000/freq - 1;
        TIM_PrescalerConfig(TIM2, psc, TIM_PSCReloadMode_Update);
}

void PWM_SetFreq_2(uint16_t freq){
        uint16_t psc = 720000/freq - 1;
        TIM_PrescalerConfig(TIM3, psc, TIM_PSCReloadMode_Update);
}

为什么打开GPIOA
首先第一个问题便是,为什么我打开TIM2定时器时钟后 仍然打开了GPIOA的端口。这其实也是许多CV工程师不会移植代码的第一个点。说白了就是,在PWM函数中帮其他我们所用到的电子元件进行了初始化。举个例子:如果我们想使用PWM来控制蜂鸣器的响度,我们在初始化PWM时,为了一体化控制就直接帮蜂鸣器进行了初始化。因此在我们构造PWM控制蜂鸣器库的时候,我们可以这样写函数。

#include "PWM.h"
void Buzzer_Init(void){
        PWM_Init_1();
}
是不是豁然开朗了呢。

像其他的初始化过程,我们就不必讲解了。另外根据我上述所说,我们设置GPIOA上的引脚工作方式时要考虑的是我们PWM需要控制的外设所需要的工作方式。而不是想当然定义。

TIM_InternalClockConfig(TIM2)
这个函数用于配置一个定时器(在这个例子中是TIM2)以使用内部时钟作为它的时钟源。

Internal Clock:内部时钟是指微控制器内部的时钟源。STM32微控制器通常有一个或多个内部振荡器(如HSI、HSE、LSI、LSE等),这些振荡器可以产生时钟信号供微控制器内部使用。在这个函数中,TIM2被配置为使用这些内部时钟源之一作为其时钟输入。
TIM_InternalClockConfig:这是一个函数,用于将指定的定时器(在这个例子中是TIM2)配置为使用内部时钟。这个函数通常在初始化定时器之前被调用,以确保定时器使用正确的时钟源。
简而言之,TIM_InternalClockConfig(TIM2);这行代码的意思是:将TIM2定时器配置为使用内部时钟作为其时钟源。这样,当定时器开始运行时,它将根据内部时钟的频率来计数和触发事件。



TIM2时间基准初始化
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
        TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
        TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
        TIM_TimeBaseInitStructure.TIM_Period = 100 - 1;  //ARR  Reso=1/(ARR+1)
        TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1;  //PSC  Freq=72M/(PSC+1)/(ARR+1)
        TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
        TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
TIM_ClockDivision:时钟分频因子,这里设置为不分频。
TIM_CounterMode:计数器模式,这里设置为向上计数。
TIM_Period:自动重装载寄存器(ARR)的值,这里设置为99(因为是从0开始计数的,所以实际计数到99)。
TIM_Prescaler:预分频器的值,这里设置为719(因为是从0开始计数的,所以实际分频值为719)。
TIM_RepetitionCounter:重复计数器的值,这里设置为0(在某些高级定时器中用于PWM模式,但在TIM2中可能不使用)。
这些值的确定需要计算,但是这里不做深究。*********但是非常重要*******日后一定去讲!现在你先认识一下就行,CV过去。

输出比较初始化
TIM_OCInitTypeDef TIM_OCInitStructure;
        TIM_OCStructInit(&TIM_OCInitStructure);
        TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
        TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
        TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
        TIM_OCInitStructure.TIM_Pulse = 0;  //CCR  Duty=CCR/(ARR+1)
        TIM_OC1Init(TIM2, &TIM_OCInitStructure);
这里定义了一个TIM_OCInitTypeDef结构体变量TIM_OCInitStructure并设置其成员变量来配置TIM2定时器的PWM输出。其中:

TIM_OCMode:输出比较模式,这里设置为PWM模式1。
TIM_OCPolarity:输出极性,这里设置为高电平有效。
TIM_OutputState:输出状态,这里设置为使能。
TIM_Pulse:捕获/比较寄存器(CCR)的值,这里设置为0(这决定了PWM的初始占空比)。
其实这就是设置其工作方式。

一定要看
很多人一定不明白,为什么初始化一个TIM2的PWM居然需要:时间基准初始化与输出比较初始化。但你翻回去看我说PWM组成时,我说过他被分成了两个部分,一部分是脉冲信号发生,另一部分是模拟信号处理,其实这两个部分就指的是我们的这两个初始化!所以在下次写代码时,一定要记住哈!

设置占空比

void PWM_SetDuty_1(uint16_t duty){
        TIM_SetCompare1(TIM2, duty);
}
固有TIM库
设置占空比的函数中调用到了TIM_SetCompare1函数,这个函数在stm的TIM库中。



uint16_t Compare1:表示要设置的比较值。这个值将被写入定时器的捕获/比较寄存器1(CCR1)。

TIM_TypeDef* TIMx:这是一个指向TIM(定时器)结构体的指针,用于指定要操作哪个定时器。在这里指向了TIM2定时器。

在STM32中,定时器通常被组织成结构体,以便通过结构体指针来访问其内部寄存器。assert_param(IS_TIM_LIST8_PERIPH(TIMx));assert断言语句,用于检查传入的参数是否有效。IS_TIM_LIST8_PERIPH(TIMx) 是一个宏,它检查 TIMx 是否是有效的定时器指针。如果 TIMx 无效,断言将失败,并可能导致程序在调试模式下停止执行。

*设置CCR1寄存器:TIMx->CCR1 = Compare1;这行代码将 Compare1 的值写入到 TIMx 定时器的CCR1寄存器中。CCR1寄存器用于PWM(脉冲宽度调制)或输出比较功能。当定时器的计数值与CCR1中的值相等时,(一般是时间达到了计数的目的值)会产生一个事件(例如,PWM输出可能会切换状态)。

TIM_SetCompare1
TIM_SetCompare1 函数用于设置指定定时器的CCR1寄存器的值。这个值决定了PWM信号的高电平持续时间(占空比)或在输出比较功能中的比较值。

总结
所以这里调用这些代码的目的是:选取TIM2定时器,并设置其占空比为duty。

设置频率
void PWM_SetFreq_1(uint16_t freq){
        uint16_t psc = 720000/freq - 1;
        TIM_PrescalerConfig(TIM2, psc, TIM_PSCReloadMode_Update);
}
同理,我的freq是我的PWM设置的频率。

重点来了!

psc的算法讲解(预分频器)
720000 通常代表STM32微控制器的某个时钟源(如APB1或APB2总线时钟)的频率。但是并不是所有都是720000

720000/freq代表了多少时钟周期才产生一次PWM信号的周期

而我的预分频器的第一位是零位,所以需要在计算结果上-1

TIM_PrescalerConfig
继续同理,他仍在我们的TIM固件库中,请看!



参数
TIM_TypeDef* TIMx:这是一个指向TIM(定时器)结构体的指针。我们这里使用的是TIM2。
uint16_t Prescaler:预分频器的值。这个值决定了定时器时钟(TIMxCLK)的预分频比例。例如,如果TIMxCLK为72MHz,且Prescaler为7199,则定时器计数器(CNT)的时钟频率将是72MHz / (7199 + 1) = 10kHz。
uint16_t TIM_PSCReloadMode:这个参数定义了预分频值如何被加载到定时器中。它决定了在何时更新预分频器的值。我们这里使用的是TIM_PSCReloadMode_Update,意思是预分频器在更新事件中加载。(毕竟是UPDATE吗)
断言函数
第二次见断言函数了(assert),是不是已经有点印象了呢,当然我可以再教一遍,他是检查我所传入的参数是否是有效的,如果有效,继续运行。如果无效则函数终端!

在这里☞

IS_TIM_ALL_PERIPH(TIMx):检查TIMx是否是一个有效的TIM外设指针。(假如我说这是TIM99,运行到这行代码就会测到我在说胡话,从而直接中断函数,防止我继续做傻事)
IS_TIM_PRESCALER_RELOAD(TIM_PSCReloadMode):检查TIM_PSCReloadMode是否是一个有效的预分频器重新加载模式。
TIM->XXX
同样的老朋友了。

TIMx->PSC = Prescaler;
预分频器的值(Prescaler)写入到TIMx结构体的PSC字段中。

举个例子:假设您有一个PWM应用,您希望设置PWM的频率为1kHz。您知道定时器的输入时钟是72MHz。为了得到1kHz的PWM频率,您需要计算预分频器的值:

uint16_t Prescaler = (uint16_t)(72000000 / 1000) - 1; // 计算预分频值
TIM2->PSC = Prescaler; // 将预分频值写入TIM2的PSC寄存器
在这个例子中,Prescaler 的值将是71999(因为72MHz除以1kHz等于72000,然后减1得到71999)。然后,这个值被写入到TIM2定时器的PSC寄存器中,以设置预分频器的值。

TIMx->EGR = TIM_PSCReloadMode;

将定时器的预分频重加载模式写入TIMX的事件生成寄存器中。

这个代码用于设置或重置定时器的UG(更新生成)位。这个非常重要,你可以把它理解为一个转换器,当转换器被重置或设置后,系统的模式才会发生反转!

(EGR事件生成/寄存器)

什么是UG
UG位的作用:
UG位是TIMx_EGR(事件生成)寄存器中的一个位,用于软件生成更新事件(UEV)。
当UG位被设置(置1)时,会生成一个更新事件(UEV)。
更新事件(UEV)的意义:
更新事件(UEV)是STM32定时器中的一个关键事件。
当定时器发生更新事件时,会触发一系列的动作,如更新定时器的影子寄存器、生成中断或DMA请求等。
在某些配置下,UEV还可以用于触发DAC(数模转换器)的转换。
设置或重置UG位的操作:
设置UG位:通过向TIMx_EGR寄存器写入一个值,将UG位设置为1,从而生成一个更新事件(UEV)。
重置UG位:通常不需要显式重置UG位,因为UG位是一个写1清零的位。一旦设置了UG位,它会自动清零。
UG位与更新事件(UEV)的关系:
设置UG位是软件生成更新事件(UEV)的方法。
更新事件(UEV)可以触发定时器的多种功能,如更新影子寄存器、产生中断等。
UG位的应用:
在需要软件控制定时器更新事件的情况下,可以使用UG位来生成UEV。例如,在配置DAC的外部触发转换时,可以通过设置UG位来触发DAC的转换。
PWM应用
都到这里了,其实我不说你都会用了!

void Buzzer_ON(uint16_t freq, uint8_t duty)
{
                        PWM_SetFreq_1(freq);
                        PWM_SetDuty_1(tone);

}
Buzzer_ON(200, 50);
这样子蜂鸣器就可以发出200HZ的声音,占空比为50%,故响度为全音的一半。

都看到这里了,给个赞吧!!!
————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/2301_76726104/article/details/139578729

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

1923

主题

15596

帖子

11

粉丝