STM32-TIM定时器

news2024/11/28 18:57:47

本内容基于江协科技STM32视频内容,整理而得。

文章目录

  • 1. TIM
    • 1.1 TIM定时器
    • 1.2 定时器类型
    • 1.3 基本定时器
    • 1.4 通用定时器
    • 1.4 高级定时器
    • 1.5 定时中断基本结构
    • 1.6 预分频器时序
    • 1.7 计数器时序
    • 1.8 计数器无预装时序
    • 1.9 计数器有预装时序
    • 1.10 RCC时钟树
  • 2. TIM库函数及代码
    • 2.1 TIM库函数
    • 2.2 6-1定时器中断代码
      • 2.2.1 硬件电路:
      • 2.2.2 代码流程
      • 2.2.3 代码
    • 2.3 6-2定时器外部时钟代码
      • 2.3.1 硬件电路
      • 2.3.2 代码流程
      • 2.3.3 代码

1. TIM

1.1 TIM定时器

  • 定时器可以对输入的时钟进行计数,并在计数值达到设定值时触发中断
  • 16位计数器、预分频器、自动重装寄存器的时基单元,在72MHz计数时钟下可以实现最大59.65s的定时。1/(72/65536/65536)(计数器就是用来执行计数定时的一个寄存器,每来一个时钟,计数器加1。预分频器可以对计数器的时钟进行分频,让计数更加灵活。自动重装寄存器就是计数的目标值,就是想要计多少个时钟申请中断。这些寄存器构成了定时器最核心的部分,把这一块电路称为时基单元)
  • 不仅具备基本的定时中断功能,而且还包含内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等多种功能。
  • 根据复杂度和应用场景分为高级定时器、通用定时器、基本定时器三种类型。

1.2 定时器类型

类型编号总线功能
高级定时器TIM1、TIM8APB2拥有通用定时器全部功能,并额外具有重复计数器、死区生成、互补输出、刹车输入等功能
通用定时器TIM2、TIM3、TIM4、TIM5APB1拥有基本定时器全部功能,并额外具有内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等功能
基本定时器TIM6、TIM7APB1拥有定时中断、主模式触发DAC的功能
  • STM32F103C8T6定时器资源:TIM1、TIM2、TIM3、TIM4
  • DAC:数模转换器

1.3 基本定时器

主要功能:

  • 16位自动重装载累加计数器
  • 16位可编程预分频器,用于对输入的时钟按系数为1~65536之间的任意数值分频
  • 触发DAC的同步电路
  • 在更新事件(计数器溢出)时产生中断/DMA请求

image.png

  • 预分频器PSC
    预分频器之前连接的就是基准计数时钟的输入。由于基本定时器只能选择内部时钟,因此可以认为预分频器的输入线是连接到内部时钟(CK_INT)。
    内部时钟的来源是RCC的TIMxCLK,这里的频率值一般都是系统的主频72MHz。
    预分频器写0,就是不分频;写1就是2分频,输出频率=输入频率/2=36MHz。
    写2就是3分频,输出= 输入/3。所以预分频器的值和实际的分频系数相差了1,实际分频系数=预分频器的值 + 1。预分频器是16位的,所以最大值可以写65535,也就是65536分频。

  • 计数器CNT
    计数器可以对预分频后的计数时钟进行计数,计数时钟每来一个上升沿,计数器的值就加1。计数器也是16位的,所以里面的值可以从0一直加到65535。如果再加的话,计数器就会回到0重新开始。所以计数器的值在计数过程中会不断自增运行,当自增运行到目标值时,会产生中断,那就完成了定时的任务。所以还需要一个存储目标值的寄存器,那就是自动重装寄存器了。

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

  • U和UI箭头
    图上画的一个向上的折现箭头,就代表这里会产生中断信号,像这种计数值等于自动重装值产生的中断,一般把它叫做“更新中断”。这个更新中断之后就会通往NVIC,我们再配置好NVIC的定时器通道,那定时器的更新中断就能够得到CPU的响应了。向下的箭头,代表的是会产生一个事件,这里对应的事件就叫做“更新事件”。更新事件不会触发中断,但可以触发内部其他电路的工作。

1.4 通用定时器

