本文章将会通过定时器Timer方式实现时间的精准控制,相当于给CPU上了一个闹钟,CPU平时处理其它任务,当定时时间到了以后,处理定时相关的任务。请设置一个5秒的定时器,每隔5秒从串口发送“hello windows!”;同时设置一个2秒的定时器,让LED等周期性地闪烁。
目录
一、定时器的介绍
1.什么是定时器
定时器/计数器作为SoC的外设,主要用来实现定时执行代码的功能。定时器相对于SoC来说,就好像闹钟相对于人来说意义一样。单核的CPU是单线程的,只能干一件事情,干完这件事情完去干另一件事情需要定时器来提醒。
基本定时器框图如下:
1. 时钟源(TIMxCLK)
定时器时钟 TIMxCLK,即内部时钟 CK_INT,经 APB1 预分频器后分频提供,如果APB1 预分频系数等于 1,则频率不变,否则频率乘以 2,库函数中 APB1 预分频的系数是 2,即 PCLK1=36M,所以定时器时钟 TIMxCLK=36*2=72M。
2. 计数器时钟(CK_CNT)
定时器时钟经过 PSC 预分频器之后,即 CK_CNT,用来驱动计数器计数。PSC 是一个16 位的预分频器,可以对定时器时钟 TIMxCLK 进行 1~65536 之间的任何一个数进行分频。具体计算方式为:CK_CNT=TIMxCLK/(PSC+1)。
3. 计数器(CNT)
计数器 CNT 是一个 16 位的计数器,只能往上计数,最大计数值为 65535。当计数达到自动重装载寄存器的时候产生更新事件,并清零从头开始计数。
4. 自动重装载寄存器(ARR)
自动重装载寄存器 ARR 是一个 16 位的寄存器,这里面装着计数器能计数的最大数值。当计数到这个值的时候,如果使能了中断的话,定时器就产生溢出中断。
2.定时器的分类
定时器可分为3类:
1、基本定时器:功能最少,只能充当基本的时基,甚至都没有外部引脚
2、通用定时器:拥有基本定时器的全部功能,同时有输入捕获模式,用以接收外部的PWM,脉冲之类的信息
3、高级定时器:又有通用定时器的全部功能,又有互补输出模式,功能最为强大
3.stm32中的定时器
就是用来定时的机器,是存在于STM32单片机中的一个外设。STM32总共有8个定时器,分别是2个高级定时器(TIM1、TIM8),4个通用定时器(TIM2、TIM3、TIM4、TIM5)和2个基本定时器(TIM5、TIM6),如下图所示:
4.定时器的作用
(1)定时器可以让SoC在执行主程序的同时,可以(通过定时器)具有计时功能,到了一定时间(计时结束)后,定时器会产生中断提醒CPU,CPU会去处理中断并执行定时器的ISR。从而去执行预先设定好的事件。
(2)定时器就好像是CPU的一个秘书一样,这个秘书专门管帮CPU来计时,并到时间后提醒CPU要做某件事情。所以CPU有了定时器之后,只需要预先把自己XX时间之后必须要做的事情绑定到定时器中断ISR即可,到了时间之后定时器就会以中断的方式提醒CPU来处理这个事情。
5.定时器的原理
(1)定时器计时其实是通过计数来实现的。定时器内部有一个计数器,这个计数器根据一个时钟(这个时钟来自于ARM的APB总线,然后经过时钟模块内部的分频器来分频得到)来工作。每隔一个时钟周期,计数器就就计数一次,定时器的时间就是计数器计数值x时钟周期。
(2)定时器内部有1个寄存器TCNT,计时开始时我们会把一个总的计数值(譬如说300)放入TCNT寄存器中,然后每隔一个时钟周期(假设为1ms)TCNT中的值会自动减1(硬件自动完成,不需要CPU软件去干预),知道TCNT中减为0的时候,TCNT就会触发定时器中断。最后的计时时间就是300ms。
(3)定时时间是由2个东西共同决定的:一个是TCNT中的计数值,一个是时钟周期。譬如上例中,定时周期就为300x1ms=300ms。
6.定时器实现方法
定时器timer的实现方法有很多种:
1、最简单易懂的,可以直接usleep(1000)/select(0),这个等待的时间就觉得了定时器的最大精度,然后轮询是否是否到到的定时器,这种定时器无论使用了怎样的定时器任务,该定时任务都会占用比较大量的CPU资源。
2、使用linux的timerfd进行定时,每个定时器,使用一个fd,使用timerfd_create()创建fd,使用timerfd_settime()可以直接设置fd的超时时间,超时时间到了,这个fd就会变为可读的。只要在任务里面监视这些定时器fd是否可读,就可以进行超时判断。
3、使用mutex和condition变量,利用pthread_cond_timedwait()来实现定时。首先将定时器进行排序,取最小的时间,作为信号量的超时等待时间。如果定时器有变化(增加,删除、停止等等)的时候,直接发送信号,触发pthread_cond_timedwait()直接返回,然后重新获取最短等待时间。
4、其实不止pthread_cond_timedwait(),任何可以控制超时等待时间的函数,都可以用来实现定时器。
二、配置工程项目
1.创建项目
点击ACCEE TO MCU SELECTOR
选择芯片,点击star project
2.环境配置
(1)配置RCC和SYS
点System Cor
,选择RCC,在右侧弹出的菜单栏中选Crystal/Ceramic Resonator
选择调试接口,点System Cor
,选择SYS。,在右侧弹出的菜单栏中选Serial Wire
:
(2)配置引脚
选择PC15作为LED灯的输出,将其选为GPIO-OUT:
(3)配置定时器
依次点击位置1,选中定时器2;位置2,配置定时器2的时钟源为内部时钟;位置3,分频系数为71,向上计数模式,计数周期为5000,使能自动重载模式。
分频系数那里虽然写的是71,但系统处理的时候会自动加上1,所以实际进行的是72分频。由于时钟我们一般会配置为72MHZ,所以72分频后得到1MHZ的时钟。1MHZ的时钟,计数5000次,得到时间5000/1000000=0.005秒。也就是每隔0.005秒定时器2会产生一次定时中断。
(4)配置中断
如下图所示,开启定时器2的中断。
生成定时器2中断优先级配置代码。
(5)配置USART1
选择Connectivity,点开USART1,Mode选择异步通信Asynchronous:
(6)配置时钟
如下图:
3.生成项目
设置项目名称
勾选Generate … …
三、在keil中设置代码
1.中断响应之后所需的代码
HAL_TIM_Base_Start_IT(&htim2);
该函数表示启动相应的定时器,“h”表示HAL库,“tim2”表示定时器2。所以这行代码的意思就是启动定时器2。
2. 串口输出hello windows!
uint8_t hello[20]="hello windows!\r\n";
3.中断回调函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
static uint32_t time_cnt =0;
static uint32_t time_cnt3 =0;
if(htim->Instance == TIM2)
{
if(++time_cnt >= 400)
{
time_cnt =0;
HAL_GPIO_TogglePin(GPIOA,GPIO_PIN_5);
}
}
if(htim->Instance == TIM3)
{
if(++time_cnt3 >= 1000)
{
time_cnt3 =0;
HAL_UART_Transmit(&huart1,hello,20,100000);
}
}
}
4.编译运行
编译成功,无错误
四、电路连接
烧录连接
LED灯短脚 —> C15
LED灯长脚 —> 3V3
五、烧录程序
烧录成功。
六、运行效果
串口接收helllo world!
3
灯定时亮
2
七、总结
通过这次实验,在STM32F103C8T6核心开发板下,通过使用定时器Timer方式,来实现串口定时输出及LED灯周期闪烁的操作步骤,初步认识了STM32定时器的相关理论知识,定时器中断相比软件延时更加准确,且不占用CPU资源,定时器中断实验中最重要还是要明白定时时间的计算原理和方法,定时器中断则是通过时钟定时计数达到设定值时触发中断,进行中断服务函数的处理任务。并将这个理论知识简单的运用到了实践中,是一次小小的实践。