STM32-输入捕获IC和编码器接口

本内容基于江协科技STM32视频学习之后整理而得。

文章目录

  • 1. 输入捕获IC
    • 1.1 输入捕获IC简介
    • 1.2 频率测量
    • 1.3 输入捕获通道
    • 1.4 主从触发模式
    • 1.5 输入捕获基本结构
    • 1.6 PWMI基本结构
  • 2. 输入捕获库函数及代码
    • 2.1 输入捕获库函数
    • 2.2 6-6 输入捕获模式测频率
      • 2.2.1 硬件连接
      • 2.2.2 硬件运行结果
      • 2.2.3 代码流程
      • 2.2.4 代码
    • 2.3 6-7PWMI模式测频率占空比
      • 2.3.1 硬件连接
      • 2.3.2 硬件运行结果
      • 2.3.3 代码实现流程
      • 2.3.4 代码
  • 3. 编码器接口
    • 3.1 编码器接口简介
    • 3.2 正交编码器
    • 3.3 编码器接口基本结构
    • 3.4 工作模式
    • 3.5 实例(均不反相)
    • 3.6 实例(TI1反相)
  • 4. 编码器接口库函数及代码
    • 4.1 编码器接口库函数
    • 4.2 6-8 编码器接口测速
      • 4.2.1 硬件连接
      • 4.2.2 代码实现流程
      • 4.2.3 代码

1. 输入捕获IC

1.1 输入捕获IC简介

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

1.2 频率测量

频率的定义:1s内出现了多少个重复的周期

频率逐渐降低的方波波形,信号只有高低电平的数字信号

image.png
对于STM32测频率而言,也是只能测量数字信号的频率。如果需要测量一个正弦波,还需要搭建一个信号预处理电路,最简单的就是用运放搭一个比较器,把正弦波转换为数字信号,再输入给STM32就行了。如果测量的电压信号非常高,那还需要考虑一下隔离的问题,比如用一些隔离放大器、电压互感器等元件,隔离高压端和低压端,保证电路的安全。总之,经过处理最终输入给STM32的信号,要是这样的高低电平信号,高电平3.3V,低电平0V。

  • 测频法原理:
    频率的定义就是1s内出现了多少个重复的周期,那频率就是多少Hz。

  • 测周法原理
    周期的倒数是频率。捕获信号的两个上升沿,测量一下这之间持续的时间就行了。测量时间的方法实际上也是定时器计次,使用一个已知的标准频率fc的计次时钟来驱动计数器,从一个上升沿开始计,计数器从0开始,一直计到下一个上升沿,停止。计一个数的时间是1/fc,计N个数,时间就是N/fc,N/fc就是周期,再取个倒数,就得到了公式fx=fc/N。

  • 测频法和测周法优缺点
    测频法适合测量高频信号,测周法适合测量低频信号。测频法在闸门时间内,最好多出现一些上升沿,计次数量多一些,这样有助于减小误差。测周法要求信号频率低一些,低频信号周期比较长,计次就会比较多,有助于减小误差。
    测频法测量结果更新的慢一些,数值相对稳定。测周法更新的快,数据跳变的也非常快。测频法测量的是在闸门时间内的多个周期,所以它自带一个均值滤波,如果在闸门时间内波形频率有变化,那得到的其实是这一段时间的平均频率。如果闸门时间选为1s,那么每隔1s才能得到一次结果,所以测频法结果更新慢,测量结果是一段时间的平均值,值比较平滑。测周法是只测量一个周期,就能出一次结果,所以出结果的速度取决于待测信号的频率,一般而言,待测信号都是几百几千Hz,所以一般情况下,测周法结果更新更新更快,但由于它只测一个周期,所以结果值会受噪声的影响,波动比较大。

  • 中界频率
    对于测频法和测周法,计次数量N要尽量大一些,N越大,相对误差越小,因为在这些方法中,计次可能会存在正负1误差。比如测频法,在闸门时间内,并不是每个周期信号都是完整的,比如在最后时间里,可能有一个周期刚出现一半,闸门时间就到了,那这只有半个周期,只有舍弃掉或者当做一整个周期来看,因为计次只有整数,不可能计次0.5个数吧。那在这个过程,就会出现多计一个,或者少计一个的情况,这就叫做正负1误差。在测周法中,用标准频率fc计次,有像这样可能,一个数刚数到一半,计时就结束了。那这半个数也只能舍弃或者按一整个数来算了,这里也会出现正负1误差。所以正负1误差是这两种方法固有的误差,要想减小正负1误差的影响,就只能尽量多计一些数。当计次N比较大时,正负1对N的影响就会很小。
    那当有一个频率,测频法和测周法计次的N相同,就说明误差相同,这不就是中界频率。对应图上,当待测信号频率小于中界频率时,测周法误差更小,选用测周法更合适,当待测信号频率大于中界频率时,测频法误差更小,选用测频法更合适。

  • 测频法应用:
    之前的对射式红外传感器计次、定时器外部时钟代码稍加改进就是测频法。比如对射式红外传感器计次,每来一个上升沿计次+1,再用一个定时器,定一个1s的定时中断,在中断里,每隔1s取一下计次值,同时清0计次,为下一次做准备,这样每次读取的计次值就直接是频率(闸门时间T是1s,计次值是N,所以频率=计次值N/1=N)。对应定时器外部时钟代码也是如此,每隔1s取一下计次,就能实现测频法测量频率的功能了。

1.3 输入捕获通道

