STM32学习笔记(七)丨TIM定时器及其应用(编码器接口丨用定时器实现编码器测速)

本篇文章包含的内容

  • 一、编码器接口
  • 1.1 编码器接口简介
  • 1.2 编码器接口的基本结构和工作模式
  • 1.3 编码器接口的工作实例分析
  • 二、代码实现编码器测速
  • ​  本次课程采用单片机型号为STM32F103C8T6。
    ​  课程链接:江科大自化协 STM32入门教程


      往期笔记链接:
      STM32学习笔记(一)丨建立工程丨GPIO 通用输入输出
      STM32学习笔记(二)丨STM32程序调试丨OLED的使用
      STM32学习笔记(三)丨中断系统丨EXTI外部中断
      STM32学习笔记(四)丨TIM定时器及其应用(定时中断、内外时钟源选择)
      STM32学习笔记(五)丨TIM定时器及其应用(输出比较丨PWM驱动呼吸灯、舵机、直流电机)
      STM32学习笔记(六)丨TIM定时器及其应用(输入捕获丨测量PWM波形的频率和占空比)


      如果上一篇笔记的内容为史诗级副本,本篇文章的内容我愿称之为传说级副本(四)


    一、编码器接口

    1.1 编码器接口简介

    请添加图片描述

      编码器接口(Encoder Interface),可以接收正交编码器(又称为增量编码器)的信号,根据编码器旋转产生的正交信号脉冲,自动由硬件电路控制定时器时基单元中的计数器CNT自增或自减,从而指示编码器的位置、旋转方向和旋转速度。从原理上看,编码器接口输出的信号就相当于一个带有方向控制的外部时钟,同时控制CNT的计数时钟和计数方向。这时,CNT的值就代表了编码器的位置。如果每隔一段时间将CNT的值取出并清0,我们就能得到编码器在这一段时间内的旋转速度(注意,不是旋转速率,这里的速度只与这段时间的长短和始末位置有关),思路与测频法测正交脉冲的频率相似,只不过这里的计数器的计数方向由编码器接口决定。

    正交(增量)编码器

      两个输出引脚输出的方波信号相位相差90°的编码器称为正交(增量)编码器。一个方波信号相对于另一个方波信号超前或滞后90°代表正传或反转。
      要测量编码器的旋转方向,还可以使用一个引脚输出方波表示旋转角度和速度,另一个引脚输出旋转方向:正传输出高电平,反转输出低电平。但相较于这一种方案,使用正交编码器的优势在于:

    1. 正交信号精度更高。正交信号的A、B相都可以计次,且相位相差90°,相当于计次频率(计数器的变化频率)提高了一倍。
    2. 正交信号可以在一定程度上免除噪声的干扰。对于正交信号而言,两个信号必须交替跳变,CNT的值才会自增或自减。如果一个信号不变,另一个信号连续跳变,则CNT的值是不会变化的。

      每个高级定时器和通用定时器都拥有1个编码器接口,且功能上没有差别。编码器接口的两个输入引脚借用了输入捕获的通道1(TIMx_CH1)和通道2(TIMx_CH2)。由于STM32中定时器的数量有限,编码器接口占用的硬件资源还是很大的,如果一个定时器被配置为编码器接口模式,那这个定时器将很难完成其他功能

      STM32的软硬件资源是互补的。如果在实际应用中,出现了硬件资源不足的情况,可以使用软件资源作弥补,对于软件也是相同。例如,我们可以通过外部中断来实现类似编码器接口测速的功能;对于PWM,也可以在定时中断中手动翻转引脚电平来达到类似的效果;输入捕获的功能也可以由外部中断完成,可以在中断中手动将CNT取出并放在变量里,然后再清0……在有硬件资源的条件下,应该优先使用硬件资源,节约出的软件资源可以完成更重要的任务

    1.2 编码器接口的基本结构和工作模式


      编码器接口借用了定时器的CH1和CH2通道,与CH3和CH4无关。且输入捕获前端的输入滤波和极性选择部分也有使用。编码器接口的输出部分,相当于一个从模式控制器,控制CNT的自增和自减。此时72Hz内部时钟和时基单元初始化时设置的计数方向都处于失效的状态,因为此时的计数时钟和计数方向都处于编码器接口托管的状态。 所以当一个定时器被配置为编码器接口模式,这个定时器就很难完成其他工作了,因为编码器接口直接控制时基单元的时钟来源。
      在编码器接口模式下ARR也是有效的,一般将ARR设置为最大量程,这样利用补码的特性,很容易得到负数。
      编码器接口的设计逻辑为:把A相和B相的所有边沿作为计数器的计数时钟,出现边沿信号时,就计数自增或自减,此时增减由另一相的状态确定。当然,也可以仅在一相进行计数,忽略另一相,只不过这样会牺牲一部分计数的精度。

    1.3 编码器接口的工作实例分析

      TI1FP1和TI2FP2均不反相时,计数器的操作实例如下图所示。下图同时也展示了编码器接口的抗噪声原理:当一个信号不变,另一个信号连续跳变,计数器就会来回加减摆动,最终计数值不变。

      TI1FP1反相,TI2FP2不反相时,计数器的操作实例如下图所示。在输入捕获模式下,极性选择模块是用来选择上升沿有效还是下降沿有效的,但编码器接口模式上升沿和下降沿都有效,所以在编码器接口模式下,极性选择不再是边沿的极性选择,而是高低电平的极性选择
      这里反相的作用是:如果在编码器模块的使用过程中,发现数据的加减方向反了,这时就可以将任意一个引脚反相,就可以反转计数方向了。当然,也可以直接把A相和B相的输入引脚更换一下。

    二、代码实现编码器测速

      本小节要实现的实验现象比较简单,这里采用编码器接口获取CNT中的值,在Encoder_Get函数中手动将CNT清0,并且采用定时中断的方法,定时1s取一次CNT的值。
    请添加图片描述

  • Timer.c(使用TIM2定时,每1s触发一次定时中断)
  • #include "stm32f10x.h"                  // Device header
    
    void Timer_Init(void)
    {
    	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
    	
    	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 = 10000 - 1;
    	TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 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
    	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);
    }
    
    
  • Encoder.c(使用TIM3配置为编码器接口模式)
  • #include "stm32f10x.h"                  // Device header
    
    void Encoder_Init(void)
    {
    	// 1. RCC开启时钟
    	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    	
    	// 2. 配置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);
    	
    	// 3. 配置时基单元
    	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    	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);
    	
    	// 4. 配置输入捕获单元
    	TIM_ICInitTypeDef TIM_ICInitStruct;
    	TIM_ICStructInit(&TIM_ICInitStruct);							// 给结构体赋一个初始值防止未知问题
    	
    	// 配置CH1
    	TIM_ICInitStruct.TIM_Channel = TIM_Channel_1;					
    	TIM_ICInitStruct.TIM_ICFilter = 0xF;							
    //	TIM_ICInitStruct.TIM_ICPolarity = TIM_ICPolarity_Rising;		
    //  这里的上升沿并不代表上升沿有效,这里代表高低电平极性不翻转
    //	它与TIM_EncoderInterfaceConfig中的参数操作的是同一个寄存器,属于重复配置
    	
    	TIM_ICInit(TIM3, &TIM_ICInitStruct);
    	
    	// 配置CH2
    	TIM_ICInitStruct.TIM_Channel = TIM_Channel_2;					
    	TIM_ICInitStruct.TIM_ICFilter = 0xF;							
    //	TIM_ICInitStruct.TIM_ICPolarity = TIM_ICPolarity_Rising;		
    	
    	TIM_ICInit(TIM3, &TIM_ICInitStruct);
    	
    	// 5. 配置编码器接口模式
    	TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);
    	
    	// 6. 启动定时器
    	TIM_Cmd(TIM3, ENABLE);
    }
    
    int16_t Encoder_Get(void)
    {
    	int16_t Temp;
    	Temp = TIM_GetCounter(TIM3);	// 这里TIM_GetCounter返回一个uint16_t类型的值,利用补码很方便地将正数变为负数,例如65535变为-1
    	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()
    {
    	OLED_Init();
    	Timer_Init();
    	Encoder_Init();
    	
    	OLED_ShowString(1, 1, "Speed:");
    
    	while(1)
    	{
    		// 一般不建议以下做法,容易阻塞主程序
    //		OLED_ShowSignedNum(1, 5, Encoder_Get(), 5);
    //		Delay_ms(1000);
    		OLED_ShowSignedNum(1, 7, Speed, 5);
    	}
    }
    
    void TIM2_IRQHandler(void)
    {
    	if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
    	{
    		Speed = Encoder_Get();
    		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
    	}
    }
    
    

      原创笔记,码字不易,欢迎点赞,收藏~ 如有谬误敬请在评论区不吝告知,感激不尽!博主将持续更新有关嵌入式开发、机器学习方面的学习笔记~

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32学习笔记(七)丨TIM定时器及其应用(编码器接口丨用定时器实现编码器测速)

    发表评论