目录
1. stm32中定时器的常见应用场景
在STM32中,定时器是一个非常重要的功能模块,广泛应用于各种场景。以下是一些常见的应用场景:
定时器中断: 使用定时器中断实现周期性的任务调度,例如定时采集传感器数据、控制LED闪烁等。
PWM输出: 利用定时器的输出比较功能生成PWM信号,用于控制电机速度、调节LED亮度等。
输入捕获: 利用定时器的输入捕获功能测量外部事件的时间间隔,例如测量脉冲宽度、计算脉冲频率等。
编码器模式: 利用定时器的输入捕获功能实现编码器的读取,用于监测旋转或位置变化。在编码器模式下,定时器捕获外部信号的边沿,并记录捕获事件的时间戳,以计算旋转方向和速度。
定时器作为时钟源: 将定时器作为系统时钟源,用于生成精确的定时时钟信号,例如用于时序控制、通信协议等。
2. 定时器中断
在STM32中,定时器中断是一种常见的应用方式,它可以用于实现周期性的任务调度和时间相关的操作。通过配置定时器的计数周期和预分频器,可以实现不同的定时时间。当定时器计数器达到设定的值时,将触发定时器中断,执行相应的中断服务程序。在定时器中断服务程序中,可以执行各种任务,如采集传感器数据。
定时器中断的使用步骤通常包括以下几个方面:
- 选择合适的定时器: 以STM32f103c8t6最小系统为例,该控制器共有TIM1、TIM2、TIM3、TIM4四个定时器,且TIM1为高级定时器。在启动文件中我们可以看到,如图所示:
- 配置定时器: 设置定时器的计数周期和预分频器,以及使能定时器中断。这些参数的设置将影响定时器中断的触发频率和精度。
void TIMX_Int_Init(uint32_t ARM_RCC,TIM_TypeDef* TIMX,u16 arr,u16 psc,uint8_t IT_flag,IRQn_Type IRQn, uint8_t EN_flag) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; NVIC_InitTypeDef NVIC_InitStructure; //开定时器时钟 if(TIMX == TIM1) { RCC_APB2PeriphClockCmd(ARM_RCC,ENABLE); } else RCC_APB1PeriphClockCmd(ARM_RCC,ENABLE); //定时器TIM初始化 TIM_TimeBaseStructure.TIM_Period = arr; //设置重装载值 TIM_TimeBaseStructure.TIM_Prescaler = psc;//设置预分频值 TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;//重复计数设置(高级定时器) TIM_TimeBaseInit(TIMX, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位 //如果标志位为1,则开启中断 if(IT_flag) { //使能中断,允许更新中断 TIM_ITConfig(TIMX,TIM_IT_Update,ENABLE); TIM_ClearFlag(TIMX, TIM_FLAG_Update);//清中断标志位 //中断优先级NVIC设置 NVIC_InitStructure.NVIC_IRQChannel = IRQn; //TIMx中断 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //先占优先级0级 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //从优先级3级 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能 NVIC_Init(&NVIC_InitStructure); //初始化NVIC寄存器 if(EN_flag) TIM_Cmd(TIMX, ENABLE); //使能TIMx } }
这是我自己编写的定时器初始化函数,将库函数中的定时器配置封装在一起,方便后续的调用
TIMX_Int_Init(RCC_APB2Periph_TIM1,TIM1,4999,7199,1,TIM1_UP_IRQn,0);//定时器1初始化 TIMX_Int_Init(RCC_APB1Periph_TIM2,TIM2,4999,7199,1,TIM2_IRQn,0);//定时器2初始化 TIMX_Int_Init(RCC_APB1Periph_TIM3,TIM3,4999,7199,1,TIM3_IRQn,0);//定时器3初始化 TIMX_Int_Init(RCC_APB1Periph_TIM4,TIM4,4999,7199,1,TIM4_IRQn,0);//定时器4初始化
配置定时中断时间的函数
void TIMX_Update_Time(TIM_TypeDef* TIMX, uint8_t time, uint32_t TIMX_hz) { uint32_t timer_freq = SystemCoreClock; // 72M // 计算预分频器值和自动重载值 uint16_t psc_value = (timer_freq/TIMX_hz) - 1; //设置定时器频率 uint16_t arr_value = (uint16_t)(time * timer_freq / (psc_value + 1))- 1;//设置溢出次数 TIM_Cmd(TIMX, DISABLE); //停止定时器 // 更新定时器的预分频器值和自动重载值 //TIMX->ARR = 9999;(1秒/10kHz) //TIMX->PSC = 7199; TIMX->ARR = arr_value; TIMX->PSC = psc_value; TIM_ClearFlag(TIMX, TIM_FLAG_Update); //清除定时器中断标志 TIM_Cmd(TIMX, ENABLE);//重启定时器 }
TIMX_Update_Time(TIM1,1,10000); //设置定时器1,定时器频率10K,1s触发更新中断 TIMX_Update_Time(TIM2,2,8000); //设置定时器2,定时器频率8K,2s触发更新中断 TIMX_Update_Time(TIM3,3,6000); //设置定时器3,定时器频率6K,3s触发更新中断 TIMX_Update_Time(TIM4,4,4000); //设置定时器4,定时器频率4K,4s触发更新中断
也可以根据自己的需求去更改参数,方便配置
- 最后编写中断服务程序: 编写定时器中断服务程序,用于处理定时器中断事件。在中断服务程序中,可以执行需要周期性执行的任务,如数据处理、状态更新等。
//定时器1中断服务程序
void TIM1_UP_IRQHandler(void)//TIM1更新中断
{
if (TIM_GetITStatus(TIM1,TIM_IT_Update) != RESET) //检查TIM1更新中断发生与否
{
TIM_ClearITPendingBit(TIM1,TIM_IT_Update); //清除TIMx更新中断标志
//进入中断后执行的程序
gpio_out_turn(GPIOA,GPIO_Pin_3);
}
}
//定时器2中断服务程序
void TIM2_IRQHandler(void)//TIM2中断
{
if (TIM_GetITStatus(TIM2,TIM_IT_Update) != RESET) //检查TIM2更新中断发生与否
{
TIM_ClearITPendingBit(TIM2,TIM_IT_Update); //清除TIMx更新中断标志
//进入中断后执行的程序
gpio_out_turn(GPIOA,GPIO_Pin_2);
}
}
//定时器3中断服务程序
void TIM3_IRQHandler(void)//TIM3中断
{
if (TIM_GetITStatus(TIM3,TIM_IT_Update) != RESET) //检查TIM3更新中断发生与否
{
TIM_ClearITPendingBit(TIM3,TIM_IT_Update); //清除TIMx更新中断标志
//进入中断后执行的程序
gpio_out_turn(GPIOA,GPIO_Pin_1);
}
}
//定时器4中断服务程序
void TIM4_IRQHandler(void)//TIM4中断
{
if (TIM_GetITStatus(TIM4,TIM_IT_Update) != RESET) //检查TIM4更新中断发生与否
{
TIM_ClearITPendingBit(TIM4,TIM_IT_Update); //清除TIMx更新中断标志
//进入中断后执行的程序
gpio_out_turn(GPIOA,GPIO_Pin_0);
}
}
通过上述函数调用可以很快的打开stm32f103c8t6最小系统的4个定时器,并通过更改定时器中断的时间能够更好的满足代码实现逻辑,但要注意定时时间的问题,如果定时时间需要比较长,psc需要大一些,减少定时器频率,防止arr16位寄存器溢出!!
3. 输出比较
输出比较是定时器的一种常见功能,它可以生成周期性的PWM(脉冲宽度调制)信号。PWM信号在许多应用中都非常有用,例如控制电机速度、调节LED亮度、产生音频信号等。输出比较功能通过比较定时器的计数值和预设的比较值来确定输出信号的状态(高电平或低电平),从而实现PWM波形的生成。
使用输出比较功能的步骤如下:
- 选择合适的定时器: 根据需要的PWM频率和精度,选择合适的定时器。通常,定时器的计数周期会决定PWM信号的周期,而比较值则决定了PWM信号的占空比。
- 配置定时器: 设置定时器的计数周期和预分频器,以及比较值。这些参数将决定生成的PWM信号的周期和占空比。
占空比Duty = CCR / (ARR + 1)TIMX_Int_Init(RCC_APB2Periph_TIM1,TIMX,arr,psc,0,IRQn,0);}//时基单元初始化 TIMX_Int_Init(RCC_APB1Periph_TIM2,TIMX,arr,psc,0,IRQn,0);}//时基单元初始化 TIMX_Int_Init(RCC_APB1Periph_TIM3,TIMX,arr,psc,0,IRQn,0);}//时基单元初始化 TIMX_Int_Init(RCC_APB1Periph_TIM4,TIMX,arr,psc,0,IRQn,0);}//时基单元初始化
- 使能输出比较功能: 启用定时器的输出比较功能,并选择合适的输出比较模式。输出比较模式可以根据需求选择单边比较模式或双边比较模式,以及输出模式(例如PWM模式1、PWM模式2等)。
void TIMX_OC_Init(TIM_TypeDef* TIMX,uint8_t CHX,u16 arr,u16 psc,IRQn_Type IRQn) { if(TIMX == TIM1){ if(CHX == 1) gpio_init(RCC_APB2Periph_GPIOA,GPIO_Mode_AF_PP,GPIOA,GPIO_Pin_8); if(CHX == 2) gpio_init(RCC_APB2Periph_GPIOA,GPIO_Mode_AF_PP,GPIOA,GPIO_Pin_9); if(CHX == 3) gpio_init(RCC_APB2Periph_GPIOA,GPIO_Mode_AF_PP,GPIOA,GPIO_Pin_10); if(CHX == 4) gpio_init(RCC_APB2Periph_GPIOA,GPIO_Mode_AF_PP,GPIOA,GPIO_Pin_11); TIMX_Int_Init(RCC_APB2Periph_TIM1,TIMX,arr,psc,0,IRQn,0);}//时基单元初始化 if(TIMX == TIM2){ if(CHX == 1) gpio_init(RCC_APB2Periph_GPIOA,GPIO_Mode_AF_PP,GPIOA,GPIO_Pin_0); if(CHX == 2) gpio_init(RCC_APB2Periph_GPIOA,GPIO_Mode_AF_PP,GPIOA,GPIO_Pin_1); if(CHX == 3) gpio_init(RCC_APB2Periph_GPIOA,GPIO_Mode_AF_PP,GPIOA,GPIO_Pin_2); if(CHX == 4) gpio_init(RCC_APB2Periph_GPIOA,GPIO_Mode_AF_PP,GPIOA,GPIO_Pin_3); TIMX_Int_Init(RCC_APB1Periph_TIM2,TIMX,arr,psc,0,IRQn,0);}//时基单元初始化 if(TIMX == TIM3){ if(CHX == 1) gpio_init(RCC_APB2Periph_GPIOA,GPIO_Mode_AF_PP,GPIOA,GPIO_Pin_6); if(CHX == 2) gpio_init(RCC_APB2Periph_GPIOA,GPIO_Mode_AF_PP,GPIOA,GPIO_Pin_7); if(CHX == 3) gpio_init(RCC_APB2Periph_GPIOB,GPIO_Mode_AF_PP,GPIOB,GPIO_Pin_0); if(CHX == 4) gpio_init(RCC_APB2Periph_GPIOB,GPIO_Mode_AF_PP,GPIOB,GPIO_Pin_1); TIMX_Int_Init(RCC_APB1Periph_TIM3,TIMX,arr,psc,0,IRQn,0);}//时基单元初始化 if(TIMX == TIM4){ if(CHX == 1) gpio_init(RCC_APB2Periph_GPIOB,GPIO_Mode_AF_PP,GPIOB,GPIO_Pin_6); if(CHX == 2) gpio_init(RCC_APB2Periph_GPIOB,GPIO_Mode_AF_PP,GPIOB,GPIO_Pin_7); if(CHX == 3) gpio_init(RCC_APB2Periph_GPIOB,GPIO_Mode_AF_PP,GPIOB,GPIO_Pin_8); if(CHX == 4) gpio_init(RCC_APB2Periph_GPIOB,GPIO_Mode_AF_PP,GPIOB,GPIO_Pin_9); TIMX_Int_Init(RCC_APB1Periph_TIM4,TIMX,arr,psc,0,IRQn,0);}//时基单元初始化 /*输出比较初始化*/ TIM_OCInitTypeDef TIM_OCInitStructure; //定义结构体变量 TIM_OCStructInit(&TIM_OCInitStructure); //结构体设置初值 TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //输出比较模式,选择PWM模式1 TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; //输出极性 TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //输出使能 if(CHX == 1) TIM_OC1Init(TIMX, &TIM_OCInitStructure);//输出比较初始化 if(CHX == 2) TIM_OC2Init(TIMX, &TIM_OCInitStructure); if(CHX == 3) TIM_OC3Init(TIMX, &TIM_OCInitStructure); if(CHX == 4) TIM_OC4Init(TIMX, &TIM_OCInitStructure); TIM_CtrlPWMOutputs(TIMX,ENABLE);//高级定时器才需要 /*TIM使能*/ TIM_Cmd(TIMX, ENABLE); //使能TIM,定时器开始运行 if(CHX == 1) TIM_SetCompare1(TIMX, 0); //设置CCR初值为0,关闭,超过CCR才输出低极性 if(CHX == 2) TIM_SetCompare2(TIMX, 0); //设置CCR初值为0,关闭,超过CCR才输出低极性 if(CHX == 3) TIM_SetCompare3(TIMX, 0); //设置CCR初值为0,关闭,超过CCR才输出低极性 if(CHX == 4) TIM_SetCompare4(TIMX, 0); //设置CCR初值为0,关闭,超过CCR才输出低极性 }
同时,每个定时器都有4个输出通道,所以stm32f103c8t6最小系统共有16个输出比较通道,通过以下函数可以打开16个通道,也就是16路PWM信号
TIMX_OC_Init(TIM1,1,99,719,TIM1_UP_IRQn); //定时器1输出比较通道1初始化
TIMX_OC_Init(TIM1,2,99,719,TIM1_UP_IRQn); //定时器1输出比较通道2初始化
TIMX_OC_Init(TIM1,3,99,719,TIM1_UP_IRQn); //定时器1输出比较通道3初始化
TIMX_OC_Init(TIM1,4,99,719,TIM1_UP_IRQn); //定时器1输出比较通道4初始化
TIMX_OC_Init(TIM2,1,99,719,TIM2_IRQn); //定时器2输出比较通道1初始化
TIMX_OC_Init(TIM2,2,99,719,TIM2_IRQn); //定时器2输出比较通道2初始化
TIMX_OC_Init(TIM2,3,99,719,TIM2_IRQn); //定时器2输出比较通道3初始化
TIMX_OC_Init(TIM2,4,99,719,TIM2_IRQn); //定时器2输出比较通道4初始化
TIMX_OC_Init(TIM3,1,99,719,TIM3_IRQn); //定时器3输出比较通道1初始化
TIMX_OC_Init(TIM3,2,99,719,TIM3_IRQn); //定时器3输出比较通道2初始化
TIMX_OC_Init(TIM3,3,99,719,TIM3_IRQn); //定时器3输出比较通道3初始化
TIMX_OC_Init(TIM3,4,99,719,TIM3_IRQn); //定时器3输出比较通道4初始化
TIMX_OC_Init(TIM4,1,99,719,TIM4_IRQn); //定时器4输出比较通道1初始化
TIMX_OC_Init(TIM4,2,99,719,TIM4_IRQn); //定时器4输出比较通道2初始化
TIMX_OC_Init(TIM4,3,99,719,TIM4_IRQn); //定时器4输出比较通道3初始化
TIMX_OC_Init(TIM4,4,99,719,TIM4_IRQn); //定时器4输出比较通道4初始化
通过下列函数更改比较值,做到占空比可调
void PWM_SetCompare(TIM_TypeDef* TIMX,uint8_t CHX,uint16_t Compare)
{
if(CHX == 1) TIM_SetCompare1(TIMX, Compare);
if(CHX == 2) TIM_SetCompare2(TIMX, Compare);
if(CHX == 3) TIM_SetCompare3(TIMX, Compare);
if(CHX == 4) TIM_SetCompare4(TIMX, Compare);
}
4.编写中断服务程序(可选):如果需要定时更新比较值或执行其他任务,可以编写定时器中断服务程序。在中断服务程序中,可以更新比较值或执行其他相关操作。
4. 输入捕获(编码器模式)
输入捕获是定时器的一种常见功能,特别适用于编码器的读取。编码器通常用于监测旋转或位置的变化,例如在机械系统中用于测量轴的转动角度或线性位移。使用输入捕获功能,可以准确地测量外部事件(例如编码器脉冲)的时间间隔,从而计算出旋转方向和速度。
使用输入捕获功能的步骤如下:
- 选择合适的定时器:根据编码器的脉冲频率和精度要求,选择合适的定时器。通常,高精度的编码器需要选择高频率的定时器来确保准确性。
- 配置定时器:设置定时器的计数模式和预分频器,以及使能输入捕获功能。根据编码器的工作原理和输出信号特点,选择合适的输入捕获触发模式。
void TIMX_IC_Encoder_Init(TIM_TypeDef* TIMX) { if(TIMX == TIM1){ gpio_init(RCC_APB2Periph_GPIOA,GPIO_Mode_IPU,GPIOA,GPIO_Pin_8); gpio_init(RCC_APB2Periph_GPIOA,GPIO_Mode_IPU,GPIOA,GPIO_Pin_9); TIMX_Int_Init(RCC_APB2Periph_TIM1,TIMX,65535,0,0,TIM1_UP_IRQn,0);}//时基单元初始化 if(TIMX == TIM2){ gpio_init(RCC_APB2Periph_GPIOA,GPIO_Mode_IPU,GPIOA,GPIO_Pin_0); gpio_init(RCC_APB2Periph_GPIOA,GPIO_Mode_IPU,GPIOA,GPIO_Pin_1); TIMX_Int_Init(RCC_APB1Periph_TIM2,TIMX,65535,0,0,TIM2_IRQn,0);}//时基单元初始化 if(TIMX == TIM3){ gpio_init(RCC_APB2Periph_GPIOA,GPIO_Mode_IPU,GPIOA,GPIO_Pin_6); gpio_init(RCC_APB2Periph_GPIOA,GPIO_Mode_IPU,GPIOA,GPIO_Pin_7); TIMX_Int_Init(RCC_APB1Periph_TIM3,TIMX,65535,0,0,TIM3_IRQn,0); }//时基单元初始化 if(TIMX == TIM4){ gpio_init(RCC_APB2Periph_GPIOB,GPIO_Mode_IPU,GPIOB,GPIO_Pin_6); gpio_init(RCC_APB2Periph_GPIOB,GPIO_Mode_IPU,GPIOB,GPIO_Pin_7); TIMX_Int_Init(RCC_APB1Periph_TIM4,TIMX,65535,0,0,TIM4_IRQn,0);}//时基单元初始化 //输入捕获初始化 TIM_ICInitTypeDef TIM_ICInitStructure; //定义结构体变量 TIM_ICStructInit(&TIM_ICInitStructure); //结构体初始化 TIM_ICInitStructure.TIM_Channel = TIM_Channel_1; //选择配置定时器通道1 TIM_ICInitStructure.TIM_ICFilter = 0xF; //输入滤波器参数,可以过滤信号抖动 TIM_ICInit(TIMX, &TIM_ICInitStructure); //将结构体变量交给TIM_ICInit,配置TIM3的输入捕获通道 TIM_ICInitStructure.TIM_Channel = TIM_Channel_2; //选择配置定时器通道2 TIM_ICInitStructure.TIM_ICFilter = 0xF; //输入滤波器参数,可以过滤信号抖动 TIM_ICInit(TIMX, &TIM_ICInitStructure); //将结构体变量交给TIM_ICInit,配置TIM3的输入捕获通道 TIM_EncoderInterfaceConfig(TIMX, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);//配置编码器接口 TIM_Cmd(TIMX, ENABLE); //定时器开始运行 }
附上gpio_init()
void gpio_init(uint32_t ARM_RCC,GPIOMode_TypeDef ARM_MODE,GPIO_TypeDef* GPIOX,uint16_t PIN) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(ARM_RCC, ENABLE); GPIO_InitStructure.GPIO_Pin = PIN; GPIO_InitStructure.GPIO_Mode = ARM_MODE; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOX, &GPIO_InitStructure); }
- 编写中断服务程序(可选): 如果需要定时更新输入捕获值或执行其他任务,可以编写定时器中断服务程序。在中断服务程序中,可以处理捕获事件,并更新相应的变量或执行其他相关操作。
- 解析输入捕获值: 在程序中解析输入捕获值,可以计算编码器的转动方向和速度,根据捕获的时间间隔和编码器的分辨率,可以计算出相应的角度变化或线性位移。
5. 总结
通过这篇学习笔记,我对定时器的常见应用场景进行了学习。在整个学习过程中,我努力从多个角度去理解定时器的应用,并在撰写笔记的过程中,尽可能详细地呈现出来。然而,我也意识到在某些方面可能存在不足和错误,并希望通过后续的学习和实践来不断改进和完善。