image.png

  • 异或门
    TIMx_CH1引脚进来,有一个三输入的异或门,异或门的执行逻辑是当三个输入引脚的任何一个电平翻转时,输出引脚就产生一次电平翻转,之后输出通过数据选择器,到达输入捕获通道1。数据选择器如果选择上面一个,那输入捕获通道1的输入就是3个引脚的异或值;如果选择下面一个,那异或门就没有用,4个通道各用各的引脚。
    设计这个异或门其实还是为三相无刷电机服务的,无刷电机有3个霍尔传感器检测转子的位置,可以根据转子的位置进行换相,有了这个异或门,就可以在前三个通道上接上无刷电机的霍尔传感器,然后这个定时器就作为无刷电机的接口定时器,去驱动换相电路工作。

  • 输入滤波器和边沿检测器
    然后信号到达输入滤波器和边沿检测器,输入滤波器可以对信号进行滤波,避免一些高频的毛刺信号误触发。
    边沿检测器就和外部中断那里一样了,可以选择高电平触发或低电平触发,当出现指定的电平时,边沿检测电路就会触发后续电路执行动作。另外这里,它其实是设计了两套滤波和边沿检测电路。第一套电路,经过滤波和极性选择,得到TI1FP1,输入给通道1的后续电路;第二套电路,经过另一个滤波和极性选择,得到TI1FP2,输入给下面通道2的后续电路。同理,下面TI2信号进来,也经过两套滤波和极性选择,得到TI2FP1和TI2FP2,其中TI2FP1输入给上面,TI2FP2输入给下面。在这里两个信号进来,可以选择各走各的,也可以选择进行一个交叉,让CH2引脚输入给通道1,或者CH1引脚输入给通道2。这里进行交叉的原因,第一个目的是可以灵活切换后续捕获电路的输入;第二个也就是主要目的,就是可以把一个引脚的输入,同时映射到两个捕获单元,这也是PWMI模式的经典结构。第一个捕获通道使用上升沿触发,用来捕获周期,第二个通道使用下降沿触发,用来捕获占空比,两个通道同时对一个引脚进行捕获,就可以同时测量频率和占空比。
    一个通道灵活切换两个引脚,和两个通道同时捕获一个引脚,这就是交叉一下的作用和目的。下面的通道3和通道4也是一样的结构。下面的TRC信号也可以作为捕获部分的输入,TRC是来源于其他定时器和CH1引脚的时钟选择(外部时钟模式1的部分输入选择),该设计也是为了无刷电机的驱动。

  • 预分频器
    输入信号经过滤波和极性选择后,就来到了预分频器。预分频器是每个通道各有一个,可以选择对前面的信号进行分频。

  • 捕获/比较寄存器
    分频之后的触发信号就可以触发捕获电路进行工作了,每来一个触发信号,CNT的值就会向CCR转运一次,转运的同时会发生一个捕获事件,这个事件会在状态寄存器置标志位,同时也可以产生中断,如果需要在捕获的瞬间,处理一些事情的话,就可以开启这个捕获中断。这就是整个电路的工作流程。

测周法:比如我们可以配置上升沿触发捕获,每来一个上升沿,CNT转运到CCR一次,又因为这个CNT计数器是由内部的标准时钟驱动的,所以CNT的数值,其实就可以用来记录两个上升沿之间的时间间隔,这个时间间隔就是周期,再取个倒数,就是测周法测量的频率了。

  • 在频率测量图中的测周法的实现
    就是:上升沿用于触发输入捕获,CNT用于计数计时,每来一个上升沿,取一下CNT的值,自动存在CCR里,CCR捕获到的值,就是计数值N,CNT的驱动时钟就是fc,fc/N就得到了待测信号的频率。每次捕获之后都要把CNT清0,这样下次上升沿再捕获的时候,取出的CNT才是两个上升沿的时间间隔,这个在一次捕获后自动将CNT清零的步骤,可以用主从触发模式自动来完成。
    image.png
  • 引脚进来先通过一个滤波器,滤波器的输入是TI1,就是CH1的引脚,输出的TI1F,是滤波后的信号;fDTS是滤波器的采样时钟来源;CCMR1寄存器里的ICF位可以控制滤波器的参数;
  • 滤波器工作原理:以采样频率对输入信号进行采样,当连续N个值都为高电平,输出才为高电平,连续N个值都为低电平,输出才为低电平。如果信号出现抖动,导致连续采样N个值不全都一样,则输出不会变化,就可以达到滤波的效果。采样频率越低,采样个数N越大,滤波效果就越好。
  • 滤波后的信号通过边沿检测器捕获上升沿或下降沿;用CCER寄存器里的CC1P位,可以选择极性,最终得到TI1FP1触发信号,通过数据选择器,进入通道1后续的捕获电路。CC1S位可以对数据选择器进行选择,ICPS位可以配置分频器,可以选择不分频、2分频、4分频、8分频;CC1E位,控制输出使能或失能。如果使能了输出,输入端产生指定边沿信号,经过层层电路,达到输出,就可以让CNT值,转运到CCR里,每捕获一次CNT的值,都要把CNT清零一下,以便于下一次的捕获。在这里硬件电路就可以在捕获之后自动完成CNT的清零工作。
  • 如何自动清零CNT:TI1FP1信号和TI1的边沿信号都可以通向从模式控制器,从模式里有电路可以自动完成CNT的清零。

1.4 主从触发模式

主模式、从模式、触发源选择的简称
image.pngimage.png

  • 主模式:可以将定时器内部的信号,映射到TRGO引脚,用于触发别的外设。
  • 从模式:接收其他外设或者自身外设的一些信号,用于控制自身定时器的运行,也就是被别的信号控制。
  • 触发源选择:就是选择从模式的触发信号源的。选择指定的一个信号,得到TRGI。TRGI来触发从模式,从模式在列表中选择一项操作来自动执行。

使TI1FP1信号自动触发CNT清零:触发源选择TI1FP1,从模式选择执行Reset操作。这样TI1FP1的信号就可以自动触发从模式,从模式自动清零CNT。

1.5 输入捕获基本结构

image.png
该图只触发了一个通道,因此只能测量频率。
时基单元配置好,启动定时器,CNT在预分频之后的时钟驱动下,不断自增。CNT就是测周法用来计数计时的。经过预分频之后的时钟频率就是驱动CNT的标准频率fc,标准频率fc=72M/预分频系数。
输入捕获通道1的GPIO口,输入一个方波信号,经过滤波器和边沿检测,选择TI1FP1为上升沿触发,之后输入选择直连的通道,分频器选择不分频。当TI1FP1转为上升沿之后,CNT的当前计数值转运到CCR1里。同时触发源选择,选中TI1FP1为触发信号,从模式选择复位操作。这样TI1FP1的上升沿也会通过上面一路(触发源选择-从模式)去触发CNT清零。先转运CNT的值到CCR里,再触发从模式给CNT清零。或者是非阻塞的同时转移,CNT的值转移到CCR,同时0转移到CNT里。

  • 左上角的图:
    信号出现一个上升沿,CCR1=CNT,就是把CNT的值转运到CCR1里面去,这是输入捕获自动执行的;然后CNT=0,清零计数器,这是从模式自动执行的。然后在一个周期之内,CNT在标准时钟的驱动下,不断自增,并且由于之前清零过了,所以CNT就是从上升沿开始,从0开始计数,一直++,直到下一次上升沿来临,然后执行相同的操作,CCR1=CNT,CNT=0。第二次捕获的时候,CNT就是从第一个上升沿到第二个上升沿的计数值,这个计数值就自动放在CCR1中,然后下一个周期,继续同样的过程,CNT从0开始自增,直到下一个上升沿,这时CCR1刷新为第二个周期的计数值,然后不断重复这个过程。所以当这个电路工作时,CCR1的值始终保持为最新一个周期的计数值,这里的CCR1的值就是N,然后fc/N就是信号的频率。所以当我们想要读取信号的频率时,只需要读取CCR1得到N,再计算fc/N,就行了。当不需要读取的时候,整个电路全自动的测量,不需要占用任何软件资源。

