首言
本文任务:
一. 使用STM32F103的 Tim2~Tim5其一定时器的某一个通道pin(与GPIOx管脚复用,见下图),连接一个LED,用定时器计数方式,控制LED以2s的频率周期性地亮-灭。
二. 接上,采用定时器pwm模式,让 LED 以呼吸灯方式渐亮渐灭,周期为1~2秒,自己调整到一个满意效果。使用Keil虚拟示波器,观察 pwm输出波形。
本文选用的是频率为1kHz,占空比为50%,分辨率为1%的PWM波形,具体公式如下,其中CK_PSC为72M:
本文选择通过更改CCR的值来实现呼吸灯。
一、TIM定时中断
STM32的TIM中断是一种强大的定时器和中断处理功能,广泛用于STM32微控制器中。它能够实现高精度的定时操作,同时提供了灵活的中断处理机制,使得开发人员可以更好地控制和优化系统性能。
首先,STM32的TIM中断通过配置TIM的周期和预分频值,可以生成毫秒级别的定时。这种定时功能非常适合于需要精确控制时间间隔的应用,比如定时器、计数器、时间戳等。同时,TIM还支持向上计数、向下计数、中央对齐等模式,可以根据不同的需求进行选择。
其次,STM32的TIM中断具有捕获功能,可以用于测量输入信号的频率和占空比。这种功能在许多应用中都非常重要,比如马达控制、脉冲宽度调制(PWM)控制等。通过捕获输入信号的边缘,TIM可以计算输入信号的频率和占空比,从而实现对系统的精确控制。
此外,STM32的TIM中断还具有多种触发方式,包括软件触发、外部触发、定时器触发等。这使得开发人员可以根据实际需求选择最适合的触发方式,比如在需要快速响应的事件中可以使用软件触发,而在需要与外部设备同步的情况下可以使用外部触发。
最后,STM32的TIM中断具有优先级可设置的功能,可以根据不同的需求设置不同的中断优先级。这种功能在实时应用中非常重要,比如在同时处理多个中断的情况下,可以通过设置优先级来保证最重要的任务得到优先处理。一般来说,TIM定时中断基本配置步骤为:
1.配置RCC时钟使能。
2.选择时钟源。
3.配置时基单元
4. 配置中断输出控制。
5.配置NVIC6.使能计数器
二、配置PWM
PWM(Pulse Width Modulation)脉冲宽度调制是一种在具有惯性的系统中常用的控制方法。通过调制一系列脉冲的宽度,可以等效地获得所需要的模拟参量,广泛应用于电机控速等领域。
PWM的参数包括频率、占空比和分辨率。其中,频率是指单位时间内脉冲的个数,通常用f表示,单位为Hz。占空比是指在一个脉冲周期内高电平持续的时间所占的比例,用Ton表示,单位为s。而分辨率则是指占空比变化的步距,表示PWM信号对模拟参量的控制精度。
通过对PWM参数的调节,可以实现精确的电机速度控制、灯光亮度调节等功能。PWM的应用范围非常广泛,如工业控制、电力电子、智能家居等领域都有PWM的应用。同时,PWM信号的生成和控制可以通过微控制器、DSP、FPGA等硬件实现,也可以通过软件算法实现。
上图是PWM的基本结构图,由图能够很直观的看出配置PWM的具体流程:
(1)RCC开启时钟
这里包括TIM外设和GPIO外设的时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
(2)配置时基单元
在这里主要是通过TIM定时中断来进行ARR和PSC的配置,具体如下:
TIM_InternalClockConfig(TIM2);//中断输出使能
//配置TIM定时中断
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period=100-1;//周期ARR
TIM_TimeBaseInitStructure.TIM_Prescaler=720-1;//预分频器PSC
TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
(3)配置输出比较单元
这里包括CCR值、输出比较模式、极性选择、输出使能。
void TIM_OC1Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC2Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC3Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC4Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
以上有四个用结构体来初始化输出比较单元的函数 ,对应四个输出比较单元,但是,因为本文选用的是PA0口,由此选择第一个输出比较单元的函数进行初始化。具体代码如下:
因为对于本文的呼吸灯来说,不需要配置TIM_OC1Init中所有的值,因此,需要借助结构体来进行赋值,结构体赋值的函数如下所示:
void TIM_OCStructInit(TIM_OCInitTypeDef* TIM_OCInitStruct);
下面四个函数就是用来单独更改CCR值,从而更改占空比:
void TIM_SetCompare1(TIM_TypeDef* TIMx, uint16_t Compare1);
void TIM_SetCompare2(TIM_TypeDef* TIMx, uint16_t Compare2);
void TIM_SetCompare3(TIM_TypeDef* TIMx, uint16_t Compare3);
void TIM_SetCompare4(TIM_TypeDef* TIMx, uint16_t Compare4);
最终配置的代码为:
//初始化输出比较单元
TIM_OCInitTypeDef TIM_OC1InitStructure;
TIM_OCStructInit(&TIM_OC1InitStructure);
TIM_OC1InitStructure.TIM_OCMode=TIM_OCMode_PWM1;//设置输出比较的模式
TIM_OC1InitStructure.TIM_OCPolarity =TIM_OCPolarity_High;//设置输出比较极性,有效电平为高电平
TIM_OC1InitStructure.TIM_OutputState =TIM_OutputState_Enable;//设置输出使能
TIM_OC1InitStructure.TIM_Pulse =50;//设置CCR
TIM_OC1Init(TIM2,&TIM_OC1InitStructure);
(4)配置GPIO
因为想要用定时器来控制引脚,这里PWM所对应的GPIO口应该设置为复用推挽输出模式,引脚的控制权才能交给片上外设。PWM波形才能通过引脚输出完整配置代码如下所示:
//开启时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
//初始化GPIO口
GPIO_InitTypeDef GPIOA_Initleds;
GPIOA_Initleds.GPIO_Mode =GPIO_Mode_AF_PP;//推挽输出模式
GPIOA_Initleds.GPIO_Pin =GPIO_Pin_0;//输出引脚为PA0-15全部作为输出端口
GPIOA_Initleds.GPIO_Speed =GPIO_Speed_50MHz;//输出速率为50MHz
GPIO_Init (GPIOA,&GPIOA_Initleds);//&是取地址的意思
本文需要实现一个呼吸灯,选择通过改变CCR的值来改变灯的亮度,那么这里就需要借助TIM _SetComparel 函数,该函数是专门用来通道1的CCR值的。
(5)打开运行控制,启动计数器
用TIM_Cmd使能TIM2即可,如下:
TIM_Cmd(TIM2,ENABLE);
本文将PWM的初始化和LED的初始化封装成了两个头文件
三、程序编写
任务二:实现一个呼吸灯,选择通过改变CCR的值来改变灯的亮度,那么这里就需要借助TIM _SetComparel 函数,该函数是专门用来通道1的CCR值的。因此之前设置的CCR值不需要了,可以先写个零,本文建立了一个更改CCR值的函数,具体代码如下所示:
void PWM_SetCompare1(uint16_t RECCR)
{
TIM_SetCompare1(TIM2,RECCR);
}
接下来只需要在主函数中调用该函数,赋予不同的RECCR值就可以实现灯亮度的变化。
所以,综上编写主函数程序:
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "LED.h"
#include "PWM.h"
uint8_t i=0;
uint8_t j=0;
int main(void)
{
LED_Init();
PWM_Init();
while (1)
{
if(j==0)
{
for(i=0;i<=100;i++)
{
PWM_SetCompare1(i);
Delay_ms(10);
}
j=100;
}
if(i==101)
{
for(j=100;j>0;j--)
{
PWM_SetCompare1(j);
Delay_ms(10);
}
i=0;
}
}
}
PWM的初始化函数以及更改CCR函数为,并且封装成了PWM.h头文件:
#include "Device/Include/stm32f10x.h" // Device header
void PWM_Init(void)
{
//初始化RCC
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
TIM_InternalClockConfig(TIM2);
//配置TIM定时中断
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period=100-1;//周期ARR
TIM_TimeBaseInitStructure.TIM_Prescaler=720-1;//预分频器PSC
TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
//初始化输出比较单元
TIM_OCInitTypeDef TIM_OC1InitStructure;
TIM_OCStructInit(&TIM_OC1InitStructure);
TIM_OC1InitStructure.TIM_OCMode=TIM_OCMode_PWM1;//设置输出比较的模式
TIM_OC1InitStructure.TIM_OCPolarity =TIM_OCPolarity_High;//设置输出比较极性,有效电平为高电平
TIM_OC1InitStructure.TIM_OutputState =TIM_OutputState_Enable;//设置输出使能
TIM_OC1InitStructure.TIM_Pulse =0;//设置CCR
TIM_OC1Init(TIM2,&TIM_OC1InitStructure);
TIM_Cmd(TIM2,ENABLE);
}
void PWM_SetCompare1(uint16_t RECCR)
{
TIM_SetCompare1(TIM2,RECCR);
}
GPIO的初始化函数如下所示,并且封装成了LED.h的头文件:
#include "Device/Include/stm32f10x.h" // Device header
void LED_Init(void)
{
//开启时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
//初始化GPIO口
GPIO_InitTypeDef GPIOA_Initleds;
GPIOA_Initleds.GPIO_Mode =GPIO_Mode_AF_PP;//推挽输出模式
GPIOA_Initleds.GPIO_Pin =GPIO_Pin_0;//输出引脚为PA0-15全部作为输出端口
GPIOA_Initleds.GPIO_Speed =GPIO_Speed_50MHz;//输出速率为50MHz
GPIO_Init (GPIOA,&GPIOA_Initleds);//&是取地址的意思
}
那么在任务二的基础上,我们可以借用同样的函数,只需要将void PWM_SetCompare1(uint16_t RECCR);中的RECCR给一个定值即可,那么主函数的程序如下:
int main(void)
{
LED_Init();
PWM_Init();
while (1)
{
PWM_SetCompare1(0);
Delay_s(2);
PWM_SetCompare1(100);
Delay_s(2);
}
}
四、结果展示
任务一:
由图可以看出来高低电平持续时间均为两秒
任务二:
五、总结
通过这次的实验,我大致了解了STM32中的定时中断功能,明白了几种基本的定时中断模式,了解了通用定时器中的输出比较功能,通过设置频率,占空比以及分辨率来实现类模拟信号,再通过PA0引脚复用输出到端口,从而实现LED呼吸灯。虽然说标准库的操作很繁琐,但是逻辑步骤还是十分清晰的,也加强了我编程检查错误避免错误的能力。
六、参考
(1)https://www.bilibili.com/video/BV1th411z7sn?p=16&vd_source=6b998429e4d25a33748fad4477a9a498