1000字范文,内容丰富有趣,学习的好帮手!
1000字范文 > 【嵌入式学习-STM32F103-TIM-定时中断和外部时钟】

【嵌入式学习-STM32F103-TIM-定时中断和外部时钟】

时间:2019-10-10 07:39:25

相关推荐

【嵌入式学习-STM32F103-TIM-定时中断和外部时钟】

TIM目录

定时器四部分讲解内容,本文是第一部分TIM简介基本定时器主从触发通用定时器总结通用定时器与高级定时器的区别==定时中断基本结构图==定时中断和内外时钟源选择时序预分频器时序计数器时序 RCC时钟树ST配置流程 ==代码部分==程序现象定时中断接线图步骤Timer.cTimer.hmain.c 外部时钟接线图Timer.cTimer.hmain.c

定时器四部分讲解内容,本文是第一部分

1、定时器基本定时,定一个时间,然后让定时器每隔一段时间产生一个中断,来实现每隔一个固定时间执行一段程序的目的,比如要做一个时钟、秒表或者使用一些程序算法

2、定时器输出比较的功能,输出比较这个模块最常见的用途是产生PWM波形,用于驱动电机等设备,使用stm32的PWM波形来驱动舵机和直流电机的例子

3、定时器输入捕获的功能,学习使用输入捕获这个模块来测量方波频率的例子

4、定时器的编码器接口,使用这个编码器接口,能够更加方便地读取正交编码器的输出波形,在编码电机测速中,应用广泛

TIM简介

72M/65536/65536得到中断频率,取倒数就是59.65s

级联,一个定时器的输出,当作另外一个定时器的输入,最大时间达到59.65s * 65536 * 65536

高级定时器连接的是性能更高的APB2总线,通用定时器和基本定时器连接的是APB1总线,在RCC开启时钟时注意。

高级定时器具有重复计数器、死区生成、互补输出、刹车输入等功能主要是为了三相无刷电机的驱动设计的。

高级到低级向下兼容

C8T6只有1个高级定时器和3个通用定时器

基本定时器

时基单元:预分频器、计数器、自动重装载寄存器

预分频器之前,连接的就是基准计数时钟的输入,由于基本定时器只能选择内部时钟,所以可以认为这根线直接到输入端这里,也就是内部时钟CK_INT,内部时钟来源是RCC_TIMxCLK,频率值一般都是系统的主频72MHZ。

如果预分频器写1,那就是2分频,输出频率=输入频率/2=36MHZ

如果预分频器写2,那就是3分频,输出频率=输入频率/3=24MHZ

预分频器的值和实际的分频系数相差1,即实际分频系数=预分频器的值+1,这个预分频器的值是16位,所以最大值可以写65535,也就是65536分频。预分频器就是对输入的基准频率提前进行一个分频的操作。

计数时钟每来一个上升沿,计数器的值就加1,计数器是16位,所以里面的值可以从0一直加到65535.如果再加的话,计数器就会回到0重新开始。所以计数器的值在计时过程中会不断自增运行,当自增运行到目标值时,产生中断,那就完成了定时的任务。

自动重装载寄存器:存储目标值的寄存器。在运行的过程中,计数值不断自增,自动重装值是固定的目标,当计数值等于自动重装值时,也就是计时时间到了。那他会产生一个中断信号,并且清零计数器。计数器自动开始下一次的计数计时。

UI折线箭头代表这里会产生中断信号,像这种计数值等于自动重装值产生的中断,称为更新中断,这个更新中断之后,就会通往NVIC,我们再配置好NVIC的定时器通道,那么定时器的更新中断就能够得到CPU的响应。

U向下箭头表示更新事件,更新事件不会触发中断,但可以触发内部其他电路的工作。

主从触发

它能让内部的硬件在不受程序的控制下实现自动运行。主模式触发DAC有啥用?

在我们使用DAC时,可能会用DAC输出一段波形,那就需要每隔一段时间来触发一次DAC,让它输出下一个电压点。正常思路,先设置一个定时器产生中断,每隔一段时间在中断程序中调用代码手动触发一次DAC转换,然后DAC输出,缺点:这样会使主程序处于频繁被中断的状态,这回影响主程序的运行和其他中断的响应。

所以定时器就设计了一个主模式,使用主模式可以把这个定时器的更新事件映射到这个触发输出TRGO(trigger out)的位置,然后TRGO直接接到DAC的触发转换引脚上。这样,定时器的更新就不需要再通过触发中断来触发DAC转换。无需软件参与,实现硬件自动化

通用定时器

通用定时器和高级定时器支持3种计数模式,向上计数、向下计数和中央对齐这三种模式。

内外时钟源选择和主从触发模式结构

