第6章 TIM定时器

文章详细介绍了STM32定时器中断的原理和配置,包括基本定时器、通用定时器和高级定时器的差异,以及如何配置预分频器和计数器。此外,还讨论了输出比较和PWM的实现,特别提到了PWM模式下的频率和占空比计算。对于输入捕获,文章讲解了其工作原理和频率测量方法,包括测周法和测频法。最后,文章介绍了编码器接口的使用,包括正交编码器的工作模式和抗噪声特性。
摘要由CSDN通过智能技术生成

定时中断

定时中断的理论部分

TIM简介

  • TIM(Timer)定时器
  • 定时器可以对输入的时钟进行计数,并在计数值达到设定值时触发中断
  • 16位计数器、预分频器、自动重装寄存器的时基单元,在72MHz计数时钟下可以实现最大59.65s的定时
    这里计数器就是用来执行计数定时的一个寄存器,每来一个时钟,计数器加1;预分频器,可以对计数器的时钟进行分频,让这个计数更加灵活;自动重装寄存器就是计数的目标值,就是我想要计多少个时钟申请中断。这些寄存器构成了定时器最核心的部分,我们把这一块电路称为时基单元。
  • 不仅具备基本的定时中断功能,而且还包含内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等多种功能
  • 根据复杂度和应用场景分为了高级定时器、通用定时器、基本定时器三种类型

定时器类型

在这里插入图片描述

  • STM32F103C8T6定时器资源:TIM1、TIM2、TIM3、TIM4
    TIM1的总线和别的不一样。
基本定时器

在这里插入图片描述
基本定时器只能选择内部时钟,内部时钟的来源是RCC_TIMxCLK,这里的频率值一般都是系统的主频72MHz,所以通向时基单元的计数基准频率就是72M。

接着看预分频器,它可以对这个72MHz的计数时钟进行预分频,比如这个寄存器写0,那就是不分频,或者说是1分频,这时候输出频率 = 输入频率 = 72MHz,如果预分频器写1,那就是2分频,输出频率 = 输入频率 / 2 = 36MHz,以此类推。这个预分频器是16位的,所以最大值可以写65535,也就是65536分频,这就是预分频器,就是对输入的基准频率提前进行一个分频的操作。

然后是计数器,这个计数器可以对预分频后的计数时钟进行计数,计数时钟每来一个上升沿,计数器的值就加1,这个计数器也是16位的,所以里面的值可以从0一直加到65535,如果再加的话,计数器就会回到0重新开始,所以计数器的值在计时过程中会不断地自增运行,当自增运行到目标值时,产生中断,那就完成了定时的任务,所以现在还需要一个存储目标值的寄存器,那就是自动重装寄存器了。

自动重装寄存器也是16位的,它存的就是我们写入的计数目标,在运行的过程中,计数值不断自增,自动重装值时固定的目标,当计数值等于自动重装值时,也就是计时时间到了,那它就会产生一个中断信号,并且清零计数器,计数器自动开始下一次的计数计时。

像这种计数值等于自动重装值产生的中断呢,我们一般把它叫做“更新中断”。这个更新中断之后就会通往NVIC,我们再配置好NVIC的定时器通道,那定时器的更新中断就能够得到CPU的响应了。

通用定时器

在这里插入图片描述
对于通用定时器而言,这个计数器的计数模式就不止向上计数这一种了,通用定时器和高级定时器还支持向下计数模式和中央对齐模式。向下计数模式就是从重装值开始,向下自减,减到0之后,回到重装值同时申请中断,然后继续下一轮,依次循环,这就是向下计数。还有中央对齐的计数模式,就是从0开始,先向上自增,计到重装值,申请中断,然后再向下自减,减到0,再申请中断,然后继续下一轮,依次循环,这是中央对齐模式。

到了通用定时器这里,时钟源不仅可以选择内部的72MHz时钟,还可以选择外部时钟。

  • 第一个外部时钟就是来自TIMx_ETR引脚上的外部时钟,这个ETR(External)引脚的位置,可以参考一下引脚定义表,可以看到这里有TIM2_CH1_TER,意思就是这个TIM2的CH1和ETR都是复用在了这个位置,也就是PA0引脚,下面还有CH2、CH3、CH4和其他定时器的一些引脚,也都可以在这里找到。
    在这里插入图片描述
    那这里我们可以在这个TIM2的ETR引脚,也就是PA0上接一个外部方波时钟,然后配置一下内部的极性选择、边沿检测和预分频器电路,再配置一下输入滤波电路,这两块电路可以对外部时钟进行一定的整形,因为是外部引脚的时钟,所以难免会有毛刺,那这些电路就可以对输入的波形进行滤波,同时也可以选择一下极性和预分频器,最后,滤波后的信号,兵分两路,上面一路ETRF进入触发控制器,紧跟着就可以选择作为时基单元的时钟了。如果你想在ETR外部引脚提供时钟,或者想对ETR时钟进行计数,把这个定时器当作计数器来用的话,那就可以配置这一路的电路,在STM32中,这一路也叫做“外部时钟模式2”,除了外部ETR引脚可以提供时钟外,下面这里还有一路可以提供时钟,就是TRGI(Trigger In),这一路从名字上来看的话,它主要是用作触发输入来使用的,这个触发输入可以触发定时器的从模式,关于触发输入和从模式,后续会讲。

当这个TRGI当作外部时钟来使用的时候,这一路就叫做“外部时钟模式1”,那通过这一路的外部时钟都有哪些呢,往左看,第一个,就是ETR引脚的信号,这里ETR引脚既可以通过上面这一路进来当作时钟,又可以通过下面这一路进来当作时钟,两种情况对于时钟输入而言是等价的,只不过下面这一路输入会占用触发输入的通道而已。然后第二个,就是ITR信号,这一部分的时钟信号是来自其他定时器的

(建议这部分直接看视频)

  • 对于时钟输入而言,最常用的还是内部的72MHz的时钟,如果要使用外部时钟,首选ETR引脚外部时钟模式2的输入,这一路最简单、最直接。
