初识STM32单片机-TIM定时器

初识STM32单片机-TIM定时器

  • 一、定时器概述
  • 二、定时器类型
    • 2.1 基本定时器(TIM6和TIM7)
    • 2.2 通用定时器(TIM2、TIM3、TIM4和TIM5)
    • 2.3 高级定时器(TIM1和TIM8)
  • 三、定时中断基本结构和时基单元工作时序
    • 3.1 定时器基本结构
    • 3.2 预分频器时序
    • 3.3 计数器时序
      • 3.3.1 计数器有无预装时序(有无缓冲寄存器)
  • 四、TIM输出比较OC
    • [4.1 PWM波形](https://blog.csdn.net/m0_51319492/article/details/138548314)
    • [4.2 舵机](https://blog.csdn.net/m0_51319492/article/details/138548314)
    • 4.3 直流电机
  • 五、TIM输入捕获IC
    • 5.1 频率测量
    • 5.2 输入捕获/PWMI基本结构
      • 5.2.1 输入捕获基本结构
      • 5.2.2 PWMI基本结构
  • 六、编码器
    • 6.1 正交编码器方向
    • 6.2 编码器电路和基本结构
  • 七、代码编写
    • 7.1 定时器库函数介绍
    • 7.2 定时器定时中断代码
    • 7.3 定时器外部时钟申请
    • 7.4 定时器输出比较代码
      • 7.4.1 PWM驱动LED呼吸灯
      • 7.4.2 PWM驱动舵机
      • 7.4.3 PWM驱动直流电机
    • 7.5 定时器输入捕获代码
      • 7.5.1 输入捕获模式测频率
      • 7.5.2 PWMI模式测频率和占空比
    • 7.6 编码器接口测速

一、定时器概述

  • TIM(Timer)定时器
  • 定时器可以对输入的时钟进行计数,并在计数值达到设定值时触发中断
  • 16计数器预分频器自动重装寄存器时基单元,在72MHz计数时钟下可以实现最大59.65s的定时

  频率1MHz对应周期1/1MHz = 1us, 1KHz对应1ms, 1Hz对应1s
  在STM32中一个基准时钟(时钟周期)是1s/72MHz,计72个数字就是过了1s/72MHz×72 = 1us,如果计72000个数,那就是1ms

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

二、定时器类型

在这里插入图片描述

  高级到低级向下兼容

  • STM32F103C8T6定时器资源:TIM1、TIM2、TIM3、TIM4

2.1 基本定时器(TIM6和TIM7)

  基本定时器TIM6和TIM7各包含一个16位自动装载计数器,由各自的可编程预分频器驱动
  它们可以作为通用定时器提供时间基准,特别地可以为数模转换器(DAC)提供时钟。实际上,它们在芯片内部直接连接到DAC并通过触发输出直接驱动DAC

  主要功能:

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

在这里插入图片描述

  RCC的TIMxCLK内部时钟输入,频率值是系统的主频72MHz,所以通向时基单元基准频率就是72MHz
  PSC预分频器CNT计数器自动重装载寄存器共同组成时基单元PSC72MHz的基准时钟进行预分频(寄存器写0就是1分频,写1就是2分频输出频率=输入频率/2=72MHz/2)。CNT(16位)对预分频后的时钟进行计数(每来一个上升沿,计数时钟加1)。自动重装载寄存器就是存的写入的计数目标,运行过程中,计数值不断自增,自动重装值固定的目标,当计数值等于自动重装值,即计数达到,产生中断信号,并清零计数器,开始下一次的计数
  其中UI更新中断,通往NVICU代表更新事件不会触发中断,触发其他事件的工作

  主从模式触发DAC:更新事件U映射TRGO,TRGO就可以触发DAC不需要利用中断去执行

  C51系列单片机是设定计数初值,STM32系列是设定计数终值

2.2 通用定时器(TIM2、TIM3、TIM4和TIM5)

  通用定时器是一个通过可编程分频器驱动16位自动装载计数器构成
  它适合于多种场合,包括测量输入信号的脉冲长度(输入捕获)或者产生输出波形(输出比较PWM)
  使用定时器预分频器RCC时钟控制器预分频器脉冲长度波形周期可以在几个微秒和几个毫秒间调整

  主要功能:

  1. 16位向上(计数器从0开始,向上自增,计到重装值,清零同时申请中断)、向下(计数器从重装值开始,向下自减,减到0以后,回到重装值同时申请中断)、向上/向下(计数器从0开始,先向上自增,计到重装值,申请中断,然后再向下自减,减到0,申请中断)自动装载计数器
  2. 16位可编程(可以实现修改)预分频器,计数器时钟频率的分频系数为1~65535之间的任意数值
  3. 4个独立通道输入捕获输出比较PWM生成单脉冲模式输出
  4. 使用外部信号控制定时器和定时器互连的同步电路
  5. 如下事件发生时产生中断/DMA
  • 更新:计数器向上溢出/向下溢出,计数器初始化(软件或者内部/外部触发)
  • 触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)
  • 输入捕获
  • 输出比较
  1. 支持针对定位的增量(正交)编码器和霍尔传感器电路
  2. 触发输入作为外部时钟或者按周期的电流管理

在这里插入图片描述

  时基单元的基本结构:预分频器计数器自动重装载寄存器同基本定时器,工作流程也是一样,但对于通用计数器而言,计数器的模式就不止向上计数这一种了

  下面介绍通用定时器与基本定时器不同的地方

  如下图所示是内外时钟源选择主从触发模式的结构
  基本定时器只能选择内部时钟,也就是系统频率72MHz通用定时器里,时钟源不仅可以选择72MHz时钟,也可以选择外部时钟,相当于定时器指定的外部引脚上输入一个方波信号,来提供定时器的时钟

  选择内部还是外部来提供时钟的本质区别就是计数频率是72MHz,还是外部方波信号

  TIMx_ETR引脚上的外部时钟,通过ETRP、输入滤波、ETRF进入时基单元,这一路叫做外部时钟模式2TRGI(触发输入)也可以提供时钟,由ETR、ITR(其他定时器)、CH1引脚边沿、CH1、CH2提供,可以触发定时器的从模式,叫做外部时钟模式1

在这里插入图片描述

  如下图是输出比较电路
  总共有4个通道,分别对应CH1-CH4的引脚,用于输出PWM的波形

Alt

  下面是输入捕获电路
  也是4个通道,对应的是CH1-CH4的引脚,用于测量输入方波的频率

在这里插入图片描述

  下面是捕获/比较寄存器,是输入捕获和输出比较电路公用的,输入捕获和输出比较不可以同时使用

Alt

2.3 高级定时器(TIM1和TIM8)

  高级定时器由一个16位的自动装载计数器组成,由一个可编程的预分频器驱动
  它适合多种用途,包含测量输入信号的脉冲宽度(输入捕获),或者产生输出波形(输出比较PWM、嵌入死区时间的互补PWM等)
  使用定时器预分频器和RCC时钟控制预分频器,可以实现脉冲宽度和波形周期从几个微秒到几个毫秒的调节

  主要功能:

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

  下面给出高级定时器的框图

在这里插入图片描述

  从上图中可以看出高级定时器通用定时器不同的部分仅两块内容
  第一块是申请中断的部分,加了一个重复次数计数器,可以实现每隔几个计数周期产生一次更新事件和更新中断,通用定时器是一个计数周期一个中断,高级定时器相当于对输出的更新信号又进行了一次分频
  第二块是对输出比较器的升级DTG死区生成电路,右边前三路的输出引脚由原来的一个变为了两个互补的输出,可以输出一组互补的PWM波,为了驱动三相无刷电机。下面的是刹车输入功能,为了给电机驱动提供安全保障的,如果外部引脚BKIN产生了刹车信号或者内部时钟失效,那么控制电路会自动切断电机的输出,防止意外发生

三、定时中断基本结构和时基单元工作时序

3.1 定时器基本结构

  下面给出定时中断基本结构,即定时器的工作配置流程

在这里插入图片描述

  其中最重要的就是由PSC预分频器CNT计数器ARR自动重装器组成的时基单元运行控制控制寄存器的一些位,比如启动停止向上或者向下计数等等,就可以控制时基单元的运行了。
  左半部分是为时基单元提供时钟的部分,可以选择RCC提供内部时钟,或者ETR引脚提供的外部时钟模式2,也可以选择触发输入(ETR、ITRx其他定时器、TIx捕获通道)当作外部时钟,即外部时钟模式1,或者是由编码器提供时钟。
  最右边是计时时间到达时,产生更新中断后的信号去向,其中如果是高级定时器,还会再输出信号部分多个重复计数器,中断信号会先在状态寄存器里置一个中断标志位,经过中断输出控制(判断哪个中断允许),进入NVIC,最后进入中断函数

3.2 预分频器时序

  当预分频器的参数从0变到1(1分频(不分频)和2分频)时,计数器的时序图如下所示
  CK_PSC内部时钟CNT_EN计数器使能,高电平计数器正常运行,低电平关闭;CK_CNT定时器时钟,它既是预分频器的时钟输出,也是计数器的时钟输入。前半段,预分频器系数为0(1分频),计数器的时钟等于预分频器的时钟;后半段,预分频器系数为1(2分频),计数器的时钟变为预分频器时钟的一半;在计数器时钟的驱动下,下面的计数器寄存器也跟随时钟的上升沿不断自增(到达FC后变0,ARR自动重装值就是FC)
  下面三行描述的是预分频器的一种缓冲机制,当计数计到一半的时候改变了分频值,这个变化不会立刻生效,而是会等到本次计数周期结束产生更新事件,预分频器寄存器的值才会被传递到预分频缓冲器中,才会生效

  计数频率CK_CNT = CK_PSC/(PSC+1)

在这里插入图片描述

3.3 计数器时序

  计数器时序图,内部时钟分频因子为2(2分频),即分频系数为1
  CK_INT内部时钟72MHz,CNT_EN时钟使能,CK_CNT为定时器时钟,周期为内部时钟的一半(2分频),计数器上升沿自增,自增到0036溢出;产生更新事件脉冲(UEV),置一个更新中断标志位(UIF置1申请中断,中断响应后,手动清零标志位)

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

  其中CK_PSC是内部时钟72MHz或者外部方波信号、PSC是给定分频器的分频、ARR为自动重装值(计数目标值)
  计数器溢出时间取溢出频率的倒数即可
  其中也是带缓冲机制的,影子寄存器可以自己设置,时序如下所示

在这里插入图片描述

3.3.1 计数器有无预装时序(有无缓冲寄存器)

  当ARPE = 0时的更新事件(TIMx_ARR没有预装入)
在这里插入图片描述  计数目标突然从FF变成了36,所以直接计到36就产生更新中断,开始下一轮计数

  当ARPE = 1时的更新事件(预装入了TIMx_ARR)

在这里插入图片描述

  计数目标从F5变成了36,但是并没有立即计到36停止,而是先计到F5,产生更新中断以后,下一次计数周期的计数目标是36
  影子寄存器才是真正起作用的,影子寄存器的功能就是让值的变化和更新事件同步发生(比如计数已经到31,你突然改成重装值30),防止在运行途中更改造成错误

四、TIM输出比较OC

  • 输出比较可以通过比较CNT(计数器)和CCR(捕获/比较寄存器)值的关系,来对输出电平进行置1置0电平翻转的操作,用于输出一定频率和占空比的PWM波形

  如下图所示就是为CNT计数器CCR,这是输入捕获和输出比较公用的寄存器。当使用输入捕获时,它就是捕获寄存器;当使用输出比较时,它就是比较寄存器
  本节介绍输出比较,所以这块电路会比较CNT和CCR的值,CNT计数自增,CCR是我们给定的值

Alt

  • 每个高级定时器和通用定时器都拥有4个输出比较通道(CH1-CH4),并且公用一个CNT计数器
  • 高级定时器前3个通道(CH1-CH3)额外拥有死区生成互补输出的功能,用于驱动三相无刷电机

4.1 PWM波形

  具体PWM介绍见跳转连接
  下面给出高级定时器的前3个输出比较部分
  最左边是CNT和CCR比较的结果,OC1和OC1N就是两个互补端口,分别控制上管和下管的导通和关闭
在这里插入图片描述

  下面给出通用定时器的输出比较部分(高级定时器的第4个输出)
  左边是通过输出模式控制器(TIMx_CCMR1寄存器控制)得到的CNT和CCR的比较结果,输出OC1REF高低电平,随之REF可以映射到主模式的TRGO输出,也可以通过极性选择(TIMx_CCER)到达输出使能电路,最后输出OC1(CH1)引脚

在这里插入图片描述

  下面给出输出模式控制器的执行逻辑—8种
  有效电平高电平无效电平低电平
  PWM模式1和PWM模式2是相反逻辑

在这里插入图片描述

  下面给出PWM的基本执行结构
  左边是定时器配置的部分,只需要比较CNT和CCR的值即可,不需要配置中断。下图中ARR=99,CCR=30

在这里插入图片描述

  PWM的参数计算如下所示

  PWM频率(计数器更新频率) = CK_PSC/(PSC+1)/(ARR+1)
  PWM占空比 = CCR/(ARR+1)
  PWM分辨率 = 1/(ARR+1)

4.2 舵机

4.3 直流电机

  • 电机正接电机正转,电机反接电机反转
  • 直流电机属于大功率器件,GPIO口无法直接驱动,需要配合电机驱动电路来操作(51单片机用达林顿管驱动电机)
  • 电机驱动模块有TB6612、L298N等
  • 下面介绍TB6612,TB6612是一款双路H桥型的直流电机驱动芯片,可以驱动两个直流电机并控制其转速方向(不需要反接也可以反转)

  下面就是H桥电路基本结构,由两路推挽电路组成
  左上和右下导通,电流从左到右;右上和左下导通,电流从右到左,可以实现电机正反转

Alt  下面给出TB6612的硬件电路,具体引脚定义见下图

Alt
Alt
  注意电机的驱动电源逻辑电源,驱动电源需要接大电源。STBY接VCC则电机工作,接GND电机不工作。PWMA、AIN2、AIN1、AO1和AO2控制1路电机PWMB、BIN2、BIN1、BO1和BO2控制2路电机

在这里插入图片描述
  如上图所示STBY低电平时,电机不转。IN1和IN2接相同电平,电机不转。O1和O2接相同电平,电机不转
  IN1低电平IN2高电平,PWM给高电平(占空比不为0)电机转。IN1高电平IN2低电平,PWM给高电平(占空比不为0)电机

五、TIM输入捕获IC

  • 输入捕获模式下,当通道输入引脚出现指定电平跳变时,当前CNT的值将被锁存CCR中(当前CNT的值读出来写入到CCR中),用于测量PWM波形的频率、占空比、脉冲间隔、电平持续时间等参数
  • 每个高级定时器和通用定时器都拥有4个输入捕获通道
  • 可配置为PWMI模式(PWM输入模式),同时测量频率和占空比
  • 可配合主从模式触发,实现硬件全自动测量

  如下图所示为通道输入部分

  输入引脚部分,有个三输入的异或门,输入接在了通道CH123端口,当三个输入引脚的任何一个有电平翻转时,输出引脚就产生一次电平翻转,输出通过数据选择器,到达输入捕获通道1。如果选择上面一个就是三个输入的异或值。如果选择下面一个,异或门无效,各用各的引脚。

  一旦有边沿(自己配置)出现,输入滤波边沿检测器就会检测到边沿,其中输出有两个通道选择TI1FP1TI2FP2,分别可以输入给两个通道。可以灵活切换后续捕获电路的输入,也可以把一个引脚的输,同时映射到两个捕获单元。

  来到预分频器,对前面的信号进行分预分频,分频之后的触发信号,就可以触发捕获电路进行工作了,每来一个信号,CNT的值就锁存到CCR中,CNT的数值就可以记录两个上升沿的时间间隔

Alt

  下面给出输入捕获/比较通道(细化)
  TI1(CH1)的引脚作为输入,fDTS是滤波器的采样时钟来源,CCMR寄存器里的ICF控制滤波器参数,输出的TI1F就是滤波后的信号,经过边沿检测,CC1P位选择极性,最终得到TI1FP1触发信号,通过数据选择器进入通道1后续的捕获电路,CC1S配置数据选择器,ICPS配置分频器,CC1E位控制使能,最终CNT的值就到了CCR里,并且每捕获一次CNT的值,就需要清零CNT,以便于下一次捕获
在这里插入图片描述

  TIFP1和TI1的边沿信号都可以通向从模式控制器,触发从模式,从模式里面可以自动完成CNT的清零

  下图是主从触发模式的框图
  它可以完成硬件的自动化操作。主模式可以将定时器内部的信号映射到TRGO引脚,用于触发别的外设。从模式就是接收其他外设或者自身外设的一些信号,用于控制自身定时器的运行,也就是被别的信号控制。触发源选择就是选择从模式的触发信号源,选择指定的一个信号,得到TRGI,TRGI去触发从模式,从模式在列表里选择一项操作去自动执行 (例:让TI1FP1信号自动触发CNT清零,触发源选择就可以选中TI1FP1,从模式执行Reset的操作,自动清零CNT,实现硬件全自动测量)

在这里插入图片描述

5.1 频率测量

  首先给出一个频率逐渐降低的方波信号,如下图所示

在这里插入图片描述

  • 测频法:在闸门时间T内,对上升沿进行计次,得到N,则频率为N/T。适合高频信号

  闸门时间通常设置为1s,在1s时间内,对信号的上升沿计次,从0开始,每来一个上升沿(1个周期信号),计次+1。在1s时间内来了多少个周期,频率就是多少Hz。

  • 测周法:在两个上升沿内,以标准频率fc计次,得到N,则频率为fc/N。适合低频信号

  捕获两个上升沿,之间持续的时间就是一个周期。用一个已知的标准频率fc来计次,驱动计数器,从一个上升沿开始计,计数器从0开始,一直计到下一个上升沿,停止。计一个数的时间是1/fc,计N个数,时间就是N/fc,所以频率就是fc/N

  • 中界频率(频率多高算高,多低算低,选用哪种方法):测频法和测周法误差相等的频率点为根号fc/T

  当待测信号频率小于中界频率时,测周法误差小;反之,测频法误差小

5.2 输入捕获/PWMI基本结构

5.2.1 输入捕获基本结构

  下图是输入捕获基本结构,只使用了一个通道,只能测量频率
  时基单元配置好,启动定时器,CNT在预分频之后的时钟驱动下,不断自增,CNT就是测周法用来计时的,标准频率=72MHz/预分频系数。输入捕获通道1的GPIO口,输入方波信号,经过滤波器边缘检测,选择TI1FP1为上升沿触发,当出现上升沿后,CNT的当前计数值进入CCR1里,同时触发源选择,选中TI1FP1为触发信号,从模式选中复位操作,CNT自动清零(CNT的值先进CCR1里,再清零),输入直连,分频器不分频

在这里插入图片描述

5.2.2 PWMI基本结构

  下图是PWMI基本结构,使用两个通道同时捕获一个引脚,可以同时测量频率和占空比
  TI1FP1同上面一样,正常测量频率,TI1FP2设置为下降沿触发,通过交叉通道,触发通道2的捕获单元。CCR2不触发CNT清零,CCR1触发CNT清零,所以CCR2就是高电平计数值,CCR1是整个周期计数值,所以CCR2/CCR1就是占空比

在这里插入图片描述

六、编码器

  • 编码器接口可以接收增量(正交)编码器的信号,根据编码器旋转产生的正交信号脉冲,自动控制CNT自增或者自减,从而指示编码器的位置、旋转方向和旋转速度
  • 每个高级定时器和通用定时器都拥有一个编码器接口
  • 两个输入引脚借用了输入捕获的通道1通道2

6.1 正交编码器方向

  下面给出正交编码器的正反转判断图,编码器接口涉及逻辑就是首先把A相和B相的所有边沿作为计数器的计数时钟,出现边沿信号时,计数自增或自减,是由另一相的状态来决定。就是当出现某一个边沿时,我们判断另一相的高低电平

  • 正转

在这里插入图片描述
在这里插入图片描述

  • 反转

在这里插入图片描述
在这里插入图片描述
  下面给出计数方向和编码器信号的关系(均不反相)
  从表中看出,当TI1计数时,B相(TI2FP2)的边沿不计数,只在A相(TI1FP1)的边沿计数,正转向上计数,反转向下计数

在这里插入图片描述

  下面给出编码器TI1和TI2上计数的操作实例。毛刺部分是当一个信号电平不变的情况下,另一个信号会使得计数器加减加减,保持计数值不变,过滤噪声

在这里插入图片描述

6.2 编码器电路和基本结构

  如下图所示是编码器接口电路图,编码器接口的两个输入端TI1FP1和TI2FP2,分别接到编码器的A相和B相,编码器的输出部分相当于从控制器,去控制CNT的计数时钟计数方向,由上述表控制CNT自增还是自减

在这里插入图片描述

  其中TI1FP1和TI2FP2对应在CH1和CH2通道,输入滤波和边沿检测也需要使用,后面的交叉、预分频、CCR寄存器与编码器无关

在这里插入图片描述

  下面给出编码器的基本结构图
  输入捕获的CH1和CH2通过GPIO接到编码器的AB相,然后通过滤波器极性选择产生TI1FP1和TI2FP2,通向编码器接口,编码器接口通过预分频器控制CNT计数器的时钟,同时还根据编码器的旋转方向,控制CNT的计数方向,编码器正转时,CNT自增,编码器反转时,CNT自减,ARR设置成最大量程

在这里插入图片描述

七、代码编写

7.1 定时器库函数介绍

  介绍一下定时器的部分库函数,打开stm32f10x_tim.h

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引脚的预分频器、极性、滤波器参数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);//清除挂起中断标志位
//输出比较
void TIM_OC1Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);//初始化输出比较单元
void TIM_OC2Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC3Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC4Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_CtrlPWMOutputs(TIM_TypeDef* TIMx, FunctionalState NewState);//高级定时器输出PWM时,使能主输出
void TIM_OCStructInit(TIM_OCInitTypeDef* TIM_OCInitStruct);//给输出比较结构体默认值
void TIM_OC1PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);//单独设置输出比较的极性
void TIM_OC1NPolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCNPolarity);//高级定时器互补通道配置
void TIM_OC2PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);
void TIM_OC2NPolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCNPolarity);
void TIM_OC3PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);
void TIM_OC3NPolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCNPolarity);
void TIM_OC4PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);
void TIM_CCxCmd(TIM_TypeDef* TIMx, uint16_t TIM_Channel, uint16_t TIM_CCx);//单独修改输出使能
void TIM_CCxNCmd(TIM_TypeDef* TIMx, uint16_t TIM_Channel, uint16_t TIM_CCxN);
void TIM_SelectOCxM(TIM_TypeDef* TIMx, uint16_t TIM_Channel, uint16_t TIM_OCMode);//单独更改输出比较模式
//输入捕获
void TIM_ICInit(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct);//输入捕获初始化,四个通道共用一个函数
void TIM_PWMIConfig(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct);//输入捕获初始化,快速配置两个通道
void TIM_ICStructInit(TIM_ICInitTypeDef* TIM_ICInitStruct);//输入捕获结构体赋初值
void TIM_SelectInputTrigger(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource);//选择输入触发源TRGI-从模式触发源
void TIM_SelectOutputTrigger(TIM_TypeDef* TIMx, uint16_t TIM_TRGOSource);//选择输出触发源TRGO-主模式
void TIM_SelectSlaveMode(TIM_TypeDef* TIMx, uint16_t TIM_SlaveMode);//选择从模式
void TIM_SetIC1Prescaler(TIM_TypeDef* TIMx, uint16_t TIM_ICPSC);//单独配置通道1的分频器
void TIM_SetIC2Prescaler(TIM_TypeDef* TIMx, uint16_t TIM_ICPSC);
void TIM_SetIC3Prescaler(TIM_TypeDef* TIMx, uint16_t TIM_ICPSC);
void TIM_SetIC4Prescaler(TIM_TypeDef* TIMx, uint16_t TIM_ICPSC);uint16_t TIM_GetCapture1(TIM_TypeDef* TIMx);//读CCR的值
uint16_t TIM_GetCapture2(TIM_TypeDef* TIMx);
uint16_t TIM_GetCapture3(TIM_TypeDef* TIMx);
uint16_t TIM_GetCapture4(TIM_TypeDef* TIMx);void TIM_SetCompare1(TIM_TypeDef* TIMx, uint16_t Compare1);//单独写CCR寄存器值
void TIM_SetCompare2(TIM_TypeDef* TIMx, uint16_t Compare2);
void TIM_SetCompare3(TIM_TypeDef* TIMx, uint16_t Compare3);
void TIM_SetCompare4(TIM_TypeDef* TIMx, uint16_t Compare4);void TIM_EncoderInterfaceConfig(TIM_TypeDef* TIMx, uint16_t TIM_EncoderMode,int16_t TIM_IC1Polarity, uint16_t TIM_IC2Polarity);//定时器编码器接口配置