主要功能:

  • 16位向上、向下、向上/向上 自动重装载累加计数器

  • 16位可编程预分频器,用于对输入的时钟按系数为1~65536之间的任意数值分频

  • 4个独立通道:

    • 输入捕获
    • 输出比较
    • PWM生成(边缘或中间对齐模式)
    • 单脉冲模式输出
  • 使用外部信号控制定时器和定时器互连的同步电路

  • 如下事件发生时产生中断/DMA:

    • 更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或内部/外部触发)
    • 触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)
    • 输入捕获
    • 输出比较
  • 支持针对定位的增量(正交)编码器和霍尔传感器电路

  • 触发输入作为外部时钟或者按周期的电流管理
    image.png

  • CNT计数器
    CNT计数器支持向上计数模式、向下计数模式、中央对齐模式。向下计数模式就是从重装值开始,向下自减,减到0之后,回到重装值同时申请中断。中央对齐模式是从0开始,先向上自增,计到重装值,申请中断,然后再向下自减,减到0,再申请中断。

  • 时钟源
    通用定时器的时钟源不仅可以选择内部72MHz时钟,还可以选择外部时钟。
    (1)第一个外部时钟就是来自TIMx_ETR引脚上的外部时钟,也就是可以在TIM2的ETR引脚即PA0上接一个外部方波时钟,然后配置一下内部的极性选择、边沿检测和预分频器电路,以及输入滤波电路,这两块电路可以对外部时钟进行一定的整形。因为是外部引脚的时钟,所以难免有一些毛刺,那这些电路就可以对输入的波形进行滤波。滤波后的信号,兵分两路,上面一路ETRF进入触发控制器,紧跟着就可以选择作为时基单元的时钟了。这一路也叫做“外部时钟模式2”。
    image.png
    (2)TRGI也可以提供外部时钟,主要用作触发输入使用的,这个触发输入可以触发定时器的从模式。当TRGI当作外部时钟来使用时,这一路就叫做“外部时钟模式1”。通过这一路的外部时钟有哪些呢?第一个就是ETR引脚的信号。第二个是ITR信号,这一部分的时钟信号是来自其他定时器的。主模式的TRGO可以通向其他定时器,通向其他定时器的时候,就接到了其他定时器的ITR引脚上来了,ITR0到ITR3分别来自其他4个定时器的TRGO输出。也可以选择TI1F_ED,这里连接的是输入捕获单元的CH1引脚,也就是从CH1引脚获得时钟,这里后缀加一个ED(Edge)就是边沿的意思。还可以通过TI1FP1和TI2FP2获得,TI1FP1是连接到了CH1引脚的时钟,TI2FP2连接到了CH2引脚的时钟。
    总结:外部时钟模式1的输入可以是ETR引脚、其他定时器、CH1引脚的边沿、CH1引脚和CH2引脚。

  • 输出比较电路
    下面的右边部分是输出比较电路,总共有四个通道,分别对应CH1到CH4的引脚,可以用于输出PWM波形,驱动电机。

  • 输入捕获电路
    左边是输入捕获电路,也是有四个通道,对应的也是CH1到CH4的引脚,可以用于测输入方波的频率等。

  • 捕获/比较寄存器
    中间的寄存器是捕获/比较寄存器,是输入捕获和输出比较电路共用的,因为输入捕获和输出比较不能同时使用,所以这里寄存器是共用的,引脚也是共用的。

1.4 高级定时器

主要功能:

  • 16位向上、向下、向上/向上 自动重装载累加计数器
  • 16位可编程预分频器,用于对输入的时钟按系数为1~65536之间的任意数值分频
  • 4个独立通道:
    • 输入捕获
    • 输出比较
    • PWM生成(边缘或中间对齐模式)
    • 单脉冲模式输出
  • 死区时间可编程的互补输出
  • 使用外部信号控制定时器和定时器互连的同步电路
  • 允许在指定数据的计数器周期之后更新定时器寄存器的重复计数器
  • 刹车输入信号可以将定时器输出信号置于复位状态或者一个已知状态
  • 如下事件发生时产生中断/DMA:
    • 更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或内部/外部触发)
    • 触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)
    • 输入捕获
    • 输出比较
    • 刹车信号输入
  • 支持针对定位的增量(正交)编码器和霍尔传感器电路
  • 触发输入作为外部时钟或者按周期的电流管理

