【RTT-Studio】详细使用教程九:EC12旋转编码器的使用

一、简介

旋转编码器简单来说,就是会输出2个PWM,依据相位可以知道旋转方向,依据脉冲个数可以知道旋转的角度。一般旋转一圈有一个固定数值的脉冲个数。旋转编码器广泛用于电机、或者角度传感器,STM32的定时器可以直接接入这两个波形获取到信息。
在这里插入图片描述
除了以顺时针方向和逆时针方向旋转旋钮外,编码器还有一个开关(低电平有效),按下内部的旋钮可以按下该开关。来自此开关的信号通过引脚3(Switch)获得。最后它有两个输出引脚。

1.增量编码器
(1)增量编码器给出两相方波,它们的相位差90°,通常称为A通道和B通道。
(2)其中一个通道给出与转速相关的信息,与此同时,通过两个通道信号进行顺序对比,得到旋转方向的信息。
(3)还有一个特殊信号称为Z或者零通道,该通道给出编码器的绝对零位,此信号是一个方波与A通道方波的中心线重合。
在这里插入图片描述
2.工作原理
两个信号相位差为90度,则这两个信号称为正交。由于两个信号相差90度,因此可以根据两个信号哪个先哪个后来判断方向、根据每个信号脉冲数量的多少及整个编码轮的周长就可以算出当前行走的距离、如果再加上定时器的话还可以计算出速度。

3.编码器接口
(1)编码器接口可接收增量(正交)编码器的信号
(2)根据编码器旋转产生的正交信号脉冲,自动控制CNT自增或自减
(3)指示编码器的位置、旋转方向和旋转速度 (PWM就是通计时器计次达到测频率的目的,而编码器是带方向的计次)
(4)每个高级定时器和通用定时器都拥有1个编码器接口(外部中断,是软件操作,定时器是硬件。硬件收到限制,比如stm32的高级和通用定时器一共就4个。编码器最多接4个。但是可以通过外部中断,来控制编码器手动自增自减。比如CNT计次,也可以通过中断的方式,实现CNT计次,然后手动把CNT取出来放到变量里)
(5)两个输入引脚借用了输入捕获的通道1和通道2
在这里插入图片描述
在这里插入图片描述

  • 编码器的输入是CH1,CH2.输出相当于是从模式控制器,控制CNT计数的控制速度和计数方向。
  • 如果出现了边沿信号,并且另外一项为正转,则控制CNT自增。否则控制CNT自减。
  • ARR为65535 正转最大值是65535。那么反转直接将无符号数据改为有符号对应的 65534就是-1

4.编码器接口的三种工作模式
一般情况适用最下面的计数方式,精度最高
在这里插入图片描述
5.正交编码器抗噪声原理
此图展示了,向上计数,和向下计数,以及抗噪声的原理。TI1 反相后,需要对TI1取反,才是正确的。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


二、使用定时器实现

1.Encoder.c

void Encode_Init(void)
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode =  GPIO_Mode_IPU;//上拉输入
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStruct);
	
	//配置时基单元
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
	TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;  //分频
	//TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;  //递增
	TIM_TimeBaseInitStruct.TIM_Period = 65536-1; //ARR
	TIM_TimeBaseInitStruct.TIM_Prescaler = 1-1;    //预分频器,psc 预分频值
	TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0;
    TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStruct);
    
	//初始化输入捕获单元
	TIM_ICInitTypeDef TIM_ICInitStruct;
	TIM_ICStructInit(&TIM_ICInitStruct);
	TIM_ICInitStruct.TIM_Channel =  TIM_Channel_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;
	TIM_ICInitStruct.TIM_ICFilter = 0xF;  //采样值越大,滤波的效果越好
	//TIM_ICInitStruct.TIM_ICPolarity = TIM_ICPolarity_Rising; //极性选择    上升沿,这里的上升沿标识的是高低电平不反转,对应的通道给上升沿就是不反向
	TIM_ICInit(TIM3,&TIM_ICInitStruct);
	
	TIM_EncoderInterfaceConfig(TIM3,TIM_EncoderMode_TI12,TIM_ICPolarity_Rising,TIM_ICPolarity_Rising);
	TIM_Cmd(TIM3,ENABLE);
}


//快速完成负数转换,引脚转换就会导致极性发生改变 ,随便一个极性改变,也导致反转
int16_t Encode_Get(void)
{
	int16_t Temp;
	Temp = TIM_GetCounter(TIM3);
	TIM_SetCounter(TIM3,0); //将CNT清零 ,读取TIM3时基单元种计数器的值, 然后将计数器清零
	return Temp;
}