高级定时器

定时中断基本结构

在这里插入图片描述

时序图

预分频器时序

在这里插入图片描述

  • 第一行是CK_PSC,预分频器的输入时钟,选内部时钟的话一般是72MHz,然后这个时钟在不断地运行。
  • 下面这个CNT_EN,计数器使能,高电平计数器正常运行,低电平计数器停止。
  • 再下面是CK_CNT,计数器时钟,它既是预分频器的时钟输出,也是计数器的时钟输入。这里可以看到,开始时,计数器未使能,计数器时钟不运行,然后使能后,前半段,预分频器系数为1,计时器的时钟等于预分频器前的时钟,后半段,预分频器系数变为2了,计数器的时钟也就变为预分频器前时钟的一半了。在计数器时钟的驱动下,下面的计数器寄存器也跟随时钟的上升沿不断自增,在中间的这个位置FC之后,计数值变为0了。这里虽然没写,但是可以推断出ARR自动重装值就是FC,当计数值计到和重装值相等,并且下一个时钟来临时,计数值才清零,同时,下面这里产生一个更新事件,这就是一个计数周期的工作流程。
  • 然后下面还有三行时序,这三行时序是什么意思呢,这里描述的其实是这个预分频寄存器的一种缓冲机制,也就是这个预分频寄存器实际上是有两个,一个是预分频控制寄存器,供我们读写用的,它并不直接决定分频系数,另外还有一个缓冲寄存器或者说是影子寄存器,这两种是一个说法,这个缓冲寄存器才是真正起作用的寄存器,比如在某个时刻,把预分频寄存器由0改成了1,如果在此时,立刻改变时钟的分频系数,那么就会导致,在一个计数周期内,前半部分和后半部分的频率不一样,这里计数计到一半,计数频率突然就会改变了。这虽然一般并不会有什么问题,但是STM32的定时器比较严谨,设计了这个缓冲寄存器,这样,当我计数计到一半的时候改变了分频值,这个变化不会立刻生效,而是会等到本次计数周期结束时,产生了更新事件,预分频寄存器的值才会被传递到缓冲寄存器里面去,才会生效。
  • 最后这里也是,(看预分频计数器),预分频器内部实际上也是靠计数来分频的,当预分频值为0时,计数器就一直为0,直接输出原频率,当预分频值为1时,计数器就0、1、0、1这样计数,在回到0时,输出一个脉冲,这样输出频率就是输入频率的2分频。预分频器的值和实际的分频系数之间有一个数的偏移。
计数器时序

在这里插入图片描述
上面这个图是计数器时序图,内部时钟分频因子为2,就是分频系数为2。

  • 第一行是内部时钟72MHz。
  • 第二行是时钟使能,高电平启动。
  • 第三行是计数器时钟,因为分频系数为2,所以这个频率是上面第一行这个除2,然后计数器在这个时钟的每个上升沿自增,当增到0036的时候,发生溢出,再来一个上升沿的时候,计数器清零,计数器溢出,产生一个更新事件的脉冲,另外还会置一个更新中断标志位UIF,这个标志位只要置1了,就会去申请中断,然后中断响应后,需要在中断程序中手动清零。
计数器有预装时序

在这里插入图片描述

  • 通过设置这个ARPE位,就可以选择是否使用预装功能。
  • 在计数的中途,突然把计数目标由F5改成了36,可以看到下面有个影子寄存器,这个影子寄存器才是真正起作用的,它还是F5,所以现在计数的目标还是计到F5,产生更新事件,同时要更改的36才被传递到影子寄存器,在下一个计数周期这个更改的36才有效。
计数器无预装时序

在这里插入图片描述
在图中,计数器正在进行自增计数,突然更改自动加载寄存器,就是自动重装寄存器,由FF改成了36,那计数值的目标值就由FF变成了36,所以这里计到36之后,就直接更新,开始下一轮计数。

RCC时钟树

这个时钟树,就是STM32中用来产生和配置时钟,并且把配置好的时钟发送到各个外设的系统,时钟是所有外设运行的基础,所以时钟也是最先需要配置的东西。

  • 程序中主函数之前,还会执行一个SystemInit函数,这个函数就是用来配置这个时钟树的,这个结构看上去挺复杂的,配置起来还是比较麻烦的,不过好在ST公司已经帮我们写好了配置这个时钟树的SystemInit函数。

(建议看视频)

定时中断(内部时钟)的代码部分

初始化

  • 第一步,RCC开启时钟,打开后,定时器的基准时钟和整个外设的工作时钟都会同时打开了
  • 第二步,选择时基单元的时钟源,对于定时中断,我们就选择内部时钟源
  • 第三步,配置时基单元,包括预分频器、自动重装器、计数模式等等,这些参数用一个结构体就可以配置好了
  • 第四步,配置输出中断控制,允许更新中断输出到NVIC
  • 第五步,配置NVIC,在NVIC中打开定时器中断的通道,并分配一个优先级
  • 第六步,就是运行控制了

整个模块配置完成后,我们还需要使能一下计数器,要不然计数器是不会运行的,当定时器使能后,计数器就开始计数了,当计数器更新后,触发中断,最后再写一个定时器的中断函数,这样中断函数没个一段时间就能自动执行一次了