7.2 定时器定时中断代码

  使用定时器中断,首先需要初始化定时器
  步骤如下:RCC开启时钟(定时器基准时钟和整个外设时钟都会打开) — 选择时基单元的时钟源(内部时钟和外部时钟) — 配置时基单元(PSC、CNT、ARR) — 配置输出中断控制(使能中断输出,允许更新中断输出到NVIC) — 配置NVIC(打开定时器中断的通道,并分配优先级) — 运行控制(使能计数器) — 定时器中断函数
  下面给出Timer.c,每隔1s进入中断

#include "stm32f10x.h"                  // Device header/*
函数功能:TIM2定时器中断(T = 1s)
*/
void Timer_Init()
{//开启RCC时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);//选择时基单元时钟-内部时钟,一般默认就是内部时钟TIM_InternalClockConfig(TIM2);//配置时基单元TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;		//1分频TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;		//向上计数//时基单元关键寄存器参数:对72MHz进行7200分频,得到10k的计数频率,计10000个数字,就是1s -> 1Hz = 1sTIM_TimeBaseInitStructure.TIM_Period = 10000-1;	//ARR自动重装器的值TIM_TimeBaseInitStructure.TIM_Prescaler = 7200-1;		//PSC预分频器的值 72MHz/(PSC+1)/(ARR+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_PriorityGroupConfig(NVIC_PriorityGroup_2);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);		//使能计数器
}/*
函数功能:定时器TIM2中断函数
*/
//void TIM2_IRQHandler()
//{
//	if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET)
//	{
//		
//		TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
//	}
//}

