STM32

发布于:2025-07-10 ⋅ 阅读:(17) ⋅ 点赞:(0)

目录

#TIM定时器中断#

TIM简介

定时器类型

定时器框图

基本定时器

通用定时器

计数器

 时钟输入

输出比较电路(四个输出控制)

输入捕获电路(四个通道)

捕获/比较寄存器

高级定时器

定时中断基本结构图

中断输出控制

时序图

预分频器

一个计数周期的工作流程

预分频器缓冲机制

计数器

计数器溢出频率:

计数器无预装

计数器有预装

 RCC时钟树

时钟产生电路

时钟分配电路

TIM定时器代码编写

1.初始化TIM定时器

2.库函数的认识

3.代码中出现的参数

4.Timer.c代码编写

5.Timer.h

6.main.c

OLED显示问题:

原因:

解决:

TIM定时外部时钟

Timer.c代码编写

main.c代码编写


#TIM定时器中断#

TIM简介

  • TIMTimer)定时器
  • 定时器可以对输入的时钟进行计数,并在计数值达到设定值时触发中断(定时触发中断/计数器)在STM32中,定时器基准时钟主频是72MHZ

对72MHZ记72个数,就是1MHZ也就是1us的时间

T(时钟周期)=1/fosc(频率) 计时时间=n(计数次数)*T,1s/72M*72=1us

1Hz对应1s,1KHz对应1ms,1MHz对应1us

  • 16位计数器(执行计数定时)、预分频器(对计数器的时钟进行分频,使计数更灵活)、自动重装寄存器(计数目标值)的时基单元,在72MHz计数时钟下可以实现最大59.65s(预分频器设置最大,自动重装设置最大→T=59.651/72M/65536/65536))的定时

若想计时更长时间,STM32定时器支持级联模式(一个定时器输出当作另一个定时器输入→T=59.65*65536*65536=8035年)

  • 不仅具备基本的定时中断功能,而且还包含内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等多种功能
  • 根据复杂度和应用场景分为了高级定时器、通用定时器、基本定时器三种类型

定时器类型

类型

编号

总线

功能

高级定时器

TIM1、TIM8

APB2

拥有通用定时器全部功能,并额外具有重复计数器、死区生成、互补输出、刹车输入等功能

通用定时器

TIM2、TIM3、TIM4、TIM5

APB1

拥有基本定时器全部功能,并额外具有内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等功能

基本定时器

TIM6、TIM7

APB1

拥有定时中断、主模式触发DAC的功能

  • STM32F103C8T6定时器资源:TIM1TIM2TIM3TIM4

定时器框图

基本定时器

预分频器:对72MHZ的计数时钟进行预分频(1→2分频,输出频率=输入频率/2=36MHZ),预分频16位,最大值为65535,也就是65536分频

计数器:对预分频后的计数时钟进行计数,计数时钟每来一个上升沿,计数器值加1,计数器16位,值从1加到65535,再加就为0重新开始;计数器一直自增到目标值,就会产生中断,完成定时任务

自动重装寄存器:16位,存入计数目标值,当计数器自增到目标值,就会产生中断信号,并且清零计数器,计数器自动开始下一次的计时(向上计数)(更新中断)

UI更新中断→NVIC(NVIC定时器通道)→CPU

U更新事件,不触发中断,但触发内部其他电路工作(DAC)

主模式触发DAC功能:内部硬件在不受程序控制下实现自动运行

思路①:设置定时器中断→中断程序调用代码手动触发DAC转换→DAC输出(影响主程序运行和其他中断响应)

思路②:主模式:定时器更新事件映射到触发输出TRGO(Trigger Out)位置→DAC触发转换引脚并触发

通用定时器

计数器

向上计数模式(加):0→重装值→0(申请中断)

#define TIM_CounterMode_Up                 ((uint16_t)0x0000)

向下计数模式(减):重装值→0→重装值(申请中断)

#define TIM_CounterMode_Down               ((uint16_t)0x0010)

中央对齐模式(加减):0(增)→重装值(申请中断)→(减)0(申请中断)

#define TIM_CounterMode_CenterAligned1     ((uint16_t)0x0020)
#define TIM_CounterMode_CenterAligned2     ((uint16_t)0x0040)
#define TIM_CounterMode_CenterAligned3     ((uint16_t)0x0060)

 

 时钟输入

若对ETR时钟进行计数,把定时器当作计数器来用(外部时钟模式2)

TRGI(Trigger In),主要作为触发输入(可触发定时器从模式),当作外部时钟输入(外部时钟模式1)

