STM32单片机-TIM定时器

一、定时器概述

  • 定时器可以对输入的时钟进行计数,并在计数值达到设定值时触发中断
  • 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-GPIO)引脚

在这里插入图片描述

  下面给出输出模式控制器的执行逻辑—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单片机用达林顿管驱动电机)
  • 电机驱动模块有TB6612L298N
  • 下面介绍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输入模式),同时测量频率和占空比
  • 可配合主从模式触发,实现硬件全自动测量

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

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

  输入引脚CH1一旦有边沿(自己配置)出现,输入滤波边沿检测器就会检测到边沿,其中输出有两个通道选择TI1FP1-IC1TI2FP2-IC2,分别可以输入给两个通道,CH2、CH3和CH4同理

  可以灵活切换后续捕获电路的输入。也可以把一个引脚的输入,同时映射到两个捕获单元-PWMI基本结构,TI1FP1上升沿触发,捕获周期,TI1FP2下降沿触发,捕获占空比,两个通道对一个引脚进行捕获,同时测量频率和占空比

  预分频器对前面的信号进行分预分频,分频之后的触发信号,就可以触发捕获电路进行工作了,每来一个信号,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同上面一样,上升沿清零CNT,TI1FP2设置为下降沿触发,通过交叉通道,触发通道2的捕获单元CCR2CCR2不触发CNT清零,CCR1触发CNT清零,所以CCR2就是高电平计数值,CCR1是整个周期计数值,所以CCR2/CCR1就是占空比

在这里插入图片描述

六、编码器

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

6.1 正交编码器方向

  下面给出正交编码器的正反转判断图
  编码器接口设计逻辑就是把A相和B相的所有边沿作为计数器的计数时钟,出现边沿信号时,计数自增或自减由另一相的状态来决定

  就是当出现某一个边沿时,我们判断另一相的高低电平来判断正转还是反转

  • 正转

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

  • 反转

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

在这里插入图片描述

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

在这里插入图片描述

6.2 编码器电路和基本结构

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

在这里插入图片描述

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

在这里插入图片描述

  下面给出编码器的基本结构图
  输入捕获的CH1和CH2通过GPIO接到编码器的A、B相,然后通过滤波器极性选择产生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

/*
@brief:TIM2初始化
*/
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 = 1s
	TIM_TimeBaseInitStructure.TIM_Period = 10000-1;							//ARR自动重装器的值,计10000个数字
	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);
}

/*
@brief:定时器TIM2中断函数
*/
//void TIM2_IRQHandler()
//{
//	if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET)
//	{
//		
//		TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
//	}
//}

7.3 定时器外部时钟申请

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

#include "stm32f10x.h"                  // Device header

/*
@brief:外部时钟(对射红外传感器)定时器初始化
*/
void Timer_Init()
{
	//开启RCC时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
	//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);
	
	//选择时基单元时钟-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,如果分频更大,则需要触发多次,计数器才会加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);
}

uint16_t Timer_GetCounter()
{
	return TIM_GetCounter(TIM2);
}

/*
@brief:中断服务函数
*/
//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) — 配置GPIO(复用推挽输出) — 时钟源选择加配置时基单元配置输出比较单元(CCR的值、输出比较模式、极性选择、输出使能参数 — 运行控制(启动计数器)
  同一个定时器可以使用多个通道输出多个PWM,多个通道共用一个计数器,所以它们的频率是相同的,占空比由各自的CCR决定,计数器更新,所有PWM同时跳变,相位同步

7.4.1 PWM驱动LED呼吸灯

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

#include "stm32f10x.h"                  // Device header

void PWM_Init()
{
	//开启RCC时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
	//GPIOA初始化(PA0-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;		//向上计数
	//时基单元关键寄存器参数---PWM f = 1000Hz
	TIM_TimeBaseInitStructure.TIM_Period = 100-1;						//ARR
	TIM_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;					//输出比较模式:PWM1	
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;			//输出比较极性:HIGH极性不反转,REF有效电平是高电平
	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输出
	
}

/*
@brief:单独设置CCR的值,调整占空比
@param:Compare:CCR的值
*/
void PWM_SetCompare(uint16_t Compare)
{
	TIM_SetCompare1(TIM2, Compare);
}

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

uint8_t i;

