IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> STM32(9):定时器 -> 正文阅读

[嵌入式]STM32(9):定时器

概述

本章主要讲述一下定时器的实现,定时器顾名思义,就是到了指定的时间就触发中断,所以定时器本身是中断的一种应用。本节的例子中实现的每秒输出一个字符。

代码概览

	#include "stm32f10x_gpio.h"
	#include "stm32f10x_rcc.h"
	#include "stm32f10x_exti.h"
	#include "misc.h"
	#include "stm32f10x_gpio.h"
	#include "stm32f10x_rcc.h"
	#include "stm32f10x_tim.h"
	
	void tim2_init()
	{
		TIM_TimeBaseInitTypeDef tim2;
	
		RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
		tim2.TIM_Period = 1999;		
		tim2.TIM_Prescaler = 3499;	
		tim2.TIM_CounterMode = TIM_CounterMode_Up;			
		tim2.TIM_ClockDivision = TIM_CKD_DIV1;				
	
		TIM_TimeBaseInit(TIM2, &tim2); 
		TIM_Cmd(TIM2, ENABLE);		   
	
		TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
	}
	
	void tim2_nvic_init(void)
	{
		NVIC_InitTypeDef tim2_nvic;
		NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
		tim2_nvic.NVIC_IRQChannel = TIM2_IRQn;
		tim2_nvic.NVIC_IRQChannelCmd = ENABLE;
		
		tim2_nvic.NVIC_IRQChannelPreemptionPriority = 0;
		tim2_nvic.NVIC_IRQChannelSubPriority = 1;
	
		NVIC_Init(&tim2_nvic);
	}
	
	int count = 0;
	void TIM2_IRQHandler(void)
	{
		if (SET == TIM_GetITStatus(TIM2, TIM_IT_Update))
		{
			//uart1_send("TIM2 IT\r\n");
			//printf("TIM2 IT\r\n");
			TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
		}
	}
	
	int main(void)
	{
		tim2_init();
		tim2_nvic_init();
		
		while (1)
		{
	     //delay(1);
		}
	}

main函数入手

int main(void)
	{
		tim2_init();
		tim2_nvic_init();
		
		while (1)
		{
	     //delay(1);
		}
	}

可以看到本小节核心代码只有两行,while语句只是用来时间循环,避免程序退出。

定时器初始化

第一行代码是“tim2_init();”,来看一下它的实现:

	void tim2_init()
	{
		TIM_TimeBaseInitTypeDef tim2;
	
		RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
		tim2.TIM_Period = 1999;		
		tim2.TIM_Prescaler = 3499;	
		tim2.TIM_CounterMode = TIM_CounterMode_Up;			
		tim2.TIM_ClockDivision = TIM_CKD_DIV1;			
	
		TIM_TimeBaseInit(TIM2, &tim2); 
		TIM_Cmd(TIM2, ENABLE); 
	
		TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
	}

定时器类型

首先定义一个TIM_TimeBaseInitTypeDef 的结构体,用来保存配置信息;
然后是使能通过调用RCC_APB1PeriphClockCmd,传参RCC_APB1Periph_TIM2来使能APB1以及TIM2;在STM32F1系列里面(我的板子是STM32F103C8),定义了2个看门狗定时器,1个系统滴答定时器,还有8个Timer定时器;这里的TIM2就是8个定时器里面的;在8个定时器里面:

  1. TIM6和TIM7是基本定时器,分辨率为16位(即2^16 = 65535);只有计数/定时功能,没有外部IO功能;
  2. TIM2/3/4/5是通用定时器,分辨率为16位,具有计数定时,输出比较,输入捕获功能;同时有4个外部IO;代码中使能的TIM2就是属于通用寄存器;
  3. TIM1,TIM8是高级定时器,分辨率是16位,具有计数定时,输出比较,输入捕获,三相电机互补输出信号功能;同时有8个外部IO;
    使能TIM2为什么要选择APB1总线呢?还是要回到系统架构图(3.1 System Architecture),可以看到通用定时器以及基本定时器(TIM2~TIM7)都是挂在在APB1下面,而高级定时器则是挂在APB2下面,所以使能TIM2,就要指定其所在的总线APB1:
    在这里插入图片描述