2.中断函数

//定时中断    通过定时中断
 void TIM2_IRQHandler(void)
 {
	 if(TIM_GetITStatus(TIM2,TIM_FLAG_Update)== SET) //当前状态是TIM2的标志位
	 {
		 Speed = Encode_Get();  //得到这个计数值
		 TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
	 }
 }

三、使用中断实现

1.初始化使用的GPIO引脚
将引脚进行初始化,配置成上拉输入模式,默认为低电平。

#include "gpio.h"

#define PWR_B              GET_PIN(A, 15)          // 调节编码器的功率,B引脚
#define PWR_A              GET_PIN(C, 10)          // 调节编码器的功率,A引脚
#define PT_B               GET_PIN(C, 11)          // 调节编码器的脉宽,B引脚
#define PT_A               GET_PIN(C, 12)          // 调节编码器的脉宽,A引脚
#define IT_B               GET_PIN(D, 0)           // 调节编码器的时间间隔,B引脚
#define IT_A               GET_PIN(D, 1)           // 调节编码器的时间间隔,A引脚

/*=====================================================### 全局变量定义 ####=================================================*/
// GPIO引脚初始化常量表
const gpio_init_t GPIO_INIT_TAB[] =
{
    {"PWR-B",     PWR_B,     PIN_MODE_INPUT_PULLUP,  PIN_LOW,   PIN_HIGH },    // 调节编码器的功率,B引脚
    {"PWR-A",     PWR_A,     PIN_MODE_INPUT_PULLUP,  PIN_LOW,   PIN_HIGH },    // 调节编码器的功率,A引脚
    {"PT-B",      PT_B,      PIN_MODE_INPUT_PULLUP,  PIN_LOW,   PIN_HIGH },    // 调节编码器的脉宽,B引脚
    {"PT-A",      PT_A,      PIN_MODE_INPUT_PULLUP,  PIN_LOW,   PIN_HIGH },    // 调节编码器的脉宽,A引脚
    {"IT-B",      IT_B,      PIN_MODE_INPUT_PULLUP,  PIN_LOW,   PIN_HIGH },    // 调节编码器的占空比,B引脚
    {"IT-A",      IT_A,      PIN_MODE_INPUT_PULLUP,  PIN_LOW,   PIN_HIGH },    // 调节编码器的占空比,A引脚
};

// 定义一个GPIO引脚初始化列表计数值
const rt_uint32_t GPIO_INIT_TAB_CNT = ARRAY_CNT(GPIO_INIT_TAB);
/*=====================================================#######  END  #######=================================================*/

/*======================================================### 静态函数调用 ###==================================================*/
/**
 * @brief  获取GPIO引脚的ID号
 * @param  tbs    : 传入的数组
 * @param  tbs_cnt: 数组的大小
 * @param  name   : GPIO的ID号
 * @return 返回引脚的ID号   -1:传递参数有误
 */
static rt_int32_t GPIO_GetPin_ID(void *tbs, rt_uint32_t tbs_cnt, char *name)
{
    rt_uint32_t i;
    gpio_init_t *gpio_tbs = RT_NULL;

    if (tbs == RT_NULL || tbs_cnt != GPIO_INIT_TAB_CNT || name == RT_NULL)
    {
        return -RT_ERROR;
    }

    gpio_tbs = (gpio_init_t *) tbs;

    // 遍历数组列表查找
    for (i = 0; i < tbs_cnt; i++)
    {
        if (gpio_tbs[i].name == RT_NULL)
        {
            continue;
        }

        if (strcmp_nocase(gpio_tbs[i].name, name) != 0)
        {
            continue;
        }

        return i;
    }

    return -RT_ERROR;
}

/**
 * @brief 获取GPIO引脚的状态
 * @param tbs    : 传入的数组
 * @param tbs_cnt: 数组的大小
 * @param index  : GPIO的ID号
 * @return -1:读取失败   0:低电平  1:高电平
 */
static rt_int32_t GPIO_GetPin_State(void *tbs, rt_uint32_t tbs_cnt, rt_uint32_t index)
{
    gpio_init_t *gpio_tbs = RT_NULL;
    rt_int32_t pinState = -1;

    if(tbs == RT_NULL || tbs_cnt != GPIO_INIT_TAB_CNT || index > (GPIO_INIT_TAB_CNT - 1))
    {
        return -RT_ERROR;
    }

    gpio_tbs = (gpio_init_t *)tbs;
    pinState = rt_pin_read(gpio_tbs[index].pin);

    if(-1 == pinState)
    {
        return -RT_ERROR;
    }

    return pinState;
}

