STM32F1单片机-TIM定时器
一、定时器简介
- 定时器对输入的时钟进行计数,计数值达到设定值时触发中断
- 16位计数器在72MHz计数时钟下可以实现最大59.65s的定时
- 单片机频率:每秒钟计多少个时钟周期,以72MHZ为例,就是1s计72M个时钟周期。那计次1个时钟周期就是1/72M
- T=1 / f=1 / 1MHz = 1us(1KHz - 1ms, 1Hz - 1s)
- STM32中一个基准时钟(时钟周期)是1s/72MHz,计72个数字就是过了1s/72MHz×72 = 1us,如果计72000个数,那就是1ms
- 定时器功能:定时中断、内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等多种功能
- 定时器根据复杂度和应用场景分为基本定时器、通用定时器、高级定时器三种类型
二、定时器类型
- 如下图所示是STM32系列的定时器类别编号
- STM32F103C8T6定时器资源:TIM1、TIM2、TIM3、TIM4
2.1 基本定时器
- 基本定时器包含一个16位自动装载计数器,由可编程预分频器驱动
- 基本定时器主要功能如下:
- 16位自动重装载累加计数器,向上计数
- 16位可编程预分频器,对输入时钟按系数(1~65535)分频
- 触发DAC的同步电路
- 在更新事件(计数器溢出)时产生中断或者DMA中断
- 如下图为基本定时器框图
- RCC的TIMxCLK是内部时钟输入,频率值是系统的主频72MHz
- PSC预分频器、CNT计数器、自动重装载寄存器共同组成时基单元
- PSC对72MHz进行预分频(寄存器写0是1分频,写1是2分频)。CNT(16位)对预分频后的时钟进行计数(每来一个上升沿,计数时钟加1)。自动重装载寄存器是计数目标。过程中,CNT不断自增,当计数值等于自动重装值,即计数达到,产生中断信号,并清零计数器,开始下一次的计数
- UI为更新中断,通往NVIC,产生中断事件
- U代表更新事件,不会触发中断,触发其他事件的工作
- 主从模式触发DAC:更新事件U映射到TRGO,TRGO触发DAC,不需要利用中断去执行
C51系列单片机是设定计数初值,STM32系列是设定计数终值
2.2 通用定时器
- 通用定时器包含一个16位自动装载计数器,由可编程预分频器驱动
- 通用定时器适合于多种场合,包括测量输入信号的脉冲长度(输入捕获)或者产生输出波形(输出比较和PWM)
- 通用定时器的主要功能如下:
- 16位向上(计数器从0开始,向上自增,计到重装值,清零同时申请中断)、向下(计数器从重装值开始,向下自减,减到0以后,回到重装值同时申请中断)、向上/向下(计数器从0开始,先向上自增,计到重装值,申请中断,然后再向下自减,减到0,申请中断)自动装载计数器
- 16位可编程预分频器,对输入时钟按系数(1~65535)分频
- 4个独立通道:输入捕获、输出比较、PWM生成和单脉冲模式输出
- 使用外部信号控制定时器和定时器互连的同步电路
- 如下事件发生时产生中断/DMA
- 更新:计数器向上溢出/向下溢出,计数器初始化(软件或者内部/外部触发)
- 触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)
- 输入捕获
- 输出比较
- 支持针对定位的增量(正交)编码器和霍尔传感器电路–电机
- 触发输入作为外部时钟或者按周期的电流管理
- 时基单元:预分频器、计数器和自动重装载寄存器,通用计数器计数器的模式有向上、向下计数等
- 基本定时器只能选择内部时钟-系统频率72MHz
- 通用定时器里,时钟源可以选择72MHz时钟和外部时钟,外部时钟相当于定时器指定的外部引脚上输入一个方波信号,来提供定时器的时钟,即ETR引脚提供的外部时钟模式2,也可以选择内部触发输入(使用一个定时器作为另一个定时器的预分频器),或者选择外部时钟模式1,由TI1-ED、TI1FP1和TI2FP2编码器接口提供时钟
- 选择内部还是外部来提供时钟的本质区别就是计数频率是72MHz,还是外部方波信号
- 输出比较总共有4个通道,分别对应CH1-CH4的引脚,用于输出PWM的波形
- 输入捕获总共有4个通道,对应的是CH1-CH4的引脚,用于测量输入方波的频率等
- 其中的捕获/比较寄存器是输入捕获和输出比较电路公用的,分时复用-不可以同时使用
2.3 高级定时器
- 高级定时器包含一个16位自动装载计数器,由可编程预分频器驱动
- 高级定时器适合多种用途,包含测量输入信号的脉冲宽度(输入捕获),或者产生输出波形(输出比较、PWM、嵌入死区时间的互补PWM等)
- 高级定时器主要功能如下:
- 16位向上(计数器从0开始,向上自增,计到重装值,清零同时申请中断)、向下(计数器从重装值开始,向下自减,减到0以后,回到重装值同时申请中断)、向上/向下(计数器从0开始,先向上自增,计到重装值,申请中断,然后再向下自减,减到0,申请中断)自动装载计数器
- 16位可编程预分频器,对输入时钟按系数(1~65535)分频
- 4个独立通道:输入捕获、输出比较、PWM生成和单脉冲模式输出
- 死区时间可编程的互补输出
- 使用外部信号控制定时器和定时器互连的同步电路
- 允许在指定数目的计数器周期之后更新定时器寄存器的重复计数器
- 刹车输入信号可以将定时器输出信号置于复位状态或者一个已知状态
- 如下事件发生时产生中断/DMA
- 更新:计数器向上溢出/向下溢出,计数器初始化(软件或者内部/外部触发)
- 触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)
- 输入捕获
- 输出比较
- 刹车信号输入
- 支持针对定位的增量(正交)编码器和霍尔传感器电路
- 触发输入作为外部时钟或者按周期的电流管理
- 下图为高级定时器与通用定时器不同的部分
- 申请中断部分,加了一个重复次数计数器,可以实现每隔几个计数周期产生一次更新事件和更新中断,通用定时器是一个计数周期一个中断,高级定时器相当于对输出的更新信号又进行了一次分频
- 输出比较器的升级,DTG是死区生成电路,输出CH1,2,3的输出引脚由原来的一个通道变为了两个互补通道,可以输出一组互补的PWM波,为了驱动三相无刷电机
- 刹车输入功能,为了给电机驱动提供安全保障的,外部刹车输入引脚BKIN经过极性选择(高或者低电平有效),产生刹车信号或者内部时钟失效,那么控制电路会自动切断电机的输出,防止意外发生
三、TIM定时中断
3.1 定时中断基本结构
下面给出通用定时中断基本结构,即定时器的工作配置流程
- 时基单元由PSC预分频器、CNT计数器和ARR自动重装器组成。运行控制是控制寄存器的一些位,比如启动停止、向上或者向下计数等等
- 时钟源可以选择RCC提供内部时钟-72MHz,或者ETR引脚提供的外部时钟模式2,也可以选择内部触发输入(使用一个定时器作为另一个定时器的预分频器),或者选择外部时钟模式1,由TI1-ED、TI1FP1和TI2FP2编码器接口提供时钟
- 计时时间到达时,产生更新中断后的信号去向,其中如果是高级定时器,还会再输出信号部分多个重复计数器,中断信号先在状态寄存器里置一个中断标志位,经过中断输出控制(判断哪个中断允许),进入NVIC,最后进入中断函数
3.2 预分频器时序
- 当预分频器的参数从0变到1(不分频到2分频)时,计数器的时序图如下所示
- CK_PSC是内部时钟。CNT_EN是计数器使能,高电平计数器正常运行,低电平关闭。CK_CNT是定时器时钟,它既是预分频器的时钟输出,也是计数器的时钟输入。前半段,预分频器系数为0(1分频),计数器的时钟等于预分频器的时钟,后半段,预分频器系数为1(2分频),计数器计数周期加倍。在计数器时钟的驱动下,下面的计数器寄存器也跟随时钟的上升沿不断自增(到达FC后变0,ARR自动重装值就是FC)
- 下面三行描述的是预分频器的一种缓冲机制,当计数计到一半的时候改变了分频值,这个变化不会立刻生效,而是会等到本次计数周期结束产生更新事件,预分频器寄存器的值才会被传递到预分频缓冲器中,才会生效
- 计数频率:CK_CNT = CK_PSC/(PSC分频值+1)
- 例:72MHz/(7199+1) = 10000 Hz
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)
- 基准时钟72MHZ表示1s计次72M个时钟周期,假设分频系数为71,则得到分频后的1MHZ,即1s计次1M个时钟周期,将这个作为计时器的基准时钟,然后ARR设定99时,表示计次100个时钟周期计时器溢出一次,那1s有1M个时钟周期,1M/100得到溢出频率(多少个100计次),即1s溢出多少次,取倒数得到溢出一次的时间,即中断产生一次的时间
- 计数器溢出时间:CK_CNT_T = (ARR+1)/CK_CNT
- 其中CK_PSC是内部时钟72MHz或者外部方波信号、PSC是给定分频器的分频、ARR为自动重装值(计数目标值)
- 例CK_CNT_OV = 72000 000/7200/10000 = 1Hz,周期是1s,即1s溢出一次
3.3.1 计数器有无预装时序(有无缓冲寄存器-影子寄存器)
- 当ARPE = 0时的更新事件(无影子寄存器)
- 计数目标突然从FF变成了36,所以直接计到36就产生更新中断,开始下一轮计数
- 当ARPE = 1时的更新事件(有影子寄存器)
- 计数目标从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波形
-
下图为高级定时器的前3个输出比较部分
-
输入是CNT和CCR比较的结果,OC1和OC1N是两个互补端口,分别控制上管和下管的导通和关闭
-
下图为通用定时器的输出比较部分(高级定时器的第4个输出)
-
左边是通过输出模式控制器(TIMx_CCMR1寄存器控制)得到的CNT和CCR的比较结果,输出OC1REF的高低电平,REF映射到主模式的TRGO输出,也可以通过极性选择(TIMx_CCER)到达输出使能电路,最后输出OC1(CH1-GPIO)引脚
- 下面给出输出模式控制器的执行逻辑
- 有效电平—高电平 无效电平—低电平
- PWM模式1和PWM模式2是相反逻辑
- 下面给出PWM的基本执行结构
- 时基单元配置好后只需要比较CNT和CCR的值即可,不需要配置中断
- 下图中ARR=99,CCR=30,按照PWM模式1输出模式配置,占空比为30%
- 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输入模式),同时测量频率和占空比
- 可配合主从模式触发,实现硬件全自动测量
- 如下图所示为通道输入部分
- 输入引脚有个三输入的异或门,接在了通道CH1、CH2和CH3端口,当三个输入引脚的任何一个有电平翻转时,输出引脚就产生一次电平翻转,输出通过数据选择器,到达输入捕获通道1。数据选择器选择上面一个,输入捕获通道1的输入就是三个输入的异或值,如果选择下面一个,异或门无效,4个通道各用各的引脚。
- 输入引脚CH1一旦有边沿(配置)出现,输入滤波和边沿检测器就会检测到边沿,其中输出有两个通道选择 — TI1FP1-IC1和TI2FP2-IC2,分别可以输入给两个通道,CH2、CH3和CH4同理
- 可以灵活切换后续捕获电路的输入,可以把一个引脚的输入,同时映射到两个捕获单元-PWMI基本结构,TI1FP1上升沿触发,捕获周期,TI1FP2下降沿触发,捕获占空比,两个通道对一个引脚进行捕获,同时测量频率和占空比
- 预分频器对前面的信号进行分预分频,分频之后的触发信号,就可以触发捕获电路进行工作了,每来一个信号,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。适合低频信号
- 例:捕获两个上升沿之间持续的时间为一个周期T。用一个已知的标准频率fc来计次,从一个上升沿开始计,计数器从0开始,一直计到下一个上升沿,停止。计一个数的时间是1/fc,计N个数,时间就是N/fc,所以频率就是fc/N
- 中界频率(频率多高算高,多低算低,选用哪种方法):测频法和测周法误差相等的频率点为根号fc/T
- 当待测信号频率小于中界频率时,测周法误差小。反之,测频法误差小
5.2 输入捕获/PWMI基本结构
5.2.1 输入捕获基本结构
- 下图是输入捕获基本结构,只使用了一个通道,只能测量频率
- 配置时基单元,启动定时器,CNT在预分频之后的时钟驱动下不断自增,标准频率为72MHz/预分频系数。
- 输入捕获通道1的GPIO口,输入方波信号,经过滤波器和边缘检测,选择TI1FP1为上升沿触发。当出现上升沿时,CNT的当前计数值进入CCR1里,同时触发源选择,选中TI1FP1为触发信号,从模式选中复位操作,CNT自动清零(CNT的值先进CCR1里,再清零),输入直连,分频器不分频
5.2.2 PWMI基本结构
- 下图是PWMI基本结构,使用两个通道同时捕获一个引脚,可以同时测量频率和占空比
- TI1FP1同上面一样,上升沿清零CNT,TI1FP2设置为下降沿触发,通过交叉通道,触发通道2的捕获单元CCR2。CCR2不触发CNT清零,CCR1触发CNT清零,所以CCR2就是高电平计数值,CCR1是整个周期计数值,所以CCR2/CCR1就是占空比
六、编码器
- 编码器接口可以接收增量(正交)编码器的信号,根据编码器旋转产生的正交信号脉冲,自动控制CNT自增或者自减,从而指示编码器的位置、旋转方向和旋转速度
- 每个高级定时器和通用定时器都拥有一个编码器接口
- 两个输入引脚借用了输入捕获的通道1和通道2来检测编码器输出的A相B相脉冲信号
6.1 正交编码器方向
- 下图为正交编码器的正反转判断图
- 编码器接口设计逻辑就是把A相和B相的所有边沿作为计数器的计数时钟,出现边沿信号时,计数自增或自减由另一相的状态来决定
- 就是当出现某一个边沿时,我们判断另一相的高低电平来判断正转还是反转
- 正转
- 例:A相上升沿时,B相低电平,表示正转
- 反转
- 例: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);
}
- main.c如下,调整占空比
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.c,我们需要设置周期为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);
}
- main.c如下
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.c里的输出通道
- 下面给出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_EncoderInterfaceConfig函数隐式完成输入捕获配置
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);
}
}