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

江协科技STM32学习- P14 示例程序(定时器定时中断和定时器外部时钟)

    🚀write in front🚀  
🔎大家好,我是黄桃罐头,希望你看完之后,能对你有所帮助,不足请指正!共同学习交流
🎁欢迎各位→点赞👍 + 收藏⭐️ + 留言📝​ 

💬本系列哔哩哔哩江科大STM32的视频为主以及自己的总结梳理📚 

🚀Projeet source code🚀   

💾工程代码放在了本人的Gitee仓库:iPickCan (iPickCan) - Gitee.com

引用:

STM32入门教程-2023版 细致讲解 中文字幕_哔哩哔哩_bilibili

Keil5 MDK版 下载与安装教程(STM32单片机编程软件)_mdk528-CSDN博客

STM32之Keil5 MDK的安装与下载_keil5下载程序到单片机stm32-CSDN博客

0. 江协科技/江科大-STM32入门教程-各章节详细笔记-查阅传送门-STM32标准库开发_江协科技stm32笔记-CSDN博客

江科大STM32学习笔记(上)_stm32博客-CSDN博客

STM32学习笔记一(基于标准库学习)_电平输出推免-CSDN博客

STM32 MCU学习资源-CSDN博客

术语:

英文缩写描述
GPIO:General Purpose Input Onuput通用输入输出
AFIO:Alternate Function Input Output复用输入输出
AO:Analog Output模拟输出
DO:Digital Output数字输出
内部时钟源 CK_INT:Clock Internal内部时钟源
外部时钟源 ETR:External clock 时钟源 External clock 
外部时钟源 ETR:External clock mode 1外部时钟源 External 时钟模式1
外部时钟源 ETR:External clock mode 2外部时钟源 External 时钟模式2
外部时钟源 ITRx:Internal trigger inputs外部时钟源,ITRx (Internal trigger inputs)内部触发输入
外部时钟源 TIx:external input pin 外部时钟源 TIx (external input pin)外部输入引脚

正文:

0. 概述

从 2024/06/12 定下计划开始学习下江协科技STM32课程,接下来将会按照哔站上江协科技STM32的教学视频来学习入门STM32 开发,本文是视频教程 P2 STM32简介一讲的笔记。


定时器共四个部分,分为八个小节笔记。本小节为第一部分第一节。

🌳在第一部分,是定时器的基本定时的功能:定时中断功能、内外时钟源选择

🌳在第二部分,是定时器的输出比较功能,最常见的用途是产生PWM波形,用于驱动电机等设备

🌳在第三部分,是定时器的输入捕获功能和主从触发模式,来实现测量方波频率

🌳在第四部分,是定时器的编码器接口,能够更加方便读取正交编码器的输出波形,编码电机测速


🌵知识点get:

滤波器工作原理:可以滤掉信号的抖动干扰。在一个固定的时钟频率f下进行采样,如果连续N个采样点都为相同的电平,那就代表输入信号稳定了,就把这个采样值输出出去;如果这N个采样值不全都相同,那就说明信号有抖动,这时就保持上一次的输出,或者直接输出低电平也行,这样就能保证输出信号在一定程度上的滤波;这里的采样频率f和采样点数N都是滤波器的参数,频率越低,采样点数越多,滤波效果越好,不过相应的信号延迟就越大;采样频率f由内部时钟直接而来,也可以是由内部时钟加一个时钟分频而来,这个分频多少是由参数ClockDivision决定的,这个参数其实跟时基单元关系并不大,它的可选值可以选择1分频(也就是不分频),2分频和4分频

1.🔎定时中断和时钟源选择相关库函数使用 

定时器相关的库函数非常多,本节仅对将要使用的库函数和 亿些使用细节 进行说明(即使这样也还是很多)。

定时中断基本结构如下,便于理解下面的库函数及程序流程

 定时器初始化步骤如下,对应定时中断结构图

🌾第一步,RCC开启时钟,定时器的基准时钟和整个外设的工作时钟就都打开了
🌾第二步,选择时基单元的时钟源,对于定时中断就选择内部时钟源
🌾第三步,配置时基单元,包括预分频器、自动重装载器、计数模式等,参数用结构体配置
🌾第四步,配置输出中断控制,允许更新中断输出到NVIC
🌾第五步,配置NVIC,在NVIC中打开定时器中断通道并分配一个优先级
🌾第六步,运行控制,使能计数器,当定时器使能后,计数器就开始计数了,当计数器更新时,触发中断
🌾最后再写一个中断函数,中断函数每隔一段时间就能自动执行一次


 2.🔎定时器TIM的库函数

  • 基本配置、时基单元、中断输出控制、NVIC、运行控制函数
