1.编码器接口简介
- Encoder Interface 编码器接口
- 编码器接口可接收增量(正交)编码器的信号,根据编码器旋转产生的正交信号脉冲,自动控制CNT自增或自减,从而指示编码器的位置、旋转方向和旋转速度
- 每个高级定时器和通用定时器都拥有1个编码器接口
- 两个输入引脚借用了输入捕获的通道1和通道2
2.正交编码器
- 编码器接口 相当于一个带有方向控制的外部时钟 它同时控制着CNT的计数时钟和计数方向
- 旋转速度:当编码器的旋转轴转起来时 A相和B相就会输出这样的方波信号 转得越快 这个方波的频率就越高 所以方波的频率就代表了速度
- 旋转方向:当正转时A相提前B相90度 反转时A相滞后B相90度 当然这个正转是A相提前还是A相滞后 并不是绝对的 这只是一个极性问题 毕竟正转和反转的定义也是相对的 总之就是朝一个方向转 是A相提前 另一个方向是A相滞后
3.编码器基本结构
- TI1FP1和TI2FP2分别对应定时器的CH1、CH2这两个引脚
-
输入捕获的前两个通道 通过GPIO口接入编码器的A、B相 然后通过滤波器和边沿检测极性选择 产生TI1FP1和TI2FP2 通向编码器接口 编码器接口通过预分频器控制CNT计数器的时钟 同时 编码器接口还根据编码器的旋转方向 控制CNT的计数方向 编码器正转时 CNT自增 编码器反转时 CNT自减
-
这里ARR是有效的 一般我们会设置ARR为65535(最大量程) 这样的话 利用补码的特性 很容易得到负数 比如CNT初始为0 正转 CNT自增,0、 1、2、3、4、5、6、7等等 反转时CNT自减 0下一个数就是65535 接着是65534 65533等等 这里负数不应该是-1、-2吗 所以直接把这个16位的无符号数转换为16位的有符号数 根据补码的定义 这个65535就对应-1 65534就对应-2(有符号编码时负数按补码计算,2^16 的补码= -1)等等 这样就可以直接得到负数 非常方便 这就是我们读出数据得到负数的一个小技巧
4.工作模式
5.实例
5.1 均不反相
- 红框内就是正交编码器抗噪声原理 由图可知 TI2没有发生变化 但TI1却跳变好几次 这不符合正交编码器的信号规律 然后通过上表逻辑 成功将这种毛刺信号滤掉 TI1为上升沿 TI2为低电平 查表可知此时为向上计数 即自增 接着看下一个状态 TI1为下降沿 TI2为低电平 查表可知此时为向下计数 即自减 所以这里一个引脚不变 另一个引脚来回跳动 计数器会来回加、减、加、减 尽管发生多次变化 但其值与原先值还是一样
5.2 TI1反相
- TI1反相的计数频率与5.1中的类似 只不过TI1真正的信号是与上图相反 如红色划线部分 后续也是如此 将其电位翻转后 通过查询表可知其计数方式
6.相关API
TIM_EncoderInterfaceConfig
void TIM_EncoderInterfaceConfig(TIM_TypeDef* TIMx, uint16_t TIM_EncoderMode,
uint16_t TIM_IC1Polarity, uint16_t TIM_IC2Polarity);
功能:
定时器编码器接口配置
参数:
TIMx:其中x可以为1、2、3、4、5或8,选择TIM外设
TIM_EncoderMode: TIMx编码器模式
TIM_IC1Polarity: IC1极性
TIM_IC2Polarity: IC2极性
//参数3、4可以配置成TIM_ICPolarity_Falling: IC下降沿(反相)、TIM_ICPolarity_Rising: IC上升沿(不反相)
返回值:
无
7.编码器接口测速
7.1 实现思路
- 第一步,RCC开启时钟,开启GPIO和定时器的时钟
- 第二步,配置GPIO,这里需要把PA6和PA7配置成输入模式
- 第三步,配置时基单元,这里预分频器我们一般选择不分频,自动重装一般给最大65535,只需要个CNT执行计数就行了
- 第四步,配置输入捕获单元。不过这里输入捕获单元只有滤波器和极性这两个参数有用,后面的参数没有用到,与编码器无关
- 第五步,配置编码器接口模式。这个直接调用一个库函数就可以了
7.2 接线图
7.3 相关代码
Encoder.c
#include "stm32f10x.h" // Device header
void Encoder_Init(void)
{
/*开启时钟*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//将PA6引脚初始化为上拉输入
//外部模块空闲默认输出高电平 选择上拉输入 默认输入高电平 外部模块空闲默认输出低电平 选择下拉输入 默认输入低电平 不过一般默认高电平 若不确定外部模块输出的默认状态或者外部模块输出功率非常小 这时就选择浮空输入模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;//这里TIM2要用来输出PWM 所以输入捕获的定时器需要换一个 暂时换到TIM3 由引脚定义表可以看到TIM3的通道1对应PA6 通道2对应PA7 通道3对应PB0 通道4对应PB1 这里选择TIM3的通道1 所以选择PA6引脚 这里需要配置上拉输入模式
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
/*配置时钟源*/
// 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 = 1 - 1;//预分频给0 就是不分频
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);
/*PWMI模式初始化*/
TIM_ICInitTypeDef TIM_ICInitStructure;
TIM_ICStructInit(&TIM_ICInitStructure);//该结构体最后两个参数在此无用处 因为这里只需配置滤波器和极性选择 但删除其他两个参数后可能显示结构体不完整 所以调用此函数给结构体赋一个初始值
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;//配置通道参数 这里选择通道1
TIM_ICInitStructure.TIM_ICFilter = 0xF;//用来配置输入捕获的滤波器 若信号有毛刺或噪声 可以增大滤波器参数 可以有效避免干扰
// TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;//对应图里的边沿选择、极性选择部分
//这里并不是上升沿有效 编码器接口始终都是上升沿、下降沿都有效 这里上升沿参数代表的是高低电平极性不反转
TIM_ICInit(TIM3,&TIM_ICInitStructure);//调用此函数后 上面结构体的配置就写入寄存器中 接着再配置通道2
TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;//配置通道参数 这里选择通道1
TIM_ICInitStructure.TIM_ICFilter = 0xF;//用来配置输入捕获的滤波器 若信号有毛刺或噪声 可以增大滤波器参数 可以有效避免干扰
// TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;//对应图里的边沿选择、极性选择部分
TIM_ICInit(TIM3,&TIM_ICInitStructure);//将第二个结构体参数配置写入寄存器中 配置通道2
TIM_EncoderInterfaceConfig(TIM3,TIM_EncoderMode_TI12,TIM_ICPolarity_Rising,TIM_ICPolarity_Rising);//编码器接口配置 第二个参数选择TI1和TI2都计数
//这里第三个参数和第四个参数与上面的极性选择配置重复 其功能一样 后定义的函数会覆盖前面的 所以前面的可以注释掉
TIM_Cmd(TIM3,ENABLE);
}
/**
* 函 数:获取编码器的增量值
* 参 数:无
* 返 回 值:自上此调用此函数后,编码器的增量值
*/
int16_t Encoder_Get(void)
{
/*使用Temp变量作为中继,目的是返回CNT后将其清零*/
int16_t Temp;
Temp = TIM_GetCounter(TIM3);
TIM_SetCounter(TIM3, 0);
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 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更新事件的中断标志位
//中断标志位必须清除
//否则中断将连续不断地触发,导致主程序卡死
}
}
现象:接上电源后OLED显示屏显示Speed:00000 旋转编码器 这里以顺时针转为正向 Speed的值会发生改变 旋转的越快数值越大 停止旋转则数值为0 逆时针旋转则为反方向 旋转越快数值越大 往负方向增大
注:若想改变旋转方向 即逆时针转为正向 顺时针转为反向
方法一:可以将A、B相接口接线互换位置
方法二:在Encoder.c的编码器配置函数中 第三个参数或者第四个参数选择反相即可 即一个选择Falling 另一个选择Rising 若两个选择一样则还是同向输出 类似于乘法(正正得正 正负得负 负负得正)