关于PWM模式,且听我娓娓道来

2019-07-21 05:40发布

首先,本人虽然初学STM32但极力反对一种误人子弟的观点:“对于STM32这样级别的MCU,有库函数就不用去看寄存器怎么操作的了!”
好了,言归正传,最近总看到很多朋友对于PWM这个实验有很多的疑惑,看到原子也在极力的回复也挺累的(体谅一下幸苦的原子大神,(*^__^*) ),所以我打算写这么一篇文字来阐述一下我个人对STM32的PWM的理解。
首先来说,你要使用PWM模式你得先选择用那个定时器来输出PWM吧!除了TIM6、TIM7这两个普通的定时器无法输出PWM外,其余的定时器都可以输出PWM,每个通用定时器可以输出4路PWM,高级定时器TIM1、TIM8每个可输出7路PWM,这里为了方便起见,我们选择与实验相同的TIM3的通道2来说明。选好定时器及通道后,下一步就是要使能定时器的时钟,根据需要看看是否需要重映射IO,然后就是配置输出PWM的IO及定时器,到这里原子的视频及例程都有详细的介绍,这里只需要提一点有些网友疑惑的TIM_TimeBaseStructure.TIM_ClockDivision = 0;这句话是什么作用?其实仔细看过技术手册后发现这句话与PWM输出实验其实是没关系的,这句话是设置定时器时钟(CK_INT)频率与数字滤波器(ETR,TIx)使用的采样频率之间的分频比例的(与输入捕获相关),0表示滤波器的频率和定时器的频率是一样的。至于其余部分,我就不再赘述。做完这些准备工作后,我就针对大多数朋友疑惑的地方——PWM模式的初始化设置做一个详细的阐述:先贴代码      1       TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; //选择定时器模式:TIM脉冲宽度调制模式2
     2       TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
     3       TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM输出比较极性高
     4       TIM_OC2Init(TIM3, &TIM_OCInitStructure);   //根据T指定的参数初始化外设TIM3 OC2
     5       TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable); //使能TIM3在CCR2上的预装载寄存器,即TIM3_CCR2的预装载值在更新事件到来时才能被传送至当前寄存器中。
     6       TIM_Cmd(TIM3, ENABLE);  //使能TIM3
这6句话就把PWM的通道配置好了,一句句来解释:
这里原子选择的PWM2模式,为什么选择的是PWM2模式呢?为什么不选择PWM1模式呢?两者又有什么区别呢?下面我们就一探究竟,PWM1和PWM2模式是由CCMR1的OC1M和OC2M来决定的,因为我们选择的是是通道2,所以设置的是OC2M,再看相关介绍
OC1M[2:0]:输出比较1模式(Output compare 1 enable) 110:PWM模式1- 在向上计数时,一旦TIMx_CNT<TIMx_CCR1时通道1为有效电平,否则为
无效电平;在向下计数时,一旦TIMx_CNT>TIMx_CCR1时通道1为无效电平(OC1REF=0),否
则为有效电平(OC1REF=1)。
111:PWM模式2- 在向上计数时,一旦TIMx_CNT<TIMx_CCR1时通道1为无效电平,否则为
有效电平;在向下计数时,一旦TIMx_CNT>TIMx_CCR1时通道1为有效电平,否则为无效电
平。  看到红 {MOD}的“有效电平”了吧,那么这又是谁定义的呢?别急,再看手册,可知它是由CCER这个寄存器的CCxP来决定的这里是通道2,所以是CC2P,继续看介绍
CC1P:输入/捕获1输出极性(Capture/Compare 1 output polarity)  位1
CC1通道配置为输出:
0:OC1高电平有效
1:OC1低电平有效
现在很清楚了吧,又因为第3句,TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM输出比较极性高,所以这里我们设置的CC2P是0,也就是默认的OC2高电平有效。这样第3句话也捎带着解释了,哈哈!由于我们的战舰板的LED是低电平亮,而刚开始的给CC2P用来设置占空比的led0pwmval为0它是小于等于TIM3_CNT的,也就符合TIMx_CNT>=TIMx_CCR1时通道2输出有效电平,也就是高电平,所以你把原子的例程原封不动的Down到板子里,会看到刚上电,LED灯是不亮的。现在这块明白了吧!若你觉得还是不爽,我就非得用PWM1模式,那也可以,就像有个网友说“我拿原子的PWM Code就改了一个PWM1模式,按原子讲的PWM1和PWM2的输出是相反的啊,可是我上电发现LED是常亮的啊?怎么回事啊,求解释啊。。。”我们来分析一下这位朋友的代码,他把PWM2改成了PWM1,别的什么都没动,那么现在符合“PWM模式1- 在向上计数时,一旦TIMx_CNT<TIMx_CCR1时通道1为有效电平”
,否则为无效电平。“结果必然是就是LED长亮喽,要想得到跟原代妈一样的效果,那就把CC2P设置成1,OC2低电平有效,这样就可以了,有兴趣的朋友可以动手试试!(实践出真知吗!)
好了,废了这么多话,也不早了 洗洗睡吧!希望这篇文字对PWM有疑惑的朋友有所帮助!希望大家共同进步!分享是一种快乐,欢迎批评指正!
友情提示: 此问题已得到解决,问题已经关闭,关闭后问题禁止继续编辑,回答。
该问题目前已经被作者或者管理员关闭, 无法添加新回复
50条回答
electric405
1楼-- · 2019-07-21 09:08
发一个自己写的SPWM
//主函数
#include"delay.h"
#include"sys.h"
#include"math.h"
u16 counter; //正弦表移动指针
const float I=3.14159;
u16 SIN_Table[360];//正弦表
u16 CCR2_VAL;
/**************************************
正弦表计算函数Calu_sintable()
**************************************/
void Calcu_sintable()
{
   float a;
   float M=0.80;//定义调制比
   int ortion=5000;
   int i;
   for( i=0;i<360;i++)
   {
    a=Portion*(1+M*sin(((0.5+i)/360)*2*PI));
    SIN_Table=(u16) a;
}
}