对于基本定时器而言,定时只能选择内部时钟,也就是系统频率72MHZ,

对于通用定时器而言,定时还可以选择外部时钟,第一个外部时钟来自TIMx_ETR引脚的外部时钟。

TIMx_ETR是一个定时器的输入端口,可以用来接收外部的时钟信号。在其引脚接上一个外部方波时钟。然后配置一下内部的极性选择、边沿检测和预分频器电路,再配置输入滤波电路,这些电路可以对外部时钟进行一定的整型,因为外部引脚时钟,难免有毛刺,这些电路可对输入的波形进行滤波。

滤波后的信号兵分两路

上面一路ETRF进入触发控制器,可以选择作为时基单元的时钟。如果要在ETR外部引脚提供时钟或者相对ETR时钟进行计数,把这个定时器当作计数器来用,可配置以下一路的电路,stm32中称为==“外部时钟模式2”==

除了ETRF可以提供时钟,TRGI还可以提供时钟,主要用作触发输入来使用的。

触发输入作为外部时钟来使用,称为“外部时钟模式1”

通过TRGI有5通路

TRGO可以通向其他定时器,而通向其他定时器的时候,就可以接到其他定时器的ITR引脚上,通过这一路就可以实现定时器级联

总结

外部时钟模式1的输入可以是ETR引脚,其他定时器,CH1引脚的边沿、CH1引脚和CH2引脚。

一般情况下,外部时钟通过ETR引脚即可

对于时钟输入而言,最常用的还是内部的72MHZ的时钟,如果要使用外部时钟,首选ETR引脚外部时钟模式2的输入。,这一路最简单最直接

定时器的编码器接口,可以读取正交编码器的输出波形

定时器的主模式输出,可以把内部的一些事件映射到这个TRGO引脚上,比如基本定时器分析中,将更新事件映射到TRGO,用于触发其他定时器、DAC或ADC。

右边:输出比较电路,总共有4个通道,可以用于输出PWM波形,驱动电机

左边:输入捕获电路,总共四个通道,可以用于测量输入方波的频率、占空比

中间寄存器:捕获/比较寄存器,是输入捕获和输出比较电路公用的,因为输入捕获和输出比较不能同时使用,因此寄存器机器引脚是公用的。

通用定时器与高级定时器的区别

第一个:申请中断的地方,增加了一个重复次数计数器,有了这个计数器之后,就可以实现每隔几个计数周期,才发生一次更新事件和更新中断。原来的结构是每个计数周期完成后都会发生更新。相当于对输出的更新信号又做了一次分频。

以下是高级定时器对输出比较模块的升级

DTG是死区生成电路,右边的输出引脚由原来的一个变为两个互补的输出,可以输出一对互补的PWM波。为了驱动三相无刷电机

刹车输入:给电机驱动提供安全保障,如果外部引脚BKIN产生了刹车信号,或者内部时钟失效,产生了故障,那么控制电路就会自动切断电机的输出,防止以外的发生。

定时中断基本结构图

定时中断和内外时钟源选择

运行控制:就是控制寄存器的一些位,比如启动停止、向上或向下计数等,操作这些寄存器,就能控制时基单元的运行。

左边:为时基单元提供时钟的部分.

第一个定时中断使用RCC的内部时钟

第二个定时器外部时钟就是用外部时钟模式2

右边:计时时间到,产生更新中断后信号的去向(如果高级定时器,还会有一个重复计数器)

中断信号会先在状态寄存器里置一个中断标志位,这个标志位会通过中断输出控制,到NVIC申请中断。中断输出控制就是一个中断输出的允许位,如果需要某个中断,就记得允许一下。

时序

预分频器时序

缓冲寄存器(影子寄存器)真正起作用的寄存器,比如我们在某一时刻,把预分频寄存器由0改为1,如果在此时立刻改变时钟的分频系数,

那么就会导致这里,在一个计数周期内,前半部分和后半部分的频率不一样。这里计数计到一半,计数频率突然改变,而缓冲寄存器,当计数计到一半的时候改变了分频值,这个变化并不会立刻生效,而是会等到本次计数周期结束时产生更新事件,预分频寄存器的值才会被传递到缓冲寄存器里面,才会生效。

因此,可以看到,即使在计数中途改变了预分频值,技术频率仍然会保持原来的频率,直到本轮计数完成,在下一轮计数时,改变后的分频值才会起作用。

计数器时序

内部时钟分频因子为2,就是分频系数为2,第一行是内部时钟72MHZ,第二行是时钟使能,高电平启动,第三行是计数器时钟,因为分频系数为2,所以这个频率是内部时钟除2,然后计数器在计数器时钟每个上升沿自增,当增加到0036的时候,发生溢出,那再来一个上升沿,计数器清零,计数器溢出,产生一个更新事件脉冲,另外还会置一个更新中断标志位UIF,该标志位置1,就回去申请中断,然后中断响应后,需要在中断程序中手动清零。

