在不了解定时器之前
蛋糕是这样的:不就是个定时器,不就只能记个数
了解了定时器之后
蛋糕是这样的:定时器!中断处理!!我可以!!!啊看看我!!!!!
那接下来就和我一起走进定时器!!!!!GOGOGO
通用定时器是一个通过可编程预分频器(PSC)驱动的16位自动装载计数器(CNT)构成的。STM32的通用定时器可以被用来:测量输入信号脉冲长度(输入捕获)或者产生输出波形(输出比较和PWM)等。在这里我们主要说明定时后的中断处理功能。
介绍到这里,大家可以就对定时器和我们已经学过的时钟心存疑惑。
时钟是CPU的心脏,一块芯片可能具有多个时钟源,以STM32为例,它具有5个时钟源(51单片机由于功能较为简单只具有一个系统时钟)它们分别是:HSI(高速内部时钟)、HSE(高速外部时钟)、LSI(低速内部时钟)、LSE(低速外部时钟)PLL(锁相环倍频输出)。
简单来说,时钟是定时器的基础部分,没有时钟定时器就没有办法工作,定时器是时钟功能的衍生。
下面小蛋糕以实验为例,带大家走进定时器。
在本实验的主要内容是:通过定时器定时产生中断,再由硬件执行中断服务函数。在本实验中,为了更好的表现出中断服务函数的作用,我们将LED的闪烁安排在中断服务函数中,使我们的实验现象更加直观。
话不多说,上代码!!!
首先我们需要设置中断的优先级分组,在STM32系列芯片中使用四位保存优先级,所以我们的优先级分组就有NVIC_PriorityGroup_0、NVIC_PriorityGroup_1、NVIC_PriorityGroup_2、NVIC_PriorityGroup_3、NVIC_PriorityGroup_4,共四种优先级分组可选。在这里根据我们的中断数量我们设置中断优先级分组为NVIC_PriorityGroup_2,代码如下:
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断优先级分组
对中断优先级分组设置完成后,由于我们在中断服务函数中使用了LED,所以我们还要在主函数(main()函数)中完成LED的初始化。
void LED_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure; //声明初始化需要的结构体
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOD, ENABLE); //使能相关时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; //设置使用引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //设置输出模式
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //设置输出频率
GPIO_Init(GPIOA, &GPIO_InitStructure); //完成GPIOA8的初始化
GPIO_SetBits(GPIOA,GPIO_Pin_8); //设置引脚输出高电平
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_Init(GPIOD, &GPIO_InitStructure);
GPIO_SetBits(GPIOD,GPIO_Pin_2);
}
在上面这段代码中我们就完成了LED连接的GPIO的初始化。
然后,重点来了。我们开始进入与定时器有关的操作——通用定时器的初始化(在这里小蛋糕使用的是TIM3定时器)
首先我们来看看,定时器初始化参数结构体里面有哪些内容
typedef struct
{
uuint16_t TIM_Prescaler; //设置分频系数
uint16_t TIM_CounterMode; //设置计数方式(向上、向下、中央对齐)
uint16_t TIM_Period; //设置自动重载计数周期值
uint16_t TIM_ClockDivision; //设置时钟分频因子
uint8_t TIM_RepetitionCounter; //仅在高级定时器中使用,这里不需要
}TIM_TimeBaseInitTypeDef;
了解了定时器初始化参数结构体后,我们就可以根据自己的需求进行定时器初始化函数的书写了。
void TIM3_Int_Init(u16 arr,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; //定义定时器初始化结构体
NVIC_InitTypeDef NVIC_InitStructure; //定义NVIC中断寄存器初始化结构体
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //使能时钟
TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //设置技术模式为向上计数
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
TIM_ITConfig(
TIM3,
TIM_IT_Update ,
ENABLE );
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; //TIM3中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //设置抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //设置子优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能对应外设的NVIC寄存器
NVIC_Init(&NVIC_InitStructure);
TIM_Cmd(TIM3, ENABLE);
}
完成定时器的初始化之后我们要开始编写我们的中断服务函数,但是需要注意的是,中断服务函数是不会被任何函数调用的,if你还不理解发生中断后的硬件工作原理,你只需要知道中断服务函数是硬件直接操作的,至于怎么操作,嗯嗯嗯嗯嗯,其实也可以暂且把它理解为调用函数的神秘力量。下面就是我们的中断服务函数。
void TIM3_IRQHandler(void) //由于中断服务函数不能被任何函数调用,所以它没有参数、没有返回值
{
if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) //检查指定的定时器是否发生中断
{
TIM_ClearITPendingBit(TIM3, TIM_IT_Update ); //清除中断待处理位,进行中断处理
LED1=!LED1; //通过LED显示中断发生,并执行中断服务函数
LED0=!LED0; //通过LED显示中断发生,并执行中断服务函数
}
}
程序的主函数如下,其中for循环的功能是实现延时,即增加函数运行一次的时长,所以for语句是没有实际作用的,因此循环体是空语句。
int main(void)
{
int i;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置优先级分组
LED_Init(); //初始化LED
TIM3_Int_Init(4999,7199);
for(i=0;i<10000000000;i++) //实现延时
{
};
}
通过上述代码的组合,我们就可以完成在一定的时间间隔内,通过定时器产生中断,由硬件调用中断服务函数的操作了。
怎么样,没想到吧,定时器还能这么用,其实定时器不仅可以产生中断,在我们的操作系统的某些进行调度算法也用到了定时器。怎么样,你学废了没?下篇文章见!