注意事项:CNT的值是有上限的,ARR一般设置为最大65535,则CNT最大也只能计65535个数,如果信号频率太低,CNT计数值可能会溢出。还有就是从模式的触发源选择,只有TI1FP1和TI2FP2,没有TI3和TI4的信号,所以如果想使用从模式自动清零CNT,就只能用通道1和通道2,对于通道3和通道4就只能开启捕获中断,在中断里手动清零了。

1.6 PWMI基本结构

image.png
PWMI模式使用两个通道同时捕获一个引脚,可以同时测量周期和占空比。
TI1FP1配置上升沿触发,触发捕获和清零CNT,正常地捕获周期。
TI1FP2配置下降沿触发,通过交叉通道,去触发通道2的捕获单元。
左上角图:最开始上升沿,CCR1捕获,同时清零CNT,之后CNT一直++,然后在下降沿这个时刻,触发CCR2捕获,所以这时CCR2的值就是CNT从第一个上升沿到第一个下降沿的计数值,就是高电平期间的计数值,CCR2捕获,并不触发CNT清零,所以CNT继续++,直到下一次上升沿,CCR1捕获周期,CNT清零。
这样执行之后,CCR2是高电平期间的CNT值,CCR1是一整个周期的计数值。
占空比=CCR2/CCR1,这就是PWMI模式,使用两个通道来捕获频率和占空比的思路。可以两个通道同时捕获第一个引脚的输入,这样通道2的前面一部分就没有用到(滤波器、边沿检测和极性选择),也可以配置两个通道同时捕获第二个引脚的输入,这样就是使用TI2FP1和TI2FP2这两个引脚了。

2. 输入捕获库函数及代码

2.1 输入捕获库函数

// 单独写入PSC的函数
void TIM_PrescalerConfig(TIM_TypeDef* TIMx, uint16_t Prescaler, uint16_t TIM_PSCReloadMode);

// 用结构体配置输入捕获单元的函数,4个通道共用一个函数。
// 该函数是单一的配置一个通道
void TIM_ICInit(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct);

// PWMI模式,可以快速配置两个通道
void TIM_PWMIConfig(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct);

// 给输入捕获结构体赋一个初始值
void TIM_ICStructInit(TIM_ICInitTypeDef* TIM_ICInitStruct);

// 选择输入触发源TRGI,从模式的触发源选择
void TIM_SelectInputTrigger(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource);

// 选择主模式输出的触发源TRGO
void TIM_SelectOutputTrigger(TIM_TypeDef* TIMx, uint16_t TIM_TRGOSource);

// 选择从模式
void TIM_SelectSlaveMode(TIM_TypeDef* TIMx, uint16_t TIM_SlaveMode);

// 分别单独配置通道1、2、3、4的分频器
void TIM_SetIC1Prescaler(TIM_TypeDef* TIMx, uint16_t TIM_ICPSC);
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);

// 分别读取4个通道的CCR,和TIM_SetCompare是对应的,读写的都是CCR寄存器
// 输出比较模式下,CCR是只写的,要用SetCompare写入,
// 输入捕获模式下,CCR是只读的,要用GetCapture读出,
uint16_t TIM_GetCapture1(TIM_TypeDef* TIMx);
uint16_t TIM_GetCapture2(TIM_TypeDef* TIMx);
uint16_t TIM_GetCapture3(TIM_TypeDef* TIMx);
uint16_t TIM_GetCapture4(TIM_TypeDef* TIMx);

2.2 6-6 输入捕获模式测频率

2.2.1 硬件连接

实现功能:测周法测频率。先用PWM模块,在PA0引脚输出一个频率和占空比可调的PWM波形。测量波形的输入口是PA6,PA0通过导线输入到PA6引脚,这样就能测量自己PWM模块产生波形的频率了。PA6是TIM3的通道1,通道1通过输入捕获模块,测量得到频率。在主循环里,通过不断修改PWM波形的PSC和CCR值来更改频率,并在OLED上输出频率值。
image.png

2.2.2 硬件运行结果

6-6输入补捕获模式测频率.jpg

2.2.3 代码流程

  1. 配置PWM-------在PA0引脚输出一个频率和占空比可调的PWM波形

    1. RCC开启时钟,GPIO和TIM2时钟
    2. 配置GPIO,为复用推挽输出
    3. 配置时基单元,为TIM2时钟,内部时钟源。设置ARR = 100 - 1
    4. 配置输出比较单元
    5. 启动定时器--------以上是PWM初始化部分
    6. 调用TIM_SetCompare1函数设置CCR
    7. 调用TIM_PrescalerConfig函数设置PSC。
  2. 输入捕获单元IC-------PA6通过测量PWM波形的频率

    1. RCC开启时钟,把GPIO和TIM3的时钟打开。因为是PA6引脚所以是TIM3_CH1。
    2. GPIO初始化,把GPIO配置成输入模式,一般选择上拉输入或浮空输入模式
    3. 配置时基单元,让CNT计数器在内部时钟的驱动下自增运行。PSC = 72 - 1;ARR = 65536 - 1,设置为最大。
    4. 配置输入捕获单元,包括滤波器、极性、直连通道还是交叉通道、分频器等参数,用结构体实现
    5. 选择从模式的触发源,触发源选择为TI1FP1,调用一个库函数,给一个参数实现
    6. 选择触发之后执行的操作,执行Reset操作,调用库函数实现
    7. 以上都配置好之后,调用TIM_Cmd函数,开启定时器

    当需要读取最新一个周期的频率时,直接读取CCR寄存器,按照fc/N计算

  3. 获取频率

    1. 由于PSC = 72 - 1;因此测周法的标准频率 fc = 72M / (PSC + 1) = 1MHz,N =CCR的值,通过TIM_GetCapture1函数获取。频率 fx = fc / N。
  4. main

    1. 通过更改PWM波形的PSC和CCR的值,来不断测频率
    2. 若设PWM的PSC = 720 - 1, CCR = 50。由于ARR = 100 - 1,所以Freq = 72M / (PSC + 1)(ARR + 1) = 1000Hz, Duty = CCR / (ARR + 1) = 50%
    3. 测周法:fc=72M/(PSC+1),Freq=fc / N = fc / CCR

