初识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
主要功能:
- 16位自动重装载累加计数器
- 16位可编程(可实施修改)预分频器,用于对输入的时钟按系数为1~65535之间的任意数值分频
- 触发DAC的同步电路
- 在更新事件(计数器溢出)时产生中断/DMA中断
RCC的TIMxCLK是内部时钟输入,频率值是系统的主频72MHz,所以通向时基单元的基准频率就是72MHz。
PSC预分频器、CNT计数器、自动重装载寄存器共同组成时基单元,PSC对72MHz的基准时钟进行预分频(寄存器写0就是1分频,写1就是2分频,输出频率=输入频率/2=72MHz/2)。CNT(16位)对预分频后的时钟进行计数(每来一个上升沿,计数时钟加1)。自动重装载寄存器就是存的写入的计数目标,运行过程中,计数值不断自增,自动重装值是固定的目标,当计数值等于自动重装值,即计数达到,产生中断信号,并清零计数器,开始下一次的计数
其中UI为更新中断,通往NVIC;U代表更新事件,不会触发中断,触发其他事件的工作
主从模式触发DAC:更新事件U映射到TRGO,TRGO就可以触发DAC,不需要利用中断去执行
C51系列单片机是设定计数初值,STM32系列是设定计数终值
2.2 通用定时器(TIM2、TIM3、TIM4和TIM5)
通用定时器是一个通过可编程分频器驱动的16位自动装载计数器构成
它适合于多种场合,包括测量输入信号的脉冲长度(输入捕获)或者产生输出波形(输出比较和PWM)
使用定时器预分频器和RCC时钟控制器预分频器,脉冲长度和波形周期可以在几个微秒和几个毫秒间调整
主要功能:
- 16位向上(计数器从0开始,向上自增,计到重装值,清零同时申请中断)、向下(计数器从重装值开始,向下自减,减到0以后,回到重装值同时申请中断)、向上/向下(计数器从0开始,先向上自增,计到重装值,申请中断,然后再向下自减,减到0,申请中断)自动装载计数器
- 16位可编程(可以实现修改)预分频器,计数器时钟频率的分频系数为1~65535之间的任意数值
- 4个独立通道:输入捕获、输出比较、PWM生成和单脉冲模式输出
- 使用外部信号控制定时器和定时器互连的同步电路
- 如下事件发生时产生中断/DMA
- 更新:计数器向上溢出/向下溢出,计数器初始化(软件或者内部/外部触发)
- 触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)
- 输入捕获
- 输出比较
- 支持针对定位的增量(正交)编码器和霍尔传感器电路
- 触发输入作为外部时钟或者按周期的电流管理
时基单元的基本结构:预分频器、计数器和自动重装载寄存器同基本定时器,工作流程也是一样,但对于通用计数器而言,计数器的模式就不止向上计数这一种了。
下面介绍通用定时器与基本定时器不同的地方
如下图所示是内外时钟源选择和主从触发模式的结构
基本定时器只能选择内部时钟,也就是系统频率72MHz。通用定时器里,时钟源不仅可以选择72MHz时钟,也可以选择外部时钟,相当于定时器指定的外部引脚上输入一个方波信号,来提供定时器的时钟。
选择内部还是外部来提供时钟的本质区别就是计数频率是72MHz,还是外部方波信号
TIMx_ETR引脚上的外部时钟,通过ETRP、输入滤波、ETRF进入时基单元,这一路叫做外部时钟模式2。TRGI(触发输入)也可以提供时钟,由ETR、ITR(其他定时器)、CH1引脚边沿、CH1、CH2提供,可以触发定时器的从模式,叫做外部时钟模式1
如下图是输出比较电路
总共有4个通道,分别对应CH1-CH4的引脚,用于输出PWM的波形
下面是输入捕获电路
也是4个通道,对应的是CH1-CH4的引脚,用于测量输入方波的频率等
下面是捕获/比较寄存器,是输入捕获和输出比较电路公用的,输入捕获和输出比较不可以同时使用
2.3 高级定时器(TIM1和TIM8)
高级定时器由一个16位的自动装载计数器组成,由一个可编程的预分频器驱动
它适合多种用途,包含测量输入信号的脉冲宽度(输入捕获),或者产生输出波形(输出比较、PWM、嵌入死区时间的互补PWM等)
使用定时器预分频器和RCC时钟控制预分频器,可以实现脉冲宽度和波形周期从几个微秒到几个毫秒的调节
主要功能:
- 16位向上(计数器从0开始,向上自增,计到重装值,清零同时申请中断)、向下(计数器从重装值开始,向下自减,减到0以后,回到重装值同时申请中断)、向上/向下(计数器从0开始,先向上自增,计到重装值,申请中断,然后再向下自减,减到0,申请中断)自动装载计数器
- 16位可编程(可以实现修改)预分频器,计数器时钟频率的分频系数为1~65535之间的任意数值
- 4个独立通道:输入捕获、输出比较、PWM生成和单脉冲模式输出
- 死区时间可编程的互补输出
- 使用外部信号控制定时器和定时器互连的同步电路
- 允许在指定数目的计数器周期之后更新定时器寄存器的重复计数器
- 刹车输入信号可以将定时器输出信号置于复位状态或者一个已知状态
- 如下事件发生时产生中断/DMA
- 更新:计数器向上溢出/向下溢出,计数器初始化(软件或者内部/外部触发)
- 触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)
- 输入捕获
- 输出比较
- 刹车信号输入
- 支持针对定位的增量(正交)编码器和霍尔传感器电路
- 触发输入作为外部时钟或者按周期的电流管理
下面给出高级定时器的框图
从上图中可以看出高级定时器与通用定时器不同的部分仅两块内容
第一块是申请中断的部分,加了一个重复次数计数器,可以实现每隔几个计数周期产生一次更新事件和更新中断,通用定时器是一个计数周期一个中断,高级定时器相当于对输出的更新信号又进行了一次分频。
第二块是对输出比较器的升级,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是我们给定的值
- 每个高级定时器和通用定时器都拥有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桥电路基本结构,由两路推挽电路组成
左上和右下导通,电流从左到右;右上和左下导通,电流从右到左,可以实现电机正反转
下面给出TB6612的硬件电路,具体引脚定义见下图
注意电机的驱动电源和逻辑电源,驱动电源需要接大电源。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。如果选择上面一个就是三个输入的异或值。如果选择下面一个,异或门无效,各用各的引脚。
一旦有边沿(自己配置)出现,输入滤波和边沿检测器就会检测到边沿,其中输出有两个通道选择,TI1FP1和TI2FP2,分别可以输入给两个通道。可以灵活切换后续捕获电路的输入,也可以把一个引脚的输,同时映射到两个捕获单元。
来到预分频器,对前面的信号进行分预分频,分频之后的触发信号,就可以触发捕获电路进行工作了,每来一个信号,CNT的值就锁存到CCR中,CNT的数值就可以记录两个上升沿的时间间隔
下面给出输入捕获/比较通道(细化)
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);}
}