引入影子寄存器的目的实际上是为了同步,就是让值的变化和更新事件同步发生,防止在运行中途更改造成错误。

下图,在计数的中途,突然把F5改为36,影子寄存器是真正起作用的。它在自动加载寄存器改变后,还是F5,所以现在计数的目标还是计算到F5,产生更新事件,同时要更改的36才被传递到影子寄存器里,在下一个计数周期36才会有效。

RCC时钟树

RCC时钟树是stm32用来产生和配置时钟,并且把配置好的时钟发送到各个外设的系统,时钟是所有外设运行的基础,所以时钟是最先需要配置的东西。

中间:SYSCLK是系统时钟72MHZ。在时钟产生电路,有四个震荡源,分别是内部的8MHZ高速RC振荡器,外部的4-16MHZ高速石英晶体振荡器,即晶振,一般是8MHZ,外部的32.768KHZ低速晶振,一般是给RTC提供时钟的,最后是内部的40KHZ低速RC振荡器,这个可以给看门狗提供时钟。

高速晶振是用来提供系统时钟,如提供给AHB APB2 APB1总线

外部的石英振荡器比内部的RC振荡器更加稳定,因此一般用外部晶振,如果系统很简单,不需要精确的时钟,可选择内部时钟

ST配置流程

在SystemInit函数里,ST配置时钟,首先它会启动内部时钟,选择内部8MHZ为系统时钟,暂时以内部8MHZ的时钟运行,然后再启动外部时钟,进入PLL锁相环进行倍频,8MHZ倍频9倍,得到72MHZ,等到锁相环输出稳定后,选择锁相环输出为系统时钟,这样就把系统时钟由8MHZ切换到72MHZ

CSS:安全保障

无论高级、通用还是基本定时器,它们的内部基准时钟都是72MHZ

控制位“外部时钟使能”–>RCC_APB2/1PeriphClockCmd

打开时钟,就是在这个位置写1,让左边的时钟能够通过与门输出给外设。

代码部分

程序现象

1、使用定时中断的功能,定时器使用内部时钟定了1秒的时间,每隔1秒申请一下中断,然后在中断函数里执行Num++,最后在OLED上显示Num

2、定时器外部时钟,使用外部时钟驱动定时器,在定时器指定的外部引脚上,输入一个方波信号,来提供定时器计数的时钟,目前用对射式红外传感器来手动模拟一个外部时钟,用挡光片一次遮挡、移开、遮挡、移开提供一个方波。OLED上的CNT就是定时器中计数器的值,每遮挡移开一次,计数器加1,然后计数器计到9后自动清零。同时申请中断,执行Num++。

定时中断

接线图

步骤

第一步:RCC开启时钟,定时器的基准时钟和整个外设的工作时钟就都会同时打开

第二步: 选择时基单元的时钟源,对于定时中断,我们选择内部时钟源

第三步:配置时基单元,包括预分频器、自动重装器、计数模式等等

第四步:配置输出中断控制,允许更新中断输出到NVIC

第五步:配置NVIC,在NVIC中打开定时器中断的通道,并分配一个优先级

第六步:运行控制

第七步:使能计数器

最后:当计数器更新时,触发中断,写一个定时器中断函数

定时1s,即定时频率为1HZ

相当于1 = 72MHz/(PSC + 1) /(ARR + 1),PSC=7200,ARR=10000

TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1; //ARR的值TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1; //PSC的值,对72M进行7200分频,得到10K的计数频率,在10K的频率下,计算10000个数,就是1s的时间。

在当前工程搜索中断源

启动文件找定时器2的中断服务函数

Timer.c

#include "stm32f10x.h"#include "Timer.h"//如果你想跨文件使用变量,可以在使用变量的那个文件的上面,用extern声明一下要用的变量extern uint16_t Num;//初始化定时器void Timer_Init(void){//开启RCC内部时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);//选择时基单元的时钟(定时器上电后默认就是使用内部时钟)TIM_InternalClockConfig(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; //ARR的值TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1; //PSC的值TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器的值TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);//本质:在调用中断前,中断状态寄存器不能有标志位//避免刚一上电就立刻进入中断,在TimeBaseInit的后面和中断的前面(手动清除中断标志位)TIM_ClearFlag(TIM2, TIM_FLAG_Update);//使能更新中断,开启了更新中断到NVIC的通路TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); //TIM_IT_Update Update更新中断//NVIC配置NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);NVIC_InitTypeDef NVIC_InitStructure;NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn ; //定时器2在NVIC的通道NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//最后NVIC_InitNVIC_Init(&NVIC_InitStructure);//启动定时器,此时定时器开始工作,当产生更新时,就会触发中断TIM_Cmd(TIM2, ENABLE);}//当定时器产生更新中断时,这个函数就会自动被执行void TIM2_IRQHandler(void){//检查中断标志位,TIM_FLAG_Update为想看的中断标志位if(TIM_GetFlagStatus(TIM2, TIM_FLAG_Update)== SET){//清除相应的标志位TIM_ClearITPendingBit(TIM2,TIM_FLAG_Update);Num++;}}

