定时中断结构图:
运行控制是由控制寄存器组成,我们操作这些寄存器就能控制时基单元的运行,比如启动停止,向上或向下计数等等。发送到中断输出控制的箭头是指当计时时间到的时候,产生更新中断都的信号去向,如果是高级定时器,还会多一个重复计数器。计时时间到的同时,中断信号会先在状态寄存器里置一个中断标志位。这个标志位也会通过通过输出控制到NVIC。
预分频器时序
CK_PSC是预分频器的输入时钟,CK_EN是计数器使能,高电平计数器运行,低电平计数器停止。CK_CNT是计数器时钟,既是预分频器的时钟输出,也是计时器的时钟输入。如图在FC位置计数器清零,可以推断出ARR自动重装值就是FC。当计数器记时到和重装值相等,并且下一个时钟来临时,计数器才清零。同时产生更新事件,这就是一个计数周期工作流程。下面的预分频控制寄存器主要是供我们用来读写,存储预分频系数,它不直接决定分频系数,下面一个是缓存寄存器,也叫影子寄存器,主要是为了保证分频系数改变时,一个周期内的时钟频率不会发生改变,当本次计数周期结束后产生更新事件,预分频系数才会生效,它才是真正起作用的寄存器。当预分频值为0时,计数器就一直为0,直接输出原频率,当值为1时,计数器就010101这样计数,在回到0的时候,输出一个脉冲,那么输出的频率就为原来频率的一半了。预分频器的值和实际的分频系数相差1。计数器计数频率=时钟频率/(预分频器值+1)。
计数器时序
如图,预分频因子为2,那么定时器时钟输出的频率为预分频器输入时钟的一半,计数器在每个上升沿自增,增加到0036发生溢出,再来一个上升沿计数器清零,产生一个更新时间脉冲,还会置一个更新中断标志位UIF为1,就会去申请中断,就要在中断程序中手动清零。计数器溢出频率=CK_PSC/(PSC + 1)/(ARR + 1),溢出时间就是取这个值的倒数。
ARR缓冲寄存器
1、计数器无预装时序
通过设置ARPE位就可以设置预装功能,如图无预装时,当更改自动重装(加载)寄存器时,设置会立即生效,如图中由FF改为36,那么计数器到36就会触发跟新事件。
2、计数器有预装时序
有预装时,影子寄存器才是真正起作用的寄存器,在下一个计时周期才会生效。如图当更改寄存器时,寄存器在F5时就会触发更新事件,如果没有预装,那么计数器会一直记到FFFF清零后再记到36才会触发更新事件。
RCC时钟树
四个震荡源,内部8MHz高速RC震荡器,外部4-16MHz石英晶体振荡器,一般为8MHz,外部32.768KHz低速晶振,一般给RTC提供时钟,内部40KHz低速RC震荡器,给看门狗提供时钟。外部晶振时钟比内部时钟更加稳定。首先启动内部8MHz时钟,选择暂时以内部8MHz时钟运行,然后再启动外部时钟,进入PLL锁相环进行倍频,8MHz变为9倍,得到72MHz,等到锁相环输出稳定后选择锁相环输出为系统时钟。
程序1(使用内部时钟计时实现一秒加1):
首先新建两个文件Timer.c和Timer.h
主要流程:
RCC开启时钟->选择时钟源->配置时基单元->配置输出中断控制,允许更新中断输出到NCIC->配置NVIC(打开定时器通道并分配优先级)->使能定时器让计数器运行->计数器更新触发中断函数
Timer.c代码:
#include "stm32f10x.h" // Device header
//extern uint16_t Num;//扩展作用域
void Timer_Init() {
//初始化定时器
//RCC->时钟源->配置时基单元->配置输出中断控制,允许更新中断输出到NCIC->配置NVIC(打开定时器通道并分配优先级)->使能计数器->中断函数
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
TIM_InternalClockConfig(TIM2);//不写也行,默认就是内部时钟
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;//1分频
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//向上计数
TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1;//ARR自动重装器的值,取值0~65535
TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1;//定时1秒,取值0~65535,预分频器分频7200得到10K计数频率
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;//高级定时器的重复计数器
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
TIM_ClearFlag(TIM2, TIM_FLAG_Update);//上电清除中断标志位
//TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
TIM_ITConfig(TIM2,TIM_IT_Update, ENABLE);//开启更新中断到NVIC的通道
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_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);//开启定时器
}
/*void TIM2_IRQHandler() {//定时器中断函数
if(TIM_GetITStatus(TIM2, TIM_IT_Update) == SET) {
//Num++;
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);//清除中断标志位
}//更新中断
}
*/
Timer.h代码:
#ifndef __TIMER_H
#define __TIMER_H
void Timer_Init();
#endif
主函数main.c代码:
#include "stm32f10x.h" // Device header
#include "DELAY.h"
#include "OLED.h"
#include "Timer.h"
uint16_t Num;
uint32_t Num_1;
int main() {
OLED_Init();
//OLED_ShowChar(1,1,'A');
//OLED_ShowString(1,1,"Hello,World!");
//OLED_ShowString(1,1,"Project2:");
OLED_ShowString(2,1,"Num:");
Timer_Init();
//Delay_ms(5000);
//OLED_Clear();
//GPIOA_LED_CONTROL(GPIO_Pin_1,ENABLE);
while(1){
OLED_ShowNum(3, 1, Num, 5);
OLED_ShowNum(4,1,TIM_GetCounter(TIM2),5);
OLED_ShowNum(1,1,Num_1,9);
}
}
void TIM2_IRQHandler() {//定时器中断函数
if(TIM_GetITStatus(TIM2, TIM_IT_Update) == SET) {
Num++;
for(int i = 0; i < 10000000; i++) {
Num_1++;
}
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);//清除中断标志位
}//更新中断
}
OLED库函数:
程序现象:
将main.c的程序中i<10000000改为i<0后上传程序,现象为每1秒钟NUM加1。
这里有个问题:
当定时中断的的中断函数中的代码执行的时间超过定时器定时的时间程序会有什么现象呢?例如我这里的定时器为1s产生一次中断,在中断函数中我写了一个超过1s的中断程序,这时候会发生什么现象呢?
答案是主程序会一直等待到中断结束才会开始继续下一轮的计时周期,可以不改动main.c代码实验一下。
程序2(使用PA0外部时钟ETR的引脚)
使用触摸传感器,当触摸时,中间的引脚会产生上升沿,将中间的引脚接开发板的PA0引脚。
开始编程:
Timer.c
#include "stm32f10x.h" // Device header
//extern uint16_t Num;//扩展作用域
void Timer_Init() {
//初始化定时器
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//开启GPIOA的时钟
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//推荐配置(浮空输入),这里采用上拉输入,如果外部输入功率很小可以用浮空输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
TIM_ETRClockMode2Config(TIM2,TIM_ExtTRGPSC_OFF,TIM_ExtTRGPolarity_NonInverted,0x00);
//时钟源为TIM2,不分频,不反向,上升沿有效,不用滤波器
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;//1分频
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//向上计数
TIM_TimeBaseInitStructure.TIM_Period = 10 - 1;//从0-9
TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 1;//不分频
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;//高级定时器的重复计数器
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
TIM_ClearFlag(TIM2, TIM_FLAG_Update);//上电清除中断标志位
//TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
TIM_ITConfig(TIM2,TIM_IT_Update, ENABLE);//开启更新中断到NVIC的通道
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_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);//开启定时器
}
uint16_t Timer_GetCounter() {
return TIM_GetCounter(TIM2);
}
/*void TIM2_IRQHandler() {//定时器中断函数
if(TIM_GetITStatus(TIM2, TIM_IT_Update) == SET) {
//Num++;
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);//清除中断标志位
}//更新中断
}
*/
Timer.h同上
主函数main.c:
#include "stm32f10x.h" // Device header
#include "DELAY.h"
#include "OLED.h"
#include "Timer.h"
uint16_t Num;
int main() {
OLED_Init();
//OLED_ShowChar(1,1,'A');
//OLED_ShowString(1,1,"Hello,World!");
OLED_ShowString(1,1,"Project2:");
OLED_ShowString(2,1,"Num:");
Timer_Init();
//Delay_ms(5000);
//OLED_Clear();
//GPIOA_LED_CONTROL(GPIO_Pin_1,ENABLE);
while(1){
OLED_ShowNum(3, 1, Num, 5);
OLED_ShowNum(4,1,TIM_GetCounter(TIM2),5);
}
}
void TIM2_IRQHandler() {//定时器中断函数
if(TIM_GetITStatus(TIM2, TIM_IT_Update) == SET) {
Num++;
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);//清除中断标志位
}//更新中断
}
程序现象:触摸一次,计数器的值加1,触摸10次进入中断,NUM的值加1。