Stm32学习笔记(1)-利用TIM1产生SPWM波

Stm32学习笔记(1)-利用TIM1产生SPWM波

  1. **SPWM波的形成原理:**利用正弦波的各点幅值成正弦变换的思想,我们可以类似的采取在一系列方波中,让占空比中高度不变,占空比大小呈正弦变换的这样的一种做法,这样占空比大小呈正弦变换的波我们称之为SPWM波。网上有生成正弦波采样点数组的软件,可以选择采样点数和精度。本次实验中就需要用这个软件来产生我们需要的正弦表。(后面会解释)

  2. SPWM.c程序代码如下(这段代码是参考网上某大神的,并不是自己写的。我只是拿过来给大家解释下怎么用~)

#define  __SPWM
#include "stm32f10x.h"
#include "SPWM.h"
const u32 spwm[100] = {
0x4000,0x4405,0x4805,0x4BFE,0x4FEA,0x53C7,0x578F,0x5B40,0x5ED5,0x624A,
0x659E,0x68CB,0x6BCF,0x6EA7,0x714F,0x73C6,0x7609,0x7815,0x79E8,0x7B81,
0x7CDD,0x7DFC,0x7EDD,0x7F7E,0x7FDF,0x7FFF,0x7FDF,0x7F7E,0x7EDD,0x7DFC,
0x7CDD,0x7B81,0x79E8,0x7815,0x7609,0x73C6,0x714F,0x6EA7,0x6BCF,0x68CB,
0x659E,0x624A,0x5ED5,0x5B40,0x578F,0x53C7,0x4FEA,0x4BFE,0x4805,0x4405,
0x4000,0x3BFB,0x37FB,0x3402,0x3016,0x2C39,0x2871,0x24C0,0x212B,0x1DB6,
0x1A62,0x1735,0x1431,0x1159,0x0EB1,0x0C3A,0x09F7,0x07EB,0x0618,0x047F,
0x0323,0x0204,0x0123,0x0082,0x0021,0x0001,0x0021,0x0082,0x0123,0x0204,
0x0323,0x047F,0x0618,0x07EB,0x09F7,0x0C3A,0x0EB1,0x1159,0x1431,0x1735,
0x1A62,0x1DB6,0x212B,0x24C0,0x2871,0x2C39,0x3016,0x3402,0x37FB,0x3BFB,
};//这里采样点为1个全波100采样点,精度为15,则数值大小为0-32767(2^15)


static u16 i = 0;				//表示正弦波取样点

u16 TimerPeriod   = 0;			//自动重装载值
u16 Channel1Pulse = 0;			
u16 Channel2Pulse = 0;

/*****************************************************************
* 功能: 定时器1产生2路互补的PWM波(频率 = pfreq / (psc+1))      
* 说明: channel1,  channel2  --> PA.8, PA.9	
*	  	channel1N, channel2N --> PB.13, PB.14 (互补通道)
*	  	TimerPeriod	  		 --> 自动重装载周期值
*	  	ChannelxPulse 		 --> 占空周期值
*****************************************************************/

