STM32的定时器
定时器应该是各位单片机学习者学完GPIO后第一个学习的内容了吧。在嵌入式芯片中,操作定时器是非常基础且重要的事,灵活使用定时器也是嵌入式软件的精髓,在略微顶层的设计中,由于更多地是与人交互,所以ms级定时器已经非常够用了,但是在操作底层时,很多us级的定时器都只能勉强够用,也有的是直接接入芯片的频率进行最快速的操作。
在学32单片机前,我先学的是51单片机。可怜的51单片机只有两个定时器,还都是16位的,在做一些复杂应用的时候是根本不够用,甚至还要一个定时器拆成两个定时器来用(真实存在)。但在学习stm32过后,就算是入门的F103,定时器资源都是用不完的。而且stm32的定时器的功能强大,基本库和HAL的功能也基本相同,这也决定了stm32拥有非常广泛的用途。
同样,有很多大佬都已经总结过、整理过stm32的定时器了。这一期中,我就不在赘述其基本功能了,就结合cubemx和hal库,整理一下定时器中断、定时器生成PWM和编码器模式的函数使用。
定时器中断
定时器中断应该是最基本的操作了,我们勾选Internal Clock来选择时钟源。当然,每个时钟的内部时钟源的频率是有所不同的,请仔细查询资料或者手册,确定使用的时钟是APB1时钟源还是APB2时钟源。如图所示,我使用的F401RCT6的时钟4,其时钟频率是84MHz,当分频84倍后,其频率为1MHz,设置计数器为10,则这个时钟是10us的时钟中断,中断频率100KHz。
注意别忘了使能NVIC,将Enabled的选项打勾即可。接下来我们就要到程序中去写了。
在生成代码后,我们可以看见CubeMX已经帮我们把MX_TIM4_Init();放在了初始化中,但是此时定时器中断是不启动的,我们需要使能中断。
HAL_TIM_Base_Start_IT(&htim4);
当然这个中断是可以随时关闭的,我们可以通过调用下面的函数来关闭中断。
HAL_TIM_Base_Stop_IT(&htim4);
接下来,我们来写中断服务函数。首先我们要重写一下下面这个函数。
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
这个函数原本已经是被弱定义过了,就是这个库其他地方需要用这个函数,但是函数内容又需要用户自己定义,这时候就会使用弱定义这种方式。如果你没有定义这个函数,编译器就会在使用时调用弱定义地方的函数。如果定义了,就会调用重新定义地方的函数。
使用结构一般如下:
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(TIM4 == htim -> Instance)
{
HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin);
}
/*
if(TIM? == htim -> Instance)
{
中断服务程序
}
*/
}
在这个函数中,只要定时器4发生中断,那么就会把LED的电平翻转一次。在HAL库中,我们不需要重置中断标志位,它会被自动置位,并且根据设定的时间精准地进入下一次中断。而且和标准库不同,它用一个函数处理了所有定时器中断。
生成PWM
CubeMX设置PWM输出
这个7200是为了凑stm32F103的72MHz的频率,具体情况可以更改
在系统自动生成的初始化后面加上PWM启动的代码。
HAL_TIM_PWM_Start(&htim5, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim5, TIM_CHANNEL_2);
HAL_TIM_PWM_Start(&htim5, TIM_CHANNEL_3);
HAL_TIM_PWM_Start(&htim5, TIM_CHANNEL_4);
随后我们只需要调用下面的函数,就可以更改PWM的占空比了。
__HAL_TIM_SET_COMPARE(&htim5, TIM_CHANNEL_2, Compare);
其中Compare与计数器最大值的比值就是PWM的占空比。
编码器模式(Encoder Mode)
首先,我们设置一下编码器模式。如果不了解编码器的使用,可以参考以下的教程。
【平衡小车制作】(三)编码器讲解(超详解)
在分频器设置中我一般不分频,使其灵敏度最高。在计数器设置中一般设置高一点,有时过零点的检测会出些问题,我也会初始化将其值设置为一个中位数。Input Filter可以适当设置一点,可以保证采样的稳定性。
记得在自动生成的初始化后还要再加上以下的函数,编码器才会正式工作。
HAL_TIM_Encoder_Start(&htim2, TIM_CHANNEL_ALL);
从上面可以看到我把这个计数器设置为65535,是16bit的最大计数器值。我们可以取其中位数,大概30000,进行预装填。
__HAL_TIM_SET_COUNTER(&htim2, 30000);
当我们转动编码器后时再去读取这个值,只需要调用:
EncoderNum = __HAL_TIM_GET_COUNTER(&htim2);
便可以读取当前的编码器值。
在小车应用中,可以将读取放在中断中,在每次读取后再重新复位30000,小车的速度就是读取的值与30000作差,可以做速度闭环。
CubeMX学习笔记总结
从2020年开始的这个系列,由于各种原因拖到了现在,而且匆匆结尾。在2020年的时候,关于CubeMX的教程在CSDN上还是比较少的,如今已是百花齐放。我也在2023年的春天把基本的功能总结完了,也为CubeMX的教程贡献出自己的想法。
由于当下科技发展太快,目前我也在努力学习,希望把考研和疫情浪费的时间尽快补回来,在做完2020年留下的任务之后,也要向前看,拥抱这个AI时代最前沿的技术。