/**
 * @brief 设置GPIO引脚新的活跃状态
 * @param tbs    : 传入的数组
 * @param tbs_cnt: 数组的大小
 * @param index  : GPIO的ID号
 * @param active : 新的活跃状态 1:活跃 0:初始
 * @return -1:设置失败   0:设置成功
 */
static rt_err_t GPIO_SetPin_Active(void *tbs, rt_uint32_t tbs_cnt, rt_uint32_t index, rt_uint8_t active)
{
    gpio_init_t *gpio_tbs = RT_NULL;

    if (tbs == RT_NULL || tbs_cnt != GPIO_INIT_TAB_CNT || index > (GPIO_INIT_TAB_CNT - 1))
    {
        return -RT_ERROR;
    }

    gpio_tbs = (gpio_init_t *) tbs;

    if (gpio_tbs[index].mode != PIN_MODE_OUTPUT)
    {
        return -RT_ERROR;
    }

    if (-1 == rt_pin_read(gpio_tbs[index].pin))
    {
        return -RT_ERROR;
    }

    // 给GPIO引脚写入新的状态
    rt_pin_write(gpio_tbs[index].pin, (rt_base_t) (active ? gpio_tbs[index].active_state : gpio_tbs[index].init_state));

    return RT_EOK;
}
/*=====================================================#######  END  #######=================================================*/

/*======================================================##### 外部调用 #####==================================================*/
/**
 * @brief  初始化GPIO引脚
 * @return 默认返回0
 */
rt_uint8_t GPIO_Tab_Init(void)
{
    rt_uint32_t i;

    for (i = 0; i < GPIO_INIT_TAB_CNT; i++)
    {
        if (GPIO_INIT_TAB[i].name == RT_NULL)
        {
            continue;
        }

        if (-1 == rt_pin_read(GPIO_INIT_TAB[i].pin))
        {
            continue;
        }

        // 设置输出模式的初始状态
        if ((GPIO_INIT_TAB[i].mode == PIN_MODE_OUTPUT) || (GPIO_INIT_TAB[i].mode == PIN_MODE_OUTPUT_OD))
        {
            rt_pin_write(GPIO_INIT_TAB[i].pin, GPIO_INIT_TAB[i].init_state);
        }

        rt_pin_mode(GPIO_INIT_TAB[i].pin, GPIO_INIT_TAB[i].mode);
    }

    return RT_EOK;
}

/**
 * @brief  获取引脚状态
 * @param  name: 引脚名称
 * @return -1:读取失败   0:低电平  1:高电平
 */
rt_int32_t get_pin_status(char *name)
{
    rt_int32_t ID = GPIO_GetPin_ID((void *)&GPIO_INIT_TAB, GPIO_INIT_TAB_CNT, name);
    if (-1 == ID)
    {
        rt_kprintf("[%s:%d] Get id error!\n", __FUNCTION__, __LINE__);
        return -RT_ERROR;
    }

    return GPIO_GetPin_State((void *)&GPIO_INIT_TAB, GPIO_INIT_TAB_CNT, ID);
}

/**
 * @brief  设置引脚状态
 * @param  name  : 引脚名称
 * @param  active: 新的活跃状态 1:活跃   0:初始
 * @return -1:设置失败   0:设置成功
 */
rt_int32_t set_pin_status(char *name, rt_uint8_t active)
{
    rt_int32_t ID = GPIO_GetPin_ID((void *)&GPIO_INIT_TAB, GPIO_INIT_TAB_CNT, name);
    if (-1 == ID)
    {
        rt_kprintf("[%s:%d] Get id error!\n", __FUNCTION__, __LINE__);
        return -RT_ERROR;
    }

    return GPIO_SetPin_Active((void *)&GPIO_INIT_TAB, GPIO_INIT_TAB_CNT, ID, active);
}

/**
 * @brief 获取GPIO引脚是否是活跃状态
 * @param tbs     传入的数组
 * @param tbs_cnt 数组的大小
 * @param index   GPIO的ID号
 * @return -1:获取失败   0:初始状态  1:活跃状态
 */
rt_uint32_t GPIO_GetPin_Active(void *tbs, rt_uint32_t tbs_cnt, rt_uint32_t index)
{
    gpio_init_t *gpio_tbs = RT_NULL;
    rt_int32_t pinState = -1;

    if (tbs == RT_NULL || tbs_cnt != GPIO_INIT_TAB_CNT || index > (GPIO_INIT_TAB_CNT - 1))
    {
        return -RT_ERROR;
    }

    gpio_tbs = (gpio_init_t *) tbs;
    pinState = rt_pin_read(gpio_tbs[index].pin);

    if (-1 == pinState)
    {
        return -RT_ERROR;
    }

    // 判断是否是活跃状态
    if (pinState == gpio_tbs[index].active_state)
    {
        return 1;
    }
    else
    {
        return 0;
    }
}
/*======================================================#######  END  #######=================================================*/

