定时器的基本功能:定时中断功能、内外时钟源选择
定时器可以对输入的时钟进行计数,并在计数值达到设定值时触发中断
16位计数器(执行计数定时的一个寄存器)、预分频器(对计数器的时钟进行分频)、自动重装寄存器(计数的目标值,就是我想要计多少个时钟申请中断)的时基单元,在72MHz计数时钟下可以实现最大59.65s的定时,可以使定时器进行级联,达到指数爆炸的定时时间。
不仅具备基本的定时中断功能,而且还包含内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等多种功能
根据复杂度和应用场景分为了高级定时器、通用定时器、基本定时器三种类型
- STM32F103C8T6定时器资源:TIM1、TIM2、TIM3、TIM4
基本定时器
基本定时器只能选择内部时钟(CK_INT,默认72Mz)
预分频器:输出=输入/自己配置个数;实际分频系数=预分频器的值+1。
CNT计数器:会根据分配后的数据进行记录,当达到目标值时,产生中断,完成定时的任务;默认是向上计数;在通用和高级定时器中还有向下计数,和中央对齐(就是从0一直加,加到目标值后,又递减)
自动重装载寄存器:存储目标值,当计数器的值==自动重装载寄存器的目标值时,会产生一个中断信号 ,并清零计数器,与CNT计数器相连接。
对于自动重装载寄存器的中断信号,一般叫更新中断,之后会通过NVIC,再配置好NVIC的定时器通道,那定时器的更新中断可以被CPU响应。
:产生一个事件,同理在自动重装载寄存器中,为更新事件
定时器的一大特色:DAC主从触发模式:可以让内部的硬件在不受程序的控制下实现自动运行
在使用DAC时,可能会用DAC输出一段波形:需要每隔一段时间来触发一次DAC,让它输出下一个电压点;
如果用中断,会不断的触发中断来响应触发输出(TRGO),会很频繁
如用主从触发模式,会触发事件,由事件映射到触发输出(TRGO),然后TRGO直接接到DAC的触发转换引脚上。整个过程不需要软件的参与,实现了硬件的自动化。
通用定时器:
高级定时器
定时中断基本结构
中断输出控制:是一个中断输出的允许位,用来处理有必要的中断和无必要的中断
预分频器时序
- 计数器计数频率:CK_CNT = CK_PSC / (PSC + 1)
计数器时序
- 计数器溢出频率:CK_CNT_OV = CK_CNT / (ARR + 1)
= CK_PSC / (PSC + 1) / (ARR + 1)
实际分频系数=预分频器的值+1。
ARR:自动重装载寄存器
在电路中含有阴影的,都有缓冲机制
在计数器里的ARR中就有缓冲器;且是手动配置;
计数器无预装时序
计数器有预装时序
RCC时钟树:
时钟树是STM32中用来产生和配置时钟,并且把配置好的时钟发送到各个外设的系统;
时钟是所有外设运行的基础,所以必须优先配置
STM32F10xxx参考手册(中文).pdf-13-15章
接线图:
定时器定时中断
定时器不涉及外部设备,所以放入System的文件夹中
void TIM_DeInit(TIM_TypeDef* TIMx); //恢复初始
void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef*TIM_TimeBaseInitStruct); //时基单元初始化
void TIM_TimeBaseStructInit(TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct); //对结构体变量赋一个默认值
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState); 使能计数器——运行控制
void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState); //用来使能中断信号的——中断输出控制
//以下6个函数,选择外部时钟:
void TIM_InternalClockConfig(TIM_TypeDef* TIMx); //选择内部时钟
void TIM_ITRxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource); //选择ITRx其他定时器的的时钟
void TIM_TIxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_TIxExternalCLKSource,
uint16_t TIM_ICPolarity, uint16_t ICFilter);//选择Tix捕获通道的时钟
void TIM_ETRClockMode1Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity,
uint16_t ExtTRGFilter);//选择ETR通过外部时钟模式1输入时钟
void TIM_ETRClockMode2Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler,
uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter); //选择ETR通过外部时钟模式2输入时钟
void TIM_ETRConfig(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity,
uint16_t ExtTRGFilter);//单独来配置ETR引脚的预分频器、极性、滤波器参数的
void TIM_PrescalerConfig(TIM_TypeDef* TIMx, uint16_t Prescaler, uint16_t TIM_PSCReloadMode);//单独写预分频值的
void TIM_CounterModeConfig(TIM_TypeDef* TIMx, uint16_t TIM_CounterMode);//用来改变计数器的计数模式
void TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState); //自动重装载寄存器功能配置
void TIM_SetCounter(TIM_TypeDef* TIMx, uint16_t Counter);//给计数器写一个值
void TIM_SetAutoreload(TIM_TypeDef* TIMx, uint16_t Autoreload);//给自动重装载寄存器写一个值
uint16_t TIM_GetCounter(TIM_TypeDef* TIMx);//获取当前计数器的值
uint16_t TIM_GetPrescaler(TIM_TypeDef* TIMx);//获取当前预分频器的值
//以下四个函数,用来获取标志位和清除标志位的
FlagStatus TIM_GetFlagStatus(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);
void TIM_ClearFlag(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);
ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t TIM_IT);
void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT);
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Time.h"
//我们想让定时器每秒自动帮我们加一下Num这个变量
uint16_t Num;
int main(void){
OLED_Init();
Time_Init();
OLED_ShowString(1,3,"hello world!"); //如果超出16个字,会从头到尾重新输入
OLED_ShowString(2,3 ,"Num:");
while(1){
OLED_ShowNum(2,7,Num,5);
OLED_ShowNum(3,7,TIM_GetCounter(TIM2),5);
}
}
void TIM2_IRQHandler(void){
//判断标志位是否存在
if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET){
Num++;
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}
}
Time.c
#include "stm32f10x.h" // Device header
//1.RCC开启时钟
//2.选择时基单元的时钟源,我们选择内部时钟源
//3.配置时基单元,通过结构体配置
//4.配置输出中断控制,允许更新中断输出到NVIC
//5.配置NVIC,并打开定时器中断的通道,并分配一个优先级
//6.运行控制,对定时器进行使能,配置中断函数
void Time_Init(void){
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE); //开启APB1的时钟函数,TIM2在APB1总线中
//选择时基单元
TIM_InternalClockConfig(TIM2);//系统默认是内部时钟,不写也可以
//配置时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1; //给输入的滤波器一个采样频率
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;//计数方式
//因为公式里CK_PSC / (PSC + 1) / (ARR + 1),所以两个值需要手动减1
//设置1s,默认是72Mz,分频分7200,每一个波形就是10k,再设置ARR目标值为10000的时候为一个周期
TIM_TimeBaseInitStructure.TIM_Period=10000-1;//ARR自动重装器的值
TIM_TimeBaseInitStructure.TIM_Prescaler=7200-1;//预分配器的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0;//重复计数器的值,我们用的通用寄存器,所以直接写0就好了
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);//我们发现当我们按下复原键时,定时器是从1开始的,就说明复原的时候,自己走了一次,在此函数中发现它为了让值立刻起作用,所以它自己更新了中断’
//想要从0开始,就需要把此时中断的标志值清空
TIM_ClearFlag(TIM2,TIM_IT_Update);
//使能更新中断
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);//开启了更新中断到NVIC的通路
//配置NVIC
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//配置NVIC的分组通道
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel=TIM2_IRQn;//匹配NVIC的TIM2通道
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(void){
// //判断标志位是否存在
// if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET){
// Num++;
// TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
// }
//}
//由于我们打算在主文件上使用这个中断函数来改写Num的值,第一个方法:extern Num到Time.c中;第二个方法,把中断函数放到主文件里
定时器外部时钟
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Time.h"
//我们想让定时器每秒自动帮我们加一下Num这个变量
uint16_t Num;
int main(void){
OLED_Init();
Time_Init();
OLED_ShowString(1,3,"hello world!"); //如果超出16个字,会从头到尾重新输入
OLED_ShowString(2,1 ,"Num:");
OLED_ShowString(3,1 ,"Count:");
while(1){
OLED_ShowNum(2,7,Num,5);
OLED_ShowNum(3,7,Timer_GetCount(),5);
}
}
void TIM2_IRQHandler(void){
//判断标志位是否存在
if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET){
Num++;
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}
}
Time.c
#include "stm32f10x.h" // Device header
//1.RCC开启时钟
//2.选择时基单元的时钟源,我们选择内部时钟源
//3.配置时基单元,通过结构体配置
//4.配置输出中断控制,允许更新中断输出到NVIC
//5.配置NVIC,并打开定时器中断的通道,并分配一个优先级
//6.运行控制,对定时器进行使能,配置中断函数
void Time_Init(void){
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE); //开启APB1的时钟函数,TIM2在APB1总线中
//初始化GPIO引脚
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
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);
//选择Ext时基单元
TIM_ETRClockMode2Config(TIM2, TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_NonInverted, 0x00);
//配置时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1; //给输入的滤波器一个采样频率
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;//计数方式
//因为公式里CK_PSC / (PSC + 1) / (ARR + 1),所以两个值需要手动减1
//不需要分频的原因是我们是使用的手动拨频,是有一定的限制的,很慢,不像机器
TIM_TimeBaseInitStructure.TIM_Period=10-1;//ARR自动重装器的值
TIM_TimeBaseInitStructure.TIM_Prescaler=1-1;//预分配器的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0;//重复计数器的值,我们用的通用寄存器,所以直接写0就好了
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);//我们发现当我们按下复原键时,定时器是从1开始的,就说明复原的时候,自己走了一次,在此函数中发现它为了让值立刻起作用,所以它自己更新了中断’
//想要从0开始,就需要把此时中断的标志值清空
TIM_ClearFlag(TIM2,TIM_IT_Update);
//使能更新中断
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);//开启了更新中断到NVIC的通路
//配置NVIC
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//配置NVIC的分组通道
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel=TIM2_IRQn;//匹配NVIC的TIM2通道
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(void){
// //判断标志位是否存在
// if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET){
// Num++;
// TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
// }
//}
//由于我们打算在主文件上使用这个中断函数来改写Num的值,第一个方法:extern Num到Time.c中;第二个方法,把中断函数放到主文件里
//实时查看CNT计数器的值
uint16_t Timer_GetCount(void){
return TIM_GetCounter(TIM2);
}