脉冲调制(PWM)是利用微处理器对数字输出来对模拟电路的一种非常有效的技术。简单点说就是对确定频率的信号,调整其占空比。
stm32的定时器除了TIM6和TIM7外,其他定时器都可以产生PWM输出。其中高级定时器TIM1和TIM8可以产生多达7路的PWM输出。通用定时器可以产生4路的PWM输出。
在stm32 TIM定时器[操作寄存器+库函数] 中我们是通过在中断中,翻转指定引脚的电平。在stm32中可以通过配置一个捕获/比较模式寄存器(TIMx_CCMR),设置通道引脚输出模式为PWM脉冲模式,在计时器计数到捕获/比较模式寄存器的值,指定引脚会输出一个有效电平,这样就可以通过定时器直接产生 PWM脉冲。这种方式下不需要开启中断。
这里说有效电平是因为这个电平不一定为1,这个在 捕获/比较使能寄存器(TIMx_CCER)中可以设置有效电平的极性。
指定引脚不是任意的,这个stm32对每个定时器通道有特定的引脚对应 对应关系如下
TIMx_CHx 对应的I/O口就是此通道对应的引脚
可以看出 TIM2的 OC通道 1-4 对应的就是 GPIOA 0-3
此例直接操作寄存器实现 Led灯由暗到亮再由亮到暗的呼吸灯效果。库函数实现用PWM脉冲输出模式,产生4个不同频率的脉冲,让led闪烁。
直接操作寄存器
通用定时器的每个通道都有6种输出模式,其中有两种PWM模式。通过捕获/比较模式寄存器1(TIMx_CCMR1)设定,由OC1M[2:0]三位决定。6种模式如下:
000:冻结。输出比较寄存器TIMx_CCR1与计数器TIMx_CNT间的比较对OC1REF不起作用;
001:匹配时设置通道1为有效电平。当计数器TIMx_CNT的值与捕获/比较寄存器1 (TIMx_CCR1)相同时,强制OC1REF为高。
010:匹配时设置通道1为无效电平。当计数器TIMx_CNT的值与捕获/比较寄存器1 (TIMx_CCR1)相同时,强制OC1REF为低。
011:翻转。当TIMx_CCR1=TIMx_CNT时,翻转OC1REF的电平。
100:强制为无效电平。强制OC1REF为低。
101:强制为有效电平。强制OC1REF为高。
110:PWM模式1- 在向上计数时,一旦TIMx_CNT
TIMx_CCR1时通道1为无效电平(OC1REF=0),否则为有效电平(OC1REF=1)。 111:PWM模式2- 在向上计数时,一旦TIMx_CNT
TIMx_CCR1时通道1为有效电平,否则为无效电平。
两种PWM模式,区别在于通道的电平极性是相反的。
首先需要设定TIMx_CCMR1寄存器:
OCxM[2:0]已经做了介绍,OC2CE:输出比较2清0使能 OC2PE:输出比较2预装载使能
通过设定OC2M[2:0]为 110/111 为PWM脉冲输出模式。
设定TIMx_CCER寄存器相关位,使能通道输出,还可以设置有效电平极性。
最后一个就是调整占空比的关键寄存器,捕获/比较寄存器(TIMx_CCRx),低16位有效,这个寄存器已经使用过,要实现PWM脉冲的占空比可调的原理就是不断改变这个寄存器的值。
要实现led亮暗的渐变,PWM的频率不能太低,低于50Hz的时候就会明显感觉到闪烁。这里用8khz的频率,调整PWM输出占空比,从0到不断增大其占空比,再递减为0.
代码如下: (system.h 和 stm32f10x_it.h 等相关代码参照 stm32 直接操作寄存器开发环境配置)
User/main.c
#include#include "system.h" #include "tim.h" void Gpio_Init(void); int main(void) { u32 var=0,flag=0; Rcc_Init(9); //系统时钟设置 // 相关TIM_x,CCR_x参数定义tim.h文件 Tim_Init(TIM_3,900,0); //初始化TIM3定时器,设定重装值和分频值 Tim_OC_Set(TIM_3,OC_2,7); //设定TIM3 通道1为PWM输出模式 Gpio_Init(); while(1){ delay(5000); //延时5ms if(flag){ var--; }else{ var++; } if(var>300) flag = 1; if(var == 0) flag = 0; Tim_CCR_Set(TIM_3,OC_2,var); } } void Gpio_Init(void) { RCC->APB2ENR|=1<<2; //使能PORTA时钟 GPIOA->CRL&=0X0FFFFFFF;//PA7输出 GPIOA->CRL|=0XB0000000;//复用功能输出 }
Library/src/tm.c
#include#include "tim.h" //通用定时器初始化 //参数说明:TIM_x 为选择定时器 TIM_1为通用寄存器1又一次类推(定义于tim.h), arr为自动重装值 ;psc 为时钟预分频数 //要使用定时器的其他函数,必须先调用此函数,因为时钟在这个函数中开启 //TIM3用于PWM输出已测试 //待完善 目前只支持TIM2 //其他定时器只做了开启时钟处理 void Tim_Init(u8 TIM_x,u16 arr,u16 psc) { switch(TIM_x) { case 1 :{ RCC->APB2ENR |=1<<11; break; } //TIM1高级定时器设置 case 2 :{ //TIM2通用定时器设置 RCC->APB1ENR |=1<<0; TIM2->ARR = arr; //设定自动重装值 TIM2->PSC = psc; //设定预分频值 TIM2->DIER |= 1<<0; //允许更新中断 TIM2->DIER |= 1<<6; //允许触发中断 TIM2->CR1 |= 0x81; //使能定时器,自动重装允许 break; } case 3 :{ RCC->APB1ENR |=1<<1; TIM3->ARR = arr; //设定自动重装值 TIM3->PSC = psc; //设定预分频值 //TIM3->DIER |= 1<<0; //允许更新中断 //TIM3->DIER |= 1<<6; //允许触发中断 TIM3->CR1 |= 0x81; //使能定时器 break; } case 4 :{ RCC->APB1ENR |=1<<2; TIM4->ARR = arr; //设定自动重装值 TIM4->PSC = psc; //设定预分频值 TIM4->DIER |= 1<<0; //允许更新中断 TIM4->DIER |= 1<<6; //允许触发中断 TIM4->CR1 |= 0x01; //使能定时器 break; } case 5 :{ RCC->APB1ENR |=1<<3; TIM5->ARR = arr; //设定自动重装值 TIM5->PSC = psc; //设定预分频值 TIM5->DIER |= 1<<0; //允许更新中断 TIM5->DIER |= 1<<6; //允许触发中断 TIM5->CR1 |= 0x01; //使能定时器 break; } case 6 :{ RCC->APB1ENR |=1<<4; break; } case 7 :{ RCC->APB1ENR |=1<<5; break; } case 8 :{ RCC->APB2ENR |=1<<13; break; } } } //捕获比较值设定函数 //参数说明: // TIM_x 为选择定时器 TIM_1为通用寄存器1又一次类推(定义于tim.h) // OC_x 为选择通道,以确定捕获/比较寄存器(1~4)(定义于tim.h) // val 为要设定的捕获/比较寄存器的值 // TIM3,OC_2 用于PWM输出已测试 // 待完善,目前只支持TIM2 void Tim_CCR_Set(u8 TIM_x,u8 OC_x,u32 val) { switch(TIM_x) { case 1 :{ break;} case 2 :{ TIM2->DIER |= 1 << OC_x; //开启相应允许捕获/比较中断 switch(OC_x){ case 1: { TIM2 ->CCR1 = val; //设置捕获/比较1的值 break; } case 2: { TIM2 ->CCR2 = val; //设置捕获/比较2的值 break; } case 3: { TIM2 ->CCR3 = val; //设置捕获/比较3的值 break; } case 4: { TIM2 ->CCR4 = val; //设置捕获/比较4的值 break; } } break; } case 3 :{ //TIM3->DIER |= 1 << OC_x; //开启相应允许捕获/比较中断 switch(OC_x){ case 1: { TIM3 ->CCR1 = val; //设置捕获/比较1的值 break; } case 2: { TIM3 ->CCR2 = val; //设置捕获/比较2的值 break; } case 3: { TIM3 ->CCR3 = val; //设置捕获/比较3的值 break; } case 4: { TIM3 ->CCR4 = val; //设置捕获/比较4的值 break; } } break; } case 4 :{ break;} case 5 :{ break;} case 6 :{ break;} case 7 :{ break;} case 8 :{ break;} } } //定时器通道引脚输出模式设定函数 //参数说明: // TIM_x 为选择定时器 TIM_1为通用寄存器1又一次类推(定义于tim.h) // OC_x 为选择输出通道选择(1~4)(定义于tim.h) // Mode 为选择通道对应引脚输出模式(0~7) // TIM3,OC_2 用于PWM输出已测试 // 待完善,目前只支持TIM2 void Tim_OC_Set(u8 TIM_x,u8 OC_x,u8 Mode) { switch(TIM_x) { case 1 :{ break;} case 2 :{ switch(OC_x){ case 1: { TIM2 ->CCMR1 |= Mode <<4; //设定引脚输出模式 TIM2 ->CCMR1 |= 1<<3; //允许预装载 //TIM2 ->CCER |= 1<<2; //引脚输出低电平为有效 TIM2 ->CCER |= 1<<0; //OC1 输出使能 break; } case 2: { TIM2 ->CCMR1 |= Mode <<12; //设定引脚输出模式 TIM2 ->CCMR1 |= 1<<11; //允许预装载 //TIM2 ->CCER |= 1<<5; //引脚输出低电平为有效 TIM2 ->CCER |= 1<<4; //OC2 输出使能 break; } case 3: { TIM2 ->CCMR2 |= Mode <<4; //设定引脚输出模式 TIM2 ->CCMR2 |= 1<<3; //允许预装载 //TIM2 ->CCER |= 1<<9; //引脚输出低电平为有效 TIM2 ->CCER |= 1<<8; //OC3 输出使能 break; } case 4: { TIM2 ->CCMR2 |= Mode <<12; //设定引脚输出模式 TIM2 ->CCMR2 |= 1<<11; //允许预装载 //TIM2 ->CCER |= 1<<5; //引脚输出低电平为有效 TIM2 ->CCER |= 1<<4; //OC1 输出使能 break; } } break; } case 3 :{ switch(OC_x){ case 1: { TIM3 ->CCMR1 |= Mode <<4; //设定引脚输出模式 TIM3 ->CCMR1 |= 1<<3; //允许预装载 //TIM3 ->CCER |= 1<<2; //引脚输出低电平为有效 TIM3 ->CCER |= 1<<0; //OC1 输出使能 break; } case 2: { TIM3 ->CCMR1 |= Mode <<12; //设定引脚输出模式 TIM3 ->CCMR1 |= 1<<11; //允许预装载 TIM3 ->CCER |= 1<<5; //引脚输出低电平为有效 TIM3 ->CCER |= 1<<4; //OC2 输出使能 break; } case 3: { TIM3 ->CCMR2 |= Mode <<4; //设定引脚输出模式 TIM3 ->CCMR2 |= 1<<3; //允许预装载 //TIM3 ->CCER |= 1<<9; //引脚输出低电平为有效 TIM3 ->CCER |= 1<<8; //OC3 输出使能 break; } case 4: { TIM3 ->CCMR2 |= Mode <<12; //设定引脚输出模式 TIM3 ->CCMR2 |= 1<<11; //允许预装载 //TIM3 ->CCER |= 1<<5; //引脚输出低电平为有效 TIM3 ->CCER |= 1<<4; //OC1 输出使能 break; } } break; } case 4 :{ break;} case 5 :{ break;} case 6 :{ break;} case 7 :{ break;} case 8 :{ break;} } }
Library/inc/tim.h
#include#define TIM_1 0x01 #define TIM_2 0x02 #define TIM_3 0x03 #define TIM_4 0x04 #define TIM_5 0x05 #define TIM_6 0x06 #define TIM_7 0x07 #define TIM_8 0x08 #define OC_1 0x01 #define OC_2 0x02 #define OC_3 0x03 #define OC_4 0x04 void Tim_Init(u8 TIM_x,u16 arr,u16 psc); void Tim_CCR_Set(u8 TIM_x,u8 OC_x,u32 val); void Tim_OC_Set(u8 TIM_x,u8 OC_x,u8 Mode);
这里还需要注意的是 Led的连接方式,我的led是低电平亮的 ,如果你的Led是高电平点亮,可以设置通道引脚输出极性为高电平有效。 在Tim_OC_Set()函数中可以设置 ,此例中选用TIM3的OC2通道,只需要注释 TIM3 ->CCER |= 1<<5; //引脚输出低电平为有效 这句代码即可。
库函数操作
要输出PWM脉冲 必须要 将io 设置为复用推挽
代码如下: main.c
#include "stm32f10x.h" vu16 CCR1_Val = 60000; vu16 CCR2_Val = 30000; vu16 CCR3_Val = 15000; vu16 CCR4_Val = 7500; void RCC_Configuration(void); void GPIO_Configuration(void); void TIM_Configuration(void); int main(void) { RCC_Configuration(); GPIO_Configuration(); TIM_Configuration(); while(1); } void TIM_Configuration(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; TIM_TimeBaseStructure.TIM_Period = 65535; TIM_TimeBaseStructure.TIM_Prescaler = 7199; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2,&TIM_TimeBaseStructure); //TIM_PrescalerConfig(TIM2,7199,TIM_PSCReloadMode_Immediate); TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //使能TIM输出 TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; TIM_OCInitStructure.TIM_Pulse = CCR1_Val; TIM_OC1Init(TIM2,&TIM_OCInitStructure); TIM_OCInitStructure.TIM_Pulse = CCR2_Val; TIM_OC2Init(TIM2,&TIM_OCInitStructure); TIM_OCInitStructure.TIM_Pulse = CCR3_Val; TIM_OC3Init(TIM2,&TIM_OCInitStructure); TIM_OCInitStructure.TIM_Pulse = CCR4_Val; TIM_OC4Init(TIM2,&TIM_OCInitStructure); TIM_OC1PreloadConfig(TIM2,TIM_OCPreload_Enable); TIM_OC2PreloadConfig(TIM2,TIM_OCPreload_Enable); TIM_OC3PreloadConfig(TIM2,TIM_OCPreload_Enable); TIM_OC4PreloadConfig(TIM2,TIM_OCPreload_Enable); //TIM_ITConfig(TIM2,TIM_IT_CC1|TIM_IT_CC2|TIM_IT_CC3|TIM_IT_CC4,ENABLE); TIM_Cmd(TIM2,ENABLE); } void GPIO_Configuration(void) { GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //设置为复用推挽 GPIO_Init(GPIOA , &GPIO_InitStructure); } void RCC_Configuration(void) { /* 定义枚举类型变量 HSEStartUpStatus */ ErrorStatus HSEStartUpStatus; /* 复位系统时钟设置*/ RCC_DeInit(); /* 开启HSE*/ RCC_HSEConfig(RCC_HSE_ON); /* 等待HSE起振并稳定*/ HSEStartUpStatus = RCC_WaitForHSEStartUp(); /* 判断HSE起是否振成功,是则进入if()内部 */ if(HSEStartUpStatus == SUCCESS) { /* 选择HCLK(AHB)时钟源为SYSCLK 1分频 */ RCC_HCLKConfig(RCC_SYSCLK_Div1); /* 选择PCLK2时钟源为 HCLK(AHB) 1分频 */ RCC_PCLK2Config(RCC_HCLK_Div1); /* 选择PCLK1时钟源为 HCLK(AHB) 2分频 */ RCC_PCLK1Config(RCC_HCLK_Div2); /* 设置FLASH延时周期数为2 */ FLASH_SetLatency(FLASH_Latency_2); /* 使能FLASH预取缓存 */ FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable); /* 选择锁相环(PLL)时钟源为HSE 1分频,倍频数为9,则PLL输出频率为 8MHz * 9 = 72MHz */ RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9); /* 使能PLL */ RCC_PLLCmd(ENABLE); /* 等待PLL输出稳定 */ while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET); /* 选择SYSCLK时钟源为PLL */ RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); /* 等待PLL成为SYSCLK时钟源 */ while(RCC_GetSYSCLKSource() != 0x08); } /* 打开APB2总线上的GPIOA时钟*/ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA , ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2 , ENABLE); }
上一篇:stm32 TIM定时器[操作寄存器+库函数]
下一篇:stm32 窗口看门狗[操作寄存器+库函数]
推荐阅读最新更新时间:2024-03-16 15:32