// 恢复定时器缺省配置
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);

3.🔎参数(PSC、ARR等)更改函数(在程序运行过程中修改

以下,单独的函数可以方便地更改PSC\ARR等参数

// 预分频值设置,TIM_PSCReloadMode为是否应用输入缓冲功能配置
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);

获取定时器中断标志位和清除定时器中断标志位,使用方法和EXTI外部中断相同。

//在主函数中获取定时器中断标志位
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);

4.🔎使用定时器库函数的一些细节

* 选择内部时钟函数:定时器上电后默认选择内部时钟,如果要选择内部时钟,这一句可以省略。

TIM_InternalClockConfig(TIM_TypeDef* TIMx);

时基单元初始化函数TIN_TimeBaseInit:在配置结构体变量时,会遇到以下几个细节问题 

  • 🌵1.TIM_TimeBaseInitStructure.TIM_ClockDivision (采样)时钟分频频率选择

  在定时器的外部信号输入引脚一般都有一个滤波器消除信号的抖动干扰,它的工作原理是:在一个固定的时钟频率f下进行采样,如果连续N个采样点都是相同的电平,就代表输入信号稳定了,就将采样值输出到下一级电路;如果N个采样点不全都相同,就说明信号有抖动,这时保持上一次的输出,或直接输出低电平。 这样就能保证输出信号在一定程度上的滤波。这里的采样频率f 和采样点数N,f和N是滤波器的参数,频率越低,采样点数越多,滤波效果就越好,不过相应的信号延迟就越大。
  采样频率f的来源可以是内部时钟直接提供,也可以是内部时钟加一个时钟分频而来。 分频是多少,就由参数TIM_ClockDivision决定。可见 TIM_ClockDivision与时基单元的关系并不大,它的可选值可以选择1分频,2分频和4分频。

  • 🌵2.TIM_TimeBaseInitStructure.TIM_CounterMode  计数器模式

        TIM_CounterMode_Up  (向上计数)

        TIM_CounterMode_CenterAligned1  (中央对齐计数)

  • 🌵3.TIM_TimeBaseInitStructure.TIM_Period     周期,ARR自动重装器的值
  • 🌵4.TIM_TimeBaseInitStructure.TIM_Prescaler  PSC预分频器的值

以上2-3-4参数就是时基单元里面每个关键寄存器的参数,在配置结构体变量时,并没有能直接操作计数器CNT的参数。如果需要,可以采用SetCounter和GetCounter两个函数来操作计数器。

  • 🌵5.TIM_TimeBaseInitStructure.TIM_RepetitionCounter 重复计数寄存器,通过这个参数可以设置重复计数寄存器。但是通用定时器中没有这一个寄存器,故可以直接设置为0。

  • 🌵6.定时时间的计算

决定定时时间的参数,是TIM_Period和TIM_Prescaler
  参考公式:

计数器溢出频率:CK_CNT_OV = CK_CNT / (ARR + 1)

                                                   = CK_PSC / (PSC + 1) / (ARR + 1)

* 定时1s也就是定时频率为1Hz,定时频率=72M/ (PSC + 1) / (ARR + 1) = 1s =1Hz,那就可以PSC给7200,ARR给10000(1MHz等于10^6Hz),然后两个参数再减1。

* 注意PSC(TIM_Prescaler)和ARR(TIM_Period)的取值都要在0~65535之间。

* PSC和ARR的取值不是唯一的。可以预分频给少点,自动重装给多点,这样就是以一个比较高的频率计比较多的数;也可以预分配给多点,自动重装给少点,这样就是以一个比较低的频率计比较少的数;两种方法都可以达到目标的定时时间。

* 在这里预分频是对72M进行7200分频,得到的就是10k的计数频率,在10k的频率下,计10000个数,就是1s的时间。

  • 🌵7.在TIM_TimeBaseInit函数的最后,会立刻生成一个更新事件,来重新装载预分频器和重复计数器的值。预分频器有缓冲寄存器,我们写入的PSC和ARR只有在更新事件时才会起作用。但是更新事件和更新中断是同时发生的,更新中断会置更新中断标志位,手动生成一个更新事件,就相当于在初始化时立刻进入更新函数执行一次,在开启中断之前手动清除一次更新中断标志位,就可以避免刚初始化完成就进入中断函数的问题。