2.配置引脚中断
将引脚配置成中断模式,并且初始化中断服务函数。

/**
 * @brief ec12编码器初始化
 */
void ec12_GPIO_Init(void)
{
    g_ec12.IT_encoder_cnt = EC12_DEFAULT_COUNT;
    g_ec12.PT_encoder_cnt = EC12_DEFAULT_COUNT;
    g_ec12.PWR_encoder_cnt = EC12_DEFAULT_COUNT;
    g_ec12.IT_direction = 0;
    g_ec12.PT_direction = 0;
    g_ec12.PWR_direction = 0;

    // 绑定中断
    rt_pin_attach_irq(PT_A,  PIN_IRQ_MODE_FALLING, (void *)callback_ec12_PT, RT_NULL);
    rt_pin_attach_irq(PWR_A, PIN_IRQ_MODE_FALLING, (void *)callback_ec12_PWR, RT_NULL);
    rt_pin_attach_irq(IT_A,  PIN_IRQ_MODE_FALLING, (void *)callback_ec12_IT, RT_NULL);

    // 使能中断
    rt_pin_irq_enable(PT_A, PIN_IRQ_ENABLE);
    rt_pin_irq_enable(PWR_A, PIN_IRQ_ENABLE);
    rt_pin_irq_enable(IT_A, PIN_IRQ_ENABLE);
}

四、完整代码

1.ec12.c

#include "ec12.h"

/*======================================================### 静态函数调用 ###==================================================*/
/**
 * @brief ec12功率中断回调函数
 */
void callback_ec12_PWR(void)
{
    rt_int32_t pinState = ec12_PWR_B_in;

    // 正转
    if (pinState == 0)
    {
        g_ec12.PWR_direction = 1;
        g_ec12.PWR_encoder_cnt += 1;
        if (g_ec12.PWR_encoder_cnt >= EC12_COUNT_MAX_PWR)
        {
            g_ec12.PWR_encoder_cnt = EC12_COUNT_MAX_PWR;
        }
    }
    // 反转
    else
    {
        g_ec12.PWR_direction = 0;
        if (g_ec12.PWR_encoder_cnt > EC12_DEFAULT_COUNT)
        {
            g_ec12.PWR_encoder_cnt -= 1;
        }

        if (g_ec12.PWR_encoder_cnt <= EC12_DEFAULT_COUNT)
        {
            g_ec12.PWR_encoder_cnt = EC12_DEFAULT_COUNT;
        }
    }
}

/**
 * @brief ec12脉宽中断回调函数
 */
void callback_ec12_PT(void)
{
    rt_int32_t pinState = ec12_PT_B_in;

    // 正转
    if (pinState == 0)
    {
        g_ec12.PT_direction = 1;

        // 脉宽小于时间间隔
        if (g_ec12.PT_encoder_cnt < g_ec12.IT_encoder_cnt)
        {
            g_ec12.PT_encoder_cnt += 1;
            if (g_ec12.PT_encoder_cnt >= g_ec12.IT_encoder_cnt)
            {
                g_ec12.PT_encoder_cnt = g_ec12.IT_encoder_cnt;
            }
        }

        if (g_ec12.PT_encoder_cnt >= EC12_COUNT_MAX_PT)
        {
            g_ec12.PT_encoder_cnt = EC12_COUNT_MAX_PT;
        }
    }
    // 反转
    else
    {
        g_ec12.PT_direction = 0;
        if (g_ec12.PT_encoder_cnt > EC12_DEFAULT_COUNT)
        {
            g_ec12.PT_encoder_cnt -= 1;
        }

        if (g_ec12.PT_encoder_cnt <= EC12_DEFAULT_COUNT)
        {
            g_ec12.PT_encoder_cnt = EC12_DEFAULT_COUNT;
        }
    }
}

/**
 * @brief ec12时间间隔中断回调函数
 */