void Timer_Init(void)
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
	
	/*@brief  Configures the TIMx internal Clock
	         配置定时器内部时钟*/
	 这样TIM2的时基单元就由内部时钟来驱动了
	TIM_InternalClockConfig(TIM2);
	
	/*初始化时基初始化结构体*/
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	/*时钟分频,设置定时器时钟CK_INT频率与数字滤波器采样时钟频率分配
	比,和时基单元关系不大,可以随便配一个,基本定时器没有此功能*/
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
	/*定时器计数方式,可是在为向上计数、向下计数、以及三种中心对齐模式。
	基本定时器只能是向上计数,即TIMx_CNT只能从0开始递增,并且无需初始化*/
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
	/*定时器周期,实际就是设定自动重装载寄存器的值,在事件生成时更新到影子
	寄存器。可设置范围为0至65535*/
	TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1;//自动重装载寄存器ARR
	/*定时器预分频器设置,时钟源经该预分频器才是定时器时钟,它设定
	TIMx_PSC寄存器的值,可设置范围为0至65535,实现1至65536分频*/
	TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1;
	/*重复计数器,属于高级控制寄存器专用寄存器位,利用它可以非常容易
	控制输出PWM的个数,基本定时器不用设置,这里也不需要用,直接给0就好了*/
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
	//初始化定时器
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
	
	//清除计数器中断标志位
	如果不加这句,一旦初始化完了,更新中断就会立刻进入,
	也就是会一上电,就进中断
	TIM_ClearFlag(TIM2, TIM_FLAG_Update);
	//开启计数器中断
	TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
	
	
	//中断优先级配置,设置中断组为2
	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);
}

中断函数

这个中断函数可以直接放在主函数里面,中断函数可以放在使用它的地方。

void TIM2_IRQHandler(void)
{
	//老规矩,检查中断标志位
	//检查的是TIM2,更新中断标志位
	if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
	{
		Num ++;
		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);//清除标志位
	}
}
TIM_GetCounter(TIM2)

这个函数可以直接看CNT计数器值的变化情况

定时中断(外部时钟)的代码部分

初始化

void Timer_Init(void)
{
	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_ExtTRGPolarity_Inverted是反向,就是低电平或下降沿有效,
	TIM_ExtTRGPolarity_NonInverted就是不反向,就是高电平或上升沿有效
	这个根据需求来
	第四个参数是外部触发滤波器,这个值必须是0x00到0x0F之间的一个值
	如果不用滤波器,可以写0x0F
	但如果你们的传感器在使用的时候,遮挡一次CNT的值胡乱增加,
	可以在这里使用滤波器,能有效提高稳定性*/
	TIM_ETRClockMode2Config(TIM2, TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_NonInverted, 0x0F);
	
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInitStructure.TIM_Period = 10 - 1;
	TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 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_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);
}

输出比较

输出比较理论部分

输出比较简介

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

CNT就是时基单元里面的计数器,CCR就是捕获/比较寄存器,CC就是捕获/比较的意思,R时Register,寄存器的意思,这个捕获/比较寄存器是输入捕获和输出比较共用的,当使用输入捕获时,它就是捕获寄存器,当使用输出比较时,它就是比较寄存器,那在输出比较这里,这块电路会比较CNT和CCR的值,CNT计数自增,CCR是我们给定的一个值,当CNT大于CCR、小于CCR或者等于CCR时,这里输出就会对应的置1、置0、置1、置0,这样就可以输出一个电平不断跳变的PWM波形了,这就是输出比较的基本功能

  • 每个高级定时器和通用定时器都拥有4个输出比较通道
    这四个通道有各自的CCR寄存器,但是它们是共用一个CNT计数器的
  • 高级定时器的前3个通道额外拥有死区生成和互补输出的功能
    这个是用于驱动三相无刷电机的

PWM简介

  • PWM(Pulse Width Modulation)脉冲宽度调制
  • 在具有惯性的系统中,可以通过对一系列脉冲的宽度进行调制,来等效地获得所需要的模拟参量,常应用于电机控速等领域
  • PWM参数:
    在这里插入图片描述
    在这里插入图片描述
    占空比决定了PWM等效出来的模拟电压的大小,占空比越大,那等效的模拟电压就越趋近于高电平,占空比越小,那等效的模拟电压就趋近于低电平。

使用PWM波形,就可以在数字系统等效输出模拟量。

输出比较通道(高级)

在这里插入图片描述
这个图是高级定时器前三个通道的输出比较电路

输出比较通道(通用)

在这里插入图片描述
这个图是通用定时器的输出比较电路,包括高级定时器的第四个通道和这个结构也基本一样。
在这个图里,左边就是CNT计数器和CCR1第一路的捕获/比较寄存器,它俩进行比较,当CNT > CCR1,或者CNT = CCR1时,就会给这个输出模式控制器传一个信号,然后输出模式控制器就会改变它输出OC1REF的高低电平,REF信号实际上就是指这里信号的高低电平,这个REF是reference的缩写,意思是参考信号,然后上面还有个ETRF输入,这个是定时器的一个小功能。

接着这个REF信号可以前往主模式控制器,你可以把这个REF映射到主模式的TRGO输出上去,不过REF的主要去向还是下面这一路,通过下面这路,到达一个极性选择,给这个寄存器写0,信号就会往上走,就是信号电平不翻转,进来啥样,出去还是啥样,写1的话,信号就会往下走,就是信号通过一个非门取反,那输出的信号就是输入信号高低电平反转的信号,这就是极性选择,就是选择是不是要把高低电平反转一下。

那接着就是输出使能电路了,选择要不要输出,最后就是OC1引脚,这个引脚就是CH1通道的引脚,在引脚定义表里就可以知道具体是哪GPIO口了。

输出比较模式

也就是上面那个输出模式控制器里面的执行逻辑
在这里插入图片描述

  • 第一个模式是冻结,描述是CNT = CCR时,REF保持为原状态,CNT = CCR时维持原状态,那其实这个CNT和CCR就根本没有用,所以也可以理解成CNT和CCR无效,REF保持为原状态,那这个模式也比较简单,它根本就不管CNT谁大谁小,直接REF保持不变。

这有什么用呢,比如你正在输出PWM波,突然想暂停一会儿输出,就可以设置成这个模式,一旦切换为冻结模式后,输出就暂停了,并且高低电平也维持为暂停时刻的状态,保持不变,这就是冻结模式的作用。

  • 接着看下面三种模式,这个有效电平和无效电平,一般是高级定时器里面的一个说法,是和关断刹车这些功能配合表述的,它说的比较严谨,所以叫有效电平和无效电平,在这里为了理解方便,你可以直接认为,置有效电平就是置高电平,置无效电平就是置低电平,这样就行了。