* 使能中断函数TIM_ITConfig

TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE); //开启更新中断到NVIC的通道 

启动定时器 

TIM_Cmd(TIM2,ENABLE);//当产生更新时,就会触发中断 

* 外部时钟配置函数TIM_ETRClockMode2Config

🌵1.TIM_ExtTRGPrescaler外部时钟预分频器:可以选择外部时钟分频关闭(1分频)、2分频、4分频、8分频。


🌵2.TIM_ExtTRGPolarity 外部触发的极性TIM_ExtTRGPolarity_Inverted为反向极性,即低电平和下降沿有效;TIM_ExtTRGPolarity_NonInverted为不反向,即高电平和上升沿有效。


🌵3.ExtTRGFilter 外部输入滤波器:工作原理与内部时钟的滤波器相似,它的值可以是0x00到0x0F之间的一个值,其决定了采样的f和N,具体的对应关系在手册中有对应表:

🌵4.GPIO配置:因为是使用外部接口输入时钟,故在使用该函数之前还需要配置GPIO端口。对于定时器,手册中给的推荐配置是浮空输入。但是浮空输入会导致引脚的输入电平极易受干扰,所以输入信号的功率不小时一般选择上拉或下拉输入。当外部的输入信号功率很小,内部的上拉/下拉电阻(较大)可能会影响到这个输入信号,这时就需要用浮空输入,防止影响外部输入的电平。

5. 🔎STM32F103 本节使用到的TIM定时器函数介绍

打开Keil5中文件 stm32f10x_tim.h ,在文件的尾部位置就是 stm32f103 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);定时器选择内部时钟源
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配置,预分频,极性,过滤器

5.1 ➡️定时器使用的步骤

按照如下stm32f10x 定时器的框图,使用stm32 定时器基本按照如下的基本步骤

  1. 🌾启用RCC内部时钟,这样stm32外设和定时器才有时钟源输入。
  2. 🌾定时钟时钟源选择,可以选择内部时钟源,外部时钟源ETR,外部ITRx,外部TIx捕获通道。
  3. 🌾时基单元配置,配置时钟预分频器,自动重装载器
  4. 🌾定时器中断输出控制,允许定时器更新中断进入NVIC
  5. 🌾NVIC中配置定时器中断的优先级
  6. 🌾定时器运行控制,启动定时器

5.2 ➡️定时器时基单元配置

在stm32f10x_tim.h中定时器时基单元配置函数原型如下

void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);定时器“时基单元”配置

‘TIM_TimeBaseInit()’接收两个输入参数,一个是定时器TIMx,一个是时基单元配置结构体,该函数配置定时器框图中的如下红框部分。

5.3 ➡️定时器时钟源选择

5.3.1 定时器选择内部时钟源,函数原型如下

void TIM_InternalClockConfig(TIM_TypeDef* TIMx);定时器选择内部时钟源

该函数选择stm32定时器的时钟源为内部时钟源,也就是如下图中箭头所示从内部时钟源进入时基单元。

5.3.2 定时器选择外部时钟源ITRx,函数原型如下

void TIM_ITRxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource);定时器选择外部时钟源 ITRx

该函数选择stm32定时器的时钟源为外部时钟源ITRx,也就是如下图中箭头所示从外部时钟源ITRx进入时基单元。

5.3.3 定时器选择外部时钟源TIx,函数原型如下

void TIM_TIxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_TIxExternalCLKSource,
                                uint16_t TIM_ICPolarity, uint16_t ICFilter);
定时器选择外部时钟源 TIx,参数预分频,极性,过滤器

该函数选择stm32定时器的时钟源为外部时钟源TIx,也就是如下图中箭头所示从外部时钟源TIx进入时基单元。

5.3.4 定时器选择外部时钟源ETR 外部时钟模式2,函数原型如下

void TIM_ETRClockMode2Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, 
                             uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter);
定时器选择外部时钟源 ETR时钟模式2,参数预分频,极性,过滤器

该函数选择stm32定时器的时钟源为外部时钟源ETR 外部时钟模式2,也就是如下图中箭头所示从外部时钟源进入时基单元。

5.3.5 定时器选择外部时钟源ETR 外部时钟模式1,函数原型如下

 该函数选择stm32定时器的时钟源为外部时钟源ETR 外部时钟模式1,也就是如下图中箭头所示从外部时钟源进入时基单元。

