STM32-TIM定时器
本内容基于江协科技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、TIM8 | APB2 | 拥有通用定时器全部功能,并额外具有重复计数器、死区生成、互补输出、刹车输入等功能 |
通用定时器 | TIM2、TIM3、TIM4、TIM5 | APB1 | 拥有基本定时器全部功能,并额外具有内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等功能 |
基本定时器 | TIM6、TIM7 | APB1 | 拥有定时中断、主模式触发DAC的功能 |
- STM32F103C8T6定时器资源:TIM1、TIM2、TIM3、TIM4
- DAC:数模转换器
1.3 基本定时器
主要功能:
- 16位自动重装载累加计数器
- 16位可编程预分频器,用于对输入的时钟按系数为1~65536之间的任意数值分频
- 触发DAC的同步电路
- 在更新事件(计数器溢出)时产生中断/DMA请求
-
预分频器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:
- 更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或内部/外部触发)
- 触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)
- 输入捕获
- 输出比较
-
支持针对定位的增量(正交)编码器和霍尔传感器电路
-
触发输入作为外部时钟或者按周期的电流管理
-
CNT计数器
CNT计数器支持向上计数模式、向下计数模式、中央对齐模式。向下计数模式就是从重装值开始,向下自减,减到0之后,回到重装值同时申请中断。中央对齐模式是从0开始,先向上自增,计到重装值,申请中断,然后再向下自减,减到0,再申请中断。 -
时钟源
通用定时器的时钟源不仅可以选择内部72MHz时钟,还可以选择外部时钟。
(1)第一个外部时钟就是来自TIMx_ETR引脚上的外部时钟,也就是可以在TIM2的ETR引脚即PA0上接一个外部方波时钟,然后配置一下内部的极性选择、边沿检测和预分频器电路,以及输入滤波电路,这两块电路可以对外部时钟进行一定的整形。因为是外部引脚的时钟,所以难免有一些毛刺,那这些电路就可以对输入的波形进行滤波。滤波后的信号,兵分两路,上面一路ETRF进入触发控制器,紧跟着就可以选择作为时基单元的时钟了。这一路也叫做“外部时钟模式2”。
(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:
- 更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或内部/外部触发)
- 触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)
- 输入捕获
- 输出比较
- 刹车信号输入
- 支持针对定位的增量(正交)编码器和霍尔传感器电路
- 触发输入作为外部时钟或者按周期的电流管理
-
重复次数计数器
和通用定时器的区别:第一个是在申请中断的地方增加了一个重复次数计数器,有了这个计数器之后,就可以实现每隔几个计数周期才发生一次更新事件和更新中断。原来的结构是每个计数周期完成后就都会发生更新,这就相当于对输出的更新信号又做了一次分频。 -
DTG死区生成器和互补输出
下面是高级定时器对输出比较模块的升级了,DTG(dead time generate)是死区生成电路。右边的输出引脚由原来的一个变为了两个互补的输出,可以输出一对互补的PWM波,这些电路是为了驱动三相无刷电机的,比如四轴飞行器、电动车的后轮、电钻等,里面都可能是三相无刷电机。因为三相无刷电机的驱动电路一般需要3个桥臂,每个桥臂2个大功率开关管来控制,所以总共需要6个大功率开关管。因此这里的输出PWM引脚的前三路就变为了互补的输出。另外,为了防止互补输出的PWM驱动桥臂时,在开关切换的瞬间,由于器件的不理想,造成短暂的直通现象,所以前面就加上了死区生成电路。在开关切换的瞬间,产生一定时长的死区,让桥臂的上下管全都关断,防止直通现象。 -
刹车输入
最后一部分就是刹车输入的功能了,这个是为了给电机驱动提供安全保障的。如果外部引脚BKIN(Break IN)产生了刹车信号,或内部时钟失效,产生了故障,那么控制电路就会自动切断电机的输出,防止意外的发生。
1.5 定时中断基本结构
运行控制:控制寄存器中的一些位,如启动停止、向上或向下计数等。
右边就是计时时间到,产生更新中断后的信号去向,如果是高级定时器的话,还会多一个重复计数器。中断信号会先在状态寄存器里置一个中断标志位,这个标志位会通过中断输出控制,到NVIC申请中断。中断输出控制就是一个中断输出的允许位,如果需要某个中断,就记得允许一下。
1.6 预分频器时序
- 计数器计数频率: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 计数器时序
- 计数器溢出频率: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 计数器无预装时序
更改了自动加载寄存器,由FF改成了36,那计数值的目标值就由FF变成了36,所以这里计到36之后,就直接更新开始下一轮的计数,
1.9 计数器有预装时序
在计数的中途,把计数目标值由F5改成了36。下面有个影子寄存器,这个影子寄存器才是真正起作用的,它还是F5,所以现在计数的目标还是计到F5,产生更新事件,同时,要更改的36才被传递到影子寄存器,在下一个计数周期这个更改的36才有效,所以这个引入影子寄存器的目的实际上是为了同步,就是让值的变化和更新事件同步发生,防止在运行途中,更改造成错误。
1.10 RCC时钟树
-
时钟源
在时钟产生电路,有四个震荡源:
(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值(中断触发次数,也即定时时间)
2.2.2 代码流程
-
定时器代码
- 开启时钟RCC,定时器的基准时钟和整个外设的工作时钟都会同时打开了;
- 选择时基单元的时钟源,对于定时中断,选择内部时钟源;
- 配置时基单元,包括预分频器、自动重装器、计数模式等–用结构体;
- 配置中断输出控制,允许更新中断输出到NVIC;
- 配置NVIC,在NVIC中打开定时器中断的通道,并分配一个优先级;
- 运行控制;
整个模块配置完成后,还需要使能一下计数器,要不然计数器是不会运行的。当定时器使能后,计数器就开始计数了,当计数器更新时,触发中断,最后再写一个定时器的中断函数,这样这个中断函数就每隔一段时间就能自动执行一次了。
-
中断函数
- 判断是否进入定时器TIM2的中断,然后清除中断标志位。
- 使定时器每秒自动加一下Num变量
-
ARR和PSC设置
- ARR=10000 - 1;PSC = 7200 - 1;
- 定时器时钟CK_CNT =72M / (PSC + 1) = 10000 ;
- 定时频率 = CK_CNT / (ARR + 1) = 1。定时1s,也就是定时频率为1Hz。
预分频是对72M进行7200分频,得到的就是10K的计数频率,在10K的频率下,计10000个数,就是1s的时间。(在1s的时间内计10000个数,当计到10000个数后,自动清0,同时申请中断,在OLED显示屏上就是显示Num值每1s加1)。也可以更改ARR和PSC的值。
- main函数
- 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线路的抢占优先级为2NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //指定NVIC线路的响应优先级为1NVIC_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的值。
2.3.2 代码流程
- 定时器代码
- 开启时钟RCC,TIM2和GPIOA。
- 配置GPIO,为上拉输入。
- 选择时基单元的时钟源,外部时钟模式2,时钟从TIM2_ETR引脚输入;
- 配置时基单元,包括预分频器、自动重装器、计数模式等–用结构体;
- 配置中断输出控制,允许更新中断输出到NVIC;
- 配置NVIC,在NVIC中打开定时器中断的通道,并分配一个优先级;
- 运行控制;
- ARR和PSC
- ARR = 10 - 1; PSC = 1 - 1;
- 因为没有进行分频,所以对射式红外传感器每遮挡一次,计数值CNT加1,当计数值加到9后,自动清零,同时申请中断,Num++。
- main函数
- 实现在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线路的抢占优先级为2NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //指定NVIC线路的响应优先级为1NVIC_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更新事件的中断标志位//中断标志位必须清除//否则中断将连续不断地触发,导致主程序卡死}
}
相关文章:

