上一篇中,介绍了内核里面的定时器SysTick,在整个STM32F407中,除了这一个内核定时器外还有很多的片上外设定时器,大致分为三大类:基本定时器、通用定时器、高级定时器。从本文开始,将对定时器这个大家族做个介绍。
首先,还是来看一看基本定时器的相关描述,搞清楚它是个啥,能干啥?
关于基本定时器的描述如下图:具体位置在中文参考手册的第17章。
从上面的描述中可以发现,
1.基本定时器是一个16位的自动重载递增(向上)计算器,不知道大家还有没有映像,上一篇中的系统滴答内部是一个24位的向下计数器;一对比,说明这个基本定时器的最大重装载值要比系统滴答小,应该是2^16-1=65536-1=65535,而且计数的方向也不一样,一个是从重装载值减到0,一个是从0加到重装载值;
2.该计数器可编程预分频驱动,说白了就是这个计数器的时钟基准是可以编程调整分频系数的,这与系统滴答不太一样,系统滴答的时钟是没有编程预分频的;
3.此定时器除了能够实现类似系统滴答的延时和定时中断外,还可以专门用于DAC(数模转换器);
4.定时器与定时器之间完全独立,不共享任何资源。
通过与系统滴答对比,可以发现基本定时器的使用也是通过控制重装载值和时间基准来实现的。这里再补充一点:
STM32F407的基本定时器是TIM6和TIM7,并不是所有的芯片都有基本定时器,或者都是TIM6和TIM7。
好了,在大致有了一个映像后,接下来就是分析它的框图了,看看根据框图能不能捋出基本定时器的大致工作流程。整体框图如下所示:为了方便理解,我们还是分开来看。
再看时钟框图之前,需要回顾一点知识,在时钟树的介绍中,有特意提到过关于定时器的时钟。
定时器的时钟大小:
先看挂靠的时钟总线是否是主频的1分频,如果是1分频,则定时器时钟大小就是时钟线大小,如果不是1分频,则定时器时钟大小就是时钟线大小x2
查阅数据手册可以发现,TIM6和TIM7在APB1上,而APB1的时钟是42MHZ,说明其分频系数≠1;因此TIM6和TIM7的时钟应该是42MHZ×2=84MHZ
但是,这还不是实际的定时器使用的频率,在概述中我们提到了,计算器的时钟输入是可以通过编程预分频的。接下来就看看结构图中的具体描述吧。
首先,右侧的来自RCC的TIMxCLK时钟,也就是我们上面提到的APB1上的频率42MHZ,经过了两个控制器,按照前面的经验这两个个控制器内部是可以编程控制的,其中一个触发控制器的输出是到了橙色框的DAC,说明这个暂时不用看;另外一个控制器输出了一个CK_PSC也就是时钟信号,此时的频率就已经变成了上面计算的42*2=84MHZ了;这个84Mhz的时钟并不是直接到了计数器,在这之间还有一个预分频器的东西,说明我们可以通过对这个预分频器进行操作,进而对这个频率进行修改。
需要注意的是如下图所示,在这个预分频器的下方还有一个阴影部分,这可不是单纯的为了好看而加的阴影,这个阴影部分是其意义的,它的名字叫做影子寄存器。
注意下方图例的描述,只有发生更新事件时,才会将预分频器里面的数据真正的写入实际的分频寄存器。也就是说,虽然我们编程写入了预分频器这个寄存器,但是实际的分频并不是写完就变成期望值的,还需要等待一个更新事件才行。这也就是为什么这个分频器要命名为“预”分频器的原因。
分频:指得是分多少次频传入得分频系数需要-1
想做74分频,实际写入分频器得值是73
基本定时器的分频:
TIM6和TIM7:84MHZ
84000 000/s 84000/ms 84/us
84分频:1000 000/s 1000/ms 1/us //最大的延时65.6ms
8400分频:10000/s 10/ms //最大的延时6553ms
42分频 2/us
计数器的结构看起来就和系统滴答大致一样了,首先橙色框的是一个递增计数器,主打工作就是来一个时钟自增一次;然后是输入部分,左侧是上面提到的经过预分频处理后的时钟,上面是自动重装载寄存器,其主打工作就是写入重装载值的,但是这里也有一个阴影,说明它和预分频一样,也有一个影子寄存器,那么它的工作流程也是和预分频一样吗?
答案是否,虽然这个重装载值也有影子寄存器,但是这个影子寄存器起不起作用是可控的,也就是如果想有一个预装载的作用,就开启它,不想用就关闭它。
如下图所示,开启了重装载的影子寄存器后,我们在红线时刻对绿色框的自动预重载寄存器写入了36,此时下方的橙色框内的自动重载影子寄存器没有直接被修改为36,一直等到上一轮计数器计数到“F5”,完成了前一个重装载值的计数周期产生了更新事件(绿线位置)后,才将36写入自动重装载影子寄存器。
此时的时序图如下图示:我们在橙色线位置对重装载寄存器写入了36,
此时没有了影子寄存器,所以直接就修改了重装载值,本轮计数也会使用此时写入的新的重装载值。如下图绿线所示位置,在36的时候就已经产生了更新事件,而不是技术到“FF”后再修改。
说完上面的那些,关于这个框图就只剩一点小东西了就是下图橙色邝中的箭头和U、UI这些,这个就是对应的中断、更新事件、以及DMA输出等等,具体的留到下面的寄存器再说。有个映像即可。
可以发现,整体的框图与系统滴答的并没有太大的区别,主要是多了一个预分频器和影子寄存器的概念。
预分频器的影子寄存器:一定有
重装载值得影子寄存器:自己配置
思考:第一次配置分频寄存器,如何将值写入影子寄存器
答案:人为产生一次更新事件,有对应的寄存器接口可供操作。
TIM6 和 TIM7 控制寄存器 1 (TIMx_CR1)
写法:TIM6->CR1
TIM7->CR1
X:表示使用得哪一个寄存器
功能:主要是控制基本定时器得功能
位 0 CEN:计数器使能 (Counter enable)
置0:关闭计数器
置1:开启计数器
位 1 UDIS:更新禁止 (Update disable)
置0:可以更新影子寄存器
置1:不能更新影子寄存器
位 2 URS:更新请求源 (Update request source)
置0:可以使用上下溢事件和UG位置1 触发更新中断
置1:只有上下溢事件才能产生更新中断
位 3 OPM:单脉冲模式 (One-pulse mode)
置0:计数器会一直工作
置1:计数器只工作一次,计数一次后清零使能位
功能:延时功能(开启单脉冲) 定时中断(关闭单脉冲)
位 7 ARPE:自动重载预装载使能 (Auto-reload preload enable)
置0:表示不开启重装载值的影子寄存器
置1:开启重装载值的影子寄存器
TIM6 和 TIM7 控制寄存器 2 (TIMx_CR2)
写法:TIM6->CR2
TIM7->CR2
功能:用于配置定时器的主模式
需要用到DAC功能时候配置,要产生TRGO
TIM6 和 TIM7 DMA/中断使能寄存器 (TIMx_DIER)
写法:TIM6->DIER
TIM7->DIER
功能:中断信号的使能
位 0 UIE:更新中断使能 (Update interrupt enable)
置1:开启更新中断
置0:关闭更新中断
TIM6 和 TIM7 状态寄存器 (TIMx_SR)
写法:TIM6->SR
TIM7->SR
位 0 UIF:更新中断标志 (Update interrupt flag)
读取时:
为0:表示没有发生更新事件
为1:表示发生了更新事件
写入:写入0表示清除标志位
TIM6 和 TIM7 事件生成寄存器 (TIMx_EGR)
写法:TIM6->EGR
TIM7->EGR
功能:人为生成一个事件
位 0 UG:更新生成 (Update generation)
置1:生成更新事件
置0:不执行任何操作
TIM6 和 TIM7 计数器 (TIMx_CNT)
写法:TIM6->CNT
TIM7->CNT
功能:记录计数器的当前值
用法直接赋值:TIM6->CNT=0;
TIM6 和 TIM7 预分频器 (TIMx_PSC)
写法:TIM6->PSC
TIM7->PSC
功能:配置预分频值
用法直接赋值:TIM6->PSC = 84-1;
TIM6 和 TIM7 自动重载寄存器 (TIMx_ARR)
写法:TIM6->ARR
TIM7->ARR
功能:写入重装载值
用法直接赋值:TIM6->ARR = 1000-1;
//直接新建一个 timer.c文件存放定时器代码
实现TIM6的ms延时函数
{
//打开TIM6的时钟
//更新禁止
//更新请求源
//单脉冲模式
//重装载值影子寄存器
//配置预分配
//重装载值
//人为产生一次更新事件
//清除计数标志
//使能计数器
//等待计数完成
}
以tim7为例:
基本定时器的中断初始化函数
{
//TIM7基础配置
//打开TIM7的时钟
//更新禁止
//更新请求源
//关闭单脉冲模式
//重装载值影子寄存器
//配置预分配
//重装载值
//人为产生一次更新事件
//开启中断使能
//NVIC控制器
//使能计数器 一直工作
}
Tim7的中断服务函数
{
判断中断
//清除标志位
//紧急事件
}
延时:
/*******************************
函数名:Time6_Delay_ms
函数功能:定时器6实现ms延时
函数形参:u32 nms
函数返回值:void
备注:
********************************/
void Time6_Delay_ms(u32 nms)
{
RCC->APB1ENR |=(1<<4);//打开APB1的Time6时钟使能
TIM6->CR1 &=~(7<<1); //清零
TIM6->CR1 |=(4<<1); //更新禁止,更新请求源,单脉冲模式
// TIM6->CR1 &=~(1<<1); // 0:使能 UEV。
// TIM6->CR1 &=~(1<<2); //URS:更新请求源0使能
// TIM6->CR1 |=(1<<3); // 单脉冲模式,只执行一次后自动关闭
TIM6->CR1 |=(1<<7); //使能重装载值的影子寄存器
TIM6->PSC =8400-1; //频率为42*2=84Mhz,为了方便计算,进行8400分频为10 000
TIM6->ARR =10*nms-1; //向上计数
TIM6->EGR |=(1<<0); //手动产生一次更新事件 ,将预分频和重装载值写入
TIM6->SR &=~(1<<0); //清除计数标志
TIM6->CR1 |=(1<<0); //使能计数器。
while(!(TIM6->SR &(1<<0)));//等待计数完成
}
/*******************************
函数名:Time6_Delay_us
函数功能:定时器6实现us延时
函数形参:u32 nus
函数返回值:void
备注:
********************************/
void Time6_Delay_us(u32 nus)
{
RCC->APB1ENR |=(1<<4);//打开APB1的Time6时钟使能
TIM6->CR1 &=~(7<<1); //清零
TIM6->CR1 |=(4<<1); //更新禁止,更新请求源,单脉冲模式
// TIM6->CR1 &=~(1<<1); // 0:使能 UEV。
// TIM6->CR1 &=~(1<<2); //URS:更新请求源0使能
// TIM6->CR1 |=(1<<3); // 单脉冲模式,只执行一次后自动关闭
TIM6->CR1 |=(1<<7); //使能重装载值的影子寄存器
TIM6->PSC =42-1; //频率为42*2=84Mhz,为了方便计算,进行8400分频为10 000
TIM6->ARR =2*nus-1; //向上计数
TIM6->EGR |=(1<<0); //手动产生一次更新事件 ,将预分频和重装载值写入
TIM6->SR &=~(1<<0); //清除计数标志
TIM6->CR1 |=(1<<0); //使能计数器。
while(!(TIM6->SR &(1<<0)));//等待计数完成
}
定时中断:
/*******************************
函数名:Time7_Interrupt
函数功能:Time7_定时中断
函数形参:u16 arr重装载值 u16 psc预分频系数
函数返回值:void
备注:
预分频系数 重装载值
********************************/
void Time7_Interrupt(u16 psc,u16 arr)
{
RCC->APB1ENR |=(1<<5);//打开APB1的Time6时钟使能
TIM7->CR1 &=~(7<<1); //清零 //更新禁止,更新请求源,关闭单脉冲模式
TIM7->CR1 |=(1<<7); //使能重装载值的影子寄存器
TIM7->PSC =psc-1; //频率为42*2=84Mhz
TIM7->ARR =arr-1;
TIM7->EGR |=(1<<0); //手动产生一次更新事件 ,将预分频和重装载值写入
TIM7->DIER |=(1<<0); //使能中断
//NVIC控制器配置EXTI0
u32 pri=NVIC_EncodePriority(7-2,1,3);
NVIC_SetPriority(TIM7_IRQn,pri);
NVIC_EnableIRQ(TIM7_IRQn);
TIM7->CR1 |=(1<<0);//使能计数器
}
借助之前的时间片的思路,这里定时500ms发送一次数据看一下时间戳。
对应的中断服务函数函数:
/*******************************
函数名:TIM7_IRQHandler
函数功能:定时器7中断服务函数函数
函数形参:无
函数返回值:void
备注:1ms中断
********************************/
void TIM7_IRQHandler(void)
{
static u16 Time7_Cnt[10];
if(TIM7->SR & (1<<0))
{
TIM7->SR &=~(1<<0);
Time7_Cnt[0]++;
if(Time7_Cnt[0]>=500)
{
printf("1\r\n");
Time7_Cnt[0]=0;
LED_1_TURN;
}
}
}
//初始化代码Time7_Interrupt(84-1,1000-1);//1ms进入一次中断