5.4 ➡️定时器除上面的时基单元配置,内/外时钟源选择,本节可能用到的函数

定时器除上面的时基单元配置,内/外时钟源选择外,本节可能用到的函数还有如下:

void TIM_PrescalerConfig(TIM_TypeDef* TIMx, uint16_t Prescaler, uint16_t TIM_PSCReloadMode);定时器预分频值设置,函数参数处预分频值外,还有预分频值Reload重加载模式设置是否使用预分频值“影子寄存器”,是预分频值设置立即生效,还是预分频值先写入“影子寄存器”等到“更新事件”时再生效
void TIM_CounterModeConfig(TIM_TypeDef* TIMx, uint16_t TIM_CounterMode);定时器计数器计数模式,向上计数,向下计数
void TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState);定时器ARR重装载寄存器,预装载功能是否启用
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);获取预分频器的值

6.🔎实验1-定时器定时中断实例 

本次实验要完成的现象是:定义一个 uint16_t 的 Num 变量,使其每秒+1。器件连接图和程序源码如下所示:

timer.c

#include "stm32f10x.h"                  // Device header
#include "Timer.h"void Timer_Init(void)
{//Setp 1.//RCC APB1的外设时钟控制,因为TIM2在STM32的APB1外设总线上RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);//Setp 2.//选择时基单元的时钟,使用内部RCC时钟 CLK_INT (Clock_Internal)TIM_InternalClockConfig(TIM2);//Setp 3.//配置时基单元TIM_TimeBaseInitTypeDef TimeBaseInitStruct;TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;		//时钟信号滤波使用,滤波的采样频率,采样点数TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;	//计数器向上计数TimeBaseInitStruct.TIM_Period = 10000 - 1;					//Auto-Reload Register 自动重装载寄存器的值,记得需要减一TimeBaseInitStruct.TIM_Prescaler = 7200 - 1;				//预分频器的值,记得需要减一TimeBaseInitStruct.TIM_RepetitionCounter = 0;				//重复计数器的值	TIM_TimeBaseInit(TIM2, &TimeBaseInitStruct);//Setp 4.//使能定时中断TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);//Setp 5.//NVIC配置NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);	//NVIC优先级分组NVIC_InitTypeDef NVIC_InitStruct;NVIC_InitStruct.NVIC_IRQChannel = TIM2_IRQn;NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2;NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;NVIC_Init(&NVIC_InitStruct);					//NVIC配置定时器中断优先级//Setp 6.//定时器启动TIM_Cmd(TIM2, ENABLE);
}uint16_t Num;//定时器TIM2,中断处理函数
void TIM2_IRQHandler(void)
{//检查中断标志位if(SET == TIM_GetITStatus(TIM2, TIM_IT_Update)){Num++;//清除中断标志位TIM_ClearITPendingBit(TIM2, TIM_IT_Update);}
}

timer.h

#ifndef __TIMER_H__
#define __TIMER_H__
void Timer_Init(void);
#endif

main.c

#include "stm32f10x.h"                  // Device header
#include "oled.h"
#include "Countersensor.h"
#include "Encoder.h"
#include "Timer.h"extern uint16_t Num;int main(int argc, char *argv[])
{	OLED_Init();OLED_ShowString(1, 1, "Cnt:");Timer_Init();while(1){OLED_ShowNum(1,5,Num,5);}return 1;
}

6.1 😛实验结果

实验结果如下

6.1 😛实验一个小问题,按下Reset键之后Cnt值从1开始增加

江科大STM32本节教程里提到,本TIM定时器中断实验一个小问题,按下Reset键之后Cnt值从1开始增加。本地测试结果的确也是按下开发板上Reset按键之后,OLED显示屏上Cnt的值从1开始增加。

江科大STM32本节教程讲解了出现这个问题的原因,在stm32f10x_timeer.c 中找到 'TIM_TimeBaseInit()'的函数实现。

在函数的结尾

😎😎为什么要加这个,我们知道预分频器是有一个缓冲寄存器的,我们写的值只有在更新事件时才真正起作用,所以这里为了让值立即起作用,就在这里最后手动生成了一个更新事件,这样预分频器的值就有效了,但同时它的副作用是更新事件更新中断是同时发生的,更新中断会置更新中断标志位,当我们之后一旦初始化晚了,更新中断就会立刻进入。这就是我们刚一上电,就立刻进中断的原因。😎😎

🤓🤓那解决方法也非常简单。