//外设时钟配置
void RCC_Configuration()
{
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO, ENABLE); 
 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);       
}
 //GPIO通道配置
 void GPIO_Configuration()
 { GPIO_InitTypeDef GPIO_InitStructure;
   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
   GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
   GPIO_Init(GPIOA, &GPIO_InitStructure);
 }
 //定时器1初始化配置
void TIM3_Configuration()
{ TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
  TIM_TimeBaseInitStruct.TIM_Period=10000;//20000十进制
  TIM_TimeBaseInitStruct.TIM_Prescaler=0;
  TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1;
  TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_CenterAligned1;
  TIM_TimeBaseInitStruct.TIM_RepetitionCounter=1;
  TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStruct);
}
// WM配置初始化
void WM_Configuration()
{ TIM_OCInitTypeDef TIM_OCInitStructure;
  NVIC_InitTypeDef NVIC_InitStructure;
  TIM_OCInitStructure.TIM_OCMode =TIM_OCMode_PWM1; 
  TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; 
  TIM_OCInitStructure.TIM_Pulse = CCR2_VAL;
  TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; 
  TIM_OC2Init(TIM3, &TIM_OCInitStructure);
  TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable);  //使能TIMx在CCR1上的预装载寄存器
  TIM_ARRPreloadConfig(TIM3, ENABLE); //使能TIMx在ARR上的预装载寄存器
  TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE); //开启计数溢出中断
  TIM_Cmd(TIM3, ENABLE);


  NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;  //TIM3中断
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;  //先占优先级0级
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;  //从优先级3级
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
  NVIC_Init(&NVIC_InitStructure);  //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
 
}
//主函数实现程序功能
int main()
{ counter=0;
  CCR2_VAL=3000;
  Calcu_sintable();//计算正弦表
  SystemInit();   //系统时钟初始化为72M   SYSCLK_FREQ_72MHz
  delay_init(72);       //延时函数初始化   
  NVIC_Configuration();   //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
  RCC_Configuration();
  GPIO_Configuration();
  TIM3_Configuration();
  WM_Configuration();
 
  while(1)
  {
  }
}
 //中断处理函数一定要写在stm32f10x_it.c 文件中
extern  u16 counter;
  extern  u16 SIN_Table[360];
  //extern  u16 CCR2_VAL;
  void TIM3_IRQHandler(void)   //TIM3中断
{
if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) //检查指定的TIM中断发生与否:TIM 中断源 
{
TIM_ClearITPendingBit(TIM3, TIM_IT_Update );  //清除TIMx的中断待处理位:TIM 中断源 
    }
//CCR2_VAL=SIN_Table[counter];
TIM_SetCompare2(TIM3,SIN_Table[counter]);
counter++;
if(counter>359)
{
counter=0;
}
}
樱洛川
2楼-- · 2019-07-21 10:26
谢谢楼主~~明白了好多呢
electric405
3楼-- · 2019-07-21 11:56
更正16楼一个错误,不开启端口复用时,没必要开启复用功能外设时钟RCC_APB2Periph_AFIO
E-angel
4楼-- · 2019-07-21 14:29
 精彩回答 2  元偷偷看……
wangyan915205
5楼-- · 2019-07-21 15:13
分享的很好!
我也是初学者,不过我以为学习因人而异,每个人的起步基础不一样;对于计算机专业且c语言水平很高的人来说,也许单片机就是小儿科;但是对于非电子专业的人来说,学习起来可能就比较费劲。愚以为:学习无捷径,任何事情都是熟能生巧。从操作寄存器版本学习可以更加熟悉STM32芯片的资源。
wangyan915205
6楼-- · 2019-07-21 20:25
希望多分享学习心得,多指教!

一周热门 更多>