Timer.h

#ifndef __TIMER_H#define __TIMER_Hvoid Timer_Init(void);#endif /*__TIMER_H*/

main.c

在main函数里,中断函数每秒自动把Num++,然后在主循环里显示。

预分频值和自动重装值对中断频率的影响

如果把自动重装值改为1000,那么由原来计10000个数变成了1000个数

#include "stm32f10x.h" // Device header#include "Delay.h"#include "OLED.h"#include "Timer.h"//定义全局变量Num,Num要在中断函数里执行++uint16_t Num;int main(void){OLED_Init();//初始化定时器,此时定时器开始工作Timer_Init();OLED_ShowChar(1, 1, 'A');OLED_ShowString(1, 3, "HelloWorld!");while (1){OLED_ShowNum(1,5,Num,5);}}

外部时钟

接线图

对射式红外传感器,DO数字输出接到PA0引脚,这个PA0引脚就是TIM2的ETR引脚,我们在这个引脚输入一个外部时钟

滤波器:以一个采样频率f采样N个点,如果N个点都一样,才会输出有效。

引脚需要用到GPIO,在配置时基单元之前还要先配置GPIO

上拉电阻防止跳动

基本任务还是定时中断,但我们不选用内部时钟,我们使用ETR外部时钟模式2

TIM_ETRClockModelConfig通过ETR引脚的外部时钟模式2配置

什么时候需要用到浮空输入呢?

如果外部的输入信号功率很小,内部的上拉电阻可能会影响到这个输入信号,这时就可以用浮空输入,放置影响外部输入的电平。

Timer.c

#include "stm32f10x.h"#include "Timer.h"//如果你想跨文件使用变量,可以在使用变量的那个文件的上面,用extern声明一下要用的变量extern uint16_t Num;//初始化定时器void Timer_Init(void){//开启RCC内部时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);/* 初始化GPIO*/RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);GPIO_InitTypeDef GPIO_InitStruct;GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStruct);//引脚要用到GPIO,因此在配置外部时钟模式2前要先配置GPIO//选择时基单元的时钟,不使用内部时钟源,通过ETR引脚的外部时钟模式2配置,不需要分频,不用滤波器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;TIM_TimeBaseInitStructure.TIM_Period = 10 - 1; //ARR的值TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 1; //PSC的值TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器的值TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);//本质:在调用中断前,中断状态寄存器不能有标志位//避免刚一上电就立刻进入中断,在TimeBaseInit的后面和中断的前面(手动清除中断标志位)TIM_ClearFlag(TIM2, TIM_FLAG_Update);//使能更新中断,开启了更新中断到NVIC的通路TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); //TIM_IT_Update Update更新中断//NVIC配置NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);NVIC_InitTypeDef NVIC_InitStructure;NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn ; //定时器2在NVIC的通道NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//最后NVIC_InitNVIC_Init(&NVIC_InitStructure);//启动定时器,此时定时器开始工作,当产生更新时,就会触发中断TIM_Cmd(TIM2, ENABLE);}//实时查看CNT的计数器的值uint16_t Timer_GetCounter(void){return TIM_GetCounter(TIM2);}//当定时器产生更新中断时,这个函数就会自动被执行void TIM2_IRQHandler(void){//检查中断标志位,TIM_FLAG_Update为想看的中断标志位if(TIM_GetFlagStatus(TIM2, TIM_FLAG_Update)== SET){//清除相应的标志位TIM_ClearITPendingBit(TIM2,TIM_FLAG_Update);Num++;}}

Timer.h

#ifndef __TIMER_H#define __TIMER_Hvoid Timer_Init(void);uint16_t Timer_GetCounter(void); //获取计数值#endif /*__TIMER_H*/

main.c

#include "stm32f10x.h" // Device header#include "Delay.h"#include "OLED.h"#include "Timer.h"uint16_t Num;int main(void){OLED_Init();//初始化定时器,此时定时器开始工作Timer_Init();OLED_ShowString(1, 1, "Num:");OLED_ShowString(2, 1, "CNT:");while (1){OLED_ShowNum(1,5,Num,5);OLED_ShowNum(1,5,Timer_GetCounter(),5);}}

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。