学习来源:[6-2] 定时器定时中断&定时器外部时钟_哔哩哔哩_bilibili
此次用STM32,因为stm32的元器件到了,有空可以触类旁通玩玩N32G003。
接线图
如下STM32
为了方便,直接复制之前的oled显示屏的代码,并把文件名字改成6-1定时器中断。
给定时器建立一个模块
建立分类的好习惯,由于定时器不涉及外部硬件,所以我习惯建立在system文件中
单击右键system,添加新文件,.C文件和.h文件
编写代码
(看完步骤,再看代码更快理解)
代码1
1·起框架
.C文件
#include "stm32f10x.h" // Device header
.H文件
#ifndef __TIMER_H
#define __TIMER_H
#endif
2·初始化
想要初始化,就要打通如上通道
第一步:RCC开启时钟
(基本每个代码的第一步都需要的,定时器的基准时钟和整个外设的工作时钟,都会同时打开)
第二步:选择时基单元的时钟源
函数TIM_InternalClockConfig()选择内部时钟
函数TIM_ITRxExternalClockConfig()选择ITRx其他定时器的时钟
函数TIM_TIxExternalClockConfig()选择TIx捕获通道的时钟
函数TIM_ETRClockMode1Config()选择ETR通过外时钟模式1输入的时钟
函数TIM_ETRClockMode2Config()选择ETR通过外时钟模式2输入的时钟
(定时中断,一般选内部时钟源)、
第三步:配置时基单元
函数TIM_TimeBaseInit()
(预分频器、自动重装器、计数模式等,只需要一个结构体就能配置好)
想要单独初始化某个参数,不用全部初始化,如预分频器,或自动重装值,可单独初始化函数
TIM_PrescalerConfig()单独写预分配值
(此函数其中有个参数TIM_PSCReloadMode,是因为预分频器有个缓冲寄存器,写入值是在更新事件之后才有效的,这个参数就是选择写入的模式,可以选择听从安排,在更新事件生效,也可以选择写入后,手动产生一个更新事件,立即生效)
TIM_CounterModeConfig()计数器计数模式的选择
TIM_ARRPreloadConfig()自动重装器预装功能配置
TIM_GetCounter()获取当前计数器的值
TIM_GerPrescaler()获取当前预分频值
第四步:配置 输出中断控制
函数TIM_ITConfig()
(允许输出中断到NVIC)
第五步:配置NVIC
NVIC_Init()
(在NVIC中打开定时器中断的通道,并分配一个优先级)
第六步:运行控制
函数TIM_Cmd()
(使能一下计数器,让其开始运行)
(回顾:计数器运行达到自动重装值, 触发中断)
第七步:写中断函数
拓展:第二步中,滤波时钟分频
外部信号输入脚,一般都有一个滤波器,可以滤掉信号的抖动干扰,
滤波原理就是,连续1多次采样,都为相同电平,代表信号稳定了,才把信号输入进去
若信号抖动,采样的电平不同,代表信号不稳定,维持上一次的输入信号进来,或直接低电平
这里的采样的频率和次数,可通过division参数 改变(随便选个参数就行,不太重要)
拓展:重复计数器
定时器设置1s产生中断,若设置重复计数器10,则10s后才产生中断。
注意:重复计数器,只有高级定时器才有,不需要用,直接给0就好。
定时的时间:由以下参数决定。
TIM_TimeBaseInitStructure.TIM_Period = ;//自动重装载值
TIM_TimeBaseInitStructure.TIM_Prescaler = ;//预分频值
譬如定时1s中断
选择内部时钟72MHz,公式变成:定时频率 =72M/(PCS+1)/(ARR+1)
所以TIM_Period 填10000-1,TIM_Prescaler 7200-1,
注意:取值范围是0-65535间。
timer.c文件
#include "stm32f10x.h" // Device header
void Timer_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);//第一步,开启时钟,因为TIM2是APB1总线的外设,所以开启APB1总线时钟
//第二步,选择时基单元的时钟
TIM_InternalClockConfig(TIM2);//选择内部时钟驱动TIM2(不写也可以,不写就默认是内部时钟驱动)
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;//滤波时钟分频
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//计数模式,向上计数
TIM_TimeBaseInitStructure.TIM_Period = 10000-1 ;//自动重装载值
TIM_TimeBaseInitStructure.TIM_Prescaler = 7200-1 ;//预分频值,定时1s
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0 ;//重复计数器,高级定时器才有的,
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);//第四步,配置中断控制,开了更新中断到NVIC的道路
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//第五步,配置NVIC,选择分组2
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;//抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //响应优先级
NVIC_Init(&NVIC_InitStructure);
TIM_Cmd(TIM2,ENABLE);//第六步:运行控制,使能一下计数器,开始计数,让其开始工作,达到触发中断的条件
}
timer.h文件
#ifndef __TIMER_H
#define __TIMER_H
void Timer_Init(void);
#endif
main.c文件
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "timer.h"
extern void Timer_Init(void);
int num =0;
int main(void)
{
Timer_Init();
OLED_Init();
while (1)
{
OLED_ShowString(1,1,"seconds:");
OLED_ShowNum(2,1,num,4);
}
}
void TIM2_IRQHandler(void)//第七步:写中断函数,这是在stm32的标准中断函数
{
//按规矩,检查中断标志位
if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET)//
{
//这里写内容
num++;
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);//手动清除中断标志位,表示中断函数跑完了
}
}
注意:extern声明的常见用法
1·没有extern void Timer_Init(void); 就会提示警报:
warning: #223-D: function "Timer_Init" declared implicitly
虽然只是警报,不影响烧录,最好还是添加上去,减少警报。
2·如果想要跨.c文件使用变量,也可进行extern 变量,告诉编译器,此变量已经声明过,可用。
额外声明变量,操作都是同一个变量,使用时候,至于变量的变化,其实头文件里的函数声明,省略了extern。
效果:
注意:
按下复位键,发现计数是从0001开始,我们设置的是0000,为什么会这样?
分析:初始化之后,已经立即进入1次中断
原因:在stm32f10x_tim.c文件中TIM_TimeBaseInit()函数末尾有提示(绿字)
翻译过来:立即生成一个更新事件,来重新装载分频器和重复计数器的值
深入分析:预分频器有缓冲寄存器,我们写的值,只有在更新事件时候,才会真正起作用 ,所以绿字下面的命令,就是手动生成了一个更新事件,这样预分频值就有效了。
但是,副作用,由于,更新中断和更新事件是同时发生的,更新中断,会把更新中断标志位置1,导致我们初始化后,更新中断就会立刻进入,这就是我们一上电,就立即进入中断的原因。
解决办法:
既然函数库,把更新中断标志位置1,我们在自写的初始化后面,手动把更新中断标志位清0即可。
TIM_ClearFlag(TIM2,TIM_FLAG_Update)
如下time.c文件:(函数在中间)
#include "stm32f10x.h" // Device header
void Timer_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);//第一步,开启时钟,因为TIM2是APB1总线的外设,所以开启APB1总线时钟
//第二步,选择时基单元的时钟
TIM_InternalClockConfig(TIM2);//选择内部时钟驱动TIM2(不写也可以,不写就默认是内部时钟驱动)
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;//滤波时钟分频
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//计数模式,向上计数
TIM_TimeBaseInitStructure.TIM_Period = 10000-1 ;//自动重装载值
TIM_TimeBaseInitStructure.TIM_Prescaler = 7200-1 ;//预分频值,定时1s
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0 ;//重复计数器,高级定时器才有的,
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
TIM_ClearFlag(TIM2,TIM_FLAG_Update);
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);//第四步,配置中断控制,开了更新中断到NVIC的道路
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//第五步,配置NVIC,选择分组2
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;//抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //响应优先级
NVIC_Init(&NVIC_InitStructure);
TIM_Cmd(TIM2,ENABLE);//第六步:运行控制,使能一下计数器,开始计数,让其开始工作,达到触发中断的条件
}
同时可运用如下函数,显示计数器的变化
OLED_ShowNum(3,5,TIM_GetCounter(TIM2),5)
由于我们设置的自动那个重装值是10000-1,所以计数0自增到9999,共10000个数,共1秒