参考资料:
蓝桥杯嵌入式备赛手册
蓝桥杯嵌入式开发经验分享(9.PWM模式配置)
蓝桥杯嵌入式开发经验分享(11.输入捕获模式讲解)
STM32F1 定时器 PWM输入捕获两路
总目录:目录
文章目录
PWM简介
- 脉冲宽度调制(Pulse width modulation)
- 脉冲宽度调制是一种模拟控制方式,根据相应载荷的变化来调制晶体管基极或MOS管栅极的偏置,来实现晶体管或MOS管导通时间的改变,从而实现开关稳压电源输出的改变
- 基本原理:控制方式就是对逆变电路开关器件的通断进行控制,使输出端得到一系列幅值相等的脉冲,用这些脉冲来代替正弦波或所需要的波形。也就是在输出波形的半个周期中产生多个脉冲,使各脉冲的等值电压为正弦波形,所获得的输出平滑且低次谐波少。按一定的规则对各脉冲的宽度进行调制,既可改变逆变电路输出电压的大小,也可改变输出频率
中文STM32手册中8.3.7 定时器复用功能重映射
TIM2,TIM3是一样的,都是通用定时器,掌握其中一个,那么另外一个也是相同的配置,只不过是对应的复用引脚不同
OC之固定频率、可变占空比
pwm.c的编写
- 我们首先要捋清楚,PWM都用了什么,那么初始化里面就要有哪些模块的东西
- 首先是上面说的定时器,TIM2和TIM3都可以
- 回顾之前的TIM的配置,我们主要用其基础配置
- 其参考例程在Project ->STM32F10x_StdPeriph_Examples->TIM->TimeBase
- 为了灵活改变其占空比,我们需要改变时钟的计数值和分频系数,其中
arr=1000000/freq - 1
- 这个频率是用户要求的,因此在初始化函数的参数中加上
u32 freq
- 关于设置arr的原理
- 以及,TIM中还需要配置NVIC,直接复制过来修改一下优先级即可
- 因为现在没什么能看PWM,所以借助TIM的操作函数进行点灯,同一目录下stm32f10x_it.c中有
void TIM2_IRQHandler(void)
- 在此函数中,直接写管脚
u8 status = 0; u16 cnt = 0; void TIM2_IRQHandler(void) { if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) { TIM_ClearITPendingBit(TIM2, TIM_IT_Update); if(cnt++ == 500) { cnt = 0; led_Control(LED_ALL, status); status = !status; } } }
- 同时,在初始化函数中,也要加上GPIO的初始化
- 具体配置可以参考之前的博客(链接),和其中led.c中初始化函数一致
- 最后,就是本次的PWM的配置
- 参考例程:Project->STM32F10x_StdPeriph_Examples->TIM->PWM_Output
- 这里,我们要把脉冲设置为我们的计算值
(arr+1)*duty/100
- 综上,pwm.c为
#include "pwm.h" #include "led.h" void PWM_OC_Init(u32 freq,u8 duty) { GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; NVIC_InitTypeDef NVIC_InitStructure; u32 arr = 1000000 / freq - 1; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD|RCC_APB2Periph_GPIOC, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_2; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOC, &GPIO_InitStructure); TIM_TimeBaseStructure.TIM_Period = arr; TIM_TimeBaseStructure.TIM_Prescaler = 71; TIM_TimeBaseStructure.TIM_ClockDivision = 0; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE ); TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse = (arr+1)*duty/100; TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; TIM_OC2Init(TIM2, &TIM_OCInitStructure); TIM_OC2PreloadConfig(TIM2, TIM_OCPreload_Enable); TIM_ARRPreloadConfig(TIM2, ENABLE); TIM_Cmd(TIM2, ENABLE); NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); } u8 status = 0; u16 cnt = 0; void TIM2_IRQHandler(void) { if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) { TIM_ClearITPendingBit(TIM2, TIM_IT_Update); //if(cnt++ == 500) //{ //cnt = 0; //led_Control(LED_ALL, status); //status = !status; //} GPIO_SetBits(GPIOD, GPIO_Pin_2); GPIO_ResetBits(GPIOD, GPIO_Pin_2); } }
main.c的编写
- 用TIM_SetCompareX(TIMX,Pulse)改变占空比
- 这里原本是想通过PWM改变灯的亮度,然而并不明显……
- 因此,主函数为:
int main(void) { u16 LedPWMVal = 0, dir = 1; SysTick_Config(SystemCoreClock/1000); NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); led_Init(); PWM_OC_Init(500, 60); while(1) { if(dir) LedPWMVal++; else LedPWMVal--; if(LedPWMVal>665) dir = 0; if(LedPWMVal == 0) dir = 1; TIM_SetCompare3(TIM2, LedPWMVal); Delay_Ms(10); } }
别忘了吧stm32f10x_tim.c加到工程中再编译
OC之可变频率、可变占空比
输出两路PWM
这里我们设PA6频率为10KHz,占空比为10%,PA7频率为1KHz,占空比为20%
-
参考例程:Project->STM32F10x_StdPeriph_Examples->TIM->OCToggle中
main.c
-
包含:
-
全局变量定义
-
TIM和TIMOC初始化
-
时钟使能
-
GPIO初始化
-
中断初始化
-
PWM的初始化完成
-
下面是中断服务函数,在同一目录下打开stm32f10x_it.c
-
自己定义占空比变量分别执行下列函数
TIM_SetCompare1(TIM3, capture + (u16)CCR1_Val *zhankong1 ); TIM_SetCompare1(TIM3, capture + (u16)CCR1_Val *(1-zhankong1));
-
其中,
CCRx_Val=频率为72M/72/频率
-
综上,pwm.c如下
#include "pwm.h" __IO uint16_t CCR1_Val = 100;//72000000/72/10000 __IO uint16_t CCR2_Val = 1000;//72000000/72/1000 float zhankong1=0.1; float zhankong2=1; void PWM_OC_Init() { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; GPIO_InitTypeDef GPIO_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; /* TIM3 clock enable */ RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); /* GPIOA clock enable */ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO, ENABLE); TIM_TimeBaseStructure.TIM_Period = 65535; TIM_TimeBaseStructure.TIM_Prescaler = 71; TIM_TimeBaseStructure.TIM_ClockDivision = 0; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM3, &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_Low; TIM_OC1Init(TIM3, &TIM_OCInitStructure); TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Disable); TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse = CCR2_Val; TIM_OC2Init(TIM3, &TIM_OCInitStructure); TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Disable); TIM_Cmd(TIM3, ENABLE); TIM_ITConfig(TIM3, TIM_IT_CC1 | TIM_IT_CC2, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); } uint16_t capture = 0; u8 pa6_state=0,pa7_state=0; void TIM3_IRQHandler(void) { if (TIM_GetITStatus(TIM3, TIM_IT_CC1) != RESET) { TIM_ClearITPendingBit(TIM3, TIM_IT_CC1 ); capture = TIM_GetCapture1(TIM3); if(pa6_state == 0) { TIM_SetCompare1(TIM3, capture + (u16)CCR1_Val * zhankong1 ); pa6_state = 1; } else { TIM_SetCompare1(TIM3, capture + (u16)CCR1_Val * (1 - zhankong1)); pa6_state = 0; } } if (TIM_GetITStatus(TIM3, TIM_IT_CC2) != RESET) { TIM_ClearITPendingBit(TIM3, TIM_IT_CC2); capture = TIM_GetCapture2(TIM3); if(pa7_state == 0) { TIM_SetCompare2(TIM3, capture + (u16)CCR2_Val * zhankong2 ); pa7_state = 1; } else { TIM_SetCompare2(TIM3, capture + (u16)CCR2_Val * (1 - zhankong2)); pa7_state = 0; } } }
按键调节占空比或频率
假设KEY1使PA6的占空比增加10%,KEY2使PA7的频率增加1KHz
- 我们只需增加按键中断处理函数即可,其参考例程在:Project->STM32F10x_StdPeriph_Examples->EXTI->EXTI_Config下的是
stmf10x_it.c
- 在操作函数中修改
zhankongx
或CCRx_Val
即可void EXTI0_IRQHandler(void) { Delay_Ms(10); if(EXTI_GetITStatus(EXTI_Line0) != RESET) { zhankong1 += 0.1; EXTI_ClearITPendingBit(EXTI_Line0); } } u16 pinlv2=1000; void EXTI9_5_IRQHandler(void) { Delay_Ms(10); if(EXTI_GetITStatus(EXTI_Line8) != RESET) { pinlv2+=1000; CCR2_Val=(u16)(1000000/pinlv2);//72MHz/72 EXTI_ClearITPendingBit(EXTI_Line8); } }
IC之一路PWM的频率和占空比
- 关于PWM输入模式的介绍
- 参考例程:Project->STM32F10x_StdPeriph_Examples->TIM->PWM_Input中
main.c
- 首先是TIM_IC的初始化:
- 时钟使能
- GPIO初始化
- 中断NVIC初始化
- 同目录下stm32f10x_it.c
- 全局变量的定义,其中DutuCycle为占空比,Frequency为频率
- 中断处理函数
- 根据题目要求灵活改变引脚和时钟通道即可
- 综上,pwm.c为:
#include "pwm.h" void PWM_IC_Init() { TIM_ICInitTypeDef TIM_ICInitStructure; GPIO_InitTypeDef GPIO_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; /* TIM3 clock enable */ RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); /* GPIOA clock enable */ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); TIM_ICInitStructure.TIM_Channel = TIM_Channel_2; TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; TIM_ICInitStructure.TIM_ICFilter = 0x0; TIM_PWMIConfig(TIM3, &TIM_ICInitStructure); /* Select the TIM3 Input Trigger: TI2FP2 */ TIM_SelectInputTrigger(TIM3, TIM_TS_TI2FP2); /* Select the slave Mode: Reset Mode */ TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset); /* Enable the Master/Slave Mode */ TIM_SelectMasterSlaveMode(TIM3, TIM_MasterSlaveMode_Enable); /* TIM enable counter */ TIM_Cmd(TIM3, ENABLE); /* Enable the CC2 Interrupt Request */ TIM_ITConfig(TIM3, TIM_IT_CC2, ENABLE); } __IO uint16_t IC2Value = 0; __IO uint16_t DutyCycle = 0; __IO uint32_t Frequency = 0; void TIM3_IRQHandler(void) { /* Clear TIM3 Capture compare interrupt pending bit */ TIM_ClearITPendingBit(TIM3, TIM_IT_CC2); /* Get the Input Capture value */ IC2Value = TIM_GetCapture2(TIM3); if (IC2Value != 0) { /* Duty cycle computation */ DutyCycle = (TIM_GetCapture1(TIM3) * 100) / IC2Value; /* Frequency computation */ Frequency = SystemCoreClock / IC2Value; } else { DutyCycle = 0; Frequency = 0; } }
- 为了直观显示,我们在LCD屏上直接显示占空比和频率,main.c代码如下:
#include "stm32f10x.h" #include "lcd.h" #include "pwm.h" #include "stdio.h" u32 TimingDelay = 0; extern __IO uint16_t IC2Value; extern __IO uint16_t DutyCycle; extern __IO uint32_t Frequency; void Delay_Ms(u32 nTime); //Main Body int main(void) { u8 pwm[20]; SysTick_Config(SystemCoreClock/1000); STM3210B_LCD_Init(); PWM_IC_Init(); LCD_Clear(Blue); LCD_SetTextColor(Black); LCD_SetBackColor(White); while(1) { sprintf((char*)pwm, "DutyCycle : %d", DutyCycle); LCD_DisplayStringLine(Line0,pwm); sprintf((char*)pwm, "Frequency : %d", Frequency); LCD_DisplayStringLine(Line2,pwm); Delay_Ms(10); } } // void Delay_Ms(u32 nTime) { TimingDelay = nTime; while(TimingDelay != 0); }
IC之两路PWM的频率和占空比
- 原理图如下:(来自蓝桥杯嵌入式备赛手册)
- 参考例程:Project->STM32F10x_StdPeriph_Examples->TIM->InputCapture中
main.c
- 时钟捕获初始化,这里由于是要捕获两路PWM,因此需要加上通道1以及中断(即
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1|TIM_Channel_2;
)以及(TIM_ITConfig(TIM3, TIM_IT_CC1|TIM_IT_CC2, ENABLE);
)
- 时钟信号
- GPIO初始化
- NVIC初始化
- 同目录下
stm32f10x_it.c
- 全局变量定义
- 中断处理函数,若没要求占空比的话,例程可以直接用
- 若是考虑占空比的计算以及两路PWM,那这里的例程和全局变量仅能做一个参考,需要我们自己完善
__IO uint16_t IC3ReadValue1 = 0, IC3ReadValue2 = 0;//
__IO uint16_t DutyCycleValue1 = 0,DutyCycleValue2 = 0;
__IO uint16_t CaptureNumber = 0,CaptureNumber_DC = 0;//捕获计数
__IO uint32_t Capture = 0,Capture_DC = 0;//捕获时间,DC表示下降沿捕获时间
__IO uint32_t TIM3Freq = 0,TIM3DutyCycle = 0;//频率和占空比
__IO uint16_t _IC3ReadValue1 = 0, _IC3ReadValue2 = 0;
__IO uint16_t _DutyCycleValue1 = 0,_DutyCycleValue2 = 0;
__IO uint16_t _CaptureNumber = 0,_CaptureNumber_DC = 0;
__IO uint32_t _Capture = 0,_Capture_DC = 0;
__IO uint32_t _TIM3Freq = 0,_TIM3DutyCycle = 0;
void TIM3_IRQHandler(void)
{
//采用定时器溢出中断,不管定时器频率如何变化,总是会出现定时器溢出中断更新不及时的情况,即计数值已重新技术到比0大的值,但定时器溢出中断未产生
if(TIM_GetITStatus(TIM3, TIM_IT_CC1) == SET) //捕获1发生捕获事件
{
TIM_ClearITPendingBit(TIM3, TIM_IT_CC1);//清楚中断标志位
if(_CaptureNumber_DC == 0)
{
//捕获到上升沿,计算时间
_DutyCycleValue1 = TIM_GetCapture1(TIM3);
if(_CaptureNumber == 0)
{
_IC3ReadValue1 = _DutyCycleValue1;
_CaptureNumber = 1;
}
else if(_CaptureNumber == 1)
{
_IC3ReadValue2 = _DutyCycleValue1;
if (_IC3ReadValue2 > _IC3ReadValue1)
_Capture = (_IC3ReadValue2 - _IC3ReadValue1);
else
_Capture = ((0xFFFF - _IC3ReadValue1) + _IC3ReadValue2);
_TIM3Freq = (uint32_t) SystemCoreClock / _Capture;
_CaptureNumber = 0;
}
TIM_OC1PolarityConfig(TIM3, TIM_ICPolarity_Falling);
_CaptureNumber_DC = 1;
}
else if(_CaptureNumber_DC==1)
{
_DutyCycleValue2 = TIM_GetCapture1(TIM3);
if (_DutyCycleValue2 > _DutyCycleValue1)
_Capture_DC = (_DutyCycleValue2 - _DutyCycleValue1);
else
_Capture_DC = ((0xFFFF - _DutyCycleValue1) + _DutyCycleValue2);
//计算占空比=下降沿捕获时间/总捕获时间
_TIM3DutyCycle = _Capture_DC/_Capture;
//设置为上升沿捕获
TIM_OC1PolarityConfig(TIM3, TIM_ICPolarity_Rising);
_CaptureNumber_DC = 0;
}
}
/*Initialize TIM3 CC2 IT*/
if(TIM_GetITStatus(TIM3, TIM_IT_CC2) == SET)
{
TIM_ClearITPendingBit(TIM3, TIM_IT_CC2);
if(CaptureNumber_DC == 0)
{
DutyCycleValue1 = TIM_GetCapture2(TIM3);
if(CaptureNumber == 0)
{
IC3ReadValue1 = DutyCycleValue1;
CaptureNumber = 1;
}
else if(CaptureNumber == 1)
{
IC3ReadValue2 = DutyCycleValue1;
if (IC3ReadValue2 > IC3ReadValue1)
Capture = (IC3ReadValue2 - IC3ReadValue1);
else
Capture = ((0xFFFF - IC3ReadValue1) + IC3ReadValue2);
TIM3Freq = (uint32_t) SystemCoreClock / Capture;
CaptureNumber = 0;
}
TIM_OC2PolarityConfig(TIM3, TIM_ICPolarity_Falling);
CaptureNumber_DC = 1;
}
else if(CaptureNumber_DC==1)
{
DutyCycleValue2 = TIM_GetCapture2(TIM3);
if (DutyCycleValue2 > DutyCycleValue1)
Capture_DC = (DutyCycleValue2 - DutyCycleValue1);
else
Capture_DC = ((0xFFFF - DutyCycleValue1) + DutyCycleValue2);
TIM3DutyCycle = Capture_DC/Capture;
TIM_OC2PolarityConfig(TIM3, TIM_ICPolarity_Rising);
CaptureNumber_DC = 0;
}
}
}
- main.c如下:
int main(void)
{
u8 pwm[20];
SysTick_Config(SystemCoreClock/1000);
STM3210B_LCD_Init();
LCD_Clear(Blue);
LCD_SetBackColor(Blue);
LCD_SetTextColor(White);
Pwm_Ic_Init();
while(1)
{
LCD_DisplayStringLine(Line1,pwm);
LCD_DisplayStringLine(Line3,pwm);
Delay_Ms(10);
}
}
单路输出利用主从模式,占用另一个通道的CCRX获得高电平时间。
双路输出不能用主从模式,且需要调节上升/下降沿触发。注意要配置TIM_TimeBaseStructure;