STM32-TIM定时器
本内容基于江协科技STM32视频内容,整理而得。 文章目录 1. TIM1.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库函数…...

Python OpenCV与霍夫变换:检测符合特定斜率范围的直线
在计算机视觉和图像处理领域,检测图像中的直线是一项常见且重要的任务。OpenCV 提供了许多强大的工具来进行图像处理,其中霍夫变换(Hough Transform)就是用于检测直线的经典方法。本文将介绍如何使用 OpenCV 和霍夫变换来检测图像…...

ubuntu22.04+pytorch2.3安装PyG图神经网络库
ubuntu下安装torch-geometric库,图神经网络 开发环境 ubuntu22.04 conda 24.5.0 python 3.9 pytorch 2.0.1 cuda 11.8 pyg的安装网上教程流传着许多安装方式,这些安装方式主要是:预先安装好pyg的依赖库,这些依赖库需要对应上pyth…...

新型开发语言的试用感受-仓颉语言发布之际
经常听一些媒体介绍一些新型的开发语言,所以最近心血来潮,安装了几种感受了一些。 先看名气,如ziglang,网址为:Home ⚡Zig Programming Language 号称是可以取代C语言的一门新语言,其实我主要是受下面这篇…...

基于字典学习的地震数据降噪(MATLAB R2021B)
稀疏表示基于研究者们提出了许多变换基函数的方法逐渐成型,比如小波域,曲波域,dreamlet 域等,其原理是利用地震信号在变换域内的稀疏性和可分离性以去除噪声。继 Donoho发表非线性去噪方法-小波阈值萎缩方法,在后续的研…...

