前言
本节课将了解定时器的基本功能及其配置方法,还接触 stm32 中最重要的概念之一——中断,介绍在 cubeMX 中如何对中断进行设置,如何开启中断以及配置中断的优先级等,最后将实现由定时器触发的定时器中断,控制 LED 灯的闪烁。
准备工具
软件:STM32CubeMx、Keil5 MDK
硬件:STM32F103C8T6核心板、下载器ST_LINK
本章节工程已上传至百度网盘,此链接永久有效
链接:https://pan.baidu.com/s/1PaQlDD1Q5i2MAWKCnDjsww?pwd=tad4
提取码:tad4
定时器讲解
定时器的基本功能是计时功能,如同闹铃一般,设定好对应的时间后,会在设定的时刻响起铃声。例如上章的滴答定时器,设定为 1ms 的定时时间,便每隔 1ms 引起中断函数。在使用定时器时,会涉及到三个非常重要的概念——分频(psc),计数(cnt),重载(arr)。这三个概念可以结合生活中使用的时钟来理解。
分频(PSC):时钟上不同的指针需要有不同的速度,也就是不同的频率,从而精确的表示时间,比如秒针,分针,时针,这三者相邻的频率之比都是 60:1,即秒针每转过 60 格分针转动 1 格,分针转动 60 格时针转动 1 格,所以分针对于秒针的分频为 60。
计数(CNT):时钟所对应的值都是与工作时间成正比的,比如秒针转动 10 格,意味着过了 10秒,同样定时器中的计数也是和计数时间成正比的值,频率越高增长速度越快。
重载(ARR):时、分、秒的刻度都是有上限的,一个表盘最多记 12 小时, 60 分钟, 60 秒,如果继续增加的话就会回到 0。同样的在定时器中也需要重载,当定时器中的计数值达到重载值时,计数值就会被清零。
时钟源处的时钟信号经过预分频寄存器,按照预分频寄存器内部的值进行分频。比如时钟源的频率为 16MHz,而预分频寄存器中设置的值为 16: 1,那么通过预分频后进入定时器的时钟频率就下降到了 1MHz。
在已经分频后的定时器时钟驱使下, TIMx_CNT 根据该时钟的频率向上计数,直到TIMx_CNT 的值增长到与设定的自动重装载寄存器 TIMx_ARR 相等时, TIMx_CNT 被清空,并重新从 0 开始向上计数, TIMx_CNT 增长到 TIMx_ARR 中的值后被清空时产生一个定时中断触发信号。综上定时器触发中断的时间是由设定的 TIMx_PSC 中的分频比和TIMx_ARR 中的自动重装载值共同决的。
定时器是 stm32 中非常重要的外设。 在大多数应用场景中,部分任务需要周期性的执行,比如上一讲中提到的 LED 闪烁,这个功能就可以依靠定时器来实现,此外 stm32 的定时器还能够提供 PWM 输出,输入捕获,输出比较等多种功能。
定时器中断
在 STM32 中,对信号的处理可以分为轮询方式和中断方式,轮询方式就是不断去访问一个信号的端口,看看有没有信号进入,有则进行处理,中断方式则是当输入产生的时候,产生一个触发信号告诉 STM32 有输入信号进入,需要进行处理。
例如厨房里烧着开水,主人在客厅里看电视。为了防止开水烧干,他有两种方式,第一种是每隔 10 分钟就去厨房看一眼,另一种是等水壶烧开了之后开始发出响声再去处理。前者是轮询的方式,后者是中断的方式。
每一种中断都有对应的中断函数,当中断发生时,程序会自动跳转到处理函数处运行,而不需要人为进行调用。
如第一节中,当定时器的计数值增长到重载值时,在清空计数值的同时,会触发一次定时器中断,即定时器更新中断。只要设定好定时器的重载值,就可以保证定时器中断以固定的频率被触发。
CubeMx配置
打开上一章节的工程文件
计算定时器中断周期,如下
为计算所得产生中断周期,其中
为自动重装载值,
为预分频,
为对应时钟频率。
如要定时1ms,Tclk已知为72Mhz,设置psc为719,arr为99,即可产生1ms中断。
生成代码。打开工程
在主函数初始化添加如下代码
/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start_IT(&htim3);//开启定时器3中断
/* USER CODE END 2 */
与标准库不同,我们一般需要在中断回调函数中判断中断来源并执行相应的用户操作。
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
该函数为弱函数,可在其他地方声明,弱函数在调用时会优先进入用户声明的函数。
在工程文件中添加以下代码即可观察LED以500ms为周期闪烁
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == htim3.Instance)
{
static int16_t tim_delay = 0;
if(++tim_delay>=500) // 1ms * 500 = 500ms
{
tim_delay = 0;
HAL_GPIO_TogglePin(LEDD_GPIO_Port, LEDD_Pin);
}
}
}