就是在Time_BaseInit()的后面,在开启中断的前面,手动调用一下 Time_CleanFlag(TIME2, X) 将更新中断标志位清除一下,就能避免刚初始化我那侧灰姑娘就进中断的问题了。

 Timer.c 

#include "stm32f10x.h"                  // Device header
#include "Timer.h"void Timer_Init(void)
{//Setp 1.//RCC APB1的外设时钟控制,因为TIM2在STM32的APB1外设总线上RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);//Setp 2.//选择时基单元的时钟,使用内部RCC时钟 CLK_INT (Clock_Internal)TIM_InternalClockConfig(TIM2);//Setp 3.//配置时基单元TIM_TimeBaseInitTypeDef TimeBaseInitStruct;TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;		//时钟信号滤波使用,滤波的采样频率,采样点数TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;	//计数器向上计数TimeBaseInitStruct.TIM_Period = 10000 - 1;					//Auto-Reload Register 自动重装载寄存器的值,记得需要减一TimeBaseInitStruct.TIM_Prescaler = 7200 - 1;				//预分频器的值,记得需要减一TimeBaseInitStruct.TIM_RepetitionCounter = 0;				//重复计数器的值	TIM_TimeBaseInit(TIM2, &TimeBaseInitStruct);//在TIM_TimeBaseInit()之后,在启用中断之前,手动清除一下定时器更新中断标志位,//就能避免初始化之后就立即进入中断的问题了。TIM_ClearITPendingBit(TIM2, TIM_IT_Update);//Setp 4.//使能定时中断TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);//Setp 5.//NVIC配置NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);	//NVIC优先级分组NVIC_InitTypeDef NVIC_InitStruct;NVIC_InitStruct.NVIC_IRQChannel = TIM2_IRQn;NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2;NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;NVIC_Init(&NVIC_InitStruct);					//NVIC配置定时器中断优先级//Setp 6.//定时器启动TIM_Cmd(TIM2, ENABLE);
}

此时再使用Keil5 MDK编译,STLink下载到开发板测试一下,按下 Reset 按钮之后,OLED屏幕上Cnt值就从 0 开始增加了,问题解决。

 

7.🔎实验2-定时器外部时钟选择

本实验使用定时器外部时钟源 EXT mode2 (External Trigger mode2)。

*🌵 可以在引脚定义图里找TIMx的ETR引脚是哪个

* 🌵在上一个定时中断实例程序基础上进行更改;基本任务仍然是定时中断,时钟部分就不使用内部时钟了

本次实验要完成的现象是:用光敏传感器手动模拟一个外部时钟,定义一个 uint16_t 的 Num 变量,当外部时钟触发10次(预分频之后的脉冲)后Num + 1。器件连接图和程序源码如下所示:

timer.c

#include "stm32f10x.h"                  // Device header
#include "Timer.h"void Timer_Init(void)
{//Setp 1.//RCC APB1的外设时钟控制,因为TIM2在STM32的APB1外设总线上RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);//GPIO初始化//开启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);//Setp 2.//选择时基单元的时钟,使用内部RCC时钟 CLK_INT (Clock_Internal)//TIM_InternalClockConfig(TIM2);//选择时基单元的时钟,使用外部时钟ETR mode2TIM_ETRClockMode2Config(TIM2, TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_NonInverted, 0x0f);//Setp 3.//配置时基单元TIM_TimeBaseInitTypeDef TimeBaseInitStruct;TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;		//时钟信号滤波使用,滤波的采样频率,采样点数TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;	//计数器向上计数TimeBaseInitStruct.TIM_Period = 10 - 1;						//Auto-Reload Register 自动重装载寄存器的值,记得需要减一TimeBaseInitStruct.TIM_Prescaler = 1 - 1;					//预分频器的值,记得需要减一TimeBaseInitStruct.TIM_RepetitionCounter = 0;				//重复计数器的值	TIM_TimeBaseInit(TIM2, &TimeBaseInitStruct);//在TIM_TimeBaseInit()之后,在启用中断之前,手动清除一下定时器更新中断标志位,//就能避免初始化之后就立即进入中断的问题了。TIM_ClearITPendingBit(TIM2, TIM_IT_Update);//Setp 4.//使能定时中断TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);//Setp 5.//NVIC配置NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);	//NVIC优先级分组NVIC_InitTypeDef NVIC_InitStruct;NVIC_InitStruct.NVIC_IRQChannel = TIM2_IRQn;NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2;NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;NVIC_Init(&NVIC_InitStruct);					//NVIC配置定时器中断优先级//Setp 6.//定时器启动TIM_Cmd(TIM2, ENABLE);
}uint16_t Num;//定时器TIM2,中断处理函数
void TIM2_IRQHandler(void)
{//检查中断标志位if(SET == TIM_GetITStatus(TIM2, TIM_IT_Update)){Num++;//清除中断标志位TIM_ClearITPendingBit(TIM2, TIM_IT_Update);}
}

 