【Web】
1、配仓库 [rootlocalhost yum.repos.d]# vi rpm.repo ##本地仓库标准写法 [baseos] namemiaoshubaseos baseurl/mnt/BaseOS gpgcheck0 [appstream] namemiaoshuappstream baseurlfile:///mnt/AppStream gpgcheck0 2、挂载 [rootlocalhost ~]mount /dev/sr0 /mnt mount: /m…...

kafka-3
Kafka 消费组 consumer-offsets-N 稀疏索引 Kafka集群 集群搭建 集群启动和验证 Topic的意义 Topic和Partition 分区 副本 集群操作指令 多分区&多副本 多分区消费组 Rebalance机制 Rebalance机制处理流程 Rebalance机制-Range Rebalance机制-RoudRobin Rebalance机制-St…...

MySQL性能优化 二、表结构设计优化
1.设计中间表 设计中间表,一般针对于统计分析功能,或者实时性不高的需求。 2.设计冗余字段 为减少关联查询,创建合理的冗余字段(创建冗余字段还需要注意数据一致性问题) 3.折表 对于字段太多的大表,考…...

用HttpURLConnection复现http响应码405
目录 使用GET方法,访问GET接口,服务端返回405使用GET方法,访问POST接口,服务端返回405使用POST方法,访问GET接口,服务端返回405 使用GET方法,访问GET接口,服务端返回405 发生场景&a…...

2-27 基于matlab的一种混凝土骨料三维随机投放模型
基于matlab的一种混凝土骨料三维随机投放模型,为混凝土细观力学研究提供一种快捷的三维建模源代码。可设置骨料数量,边界距离、骨料大小等参数。程序已调通,可直接运行。 2-27 matlab 混凝土骨料三维随机投放模型 - 小红书 (xiaohongshu.com)…...

ISA95-Part4-业务流程的解析与设计思路
MES/MOM系统实现ISA-95标准的业务流程通常遵循以下思路,并包含一系列内容。 一、功能模块: 1. 需求分析与规划: - 确定业务流程需求,包括订单管理、生产调度、库存控制等,并规划如何将这些流程与MES/MOM系统集成。 2. 系统集成架构设计: - 设计一个系统集成架构,确保M…...

【Spring Cloud】一个例程快速了解网关Gateway的使用
Spring Cloud Gateway提供了一个在Spring生态系统之上构建的API网关,包括:Spring 5,Spring Boot 2和Project Reactor。Spring Cloud Gateway旨在提供一种简单而有效的路由方式,并为它们提供一些网关基本功能,例如&…...

仿哔哩哔哩视频app小程序模板源码
仿哔哩哔哩视频app小程序模板源码 粉色的哔哩哔哩手机视频网页,多媒体视频类微信小程序ui前端模板下载。包含:视频主页和播放详情页。 仿哔哩哔哩视频app小程序模板源码...

数据库存储引擎
MySQL体系结构 存储引擎 -- 查询建表语句 show create table account; -- 查询引擎 show engines; InnoDB 特点 DML操作遵循ACID模型,支持事务 行级锁,提高并发访问性能支持外键约束 文件 xxx.ibd:xxx代表的是表名,innoDB引擎的每张表都会对应这样…...

【单片机毕业设计选题24049】-基于STM32单片机的智能手表设计
系统功能: 显示时间,温湿度,体温信息,播放音乐及控制红外小夜灯,通过蓝牙模块连接手机APP。 系统上电后OLED显示“欢迎使用智能手表系统请稍后”,两秒后进入正常页面显示 第一行显示获取到的当前时间 第二行显示获…...

利用面向AWS的Thales Sovereign解决方案保护AI之旅
亚马逊网络服务(AWS)是全球最大的云服务提供商。众所周知,他们致力于提供工具、解决方案和最佳实践,使其客户能够安全地利用AWS上的生成式人工智能 (GenAI) 工作负载。组织正在迅速使用GenAI为企业带来更高的生产力和创造力。在GenAI的几乎所有用途中&am…...

学习笔记——交通安全分析13
目录 前言 当天学习笔记整理 5城市主干道交通安全分析 结束语 前言 #随着上一轮SPSS学习完成之后,本人又开始了新教材《交通安全分析》的学习 #整理过程不易,喜欢UP就点个免费的关注趴 #本期内容接上一期12笔记 当天学习笔记整理 5城市主干道交…...