void TIM1_PWM_Init(u16 pfreq ,u16 psc)			//pfreq为不分频时的PWM频率,psc为预分频值
{  
	GPIO_InitTypeDef GPIO_InitStructure;
	TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;		
	TIM_OCInitTypeDef TIM_OCInitStructure;			//输出通道配置
	TIM_BDTRInitTypeDef TIM_BDTRInitStructure;		//死区和刹车配置

	TimerPeriod = (SystemCoreClock / pfreq) - 1; 			//自动重装载周期值

	/* ChannelxPulse = DutyCycle * (TIM1_Period - 1) / 100 */
	Channel1Pulse = (u16)((u32)(50  * (TimerPeriod - 1)) / 100 );	//占空比50%
	Channel2Pulse = (u16)((u32)(50  * (TimerPeriod - 1)) / 100 );	//占空比50%

	/* 使能TIM1,GPIOA,GPIOB*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);	
 	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB, ENABLE); 
	
	/*channel1 ,channel2  --> PA.9, PA.10*
	 *channel1N,channel2N --> PB.14, PB.15*/
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10;	  
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  				  				
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);

	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14 | GPIO_Pin_15;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
 
  	/*  初始化TIM1  */
	TIM_TimeBaseStructure.TIM_Period            = TimerPeriod;			//设置重装载周期值  
	TIM_TimeBaseStructure.TIM_Prescaler         = psc;					//设置预分频值 
	TIM_TimeBaseStructure.TIM_ClockDivision     = 0;					//时钟分频因子,仅与输入捕获有关(定时器与滤波器的频率比)
	TIM_TimeBaseStructure.TIM_CounterMode       = TIM_CounterMode_Up;	//TIM向上计数模式
	TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;					//重复溢出中断
	TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);						//初始化定时器基本配置
	
	/* Channel_1   TIM_OCMode_PWM1模式 */	 
	TIM_OCInitStructure.TIM_OCMode       = TIM_OCMode_PWM1;				//在向上计数时,一旦TIMx_CNTTIMx_CCR1时通道1为有效电平
 	TIM_OCInitStructure.TIM_OutputState  = TIM_OutputState_Enable;		//比较输出使能
	TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable;		//PWM互补输出使能
	TIM_OCInitStructure.TIM_Pulse        = Channel2Pulse;				//占空比 = TIM_Pulse/TIM_Period;
	TIM_OCInitStructure.TIM_OCPolarity   = TIM_OCPolarity_High;			//有效电平为高电平
	TIM_OCInitStructure.TIM_OCNPolarity  = TIM_OCNPolarity_High;		//互补通道电平与普通通道电平相反
	TIM_OCInitStructure.TIM_OCIdleState  = TIM_OCIdleState_Set;			//输出空闲状态
	TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCNIdleState_Reset;		//PWM互补输出空闲状态
	TIM_OC3Init(TIM1, &TIM_OCInitStructure);							//根据指定的参数初始化外设TIM1 OC2

	TIM_OC2PreloadConfig(TIM1, TIM_OCPreload_Enable);  					//使能TIM3在CCR1上的预装载寄存器
	TIM_OC3PreloadConfig(TIM1, TIM_OCPreload_Enable);  					//使能TIM3在CCR2上的预装载寄存器

	/*死区和刹车功能配置*/
	TIM_BDTRInitStructure.TIM_OSSRState       = TIM_OSSRState_Enable;
	TIM_BDTRInitStructure.TIM_OSSIState       = TIM_OSSIState_Enable;
	TIM_BDTRInitStructure.TIM_LOCKLevel       = TIM_LOCKLevel_1;
	TIM_BDTRInitStructure.TIM_DeadTime        = 0x2F;					//设置TIM1_BDTR的DTG[7:0]
	TIM_BDTRInitStructure.TIM_Break           = TIM_Break_Disable;
	TIM_BDTRInitStructure.TIM_BreakPolarity   = TIM_BreakPolarity_High;
	TIM_BDTRInitStructure.TIM_AutomaticOutput = TIM_AutomaticOutput_Enable;
	TIM_BDTRConfig(TIM1, &TIM_BDTRInitStructure);
	
	TIM_Cmd(TIM1, ENABLE); 												//使能TIM1
	TIM_CtrlPWMOutputs(TIM1,ENABLE);									//PWM输出使能(输出PWM的这句一定要加)
}

/**************************************************
*	名称:利用定时器2产生中断改变SPWM波的占空比  
***************************************************/
void TIM2_Int_Init(u16 ifreq,u16 psc)						//ifreq为不分频时的更新中断频率,psc为预分频值
{
  	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	NVIC_InitTypeDef NVIC_InitStructure;

	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);		 //时钟使能

	TimerPeriod = (SystemCoreClock / ifreq) - 1; 							 

	TIM_TimeBaseStructure.TIM_Period        = TimerPeriod; 	 			//设置自动重装载寄存器周期值
	TIM_TimeBaseStructure.TIM_Prescaler     = psc; 				   		//设置预分频值  10Khz的计数频率  
	TIM_TimeBaseStructure.TIM_ClockDivision = 0;            			//设置时钟分频系数
	TIM_TimeBaseStructure.TIM_CounterMode   = TIM_CounterMode_Up;  		//TIM向上计数模式
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); 	      
 
	TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE );                			//使能指定的TIM2中断,允许更新中断(计数器溢出或软件初始化时)

	NVIC_InitStructure.NVIC_IRQChannel                   = TIM2_IRQn;  	//TIM2中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3;          	//主优先级3级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority        = 4;          	//从优先级4级
	NVIC_InitStructure.NVIC_IRQChannelCmd                = ENABLE;     	//IRQ通道被使能
	NVIC_Init(&NVIC_InitStructure);                                    	//根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器

	TIM_Cmd(TIM2, ENABLE);   											//使能TIM2				 
}

//定时器2中断服务程序
void TIM2_IRQHandler(void)  
{
	extern float Period_percent;
	
	if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)  				//检查TIM2更新中断发生与否
	{	 
		TIM_SetCompare2(TIM1,(u16)(Period_percent*spwm[i])) ;		//修改TIM1通道1的PWM占空比,后者为捕获/比较寄存器1的值
		TIM_SetCompare3(TIM1,(u16)(Period_percent*spwm[i++]));		//修改TIM1通道2的PWM占空比,后者为捕获/比较寄存器2的值
		if(i == 101)  //一周期采样100个点
		{
			i = 0;
		}
		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);  //清除TIM2更新中断标志	
	}
}