image.png

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

  • DTG死区生成器和互补输出
    下面是高级定时器对输出比较模块的升级了,DTG(dead time generate)是死区生成电路。右边的输出引脚由原来的一个变为了两个互补的输出,可以输出一对互补的PWM波,这些电路是为了驱动三相无刷电机的,比如四轴飞行器、电动车的后轮、电钻等,里面都可能是三相无刷电机。因为三相无刷电机的驱动电路一般需要3个桥臂,每个桥臂2个大功率开关管来控制,所以总共需要6个大功率开关管。因此这里的输出PWM引脚的前三路就变为了互补的输出。另外,为了防止互补输出的PWM驱动桥臂时,在开关切换的瞬间,由于器件的不理想,造成短暂的直通现象,所以前面就加上了死区生成电路。在开关切换的瞬间,产生一定时长的死区,让桥臂的上下管全都关断,防止直通现象。

  • 刹车输入
    最后一部分就是刹车输入的功能了,这个是为了给电机驱动提供安全保障的。如果外部引脚BKIN(Break IN)产生了刹车信号,或内部时钟失效,产生了故障,那么控制电路就会自动切断电机的输出,防止意外的发生。

1.5 定时中断基本结构

image.png
运行控制:控制寄存器中的一些位,如启动停止、向上或向下计数等。
右边就是计时时间到,产生更新中断后的信号去向,如果是高级定时器的话,还会多一个重复计数器。中断信号会先在状态寄存器里置一个中断标志位,这个标志位会通过中断输出控制,到NVIC申请中断。中断输出控制就是一个中断输出的允许位,如果需要某个中断,就记得允许一下。
image.png
image.png

1.6 预分频器时序

image.png

  • 计数器计数频率:CK_CNT = CK_PSC / (PSC + 1)
  • CK_PSC:预分频器时钟,内部时钟就是72MHz。
  • CNT_EN:计数器使能,高电平计数器正常运行,低电平计数器停止。
  • CK_CNT:计数器时钟,它既是预分频器的时钟输出,也是计数器的时钟输入。
  • 在开始时,计数器未使能,计数器时钟不运行。使能后,前半段,实际分频系数为1(PSC=0),计数器的时钟等于预分频器前的时钟;后半段,实际分频系数为2(PSC=1)了,计数器的时钟也就变为预分频器前时钟的一半了。在计数器时钟的驱动下,下面的计数器寄存器也跟随时钟的上升沿不断自增,在中间的这个位置FC之后,计数值变为0了,从这里可以推断出ARR自动重装值就是FC。当计数值计到和重装值相等,并且下一个时钟来临时,计数值才清零,同时下面产生一个更新事件,这就是一个计数周期的工作流程。
  • 下面的三行描述的是预分频寄存器的一种缓冲机制,也就是这个预分频寄存器实际上是有两个,一个是预分频控制寄存器,是供我们读写用的,它并不直接决定分频系数。另外还有一个缓冲寄存器或者说是影子寄存器:预分频缓冲器,这个缓冲寄存器才是真正起作用的寄存器,比如我们在某个时刻,把预分频寄存器由0改成了1,如果在此时立刻改变时钟的分频系数,那么就会导致在一个计数周期内,前半部分和后半部分的频率不一样。因此设计了缓冲器,当计数计到一半的时候改变了分频值,这个变化并不会立刻生效,而是会等到本次计数周期结束时,产生了更新事件,预分频寄存器的值才会被传递到缓冲寄存器里,才会生效。
  • 最后一行可以看出:预分频器内部也是靠计数来分频的,当预分频值为0时,计数器就一直为0,直接输出原频率;当预分频值为1时,计数器就0、1、0、1、0、1这样计数,在回到0时,输出1个脉冲,这样输出频率就是输入频率的2分频,预分频器的值和实际的分频系数之间有一个数的偏移。

1.7 计数器时序

image.png

  • 计数器溢出频率:CK_CNT_OV = CK_CNT / (ARR + 1)= CK_PSC / (PSC + 1) / (ARR + 1)
  • CK_INT:内部时钟72MHz;
  • CNT_EN:计数器使能,上升沿有效;
  • CK_CNT:计数器时钟,因为分频系数为2,所以这个频率是CK_INT除2。然后计数器在这个时钟每个上升沿自增,当增到0036的时候,发生溢出。计到36之后,再来一个上升沿,计数器清零,计数器溢出,产生一个更新事件脉冲,另外还会置一个更新中断标志(UIF),更新中断标志(UIF)置1了,就会申请中断,中断响应后,需要在中断程序中手动清零。