2.2.4 代码

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

void PWM_Init(void)
{
	// 打开时钟、选择内部时钟、
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
	
	// 初始化PA0引脚
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
	/*
	// 重映射
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
	GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2,ENABLE);//将PA0换到PA15
	GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);//解除调试端口的复用
	*/
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;// 复用推挽输出
	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;
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up ;
	TIM_TimeBaseInitStructure.TIM_Period = 100-1;  // ARR
	TIM_TimeBaseInitStructure.TIM_Prescaler = 720-1; // PSC
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
	
	// 初始化输出比较单元
	
	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
	TIM_OC1Init(TIM2, &TIM_OCInitStructure);
	
	// 启动定时器
	TIM_Cmd(TIM2, ENABLE);
}

// 改变通道1的占空比
void PWM_SetCompare1(uint16_t Compare)
{
	TIM_SetCompare1(TIM2, Compare);
}

// 写入PSC,改变频率
void PWM_SetPrescaler(uint16_t Prescaler)
{
	TIM_PrescalerConfig(TIM2,Prescaler,TIM_PSCReloadMode_Immediate);
}
  • IC.c
#include "stm32f10x.h"                  // Device header
// 输入捕获的代码

void IC_Init(void)
{
	/*
	1. RCC开启时钟,把GPIO和TIM的时钟打开
	2. GPIO初始化,把GPIO配置成输入模式,
	   一般选择上拉输入或浮空输入模式
	3. 配置时基单元,让CNT计数器在内部时钟的驱动下自增运行
	4. 配置输入捕获单元,包括滤波器、极性、
	   直连通道还是交叉通道、分频器等参数,用结构体实现
	5. 选择从模式的触发源,触发源选择为TI1FP1,
	   调用一个库函数,给一个参数实现
	6. 选择触发之后执行的操作,执行Reset操作,调用库函数实现
	7. 以上都配置好之后,调用TIM_Cmd函数,开启定时器
	当需要读取最新一个周期的频率时,直接读取CCR寄存器,按照fc/N计算
	
	*/
	
	// 打开时钟、选择内部时钟
	// 用TIM3_CH1,初始化PA6引脚
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
	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);
	// 该值决定了测周法的标准频率fc,72M/预分频,
	// 就是计数器自增的频率,就是计数标准频率。在这里标准频率就是72M/72=1MHz,
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up ;
	TIM_TimeBaseInitStructure.TIM_Period = 65536-1;  // ARR
	TIM_TimeBaseInitStructure.TIM_Prescaler = 72-1; // PSC,
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);
	
    // 初始化输入捕获单元
	TIM_ICInitTypeDef TIM_ICInitStructure;
	TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;// 选择通道
	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);
	
	// 配置TRGI的触发源为TI1FP1
	TIM_SelectInputTrigger(TIM3,TIM_TS_TI1FP1);
	
	// 配置从模式为Reset
	TIM_SelectSlaveMode(TIM3,TIM_SlaveMode_Reset);
	
	// 调用TIM_Cmd函数,开启定时器
	TIM_Cmd(TIM3,ENABLE);
	
}

// 想要查看频率,需要读取CCR进行计算,
uint32_t IC_GetFreq(void)
{
	// fc = 72M / (PSC + 1) = 1MHz
	// fx = fc / N
	// N 就是读取CCR的值
	return 1000000 / TIM_GetCapture1(TIM3);
	
}
  • main.c
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "PWM.h"
#include "IC.h"


int main(void)
{
	
	OLED_Init();
	PWM_Init();
	IC_Init();
	
	OLED_ShowString(1,1,"Freq:00000Hz");
	
	// PWM模块已经将待测信号输出到PA0,PA0又通过导线,输入到PA6,
	// PA6是TIM3的通道1,通道1通过输入捕获模块,测量得到频率,在主循环里,不断刷新显示频率
	PWM_SetPrescaler(720-1);  // Freq = 72M / (PSC+1) / (ARR+1)
	// ARR + 1 = 100,则Freq = 72M / (PSC+1) / 100
	PWM_SetCompare1(50); // Duty = CCR / (ARR + 1) =CCR / 100
	// 输出频率为1KHz,占空比=50%
	
	
	while(1)
	{	
		OLED_ShowNum(1, 6, IC_GetFreq(), 5);		
	}
}

2.3 6-7PWMI模式测频率占空比

2.3.1 硬件连接

通过PWMI同时测频率和占空比
image.png

2.3.2 硬件运行结果

image.png

2.3.3 代码实现流程

  1. 配置PWM

    1. RCC开启时钟,GPIO和TIM2时钟
    2. 配置GPIO,为复用推挽输出
    3. 配置时基单元,为TIM2时钟,内部时钟源。设置ARR = 100 - 1
    4. 配置输出比较单元
    5. 启动定时器--------以上是PWM初始化部分
    6. 调用TIM_SetCompare1函数设置CCR
    7. 调用TIM_PrescalerConfig函数设置PSC
  2. 输入捕获单元IC

    1. RCC开启时钟,把GPIO和TIM3的时钟打开。因为是PA6引脚所以是TIM3_CH1。
    2. GPIO初始化,把GPIO配置成输入模式,一般选择上拉输入或浮空输入模式
    3. 配置时基单元,让CNT计数器在内部时钟的驱动下自增运行。PSC = 72 - 1;ARR = 65536 - 1,设置为最大。
    4. 配置PWMI,包括滤波器、极性、直连通道还是交叉通道、分频器等参数,用结构体实现。选择上升沿触发,捕获CCR1,另一个通道自动配置为下降沿触发,捕获CCR2。
    5. 选择从模式的触发源,触发源选择为TI1FP1,调用一个库函数,给一个参数实现
    6. 选择触发之后执行的操作,执行Reset操作,调用库函数实现
    7. 以上都配置好之后,调用TIM_Cmd函数,开启定时器

    当需要读取最新一个周期的频率时,直接读取CCR寄存器,按照fc/N计算

  3. 获取频率和占空比

    1. 由于PSC = 72 - 1,因此测周法的标准频率 fc = 72M / (PSC + 1) = 1MHz。N =CCR的值,通过TIM_GetCapture1函数获取。频率 fx = fc / N。
    2. 占空比=CCR2 / CCR1
  4. main

    1. 更改PWM波形的PSC和CCR的值
    2. OLED显示频率和占空比,频率 fx = fc / N,占空比=CCR2 / CCR1。