那这三个模式都是当CNT与CCR值相同时,执行操作。这些模式就可以用作波形输出了,比如相等时电平翻转这个模式,这个可以方便地输出一个频率可调,占空比始终为50%的PWM波形,比如你设置CCR为0,那CNT每次更新清0时,就会产生一次CNT = CCR的事件,这就会导致输出电平翻转一次,每更新两次,输出为一个周期,并且高电平和低电平的时间是始终相等的,也就是占空比始终为50%,当你改变定时器更新频率时,输出波形的频率也会随之改变,它俩的关系是输出波形的频率 = 更新频率/2,因为更新两次输出才为一个周期对吧,这就是这个匹配时电平翻转模式的用途。

  • 再接着看下面两个模式,这两个模式和冻结模式也差不多啊,如果你想暂停波形输出,并且在暂停期间保持低电平或者高电平,那你就可以设置这两个强制输出模式。
  • 最后两个模式,它们可以用于输出频率和占空比都可调的PWM波形,也是我们主要使用的模式。PWM模式2实际上就是PWM模式1输出的取反改变PWM模式1和PWM模式2,就只是改变了REF电平的极性而已。

PWM基本结构

在这里插入图片描述
首先,左上角这里,是时间单元和运行控制部分,再左边是时钟源选择,这里就忽略了。配置好了时基单元,这里的CNT就可以开始不断地自增运行了,然后下面这里,就是输出比较单元了,总共有四路,输出比较单元的最开始,是CCR,CCR是我们自己设置的,CNT不断自增运行,同时它俩还在不断进行比较,后面这个就是输出模式控制器,这里面是PWM模式1的执行逻辑。

那它怎么输出PWM波形的呢,看上面的图。这里蓝色线是CNT的值,黄色线是ARR的值,蓝色线从0开始自增,一直增到ARR,也就是00,之后清0继续自增,在这个过程中,我们再设置一条红色线,这条红色线就是CCR,比如我们设置CCR为30,之后再执行这里的这个逻辑,下面这里绿色线就是输出,可以看到,CNT < CCR,所以置高电平,之后CNT大于等于CCR了,所以就变为低电平。

这里REF,就是一个频率可调,占空比也可调的PWM波形,最终再经过极性选择,输出使能,最终通向GPIO口,这样就能完成PWM波形的输出了

参数计算

在这里插入图片描述

舵机简介

TIM输入捕获

TIM输入捕获理论部分

输入捕获简介

  • IC(Input Capture)输入捕获
    4个输入捕获和输出比较通道,共用4个CCR寄存器,另外它们的CH1和CH4,4个通道的引脚,也是共用的,所以对于同一个定时器,输入捕获和输出比较,只能使用其中一个,不能同时使用。
  • 输入捕获模式下,当通道输入引脚出现指定电平跳变时,当前CNT的值将被锁存到CCR中,可用于测量PWM波形的频率、占空比、脉冲间隔、电平持续时间等参数
    对比一下输出比较,就是,输出比较,引脚是输出端口,输入捕获,引脚是输入端口,输出比较,是根据CNT和CCR的大小关系来执行输出动作,输入捕获,是接收到输入信号,执行CNT锁存到CCR的动作。
  • 每个高级定时器和通用定时器都拥有4个输入捕获通道
    输入捕获电路,通用定时器和高级定时器没有区别,都是一样的
  • 可配置为PWMI模式,同时测量频率和占空比
    这个PWMI模式就是PWM输入模式,是专门为测量PWM频率和占空比设计的
  • 可配合主从触发模式,实现硬件全自动测量

频率测量

在这里插入图片描述
测周法的基本原理就是,周期的倒数就是频率,如果能测出一个周期的时间,再娶个倒数,就是频率了。我们捕获信号的两个上升沿,然后测量一下这之间持续的时间,就行了,但是实际上,我们并没有一个精度无穷大的秒表来测量时间,测量时间的方法,实际上也是定时器计次,我们使用一个已知的标准频率fc的计次时钟,来驱动计数器,从一个上升沿开始计,计数器从0开始,一直计到下一个上升沿,停止,及一个数的时间是1/fc,计N个数,时间就是N/fc,N/fc就是周期,再取个倒数,就得到了公式。

  • 首先,测频法适合测量高频信号,测周法适合测量低频信号。
  • 然后是测频法测量结果更新的慢一些,数值相对稳定,测周法更新的快,数据跳变也非常快。测频法测量的是在闸门时间内的多个周期,所以它自带一个均值滤波,如果在闸门时间内波形频率有变化,那得到的其实是这一段时间的平均频率,如果闸门时间选为1s,那么每隔1s才能得到一次结果,所以测频法结果更新满,测量结果是一段时间的平均值,值比较平滑,反观测周法,它只测量一个周期,就能出一次结果,所以出结果的速度取决于待测信号的频率,一般而言,待测信号都是几百几千Hz,所以一般情况下,测周法结果更新更快,但是由于它只测量一个周期,所以结果值会受噪声的影响,波动比较大。
  • 那多高算高,多低算低呢,这就涉及到了中界频率了。中界频率是,测频法与测周法误差相等的频率点。对应图上,当待测信号频率小于中界频率时,测周法误差更小,选用测周法更合适。
STM32实现频率测量
  • 首先,测频法,这个我们用之前学过的外设就可以实现,我们之前写过,对射式红外传感器计次、定时器外部时钟,这些代码,稍加改进,就是测频法,比如对射式红外传感器计次,每来一个上升沿计次+1,那我们再用一个定时器,定一个1s的定时中断,在中断里,每隔1s取一下计次值,同时清0计次,为下一次做准备,这样每次读取的计次值就直接是频率,对应定时器外部时钟的代码,也是如此,每个1s取一下计次,就能实现测频法测量频率的功能了。
  • 而本节输入捕获测频率,使用的方法是测周法,就是测量两个上升沿之间的时间,来进行频率测量的。
    上升沿用于触发输入捕获,CNT用于计数计时,每来一个上升沿,取一下CNT的值,自动存在CCR里,CCR捕获到的值,就是计数值N,CNT的驱动时钟,就是fc,fc/N,就得到了待测信号的频率。另外这里还有个细节问题,就是每次捕获之后,我们都要把CNT清0一下,这样下次上升沿再捕获的时候,取出的CNT才是两个上升沿的时间间隔,这个在一次捕获后自动将CNT清零的步骤,我们可以用主从触发模式,自动来完成。