7.1 😛实验结果

7.2😛 实验注意和经验总结

1.初始化GPIOA PA0的时候,需要注意使用的是

    RCC_APB2PeriphClockCmd()

   别错误的写成了 RCC_APB1PeriphClockCmd(),编译器并不会报错,但是GPIOA却没有启用RCC时钟,是因为 GPIOA 是挂载 APB2上的外设,不能使用RCC_APB1开启它的时钟。

2. 在做定时器外部时钟源选择 ETR mode2的实验的时候,需要注意调小时钟源的预分频器值和重装载器的值。因为手动使用红外对射传感器的方式来产生方波信号的频率很低,如果有一个很大的预分频值,则需要产生很多的预分频方波信号之后才能产生一个计数器时钟信号让计数器加一。

8. 🔎结束

本章至此结束

 

相关文章:

江协科技STM32学习- P14 示例程序(定时器定时中断和定时器外部时钟)

🚀write in front🚀 🔎大家好,我是黄桃罐头,希望你看完之后,能对你有所帮助,不足请指正!共同学习交流 🎁欢迎各位→点赞👍 收藏⭐️ 留言📝​…...

2024年CSP-J认证 CCF信息学奥赛C++ 中小学初级组 第一轮真题-阅读程序题解析

2024 CCF认证第一轮&#xff08;CSP-J&#xff09;真题 二、阅读程序题 (程序输入不超过数组或字符串定义的范围&#xff0c;判断题正确填√错误填X;除特殊说明外&#xff0c;判断题 1.5分&#xff0c;选择题3分&#xff0c;共计40 分) 第一题 01 #include <iostream>…...

Hive ROW_NUMBER() 简介

在 Apache Hive 中&#xff0c;ROW_NUMBER() 是一个窗口函数&#xff0c;常用于为查询结果中的每一行生成唯一的行号。它在 SQL 查询结果集中按照指定的排序规则对每一行进行编号。ROW_NUMBER() 的实现依赖于 Hive 的分布式执行框架和排序机制。 为了理解 ROW_NUMBER() 的底层实…...

java是干什么的

Java 是一种广泛使用的编程语言&#xff0c;主要用于以下几个方面&#xff1a; Web 开发&#xff1a;Java 可以用于创建动态网页和 Web 应用程序&#xff0c;常见的框架有 Spring 和 JavaServer Faces&#xff08;JSF&#xff09;。 企业级应用&#xff1a;Java 被广泛应用于…...

AI与量化投资人才培养计划-连接职场 助力走在金融行业前沿

AI与量化投资人才培养计划-连接职场 助力走在金融行业前沿 人工智能&#xff08;AI&#xff09;的快速发展&#xff0c;量化投资已逐渐成为金融行业的新趋势&#xff0c;对专业人才的需求日益迫切。本文将深入探讨一项针对AI与量化投资的人才培养计划&#xff0c;旨在为金融专业…...

《CUDA编程》2.CUDA中的线程组织

0 来自GPU的hello world 在visua studio 中新建一个CUDA runtime项目&#xff0c;然后把kernel.cu中的代码删掉&#xff0c;输入以下代码 #include"cuda_runtime.h" #include"device_launch_parameters.h"#include<stdio.h>__global__ void hello_…...

学习篇 | Dockerized GitLab 安装使用(简单实操版)

1. 详细步骤 1.1 安装启动 postgresql 服务 docker pull sameersbn/postgresql:14-20230628docker run --name gitlab-postgresql -d \--env DB_NAMEgitlabhq_production \--env DB_USERgitlab --env DB_PASSpassword \--env DB_EXTENSIONpg_trgm,btree_gist \--volume /srv/…...

Linux服务器磁盘扩容

文章目录 扩容挂载 扩容 [rootserver8 ~]# lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT sr0 11:0 1 1024M 0 rom vda 252:0 0 1T 0 disk ├─vda1 252:1 0 1G 0 par…...

Redis的一些数据类型(一)