int main(void)
{
	OLED_Init();
	PWM_Init();
	
	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(TIM2-CH2)口
  下面给出PWM.h,我们需要设置周期为20ms,高电平时间对应从0.5ms-2.5ms。ARR和PSC决定周期,CCR和ARR决定占空比

#include "stm32f10x.h"                  // Device header

void PWM_Init()
{
	//开启RCC时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	//GPIOA初始化(CH2)
	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;		//向上计数
	//时基单元关键寄存器参数-- 72000000/72/20000 = 50Hz  f = 50Hz T = 20ms
	TIM_TimeBaseInitStructure.TIM_Period = 20000-1;						//ARR
	TIM_TimeBaseInitStructure.TIM_Prescaler = 72-1;						//PSC 
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;				//重复计数器的值,高级定时器专用
	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
		
	//配置输出比较单元(PA1是OC2输出口,使用OC2初始化函数)
	TIM_OCInitTypeDef TIM_OCInitStructure;
	TIM_OCStructInit(&TIM_OCInitStructure);								//结构体赋初始值
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;					//设置输出比较模式:PWM1	
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;			//设置输出比较极性:REF有效电平高电平
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;		//输出使能
	TIM_OCInitStructure.TIM_Pulse = 0;									//CCR,占空比为0%
	TIM_OC2Init(TIM2,&TIM_OCInitStructure);
	
	//启动定时器
	TIM_Cmd(TIM2,ENABLE);		//使能计数器,PWM波形通过PA1输出
	
}

/*
@brief:单独设置CCR的值,调整占空比 20000-20ms,Compare = 500--0.5ms  2500--2.5ms
@param:Compare:CCR值
*/
void PWM_SetCompare(uint16_t Compare)
{
	TIM_SetCompare2(TIM2, Compare);
}

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

void Servo_Init()
{
	PWM_Init();
}

/*
@brief:输入转动角度,舵机旋转固定角度
@param:转动角度
*/
//x        y 
//0°   -- 500
//180° -- 2500  
//y = kx + b
//b = 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里的输出通道

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

#include "stm32f10x.h"                  // Device header

void PWM_Init()
{
	//开启RCC时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	//GPIOA初始化(CH3)
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;						//复用推挽输出模式(引脚的控制权交给片上外设,PWM通过引脚输出)
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
	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;		//向上计数
	//时基单元关键寄存器参数---频率1000Hz	(频率越高,电机转动声音越小)
	TIM_TimeBaseInitStructure.TIM_Period = 100-1;						//ARR
	TIM_TimeBaseInitStructure.TIM_Prescaler = 720-1;					//PSC
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;				//重复计数器的值,高级定时器专用
	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
		
	//配置输出比较单元(PA2是OC3输出口,使用OC3初始化函数)
	TIM_OCInitTypeDef TIM_OCInitStructure;
	TIM_OCStructInit(&TIM_OCInitStructure);								//结构体赋初始值
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;					//设置输出比较模式:PWM1
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;			//设置输出比较极性:REF有效电平高电平
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;		//输出使能
	TIM_OCInitStructure.TIM_Pulse = 0;									//CCR,占空比为0%
	TIM_OC3Init(TIM2,&TIM_OCInitStructure);
	

	//启动定时器
	TIM_Cmd(TIM2,ENABLE);												//使能计数器,PWM波形通过PA2输出
	
}

/*
@brief:单独设置CCR的值,调整占空比
*/
void PWM_SetCompare(uint16_t Compare)
{
	TIM_SetCompare3(TIM2, Compare);
}

  下面给出Motor.c

#include "stm32f10x.h"                  // Device header
#include "PWM.h"


void Motor_Init()
{
	PWM_Init();
	//电机控制方向的GPIO口 AIN1-PA4 AIN2-PA5
	RCC_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);
}

/*
@brief:设置电机转动方向和速度
@param:Speed:速度
*/
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屏幕上

uint8_t KeyNum;
int8_t Speed;		//负的时候反方向转


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 输入捕获模式测频率

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

#include "stm32f10x.h"                  // Device header

void 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;		//向上计数
	//时基单元关键寄存器参数---1000Hz
	TIM_TimeBaseInitStructure.TIM_Period = 100-1;						//ARR
	TIM_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;					//输出比较模式:PWM1	
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;			//输出比较极性:REF有效电平为高电平
	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输出
	
}

/*
@brief:单独设置CCR的值,调整占空比
@param:CCR值
*/
void PWM_SetCompare(uint16_t Compare)
{
	TIM_SetCompare1(TIM2, Compare);
}
/*
@brief:单独设置PSC的值,调整频率
@param:PSC值
*/
void PWM_SetPrescaler(uint16_t Prescaler)
{
	TIM_PrescalerConfig(TIM2, Prescaler,TIM_PSCReloadMode_Immediate);
}

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

#include "stm32f10x.h"                  // Device header


void IC_Init()
{
	//开启RCC时钟PA6输入方波,TIM2输出PWM波,TIM3输入捕获
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
	//GPIOA初始化TIM3的CH1为PA6
	GPIO_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;						//标准频率1MHz
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;				//重复计数器的值,高级定时器专用
	TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);
	
	//初始化输入捕获单元
	TIM_ICInitTypeDef TIM_ICInitStructure;
	TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;					//TIM3的CH1
	TIM_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,TI1FP1
	TIM_SelectSlaveMode(TIM3,TIM_SlaveMode_Reset);						//从模式:Reset
	
	//启动定时器
	TIM_Cmd(TIM3,ENABLE);
}
/*
@brief:读取最新一个周期的频率,f = fc/N = 72M/PSC+1 / N(N=CCR)
*/
uint32_t IC_GetFreq()
{
	return 1000000 / (TIM_GetCapture1(TIM3)+1);		//面向结果编程+1
}

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

