当前位置: 首页 > news >正文

STM32学习笔记5-TIM定时器-1

定时器的基本功能:定时中断功能、内外时钟源选择

定时器可以对输入的时钟进行计数,并在计数值达到设定值时触发中断

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的通路//配置NVICNVIC_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的通路//配置NVICNVIC_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);
}

http://www.lryc.cn/news/614117.html

相关文章:

  • 线程池基础知识
  • wstool和catkin_tools工具介绍
  • 智慧社区(十)——声明式日志记录与小区地图功能实现
  • Python实现点云PCA配准——粗配准
  • Ubuntu安装 L20显卡驱动
  • Linux网络--2、Socket编程
  • 中国电信清华:大模型驱动的具身智能发展与挑战综述
  • 动漫软件集合分享
  • Pytest项目_day08(setup、teardown前置后置操作)
  • 144.二叉树的前序遍历
  • 鲸签云解决互联网行业合同管理难题​
  • 【Rust】多级目录模块化集成测试——以Cucumber为例
  • 线程组和线程池的基本用法
  • 【Spring Boot 快速入门】八、登录认证
  • duxapp 2025-05-29 更新 兼容鸿蒙C-API方案,现在鸿蒙端可以用于生产
  • React SSR 水合问题
  • 《告别Bug!GDB/CGDB调试实战指南》
  • TF 上架全流程实战,从构建到 TestFlight 分发
  • UniApp 跳转外部链接实现
  • Elasticsearch LTR(Learning To Rank)从训练到检索与重排
  • Elasticsearch:在向量搜索中使用 Direct IO
  • 力扣-438.找到字符串中所有字母异位词
  • ctfshow_萌新web9-web13-----rce
  • python学智能算法(三十五)|SVM-软边界拉格朗日方程乘子非负性理解
  • LeetCode 刷题【34. 在排序数组中查找元素的第一个和最后一个位置、35. 搜索插入位置】
  • 文件管理从基础到高级:文件描述符、超大文件切片重组与快速删除实战
  • 五、CV_ResNet
  • 腾讯iOA:数据安全的港湾
  • wordpress的wp-config.php文件的详解
  • proteus实现简易DS18B20温度计(stm32)