void callback_ec12_IT(void)
{
    rt_int32_t pinState = ec12_IT_B_in;

    // 正转
    if (pinState == 0)
    {
        g_ec12.IT_direction = 1;
        g_ec12.IT_encoder_cnt += 1;
        if (g_ec12.IT_encoder_cnt >= EC12_COUNT_MAX_IT)
        {
            g_ec12.IT_encoder_cnt = EC12_COUNT_MAX_IT;
        }
    }
    // 反转
    else
    {
        g_ec12.IT_direction = 0;
        if (g_ec12.IT_encoder_cnt > EC12_DEFAULT_COUNT)
        {
            g_ec12.IT_encoder_cnt -= 1;
        }

        if (g_ec12.IT_encoder_cnt <= EC12_DEFAULT_COUNT)
        {
            g_ec12.IT_encoder_cnt = EC12_DEFAULT_COUNT;
        }

        if (g_ec12.IT_encoder_cnt < g_ec12.PT_encoder_cnt)
        {
            g_ec12.PT_encoder_cnt = g_ec12.IT_encoder_cnt;
        }
    }
}

/**
 * @brief ec12编码器初始化
 */
void ec12_GPIO_Init(void)
{
    g_ec12.IT_encoder_cnt = EC12_DEFAULT_COUNT;
    g_ec12.PT_encoder_cnt = EC12_DEFAULT_COUNT;
    g_ec12.PWR_encoder_cnt = EC12_DEFAULT_COUNT;
    g_ec12.IT_direction = 0;
    g_ec12.PT_direction = 0;
    g_ec12.PWR_direction = 0;

    // 绑定中断
    rt_pin_attach_irq(PT_A,  PIN_IRQ_MODE_FALLING, (void *)callback_ec12_PT, RT_NULL);
    rt_pin_attach_irq(PWR_A, PIN_IRQ_MODE_FALLING, (void *)callback_ec12_PWR, RT_NULL);
    rt_pin_attach_irq(IT_A,  PIN_IRQ_MODE_FALLING, (void *)callback_ec12_IT, RT_NULL);

    // 使能中断
    rt_pin_irq_enable(PT_A, PIN_IRQ_ENABLE);
    rt_pin_irq_enable(PWR_A, PIN_IRQ_ENABLE);
    rt_pin_irq_enable(IT_A, PIN_IRQ_ENABLE);
}

2.ec12.h

#ifndef APPLICATIONS_HARDWARE_INC_EC12_H_
#define APPLICATIONS_HARDWARE_INC_EC12_H_

#include <rtthread.h>
#include <rtdevice.h>
#include <drv_common.h>
#include "gpio.h"

/**=====================================================###### 宏定义 ######==================================================*/
#define EC12_DEFAULT_COUNT          1000                // 编码器默认计数值
#define EC12_COUNT_MAX_PT           1100                // 脉宽最大计数值
#define EC12_COUNT_MAX_PWR          1700                // 功率最大计数值
#define EC12_COUNT_MAX_IT           1999                // 时间间隔最大计数值

#define ec12_PT_B_in    rt_pin_read(PT_B)               // 读取脉宽B引脚状态
#define ec12_PT_A_in    rt_pin_read(PT_A)               // 读取脉宽A引脚状态
#define ec12_PWR_B_in   rt_pin_read(PWR_B)              // 读取功率B引脚状态
#define ec12_PWR_A_in   rt_pin_read(PWR_A)              // 读取功率A引脚状态
#define ec12_IT_B_in    rt_pin_read(IT_B)               // 读取时间间隔B引脚状态
#define ec12_IT_A_in    rt_pin_read(IT_A)               // 读取时间间隔A引脚状态
/**=====================================================#######  END  #######=================================================*/

/**=====================================================### 全局变量定义 ####=================================================*/
struct ec12_info
{
    rt_uint32_t IT_encoder_cnt;        // 时间间隔旋转计数
    rt_uint8_t  IT_direction;          // 旋转方向 1正传 0反转

    rt_uint32_t PWR_encoder_cnt;       // 功率旋转计数
    rt_uint8_t  PWR_direction;         // 旋转方向 1正传 0反转

    rt_uint32_t PT_encoder_cnt;        // 脉宽旋转计数
    rt_uint8_t  PT_direction;          // 旋转方向 1正传 0反转

};

struct ec12_info g_ec12;                 // 编码器相关信息
/**=====================================================#######  END  #######=================================================*/

/**=====================================================##### 函数声明 #####==================================================*/
extern void ec12_GPIO_Init(void);       // ec12编码器初始化
extern void Adjust_Display_Value(void); // 调节LED数码管显示值
/**=====================================================#######  END  #######=================================================*/

#endif /* APPLICATIONS_HARDWARE_INC_EC12_H_ */

五、测试验证

通过使用上面的代码下载后进行验证,当正转时显示:IT +++;当反转时显示:IT —。
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值