7.3 定时器外部时钟申请

  使用定时器中断,首先需要初始化定时器
  步骤如下:RCC开启时钟(定时器基准时钟和整个外设时钟都会打开) — 选择时基单元的时钟源(内部时钟和外部时钟) — 配置时基单元(PSC、CNT、ARR) — 配置输出中断控制(使能中断输出,允许更新中断输出到NVIC) — 配置NVIC(打开定时器中断的通道,并分配优先级) — 运行控制(使能计数器) — 定时器中断函数
  下面给出Timer.c,对射红外传感器挡一次计数器加1,计到第10次,进入中断

#include "stm32f10x.h"                  // Device header/*
函数功能:外部时钟(对射红外传感器)定时器中断
*/
void Timer_Init()
{//开启RCC时钟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配置TIM_ETRClockMode2Config(TIM2,TIM_ExtTRGPSC_OFF,TIM_ExtTRGPolarity_NonInverted,0x0F);		//滤波器0x00-0x0F给最大,比较准确//配置时基单元TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;		//1分频TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;		//向上计数//时基单元关键寄存器参数TIM_TimeBaseInitStructure.TIM_Period = 10-1;	//ARR自动重装器的值 计0-9 10个数字TIM_TimeBaseInitStructure.TIM_Prescaler = 1-1;		//外部时钟提供,不是内部的72MHz,如果分频更大,则需要触发多次,计数器才会加1TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;		//重复计数器的值TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);//使能更新中断TIM_ClearFlag(TIM2,TIM_FLAG_Update);		//避免刚初始化完就进入中断TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);//NVIC配置NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);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);
}uint16_t Timer_GetCounter()
{return TIM_GetCounter(TIM2);
}//void TIM2_IRQHandler()
//{
//	if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET)
//	{
//		
//		TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
//	}
//}

  对于外部时钟源来控制定时器需要了解,本质上是将外部引脚给的方波信号作为时钟来完成功能,以对射红外为例,预分频数值越大,方波信号被分频,导致频率小周期变大,所以挡一次计数器不会加1,需要多挡几次,达到周期时间,计数器才会加1

  外部中断使用外部时钟源的定时器中断也有不同。前者是直接在GPIO口获取到中断电平要求,执行操作;后者是达到定时器重装值,计数器溢出触发中断,执行操作