1.8 计数器无预装时序

image.png
更改了自动加载寄存器,由FF改成了36,那计数值的目标值就由FF变成了36,所以这里计到36之后,就直接更新开始下一轮的计数,

1.9 计数器有预装时序

image.png
在计数的中途,把计数目标值由F5改成了36。下面有个影子寄存器,这个影子寄存器才是真正起作用的,它还是F5,所以现在计数的目标还是计到F5,产生更新事件,同时,要更改的36才被传递到影子寄存器,在下一个计数周期这个更改的36才有效,所以这个引入影子寄存器的目的实际上是为了同步,就是让值的变化和更新事件同步发生,防止在运行途中,更改造成错误。

1.10 RCC时钟树

image.png

  • 时钟源
    在时钟产生电路,有四个震荡源:
    (1)HSI:内部的8MHz高速RC振荡器;
    (2)HSE:外部的4~16MHz高速石英晶体振荡器,也就是晶振,一般都是接8MHz;
    (3)LSE:外部的32.768KHz低速晶振,这个一般是给RTC提供时钟的;
    (4)LSI:最后是内部的40KHz低速RC振荡器,这个可以给看门狗提供时钟。
    上面两个高速晶振是用提供系统时钟的,AHB、APB2、APB1的时钟都是来源于这两个高速晶振,只不过是外部的石英振荡器比内部的RC振荡器更加稳定,所以一般都是用外部晶振。

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

  • CSS时钟安全系统
    CSS(clock security system):时钟安全系统,也是负责切换时钟的,可以监测外部时钟的运行状态,一旦外部时钟失效,就会自动把外部时钟切换回内部时钟,保证系统时钟的运行,防止程序卡死造成事故。

  • 时钟分配电路

    • AHB总线: 首先系统时钟72MHz进入AHB总线,AHB总线有个预分频器,在SystemInit里配置的分配系数为1,那AHB的时钟就是72MHz。
    • APB1总线:这里配置的分配系数为2,所以APB1总线的时钟为72MHz/2=36MHz。下面的如果APB1预分频系数=1,则频率不变,否则频率*2,然后右边,是单独为定时器2-7开通的,因为这里预分频系数,我们给的是2,所以这里频率要再*2,所以通向定时器2~7的时钟为72MHz。因此无论是高级定时器、通用定时器还是基本定时器,它们的内部基准时钟都是72MHz。
    • APB2总线:APB2的分频系数为1,所以时钟为72MHz。然后接在APB2上的时钟也单开了一路,即如果APB2预分频系数=1,则频率不变,否则频率*2。因为分频系数给的是1,所以定时器1和8的时钟就是72MHz。
    • 时钟输出部分都有一个与门进行输出控制,控制位写的是外部时钟使能,这就是我们再在程序中写RCC_APB2/1PeriphClockCmd作用的地方,打开时钟,就是在这个位置写1,让左边的时钟能够通过与门输出给外设。

2. TIM库函数及代码

2.1 TIM库函数

// 恢复缺省配置
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);

/*-----------------时基单元的时钟选择---------*/
// 选择内部时钟
void TIM_InternalClockConfig(TIM_TypeDef* TIMx);

// 选择ITRx其他定时器的时钟
void TIM_ITRxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource);

// 选择TIx捕获通道的时钟
void TIM_TIxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_TIxExternalCLKSource,
                                uint16_t TIM_ICPolarity, uint16_t ICFilter);

// 选择ETR通过外部时钟模式1输入的时钟
void TIM_ETRClockMode1Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity,
                             uint16_t ExtTRGFilter);

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

/*--------------------------------------------------------*/
// 不是用来选择时钟的,单独用来配置ETR引脚的预分频器、极性、滤波参数的
void TIM_ETRConfig(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity,
                   uint16_t ExtTRGFilter);

// 用来单独写预分频值的
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);


2.2 6-1定时器中断代码

2.2.1 硬件电路:

实现功能:定时1s,并在OLED上显示Num值(中断触发次数,也即定时时间)
image.png