&#xff08;一&#xff09;数据类型 我们说redis是key value键值对的方式存储数据&#xff0c;key是字符串&#xff0c;而value是一些数据结构,那今天就来说一下value存储的数据。 我们数据结构包含&#xff0c;String&#xff0c;hash&#xff0c;list&#xff0c;set和zest但…...

论文复现:考虑电网交互的风电、光伏与电池互补调度运行(MATLAB-Yalmip-Cplex全代码)

论文复现:考虑电网交互的风电、光伏与电池储能互补调度运行(MATLAB-Yalmip-Cplex全代码) 针对风电、光伏与电化学储能电站互补运行的问题,已有大量通过启发式算法寻优的案例,但工程上更注重实用性和普适性。Yalmip工具箱则是一种基于MATLAB平台的优化软件工具箱,被广泛应用…...

HTTP 协议介绍

基本介绍&#xff1a; HTTP&#xff08;Hyper Text Transfer Protocol&#xff09;&#xff1a; 全称超文本传输协议&#xff0c;是用于从万维网&#xff08;WWW:World Wide Web &#xff09;服务器传输超文本到本地浏览器的传送协议。 HTTP 是一种应用层协议&#xff0c;是基…...

解决windows上VMware的ubuntu虚拟机不能拷贝和共享

困扰多时的VMware虚拟机不能复制拷贝和不能看到共享文件夹的问题&#xff0c;终于解决了~ 首先确定你已经开启了复制拷贝和共享文件夹&#xff0c;并且发现不好用。。。 按照下面方式解决这个问题。 1&#xff0c;删除当前的vmware tools。 sudo apt-get remove --purge ope…...

Python+rust会是一个强大的组合吗?

今天想和大家讨论一个在技术圈子里越来越火的话题——Python和Rust的组合。 不少程序员都开始探索这两个语言的结合&#xff0c;希望能借助Python的简洁和Rust的高性能&#xff0c;来打造出既易用又强大的软件。 那么&#xff0c;这对CP&#xff08;编程组合&#xff09;真的…...

引用和指针的区别

引用&#xff08;reference&#xff09;和指针&#xff08;pointer&#xff09;都是 C 中用来间接访问内存中对象的机制&#xff0c;但它们有一些重要的区别。以下是它们在语法、用法和特性上的详细区别。 下面从7个方面来详细说明引用和指针的区别 1. 定义与语法区别 引用&…...

内容生态短缺,Rokid AR眼镜面临市场淘汰赛

AR是未来&#xff0c;但在技术路径难突破、生态系统难建设&#xff0c;且巨头纷纷下场的背景下&#xff0c;Rokid能坚持到黎明吗&#xff1f; 转载&#xff1a;科技新知 原创 作者丨王思原 编辑丨蕨影 苹果Vision Pro的成功量产和发售&#xff0c;以及热门游戏《黑神话》等在A…...

【论文阅读】StoryMaker | 更全面的人物一致性开源工作

文章目录 1 Motivation2 背景 相关工作 Related work3 Method 方法4 效果 1 Motivation 背景是 Tuning-free personalized image generation methods无微调的个性化图像生成方式在维持脸部一致性上取得了显著性的成功。这里我不是很了解 然而&#xff0c;在多个场景中缺乏整…...

读构建可扩展分布式系统:方法与实践14流处理系统

1. 流处理系统 1.1. 时间就是金钱 1.1.1. 从数据中提取有价值的知识和获得洞见的速度越快&#xff0c;就能越快地响应系统所观察的世界的变化 1.1.2. 信用卡欺诈检测 1.1.3. 网络安全中异常网络流量的捕获 1.1.4. 在支持GPS的驾驶应用程序中进行的实时路线规划 1.1.5. 社交…...

C++第2课——取余运算符的应用、浮点型和字符型(含视频讲解)