2.3.4 代码

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

/**
  * 函    数:PWM初始化
  * 参    数:无
  * 返 回 值:无
  */
void PWM_Init(void)
{
	/*开启时钟*/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);			//开启TIM2的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);			//开启GPIOA的时钟
	
	/*GPIO重映射*/
//	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);			//开启AFIO的时钟,重映射必须先开启AFIO的时钟
//	GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2, ENABLE);			//将TIM2的引脚部分重映射,具体的映射方案需查看参考手册
//	GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);		//将JTAG引脚失能,作为普通GPIO引脚使用
	
	/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;		//GPIO_Pin_15;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);							//将PA0引脚初始化为复用推挽输出	
																	//受外设控制的引脚,均需要配置为复用模式		
	
	/*配置时钟源*/
	TIM_InternalClockConfig(TIM2);		//选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟
	
	/*时基单元初始化*/
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;				//定义结构体变量
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;     //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
	TIM_TimeBaseInitStructure.TIM_Period = 100 - 1;					//计数周期,即ARR的值
	TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1;				//预分频器,即PSC的值
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;            //重复计数器,高级定时器才会用到
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);             //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元
	
	/*输出比较初始化*/
	TIM_OCInitTypeDef TIM_OCInitStructure;							//定义结构体变量
	TIM_OCStructInit(&TIM_OCInitStructure);							//结构体初始化,若结构体没有完整赋值
																	//则最好执行此函数,给结构体所有成员都赋一个默认值
																	//避免结构体初值不确定的问题
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;				//输出比较模式,选择PWM模式1
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;		//输出极性,选择为高,若选择极性为低,则输出高低电平取反
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;	//输出使能
	TIM_OCInitStructure.TIM_Pulse = 0;								//初始的CCR值
	TIM_OC1Init(TIM2, &TIM_OCInitStructure);						//将结构体变量交给TIM_OC1Init,配置TIM2的输出比较通道1
	
	/*TIM使能*/
	TIM_Cmd(TIM2, ENABLE);			//使能TIM2,定时器开始运行
}

/**
  * 函    数:PWM设置CCR
  * 参    数:Compare 要写入的CCR的值,范围:0~100
  * 返 回 值:无
  * 注意事项:CCR和ARR共同决定占空比,此函数仅设置CCR的值,并不直接是占空比
  *           占空比Duty = CCR / (ARR + 1)
  */
void PWM_SetCompare1(uint16_t Compare)
{
	TIM_SetCompare1(TIM2, Compare);		//设置CCR1的值
}

/**
  * 函    数:PWM设置PSC
  * 参    数:Prescaler 要写入的PSC的值,范围:0~65535
  * 返 回 值:无
  * 注意事项:PSC和ARR共同决定频率,此函数仅设置PSC的值,并不直接是频率
  *           频率Freq = CK_PSC / (PSC + 1) / (ARR + 1)
  */
void PWM_SetPrescaler(uint16_t Prescaler)
{
	TIM_PrescalerConfig(TIM2, Prescaler, TIM_PSCReloadMode_Immediate);		//设置PSC的值
}
  • IC.c
#include "stm32f10x.h"                  // Device header

/**
  * 函    数:输入捕获初始化
  * 参    数:无
  * 返 回 值:无
  */
void IC_Init(void)
{
	/*开启时钟*/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);			//开启TIM3的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);			//开启GPIOA的时钟
	
	/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);							//将PA6引脚初始化为上拉输入
	
	/*配置时钟源*/
	TIM_InternalClockConfig(TIM3);		//选择TIM3为内部时钟,若不调用此函数,TIM默认也为内部时钟
	
	/*时基单元初始化*/
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;				//定义结构体变量
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;     //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
	TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1;               //计数周期,即ARR的值
	TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;               //预分频器,即PSC的值
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;            //重复计数器,高级定时器才会用到
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);             //将结构体变量交给TIM_TimeBaseInit,配置TIM3的时基单元
	
	/*PWMI模式初始化*/
	TIM_ICInitTypeDef TIM_ICInitStructure;							//定义结构体变量
	TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;				//选择配置定时器通道1
	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_PWMIConfig,配置TIM3的输入捕获通道
																	//此函数同时会把另一个通道配置为相反的配置,实现PWMI模式

	/*选择触发源及从模式*/
	TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);					//触发源选择TI1FP1
	TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);					//从模式选择复位
																	//即TI1产生上升沿时,会触发CNT归零
	
	/*TIM使能*/
	TIM_Cmd(TIM3, ENABLE);			//使能TIM3,定时器开始运行
}

/**
  * 函    数:获取输入捕获的频率
  * 参    数:无
  * 返 回 值:捕获得到的频率
  */
uint32_t IC_GetFreq(void)
{
	return 1000000 / (TIM_GetCapture1(TIM3) + 1);		//测周法得到频率fx = fc / N,这里不执行+1的操作也可
}

/**
  * 函    数:获取输入捕获的占空比
  * 参    数:无
  * 返 回 值:捕获得到的占空比
  */
uint32_t IC_GetDuty(void)
{
	return (TIM_GetCapture2(TIM3) + 1) * 100 / (TIM_GetCapture1(TIM3) + 1);	//占空比Duty = CCR2 / CCR1 * 100,这里不执行+1的操作也可
}
  • main.c
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "PWM.h"
#include "IC.h"

