文章目录
前言
我们在学习了stm32后,不用库函数,应该怎样准确的定时呢?或者这个准确的定时可以做什么呢?接下来,为大家讲解三个定时器的重要的实验
一、定时器基本定义
STM32 的通用定时器是一个通过可编程预分频器(PSC)驱动的 16 位自动装载计数器(CNT) 构成。STM32 的通用定时器可以被用于:测量输入信号的脉冲长度(输入捕获)或者产生输出波 形(输出比较和 PWM)等。 使用定时器预分频器和 RCC 时钟控制器预分频器,脉冲长度和波形 周期可以在几个微秒到几个毫秒间调整。STM32 的每个通用定时器都是完全独立的,没有互相 共享的任何资源.
我使用的是stm32f103,其中通用定时器的主要功能包括:
- 16 位向上、向下、向上/向下自动装载计数器(TIMx_CNT)。
- 16 位可编程(可以实时修改)预分频器(TIMx_PSC),计数器时钟频率的分频系数为 1~
65535 之间的任意数值。- 4 个独立通道(TIMx_CH1~4),这些通道可以用来作为:输入捕获,输出比较,PWM 生成(边缘或中间对齐模式),单脉冲模式输出
- 可使用外部信号(TIMx_ETR)控制定时器和定时器互连(可以用 1 个定时器控制另外
一个定时器)的同步电路。- 如下事件发生时产生中断/DMA:
A.更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或者内部/外部触发)
B.触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)
C.输入捕获
D.输出比较
E.支持针对定位的增量(正交)编码器和霍尔传感器电路
F.触发输入作为外部时钟或者按周期的电流管理
二. 定时器的中断
首先,来说几个重要的寄存器。
1.相关的寄存器
控制寄存器1(TIMx_CR1)
我们这里只用到了该寄存器的最低位,也就是计数器使能位,该位必须置1,这样子定时器才能开始计数
DMA/中断使能寄存器(TIMx_DIER)
我们同样,也只关心它的第0位,该位是中断更新允许位,在定时器中断的实验中,我们用到的就是更新中断,所以该位设置位为1,这样子来允许更新事件产生的中断。
预分频寄存器(TIMx_PSC)
该寄存器用设置对时钟进行分频,然后提供给计数器,作为计数器的时钟。
这里定时器的来源一共有4个:
1)内部时钟(CK_INT)
2)外部时钟模式 1:外部输入脚(TIx)
3)外部时钟模式 2:外部触发输入(ETR)
4)内部触发输入(ITRx):使用 A 定时器作为 B 定时器的预分频器(A 为 B 提供时钟)。
具体选择哪一个,可以通过TIMx_SMCR寄存器的相关位来设置。
内部时钟(CK_INT)是由APB1倍频产生的,如果APB1的分频系数是1的话,那么通用定时器的时钟等于APB1时钟,否则,当分频系数为其他的数字的时候,通用定时器的时钟等于APB1时钟的2倍(无论分频系数设置的是多少);如果不设置分频系数的话,那么通用定时器的时钟就是APB1的时钟。!!!上面我们的说的这个规律是通用定时器的,高级定时器的时钟是来自于APB2的
定时器的计数器TIMx_CNT
该寄存器存储了当前定时器的计数值,在PWM输出的时候会和给定的值进行比较,从而选择输出高电平还是低电平,在输入捕获的实验中,最后计算时间的时候也会使用的到。这些下文我会详细讲解。在这里大家只要知道这个寄存器的基本功能就行了。
自动重装载寄存器(TIMx_ARR)
该定时器其实对应2个定时器,一个是我们可以操作的,另一个看不到的。这个看不到的我们称为影子寄存器,当TIMx_CR1定时器的APRE的值为0的时候,预装载寄存器的值随时可以传到影子寄存器中,当TIMx_CR1定时器的APRE的值为1的时候,预装载寄存器的值只有在每一次更新事件的时候才可以传送到影子寄存器当中。
状态寄存器(TIMx_SR)
该寄存器的各位,用来标记当前与定时器相关的事件或者中断的发生与否。
只要对以上的寄存器进行简单的设置,我们就可以使用通用定时器了
我使用的是stm32的F1mini版,接下来以通用定时器TIM3为例,为大家详细的讲解通用定时器中断的代码
2.代码的详解
①TIM3时钟使能
首先我们通过系统的结构图,可以看出通用定时器TIM3挂载在APB1上的
我们利用库函数来使能TIM3的时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //时钟使能
②初始化定时器参数,设置自动重装值,分频系数,计数方式等
我们使用TIM_TimeBaseInit来初始化定时器的参数
voidTIM_TimeBaseInit(TIM_TypeDef*TIMx,TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);
通过找到函数实现的地方我们可以发现
函数的第一个参数为选择的是哪个定时器,参数形式为上表所示。第二个参数是一个结构体,我们需要去确定结构体中与定时器中断相关的参数的值。
typedef struct
{
uint16_t TIM_Prescaler;
uint16_t TIM_CounterMode;
uint16_t TIM_Period;
uint16_t TIM_ClockDivision; //设置时钟分割
uint8_t TIM_RepetitionCounter; //在本次定时结束后,再重装载几次,赋的值表示的是重新装载的次数
} TIM_TimeBaseInitTypeDef;
TIM_Prescaler设置分频的系数
TIM_CounterMode设置定时器的计数的模式
TIM_Period设置重新装载的值
TIM_ClockDivision设置时钟的分频因子
最后一个结构体成员TIM_RepetitionCounter,只有高级定时器才会使用。
利用定时器TIM3的代码具体实现如下:
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_TimeBaseStructure.TIM_Period = 5000;
TIM_TimeBaseStructure.TIM_Prescaler =7199;
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
③设置 TIM3_DIER 允许更新中断
在上面的中断相关的寄存器部分,我们提到了TIMx_DIER寄存器,在程序的时候,我们可以使用寄存器操作,也可以使用库函数,这里我使用的是库函数编写。允许更新中断更新的函数为TIM_ITConfig。
void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);
还是相同的办法,在文件中找到函数实现的地方,确定函数的各个参数。
第一个参数还是选择定时器,第二个参数是选择定时器的中断的类型,如下
我们这里选择的是更新中断TIM_IT_Update。
第三个参数就是选择ENBALE or DISABLE。
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE );
④TIM3 中断优先级设置
既然是中断,就必须设置中断分组,即NVIC相关的寄存器,设置中断的优先级,利用NVIC_Init函数实现
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
我设置的是分组2,即2位抢占优先级,2位响应优先级
⑤允许 TIM3 工作,也就是使能 TIM3
上面的设置好了,没有开启定时器,我们还是不能进行定时器的使用。可以利用寄存器TIMx_CR1的CEN位来设置。也可以利用库函数TIM_Cmd来实现。
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState)
具体实现
TIM_Cmd(TIM3, ENABLE);
⑥编写中断服务函数
通过上面的设置,我们可以产生更新中断了,具体产生中断后需要做什么呢,这个就是接下来我们编写的中断服务函数。
写中断服务函数之前,我们首先要判断产生的是哪一种中断,通过TIMx_SR来判断,我们开启的是更新中断,所以只需看最低位就可以了,在处理函数的最后,我们应该清除这次的中断标志,以便产生下一次的中断,即最低位写0.
第一步当然是判断中断发生没有。
ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t)
如果发生了中断,接下来判断是否发生了我们设置的更新中断
if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET){}
在中断响应函数的最后,一定要清除中断标志,这样子才能再次产生中断,函数清除中断标志位的函数是
void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT)
三.time.c和time.h和main函数
其中的led_init函数我就不在这里写了,就是简单的GPIO口初始化
time.h
#ifndef __TIMER_H
#define __TIMER_H
#include"sys.h"
void TIME3_Int_Init(u16 arr,u16 psc);
#endif
time.c
#include"time.h"
#include"led.h"
#include"stm32f10x.h"
void TIME3_Int_Init(u16 arr,u16 psc)//arr为自动装载值,psc为预分频系数
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitTypeStruct;
NVIC_InitTypeDef NVIC_InitTypeStruct;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);//时钟使能
//定时器初始化
TIM_TimeBaseInitTypeStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitTypeStruct.TIM_CounterMode = TIM_CounterMode_Up;//计数方式
TIM_TimeBaseInitTypeStruct.TIM_Period = arr;//自动装载值
TIM_TimeBaseInitTypeStruct.TIM_Prescaler = psc;//预分频系数
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitTypeStruct);
//使能定时器的中断
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE);
//中断优先级分组
NVIC_InitTypeStruct.NVIC_IRQChannel = TIM3_IRQn;//中断通道
NVIC_InitTypeStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitTypeStruct.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitTypeStruct.NVIC_IRQChannelSubPriority = 3;
NVIC_Init(&NVIC_InitTypeStruct);
//使能定时器
TIM_Cmd(TIM3,ENABLE);
}
//中断服务函数
u16 flag = 0;
void TIM3_IRQHandler(void)
{
//判断是否为定时器3产生的中断
if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET)
{
if(flag)
{
GPIO_SetBits(GPIOA,GPIO_Pin_8);
}
else{
GPIO_ResetBits(GPIOA,GPIO_Pin_8);
}
flag = !flag;
//要手动的清理中断标志位
TIM_ClearITPendingBit(TIM3,TIM_IT_Update);
}
}
main
#include"stm32f10x.h"
#include"led.h"
#include"delay.h"
#include"time.h"
int main()
{
delay_init();
led_init();
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
TIME3_Int_Init(4999,7199);//500ms
while(1);
}
总结
总的来说,这个定时器的中断还是比较好理解的,这是后修相关定时器操作的基础