输入捕获通道框图

在这里插入图片描述
引脚进来,还是先经过一个滤波器,滤波器的输入是TI1,就是CH1的引脚,输出的TI1F,就是滤波后的信号,fDTS是滤波器的采样时钟来源,下面CCMR1寄存器里的ICF位可以控制滤波器的参数,那这个滤波器具体是怎么工作的呢,可以看一下手册,在CCMR1寄存器这里,有IC1F位,描述是,这几位定义了TI1输入的采样频率及数字滤波长度,数字滤波器是由一个事件计数器组成,它记录到N个事件后会产生一个输出的跳变。简单理解,这个滤波器工作原理就是,以采样频率对输入信号进行采样,当连续N个值都为高电平,输出才为高电平,连续N个值都为低电平,输出才为低电平,如果你信号出现高频抖动,导致连续采样N个值不全都一样,那输出就不会变化,这样就可以达到滤波的效果,采样频率越低,采样个数N越大,滤波效果就越好。

然后回过来,滤波之后的信号通过边沿检测器,捕获上升沿或者下降沿,用这个CCER寄存器里的CC1P位,就可以选择极性了,最终得到TI1FP1触发信号,通过数据选择器,进入通道1后续的捕获电路,当然这里时基应该还有一套一样的电路,得到TI2FP2触发信号,连通到通道2的后续电路,这里并没有画出来,同样,通道2有TI2FP1,连通到通道1的后续,通道2也还有TI2FP2,连通到通道2的后续,总共是4种连接方式,然后经过这里的数据选择器,进入后续捕获部分电路。CC1S位可以对数据选择器进行选择,之后ICPS位,可以配置这里的分频器,可以选择不分配、2分配、4分配、8分配,最后CC1E位,控制输出使能或使能,如果使能了输出,输入端产生指定边沿信号,经过层层电路,到达这里,就可以让这里CNT的值,转运到CCR里面来。

另外我们说了,每捕获一次CNT的值,都要把CNT清零一下,以便于下一次的捕获,在这里硬件电路就可以在捕获之后自动完成CNT的清零工作,如何自动清零CNT呢,这个TI1FP1信号和TI1的边沿信号,都可以通向从模式控制器,比如TI1FP1信号的上升沿触发捕获,那通过这里,TI1FP1还可以同时触发从模式,这个从模式里面,就有电路,可以自动完成CNT的清零,所以可以看出,这个从模式就是完成自动化操作的利器。

主从触发模式

在这里插入图片描述
主从触发模式这个名字是江科大自己取的,主从触发模式,就是主模式、从模式和触发源选择这三个功能的简称,其中主模式可以将定时器内部的信号,映射到TRGO引脚,用于触发别的外设,所以这部分叫做主模式,从模式呢,就是接收其他外设或者自身外设的一些信号,用于控制自身定时器的运行,也就是被别的信号控制,所以这部分叫从模式,触发源选择,就是选择从模式的触发信号源的,你可以认为它是从模式的一部分,触发源选择,选择指定的一个信号,得到TRGI,TRGI去触发从模式,从模式可以在这个列表里,选择一项操作来自动执行。

如果想完成刚刚说的任务,想让TI1FP1信号自动触发CNT清零,那触发源选择,就可以选中这里的TI1FP1,从模式执行的操作,就可以选择执行Reset的操作,这样TI1FP1的信号就可以自动触发从模式,从模式自动清零CNT,实现硬件全自动测量,这就是主从触发模式的用途,那有关这些信号的具体解释,可以看看手册。

在库函数里也比较简单,这三个东西,就对应三个函数,调用函数,给个参数,就行了。

输入捕获基本结构

在这里插入图片描述这个结构我们只使用了一个通道,所以它目前只能测量频率,在右上角这里,是时基单元,我们把时基单元配置好,启动定时器,那这个CNT,就会在预分频之后的这个时钟驱动下,不断自增,这个CNT,就是我们测周法用来计数计时的东西,经过预分频之后这个位置的时钟频率,就是驱动CNT的标准频率fc,这里不难看出,标准频率 = 72M/预分频系数,然后下面输入捕获通道1的GPIO口,输入这样的方波信号,经过滤波器和边沿检测,选择TI1FP1为上升沿触发,之后输入选择直连的通道,分频器选择不分频,当TI1FP1出现上升沿之后,CNT的当前计数值转运到CCR1里,同时触发源选择,选择TI1FP1为触发信号,从模式选择复位操作,这样TI1FP1的上升沿,也会通过上面这一路,去触发CNT清零,当然这里会有个先后顺序,肯定得是先转运CNT的值到CCR里去,再触发从模式给CNT清零,或者是非阻塞的同时转移,CNT的值转移到CCR,同时0转移到CNT里面去,总之肯定不会是先清零,再捕获,要不然捕获值肯定都是0了,这是这两条路的执行逻辑。

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

然后还有几个注意事项说明一下,首先是这里,CNT的值是有上限的,ARR一般设置为最大65535,那CNT最大也只能计65535个数,如果信号频率太低,CNT的值可能会溢出,另外还有就是,这个从模式的触发源选择,在主从触发模式那里看到,只有TI1FP1和TI2FP2,没有TI3和TI4的信号,所以这里如果想使用从模式自动清零CNT,就只能用通道1和通道2,对于通道3和通道4,就只能开启捕获中断,在中断里手动清零了,不过这样,程序就会处于频繁中断的状态,比较消耗软件资源,这里注意一下。

PWMI基本结构