通过这一路外部时钟:

①ETR引脚信号(占用触发输入模式)

②ITR时钟信号(来自其他定时器的TRGO输出),主模式输出TRGO可以通向其他定时器(接到它们的ITR引脚),这一路可以实现定时器级联功能

外部时钟模式1:输入可以是ETR引脚(一般情况),其他定时器,CH1引脚边沿,CH1引脚,CH2引脚

若使用外部时钟,首选ETR引脚外部时钟模式2的输入

输出比较电路(四个输出控制)

输入捕获电路(四个通道)

捕获/比较寄存器

是输入捕获和输出比较电路共用的

高级定时器

与通用定时器三个不同的地方

定时中断基本结构图

中断信号产生会先在状态寄存器内一个中断标志位,标志位会通过中断输出控制到NVIC申请中断

中断输出控制

定时器模块有很多地方要申请中断(更新/触发信号/输入捕获,输出比较),需要某个中断就允许,不需要就禁止(中断输出控制就是一个中断输出允许位)

时序图

预分频器

一个计数周期的工作流程

开始时计数器未使能,计数器时钟不运行,计数器使能后,前半段,预分频器系数为1,计数器的时钟等于预分频器前的时钟。后半段,预分频器系数变为2,计数器的时钟就变为预分频器前时钟的一半。在计数器时钟的驱动下,下面的计数器寄存器也跟随时钟的上升沿不断自增。在FC之后,计数值变为0→ARR自动重装值就是FC。当计数值计到和重装值相等,并且下一个时钟来临时计数值才清零。同时,产生一个更新事件。

预分频器缓冲机制

①预分频器有俩个寄存器,预分频控制寄存器(读写使用,不直接决定分频系数)和预分频缓冲寄存器(起作用)

将预分频寄存器0→1,会导致在一个计数周期内,前半部分和后半部分的频率不一样,计数频率改变,改变分频值,但变化不会立刻产生,而是等到本次周期结束,产生了更新事件,预分频器的值才会被传递到缓冲寄存器里,才会生效(缓冲寄存器起作用)

因此,即使在计数中途改变预分频值,计数频率仍保持原来的频率,直到本轮计数完成(产生更新事件),在下一轮计数时,改变后的分频值才会起作用

②预分频器内部实际也是靠计数分频,当预分频值为1时,计数器就010101这样计数,回到0时输出一个脉冲(输出频率=输入频率的二分频)

预分频器的值和实际的分频系数之间有一个数的偏移

  • 计数器计数频率:CK_CNT定时器经过PSC预分频器之后,驱动计数器计数) = CK_PSC(预分频器输入时钟频率) / (PSC + 1)
  • 预分频系数(参数/因子)=PSC(分频值)+1

计数器

由于分频系数为2,CK_CNT=CK_INT/2

计数器溢出频率:

CK_CNT_OV (定时器溢出频率/计数周期频率)

= CK_CNT(计数) / (ARR 自动重装载值+ 1)

= CK_PSC(输入时钟频率) / (PSC(预分频器的值) + 1) / (ARR自动重装值 + 1)

=72HMZ/PSC+1/ARR+1

ARR自动重装载值,定时器计数到ARR+1次时触发溢出或更新事件


计数器无预装

当计数器自增时,加入一个自动加载寄存器(自动重装寄存器,FF→36),计数目标值就改成36,计数器自增到36直接开始下一轮计数


计数器有预装