2.2.2 代码流程

  1. 定时器代码

    1. 开启时钟RCC,定时器的基准时钟和整个外设的工作时钟都会同时打开了;
    2. 选择时基单元的时钟源,对于定时中断,选择内部时钟源;
    3. 配置时基单元,包括预分频器、自动重装器、计数模式等–用结构体;
    4. 配置中断输出控制,允许更新中断输出到NVIC;
    5. 配置NVIC,在NVIC中打开定时器中断的通道,并分配一个优先级;
    6. 运行控制;
      整个模块配置完成后,还需要使能一下计数器,要不然计数器是不会运行的。当定时器使能后,计数器就开始计数了,当计数器更新时,触发中断,最后再写一个定时器的中断函数,这样这个中断函数就每隔一段时间就能自动执行一次了。
  2. 中断函数

    1. 判断是否进入定时器TIM2的中断,然后清除中断标志位。
    2. 使定时器每秒自动加一下Num变量
  3. ARR和PSC设置

    1. ARR=10000 - 1;PSC = 7200 - 1;
    2. 定时器时钟CK_CNT =72M / (PSC + 1) = 10000 ;
    3. 定时频率 = CK_CNT / (ARR + 1) = 1。定时1s,也就是定时频率为1Hz。

预分频是对72M进行7200分频,得到的就是10K的计数频率,在10K的频率下,计10000个数,就是1s的时间。(在1s的时间内计10000个数,当计到10000个数后,自动清0,同时申请中断,在OLED显示屏上就是显示Num值每1s加1)。也可以更改ARR和PSC的值。

  1. main函数
    1. OLED显示Num值。

2.2.3 代码

  • Timer.c代码
#include "stm32f10x.h"                  // Device header

/**
  * 函    数:定时中断初始化
  * 参    数:无
  * 返 回 值:无
  */
void Timer_Init(void)
{
	/*开启时钟*/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);			//开启TIM2的时钟
	
	/*配置时钟源*/
	TIM_InternalClockConfig(TIM2);		//选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟
	
	/*时基单元初始化*/
	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);				//将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元	
	
	/*中断输出配置*/
	TIM_ClearFlag(TIM2, TIM_FLAG_Update);						//清除定时器更新标志位
																//TIM_TimeBaseInit函数末尾,手动产生了更新事件
																//若不清除此标志位,则开启中断后,会立刻进入一次中断
																//如果不介意此问题,则不清除此标志位也可
	
	TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);					//开启TIM2的更新中断
	
	/*NVIC中断分组*/
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);				//配置NVIC为分组2
																//即抢占优先级范围:0~3,响应优先级范围:0~3
																//此分组配置在整个工程中仅需调用一次
																//若有多个中断,可以把此代码放在main函数内,while循环之前
																//若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置
	
	/*NVIC配置*/
	NVIC_InitTypeDef NVIC_InitStructure;						//定义结构体变量
	NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;				//选择配置NVIC的TIM2线
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;				//指定NVIC线路使能
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;	//指定NVIC线路的抢占优先级为2
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;			//指定NVIC线路的响应优先级为1
	NVIC_Init(&NVIC_InitStructure);								//将结构体变量交给NVIC_Init,配置NVIC外设
	
	/*TIM使能*/
	TIM_Cmd(TIM2, ENABLE);			//使能TIM2,定时器开始运行
}

  • main.c
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Timer.h"

uint16_t Num;			//定义在定时器中断里自增的变量

int main(void)
{
	/*模块初始化*/
	OLED_Init();		//OLED初始化
	Timer_Init();		//定时中断初始化
	
	/*显示静态字符串*/
	OLED_ShowString(1, 1, "Num:");			//1行1列显示字符串Num:
	
	while (1)
	{
		OLED_ShowNum(1, 5, Num, 5);			//不断刷新显示Num变量
	}
}

/**
  * 函    数:TIM2中断函数
  * 参    数:无
  * 返 回 值:无
  * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
  *           函数名为预留的指定名称,可以从启动文件复制
  *           请确保函数名正确,不能有任何差异,否则中断函数将不能进入
  */
void TIM2_IRQHandler(void)
{
	if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)		//判断是否是TIM2的更新事件触发的中断
	{
		Num ++;												//Num变量自增,用于测试定时中断
		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);			//清除TIM2更新事件的中断标志位
															//中断标志位必须清除
															//否则中断将连续不断地触发,导致主程序卡死
	}
}

2.3 6-2定时器外部时钟代码