在这里插入图片描述
这个PWMI模式,使用了两个通道同时捕获一个引脚,可以同时测量周期和占空比。上面结构和之前讲的一样,下面这里多了一个通道,首先TI1FP1配置上升沿触发,触发捕获和清零CNT,正常地捕获周期,这时我们再来一个TI1FP2,配置为下降沿触发,通过交叉通道,去触发通道2的捕获单元,这时会发生什么呢,我们看下左上角这个图,最开始上升沿,CCR1捕获,同时清零CNT,之后CNT一直++,然后,在下降沿这个时刻,触发CCR2捕获,所以这时CCR2的值,就是CNT从这里到这里的计数值,就是高电平期间的计数值,CCR2捕获,并不触发CNT清零,所以CNT继续++,直到下一次上升沿,CCR1捕获周期,CNT清零,这样执行之后,CCR1就是一整个周期的计数值,CCR2就是高电平期间的计数值,我们用CCR2/CCR1,是不是就是占空比了,这就是PWMI模式,使用两个通道来捕获频率和占空比的思路。

另外这里,你可以两个通道同时捕获第一个引脚的输入,这样通道2的前面这一部分就没有用到,当然也可以配置两个通道同时捕获第二个引脚的输入,这样我们就是使用TI2FP1和TI2FP2这两个引脚了,这两个输入可以灵活切换。

输入捕获的代码部分

输入捕获模式测频率

调节PWM频率

通过ARR调节频率,同时还会影响到占空比,而通过PSC调节频率,不会影响占空比,显然比较方便,所以可以固定ARR为100 - 1,通过调节PSC来改变PWM频率

一般可以根据分辨率的要求,先确定好ARR,比如分辨率,1%就足够了,那ARR给100 - 1,这样PSC决定频率,CCR决定占空比。如果我想要更高的分辨率,比如0.1% ,那ARR就先固定为1000 - 1.

void PWM_SetPrescaler(uint16_t Prescaler)
{
//第二个参数,就是要写入PSC的值
/*第三个参数解释是,指定定时器预分频器的重装模式
可以是Update,预分频器在更新事件重装
可以是Immediate,预分频器立即重装,
说白了,就还是影子寄存器、预装载这个问题
就是你写入的值是立刻生效,还是在更新事件生效,
立刻生效,可能会在值改变时产生切断波形的现象*/
	TIM_PrescalerConfig(TIM2, Prescaler, TIM_PSCReloadMode_Immediate);
}
输入捕获初始化代码
  • 第一步,RCC开启时钟,把GPIO和TIM的时钟打开
  • 第二步,GPIO初始化,把GPIO配置成输入模式,一般选择上拉输入或者浮空输入
  • 第三步,配置时基单元,让CNT计数器在内部时钟的驱动下自增运行
  • 第四步,配置输入捕获单元,包括滤波器、极性、直连通道还是交叉通道、分频器这些参数,用一个结构体就可以统一进行配置了
  • 第五步,选择从模式的触发源触发源选择为TI1FP1,这里调用一个库函数,给一个参数就行了
  • 第六步,选择触发之后执行的操作,执行Reset操作,这里也是调用一个库函数就行了
  • 最后,当这些电路都配置好之后,调用TIM_Cmd函数,开启定时器
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_ICInit(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct);

另外注意,输入捕获和输出比较都有4个通道,OCInit,4个通道,像上面那样,每个通道单独占一个函数,而ICInit,4个通道是共用一个函数的,在结构体里,会额外有一个参数,可以用来具体是配置哪个通道,因为可能有交叉通道的配置,所以函数合在一起比较方便。

void TIM_SetCompare1(TIM_TypeDef* TIMx, uint16_t Compare1);
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);

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

输出比较模式下,CCR是只写的,要用SetComapre写入,输入捕获模式下,CCR是只读的,要用GetCapture读出。

void IC_Init(void)
{
	/*TIM3的通道1和通道2,对应PA6和PA7,通道3和通道4,对应PB0和PB1
	所以引脚需要根据实际需求来*/
	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);//选择内部时钟
	
	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
	//ARR的值最好设置大一些,防止溢出,设置成65536 - 1
	TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;		//PSC
	//72M/预分频,就是计数器自增的频率,就是计数标准频率
	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_ICPolarity_Rising上升沿触发
		第二个TIM_ICPolarity_Falling下降沿触发
		第三个TIM_ICPolarity_BothEdge上升沿下降沿都触发*/
	TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;//触发信号分频器,这里是不分频,我们需要每次触发都有效,所以选择不分频
	TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
	/*从哪个引脚输入,这个参数是用于配置数据选择器的,
	可以选择直连通道,或者是交叉通道
	第一个参数TIM_ICSelection_DirectTI直连通道的输入
	第二个参数TIM_ICSelection_IndirectTI交叉通道的输入
	还有一个TRC引脚,这个暂时不用*/
	TIM_ICInit(TIM3, &TIM_ICInitStructure);
	
	//第五步,配置TRGI的触发源为TI1FP1
	TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);
	//第六步,配置从模式为Reset
	TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);
	
	TIM_Cmd(TIM3, ENABLE);
}

注意滤波器和分频器的区别,虽然它俩都是计次的东西,但是滤波器计次,并不会改变信号的原有频率,一般滤波器的采样频率都会高于信号频率,所以它只会滤除高频噪声,使信号更平滑,1KHz滤波之后仍然是1KHz,信号频率不会变化,而分频器就是对信号本身进行计次了,会改变频率,1KHz,2分频之后就是500Hz,4分频之后就是250Hz。

读取CCR进行计算

频率fx = fc / N,fc = 72M / ( PSC + 1),而在上面配置中,PSC是72 - 1,所以fc = 1MHz

uint32_t IC_GetFreq(void)
{
	return 1000000 / (TIM_GetCapture1(TIM3) + 1);
}

PWMI模式测频率占空比

初始化

首先,开启时钟、GPIO和时基单元都不需要改,然后输入捕获初始化的部分,需要进行一下升级,配置成两个通道同时捕获同一个引脚的模式

