【江协STM32】6-7/8 TIM编码器接口、编码器接口测速

1. 编码器接口简介

  • Encoder Interface 编码器接口
  • 编码器接口可接收增量(正交)编码器的信号,根据编码器旋转产生的正交信号脉冲,自动控制CNT自增或自减,从而指示编码器的位置、旋转方向和旋转速度
  • 编码器接口相当于一个带有方向控制的外部时钟,同时控制CNT的计数时钟和计数方向
  • 每个高级定时器和通用定时器都拥有1个编码器接口
  • 两个输入引脚借用了输入捕获的通道1和通道2,通道3和通道4不能接编码器
  • 2. 正交编码器

    可以测量位置,或带有方向的速度值。

    方波的频率代表速度,方向可以根据右侧表格判断。

    3. 编码器接口基本结构

    输入部分:编码器接口的两个引脚使用了输入捕获单元的CH1和CH2

    输出部分:相当于从模式控制器,控制CNT的计数时钟和计数方向。此时,不会使用72MHz内部时钟和时基单元初始化时设置的计数方向,因为此时计数时钟和计数方向均处于编码器接口托管的状态,计数器的自增和自减受编码器控制

    编码器接口根据编码器的旋转方向控制CNT的计数方向,编码器正转时CNT自增,编码器反转时CNT自减。ARR一般设置为最大量程65535,负数可以通过补码获得。

    4. 编码器接口的工作模式

    可以参考“2.正交编码器”中的图表。如果表中所有情况都计数,就是“在TI1和TI2上计数”;如果只在A相的上升和下降沿计数,可以是“仅在TI1计数”,则只在B相的上升和下降沿计数就为“仅在TI2计数”。

    实例(均不反相)

    输入信号经过滤波器后,都会经过极性选择的部分。在输入捕获模式下,极性选择用来选择上升沿有效或下降沿有效。编码器接口,始终是上升沿和下降沿都有效,所以在此模式下为高低电平的极性选择,如果选择上升沿的参数,就是信号直通,高低电平极性不反转;如果选择下降沿的参数,就是信号通过一个非门,高低电平极性反转。

    如果把TI1的高低电平反转一下,如下图。分析时需要先把TI1的高低电平取反,再查表。

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

    实例(TI1反相)

    5. 编码器接口测速

    与旋转编码器计次代码(【江协STM32】5 EXTI外部中断,第2.2节)实现的功能基本一致,本节所写代码本质上也是旋转编码器计次。但本节代码是通过定时器的编码器接口实现自动计次,可以节约软件资源。而之前的代码是通过触发外部中断,然后在中断函数中手动计次,当电机高速旋转时,编码器每秒产生大量脉冲,程序会频繁进入中断,占用软件资源。

    5.1 代码现象

    OLED显示旋转速度,向右慢速旋转,数值为正,计次比较小,向右快速旋转,计次增大,向左数值为负。

    5.2 接线图

    旋转编码器的A相接PA6(TIM3_CH1),B相接PA7(TIM3_CH2)

    5.3 代码

     编码器初始化步骤:

    1. RCC开启GPIO和定时器的时钟
    2. 配置GPIO,需要把PA6和PA7配置成输入模式
    3. 配置时基单元,PSC一般选择不分频,ARR一般给65535,只需要CNT执行计数
    4. 配置输入捕获单元,这里只有滤波器和极性两个参数有用
    5. 配置编码器接口模式
    6. 调用TIM_Cmd启动定时器

    GPIO的模式可以选择上拉、下拉或浮空。对于上拉和下拉的选择,一般可以看引脚的外部模块输出的默认电平,如果外部模块空闲默认输出高电平,就选择上拉输入,默认输入高电平;如果外部模块默认输出低电平,配置下拉输入,默认输入低电平。如果不确定外部模块输出的默认状态,或者外部信号输出功率非常小,尽量选择浮空输入。

    Encoder.c

    #include "stm32f10x.h"                  // Device header
    
    void Encoder_Init(void)
    {
        //	1、初始化时钟RCC TIM3
    	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);//	TIM2是APB1总线的外设
    	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);
    	//	选择时基单元的时钟源。不需要,因为编码器接口会托管时钟,所以这个内部时钟就没有用了
    //	TIM_InternalClockConfig(TIM3);//	因为定时器上电后默认就是使用内部时钟,所以此行可省略
    	
    	//	3、配置时基单元
    	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;//	指定时钟分频,设置的是输入滤波的采样频率,与时基单元关系不大。这里随便选一个
    	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//	计数器模式,选择向上计数
    	//	计数器溢出频率:CK_CNT_OV=CK_CNT/(ARR+1)=CK_PSC/(PSC+1)/(ARR+1)=72M/(PSC+1)/(ARR+1)
    	//	注意:PSC和ARR的取值都要在0~65535之间
    	TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1;//	自动重装器(ARR)的值
    	TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 1;//	预分频器(PSC)的值,不分频
    	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;//	重复计数器的值(高级定时器才有,不是CNT),不需要用,直接给0
    	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);//	刚初始化完会生成一次更新事件,会使Num刚复位就显示1
        
        //  4、初始化输入捕获单元
        TIM_ICInitTypeDef TIM_ICInitStruct;
        TIM_ICStructInit(&TIM_ICInitStruct);
        TIM_ICInitStruct.TIM_Channel = TIM_Channel_1;// 配置通道1
        TIM_ICInitStruct.TIM_ICFilter = 0xF;// 配置输入捕获的滤波器
    //    TIM_ICInitStruct.TIM_ICPolarity = TIM_ICPolarity_Rising;//   可以删掉,在配置编码器接口时也设置了该属性。边沿检测,选择极性。高低电平极性不反转。
        TIM_ICInit(TIM3, &TIM_ICInitStruct);
        TIM_ICInitStruct.TIM_Channel = TIM_Channel_2;// 配置通道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启动定时器
        TIM_Cmd(TIM3, ENABLE);
    }
    
    //  函数类型uint16_t(无符号数)时,旋转编码器从0反向转时会从65535减小
    //  把函数类型换成int16_t(有符号数)时,旋转编码器从0反向转时就会显示负数(借用补码特性)
    //  同时OLED显示函数需要由OLED_ShowNum换成OLED_ShowSignedNum
    int16_t Encoder_Get(void)
    {
        //  如果用编码器测速,可以在固定的闸门时间读一次CNT,然后把CNT清零
        int16_t Temp;
        Temp = TIM_GetCounter(TIM3);
        TIM_SetCounter(TIM3, 0);//  清零CNT
        return Temp;
    }
    

    Encoder.h

    #ifndef __ENCODER_H
    #define __ENCODER_H
    
    void Encoder_Init(void);
    int16_t Encoder_Get(void);
    
    #endif
    

    main.c

    #include "stm32f10x.h"                  // Device 
    #include "Delay.h"
    #include "OLED.h"
    #include "Timer.h"
    #include "Encoder.h"
    
    int main(void)
    {
    		OLED_Init();
            Encoder_Init();
    
    		OLED_ShowString(1, 1, "CNT:");
    		while(1)
    		{
                OLED_ShowSignedNum(1, 5, Encoder_Get(), 5);
                Delay_ms(1000);//   闸门时间1s。每隔一段时间Encoder_Get一次。如果主程序有其他代码,会堵塞程序的执行,因此最好用中断的方式。
    		}
    }

    上述主函数使用延时函数来控制闸门时间,可以实现功能。如果主程序有其他代码,会堵塞程序的执行,因此最好用中断的方式。优化后的代码如下:

    main.c

    #include "stm32f10x.h"                  // Device 
    #include "Delay.h"
    #include "OLED.h"
    #include "Timer.h"
    #include "Encoder.h"
    
    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);
    		}
    }
    
    //	定时器2中断函数,定时1s
    void TIM2_IRQHandler(void)
    {
    	if(TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)//	检查中断标志位
    	{
    		Speed = Encoder_Get();//    每隔1s读取一下速度,存在Speed变量里
    		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);//	清除中断标志位
    	}
    }
    

    其他引用的头文件和c代码可在此处查阅:OLED.h(【江协STM32】4 OLED调试工具,第5节)、  Delay.h(【江协STM32】3-2 LED闪烁&LED流水灯&蜂鸣器,第1.3节)、Timer.h(【江协STM32】6-1/2 TIM定时中断、定时器定时中断&定时器外部时钟,第2.2节)

    作者:冰糖雪莲IO

    物联沃分享整理
    物联沃-IOTWORD物联网 » 【江协STM32】6-7/8 TIM编码器接口、编码器接口测速

    发表回复