STM32的定时器还有一个模式叫做输出比较 翻转模式。这种模式toggle,顾名思义toggle,可以翻转电平,但是条件是toggle:当计数值达到比较值时,才会在对应的通道引脚翻转原先的电平。利用这个特点,我们可以在引脚上生成PWM波。
下面就讲讲如何利用这个“翻转”这个特点,来输出PWM波。还是基于我自己的规工程。
1、工程的修改
1)这里用到了定时器,所以需要将stm32f10x_tim.h添加到STM32F10x_StdPeriod_Driver工程组中。
2)打开stm32f0x_conf.h文件,将其中原先被屏蔽的语句toggle:#include "stm32f10x_tim.h"的注释去掉。
3)新建OCToggle.c与OCToggle.h两个文件,分别保存在BSP文件夹里下的src与inc中,然后在将OCToggle.c添加到BSP工程组。
2、OCToggle.c与OCToggle.h文件程序的编写
首先是引脚的初始化。我使用TIM2,所以需要初始化TIM2对应的引脚PA0、PA1、PA2、PA3这四个引脚将它们配置成复用推挽输出,代码如下:
/*************************************************************
Function : OCToggle_GPIO_Init
Deion: 输出比较翻转模式下定时器对应通道引脚初始化
Input : none
return : none
*************************************************************/
static void OCToggle_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_Apb2PeriphClockCmd(RCC_Apb2Periph_GPIOA, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//定时器各通道引脚配置成复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
接下去当然是要配置定时器了。它的代码如下:
/*************************************************************
Function : OCToggle_TIM2_Init
Deion: 输出比较翻转模式定时器2初始化
Input : none
return : none
*************************************************************/
static void OCToggle_TIM2_Init(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);//初始化TIM2时钟
/* -------------------------------------------------------
TIM_OCMode_Toggle模式计数值等于比较值翻转电平
在PA0引脚输出频率为72M/CCR1_Val/2=732Hz,占空比为50%的PWM波
在PA1引脚输出频率为72M/CCR2_Val/2=1099Hz,占空比为50%的PWM波
在PA2引脚输出频率为72M/CCR3_Val/2=2197Hz,占空比为50%的PWM波
在PA3引脚输出频率为72M/CCR4_Val/2=4395Hz,占空比为50%的PWM波
---------------------------------------------------------*/
TIM_TimeBaseStructure.TIM_Period = 65535;//定时器计数周期
TIM_TimeBaseStructure.TIM_Prescaler = 1 - 1;//预分频
TIM_TimeBaseStructure.TIM_ClockDivision = 1 - 1;//时钟不分频
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;//增计数
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);//初始化定时器
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Toggle;//输出比较主动模式
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;//输出使能
TIM_OCInitStructure.TIM_Pulse = CCR1_Val;//设置比较值(跳变值)
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;//有效电平为高电平
TIM_OC1Init(TIM2, &TIM_OCInitStructure);//初始化输出比较寄存器
TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Disable);//关闭预转载
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;//输出使能
TIM_OCInitStructure.TIM_Pulse = CCR2_Val;//设置比较值(跳变值)
TIM_OC2Init(TIM2, &TIM_OCInitStructure);//初始化输出比较寄存器
TIM_OC2PreloadConfig(TIM2, TIM_OCPreload_Disable);
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;//输出使能
TIM_OCInitStructure.TIM_Pulse = CCR3_Val;//设置比较值(跳变值)
TIM_OC3Init(TIM2, &TIM_OCInitStructure);//初始化输出比较寄存器
TIM_OC3PreloadConfig(TIM2, TIM_OCPreload_Disable);
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;//输出使能
TIM_OCInitStructure.TIM_Pulse = CCR4_Val;//设置比较值(跳变值)
TIM_OC4Init(TIM2, &TIM_OCInitStructure);//初始化输出比较寄存器
TIM_OC4PreloadConfig(TIM2, TIM_OCPreload_Disable);
TIM_ITConfig(TIM2, TIM_IT_CC1 | TIM_IT_CC2 | TIM_IT_CC3 | TIM_IT_CC4, ENABLE);//清除中断标志
TIM_Cmd(TIM2, ENABLE);//打开定时器2
}
不对定时器做任何分频,并且让它满计数,即计数周期为65535。接下去再设置个通道的的工作模式为输出比较翻转模式,设置通道1的比较值为CCR1_Val、CCR3_Val、CCR3_Val、CCR4_Val,它们的值在OCToggle.h中定义。然后打开个通道的输出比较事件的中断。最后在打开定时器。这样的话,定时器这段就配置完成了。
既然打开了中断,则需要配置下中断,设置TIM2的中断优先级为1,代码如下:
/*************************************************************
Function : OCToggle_Int_Init
Deion: 输出比较翻转模式中断初始化
Input : none
return : none
*************************************************************/
static void OCToggle_Int_Init(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;//中断优先级为1
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
还要编写一个总函数:OCToggle_Init()函数将上面的初始化相关代码都在这个函数中调用,代码如下:
/*************************************************************
Function : OCToggle_Init
Deion: 输出比较翻转模式初始化
Input : none
return : none
*************************************************************/
void OCToggle_Init(void)
{
OCToggle_GPIO_Init();
OCToggle_TIM2_Init();
OCToggle_Int_Init();
}
接下去OCToggle.h的代码:
#ifndef __OCTOGGLE_H__
#define __OCTOGGLE_H__
#include "stm32f10x.h"
#define CCR1_Val 49152
#define CCR2_Val 32768
#define CCR3_Val 16384
#define CCR4_Val 8192
void OCToggle_Init(void);
#endif
这里之所以将CCR1_Val的值在这个h文件中宏定义,而不在c文件中直接定义成变量,原因是为了在其toggle他文件中调用方便,如果定义成变量的话,在其他文件还要用extern关键字来声明,比较麻烦!
3、stm32f10x_it.c文件的修改
TIM2的中断服务函数的程序如下:
/*************************************************************
Function : TIM2_IRQHandler
Deion: 定时器2中断服务程序
Input : none
return : none
*************************************************************/
void TIM2_IRQHandler(void)
{
static u16 capture = 0;
if (TIM_GetitStatus(TIM2, TIM_IT_CC1) != RESET)//通道1检测到比较事件
{
TIM_ClearITPendingBit(TIM2, TIM_IT_CC1);//清除标志位
capture = TIM_GetCapture1(TIM2);
TIM_SetCompare1(TIM2, capture + CCR1_Val);//重新设置比较值
}
if (TIM_GetITStatus(TIM2, TIM_IT_CC2) != RESET)//通道1检测到比较事件
{
TIM_ClearITPendingBit(TIM2, TIM_IT_CC2);//清除标志位
capture = TIM_GetCapture2(TIM2);
TIM_SetCompare2(TIM2, capture + CCR2_Val);//重新设置比较值
}
if (TIM_GetITStatus(TIM2, TIM_IT_CC3) != RESET)//通道1检测到比较事件
{
TIM_ClearITPendingBit(TIM2, TIM_IT_CC3);//清除标志位
capture = TIM_GetCapture3(TIM2);
TIM_SetCompare3(TIM2, capture + CCR3_Val);//重新设置比较值
}
if (TIM_GetITStatus(TIM2, TIM_IT_CC4) != RESET)//通道1检测到比较事件
{
TIM_ClearITPendingBit(TIM2, TIM_IT_CC4);//清除标志位
capture = TIM_GetCapture4(TIM2);
TIM_SetCompare4(TIM2, capture + CCR4_Val);//重新设置比较值
}
}
在这个中断服务程序中,每当检测到输出比较时事件时,通道对应的引脚就会自动翻转电平,然后我们在中断中,重新设置它的比较值,在原来的计数值的基础上再增加与计数值相同的计数值作为下一次的比较值。这样的话,就会形成占空比为50%的PWM波。以通道1为例解释下。通道1原来设置的计数值为49152,当定时器的计数值达到这个数时,就翻转通道1对应的输出引脚PA0翻转电平,然后在设置新的计数值为49152+49152,因为定时器只有16位,会溢出,所以下一个比较值为32768,这样的话,如果49152计数值时间内引脚输出高电平的话,下一个49152计数值时间内就会输出低电平,形成频率为72M/49152/2=732Hz,占空比为50%的PWM波。同样的,通道2会输出频率为1099Hz,占空比为50%的PWM波;通道3输出频率为2197Hz,占空比为50%的PWM波;通道4输出频率为4395Hz,占空比为50%的PWM波。
4、main函数的编写
main函数很简单,只是调用一些初始化函数:
/*************************************************************
Function : main
Deion: main入口
Input : none
return : none
*************************************************************/
int main(void)
{
BSP_Init();
OCToggle_Init();
PRINTF("\nmain() is running!\r\n");
while(1)
{
LED1_Toggle();
Delay_ms(1000);
}
}
5、测试
用 示波器的探头分别连接引脚PA0、PA1、PA2、PA3。可以看到下面的现象:
连接PA0可以检测到频率为732Hz,占空比为50%的PWM波。如下图所示:
连接PA1可以检测到频率为1099Hz,占空比为50%的PWM波。如下图所示:
连接PA2可以检测到频率为2197Hz,占空比为50%的PWM波。如下图所示:
连接PA3可以检测到频率为4395Hz,占空比为50%的PWM波。如下图所示:
原文链接:http://www.eeworld.com.cn/mcu/article_2016101130330.html