在计数器自增时,加入一个自动加载寄存器(自动重装寄存器,计数目标F536

影子寄存器真正起作用,目前还是F5,所以计数目标计数到F5,产生更新事件,同时更改的36才被传递到影子寄存器,在下一个计数周期,36才作为计数目标

影子寄存器的存在,让值的变化和更新事件同步发生,防止在运行途中更改造成错误


 RCC时钟树

STM32中用来产生和配置时钟,并且把配置好的时钟发送到各个外设的系统

时钟是所有外设的基础→最先需要配置好时钟

在主程序执行之前会执行一个Systemlnit函数→配置时钟树

时钟产生电路

俩个高速晶振,提供系统时钟,可分配AHB,APB2,APB1时钟

内部和外部都有一个8MHZ的晶振,但外部更稳定

①先是内部高频振荡器输入时钟

②当外部高频振荡器倍频到72MHZ,就选择其输入时钟


若外部晶振出问题,可能会导致程序时钟慢了大概10倍(会选择内部8MHZ运行)

CSS时钟安全系统,监测外部时钟运行状态,外部时钟失效,自动转换为内部时钟,保证时钟运行


时钟分配电路

72MHZ进入AHB总线(AHB内预分频器,在Systemlnit分配系数为1→AHB时钟72MHZ),进入AHB1总线(分配系数2→APB1总线时钟为72/2=36MHZ)

APB2系数为1,频率不变,为72MHz

TIM定时器代码编写

1.初始化TIM定时器

    ①开启RCC时钟(定时器基准时钟和整个外设时钟都会打开)
    ②选择时基单元时钟源,对于定时中断,选择内部时钟源
    ③配置时基单元(预分频器,自动重装器,计数模式)用结构体配置
    ④配置输出中断控制,允许更新中断输出到NVIC
    ⑤配置NVIC,在NVIC中打开定时器中断的通道,并分配一个优先级
    ⑥配置运行控制,整个模块配置完成后,需要使能计数器,使计数器运行
    计数器使能,计数器计数,定时器更新并触发中断
    ⑦配置中断函数

 

2.库函数的认识

    定时器库函数:
    ①TIM_DeInit(TIM_TypeDef* TIMx);
    *恢复初始配置
    
    ②TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);
    *时基单元初始化
    
    ③TIM_TimeBaseStructInit(TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);
    *将结构体变量赋一个默认值
    
    ④TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);
    *使能计数器(运行控制)
    
    ⑤TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);
    使能外设中断输出信号


    时基单元时钟源选择部分:
    1. RCC内部时钟
     TIM_InternalClockConfig(TIM_TypeDef* TIMx);
     内部时钟模式


    2. ETR外部时钟
   1)TIM_ETRClockMode1Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity,uint16_t ExtTRGFilter);
    ETR通过外部时钟模式1输入时钟


   2)TIM_ETRClockMode2Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter);
    ETR通过外部时钟模式2输入时钟


    若不需要触发输入功能,俩函数可以互换


        TIM_ETRConfig(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity,uint16_t ExtTRGFilter);
    单独配置ETR引脚预分频器,极性,滤波器参数


    3. ITRX其他定时器
     TIM_ITRxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource);
        外部时钟模式1


    4. TIX捕获通道
     TIM_TIxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_TIxExternalCLKSource, uint16_t TIM_ICPolarity, uint16_t ICFilter);
     定时器型号,TIx具体引脚,输入极性,滤波器
        外部时钟模式1


        自动重装值和预分频值可能会在初始化之后还需修改
        ①TIM_PrescalerConfig(TIM_TypeDef* TIMx, uint16_t Prescaler, uint16_t TIM_PSCReloadMode);
        单独写预分频值


        ②TIM_CounterModeConfig(TIM_TypeDef* TIMx, uint16_t TIM_CounterMode);
        改变计数器的计数模式


        ③TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState);
        自动重装器预装功能配置


        ④TIM_SetCounter(TIM_TypeDef* TIMx, uint16_t Counter);
        给计数器写入一个值(手动输入)
        

        ⑤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);
        vo id 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);
      

STM32-CSDN博客(包含NVIC初始化与使用)

3.代码中出现的参数

滤波器工作原理中TIM_Clock_Division_CKD分频

滤波器可以滤掉信号抖动干扰

在采样频率f采取N个采样点,若都是相同电平,表示稳定

若N个采样值不全相同,就说明信号有抖动,保持上一次的输出,或者直接输出低电平,以此来保证输出信号好在一定程度上的滤波

采样频率f和采样点数N都是滤波器的参数

频率越低,采样点数越多,滤波效果越好,但信号延迟越大

采样频率f从内部时钟或者由内部时钟加一个时钟分频产生,分频多少TIM_Clock_Division_CKD决定

#define TIM_CKD_DIV1                       ((uint16_t)0x0000) 1分频=不分频

#define TIM_CKD_DIV2                       ((uint16_t)0x0100)

#define TIM_CKD_DIV4                       ((uint16_t)0x0200)

4.Timer.c代码编写
#include "stm32f10x.h"                  // Device header