PHP-实例-文件上传
1 需求 2 basename 在 PHP 中,basename() 函数用于返回路径中的文件名部分。如果路径中包含了文件扩展名,则该函数也会返回它。如果路径的结尾有斜杠(/)或反斜杠(\),则 basename() 函数会返回空…...

LeetCode刷题之HOT100之完全平方数
2024 7/7 转眼间就到周日啦!昨天下午开组会,开了三个半小时。如坐针毡,会后跑了个步、洗了个澡、洗了衣服、躺床上看了会《罪与罚》,睡着了。早上起来,去拿我昨晚充电的车,当我看到车没有停在昨天的位置&am…...

【SpringCloud应用框架】Nacos集群架构说明
第六章 Spring Cloud Alibaba Nacos之集群架构说明 文章目录 前言一、Nacos支持三种部署模式二、集群部署说明三、预备环境 前言 到目前为止,已经完成了对Nacos的一些基本使用和配置,接下来还需要了解一个非常重要的点,就是Nacos的集群相关的…...

JS进阶-作用域
学习目标: 掌握作用域 学习内容: 作用域局部作用域全局作用域作用域链JS垃圾回收机制拓展-JS垃圾回收机制-算法说明闭包变量提升 作用域: 作用域规定了变量能够被访问的"范围",离开了这个"范围"变量便不能被…...

stm32 使用GPIO模拟串口发送
在STM32微控制器上实现模拟串口输出(也称为软件串口或比特邦定(Bit-Banging)串口),主要是因为硬件上的UART资源有限或者为了特定需求而需要更多的串口通信接口。模拟串口意味着使用GPIO引脚模拟UART的TX(发…...

数据的统计探针:SKlearn中的统计分析方法
数据的统计探针:SKlearn中的统计分析方法 在数据科学领域,统计分析是理解和解释数据的关键工具。Scikit-learn(简称sklearn),作为Python中一个功能强大的机器学习库,提供了多种方法来进行数据的统计分析。…...

实例演示Kafka-Stream消息流式处理流程及原理
以下结合案例:统计消息中单词出现次数,来测试并说明kafka消息流式处理的执行流程 Maven依赖 <dependencies><dependency><groupId>org.apache.kafka</groupId><artifactId>kafka-streams</artifactId><exclusio…...

【博士每天一篇文献-综述】Threats, Attacks, and Defenses in Machine Unlearning A Survey
1 介绍 年份:2024 作者:刘子耀,陈晨,南洋理工大学 期刊: 未发表 引用量:6 Liu Z, Ye H, Chen C, et al. Threats, attacks, and defenses in machine unlearning: A survey[J]. arXiv preprint arXiv:2403…...

Python数据分析实战,运输车辆驾驶行为分析,案例教程编程实例课程详解
引言 运输车辆的安全驾驶行为分析是确保道路安全、提高运输效率的重要环节。随着数据采集技术的发展和数据分析工具的普及,利用Python进行数据分析已成为这一领域的重要工具。本文将详细介绍如何使用Python进行运输车辆驾驶行为分析,涵盖数据采集、数据预处理、数据分析及结果…...

网络安全法对等级保护中的权利和义务有何规范?
在数字时代的交响乐章中,网络安全法与等级保护共同编织了一曲关于权利与义务的和谐旋律。《中华人民共和国网络安全法》作为我国网络安全领域的基本法,对等级保护提出了明确的规范,旨在构建一个安全、有序的网络空间。本文将深入解析网络安全…...

苹果清理软件:让你的设备焕然一新
随着时间的推移,无论是Mac电脑还是iOS设备,都可能会因为积累的垃圾文件、缓存、未使用的应用和其他冗余数据而开始表现出性能下降。这不仅会占用宝贵的存储空间,还可能影响设备的响应速度和电池寿命。幸运的是,有多种苹果清理软件…...

vue前端通过sessionStorage缓存字典
正常来说,一个vue项目前端需要用到的一些翻译字典对象保存方式一般有多重, 新建js文件方式保存通过vuex方式保存通过sessionStorage保存通过localStorage保存 正常以上几点的保存方式是够用了。 但是,当有字典不能以文件方式保存并且字典量…...

React Redux使用@reduxjs/toolkit的hooks
关于redux的学习过程需要几个官网,有redux官网,React Redux官网和Redux Toolkit的官网。 其中后者的中文没有找到,不过其中的使用在React Redux官网的快速入门中有介绍。 现在一般不使用connect借接口了。 对于借助Redux Toolkit的React Redu…...