| 
 
| 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
 
 
 | 
 |