2.3.1 硬件电路

  • 将对射式红外传感器接在PA0引脚,即TIM2_CH1_ETR,时钟配置为外部时钟模式2。
  • 每次对传感器进行遮挡时,计数值CNT(TIM_GetCounter(TIM2))加1,当加到ARR时,触发中断,使Num值加1。
  • 在OLED显示屏上显示Num和CNT的值。

image.png

2.3.2 代码流程

  1. 定时器代码
    1. 开启时钟RCC,TIM2和GPIOA。
    2. 配置GPIO,为上拉输入。
    3. 选择时基单元的时钟源,外部时钟模式2,时钟从TIM2_ETR引脚输入;
    4. 配置时基单元,包括预分频器、自动重装器、计数模式等–用结构体;
    5. 配置中断输出控制,允许更新中断输出到NVIC;
    6. 配置NVIC,在NVIC中打开定时器中断的通道,并分配一个优先级;
    7. 运行控制;
  2. ARR和PSC
    1. ARR = 10 - 1; PSC = 1 - 1;
    2. 因为没有进行分频,所以对射式红外传感器每遮挡一次,计数值CNT加1,当计数值加到9后,自动清零,同时申请中断,Num++。
  3. main函数
    1. 实现在OLED显示屏上显示Num和CNT的值。

2.3.3 代码

  • Timer.c
#include "stm32f10x.h"                  // Device header

/**
  * 函    数:定时中断初始化
  * 参    数:无
  * 返 回 值:无
  * 注意事项:此函数配置为外部时钟,定时器相当于计数器
  */
void Timer_Init(void)
{
	/*开启时钟*/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);			//开启TIM2的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);			//开启GPIOA的时钟
	
	/*GPIO初始化*/
	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);						//将PA0引脚初始化为上拉输入
	
	/*外部时钟配置*/
	TIM_ETRClockMode2Config(TIM2, TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_NonInverted, 0x0F);
																//选择外部时钟模式2,时钟从TIM_ETR引脚输入
																//注意TIM2的ETR引脚固定为PA0,无法随意更改
																//最后一个滤波器参数加到最大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的值
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;			//重复计数器,高级定时器才会用到
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);				//将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元	
	
	/*中断输出配置*/
	TIM_ClearFlag(TIM2, TIM_FLAG_Update);						//清除定时器更新标志位
																//TIM_TimeBaseInit函数末尾,手动产生了更新事件
																//若不清除此标志位,则开启中断后,会立刻进入一次中断
																//如果不介意此问题,则不清除此标志位也可
																
	TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);					//开启TIM2的更新中断
	
	/*NVIC中断分组*/
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);				//配置NVIC为分组2
																//即抢占优先级范围:0~3,响应优先级范围:0~3
																//此分组配置在整个工程中仅需调用一次
																//若有多个中断,可以把此代码放在main函数内,while循环之前
																//若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置
	
	/*NVIC配置*/
	NVIC_InitTypeDef NVIC_InitStructure;						//定义结构体变量
	NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;				//选择配置NVIC的TIM2线
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;				//指定NVIC线路使能
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;	//指定NVIC线路的抢占优先级为2
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;			//指定NVIC线路的响应优先级为1
	NVIC_Init(&NVIC_InitStructure);								//将结构体变量交给NVIC_Init,配置NVIC外设
	
	/*TIM使能*/
	TIM_Cmd(TIM2, ENABLE);			//使能TIM2,定时器开始运行
}

/**
  * 函    数:返回定时器CNT的值
  * 参    数:无
  * 返 回 值:定时器CNT的值,范围:0~65535
  */
uint16_t Timer_GetCounter(void)
{
	return TIM_GetCounter(TIM2);	//返回定时器TIM2的CNT
}

  • main.c
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Timer.h"

uint16_t Num;			//定义在定时器中断里自增的变量

int main(void)
{
	/*模块初始化*/
	OLED_Init();		//OLED初始化
	Timer_Init();		//定时中断初始化
	
	/*显示静态字符串*/
	OLED_ShowString(1, 1, "Num:");			//1行1列显示字符串Num:
	OLED_ShowString(2, 1, "CNT:");			//2行1列显示字符串CNT:
	
	while (1)
	{
		OLED_ShowNum(1, 5, Num, 5);			//不断刷新显示Num变量
		OLED_ShowNum(2, 5, Timer_GetCounter(), 5);		//不断刷新显示CNT的值
	}
}

