江协科技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 定时器基本按照如下的基本步骤
- 🌾启用RCC内部时钟,这样stm32外设和定时器才有时钟源输入。
- 🌾定时钟时钟源选择,可以选择内部时钟源,外部时钟源ETR,外部ITRx,外部TIx捕获通道。
- 🌾时基单元配置,配置时钟预分频器,自动重装载器
- 🌾定时器中断输出控制,允许定时器更新中断进入NVIC
- 🌾NVIC中配置定时器中断的优先级
- 🌾定时器运行控制,启动定时器
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 mode2
TIM_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. 🔎结束
本章至此结束