文章目录 1、课程笔记2、课程视频 1、课程笔记 /* #include<iostream> using namespace std; int main(){//cout<<"hello,world!";//运算符的优先级 () * / % -// 3/2 1...1 3%21 5%32 3%53 -3%2-1 3%-21//cout<<6/4%2;//int 向下取整6…...

SQL常用技巧总结

查询优化基本准则 1、ORACLE 的解析器按照从右到左的顺序处理 FROM 子句中的表名&#xff0c;因此 FROM 子句中写在最后的表(基础表 driving table)将被最先处理。 在FROM 子句中包含多个表的情况下&#xff0c;你必须选择记录条数最少的表作为基础表。 例如&#xff1a; 表 T…...

AJAX(简介以及一些用法)

AJAX 1. 简介 什么是 Ajax Ajax 的全称是 Asynchronous JavaScript And XML &#xff08;异步 JavaScript 和 XML &#xff09;我们可以理解为&#xff1a;在网页中 利用 XMLHttpRequest 对象和服务器进行数据交互的方式就是 Ajax &#xff0c;它可以帮助我们轻松实现网页…...

美畅物联丨GB/T 28181系列之TCP/UDP被动模式和TCP主动模式

GB/T 28181《安全防范视频监控联网系统信息传输、交换、控制技术要求》作为我国安防领域的重要标准&#xff0c;为视频监控系统的建设提供了全面的技术指导和规范。该标准详细规定了视频监控系统的信息传输、交换和控制技术要求&#xff0c;在视频流传输方面&#xff0c;GB/T 2…...

机器学习之实战篇——图像压缩(K-means聚类算法)

机器学习之实战篇——图像压缩(K-means聚类算法&#xff09; 0. 文章传送1.实验任务2.实验思想3.实验过程 0. 文章传送 机器学习之监督学习&#xff08;一&#xff09;线性回归、多项式回归、算法优化[巨详细笔记] 机器学习之监督学习&#xff08;二&#xff09;二元逻辑回归 …...

轴承介绍以及使用

轴承&#xff08;Bearing&#xff09;是在机械传动过程中起固定、旋转和减小载荷摩擦系数的部件。也可以说&#xff0c;当其它机件在轴上彼此产生相对运动时&#xff0c;用来降低运动力传递过程中的摩擦系数和保持转轴中心位置固定的机件。 轴承是当代机械设备中一种举足轻重的…...

【JAVA】算法笔记

一、ArrayList ArrayList类是一个可以动态变化的数组&#xff0c;与普通数组的区别就是它没有固定的长度。 ArrayList<String> arrList new ArrayList<String>(); arrList.add("吐泡泡"); System.out.println(arrList.get(0)); arrList.set(0,"J…...

Gnu Radio抓取WiFi信号,流程图中模块功能

模块流程如图所示&#xff1a; GNURadio中抓取WiFi信号的流程图中各个模块的功能&#xff1a; UHD: USRP Source&#xff1a; 使用此模块配置USRP硬件进行信号采集。设置频率、增益、采样率等参数。Complex to Mag^2&#xff1a; 将复数IQ数据转换为幅度的平方。Delay&#xf…...

GO语言中make与new的区别

区别 1 make不仅分配内存&#xff0c;还会初始化。 new只会分配零值填充的值2make只适用slice,map,channel的数据&#xff0c;new 没有限制3make返回原始类型(T),new返回类型的指针(*T) 源码中定义的区别 func make(t Type,size …IntegerType) Type func new(Type) *Type f…...

安全运维类面试题

1、你熟悉哪些品牌的安全设备 答&#xff1a;天融信的ngfw防火墙&#xff0c;老牌防火墙厂商&#xff0c;功能比较齐全&#xff0c;像流量检测&#xff0c;web应用防护和僵木蠕等模块都有&#xff0c;界面是红白配色&#xff0c;设计稍微有点老 2、IPS用的是哪个牌子的 答&…...

STM32外设之LTDC/DMA2D—液晶显示(野火)

文章目录 显示屏有几种?基本参数控制?显存 LTDC 液晶控制器LTDC 结构框图LTDC 初始化结构体 LTDC_InitTypeDefLTDC 层级初始化结构体 DMA2D 图形加速器DMA2D 初始化结构体 要了解什么 屏幕是什么&#xff0c;有几种屏&#xff0c;有什么组成。 怎么控制&#xff0c;不同屏幕控…...

调试vue build之后的js文件

调试 dist 目录下的 JavaScript 文件可以按照以下步骤进行&#xff1a; 1. 确保 Source Maps 正常生成 确认你的构建配置中已启用 Source Maps&#xff0c;确保 .map 文件与构建后的 .js 文件位于同一目录。 2. 启动一个本地服务器 使用本地服务器来服务 dist 目录&#xf…...

Django一分钟:DRF快速实现JWT认证与RBAC权限校验

一、项目创建并实现JWT认证 1. 下载依赖 下载django、djangorestframework、djangorestframework_simplejwt pip install django djangorestframework djangorestframework_simplejwt2. 创建项目 启动Django项目 django-admin startproject <myproject> cd myprojec…...