EC11旋转编码器STC_51单片机 示例程序 稳定版
最近对单片机重新拾回了兴趣,吃灰了多年的STC-51开发板再次复活。很早以前就接触过这个编码器了,但是一直都没有研究过,今天就简单记录一下学习EC11旋转编码器的过程。关于这个EC11编码器,最近也看了挺多别人的代码,愣是代入不了,所以才决定自己琢磨琢磨,顺手分享给有兴趣小伙伴。
目录
前言
前言
最近对单片机重新拾回了兴趣,吃灰了多年的STC-51开发板再次复活。很早以前就接触过这个编码器了,但是一直都没有研究过,今天就简单记录一下学习EC11旋转编码器的过程。关于这个EC11编码器,最近也看了挺多别人的代码,愣是代入不了,所以才决定自己琢磨琢磨,顺手分享给有兴趣小伙伴。
1.EC11旋转编码器概览
市面上的开关编码器种类繁多,有各种各样的,有脉冲数不一样的,有带开关的,有半轴的,全轴的,带滚花的,在外观和参数上面有差别,但是基本原理可以说是一模一样。下面就以我手上的20P/360°不带开关的为例。(解释一下20P/360°,指的是旋转360°发生20个周期脉冲)
2.EC11旋转编码器原理
上图为编码器的原理图,A端子和B端子分别接单片机的IO口,C端子接地。如果是准双向IO,上拉电阻可省略,如果电源纹波较大,且程序无消抖处理,建议AB端子各加一个104的滤波电容。
EC11旋转编码器的基本工作原理即是通过旋转轴来拨动编码器内的两个开关,使两个开关周期性开合从而实现两组带有相位差的周期性的信号输出。而MCU则是通过编程来识别这两组信号的差异从而识别不同的信号输入。
与一般的开关相比,这类编码器的优点即是可以连续不间断丝滑的输入信号,对于需要连续输入的场景尤其方便,而且体验更好。
3.EC11旋转编码器输出波形图
看了这个波形图,我们不难看出,EC11编码器一个周期,不外乎就四种状态,下面就列个表格分简单分析一下
4.EC11旋转编码器“时序”分析
正旋时序 | |||||
A相 | 0 | 1 | 1 | 0 | 0 |
B相 | 0 | 0 | 1 | 1 | 0 |
反旋时序 | |||||
A相 | 0 | 0 | 1 | 1 | 0 |
B相 | 0 | 1 | 1 | 0 | 0 |
有了这个时“序表”,那么我们的思路就更清晰了,大概来说就两步:
第一步:判断编码器的转向。这个看时序表就很清晰了,一个周期内所有的状态都能找到唯一性,那么从旋钮静止开始旋转,A=0,B=1,就是可以认定为正旋,A=1,B=0就认定为反旋。当然这里的状态和单片机IO的状态是刚好相反的,这点要注意,这里的0指的是断开的状态,而不是电平,开关断开时单片IO高电平,开关闭合IO低电平,这点要理清楚。
void EC11_Driver()
{
static bit turn_R; //检测EC11正转标志位
static bit turn_L; //检测EC11反转标志位
static bit turn_on; //检测EC11动作标志位
static bit turn_on_last; //备份EC11上一次动作值
/**********************EC11状态分析**********************/
if(EC11_A_Now&&!EC11_B_Now) //正转状态
{
turn_R = 1; //正转标志
}
else if(!EC11_A_Now&&EC11_B_Now) //反转状态
{
turn_L = 1; //反转标志
}
else if(!EC11_A_Now&&!EC11_B_Now) //旋转状态
{
turn_on = 1; //旋转标记
}
else //起始状态 编码器未旋转或旋转到起始状态
{
turn_on = 0; //旋转标志复位
turn_R = 0; //正转标志复位
turn_L = 0; //反转标志复位
}
第二步:判断编码器的状态。这个也好处理,编码器一个周期是不是也类似与轻触按键的一次按压周期呢?轻触开关按一次的过程:0 - 1 - 0.编码电位器旋转一格的过程:00 - 11 - 00.是不是简直一模一样,只不过旋转编码器是两路开关而已,那这部分的处理方式就参考处理独立按键的方式就完全没问题了。
/**********************EC11状态处理**********************/
if(turn_R) //判定为正转状态 进行正转处理
{
if(turn_on != turn_on_last) //如果编码器转动
{
if(turn_on_last == 0) //如果编码器上次值为0
{ //则该当前值为1
EC11_Action(EC11_A_Code); //执行动作函数 传送正转值
}
}
turn_on_last = turn_on; //更新编码器状态
}
else if(turn_L) //判定为正转状态 反转处理(原理同正转)
{
if(turn_on != turn_on_last) //如果编码器转动
{
if(turn_on_last == 0) //如果编码器上次值为0
{ //则该当前值为1
EC11_Action(EC11_B_Code); //执行动作函数 传送反转值
}
}
turn_on_last = turn_on; //更新编码器状态
}
具体的编程思路就这样:先判断旋转方向,再确认旋钮动作,之后该干嘛干嘛。
5.关于消抖
如果AB引脚接了滤波电容的情况下,并且主循环时间很短,那么程序消抖这段直接跳过也没啥问题。但是呢如果没有接滤波电容,为了保证旋钮的稳定性和可靠性那么还是建议程序做下消抖处理。下面以旋转速度20P/360°/s为例,AB相位差为1/5~1/8个周期,按最小1/8个周期来算,那么最小AB相位时差T等于:
T = 1*1000/20/8= 6.25ms
那么这个T(相位时差)有什么用呢?下面我们在扫描消抖的时候就会用到,转到下面的IO扫描即消抖程序:
/******************EC11_IO状态扫描及消抖*******************/
void EC11_Scan()
{
static unsigned char EC11_A_Buf; //声明静态缓存变量
static unsigned char EC11_B_Buf; //声明静态缓存变量
EC11_A_Buf = ((EC11_A_Buf << 1) & 0x03) | EC11_A; //将编码器IO状态送入缓存
EC11_B_Buf = ((EC11_B_Buf << 1) & 0x03) | EC11_B; //连续三次都为0则判定为0
if(EC11_A_Buf == 0x00) //编码器A引脚电平判定
EC11_A_Now = 0;
else //否则
EC11_A_Now = 1;
if(EC11_B_Buf == 0x00) //编码器B引脚电平判定
EC11_B_Now = 0;
else //否则
EC11_B_Now = 1;
}
这段放在1ms定时中断处理,即扫描周期为1ms,连续3次判断IO口的值都为0时才确认当前为低电平。即完成一次判断的时间(判定周期)为:
判定周期 = 3*扫描周期
那么为了准确判断AB脚IO口电平的状态,扫描周期与判定周期必须满足:
扫描周期<判定周期<AB相位时差T
满足了这个条件就可以更加准确的判断AB脚IO口的当前状态,有效减少误判。下面将完整的STC_51的示例程序分享给感兴趣的小伙伴。
6.完整STC_51演示程序
/************************************************************/
/******************EC11旋转编码电位器例程********************/
/************************************************************/
#include<reg52.h>
/*sbit SMG0 = P2^3; //声明数码管位选 个
sbit SMG1 = P2^2; //声明数码管位选 十
sbit SMG2 = P2^1; //声明数码管位选 百
sbit SMG3 = P2^0; //声明数码管位选 千*/
sbit EC11_A = P2^7; //声明编码器A脚IO
sbit EC11_B = P2^6; //声明编码器B脚IO
bit EC11_A_Now = 1; //编码器A脚稳定值
bit EC11_B_Now = 1; //编码器B脚稳定值
unsigned char T0RH; //定时器0高八位初值
unsigned char T0RL; //定时器0低八位初值
signed int count; //数码管演示变量
signed int EC11_A_Code = 1; //EC11编码器正转一格键值
signed int EC11_B_Code = -1; //EC11编码器反转一格键值
/***********************数码管段码**********************/
unsigned char code Smg_Char[16] =
{0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,
0x80,0x90,0x88,0x83,0xc6,0xa1,0x86,0x8e};
/*********************数码管显示缓存********************/
unsigned char pdata Smg_buf[4] = {0xff,0xff,0xff,0xff};
/**********************子函数声明***********************/
void EC11_Driver(); //EC11驱动函数声明
void EC11_Action(signed char num); //EC11动作函数声明
void Smg_Disp(unsigned int count); //数码管显示函数声明
void Config_T0(unsigned char ms); //定时器0中断配置函数声明
/*************************主函数************************/
void main()
{
EA = 1; //总中断允许位
Config_T0(1); //配置定时器0 1ms中断
Smg_Disp(0); //上电数码管显示0000
while(1) //主循环
{
EC11_Driver(); //EC11编码器驱动函数
Smg_Disp(count); //数码管显示变量当前值
}
}
/**********************EC11动作函数**********************/
void EC11_Action(signed char num)
{
count += num; //演示变量 加减 编码器正旋+1反旋减1
}
/**********************EC11驱动函数**********************/
void EC11_Driver()
{
static bit turn_R; //检测EC11正转标志位
static bit turn_L; //检测EC11反转标志位
static bit turn_on; //检测EC11动作标志位
static bit turn_on_last; //备份EC11上一次动作值
/**********************EC11状态分析**********************/
if(EC11_A_Now&&!EC11_B_Now) //正转状态
{
turn_R = 1; //正转标志
}
else if(!EC11_A_Now&&EC11_B_Now) //反转状态
{
turn_L = 1; //反转标志
}
else if(!EC11_A_Now&&!EC11_B_Now) //旋转状态
{
turn_on = 1; //旋转标记
}
else //起始状态 编码器未旋转
{ //或旋转到起始状态
turn_on = 0; //旋转标志复位
turn_R = 0; //正转标志复位
turn_L = 0; //反转标志复位
}
/**********************EC11状态处理**********************/
if(turn_R) //判定为正转状态 进行正转处理
{
if(turn_on != turn_on_last) //如果编码器转动
{
if(turn_on_last == 0) //如果编码器上次值为0
{ //则该当前值为1
EC11_Action(EC11_A_Code); //执行动作函数 传送正转值
}
}
turn_on_last = turn_on; //更新编码器状态
}
else if(turn_L) //判定为正转状态 反转处理
{
if(turn_on != turn_on_last) //如果编码器转动
{
if(turn_on_last == 0) //如果编码器上次值为0
{ //则该当前值为1
EC11_Action(EC11_B_Code); //执行动作函数 传送反转值
}
}
turn_on_last = turn_on; //更新编码器状态
}
else //不旋转时复位
{
turn_on_last = 0; //编码器最后动作状态复位
}
}
/******************EC11_IO状态扫描及消抖*******************/
void EC11_Scan()
{
static unsigned char EC11_A_Buf; //声明静态缓存变量
static unsigned char EC11_B_Buf; //声明静态缓存变量
EC11_A_Buf = ((EC11_A_Buf << 1) & 0x03) | EC11_A; //将编码器IO状态送入缓存
EC11_B_Buf = ((EC11_B_Buf << 1) & 0x03) | EC11_B; //连续三次都为0则判定为0
if(EC11_A_Buf == 0x00) //编码器A引脚电平判定
EC11_A_Now = 0;
else //否则
EC11_A_Now = 1;
if(EC11_B_Buf == 0x00) //编码器B引脚电平判定
EC11_B_Now = 0;
else //否则
EC11_B_Now = 1;
}
/**********************数码管分位显示**********************/
void Smg_Disp(unsigned int count)
{
Smg_buf[0] = Smg_Char[count%10]; //个
Smg_buf[1] = Smg_Char[count/10%10]; //十
Smg_buf[2] = Smg_Char[count/100%10]; //百
Smg_buf[3] = Smg_Char[count/1000%10]; //千
}
/**********************数码管扫描函数**********************/
void Smg_Scan()
{
static unsigned char i = 0;
unsigned char P2_buf = 0xF7;
P0 = 0xFF; //消隐
P2_buf = P2_buf>>i; //
P2_buf &= 0x0F; //
P2 &= 0xF0; //P2高位保持 低位复位
P2 |= P2_buf; //数码管位码送P2
P0 = Smg_buf[i]; //数码管缓存送P0口
i++; //索引++
i &= 0x03; //索引=4归零
}
/**********************配置定时器0中断*********************/
void Config_T0(unsigned char ms)
{
unsigned int tmp; //初值计算缓存
tmp = 65536 - 11059200/12/1000*ms; //初值计算
T0RH = (unsigned char)(tmp>>8); //初值分配高八位
T0RL = (unsigned char)tmp; //初值分配低八位
TMOD &= 0xF0; //定时器0复位
TMOD |= 0x01; //定时器0方式选择1
TH0 = T0RH; //高八位初值
TL0 = T0RL; //低八位初值
ET0 = 1; //定时器0中断允许位
TR0 = 1; //打开定时器0
}
/**********************定时器0中断服务********************/
void Interrupt_T0() interrupt 1
{
TH0 = T0RH; //重装高八位初值
TL0 = T0RL; //重装低八位初值
EC11_Scan(); //EC11编码器扫描
Smg_Scan(); //数码管动态扫描
}
/***************************恩典**************************/
/********************END********************/
更多推荐
所有评论(0)