定时器的计算

定时器的计时过程计算有点复杂,我们先来看一下他的模块图(15.2 TIMx main features):
在这里插入图片描述

首先是将CK_PSC值赋给prescaler(PSC),然后是将CK_CNT的值赋给CNT counter,最后由Auto-reload register(ARR)来控制Counter增加/ 下降,以及重启。这三个部分我们逐一来看;
第一部分是prescaler,中文意思是预分频器,为什么要有预分频器呢?
如果定时器采用内部时钟,一般都是很大,比如72MHz,对于Int计数上限就是65535,为什么是65535呢,因为STM32的定时器的分辨率是16位,所谓分辨率就是定时器计数可以用多少bit来表示,16位能够表示的上限,即2^16=65535,如果所以如果溢出(达到65535),只需要0.9ms。计算过程如下:

72MHz=>T = 1/72(us,微妙) <=> 1/72000(ms,毫秒);

所以65535计数所需要的时间:t = 65535 / 72000 ≈ 0.9ms;
所以这个定时器溢出时间在很多场景下是不适用的,如果我想要定时时间为1s咋个办?所以需要降频,于是需要分频,这里需要指定分频系数,来进行分频,是指达到目标量级,比如我通过72分频,将时钟频率降为1MHz,这样每个周期就是1us,于是1000次计数就可以达到1ms;10000次就可以达到1s;
可以看到,通过预分频器,可以通过调整度量时间长短,达到方便的控制定时时长,这个就是预分频器的价值。

第二部分是CNT Counter,这个就是计数器寄存器,其实最重要的还是第三部分auto-reload register(ARR);ARR作为计算代表每个周期翻转的次数,我们用T代表翻转一次所需要的时间;样溢出时间t的公式:

t = 翻转次数*每次翻转的时间;

如果ARR代表翻转的序号,从0开始,那么ARR+1代表翻转次数,f代表时钟频率,于是有:

t =(ARR+1)/ (1/f)

再考虑引入分频 倍数PSC,也是0开始;其实相当于降低频率,即f_psc = f / (PSC+1),于是有:

t = (ARR + 1) / (1 / f_psc) = (ARR+1) / (1 / f / (PSC+1));
t = (ARR + 1) * (PSC+1) / f

比如我现在想要控制时间是1us(微秒),即10^(-6),假设当前系统频率是36MHz,分频系数PSC=1,即:

10^(-6) = (ARR+1)/ (1 /  36*10^6/2)  => ARR = 17

即时钟翻转18次完成一个1us计时;
我们再回到代码中:

tim2.TIM_Period = 1999;		
tim2.TIM_Prescaler = 3499;

TIM_Period就是上面提到的ARR,Prescaler即分频器,分频倍数是3500;假设时钟是72MHz,通过上面的公式可以获得t约等于0.1s。

时钟分频系数

之类clockdivision是采样时钟倍频系数,如果系统时钟频率很快,但是我们可能只是需要每两个,或者每四个周期进行一次采样(这里描述的输入捕获),这里可以设置一下分频系数;详细的内容我们将会在输入捕获小节中进行讲解,这里只要知道描述的采样间隔即可,用于减少中断次数。

tim2.TIM_ClockDivision = TIM_CKD_DIV1;

定时器配置生效

TIM_TimeBaseInit函数将上面配置定时器的参数写入到相应的寄存器里面:

TIM_TimeBaseInit(TIM2, &tim2); 

使能定时器计数器

TIM_Cmd这是使用定时器的计数器:

TIM_Cmd(TIM2, ENABLE); 