怎么配置呢,一个简单的想法就是,把这个通道初始化的部分,复制一份,这个结构体定义的不要复制了,然后呢,通道1是直连输入,上升沿触发,这个还沿用这个配置,接着下面,通道1改成通道2,直连输入,改成这个交叉输入,上升沿触发,改成下降沿触发。

	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);
	TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;
	TIM_ICInitStructure.TIM_ICFilter = 0xF;
	TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Falling;//极性
	TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
	TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_IndirectTI;
	TIM_ICInit(TIM3, &TIM_ICInitStructure);

这样的配置是可行的,没问题。但是呢,ST公司怕我们麻烦,还专门给我们封装了一个函数,来快捷地完成这个配置

	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_PWMIConfig(TIM3, &TIM_ICInitStructure);

上面演示的这个代码,和刚才那个代码效果是一样的,使用这个函数,只需要传入一个通道的参数就行了,在函数里,会自动把剩下的一个通道初始化成相反的配置。比如上面这边传入通道1,直连,上升沿,那函数里面就会顺带配置通道2,交叉,下降沿,这个函数可以快捷的把电路配置成PWMI模式的标准结构。

获取占空比的函数

高电平的计数值存在CCR2里,整个周期的计数值存在CCR1里,用CCR2/CCR1,就能得到占空比了。

uint32_t IC_GetDuty(void)
{
	return (TIM_GetCapture2(TIM3) + 1) * 100 / (TIM_GetCapture1(TIM3) + 1);
}

性能测评

  • 首先是测频率的范围,目前我们给的标准频率是1MHz,计数器最大只能计到65535,所以所测量的最低频率是1M/65535,这个值算一下大概是15Hz,如果信号频率再低,计数器就要溢出了,所以最低频率就是15Hz左右,那如果想要再降低一些最低频率的限制,可以把预分频PSC加大点,这样标准频率就更低,所支持测量的最低频率也就更低,这是测量频率的下限。然后是测量频率的上限,就是支持的最大频率,这个最大频率,并没有一个明显的界限,因为随着待测频率的增大,误差也会逐渐增大,如果非要找个频率上限,那应该是标准频率1MHz,超过1MHz,信号频率就比标准频率还高,那肯定测不了,但这个1MHz的上限并没有意义,因为信号接近1MHz时,误差已经非常大了,所以最大频率要看你对误差的要求。上面说了正负1误差,计100个数,误差1个,相对误差就是百分之一,计1000个数,误差1个,相对误差就是千分之一,所以正负1误差可以认为是1/计数值。在这里,如果要求误差等于千分之一,频率为上限,那这个上限就是1M/1000 = 1KHz。

  • 还有一个就是误差分析,在实际测量时,还会有晶振误差,比如我们STM32的晶振不是那么准,在计次几百几万之后,误差积累起来,也会造成一些影响。后期可以再做些滤波处理。

编码器接口

编码器接口理论部分

编码器接口简介

  • Encoder Interface编码器接口
  • 编码器接口可接收增量(正交)编码器的信号,根据编码器旋转产生的正交信号脉冲,自动控制CNT自增或自减,从而指示编码器的位置、旋转方向和旋转速度

比如,一个编码器,它有两个输出,一个是A相,一个是B相,然后接入到STM32,定时器的编码器接口,编码器接口自动控制定时器时基单元中的CNT计数器,进行自增或自减,比如初始化之后,CNT初始值为0,然后编码器右转,CNT就++,右转产生一个脉冲,CNT就加一次,比如右转产生10个脉冲后,停下来,那么这个过程CNT就由0自增到10,停下来,编码器左转,CNT就- -,左转产生一个脉冲,CNT减一次,比如我编码器再左转产生5个脉冲,那CNT就在原来10的基础上自减5,停下来。这个编码器接口,其实就相当于是一个带有方向控制的外部时钟,它同时控制着CNT的计数时钟和计数方向,这样的话,CNT的值就表示了编码器的位置,如果我们每隔一段时间取一次CNT的值,再把CNT清零,那么每次取出来的值就表示了编码器的速度。

这个编码器测速实际上就是测频法测正交脉冲的频率,CNT计次,然后每隔一段时间取一次计次,这就是测频法的思路,只不过这个编码器接口计次更高级,它能根据旋转方向,不仅能自增计次,还能自减计次,是一个带方向的测速。

  • 每个高级定时器和通用定时器都拥有1个编码器接口

如果硬件资源实在不够接编码器的话,还是可以用外部中断来接编码器的,这样就是用软件资源来弥补硬件资源的,所以这里也可以看出,硬件资源和软件资源是互补的,硬件资源越多,软件就会越轻松,硬件不够呢,那就软件来凑。比如PWM,我可以直接来个定时中断,然后在中断里手动计数,手动翻转电平,比如输入捕获,我可以来个外部中断,然后在中断里手动把CNT取出来,放在变量里,比如编码器接口,我也可以来外部中断,然后在中断里,手动自增或自减计数,这都可以实现功能。但是这样就是消耗软件资源了。

  • 两个输入引脚借用了输入捕获的通道1和通道2
    编码器的两个输入引脚,就是每个定时器的CH1和CH2引脚,CH3和CH4不能接编码器。

正交编码器