7.4 定时器输出比较代码

  定时器输出PWM基本步骤如下:RCC开启时钟(TIM和GPIO) — 时钟源选择加配置时基单元配置输出比较单元(CCR的值、输出比较模式、极性选择、输出使能参数 — 配置GPIO(复用推挽输出) — 运行控制(启动计数器)
  同一个定时器可以使用多个通道输出多个PWM,多个通道公用一个计数器,所以它们的频率是相同的,占空比由各自的CCR决定,计数器更新,所有PWM同时跳变,相位同步

7.4.1 PWM驱动LED呼吸灯

  LED正极接在PA0(CH1)口,负极接GND,占空比越大,LED越亮
  选取GPIO模式的时候,需要选取复用推挽输出才可以将引脚的控制权交给片上外设,PWM才可以通过引脚输出
  下面给出PWM.c

#include "stm32f10x.h"                  // Device headervoid PWM_Init()
{//开启RCC时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//GPIOA初始化(CH1)GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;		//复用推挽输出模式(在此模式下引脚的控制权才可以交给片上外设,PWM才可以通过引脚输出GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStructure);//选择时基单元时钟-内部时钟,一般默认就是内部时钟TIM_InternalClockConfig(TIM2);//配置时基单元TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;		//1分频TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;		//向上计数//时基单元关键寄存器参数---频率1000HzTIM_TimeBaseInitStructure.TIM_Period = 100-1;		//ARRTIM_TimeBaseInitStructure.TIM_Prescaler = 720-1;		//PSC TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;		//重复计数器的值,高级定时器专用TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);//配置输出比较单元(PA0是OC1输出口,使用OC1初始化函数)TIM_OCInitTypeDef TIM_OCInitStructure;TIM_OCStructInit(&TIM_OCInitStructure);		//给结构体赋初始值TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;		//设置输出比较模式	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;		//设置输出比较极性TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;		//设置输出使能TIM_OCInitStructure.TIM_Pulse = 0;		//CCR ,占空比为0%TIM_OC1Init(TIM2,&TIM_OCInitStructure);//启动定时器TIM_Cmd(TIM2,ENABLE);		//使能计数器,PWM波形通过PA0输出
}/*
函数功能:单独设置CCR的值,调整占空比
*/
void PWM_SetCompare(uint16_t Compare)
{TIM_SetCompare1(TIM2, Compare);
}

  主函数中调用可调CCR函数,调整占空比

while(1){for(i = 0;i<=100;i++){PWM_SetCompare(i);		//逐渐变亮Delay_ms(10);}for(i = 0;i<=100;i++){PWM_SetCompare(100-i);		//逐渐变暗Delay_ms(10);}}

7.4.2 PWM驱动舵机

  舵机信号输出线接在PA1(CH2)口
  下面给出PWM.h,我们需要设置周期为20ms,高电平时间对应从0.5ms-2.5ms。ARR和PSC决定周期,CCR和ARR决定占空比

#include "stm32f10x.h"                  // Device headervoid PWM_Init()
{//开启RCC时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//GPIOA初始化(CH1)GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;		//复用推挽输出模式(在此模式下引脚的控制权才可以交给片上外设,PWM才可以通过引脚输出GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStructure);//选择时基单元时钟-内部时钟,一般默认就是内部时钟TIM_InternalClockConfig(TIM2);//配置时基单元TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;		//1分频TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;		//向上计数//时基单元关键寄存器参数---频率50Hz   72000000/72/20000 = 50HZ  周期为20msTIM_TimeBaseInitStructure.TIM_Period = 20000-1;		//ARRTIM_TimeBaseInitStructure.TIM_Prescaler = 72-1;		//PSC TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;		//重复计数器的值,高级定时器专用TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);//配置输出比较单元(PA0是OC1输出口,使用OC1初始化函数)TIM_OCInitTypeDef TIM_OCInitStructure;TIM_OCStructInit(&TIM_OCInitStructure);		//给结构体赋初始值TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;		//设置输出比较模式	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;		//设置输出比较极性TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;		//设置输出使能TIM_OCInitStructure.TIM_Pulse = 0;		//CCR ,占空比为0%TIM_OC2Init(TIM2,&TIM_OCInitStructure);//启动定时器TIM_Cmd(TIM2,ENABLE);		//使能计数器,PWM波形通过PA0输出}/*
函数功能:单独设置CCR的值,调整占空比 20000对应20ms,Compare = 500--0.5ms  2500--2.5ms
*/
void PWM_SetCompare(uint16_t Compare)
{TIM_SetCompare2(TIM2, Compare);
}

  将舵机封装成函数,并在主函数调用,实现按键每按一下,舵机转动45°,并在OLED显示屏上显示出度数

#include "stm32f10x.h"                  // Device header
#include "PWM.h"void Servo_Init()
{PWM_Init();
}/*
0°x   -- 500y
180° -- 2500  
y = kx + bb = 500  
180k+500 = 2500
y = x/180*2000 + 500函数功能:输入角度,舵机旋转固定角度
形式参数:转动的角度
*/
void Servo_SetAngle(float Angle)
{PWM_SetCompare(Angle/180*2000 + 500);
}
uint8_t KeyNum;
float Angle;int main(void)
{OLED_Init();Servo_Init();Key_Init();		//这里不初始化,舵机会乱转Servo_SetAngle(0);OLED_ShowString(1, 1, "Angle:");while(1){KeyNum = Key_GetNum();if(KeyNum == 1){Angle += 45;if(Angle >180)Angle = 0;Servo_SetAngle(Angle);}OLED_ShowNum(1, 7, Angle,3);}
}

7.4.3 PWM驱动直流电机

  驱动直流电机需要通过电机驱动电路,这里使用的TB6612模块
  接线如下,注意的是PWM输出口接的是PA2口,对应的是TIM2的通道3,需要更改PWM.h里的输出通道

在这里插入图片描述  下面给出Motor.c

#include "stm32f10x.h"                  // Device header
#include "PWM.h"void Motor_Init()
{PWM_Init();//电机控制方向的GPIO口 PA4 PA5RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);GPIO_InitTypeDef GPIO_Initstructure;GPIO_Initstructure.GPIO_Mode = GPIO_Mode_Out_PP;		//推挽输出GPIO_Initstructure.GPIO_Pin = GPIO_Pin_4|GPIO_Pin_5;GPIO_Initstructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_Initstructure);
}/*
函数功能:设置电机转动方向和速度
*/
void Motor_SetSpeed(int8_t Speed)
{if(Speed >= 0)		//正转{GPIO_SetBits(GPIOA,GPIO_Pin_4);		//IN1-PA4高GPIO_ResetBits(GPIOA,GPIO_Pin_5);		//IN2-PA5低PWM_SetCompare(Speed);}else		//反转{GPIO_ResetBits(GPIOA,GPIO_Pin_4);		//IN1-PA4低GPIO_SetBits(GPIOA,GPIO_Pin_5);		//IN2-PA5高PWM_SetCompare(-Speed);}
}

  主函数调用,使用按键去控制电机的转速,将速度显示在OLED屏幕上

int main(void)
{OLED_Init();Key_Init();		//必须初始化,不初始化的情况下,会乱转Motor_Init();OLED_ShowString(1, 1, "Speed:");while(1){KeyNum = Key_GetNum();if(KeyNum == 1){Speed += 20;if(Speed >100)Speed = 0;Motor_SetSpeed(Speed);}OLED_ShowNum(1, 7, Speed,3);}
}

7.5 定时器输入捕获代码

  信号输入是由自己生成,所以直接连接单片机的GPIO口,将PA6和PA0接起来,PA0输出方波信号PA6检测输入捕获

7.5.1 输入捕获模式测频率

  首先使用定时器配置好PA0(TIM2_CH1)口输出一个方波信号,这里是产生一个1000Hz,占空比为50%的方波信号
  然后再配置PA6(TIM3_CH1)口,实现输入捕获,具体步骤如下:配置RCC时钟(GPIO和TIM) — GPIO初始化(输入模式) — 配置时基单元配置输入捕获单元(滤波器,极性、直连还是交叉、分频器等) — 从模式触发源,并执行Reset操作 — 开启定时器
  当要读取最新一个周期频率时,读取CCR,按照fc/N得到
  首先给出PWM.c,此函数内主要是通过PA0口输出一个模拟的方波信号,就是利用定时器输出一个波形

#include "stm32f10x.h"                  // Device headervoid PWM_Init()
{//开启RCC时钟PA0口输出方波信号RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//GPIOA初始化(CH1)GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;		//复用推挽输出模式(在此模式下引脚的控制权才可以交给片上外设,PWM才可以通过引脚输出GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStructure);//选择时基单元时钟-内部时钟,一般默认就是内部时钟TIM_InternalClockConfig(TIM2);//配置时基单元TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;		//1分频TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;		//向上计数//时基单元关键寄存器参数---调节PSC来调节频率TIM_TimeBaseInitStructure.TIM_Period = 100-1;		//ARRTIM_TimeBaseInitStructure.TIM_Prescaler = 720-1;		//PSC TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;		//重复计数器的值,高级定时器专用TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);//配置输出比较单元(PA0是OC1输出口,使用OC1初始化函数)TIM_OCInitTypeDef TIM_OCInitStructure;TIM_OCStructInit(&TIM_OCInitStructure);		//给结构体赋初始值TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;		//设置输出比较模式	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;		//设置输出比较极性TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;		//设置输出使能TIM_OCInitStructure.TIM_Pulse = 0;		//CCR,占空比为0%TIM_OC1Init(TIM2,&TIM_OCInitStructure);//启动定时器TIM_Cmd(TIM2,ENABLE);		//使能计数器,PWM波形通过PA0输出}/*
函数功能:单独设置CCR的值,调整占空比
*/
void PWM_SetCompare(uint16_t Compare)
{TIM_SetCompare1(TIM2, Compare);
}
/*
函数功能:单独设置PSC的值,调整频率
*/
void PWM_SetPrescaler(uint16_t Prescaler)
{TIM_PrescalerConfig(TIM2, Prescaler,TIM_PSCReloadMode_Immediate);
}

  然后给出IC.c,此函数内主要是通过PA6(TIM3_CH1)口输入捕获到PA0口的方波信号

#include "stm32f10x.h"                  // Device headervoid IC_Init()
{//开启RCC时钟P6口输入方波,TIM2输出PWM波,TIM3输入捕获RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//GPIOA初始化TIM3的CH1为PA6GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;		//输入模式:上拉输入GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStructure);//选择时基单元时钟-内部时钟,一般默认就是内部时钟TIM_InternalClockConfig(TIM3);//配置时基单元TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;		//1分频TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;		//向上计数//时基单元关键寄存器参数TIM_TimeBaseInitStructure.TIM_Period = 65536-1;		//防止计数溢出,给最大TIM_TimeBaseInitStructure.TIM_Prescaler = 72-1;		//标准频率1MHzTIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;		//重复计数器的值,高级定时器专用TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);//初始化输入捕获单元TIM_ICInitTypeDef TIM_ICInitStructure;TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;		//TIM3的CH1TIM_ICInitStructure.TIM_ICFilter = 0xF;		//滤波TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;		//上升沿触发TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;		//不分频TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;		//直连通道TIM_ICInit(TIM3,&TIM_ICInitStructure);//主从触发源选择TIM_SelectInputTrigger(TIM3,TIM_TS_TI1FP1);		//TIM3 ,TI1FP1TIM_SelectSlaveMode(TIM3,TIM_SlaveMode_Reset);		//从模式 Reset//启动定时器TIM_Cmd(TIM3,ENABLE);
}
/*
函数功能:读取最新一个周期的频率,f = fc/N = 72M/PSC+1/N(N=CCR)
*/
uint32_t IC_GetFreq()
{return 1000000 / (TIM_GetCapture1(TIM3)+1);		//面向结果编程+1
}

  在主函数中,首先设置好PA0口的输出波形,再捕获PA6的输入频率

//PA0输出频率1000Hz,占空比50%的方波信号PWM_SetPrescaler(720 - 1);		//f = 72M / PSC+1 / ARR+1 = 1000Hz PWM_SetCompare(50);		//D = CCR / ARR+1 = 50%while(1){OLED_ShowNum(1,6,IC_GetFreq(),5);}

7.5.2 PWMI模式测频率和占空比

  同时测频率和占空比时,与上述不同的部分是,要配置好两个通道,之后用CCR2/CCR1得到占空比大小,PWM.c没有变化
  下面给出IC.c文件

#include "stm32f10x.h"                  // Device headervoid IC_Init()
{//开启RCC时钟P6口输入方波,TIM2输出PWM波RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//GPIOA初始化TIM3的CH1为PA6GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;		//输入模式:上拉输入GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStructure);//选择时基单元时钟-内部时钟,一般默认就是内部时钟TIM_InternalClockConfig(TIM3);//配置时基单元TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;		//1分频TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;		//向上计数//时基单元关键寄存器参数TIM_TimeBaseInitStructure.TIM_Period = 65536-1;		//防止计数溢出,给最大TIM_TimeBaseInitStructure.TIM_Prescaler = 72-1;		//标准频率1MHzTIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;		//重复计数器的值,高级定时器专用TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);//初始化输入捕获单元TIM_ICInitTypeDef TIM_ICInitStructure;TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;		//TIM3的CH1TIM_ICInitStructure.TIM_ICFilter = 0xF;		//滤波TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;		//上升沿触发TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;		//不分频TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;		//直连通道TIM_PWMIConfig(TIM3,&TIM_ICInitStructure);		//直接配置第二通道(初始化一个通道即可,另一个通道会直接初始化成相反的配置),下降沿触发,交叉//主从触发源选择TIM_SelectInputTrigger(TIM3,TIM_TS_TI1FP1);		//TIM3 ,TI1FP1TIM_SelectSlaveMode(TIM3,TIM_SlaveMode_Reset);		//从模式 Reset//启动定时器TIM_Cmd(TIM3,ENABLE);
}
/*
函数功能:读取最新一个周期的频率,f = fc/N = 72M/PSC+1/N(N=CCR1)
*/
uint32_t IC_GetFreq()
{return 1000000 / (TIM_GetCapture1(TIM3)+1);		//面向结果编程+1
}
/*
函数功能:获取PWM,CCR2/CCR1
*/
uint32_t IC_GetDuty()
{return (TIM_GetCapture2(TIM3)+1)*100/(TIM_GetCapture1(TIM3)+1);
}

  同样在主函数中调用,如下所示

//PA0输出频率1000Hz,占空比50%的方波信号PWM_SetPrescaler(720 - 1);		//f = 72M / PSC+1 / ARR+1 = 1000Hz PWM_SetCompare(50);		//D = CCR / ARR+1 = 50%while(1){OLED_ShowNum(1,6,IC_GetFreq(),5);OLED_ShowNum(2,6,IC_GetDuty(),3);}

7.6 编码器接口测速

  本节使用旋转编码器,AB相选择接到PA6(TIM3_CH1)PA7(TIM3_CH2)输入引脚
  代码步骤如下:开启RCC时钟(GPIO和TIM时钟)配置GPIO(PA6和PA7输入模式)配置时基单元配置输入捕获单元(滤波器和极性)配置编码器接口模式启动定时器
  代码主要功能是测旋转编码器旋转的速度,利用TIM2去每隔1s读取速度,显示在OLED上

#include "stm32f10x.h"                  // Device headervoid Encoder_Init()
{//开启RCC时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//GPIOA初始化TIM3的CH1\CH2为PA6\PA7GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;		//输入模式:上拉输入GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStructure);//配置时基单元--编码器时钟驱动计数器TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;		//1分频TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;		//向上计数//时基单元关键寄存器参数TIM_TimeBaseInitStructure.TIM_Period = 65536-1;		//最大量程TIM_TimeBaseInitStructure.TIM_Prescaler = 1-1;		//不分频TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;		//重复计数器的值,高级定时器专用TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);//初始化输入捕获单元TIM_ICInitTypeDef TIM_ICInitStructure;TIM_ICStructInit(&TIM_ICInitStructure);		//结构体配置不完整,赋一个初始值TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;		//TIM3的CH1-PA6TIM_ICInitStructure.TIM_ICFilter = 0xF;		//滤波//TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;		//高低电平极性不反转(不反相)TIM_ICInit(TIM3,&TIM_ICInitStructure);TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;		//TIM3的CH2-PA7TIM_ICInitStructure.TIM_ICFilter = 0xF;		//滤波//TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;		//高低电平极性不反转(不反相)TIM_ICInit(TIM3,&TIM_ICInitStructure);//配置编码器接口TIM_EncoderInterfaceConfig(TIM3,TIM_EncoderMode_TI12,TIM_ICPolarity_Rising,TIM_ICPolarity_Rising);		//任意一个极性反转,正转就是增//打开定时器TIM_Cmd(TIM3,ENABLE);
}int16_t Encoder_Get()		//有符号
{int16_t Temp;Temp = TIM_GetCounter(TIM3);TIM_SetCounter(TIM3,0);		//CNT清零return Temp;
}

  在主函数利用中断,1s检测一次

int16_t Speed;int main(void)
{OLED_Init();Timer_Init();Encoder_Init();OLED_ShowString(1,1,"Speed:");while(1){OLED_ShowSignedNum(1,7,Speed,5);}
}/*
函数功能:定时器TIM2中断函数 1s
*/
void TIM2_IRQHandler()
{if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET){Speed = Encoder_Get();TIM_ClearITPendingBit(TIM2,TIM_IT_Update);}
}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

N的阶乘(高精度)

目录 题目描述 输入格式 输出格式 样例输入 样例输出 思路 参考代码 题目描述 输入正整数n&#xff0c;输出n&#xff01; 输入格式 一个正整数n&#xff0c;n 3000 输出格式 输出n&#xff01; 样例输入 3 样例输出 9 思路 主要就是高精度乘法的模版&#x…

“大数据建模、分析、挖掘技术应用研修班”的通知!

随着2015年9月国务院发布了《关于印发促进大数据发展行动纲要的通知》&#xff0c;各类型数据呈现出了指数级增长&#xff0c;数据成了每个组织的命脉。今天所产生的数据比过去几年所产生的数据大好几个数量级&#xff0c;企业有了能够轻松访问和分析数据以提高性能的新机会&am…

平方回文数-第13届蓝桥杯选拔赛Python真题精选

[导读]&#xff1a;超平老师的Scratch蓝桥杯真题解读系列在推出之后&#xff0c;受到了广大老师和家长的好评&#xff0c;非常感谢各位的认可和厚爱。作为回馈&#xff0c;超平老师计划推出《Python蓝桥杯真题解析100讲》&#xff0c;这是解读系列的第73讲。 平方回文数&#…

低价焕新用户体验生态 京东向上增长通道宽了

5月16日&#xff0c;京东对外发布了其2024年第一季度财报。整体来看&#xff0c;相当不错&#xff0c;营收与净利润双双超预期。一季度&#xff0c;京东集团收入达到2,600亿元人民币&#xff08;约360亿美元&#xff09;&#xff0c;同比增长7.0%&#xff0c;尤其是在持续补贴和…

实现UI显示在最上面的功能

同学们肯定遇到过UI被遮挡的情况&#xff0c;那如何让UI显示在最前面呢&#xff0c;先看效果 原理:UI的排序方式是和unityHierarchy窗口的层级顺序有关的&#xff0c;排序在下就越后显示&#xff0c;所以按照这个理论&#xff0c;当我们鼠标指到UI的时候把层级设置到最下层就好…

IOPS:存储芯片的“心跳”性能

IOPS&#xff0c;即每秒输入/输出操作数&#xff08;Input/Output Operations Per Second&#xff09;&#xff0c;是一个用于计算机存储设备&#xff08;如硬盘,SD Nand 、eMMC等&#xff09;性能测试的量测方式&#xff0c;是评估存储系统性能的一个关键指标。 常见IOPS量测方…

制作Dcoker镜像

文章目录 一、Docker构建镜像的原理1、镜像分层原理2、Docker的镜像结构3、分层存储原理4、构建命令与层的关系5、最终镜像的创建 二、docker commit 构建镜像1、使用场景2、手动制作yum版的nginx镜像2.1、启动一个centos容器&#xff0c;安装好常用的软件以及nginx2.2、关闭ng…

如何评价GPT-4o

一&#xff1a;简介 GPT-4o作为OpenAI的又一里程碑式技术成果&#xff0c;展现了显著的技术进步和创新。以下是对GPT-4o的评价&#xff0c;包括与先前版本的对比分析、技术能力以及个人感受。 1、版本间的对比分析 相较于先前的GPT系列模型&#xff0c;GPT-4o在多个方…

Redis解决缓存一致性问题

文章目录 ☃️概述☃️数据库和缓存不一致采用什么方案☃️代码实现☃️其他 ☃️概述 由于我们的 缓存的数据源来自于数据库, 而数据库的 数据是会发生变化的, 因此,如果当数据库中 数据发生变化,而缓存却没有同步, 此时就会有 一致性问题存在, 其后果是: 用户使用缓存中的过…

Python读取Excel表格文件并绘制多列数据的曲线图

本文介绍基于Python语言&#xff0c;读取Excel表格数据&#xff0c;并基于给定的行数范围内的指定列数据&#xff0c;绘制多条曲线图&#xff0c;并动态调整图片长度的方法。 首先&#xff0c;我们来明确一下本文的需求。现有一个.csv格式的Excel表格文件&#xff0c;其第一列为…

将本地项目上传到 gitee 仓库

1、创建 gitee 仓库 到 gitee 官网&#xff0c;新建仓库 配置新建仓库 完成仓库的创建 项目上传到仓库 上传项目需要安装git git官方下载地址&#xff1a;git下载地址 安装完成&#xff0c;前往本地项目所在文件夹&#xff0c;右击选择 Git Bash Here 刚下载完成需要配置G…

【全开源】Java养老护理助浴陪诊小程序医院陪护陪诊小程序APP源码

打造智慧养老服务新篇章 一、引言&#xff1a;养老护理的数字化转型 随着老龄化社会的到来&#xff0c;养老护理需求日益凸显。为了更好地满足老年人及其家庭的需求&#xff0c;我们推出了养老护理助浴陪诊小程序系统源码。该系统源码旨在通过数字化技术&#xff0c;优化养老…

语音控制系统的安全挑战与防御策略(上)

语音控制系统&#xff08;VCS&#xff09;提供了便捷的用户界面&#xff0c;涉及智能家居、自动驾驶汽车、智能客服等众多应用场景&#xff0c;已成为现代智能设备不可或缺的一部分。其市场规模预计到2023年达到70亿美元&#xff0c;这种扩张带来了重大的安全挑战&#xff0c;如…

走进智慧仓储:3D可视化工厂园区革新物流新纪元

在快节奏的现代生活中&#xff0c;物流仓储行业扮演着至关重要的角色。随着科技的飞速发展&#xff0c;传统仓储模式正面临一场前所未有的变革。今天&#xff0c;就让我们一起看看3D可视化技术如何为物流行业带来前所未有的便利与效率。 什么是3D可视化工厂园区&#xff1f; 3…

第13章 层次式架构设计理论与实践

层次式架构的核心思想是将系统组成为一种层次结构&#xff0c;每一层为上层服务&#xff0c;并作为下层客户。其实不管是分层还是其他的架构都是为了解耦&#xff0c;更好的复用&#xff0c;只要秉承着这种思想去理解一切都迎刃而解了。 13.1 层次上体系结构概述 回顾一下软件…

服务器数据恢复—EVA存储异常断电重启后虚拟机无法启动如何恢复数据?

服务器存储数据恢复环境&#xff1a; 某品牌EVA8400&#xff0c;服务器上安装VMware ESXi虚拟化平台&#xff0c;虚拟机的虚拟磁盘包括数据盘&#xff08;精简模式&#xff09;快照数据盘&#xff0c;部分虚拟机中运行oracle数据库和mysql数据库。 服务器存储故障&检测&…

python--pycharm中将venv删除后怎么办

在终端中输入以下命令来创建一个新的虚拟环境&#xff08;可选&#xff09;&#xff1a; python -m venv venv 激活虚拟环境&#xff1a; Windows: .\venv\Scripts\activate选择自己项目的虚拟环境

一款颜值颇高的虚拟列表!差点就被埋没了,终于还是被我挖出来了

大家好&#xff0c;我是晓衡&#xff01; 今天&#xff0c;推荐一款颇有颜值的虚拟列表组件&#xff0c;不然真的被埋没就可惜了&#xff01; 我们先来看下效果&#xff1a; 感觉怎么样&#xff1f;还不错吧&#xff01; 为什么说这个资源差点被埋没呢&#xff1f;因为个朋友找…

visual studis 安装教程

1、下载软件 2、直接安装。根据自己的需求选择需要的模板类型。 如果是.net环境&#xff0c;可以选择.net项目&#xff1b; 如果是c环境&#xff0c;可以选择c项目模板&#xff0c;多个模板可以同时并存。 3、选择C模板&#xff0c;然后重新启动项目。 我是小路&#xff0c;一枚…

python基础知识:py文件转换为jupyter文件

搜索了很多&#xff0c;都没什么用&#xff0c;会出现一些json错误&#xff0c;最终直接新建文件成功: 在自己电脑安装Anaconda&#xff0c;安装jupyter notebook&#xff0c;输入命令打开jupyter notebook&#xff1a; 在Anoconda命令行中cd到自己要转换文件的地址&#xff0…