这个函数的名称还是具有一定的迷惑性,直译是定时器使能(在STM32里面Cmd一般代表使能之意),但是,我们跟踪到TIM_Cmd的内部实现,根据注释可以看到,这个函数主要是通过配置CR1寄存器来实现使能指定定时器的计数器:

	void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState)
	{
	  /* Check the parameters */
	  assert_param(IS_TIM_ALL_PERIPH(TIMx));
	  assert_param(IS_FUNCTIONAL_STATE(NewState));
  
	  if (NewState != DISABLE)
	  {
	    /* Enable the TIM Counter */
	    TIMx->CR1 |= TIM_CR1_CEN;
	  }
	  else
	  {
	    /* Disable the TIM Counter */
	    TIMx->CR1 &= (uint16_t)(~((uint16_t)TIM_CR1_CEN));
	  }
	}

使能中断响应

TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);

这里主要是配置了定时器要响应哪种中断源,中断源也是有很多种的,这里配置的是TIM_IT_Update,下面我们看一下中断源:

	"stm32f10x_tim.h"
	/** @defgroup TIM_interrupt_sources 
	  * @{
	  */

	#define TIM_IT_Update                      ((uint16_t)0x0001)
	#define TIM_IT_CC1                         ((uint16_t)0x0002)
	#define TIM_IT_CC2                         ((uint16_t)0x0004)
	#define TIM_IT_CC3                         ((uint16_t)0x0008)
	#define TIM_IT_CC4                         ((uint16_t)0x0010)
	#define TIM_IT_COM                         ((uint16_t)0x0020)
	#define TIM_IT_Trigger                     ((uint16_t)0x0040)
	#define TIM_IT_Break                       ((uint16_t)0x0080)

下面的是TIM_ITConfig的函数注释,对于每种中断源有介绍:

"stm32f10x_tim.c"
/**
  ... ...
  * @param  TIM_IT: specifies the TIM interrupts sources to be enabled or disabled.
  *   This parameter can be any combination of the following values:
  *     @arg TIM_IT_Update: TIM update Interrupt source
  *     @arg TIM_IT_CC1: TIM Capture Compare 1 Interrupt source
  *     @arg TIM_IT_CC2: TIM Capture Compare 2 Interrupt source
  *     @arg TIM_IT_CC3: TIM Capture Compare 3 Interrupt source
  *     @arg TIM_IT_CC4: TIM Capture Compare 4 Interrupt source
  *     @arg TIM_IT_COM: TIM Commutation Interrupt source
  *     @arg TIM_IT_Trigger: TIM Trigger Interrupt source
  *     @arg TIM_IT_Break: TIM Break Interrupt source
 ... ...
  */
void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState)

第一种是TIM_IT_Update,也就是我们本次实验使用到的中断源,它代表响应定时器的计数器的初始化(通过软件,或者内部/ 外部触发),上溢/ 下溢,即定时器计数达到峰值之后将会触发中断;

第二种则是输出捕获(Capture)以及输入比较(Compare)触发中断你,后面的实验中将会有介绍;

第三种是触发事件,即“触发”计数后的中断响应,能够触发“计数”的场景包括计数器启动、停止、初始化或者由内部/外部触发计数;
第四种则是定时器中止事件的中断响应;

TIM2的NVIC配置

void tim2_nvic_init(void)
{
    NVIC_InitTypeDef tim2_nvic;
    tim2_nvic.NVIC_IRQChannel = TIM2_IRQn;
    tim2_nvic.NVIC_IRQChannelCmd = ENABLE;
    
    tim2_nvic.NVIC_IRQChannelPreemptionPriority = 0;
    
    tim2_nvic.NVIC_IRQChannelSubPriority = 1;

    NVIC_Init(&tim2_nvic);
}

TIM2的NVIC大部分都是上一个小节介绍的内容,这里就不再多做介绍。

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2022-01-29 23:14:49  更:2022-01-29 23:15:29 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 11:51:06-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码