在这里插入图片描述

  • 正交编码器一般可以测量位置,或者带有方向的速度值,它一般有两个信号输出引脚,一个是A相,一个是B相。
  • 当编码器的旋转轴转起来时,A相和B相会输出这样的方波信号,转的越快,这个方波的频率就越高,所以方波的频率就代表了速度,我们取出任意一相的信号来测频率,就能知道旋转速度了,但是只有一相的信号,无法测量旋转方向,因为无论正转还是反转,它都是这样的方波,想要测量方向,还必须要有另一根线的辅助,比如,我看可以不要这个B相,再定义一个方向输出脚,正转置高电平,反转置低电平,这是一种解决方案,但这样的信号并不是正交信号,另一种解决方案,就是我们本节说的正交信号,当正转时,A相提前B相90度,反转时,A相滞后B相90度,当然这个正转,是A相提前还是A相滞后,并不是绝对的,这只是一个极性问题,毕竟正转和反转的定义也是相对的,总之就是朝一个反向转是A相提前,另一个方向是A相滞后。
  • 那使用正交信号相比较单独定义一个方向引脚,有什么好处呢,首先就是正交信号精度更高,因为A、B相都可以计次,相当于计次频率提高了一倍,其次就是正交信号可以抗噪声,因为正交信号,两个信号必须是交替跳变的,所以可以设计一个抗噪声电路,如果一个信号不变,另一个信号连续跳变,也就是产生了噪声,那这时计次值是不会变化的。
  • 计数的方向由另一相的状态来确定,当出现某个边沿时,我们判断另一相的高低电平,如果对应另一相的状态出现在上面这个表里,那就是正转,计数自增,反之,另一相的状态出现在下面这个表里,那就是反转,计数自减,这样就能实现编码器接口的功能了,这也是我们STM32定时器编码器接口的执行逻辑了。

编码器接口的框图

在这里插入图片描述
这里编码器接口有两个输入端,分别要接到编码器的A相和B相,然后左边是两个网络标号,分别写的是TI1FP1和TI2FP2,对应的就是下面,TI1FP1和TI2FP2。可以看出,编码器的两个引脚,借用了输入捕获单元的前两个通道,所以最终编码器的输入引脚,就是定时器的CH1和CH2这两个引脚。信号的通路是,CH1通过这里,通向编码器接口,CH2通过这里,通向编码器接口,CH3和CH4,与编码器接口无关,其中CH1和CH2的输入捕获滤波器和边沿检测,编码器接口也有使用,但是后面的是否交叉、预分频器和CCR寄存器,与编码器接口无关,这就是编码器接口的输入部分。那编码器接口的输入出部分,其实就相当于从模式控制器了,去控制CNT的计数时钟和计数方向,简单来说,这里的输出执行流程是,按照我们之前总结的那个表,如果出现了边沿信号,并且对应另一相的状态为正转,则控制CNT自增,否则,控制CNT自减。注意在这里,我们之前一直在使用的72MHz内部时钟,和我们在时基单元初始化时设置的计数方向,并不会使用,因为此时计数时钟和计数方向都处于编码器接口托管的状态,计数器的自增和自减,受编码器控制。

编码器接口基本结构

在这里插入图片描述
输入捕获的前2个通道,通过GPIO口接入编码器的A、B相,然后通过滤波器和边沿检测极性选择,产生TI1FP1和TI2FP2,通向编码器接口,编码器接口通过预分频器控制CNT计数器的时钟,同时,编码器接口还根据编码器的旋转方向,控制CNT的计数方向,编码器正转时,CNT自增,编码器反转时,CNT自减。

另外这里ARR也是有效的,一般我们会设置ARR为65535,最大量程,这样的话,利用补码的特性,很容易得到负数,比如CNT初始为0,我正转,CNT自增,0、1、2、3、4、5、6等等,这都没问题。但是我反转呢,CNT自减,0下一个数就是65535,接着是65534、65533等等,这里负数不应该是-1、-2吗,没关系,可以做一个操作,直接把这个16位的无符号数转换为16位的有符号数,根据补码的定义,这个65535就对应-1,65534就对应-2,这样就可以直接得到负数,非常方便,这就是我们读出数据得到负数的一个小技巧。

工作模式

在这里插入图片描述
这个表,描述的就是上面说的,编码器接口的工作逻辑,这里TI1FP1和TI2FP2接的就是编码器的A、B相,在A相和B相的上升沿或者下降沿触发计数,到底是向上计数还是向下计数,取决于边沿信号发生的这个时刻,另一相电平状态,也就是这里的相对信号的电平,TI1FP1对应TI2,TI2FP2对应TI1,就是另一相电平的意思,然后在这里,这个编码器还分了3种工作模式,分别是仅在TI1计数、仅在TI2计数和TI1 TI2都计数。这三个模式是啥意思呢?回到下面这张图
在这里插入图片描述
上面总结的是,上面四种状态,都是正转,都可以计次自增,下面这四种状态,都是反转,都可以计次自减,这四种状态设计了两个引脚,分别是A相上升沿、A相下降沿、B相上升沿、B相下降沿,如果这四种状态都执行自增或自减,就是A相和B相的边沿都计数,那就对应工作模式那张图中的第3种模式,TI1和TI2都计数,当然这里,我们还可以忽略一些边沿,比如我们可以仅在A相的上升沿和下降沿自增或自减,而B相的这两个状态忽略掉,不执行计数,或者仅在B相的上升沿和下降沿计数,A相的边沿不管它,这样也可以实现功能,只不过是计次的精度低了一些。

实例(均不反相)

在这里插入图片描述

在这里插入图片描述
这里展示的就是正交编码器抗噪声的原理了,在这里TI2没有变化,但是TI1却跳变了好几次,这不符合正交编码器的信号规律,正交信号,两个输出交替变化。

实例(TI1反相)

在这里插入图片描述
在这里,把TI1高低电平都反转一下,就是这里的TI1反相。分析的时候呢,这里图上画的是输入信号,如果直接对照上面这个表,得到的计数方向就是错误的,比如第一个状态,TI1上升沿,TI2低电平,查表应该是向上计数,但这里实际确实向下计数,所以TI1反相之后,我们先这样,把TI1高低电平取反,这才是反相后实际给编码器接口的电平,然后再查表,第一个状态应该是TI1下降沿,TI2低电平,查表得到是向下计数,和这里是对应的。

这有什么用呢,比如你接一个编码器,发现它数据的加减方向反了,你想要正转的方向,结果它自减了,你想要反转的方向,结果它自增了,这时,就可以调整一下极性,把任意一个引脚反相,就能反转计数方向了,当然如果想改变计数方向的话,我们还可以直接把A、B两个引脚换一下。

编码器接口代码部分

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值