以上代码需要解释的几点有:
(1)TimerPeriod的计算
我们知道,pfreq是未分频的PWM频率,则有pfreq = SystemCoreClock / (TimerPeriod + 1);
TimerPeriod = (SystemCoreClock / pfreq) - 1
(2)Channel1Pulse的计算(疑惑)
关于这部分我总觉得下面这段公式只适用于50%占空比的。我个人计算占空比后发现不是这个公式。希望各位大神能帮忙解释下这个公式是怎么推的。
Channel1Pulse = (u16)((u32)(50 * (TimerPeriod - 1)) / 100 )
(3)TIM_SetCompare2(TIM1,(u16)(Period_percent*spwm[i]))里面后面参数的含义
这部分实际上我们的Period_percent在主函数main里面被赋值了,如下
Period_percent = ((SystemCoreClock / freq) - 1) / 32767.0
其实也就等价于
Period_percent = TimerPeriod / 32767.0
而我们的spwm[i]数组里面,表示的是正弦波各个采样点的大小,大小的幅度在0-32767之间。因此将spwm[i]的值乘以Period_percent其实就表示了我们给TIM_OCInitStructure.TIM_Pulse赋多少的值。
可以用下面这个公式理解:
TIM_Pulse / TimerPeriod = spwm[i] / 32767
【注意】在这里我们由于用软件产生的正弦波的精度是选择了15位,因此大小是32767,若是采取其他精度位,则要把32767改为相应的值。
3. main函数如下

#include "delay.h"
#include "sys.h"
#include "SPWM.h"

vu16 freq = 8000, Period = 0;						//PWM与定时器中断频率;自动重装载值
__IO float Period_percent; 							//PWM占空比变化因子,用于修改脉宽

int main(void)
{	

	NVIC_Configuration(); 
	delay_init();
	Period_percent = ((SystemCoreClock / freq) - 1) / 32767.0;
	
	TIM1_PWM_Init(8000,0);	 						//输出PWM的频率为8KHz(一周期100个点,正弦波的频率为8000/100=80Hz)
	TIM2_Int_Init(8000,0);	 						//定时器中断的频率为8KHz
}

以上代码需要说明的有:
(1)__IO的作用
其实,在core_cm3.h中,__IO被重定义位volatile.

#define     __IO    volatile 

那么问题又来了,volatile又是干嘛用的呢?
其实,volatile 的作用就是指示编译器不要因优化而省略此指令,必须每次都直接读写其值。
比如,下面这段代码
写一段测试代码如下

  u8 test;
 
  test = 1;
  test = 2;
  test = 3;

设置优化级别中级
运行后test会被直接取值为3 只有最后一个语句被编译
如果用volatile

  volatile u8 test;
 
  test = 1;
  test = 2;
  test = 3;

则所有语句都会被编译。test先后被设置成1、2、3
在本实验中,将float Period_percent定义成__IO,就是为了在每一次改变值的过程,编译器都会依次将其运行。
(2) 关于两个定时器初始化

TIM1_PWM_Init(8000,0);	 						
TIM2_Int_Init(8000,0);

我们希望每次产生PWM波就对应着我们正弦波上的一个采样点,因此,要把PWM的频率和定时器产生中断的频率设置成一致。在本实验设置成了8KHz。
(3)正弦波的频率设置
关于正弦波的频率,比如对于8KHz的PWM频率(中断频率),我要一个80Hz的正弦波输出。那么可以得知正弦波一个周期的采样点为8000 / 80 = 100。因此,正弦波频率的设置可以通过调节PWM频率,也可以调节采样点数。当然,采样点数越多波形会越好,不过也不能无限多。
4. 实验现象
如下图,示波器的CH1是TIM1_CH2,CH2是TIM_CH2N,实现了互补通道的SPWM的输出

Stm32学习笔记(1)-利用TIM1产生SPWM波_第1张图片

【注】本实验中是采用了4个通道来输出spwm波的,包括两个互补的通道。但是示波器仅仅显示了其中一对互补通道的SPWM波。

【2020年6月22更新】

  • 太久没上博客了发现原来这么多人评论,没能一一回复深感抱歉,为了方便,我把stm32相关的模块源代码都已经开源到了github上,各位有需要的可以上https://github.com/rfhklwt/stm32自取,如果觉得我的文件对你有用,可以考虑在github右上方的star点一下,谢谢各位啦。
  • 另外,欢迎各位转载,但请务必要标注版权!
  • 另外我已经3年没碰stm32了,因此基本知识上都忘得差不多了,对于评论区的一些问题没能回复,请见谅哈

你可能感兴趣的:(stm32学习笔记)