/**
  * 函    数:TIM2中断函数
  * 参    数:无
  * 返 回 值:无
  * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
  *           函数名为预留的指定名称,可以从启动文件复制
  *           请确保函数名正确,不能有任何差异,否则中断函数将不能进入
  */
void TIM2_IRQHandler(void)
{
	if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)		//判断是否是TIM2的更新事件触发的中断
	{
		Num ++;												//Num变量自增,用于测试定时中断
		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);			//清除TIM2更新事件的中断标志位
															//中断标志位必须清除
															//否则中断将连续不断地触发,导致主程序卡死
	}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1902869.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于单片机的多功能电子时钟的设计

摘要:提出了一种基于单片机的多功能电子时钟的设计方法,以 AT89C52单片机作为系统的主控芯片,采用DS1302作为时钟控制芯片,实现日期时钟显示并且提供精准定时的功能。此外,还可经由DHT22所构成的温湿度传感电路&#x…

JVM原理(十九):JVM虚拟机内存模型

1. 硬件的效率与一致性 数据不安全的原因:缓存一致性的问题 共享内存多核系统:在多路处理器系统中,每个处理器都有自己的高速缓存,而他们又共享同一主内存。 线程先后执行结果不一致问题:除了增加高速缓存之外&#…

Kotlin算法:把一个整数向上取值为最接近的2的幂指数值

Kotlin算法&#xff1a;把一个整数向上取值为最接近的2的幂指数值 import kotlin.math.ln import kotlin.math.powfun main(args: Array<String>) {val number intArrayOf(2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18)number.forEach {println("$…

IT高手修炼手册(3)程序员命令

一、前言 程序员在日常工作中&#xff0c;掌握一些高效的快捷键可以大大提高编码和开发效率。 二、通用快捷键 文本操作Ctrl A&#xff1a;全选当前页面内容 Ctrl C&#xff1a;复制当前选中内容 Ctrl V&#xff1a;粘贴当前剪贴板内的内容 Ctrl X&#xff1a;剪切当前选中…

【基于R语言群体遗传学】-8-代际及时间推移对于变异的影响

上一篇博客&#xff0c;我们学习了在非选择下&#xff0c;以二项分布模拟遗传漂变的过程&#xff1a;【基于R语言群体遗传学】-7-遗传变异&#xff08;genetic variation&#xff09;-CSDN博客 那么我们之前有在代际之间去模拟&#xff0c;那么我们就想知道&#xff0c;遗传变…

Java版Flink使用指南——安装Flink和使用IntelliJ制作任务包

大纲 安装Flink操作系统安装JDK安装Flink修改配置启动Flink测试 使用IntelliJ制作任务包新建工程Archetype 编写测试代码打包测试 参考资料 在《0基础学习PyFlink》专题中&#xff0c;我们熟悉了Flink的相关知识以及Python编码方案。这个系列我们将使用相对主流的Java语言&…

SQL Server 2022 中的 Tempdb 性能改进非常显著

无论是在我的会话中还是在我写的博客中&#xff0c;Tempdb 始终是我的话题。然而&#xff0c;当谈到 SQL Server 2022 中引入的重大性能变化时&#xff0c;我从未如此兴奋过。他们解决了我们最大的性能瓶颈之一&#xff0c;即系统页面闩锁并发。 在 SQL Server 2019 中&#x…

python函数和c的区别有哪些

Python有很多内置函数&#xff08;build in function&#xff09;&#xff0c;不需要写头文件&#xff0c;Python还有很多强大的模块&#xff0c;需要时导入便可。C语言在这一点上远不及Python&#xff0c;大多时候都需要自己手动实现。 C语言中的函数&#xff0c;有着严格的顺…

【pyhton学习】深度理解类和对象

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 文章目录 一、一切皆对象1.1 对象的概念1.2 如何创建类对象1.3 类型检测 二、属性与方法2.1 如何查看属性与方法2.2 属性和方法…

14-39 剑和诗人13 - 顶级大模型测试分析和建议

​​​​​ 随着对高级语言功能的需求不断飙升&#xff0c;市场上涌现出大量语言模型&#xff0c;每种模型都拥有独特的优势和功能。然而&#xff0c;驾驭这个错综复杂的生态系统可能是一项艰巨的任务&#xff0c;开发人员和研究人员经常面临选择最适合其特定需求的模型的挑战。…