int main(void)
{
	/*模块初始化*/
	OLED_Init();		//OLED初始化
	PWM_Init();			//PWM初始化
	IC_Init();			//输入捕获初始化
	
	/*显示静态字符串*/
	OLED_ShowString(1, 1, "Freq:00000Hz");		//1行1列显示字符串Freq:00000Hz
	OLED_ShowString(2, 1, "Duty:00%");			//2行1列显示字符串Duty:00%
	
	/*使用PWM模块提供输入捕获的测试信号*/
	PWM_SetPrescaler(720 - 1);					//PWM频率Freq = 72M / (PSC + 1) / 100
	PWM_SetCompare1(50);						//PWM占空比Duty = CCR / 100
	
	while (1)
	{
		OLED_ShowNum(1, 6, IC_GetFreq(), 5);	//不断刷新显示输入捕获测得的频率
		OLED_ShowNum(2, 6, IC_GetDuty(), 2);	//不断刷新显示输入捕获测得的占空比
	}
}

3. 编码器接口

使用定时器的编码器接口,再配合编码器,就可以测量旋转速度和旋转方向了。编码器测速一般应用于电机控制的项目上,使用PWM驱动电机,再使用编码器测量电机的速度,再用PID算法进行闭环控制。

3.1 编码器接口简介

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

3.2 正交编码器

image.png
image.png
正交编码器一般可以测量位置,或带有方向的速度值。两个信号输出引脚,一个是A相,一个是B相。

转的越快,方波的频率越高,因此方波的频率代表了速度。

当正转时,A相提前B相90度;反转时,A相滞后B相90度。

正交信号精度高,可抗噪声。

编码器接口的设计逻辑:首先将A相和B相的所有边沿作为计数器的计数时钟,出现边沿信号时,就计数自增或自减。是自增还是自减呢,计数的方向由另一相的状态来确定。当出现某个边沿时,我们判断另一相的高低电平,如果对应另一相的状态出现在上面的表里(正转),那就是正转,计数自增;反之,另一相的状态出现在下面的表里,就是反转,计数自减。

3.3 编码器接口基本结构

image.png

  • 编码器的输出部分就相当于从模式控制器,去控制CNT的计数时钟和计数方向。如果出现了边沿信号,并且对应另一相的状态为正转,则控制CNT自增,否则控制CNT自减。之前一直使用的72MHz内部时钟,和在时基单元初始化设置的计数方向,并不会使用,因为此时计数时钟和计数方向都处于编码器接口托管的状态,计数器的自增和自减受编码器控制。
  • 编码器接口通过预分频器控制CNT计数器的时钟,同时编码器接口还根据编码器的旋转方向,控制CNT的计数方向,编码器正转时,CNT自增,编码器反转时,CNT自减。ARR也是有效的,一般会设置ARR为65535,最大量程。
  • 这样利用补码的特性很容易得到负数,比如CNT初始为0,正转,CNT自增,0、1、2、3等等,反转,CNT自减,0下一个数就是65535,这里就需要做一个操作直接把这个16位的无符号数转换为16位的有符号数,根据补码的定义,这个65535对应-1,这样就可以得到负数。

3.4 工作模式

image.png
正转的状态都向上计数,反转的状态都向下计数。
TI1FP1和TI2FP2接的就是编码器的A、B相,在A相和B相的上升沿或下降沿触发计数,到底是向上计数还是向下计数取决于边沿信号发生的这个时刻,另一相的电平状态,也就是表里的相对信号的电平,TI1FP1对应TI2,TI2FP2对应TI1,就是另一相电平的意思。编码器还分为3种工作模式,分别仅在TI1计数、仅在TI2计数和TI1、TI2都计数。
image.png
A相上升沿、A相下降沿、B相上升沿、B相下降沿这四种状态都执行自增或自减,就是A相和B相的边沿都计数,那就对应第3种模式,TI1和TI2都计数。仅在一个边沿计数的模式(就是仅在A相上升沿、A相下降沿执行自增或自减,或者是仅在B相上升沿、B相下降沿执行自增或自减)就是对应第1种和第2种模式仅在TI1计数和仅在TI2计数。

3.5 实例(均不反相)

image.png
image.png

3.6 实例(TI1反相)

image.png
image.png

4. 编码器接口库函数及代码

4.1 编码器接口库函数

// 定时器编码器接口配置,参1:定时器,参2:编码器模式,参3:选择通道1和通道2的电平极性
void TIM_EncoderInterfaceConfig(TIM_TypeDef* TIMx, uint16_t TIM_EncoderMode,
                                uint16_t TIM_IC1Polarity, uint16_t TIM_IC2Polarity);

4.2 6-8 编码器接口测速

4.2.1 硬件连接

实现功能:通过定时器的编码器接口,实现自动计次。编码器接口就是用来自动给编码器进行计次的电路,如果每隔一段时间取一下计次值,就能得到编码器旋转的速度了。OLED上显示速度Speed。向右慢速旋转,速度为正,计次比较小,快速旋转,计次比较大;向左旋转,速度为负,慢速旋转,计次比较小,快速旋转,计次比较大。
image.png

4.2.2 代码实现流程

  1. 定时器初始化
    1. RCC开启时钟,TIM2时钟
    2. 内部时钟72MHz。
    3. 时基单元初始化,ARR = 10000 - 1, PSC = 7200 - 1。得Freq = 72M / (PSC+1) / (ARR+1) = 1
    4. 中断输出配置,清除更新标志位,开启更新中断
    5. NVIC 中断分组和NVIC配置
    6. TIM使能
  2. 编码器初始化
    1. RCC开启时钟,开启GPIO和定时器TIM3的时钟
    2. 配置GPIO,把PA6和PA7配置成输入模式(PA6和PA7是TIM3的CH1和CH2)
    3. 配置时基单元,预分频器,选择不分频,自动重装,一般给最大65535,只需要CNT执行计数
    4. 配置输入捕获单元,包含滤波器、极性
    5. 配置编码器接口模式,采用库函数
    6. 调用TIM_Cmd,启动定时器

电路初始化完成后,CNT就会随着编码器旋转而自增自减
测编码器的位置,则直接读出CNT的值;
测编码器的速度和方向,则需要每隔一段固定的闸门时间,取出一次CNT,再把CNT清零,即测频法测量速度。
编码器接口会托管时钟,编码器接口就是一个带方向控制的外部时钟。初始化时基单元计数方向也是被编码器接口托管的。

  1. main
    1. OLED显示速度
    2. 在定时器TIM2中,ARR = 10000 - 1, PSC = 7200 - 1,所以频率 = 72M / (PSC + 1) / (ARR + 1) = 1,即定时1s,将此作为闸门时间T(闸门时间T = 1s)。在定时器定时1s的中断中对编码器的上升沿计次得N(CCR),则编码器的频率fx = N / 1=N,该频率即编码器的速度。