int main(void)
{
	OLED_Init();
	PWM_Init();
	IC_Init();
	OLED_ShowString(1,1,"Freq:00000Hz");
	
	//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 header


void IC_Init()
{
	//开启RCC时钟-PA6输入方波,TIM2输出PWM波
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	//GPIOA初始化TIM3的CH1为PA6
	GPIO_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;						//标准频率1MHz
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;				//重复计数器的值,高级定时器专用
	TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);
	
	//初始化输入捕获单元
	TIM_ICInitTypeDef TIM_ICInitStructure;
	TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;					//TIM3的CH1
	TIM_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 ,TI1FP1
	TIM_SelectSlaveMode(TIM3,TIM_SlaveMode_Reset);		//从模式Reset
	
	//启动定时器
	TIM_Cmd(TIM3,ENABLE);
}
/*
@brief:读取最新一个周期的频率,f = fc/N = 72M/PSC+1/N(N=CCR1)
@retval:最新一个周期的频率
*/
uint32_t IC_GetFreq()
{
	return 1000000 / (TIM_GetCapture1(TIM3)+1);		//面向结果编程+1
}
/*
@brief:获取PWM占空比,CCR2/CCR1
@retval:PWM占空比
*/
uint32_t IC_GetDuty()
{
	return (TIM_GetCapture2(TIM3)+1)*100/(TIM_GetCapture1(TIM3)+1);
}

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

int main(void)
{
	OLED_Init();
	PWM_Init();
	IC_Init();
	OLED_ShowString(1,1,"Freq:00000Hz");
	OLED_ShowString(2,1,"Duty:000%");

	//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 编码器接口测速

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

#include "stm32f10x.h"                  // Device header


void Encoder_Init()
{
	//开启RCC时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	//GPIOA初始化TIM3的CH1\CH2为PA6\PA7
	GPIO_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
	TIM_ICInitStructure.TIM_ICFilter = 0xF;								//滤波
	//TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;		//高低电平极性不反转(不反相)
	TIM_ICInit(TIM3,&TIM_ICInitStructure);
	
	TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;					//TIM3CH2
	TIM_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);	//取CNT值,编码器旋转CNT自增或自减
	TIM_SetCounter(TIM3,0);			//CNT清零
	return Temp;
}


  利用定时器中断去实现间隔1s刷新一次显示的值,main.c如下所示

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);
	}
}

/*
@brief:定时器TIM2中断函数1s
*/
void TIM2_IRQHandler()
{
	if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET)
	{
		Speed = Encoder_Get();
		TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
	}
}

  • 19
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
STM32 单片机中,定时器是一种非常有用的功能模块,可以用于生成精确的时间延迟、计时、PWM 输出等。下面是使用定时器的基本步骤: 1. 首先,在 CubeMX 或者 HAL 库中配置定时器的参数,包括计数模式、时钟源、分频系数等。你可以选择使用内部时钟源(如内部 RC 振荡器)或外部时钟源(如外部晶体)。 2. 在代码中初始化定时器,并设置相关的参数。你可以使用 HAL 库提供的函数来完成这些操作。例如,使用 `HAL_TIM_Base_Init()` 函数初始化定时器。 3. 配置定时器的中断功能(可选)。如果你想在定时器达到预定值时触发中断,可以使用 `HAL_TIM_Base_Start_IT()` 函数启动定时器中断,并编写相应的中断处理函数。 4. 在需要使用定时器的地方,启动定时器。你可以使用 `HAL_TIM_Base_Start()` 函数来启动定时器。 5. 如果需要进行计时或者生成 PWM 输出,可以使用 `__HAL_TIM_SetCounter()` 函数设置计数器的初始值,并使用 `__HAL_TIM_SetCompare()` 函数设置比较值。 6. 如果需要停止定时器,可以使用 `HAL_TIM_Base_Stop()` 函数停止定时器的计数。 下面是一个简单的示例代码,演示如何在 STM32 上使用定时器: ```c #include "main.h" TIM_HandleTypeDef htim2; // 定时器句柄 void SystemClock_Config(void); static void MX_GPIO_Init(void); static void MX_TIM2_Init(void); int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_TIM2_Init(); HAL_TIM_Base_Start(&htim2); // 启动定时器 while (1) { // 在这里进行其他操作 } } void MX_TIM2_Init(void) { TIM_ClockConfigTypeDef sClockSourceConfig = {0}; htim2.Instance = TIM2; htim2.Init.Prescaler = 0; htim2.Init.CounterMode = TIM_COUNTERMODE_UP; htim2.Init.Period = 10000; if (HAL_TIM_Base_Init(&htim2) != HAL_OK) { Error_Handler(); } sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL; if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK) { Error_Handler(); } } ``` 在上面的代码中,我们使用 CubeMX 或者 HAL 库生成了一个名为 TIM2 的定时器,并将其初始化为向上计数模式,计数器周期为 10000。然后在 `main()` 函数中启动了定时器。 你可以根据自己的需求修改上述代码,并根据定时器的不同功能来进行相应的配置和操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值