int16_t number;
//初始化函数
void Timer_Init(void)
{
	/*
	初始化定时器
	①开启RCC时钟(定时器基准时钟和整个外设时钟都会打开)
	②选择时基单元时钟源,对于定时中断,选择内部时钟源
	③配置时基单元(预分频器,自动重装器,计数模式)用结构体配置
	④配置输出中断控制,允许更新中断输出到NVIC
	⑤配置NVIC,在NVIC中打开定时器中断的通道,并分配一个优先级
	⑥配置运行控制,整个模块配置完成后,需要使能计数器,使计数器运行
	计数器使能,计数器计数,定时器更新并触发中断
	⑦配置中断函数
	
	*/
	
	//初始化TIM2 通用定时器
	//①开启时钟 APB1开启时钟
	RCC_APB1PeriphClockCmd (RCC_APB1Periph_TIM2,ENABLE);
	//②选择时基单元时钟,tim.h,选择内部时钟
	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预分频器的值
	//定时1s-1HZ
	//计数器溢出频率CK_CNT_OV=72MHZ/(PSC+1)/(ARR+1)
	//时间周期=1/CK_CNT_OV
	//PSC和ARR取值范围是0~65535 
	//因为从0开始,所以减1
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0;//高级定时器的重复计数器的值,初级没有
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
	//④使能更新中断
	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_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)
	{
		number++;	
		//清楚标志位
		TIM_ClearITPendingBit(TIM2,TIM_IT_Update);

	}

}
5.Timer.h
#ifndef __TIMER_H_
#define __TIMER_H_

void Timer_Init(void);

extern int16_t number;

#endif
6.main.c
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Timer.h"
int main(void)
{	
	
  OLED_Init();
	Timer_Init();
	OLED_ShowString(1,1,"number:");
	
	
	while(1)
	{
     OLED_ShowNum(2,3,number,5);
	}
}

OLED显示问题:

在OLED上显示number数字从1开始

原因:

TimeBaseInit函数生成一个更新事件,立刻重新装载预分频器和重复计数器的值

预分频器有个缓冲器,写入的值只有在更新事件时才会真正起作用

这个函数为了让写入的值立刻起作用,手动生成一个更新事件,但更新事件和更新中断同时发生,更新中断会置更新中断标志位,初始化后,更新中断立刻进入

解决:

在TimeBaseInit函数后面,开启中断的前面,手动调用一下    TIM_ClearFlag(TIM2,TIM_FLAG_Update)手动清除标志位

或者简单粗暴,直接 OLED_ShowNum(2,3,number-1,5);


TIM定时外部时钟

Timer.c代码编写

①更换内部时钟为ETR外部时钟模式2

②使用PA0作为ETR引脚,进行GPIO初始化

#include "stm32f10x.h"                  // Device header

int16_t number;
//初始化函数
void Timer_Init(void)
{
	/*
	初始化定时器
	①开启RCC时钟(定时器基准时钟和整个外设时钟都会打开)
	②选择时基单元时钟源,对于定时中断,选择内部时钟源
	③配置时基单元(预分频器,自动重装器,计数模式)用结构体配置
	④配置输出中断控制,允许更新中断输出到NVIC
	⑤配置NVIC,在NVIC中打开定时器中断的通道,并分配一个优先级
	⑥配置运行控制,整个模块配置完成后,需要使能计数器,使计数器运行
	计数器使能,计数器计数,定时器更新并触发中断
	⑦配置中断函数
	
	*/
	
	//初始化TIM2 通用定时器
	//①开启时钟 APB1开启时钟
	RCC_APB1PeriphClockCmd (RCC_APB1Periph_TIM2,ENABLE);
	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);
	//②选择时基单元时钟, ETR通过外部时钟模式2输入时钟,引脚需要用到GPIO,配置GPIO
	TIM_ETRClockMode2Config(TIM2, TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_NonInverted,0x0F);

	//③配置时基单元
	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预分频器的值
	//定时1s-1HZ
	//计数器溢出频率CK_CNT_OV=72MHZ/(PSC+1)/(ARR+1)
	//时间周期=1/CK_CNT_OV
	//PSC和ARR取值范围是0~65535 
	//因为从0开始,所以减1
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0;//高级定时器的重复计数器的值,初级没有
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
	
	TIM_ClearFlag(TIM2,TIM_FLAG_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_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)
	{
		number++;	
		//清楚标志位
		TIM_ClearITPendingBit(TIM2,TIM_IT_Update);

	}

}

main.c代码编写

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Timer.h"
int main(void)
{	
	
  OLED_Init();
	Timer_Init();
	OLED_ShowString(1,1,"number:");
	OLED_ShowString(3,1,"CNT:");
	
	
	while(1)
	{
     OLED_ShowNum(2,3,number,5);
     OLED_ShowNum(4,3,TIM_GetCounter(TIM2),5);
		//时基单元没有分频,遮挡CNT加1,加到9,申请中断,number加1
		//有预分频,遮挡几次CNT加1

	}
}


网站公告

今日签到

点亮在社区的每一天
去签到