4.2.3 代码

  • Timer.c
#include "stm32f10x.h"                  // Device header

/**
  * 函    数:定时中断初始化
  * 参    数:无
  * 返 回 值:无
  */
void Timer_Init(void)
{
	/*开启时钟*/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);			//开启TIM2的时钟
	
	/*配置时钟源*/
	TIM_InternalClockConfig(TIM2);		//选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟
	
	/*时基单元初始化*/
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;				//定义结构体变量
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;		//时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;	//计数器模式,选择向上计数
	TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1;				//计数周期,即ARR的值
	TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1;				//预分频器,即PSC的值
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;			//重复计数器,高级定时器才会用到
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);				//将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元	
	
	/*中断输出配置*/
	TIM_ClearFlag(TIM2, TIM_FLAG_Update);						//清除定时器更新标志位
																//TIM_TimeBaseInit函数末尾,手动产生了更新事件
																//若不清除此标志位,则开启中断后,会立刻进入一次中断
																//如果不介意此问题,则不清除此标志位也可
	
	TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);					//开启TIM2的更新中断
	
	/*NVIC中断分组*/
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);				//配置NVIC为分组2
																//即抢占优先级范围:0~3,响应优先级范围:0~3
																//此分组配置在整个工程中仅需调用一次
																//若有多个中断,可以把此代码放在main函数内,while循环之前
																//若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置
	
	/*NVIC配置*/
	NVIC_InitTypeDef NVIC_InitStructure;						//定义结构体变量
	NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;				//选择配置NVIC的TIM2线
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;				//指定NVIC线路使能
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;	//指定NVIC线路的抢占优先级为2
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;			//指定NVIC线路的响应优先级为1
	NVIC_Init(&NVIC_InitStructure);								//将结构体变量交给NVIC_Init,配置NVIC外设
	
	/*TIM使能*/
	TIM_Cmd(TIM2, ENABLE);			//使能TIM2,定时器开始运行
}

  • Encoder.c
#include "stm32f10x.h"                  // Device header

/**
  * 函    数:编码器初始化
  * 参    数:无
  * 返 回 值:无
  */
void Encoder_Init(void)
{
	/*开启时钟*/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);			//开启TIM3的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);			//开启GPIOA的时钟
	
	/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);							//将PA6和PA7引脚初始化为上拉输入
	
	/*时基单元初始化*/
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;				//定义结构体变量
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;     //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
	TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1;               //计数周期,即ARR的值
	TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 1;                //预分频器,即PSC的值
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;            //重复计数器,高级定时器才会用到
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);             //将结构体变量交给TIM_TimeBaseInit,配置TIM3的时基单元
	
	/*输入捕获初始化*/
	TIM_ICInitTypeDef TIM_ICInitStructure;							//定义结构体变量
	TIM_ICStructInit(&TIM_ICInitStructure);							//结构体初始化,若结构体没有完整赋值
																	//则最好执行此函数,给结构体所有成员都赋一个默认值
																	//避免结构体初值不确定的问题
	TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;				//选择配置定时器通道1
	TIM_ICInitStructure.TIM_ICFilter = 0xF;							//输入滤波器参数,可以过滤信号抖动
	TIM_ICInit(TIM3, &TIM_ICInitStructure);							//将结构体变量交给TIM_ICInit,配置TIM3的输入捕获通道
	TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;				//选择配置定时器通道2
	TIM_ICInitStructure.TIM_ICFilter = 0xF;							//输入滤波器参数,可以过滤信号抖动
	TIM_ICInit(TIM3, &TIM_ICInitStructure);							//将结构体变量交给TIM_ICInit,配置TIM3的输入捕获通道
	
	/*编码器接口配置*/
	TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);
																	//配置编码器模式以及两个输入通道是否反相
																	//注意此时参数的Rising和Falling已经不代表上升沿和下降沿了,而是代表是否反相
																	//此函数必须在输入捕获初始化之后进行,否则输入捕获的配置会覆盖此函数的部分配置
	
	/*TIM使能*/
	TIM_Cmd(TIM3, ENABLE);			//使能TIM3,定时器开始运行
}

/**
  * 函    数:获取编码器的增量值
  * 参    数:无
  * 返 回 值:自上此调用此函数后,编码器的增量值
  */
int16_t Encoder_Get(void)
{
	/*使用Temp变量作为中继,目的是返回CNT后将其清零*/
	int16_t Temp;
	Temp = TIM_GetCounter(TIM3);
	TIM_SetCounter(TIM3, 0);
	return Temp;
}
  • main.c
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Timer.h"
#include "Encoder.h"

int16_t Speed;			//定义速度变量

int main(void)
{
	/*模块初始化*/
	OLED_Init();		//OLED初始化
	Timer_Init();		//定时器初始化
	Encoder_Init();		//编码器初始化
	
	/*显示静态字符串*/
	OLED_ShowString(1, 1, "Speed:");		//1行1列显示字符串Speed:
	
	while (1)
	{
		OLED_ShowSignedNum(1, 7, Speed, 5);	//不断刷新显示编码器测得的最新速度
	}
}

/**
  * 函    数:TIM2中断函数
  * 参    数:无
  * 返 回 值:无
  * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
  *           函数名为预留的指定名称,可以从启动文件复制
  *           请确保函数名正确,不能有任何差异,否则中断函数将不能进入
  */
void TIM2_IRQHandler(void)
{
	if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)		//判断是否是TIM2的更新事件触发的中断
	{
		Speed = Encoder_Get();								//每隔固定时间段读取一次编码器计数增量值,即为速度值
		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);			//清除TIM2更新事件的中断标志位
															//中断标志位必须清除
															//否则中断将连续不断地触发,导致主程序卡死
	}
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

曹操的五色棋布阵 - 工厂方法模式

定场诗 “兵无常势,水无常形,能因敌变化而取胜者,谓之神。” 在三国的战场上,兵法如棋,布阵如画。曹操的五色棋布阵,不正是今日软件设计中工厂方法模式的绝妙写照吗?让我们从这个神奇的布阵之…