SpringBoot新手快速入门系列教程七:基于一个低配centoos服务器,如何通过宝塔面板部署一个SpringBoot项目

1&#xff0c;如何打包一个项目 通过IDEA自带的命令行&#xff0c;执行 ./gradlew clean build 2&#xff0c;检查生成的JAR文件 进入 build/libs 目录&#xff0c;你应该会看到一个类似 helloredis-0.0.1-SNAPSHOT.jar 的文件。 3&#xff1a;运行生成的JAR文件 你可以使…

【工具】豆瓣自动回贴软件

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你&#xff0c;欢迎[点赞、收藏、关注]哦~ 相比于之前粗糙丑陋的黑命令框版本&#xff0c;这个版本新增了UI界面&#xff0c;从此可以不需要再挨个去翻配置文件了。 另外&#xff0c;升级了隐藏浏…

Java基础(六)——继承

个人简介 &#x1f440;个人主页&#xff1a; 前端杂货铺 ⚡开源项目&#xff1a; rich-vue3 &#xff08;基于 Vue3 TS Pinia Element Plus Spring全家桶 MySQL&#xff09; &#x1f64b;‍♂️学习方向&#xff1a; 主攻前端方向&#xff0c;正逐渐往全干发展 &#x1…

Blazor SPA 的本质是什么以及服务器端渲染如何与 Blazor 的新 Web 应用程序配合使用

Blazor 通常被称为单页应用程序 (SPA) 框架。当我第一次开始使用 Blazor 时&#xff0c;我对 SPA 的含义、组件如何为 SPA 架构做出贡献以及所有这些如何与交互性联系在一起感到困惑。 今天&#xff0c;我将解答大家可能关心的三个问题&#xff1a; 什么是 SPA&#xff1f;了…

A Threat Actors 出售 18 万名 Shopify 用户信息

BreachForums 论坛成员最近发布了涉及 Shopify 的重大数据泄露事件。 据报道&#xff0c;属于近 180,000 名用户的敏感数据遭到泄露。 Shopify Inc. 是一家总部位于安大略省渥太华的加拿大公司。 开发和营销同名电子商务平台、Shopify POS 销售点系统以及专用于企业的营销工…

文心一言最常用的20条指令及指令说明,含增强指令

下面是20条文心一言的指令及其说明&#xff0c;每条指令尽量简洁明了&#xff0c;以便在有限的字数内提供尽可能多的信息。以下是这些指令及其说明&#xff1a; 1. 查询天气 指令&#xff1a;今天北京的天气怎么样&#xff1f;说明&#xff1a;此指令用于查询特定城市&#xf…

24西安电子科技大学经济与管理学院—考研录取情况

24西安电子科技大学—经理与管理学院—考研录取统计 01、经理与管理学院各个方向 02、24经济与管理近三年复试分数线对比 1、经管院24年院线相对于23年院线普遍下降2-15分&#xff0c;个别专业上涨4-10分。 2、经管院应用经济学2024年院线350分&#xff1b;管理科学与工程院线…

用kimi实现一键实体识别与关系抽取

实体识别与关系抽取是自然语言处理&#xff08;NLP&#xff09;中的两个重要任务&#xff0c;通常被视为知识图谱构建的基础技术。 实体识别&#xff08;Named Entity Recognition, NER&#xff09;&#xff1a; 实体识别的目标是从文本中识别出具有特定意义的实体&#xff0…

Java.lang.Thread类和Java的主线程

一.Java.lang.Thread类 支持多线程编程 常用方法 二.主线程 ◆Java程序启动时&#xff0c;一个线程立即随之启动&#xff0c;通常称之为程序的主线程 ◆main()方法即为主线程入口 ◆产生其他子线程的线程 ◆必须最后完成执行&#xff0c;因为它执行各种关闭动作 示例 使用…

Vben:表格的表头和表格的内容对不齐,以及解决方法

文章目录 一、问题描述二、解决方法 一、问题描述 基于Vue-Vbne-admin框架进行前端开发的时候&#xff0c;调用表格useTable函数实现表格之后&#xff0c;发现表格的表头和表格的内容对不齐。如下图所示。针对这种情况&#xff0c;本文记录了解决方法。 调用的模块如下&#x…