抛开工作,以电子爱好者的身份,单片机玩多了都会想着在单片机的外围设备玩出一些花样来。
这其中首先想到的就是“升级”输入输出设备。
这里先说一说我们常给单片机使用的输入设备。
以上这些输入设备,不是按键就是电位器。厌倦了在矩阵键盘里一个个的找按键,也厌倦了使用ADC扫描的方式来读取输入值。就只有拨轮开关和旋转编码器最简洁了。其中拨轮开关本质上就是三个按键,就只剩下旋转编码器看起来比较有逼格了。
一开始我只是在示波器和数控电源上见到过旋转编码器,那时候我听人说这叫数字电位器,就觉得很神奇,是什么结构竟然可以让一个“电位器”无限的转下去。后来才知道汽车的车机上用的也是旋转编码器。
接着在一乐看到的V8电子负载,V9电源,以及一些白光焊台上也用的旋转编码器之后我就深深的喜欢上了这个精巧的小玩意。不多废话,下面来了解一下旋转编码器。
旋转编码器作为人机交互的输入设备,最常用的是EC11(类似的还有EC12、EC16等)
ALPS旋转编码器选型手册.EC11实物图.垂直型
施工中——ALPS旋转编码器选型手册.EC11实物图.侧装型
ALPS旋转编码器选型手册.EC11垂直型机械尺寸图
ALPS旋转编码器选型手册.EC11引脚图
ALPS旋转编码器选型手册.EC11内部触点开关结构图
ALPS旋转编码器选型手册.EC11输出时序图
EC11结构上由编码器部分和按键部分(带按键的型号)组合而成,编码器部分有A,B,C三个引脚。按键部分有D,E两个引脚,为独立按键。
EC11测试电路原理图.接外部上拉电阻.适用于浮空输入和输入上拉模式的IO口
EC11测试电路原理图.无外部上拉电阻.几乎所有单片机IO口都可以设置为输入上拉模式,就可以省略EC11的外部上拉电阻
在研究EC11的时序之前首先要了解一点,EC11按旋转的输出动作可以分为两种。一种是转两格,A、B对C端输出一个完整脉冲(转一格就只是由低电平->高电平或由高电平->低电平);另一种就是转一格,A、B对C端输出一个完整脉冲。
一定位一脉冲EC11转动一格输出波形图
一定位一脉冲的EC11按测试电路图的接法,在静止的时候AB两线输出都是高电平。转动一格,AB两线各自输出一个低电平脉冲,然后又回到高电平状态。对应于EC11内部AB两个触点开关的动作为断开-->闭合-->断开。
两定位一脉冲EC11转动一格输出波形图.下降沿
两定位一脉冲EC11转动一格输出波形图.上升沿
两定位一脉冲的EC11稍微复杂一些,转动一格只会输出半个脉冲。静止时,AB触点开关可以是断开的也可以是闭合的。
当然了,有一些质量比较差的EC11会有一些额外的问题要考虑,例如开关的抖动问题,例如转动定位不清晰,静止时AB两个触点都要闭合或者都要断开才对,但是定位点不清晰,转动的角度不到位导致一个触点已经闭合(断开)了,另一个触点却还保持在断开(闭合)。对于这些问题我们在后面再做考虑。
在淘宝上搜EC11可以看到一些买家描述为
30定位15脉冲
20定位数20脉冲
以上指的就是两定位一脉冲或者一定位一脉冲类型的EC11
要辨别的话也很简单,拿一个位未知类型的EC11,看准一个点转一圈,变转圈边数这一圈有多少格就知道了。一般转一圈有30格的都是两定位一脉冲的类型,20格的都是一定位一脉冲的类型。
有些汽车车机上用来调音量的EC11是无步进的,旋转的时候是均匀的阻尼感而不会有一格一格的步进手感,这种时候想辨别就需要用一个万用表了。如果是一定位一脉冲的类型,不转动的时候A,B于C端都不导通。如果是两定位一脉冲的类型,会有A,B与C端导通和A,B与C端不导通两种情况。稍微转一下转轴然后测量,若A,B与C端都导通,那么就是两定位一脉冲类型。
虽然这种EC11无步进手感,但是大多数也是转一圈输出15脉冲(30格)或20脉冲(20格)的类型。
除此之外,大多数用的EC11是带独立按键的,可以实现单按键或者按下按键转动的功能。但是我也遇到过一些国产的(主要是特别便宜的)EC11的结构不同会使得按下按键转动的时候,按键会有送开的过程,这时候随着EC11的转轴转动,按键输出端会出现开-关-开的过程。即市面上有少部分国产的(并且是便宜货)EC11编码器无法实现按着按键转动。
EC11按下按键转动时序图
当然,EC11也有长得不常规的,这类EC11由于按键和转轴的连接结构不同,也是可以按下按键转动的。
【施工中——松下滑动式旋转编码器.型号不详】
【施工中——松下滑动式旋转编码器.拆解图】
从结构来看,这款旋转编码器的按键部分和编码器部分是分离的,并且按键受转轴挤压按下时,转轴仍可正常转动而不影响按键。
也还有一些是用于便携消费电子产品的EC11。例如松下的EVQWK系列拨盘编码器。
【施工中——松下EVQWK拨盘编码器.具体型号不详】
【施工中——松下EVQWK拨盘编码器.数据手册】
【施工中——松下EVQWK拨盘编码器.机械结构图】
要写驱动程序,得先了解EC11的工作过程。使用逻辑分析仪(LA)抓取时序可以很方便的从单片机的角度了解EC11的工作过程并依此来编写驱动程序。
EC11的编码器部分有3个引脚,A,B,和C。通常可以把C端接GND,A,B端接到输入上拉模式的IO口。可以取A或B任意一根线作为时钟线,另一根作为信号输出线。我个人习惯把A线作为时钟线,B线作为信号线。
本文中出现的逻辑分析仪抓取的时序图中均是最上方通道为EC11的A线,视为时钟;下方一个通道为EC11的B线,视为数据输出。
由于不同厂家的A,C,B引脚顺序可能是反的,没有数据手册就只能看时序。除了EC11EH这个型号,其余的EC11正转时A引脚的输出脉冲相位超前于B引脚。反转则相反,A滞后于B。
某国产侧装型EC11
ALPS侧装型EC11
一定位一脉冲EC11.典型正转时序图
实际操作顺序:正转一格-->停顿-->连续正转-->停
其实把正转的时序图从右往左看,就是反转的时序图了。
一定位一脉冲EC11.典型反转时序图.PNG
实际操作顺序:反转一格-->停顿-->连续反转-->停
时序图解读
若将EC11的A端视为时钟,B端视为数据,整个EC11就可以视为根据时钟脉冲输出信号的同步元件。可以看做数据在时钟的边沿处输出(时钟线检测边沿,数据线检测电平,这个思路编程最简单)
正转时,在时钟线的下降沿处,数据线为高电平。或在时钟线的上升沿处,数据线为低电平;
反转时,在时钟线的下降沿处,数据线为低电平。或在时钟线的上升沿处,数据线为高电平。
可以不严谨的简单总结为时钟的下降沿处A、B反相为正转,同相为反转
EC11正反转引脚动作 | 正转 | 正转 | 反转 | 反转 |
---|---|---|---|---|
A | 上升沿 | 下降沿 | 上升沿 | 下降沿 |
B | L | H | H | L |
由于每转一格,A,B两线就会各自输出一个完整的脉冲,因此我们可以仅检测时钟线的时钟单边沿(上升沿或者下降沿任选一个做检测),根据时钟线的边沿处,信号线的电平来判断EC11是正转还是反转。由于一定位一脉冲的EC11在正常情况下AB线初始状态都是高电平,所以直接检测时钟线的下降沿更方便。
另一种方法也可以看做数据与时钟的相位关系(检测数据线和时钟线哪根线的边沿先出现,这个思路编程较为复杂,适合做硬件实现):正转时,A线相位超前于B线相位;反转时,B线相位超前于A线相位。使用简单的数字逻辑电路(异或门与D触发器)就可以识别到AB的相位关系与转动次数。
详见
基于FPGA的光电编码器四倍频电路设计
基于CPLD的增量式旋转编码器接口电路模块设计
部分驱动程序(以C51为例)
//----------------IO口定义----------------//
#define EC11_A_Now P36 //EC11的A引脚,视为时钟线
#define EC11_B_Now P35 //EC11的B引脚,视为信号线
#define EC11_Key P37 //EC11的按键
//----------------局部文件内变量列表----------------//
static char EC11_A_Last = 0; //EC11的A引脚上一次的状态
static char EC11_B_Last = 0; //EC11的B引脚上一次的状态
static char EC11_Type = 1; //定义变量暂存EC11的类型---->>>>---- 0:一定位对应一脉冲; 1:两定位对应一脉冲
//所谓一定位对应一脉冲,是指EC11旋转编码器每转动一格,A和B都会输出一个完整的方波。
//而 两定位对应一脉冲,是指EC11旋转编码器每转动两格,A和B才会输出一个完整的方波,只转动一格只输出A和B的上升沿或下降沿
char Encoder_EC11_Scan() /* 这里只是部分代码 */
{
//以下储存A、B上一次值的变量声明为静态全局变量,方便对EC11对应的IO口做初始化
// static char EC11_A_Last = 0;
// static char EC11_B_Last = 0;
char ScanResult = 0; //返回编码器扫描结果,用于分析编码器的动作
//返回值的取值: 0:无动作; 1:正转; -1:反转;
// 2:只按下按键; 3:按着按键正转; -3:按着按键反转
//======================================================//
if(EC11_Type == 0) //================一定位对应一脉冲的EC11================//
{ //======================================================//
if(EC11_A_Now != EC11_A_Last) //以A为时钟,B为数据。正转时AB反相,反转时AB同相
{
if(EC11_A_Now == 0)
{
if(EC11_B_Now ==1) //只需要采集A的上升沿或下降沿的任意一个状态,若A下降沿时B为1,正转
ScanResult = 1; //正转
else //反转
ScanResult = -1;
}
EC11_A_Last = EC11_A_Now; //更新编码器上一个状态暂存变量
EC11_B_Last = EC11_B_Now; //更新编码器上一个状态暂存变量
}
}
return ScanResult; //返回值的取值: 0:无动作; 1:正转; -1:反转;
}
两定位一脉冲EC11.典型正转时序图
上图 实际操作顺序:正转第一格-->停顿-->正转第二格-->停顿-->连续正转-->停
两定位一脉冲EC11.典型反转时序图
上图 实际操作顺序:反转第一格-->停顿-->反转第二格-->停顿-->连续反转-->停
两定位一脉冲EC11.正转-反转-正转-正转-反转时序图
上图 实际操作顺序:正转一格-->反转一格-->正转一格-->正转一格-->反转一格-->停
时序图解读
两定位一脉冲的EC11时序稍微复杂一些。按照转动的初始情况和先后顺序可以细分为这6种情况:
由于这类EC11输出一个脉冲已经转了两格,因此对于上升沿和下降沿都需要检测。由因为要考虑正转再反转或者反转再正转的情形,用检测相位超前或滞后的思路的程序除了要检测A线的上升沿下降沿外,还要检测B线的前后状态。即时钟线边沿来到之前之后都需要检测B的状态并作比较。
部分驱动程序(以C51为例)
下面为检测相位超前或滞后的方式扫描EC11
/* 这是对上面char Encoder_EC11_Scan()函数的补充。else对应的if为if(EC11_Type == 0) 即此处EC11_Type !=0 */
//======================================================//
else //================两定位对应一脉冲的EC11================//
{ //======================================================//
if(EC11_A_Now !=EC11_A_Last) //当A发生跳变时采集B当前的状态,并将B与上一次的状态进行对比。
{ //若A 0->1 时,B 1->0 正转;若A 1->0 时,B 0->1 正转;
//若A 0->1 时,B 0->1 反转;若A 1->0 时,B 1->0 反转
if(EC11_A_Now == 1) //EC11_A和上一次状态相比,为上升沿
{
if((EC11_B_Last == 1)&&(EC11_B_Now == 0)) //EC11_B和上一次状态相比,为下降沿
ScanResult = 1; //正转
if((EC11_B_Last == 0)&&(EC11_B_Now == 1)) //EC11_B和上一次状态相比,为上升沿
ScanResult = -1; //反转
//>>>>>>>>>>>>>>>>下面为正转一次再反转或反转一次再正转处理<<<<<<<<<<<<<<</
if((EC11_B_Last == EC11_B_Now)&&(EC11_B_Now == 0)) //A上升沿时,采集的B不变且为0
ScanResult = 1; //正转
if((EC11_B_Last == EC11_B_Now)&&(EC11_B_Now == 1)) //A上升沿时,采集的B不变且为1
ScanResult = -1; //反转
}
else //EC11_A和上一次状态相比,为下降沿
{
if((EC11_B_Last == 1)&&(EC11_B_Now == 0)) //EC11_B和上一次状态相比,为下降沿
ScanResult = -1; //反转
if((EC11_B_Last == 0)&&(EC11_B_Now == 1)) //EC11_B和上一次状态相比,为上升沿
ScanResult = 1; //正转
//>>>>>>>>>>>>>>>>下面为正转一次再反转或反转一次再正转处理<<<<<<<<<<<<<<</
if((EC11_B_Last == EC11_B_Now)&&(EC11_B_Now == 0)) //A上升沿时,采集的B不变且为0
ScanResult = -1; //反转
if((EC11_B_Last == EC11_B_Now)&&(EC11_B_Now == 1)) //A上升沿时,采集的B不变且为1
ScanResult = 1; //正转
}
EC11_A_Last = EC11_A_Now; //更新编码器上一个状态暂存变量
EC11_B_Last = EC11_B_Now; //更新编码器上一个状态暂存变量
return ScanResult; //返回值的取值: 0:无动作; 1:正转; -1:反转;
}
}
按键检测部分
我们自己用EC11,大多数时候都是选择带按键的,这就可以把按键检测加入到EC11的动作扫描程序中,实现单独的检测按键以及按下按键时转动转轴。
在定时器中断内调用该扫描函数即可
/*-------->>>>>>>>--------注意事项:EC11旋转编码器的扫描时间间隔控制在1~4ms之间,
否则5ms及以上的扫描时间在快速旋转时可能会误判旋转方向--------<<<<<<<<--------*/
//*******************************************************************/
//功能:初始化EC11旋转编码器相关参数
//形参:EC11旋转编码器的类型-->> unsigned char Set_EC11_TYPE <<-- :0----一定位对应一脉冲;1(或非0)----两定位对应一脉冲。
//返回:无
//详解:对EC11旋转编码器的连接IO口做IO口模式设置。以及将相关的变量进行初始化
//*******************************************************************/
void Encoder_EC11_Init(unsigned char Set_EC11_TYPE)
{
//IO口模式初始化。初始化EC11的IO口为准双向模式
P35_QB();
P36_QB();
P37_QB();
EC11_A_Now = 1;
EC11_B_Now = 1;
EC11_Key = 1;
//EC11类型选择:0-一定位一脉冲;1-两定位一脉冲
if(Set_EC11_TYPE == 0)
{
EC11_Type = 0;
}
else
{
EC11_Type = 1;
}
//避免上电时EC11旋钮位置不确定导致一次动作误判
EC11_A_Last = EC11_A_Now;
EC11_B_Last = EC11_B_Now;
//--------清除按键计数器和标志位--------//
EC11_KEY_COUNT = 0; //EC11按键动作计数器
EC11_KEY_DoubleClick_Count = 0; //EC11按键双击动作计数器
FLAG_EC11_KEY_ShotClick = 0; //EC11按键短按动作标志
FLAG_EC11_KEY_LongClick = 0; //EC11按键长按动作标志
FLAG_EC11_KEY_DoubleClick = 0; //EC11按键双击动作标志
}
//*******************************************************************/
//功能:扫描EC11旋转编码器的动作并将参数返回给动作分析函数使用
//形参:EC11旋转编码器的类型-->> unsigned char Set_EC11_TYPE <<-- :0----一定位对应一脉冲;1(或非0)----两定位对应一脉冲
//返回:EC11旋转编码器的扫描结果-->> char ScanResult -->> 0:无动作;1:正转; -1:反转;2:只按下按键;
// 3:按着按键正转;-3:按着按键反转
//详解:只扫描EC11旋转编码器有没有动作,不关心是第几次按下按键或长按或双击。
//返回值直接作为形参传给 [ void Encoder_EC11_Analyze(char EC11_Value); ] 函数使用
//*******************************************************************/
char Encoder_EC11_Scan()
{
//以下储存A、B上一次值的变量声明为静态全局变量,方便对EC11对应的IO口做初始化
// static char EC11_A_Last = 0;
// static char EC11_B_Last = 0;
char ScanResult = 0; //返回编码器扫描结果,用于分析编码器的动作
//返回值的取值: 0:无动作; 1:正转; -1:反转;
// 2:只按下按键; 3:按着按键正转; -3:按着按键反转
//======================================================//
if(EC11_Type == 0) //================一定位对应一脉冲的EC11================//
{ //======================================================//
if(EC11_A_Now != EC11_A_Last) //以A为时钟,B为数据。正转时AB反相,反转时AB同相
{
if(EC11_A_Now == 0)
{
if(EC11_B_Now ==1) //只需要采集A的上升沿或下降沿的任意一个状态,若A下降沿时B为1,正转
ScanResult = 1; //正转
else //反转
ScanResult = -1;
}
EC11_A_Last = EC11_A_Now; //更新编码器上一个状态暂存变量
EC11_B_Last = EC11_B_Now; //更新编码器上一个状态暂存变量
}
}
//======================================================//
else //================两定位对应一脉冲的EC11================//
{ //======================================================//
if(EC11_A_Now !=EC11_A_Last) //当A发生跳变时采集B当前的状态,并将B与上一次的状态进行对比。
{ //若A 0->1 时,B 1->0 正转;若A 1->0 时,B 0->1 正转;
//若A 0->1 时,B 0->1 反转;若A 1->0 时,B 1->0 反转
if(EC11_A_Now == 1) //EC11_A和上一次状态相比,为上升沿
{
if((EC11_B_Last == 1)&&(EC11_B_Now == 0)) //EC11_B和上一次状态相比,为下降沿
ScanResult = 1; //正转
if((EC11_B_Last == 0)&&(EC11_B_Now == 1)) //EC11_B和上一次状态相比,为上升沿
ScanResult = -1; //反转
//>>>>>>>>>>>>>>>>下面为正转一次再反转或反转一次再正转处理<<<<<<<<<<<<<<</
if((EC11_B_Last == EC11_B_Now)&&(EC11_B_Now == 0)) //A上升沿时,采集的B不变且为0
ScanResult = 1; //正转
if((EC11_B_Last == EC11_B_Now)&&(EC11_B_Now == 1)) //A上升沿时,采集的B不变且为1
ScanResult = -1; //反转
}
else //EC11_A和上一次状态相比,为下降沿
{
if((EC11_B_Last == 1)&&(EC11_B_Now == 0)) //EC11_B和上一次状态相比,为下降沿
ScanResult = -1; //反转
if((EC11_B_Last == 0)&&(EC11_B_Now == 1)) //EC11_B和上一次状态相比,为上升沿
ScanResult = 1; //正转
//>>>>>>>>>>>>>>>>下面为正转一次再反转或反转一次再正转处理<<<<<<<<<<<<<<</
if((EC11_B_Last == EC11_B_Now)&&(EC11_B_Now == 0)) //A上升沿时,采集的B不变且为0
ScanResult = -1; //反转
if((EC11_B_Last == EC11_B_Now)&&(EC11_B_Now == 1)) //A上升沿时,采集的B不变且为1
ScanResult = 1; //正转
}
EC11_A_Last = EC11_A_Now; //更新编码器上一个状态暂存变量
EC11_B_Last = EC11_B_Now; //更新编码器上一个状态暂存变量
}
}
if(EC11_Key == 0) //如果EC11的按键按下,并且没有EC11没有转动,
{
if(ScanResult == 0) //按下按键时未转动
ScanResult = 2; //返回值为2
else
{
if(ScanResult == 1) //按下按键时候正转
ScanResult = 3; //返回值为3
if(ScanResult == -1) //按下按键时候反转
ScanResult = -3; //返回值为-3
}
}
return ScanResult; //返回值的取值: 0:无动作; 1:正转; -1:反转;
}
扫描到EC11的动作后,可以在此函数内做出对应的动作处理。函数内包含了按键的单击,双击,长按和长按松手检测。
/*与按键检测相关的静态局部变量*/
static int EC11_KEY_COUNT = 0; //EC11按键动作计数器
static int EC11_KEY_DoubleClick_Count = 0; //EC11按键双击动作计数器
static char FLAG_EC11_KEY_ShotClick = 0; //EC11按键短按动作标志
static char FLAG_EC11_KEY_LongClick = 0; //EC11按键长按动作标志
static char FLAG_EC11_KEY_DoubleClick = 0; //EC11按键双击动作标志
//----------------编码器参数微调宏定义----------------//
#define EC11_SCAN_PERIOD_MS 1 //EC11编码器扫描周期
#define KEY_COUNT_DESHAKING ( 20/EC11_SCAN_PERIOD_MS) //按键消抖时间
#define KEY_COUNT_LONGTIME (600/EC11_SCAN_PERIOD_MS) //长按按键判断时间
#define KEY_COUNT_DUALCLICKTIME (150/EC11_SCAN_PERIOD_MS) //双击按键判断时间
#define KEY_LONG_REPEAT_TIME (200/EC11_SCAN_PERIOD_MS) //长按按键的回报率的倒数,即一直长按按键时响应的时间间隔
//*******************************************************************/
//功能:对EC11旋转编码器的动作进行分析,并作出相应的动作处理代码
//形参:无
//返回:char AnalyzeResult = 0;目前无用。若在该函数里做了动作处理,则函数的返回值无需理会
//详解:对EC11旋转编码器的动作进行模式分析,是单击还是双击还是长按松手还是一直按下。形参从 [ char Encoder_EC11_Scan(unsigned char Set_EC11_TYPE) ] 函数传入。在本函数内修改需要的动作处理代码
//*******************************************************************/
char Encoder_EC11_Analyze(char EC11_Value)
{
char AnalyzeResult = 0;
static unsigned int TMP_Value = 0; //中间计数值,用于连续长按按键的动作延时间隔
//>>>>>>>>>>>>>>>>编码器正转处理程序<<<<<<<<<<<<<<</
if(EC11_Value == 1) //正转
{
//--------编码器正转动作代码--------//
}
//>>>>>>>>>>>>>>>>编码器反转处理程序<<<<<<<<<<<<<<</
if(EC11_Value == -1) //反转
{
//--------编码器反转动作代码--------//
}
//>>>>>>>>>>>>>>>>编码器按键按下并正转处理程序<<<<<<<<<<<<<<</
if(EC11_Value == 3)
{
//--------编码器按键按下并正转动作代码--------//
}
//>>>>>>>>>>>>>>>>编码器按键按下并反转处理程序<<<<<<<<<<<<<<</
if(EC11_Value == -3)
{
//--------编码器按键按下并反转动作代码--------//
}
//>>>>>>>>>>>>>>>>编码器按键按下处理程序<<<<<<<<<<<<<<</
if(EC11_Value == 2) //====检测到按键按下====//
{
if(EC11_KEY_COUNT<10000) //打开按键按下时间定时器
EC11_KEY_COUNT++;
if(EC11_KEY_COUNT == KEY_COUNT_DESHAKING) //按下按键时间到达消抖时间时
{ //置位短按按键标志
FLAG_EC11_KEY_ShotClick = 1;
}
if((EC11_KEY_DoubleClick_Count > 0)&&(EC11_KEY_DoubleClick_Count <= KEY_COUNT_DUALCLICKTIME)) //松开按键后,又在定时器在双击时间内按下按键
{ //置位双击按键标志
FLAG_EC11_KEY_DoubleClick = 1;
}
if(EC11_KEY_COUNT == KEY_COUNT_LONGTIME) //按下按键时间到达长按时间
{ //置位长按按键标志并复位短按按键标志
FLAG_EC11_KEY_LongClick = 1;
FLAG_EC11_KEY_ShotClick = 0;
}
}
else //====检测到按键松开====//
{
if(EC11_KEY_COUNT < KEY_COUNT_DESHAKING) //没到消抖时长就松开按键,复位所有定时器和按键标志
{
EC11_KEY_COUNT = 0;
FLAG_EC11_KEY_ShotClick = 0;
FLAG_EC11_KEY_LongClick = 0;
FLAG_EC11_KEY_DoubleClick = 0;
EC11_KEY_DoubleClick_Count = 0;
}
else
{
if(FLAG_EC11_KEY_ShotClick == 1) //短按按键定时有效期间
{
if((FLAG_EC11_KEY_DoubleClick == 0)&&(EC11_KEY_DoubleClick_Count >= 0))
EC11_KEY_DoubleClick_Count++;
if((FLAG_EC11_KEY_DoubleClick == 1)&&(EC11_KEY_DoubleClick_Count <= KEY_COUNT_DUALCLICKTIME)) //如果在规定双击时间内再次按下按键
{ //认为按键是双击动作
FLAG_EC11_KEY_DoubleClick = 2;
}
if((FLAG_EC11_KEY_DoubleClick == 0)&&(EC11_KEY_DoubleClick_Count > KEY_COUNT_DUALCLICKTIME)) //如果没有在规定双击时间内再次按下按键
FLAG_EC11_KEY_ShotClick = 0; //认为按键是单击动作
}
if(FLAG_EC11_KEY_LongClick == 1) //检测到长按按键松开
FLAG_EC11_KEY_LongClick = 0;
}
}
//>>>>>>>>>>>>>>>>编码器按键分析处理程序<<<<<<<<<<<<<<</
if(EC11_KEY_COUNT > KEY_COUNT_DESHAKING) //短按按键延时到了时间
{
//短按按键动作结束代码
if((FLAG_EC11_KEY_ShotClick == 0)&&(EC11_KEY_DoubleClick_Count > KEY_COUNT_DUALCLICKTIME)&&(EC11_KEY_COUNT < KEY_COUNT_LONGTIME)) //短按按键动作结束代码
{
//--------短按按键动作结束代码--------//
AnalyzeResult = 1;
//--------清除标志位--------//
EC11_KEY_COUNT = 0;
EC11_KEY_DoubleClick_Count = 0;
FLAG_EC11_KEY_DoubleClick = 0;
}
//双击按键动作结束代码
if((FLAG_EC11_KEY_DoubleClick == 2)&&(EC11_KEY_DoubleClick_Count > 0)&&(EC11_KEY_DoubleClick_Count <= KEY_COUNT_DUALCLICKTIME)) //双击按键动作结束代码
{
//--------双击按键动作结束代码--------//
AnalyzeResult = 2;
//--------清除标志位--------//
EC11_KEY_COUNT = 0;
EC11_KEY_DoubleClick_Count = 0;
FLAG_EC11_KEY_ShotClick = 0;
FLAG_EC11_KEY_DoubleClick = 0;
}
//连续长按按键按下代码
if((FLAG_EC11_KEY_LongClick == 1)&&(EC11_KEY_COUNT >= KEY_COUNT_LONGTIME)) //连续长按按键按下代码
{
TMP_Value ++;
if(TMP_Value % KEY_LONG_REPEAT_TIME == 0)
{
TMP_Value = 0;
//-------连续长按按键按下代码--------//
AnalyzeResult = 4;
}
}
//长按按键动作结束代码
if((FLAG_EC11_KEY_LongClick == 0)&&(EC11_KEY_COUNT >= KEY_COUNT_LONGTIME)) //长按按键动作结束代码
{
//--------长按按键按下动作结束代码--------//
AnalyzeResult = 3;
//--------清除标志位--------//
EC11_KEY_COUNT = 0;
}
}
return AnalyzeResult;
}
我把整个EC11的驱动程序写在EncoderEC11.h和EncoderEC11.c中。请按需自行移植。
//---->>>>----文件描述:EC11旋转编码器底层驱动程序---<<<<----//
//---->>>>----文件版本:V1.0----<<<<----//
#ifndef __EncoderEC11_H
#define __EncoderEC11_H
#include "config.H"
//----------------IO口定义----------------//
#define EC11_A_Now P36 //EC11的A引脚,视为时钟线
#define EC11_B_Now P35 //EC11的B引脚,视为信号线
#define EC11_Key P37 //EC11的按键
//----------------编码器动作代码相关定义----------------//
extern int G_PWM_NUM1;
extern int G_PWM_NUM2;
extern int G_PWM_NUM3;
static unsigned char EC11_NUM_SW = 0;
//----------------编码器参数微调宏定义----------------//
#define EC11_SCAN_PERIOD_MS 1 //EC11编码器扫描周期
#define KEY_COUNT_DESHAKING ( 20/EC11_SCAN_PERIOD_MS) //按键消抖时间
#define KEY_COUNT_LONGTIME (600/EC11_SCAN_PERIOD_MS) //长按按键判断时间
#define KEY_COUNT_DUALCLICKTIME (150/EC11_SCAN_PERIOD_MS) //双击按键判断时间
#define KEY_LONG_REPEAT_TIME (200/EC11_SCAN_PERIOD_MS) //长按按键的回报率的倒数,即一直长按按键时响应的时间间隔
//----------------局部文件内变量列表----------------//
static char EC11_A_Last = 0; //EC11的A引脚上一次的状态
static char EC11_B_Last = 0; //EC11的B引脚上一次的状态
static char EC11_Type = 1; //定义变量暂存EC11的类型---->>>>---- 0:一定位对应一脉冲; 1:两定位对应一脉冲
//所谓一定位对应一脉冲,是指EC11旋转编码器每转动一格,A和B都会输出一个完整的方波。
//而 两定位对应一脉冲,是指EC11旋转编码器每转动两格,A和B才会输出一个完整的方波,只转动一格只输出A和B的上升沿或下降沿
static int EC11_KEY_COUNT = 0; //EC11按键动作计数器
static int EC11_KEY_DoubleClick_Count = 0; //EC11按键双击动作计数器
static char FLAG_EC11_KEY_ShotClick = 0; //EC11按键短按动作标志
static char FLAG_EC11_KEY_LongClick = 0; //EC11按键长按动作标志
static char FLAG_EC11_KEY_DoubleClick = 0; //EC11按键双击动作标志
//----------------函数快速调用(复制粘贴)列表----------------//
//
/*******************************************************************
void Encoder_EC11_Init(unsigned char Set_EC11_TYPE); //初始化EC11旋转编码器IO口和类型以及变量初始化
char Encoder_EC11_Scan(); //扫描旋转编码器的动作
void Encoder_EC11_Analyze(char EC11_Value); //分析EC11旋转编码器的动作以及动作处理代码
******************************************************************/
//-------->>>>>>>>--------注意事项:EC11旋转编码器的扫描时间间隔控制在1~4ms之间,否则5ms及以上的扫描时间在快速旋转时可能会误判旋转方向--------<<<<<<<<--------//
//-------->>>>>>>>--------注意事项:EC11旋转编码器的扫描时间间隔控制在1~4ms之间,否则5ms及以上的扫描时间在快速旋转时可能会误判旋转方向--------<<<<<<<<--------//
//-------->>>>>>>>--------注意事项:EC11旋转编码器的扫描时间间隔控制在1~4ms之间,否则5ms及以上的扫描时间在快速旋转时可能会误判旋转方向--------<<<<<<<<--------//
//----------------函数声明列表----------------//
//
//*******************************************************************/
//功能:初始化EC11旋转编码器相关参数
//形参:EC11旋转编码器的类型-->> unsigned char Set_EC11_TYPE <<-- :0----一定位对应一脉冲;1(或非0)----两定位对应一脉冲。
//返回:无
//详解:对EC11旋转编码器的连接IO口做IO口模式设置。以及将相关的变量进行初始化
//*******************************************************************/
void Encoder_EC11_Init(unsigned char Set_EC11_TYPE);
//*******************************************************************/
//功能:扫描EC11旋转编码器的动作并将参数返回给动作分析函数使用
//形参:EC11旋转编码器的类型-->> unsigned char Set_EC11_TYPE <<-- :0----一定位对应一脉冲;1(或非0)----两定位对应一脉冲
//返回:EC11旋转编码器的扫描结果-->> char ScanResult -->> 0:无动作;1:正转; -1:反转;2:只按下按键;3:按着按键正转;-3:按着按键反转
//详解:只扫描EC11旋转编码器有没有动作,不关心是第几次按下按键或长按或双击。返回值直接作为形参传给 [ void Encoder_EC11_Analyze(char EC11_Value); ] 函数使用
//*******************************************************************/
char Encoder_EC11_Scan();
//*******************************************************************/
//功能:对EC11旋转编码器的动作进行分析,并作出相应的动作处理代码
//形参:无
//返回:char AnalyzeResult = 0;目前无用。若在该函数里做了动作处理,则函数的返回值无需理会
//详解:对EC11旋转编码器的动作进行模式分析,是单击还是双击还是长按松手还是一直按下。形参从 [ char Encoder_EC11_Scan(unsigned char Set_EC11_TYPE) ] 函数传入。在本函数内修改需要的动作处理代码
//*******************************************************************/
char Encoder_EC11_Analyze(char EC11_Value);
#endif
//---->>>>----函数使用示例----<<<<----//
/********
#include "config.h"
#include "delay.h"
#include "EncoderEC11.h"
int cnt = -1; //流水灯速查表偏移变量
unsigned char disp_tmp[] = {~0x01,~0x02,~0x04,~0x08,~0x10,~0x20,~0x40,~0x80}; //流水灯速查表
void Timer0Init(void) //1毫秒@22.1184MHz
{
AUXR &= 0x7F; //定时器时钟12T模式
TMOD &= 0xF0; //设置定时器模式
TL0 = 0xCD; //设置定时初值
TH0 = 0xF8; //设置定时初值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
}
void main()
{
P1_QB_ALL();
P2_QB_ALL();
P3_QB_ALL();
delay_ms(50); //延时100毫秒等待所有MCU复位
Encoder_EC11_Init(1);
EA = 1;
ET0 = 1;
Timer0Init();
while(1)
{
}
}
//-------->>>>>>>>--------注意事项:EC11旋转编码器的扫描时间间隔控制在1~4ms之间,否则5ms及以上的扫描时间在快速旋转时可能会误判旋转方向--------<<<<<<<<--------//
void T0_ISR() interrupt 1
{
static int tmp =0;
Encoder_EC11_Analyze(Encoder_EC11_Scan());
if(P33 == 0)
{
tmp ++;
if(tmp == 500)
{
tmp =0;
P27 = !P27;
}
}
}
********/
//---->>>>----文件描述:EC11旋转编码器底层驱动程序---<<<<----//
//---->>>>----文件版本:V1.0----<<<<----//
#include "EncoderEC11.h"
//-------->>>>>>>>--------注意事项:EC11旋转编码器的扫描时间间隔控制在1~4ms之间,否则5ms及以上的扫描时间在快速旋转时可能会误判旋转方向--------<<<<<<<<--------//
//*******************************************************************/
//功能:初始化EC11旋转编码器相关参数
//形参:EC11旋转编码器的类型-->> unsigned char Set_EC11_TYPE <<-- :0----一定位对应一脉冲;1(或非0)----两定位对应一脉冲。
//返回:无
//详解:对EC11旋转编码器的连接IO口做IO口模式设置。以及将相关的变量进行初始化
//*******************************************************************/
void Encoder_EC11_Init(unsigned char Set_EC11_TYPE)
{
//IO口模式初始化。初始化EC11的IO口为准双向模式
P35_QB();
P36_QB();
P37_QB();
EC11_A_Now = 1;
EC11_B_Now = 1;
EC11_Key = 1;
//EC11类型选择:0-一定位一脉冲;1-两定位一脉冲
if(Set_EC11_TYPE == 0)
{
EC11_Type = 0;
}
else
{
EC11_Type = 1;
}
//避免上电时EC11旋钮位置不确定导致一次动作误判
EC11_A_Last = EC11_A_Now;
EC11_B_Last = EC11_B_Now;
//--------清除按键计数器和标志位--------//
EC11_KEY_COUNT = 0; //EC11按键动作计数器
EC11_KEY_DoubleClick_Count = 0; //EC11按键双击动作计数器
FLAG_EC11_KEY_ShotClick = 0; //EC11按键短按动作标志
FLAG_EC11_KEY_LongClick = 0; //EC11按键长按动作标志
FLAG_EC11_KEY_DoubleClick = 0; //EC11按键双击动作标志
}
//*******************************************************************/
//功能:扫描EC11旋转编码器的动作并将参数返回给动作分析函数使用
//形参:EC11旋转编码器的类型-->> unsigned char Set_EC11_TYPE <<-- :0----一定位对应一脉冲;1(或非0)----两定位对应一脉冲
//返回:EC11旋转编码器的扫描结果-->> char ScanResult -->> 0:无动作;1:正转; -1:反转;2:只按下按键;3:按着按键正转;-3:按着按键反转
//详解:只扫描EC11旋转编码器有没有动作,不关心是第几次按下按键或长按或双击。返回值直接作为形参传给 [ void Encoder_EC11_Analyze(char EC11_Value); ] 函数使用
//*******************************************************************/
char Encoder_EC11_Scan()
{
//以下储存A、B上一次值的变量声明为静态全局变量,方便对EC11对应的IO口做初始化
// static char EC11_A_Last = 0;
// static char EC11_B_Last = 0;
char ScanResult = 0; //返回编码器扫描结果,用于分析编码器的动作
//返回值的取值: 0:无动作; 1:正转; -1:反转;
// 2:只按下按键; 3:按着按键正转; -3:按着按键反转
//======================================================//
if(EC11_Type == 0) //================一定位对应一脉冲的EC11================//
{ //======================================================//
if(EC11_A_Now != EC11_A_Last) //以A为时钟,B为数据。正转时AB反相,反转时AB同相
{
if(EC11_A_Now == 0)
{
if(EC11_B_Now ==1) //只需要采集A的上升沿或下降沿的任意一个状态,若A下降沿时B为1,正转
ScanResult = 1; //正转
else //反转
ScanResult = -1;
}
EC11_A_Last = EC11_A_Now; //更新编码器上一个状态暂存变量
EC11_B_Last = EC11_B_Now; //更新编码器上一个状态暂存变量
}
}
//======================================================//
else //================两定位对应一脉冲的EC11================//
{ //======================================================//
if(EC11_A_Now !=EC11_A_Last) //当A发生跳变时采集B当前的状态,并将B与上一次的状态进行对比。
{ //若A 0->1 时,B 1->0 正转;若A 1->0 时,B 0->1 正转;
//若A 0->1 时,B 0->1 反转;若A 1->0 时,B 1->0 反转
if(EC11_A_Now == 1) //EC11_A和上一次状态相比,为上升沿
{
if((EC11_B_Last == 1)&&(EC11_B_Now == 0)) //EC11_B和上一次状态相比,为下降沿
ScanResult = 1; //正转
if((EC11_B_Last == 0)&&(EC11_B_Now == 1)) //EC11_B和上一次状态相比,为上升沿
ScanResult = -1; //反转
//>>>>>>>>>>>>>>>>下面为正转一次再反转或反转一次再正转处理<<<<<<<<<<<<<<</
if((EC11_B_Last == EC11_B_Now)&&(EC11_B_Now == 0)) //A上升沿时,采集的B不变且为0
ScanResult = 1; //正转
if((EC11_B_Last == EC11_B_Now)&&(EC11_B_Now == 1)) //A上升沿时,采集的B不变且为1
ScanResult = -1; //反转
}
else //EC11_A和上一次状态相比,为下降沿
{
if((EC11_B_Last == 1)&&(EC11_B_Now == 0)) //EC11_B和上一次状态相比,为下降沿
ScanResult = -1; //反转
if((EC11_B_Last == 0)&&(EC11_B_Now == 1)) //EC11_B和上一次状态相比,为上升沿
ScanResult = 1; //正转
//>>>>>>>>>>>>>>>>下面为正转一次再反转或反转一次再正转处理<<<<<<<<<<<<<<</
if((EC11_B_Last == EC11_B_Now)&&(EC11_B_Now == 0)) //A上升沿时,采集的B不变且为0
ScanResult = -1; //反转
if((EC11_B_Last == EC11_B_Now)&&(EC11_B_Now == 1)) //A上升沿时,采集的B不变且为1
ScanResult = 1; //正转
}
EC11_A_Last = EC11_A_Now; //更新编码器上一个状态暂存变量
EC11_B_Last = EC11_B_Now; //更新编码器上一个状态暂存变量
}
}
if(EC11_Key == 0) //如果EC11的按键按下,并且没有EC11没有转动,
{
if(ScanResult == 0) //按下按键时未转动
ScanResult = 2; //返回值为2
else
{
if(ScanResult == 1) //按下按键时候正转
ScanResult = 3; //返回值为3
if(ScanResult == -1) //按下按键时候反转
ScanResult = -3; //返回值为-3
}
}
return ScanResult; //返回值的取值: 0:无动作; 1:正转; -1:反转;
} // 2:只按下按键; 3:按着按键正转; -3:按着按键反转
//*******************************************************************/
//功能:对EC11旋转编码器的动作进行分析,并作出相应的动作处理代码
//形参:无
//返回:char AnalyzeResult = 0;目前无用。若在该函数里做了动作处理,则函数的返回值无需理会
//详解:对EC11旋转编码器的动作进行模式分析,是单击还是双击还是长按松手还是一直按下。形参从 [ char Encoder_EC11_Scan(unsigned char Set_EC11_TYPE) ] 函数传入。在本函数内修改需要的动作处理代码
//*******************************************************************/
char Encoder_EC11_Analyze(char EC11_Value)
{
char AnalyzeResult = 0;
static unsigned int TMP_Value = 0; //中间计数值,用于连续长按按键的动作延时间隔
//>>>>>>>>>>>>>>>>编码器正转处理程序<<<<<<<<<<<<<<</
if(EC11_Value == 1) //正转
{
//--------编码器正转动作代码--------//
switch(EC11_NUM_SW)
{
case 1: G_PWM_NUM1+=10; if(G_PWM_NUM1>255)G_PWM_NUM1 = 0; break;
case 2: G_PWM_NUM2+=10; if(G_PWM_NUM1>255)G_PWM_NUM2 = 0; break;
case 3: G_PWM_NUM3+=10; if(G_PWM_NUM1>255)G_PWM_NUM3 = 0; break;
case 4: G_PWM_NUM1+=10; if(G_PWM_NUM1>255)G_PWM_NUM1 = 0; G_PWM_NUM3 = G_PWM_NUM2 = G_PWM_NUM1; break;
case 5: G_PWM_NUM1+=20; if(G_PWM_NUM1>=255){G_PWM_NUM1 = 0;G_PWM_NUM3+=20;if(G_PWM_NUM3>=255){G_PWM_NUM3 = 0;G_PWM_NUM2+=20;if(G_PWM_NUM2 >=255)G_PWM_NUM2 = 0;}}
default :break;
}
}
//>>>>>>>>>>>>>>>>编码器反转处理程序<<<<<<<<<<<<<<</
if(EC11_Value == -1) //反转
{
//--------编码器反转动作代码--------//
switch(EC11_NUM_SW)
{
case 1: G_PWM_NUM1-=10; if(G_PWM_NUM1<0)G_PWM_NUM1 = 255; break;
case 2: G_PWM_NUM2-=10; if(G_PWM_NUM2<0)G_PWM_NUM1 = 255; break;
case 3: G_PWM_NUM3-=10; if(G_PWM_NUM3<0)G_PWM_NUM1 = 255; break;
case 4: G_PWM_NUM1-=10; if(G_PWM_NUM1<0)G_PWM_NUM1 = 255; G_PWM_NUM3 = G_PWM_NUM2 = G_PWM_NUM1; break;
case 5: G_PWM_NUM1-=20; if(G_PWM_NUM1<0){G_PWM_NUM1 = 255;G_PWM_NUM3-=20;if(G_PWM_NUM3<0){G_PWM_NUM3 = 255;G_PWM_NUM2-=20;if(G_PWM_NUM2 <0)G_PWM_NUM2 = 255;}}
default :break;
}
}
//>>>>>>>>>>>>>>>>编码器按键按下并正转处理程序<<<<<<<<<<<<<<</
if(EC11_Value == 3)
{
//--------编码器按键按下并正转动作代码--------//
}
//>>>>>>>>>>>>>>>>编码器按键按下并反转处理程序<<<<<<<<<<<<<<</
if(EC11_Value == -3)
{
//--------编码器按键按下并反转动作代码--------//
}
//>>>>>>>>>>>>>>>>编码器按键按下处理程序<<<<<<<<<<<<<<</
if(EC11_Value == 2) //====检测到按键按下====//
{
if(EC11_KEY_COUNT<10000) //打开按键按下时间定时器
EC11_KEY_COUNT++;
if(EC11_KEY_COUNT == KEY_COUNT_DESHAKING) //按下按键时间到达消抖时间时
{ //置位短按按键标志
FLAG_EC11_KEY_ShotClick = 1;
}
if((EC11_KEY_DoubleClick_Count > 0)&&(EC11_KEY_DoubleClick_Count <= KEY_COUNT_DUALCLICKTIME)) //松开按键后,又在定时器在双击时间内按下按键
{ //置位双击按键标志
FLAG_EC11_KEY_DoubleClick = 1;
}
if(EC11_KEY_COUNT == KEY_COUNT_LONGTIME) //按下按键时间到达长按时间
{ //置位长按按键标志并复位短按按键标志
FLAG_EC11_KEY_LongClick = 1;
FLAG_EC11_KEY_ShotClick = 0;
}
}
else //====检测到按键松开====//
{
if(EC11_KEY_COUNT < KEY_COUNT_DESHAKING) //没到消抖时长就松开按键,复位所有定时器和按键标志
{
EC11_KEY_COUNT = 0;
FLAG_EC11_KEY_ShotClick = 0;
FLAG_EC11_KEY_LongClick = 0;
FLAG_EC11_KEY_DoubleClick = 0;
EC11_KEY_DoubleClick_Count = 0;
}
else
{
if(FLAG_EC11_KEY_ShotClick == 1) //短按按键定时有效期间
{
if((FLAG_EC11_KEY_DoubleClick == 0)&&(EC11_KEY_DoubleClick_Count >= 0))
EC11_KEY_DoubleClick_Count++;
if((FLAG_EC11_KEY_DoubleClick == 1)&&(EC11_KEY_DoubleClick_Count <= KEY_COUNT_DUALCLICKTIME)) //如果在规定双击时间内再次按下按键
{ //认为按键是双击动作
FLAG_EC11_KEY_DoubleClick = 2;
}
if((FLAG_EC11_KEY_DoubleClick == 0)&&(EC11_KEY_DoubleClick_Count > KEY_COUNT_DUALCLICKTIME)) //如果没有在规定双击时间内再次按下按键
FLAG_EC11_KEY_ShotClick = 0; //认为按键是单击动作
}
if(FLAG_EC11_KEY_LongClick == 1) //检测到长按按键松开
FLAG_EC11_KEY_LongClick = 0;
}
}
//>>>>>>>>>>>>>>>>编码器按键分析处理程序<<<<<<<<<<<<<<</
if(EC11_KEY_COUNT > KEY_COUNT_DESHAKING) //短按按键延时到了时间
{
//短按按键动作结束代码
if((FLAG_EC11_KEY_ShotClick == 0)&&(EC11_KEY_DoubleClick_Count > KEY_COUNT_DUALCLICKTIME)&&(EC11_KEY_COUNT < KEY_COUNT_LONGTIME)) //短按按键动作结束代码
{
//--------短按按键动作结束代码--------//
EC11_NUM_SW++;
if(EC11_NUM_SW >= 4)
EC11_NUM_SW = 1;
AnalyzeResult = 1;
//--------清除标志位--------//
EC11_KEY_COUNT = 0;
EC11_KEY_DoubleClick_Count = 0;
FLAG_EC11_KEY_DoubleClick = 0;
}
//双击按键动作结束代码
if((FLAG_EC11_KEY_DoubleClick == 2)&&(EC11_KEY_DoubleClick_Count > 0)&&(EC11_KEY_DoubleClick_Count <= KEY_COUNT_DUALCLICKTIME)) //双击按键动作结束代码
{
//--------双击按键动作结束代码--------//
if(EC11_NUM_SW == 5)
EC11_NUM_SW = 0;
if(EC11_NUM_SW == 4)
EC11_NUM_SW = 5;
if(EC11_NUM_SW <4)
{
EC11_NUM_SW = 4;
}
AnalyzeResult = 2;
//--------清除标志位--------//
EC11_KEY_COUNT = 0;
EC11_KEY_DoubleClick_Count = 0;
FLAG_EC11_KEY_ShotClick = 0;
FLAG_EC11_KEY_DoubleClick = 0;
}
//连续长按按键按下代码
if((FLAG_EC11_KEY_LongClick == 1)&&(EC11_KEY_COUNT >= KEY_COUNT_LONGTIME)) //连续长按按键按下代码
{
TMP_Value ++;
if(TMP_Value % KEY_LONG_REPEAT_TIME == 0)
{
TMP_Value = 0;
//-------连续长按按键按下代码--------//
AnalyzeResult = 4;
}
}
//长按按键动作结束代码
if((FLAG_EC11_KEY_LongClick == 0)&&(EC11_KEY_COUNT >= KEY_COUNT_LONGTIME)) //长按按键动作结束代码
{
//--------长按按键按下动作结束代码--------//
EC11_NUM_SW = 0;
G_PWM_NUM1 = 0x20;
G_PWM_NUM2 = 0x20;
G_PWM_NUM3 = 0x20;
AnalyzeResult = 3;
//--------清除标志位--------//
EC11_KEY_COUNT = 0;
}
}
return AnalyzeResult;
}
作者:淡定的H羊
链接:https://www.jianshu.com/p/41fa67ecb248
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。