MSPM0G3507——串口0从数据线传输变为IO口传输

默认的跳线帽时这样的,这样时是数据线传输 需要改成这样,即可用IO口进行数据传输

实验六 图像的傅立叶变换

一.实验目的 1了解图像变换的意义和手段; 2熟悉傅立叶变换的基本性质; 3熟练掌握FFT变换方法及应用; 4通过实验了解二维频谱的分布特点; 5通过本实验掌握利用MATLAB编程实现数字图像的傅立叶变换。 6评价人眼对图…

Mac 系统如何将搜狗输入法设置为默认输入法

Mac 系统默认将自带的ABC输入法作为默认输入法,很不方便中文输入,想设置搜狗输入法为默认输入法如何设置呢?具体步骤如下: 1、打开:系统设置——键盘——文字输入,点击设置 2、点击左下角的 3、选择 其他…

52-5 内网代理2 - LCX端口转发(不推荐使用LCX)

环境搭建: 本地开3台虚拟机:kali(必须)、windows2012与2008 (可换成其他windows虚拟机) kali - 网络配置成桥接模式 windows2012 - 设置两个网卡,NAT与桥接模式 注意:windows2012要关闭防火墙,要不然其他主机ping不通 关闭防火墙后再开启远程桌面连接 windwos20…

Java项目:基于SSM框架实现的德云社票务管理系统【ssm+B/S架构+源码+数据库+开题报告+毕业论文】

一、项目简介 本项目是一套基于SSM框架实现的德云社票务管理系统 包含:项目源码、数据库脚本等,该项目附带全部源码可作为毕设使用。 项目都经过严格调试,eclipse或者idea 确保可以运行! 该系统功能完善、界面美观、操作简单、功…

Python 学习中什么是字典,如何操作字典?

什么是字典 字典(Dictionary)是Python中的一种内置数据结构,用于存储键值对(key-value pair)。字典的特点是通过键来快速查找值,键必须是唯一的,而值可以是任何数据类型。字典在其他编程语言中…

游戏AI的创造思路-技术基础-遗传算法

遗传算法,选对了遗传算子,那就是优秀的继承者,选错了,那就是传说在祸害遗千年~~~~~ 目录 1. 定义 2. 发展历史 3. 遗传算法的基本原理和流程 3.1. 基本原理 3.1.1.基本原理 3.1.2. 算法流程 3.1.3. 关键要素 3.2. 函数和方…

栈和队列---循环队列

1.循环队列的出现 (1)上面的这个就是一个普通的数据的入队和出队的过程我们正常情况下去实现这个入队和出队的过程,就是这个数据从这个队尾进入,从队头离开,但是这个加入的时候肯定是没有其他的问题的,直接…

为什么固定尺寸 AdSense 广告依旧会出现并非指定的尺寸广告?

经常在网站上投放谷歌 AdSense广告的站长应该都碰到过,明明投放的是固定尺寸的广告位里旧会出现并非指定尺寸的AdSense 广告,很诡异的感觉。其实这都是因为你的 AdSense 账号广告优化造成的,其中里面就包含了广告尺寸优化,只需要在…

盘点当下智能体应用开发的几种形态

现在多智能体系统开发的关注度越来越高了,不光在开发者的圈子热度很高,很多职场人士,甚至是小白也参与其中,因为现在的门槛越来越低了,尤其是,最近特别火的扣子(coze)和百度的appbui…

Sequelize 操作 MySQL 数据库

安装 npm install --save sequelize安装驱动程序: npm install --save mysql2连接到数据库 要连接到数据库,必须创建一个 Sequelize 实例. 这可以通过将连接参数分别传递到 Sequelize 构造函数或通过传递一个连接 URI 来完成: const {Sequelize} re…

【Java12】封装

封装(Encapsulation)是面向对象的三大特征之一(另两个是继承和多态),指的是将对象的状态信息隐藏在对象内部,不允许外部程序直接访问对象的内部信息,而是通过该类所提供的方法来实现对内部信息的…

[护网训练]原创应急响应靶机整理集合

前言 目前已经出了很多应急响应靶机了,有意愿的时间,或者正在准备国护的师傅,可以尝试着做一做已知的应急响应靶机。 关于后期: 后期的应急响应会偏向拓扑化,不再是单单一台机器,也会慢慢完善整体制度。…

《昇思25天学习打卡营第14天|onereal》

第14天学习内容如下: Diffusion扩散模型 本文基于Hugging Face:The Annotated Diffusion Model一文翻译迁移而来,同时参考了由浅入深了解Diffusion Model一文。 本教程在Jupyter Notebook上成功运行。如您下载本文档为Python文件&#xff0c…

Zabbix 配置grafana对接

zabbix对接grafana简介 Zabbix与Grafana对接可以实现更加丰富和美观的数据可视化,可以利用Grafana强大的可视化功能来展示Zabbix收集的数据。 Grafana 本身是提供了Zabbix的对接插件,开箱即用,安装好了之后点击 enable 一下就能启用。然后就…

深度学习中的Channel,通道数是什么?

参考文章: 直观理解深度学习的卷积操作,超赞!-CSDN博客​​​​​​如何理解卷积神经网络中的通道(channel)_神经网络通道数-CSDN博客 深度学习-卷积神经网络—卷积操作详细介绍_深度卷积的作用-CSDN博客 正文&…

护网在即,助力安服仔漏洞扫描~

整合了个漏扫系统,安服仔必备~ 使用场景 网前布防,漏洞扫描,资产梳理 使用方法: 启动虚拟机后运行命令: ./StartSystemScript.sh 输入密码attack 启动完成后浏览器打开网站: http://IP:5000 相关账户…

VSCode神仙插件——Codeium (AI编程助手)

1、安装&登录插件 安装过程中会让你登录Codeium账户,可以通过Google账户登录,或者可以注册一个Codeium账户(如果没有弹出让你登录账户的界面,可以等安装结束后在右下角找到登录的地方) 右下角显示如下图所示&#…

异常组成、作用、处理方式(3种)、异常方法、自定义异常

目录 异常的组成:运行异常与编译异常 两者区别:编译异常用来提醒程序员,运行异常大部分是由于参数传递错误导致 异常作用: 作用1:就是平时的报错,方便我们找到报错的来源 作用2:在方法内部…