利用队列收集单双击和长按按键
引言
当我们仅仅通过在while循环里面进行判断按键类型的标志位, 然后进行操作的时候, 我们的最小例程很小, 所以能够实时的检测到按键,从而触发实验现象.
![]()
假如我们此时进入了一个事件处理函数呢 ? 并且这个这个函数的操作是不可被打断的, 如果此时我们重复按下多次按键, 标志位只能存放一个数值, 等事件处理完, 我们再处理的时候, 只能处理最后一次按下的按键类型.
![]()
最常见的就是, 我们使用遥控器, 我们连着按下了多个下箭头, 因为电视太卡, 导致只向下一次, 会极大影响操作体验.
所以我们的需求就是, 虽然你现在可能有重要事情要做, 但是, 你可以把我的按键需求存下来, 等不忙的时候, 再来处理. 因为处理器的处理是很快的, 所以我们也不会耽搁太多的时间。
按键收集模型分析
对于按键的收集, 我们分成三种类型,单击,双击和长按, 并且我们是按顺序按下的, 按顺序处理的, 所以这很符合队列的模型, 我们把他们排成一排, 放进数组, 然后到处理的时候, 再按顺序, 逐个处理, 按键需求和用户需求保持一致即可.
按键收集时机
对于按键收集, 假如现在我们有重要的事情去做, 不能立即去处理按键的功能, 但是我们可以中断的去收集按键类型, 因为中断是不会耽搁时间的. 只是花费CPU去记录一下数据. 所以我们在定时器函数内, 判断按键类型的时候, 把按键类型 button_mode 收集到队列里面。
![image-20240820184434471](https://img-blog.csdnimg.cn/img_convert/cb51d8a7edc1a47752482df5d06a5fec.png)
按键收集队列
我们采用数组存储, 定义一个队头,定义一个队尾,加入按键的时候,我们放在队尾, 处理按键的时候, 我们拿取队头按键处理。
初始化时候
队首,队尾都指向数组初始的时候
加入一个按键的时候
此按键, 加入到队尾(位序0), 然后队尾后移, 因为队尾始终要承载按键, 所以后移, 为后续腾出位置
加入两个按键的时候
把按键2,加入队尾, 位序为1处, 然后队尾后移到位序2
所以, 在收集按键的时候, 我们定义一个数组, 队尾一直后移, 因为我们相当于一个循环队列, 所以队尾后移到最大位序的时候, 可以再次回到开头, 继续添加(这里有一个前提就是, 我们一次性处理的数据, 不能超过数组的最大位序, 保证出队和入队的正常运转即可)。
void collect_button(void)
{
//采集按键信息
button_order[button_send] = button_mode;
if(button_send >= 5) // 说明5的位置已经被填了,下个该0了(++后,所指位置为空)
{
button_send = 0;
}
else
{
button_send++;
}
}
按键队列处理
有加入,就有取出。所以当我们取出按键的时候, 把队头按键取出, 处理此按键, 然后队头后移.
直到队头和队尾的数组位序重合, 那么我们就可以宣布,按键处理完毕了
取出按键
按键处理完毕的时候
当队头对应的按键处理完的时候, 我们队头后移, 为下次抽取按键做准备, 但是当队头和队尾 , 重合的时候, 就代表我们没有按键需要处理了.(队尾实时指向的位置, 是为下一次加入队列的按键做准备的, 所以是空的)
代码实现
我们一边处理着按键, 一边判断着队首和队尾是否重合, 一旦重合就代表着我们按键处理完了.
当 button_deal 队首 和 button_send 队尾 不重合时, 就一直取出按键
每次处理完一个按键后, 队尾位序后移, 同时要记得我们是数组,所以容量总会有不够用的时候, 所以我们要回到开头, 继续添加, 逻辑上是连在一起的(只需要保证同时处理的按键总和小于数组总容量即可)
void oled_chose(void)
{
//下面是原子时间,不可被定时器新加命令
button_atom = 1;
//if(button_deal != button_send)//如果有按键指令
while(button_deal != button_send ) //直到处理完指令
{
button_down = 1; //代表已经有指令了
//(短按 1, 双按 2, 长按 3)
if(button_order[button_deal] == 1)
{
if(now_mode[0] == 1)
{
now_mode[1] ^= 1;
}
else
if(now_mode[0] == 2)//歌曲选择
{
if(now_mode[1] == 3)
{
now_mode[1] = 0;
}
else
{
now_mode[1]++;
}
}
else
if(now_mode[0] == 3)//歌曲播放页面
{
//开关歌曲
now_mode[2] ^= 1;
}
}
else
if(button_order[button_deal] == 2) //(双击)
{
switch(now_mode[0])
{
case 1:break; //没想好干啥
case 2:
now_mode[0] = 3;//进入音乐播放界面
now_mode[2] = 1;//播放按钮打开
break;
case 3:
now_mode[0] = 2;//返回音乐菜单
now_mode[2] = 0;//播放按钮关闭
break;
default:break;
}
}
else
if(button_order[button_deal] == 3)//长按
{
if(now_mode[0] == 1)
{
now_mode[0] = 2;//切换为音乐模式
now_mode[1] = 0;//音乐界面默认第一个
now_mode[2] = 0;//播放按钮也关闭
}
else
{
now_mode[0] = 1;//切换为智能模式
now_mode[1] = 0;//界面默认第一个
now_mode[2] = 0;//播放按钮也关闭
}
}
//然后跳到下一位,接着判断是否重合
if(button_deal >= 5) //队尾始终为空,向目标前进
{
button_deal = 0;
}
else
{
button_deal++;
}
}
//可以接受新命令
button_atom = 0;
}
注意, 我们这里队首处理完按键,并没有清除其数据, 而是位序往后走, 去覆盖了,队尾移动到此处, 相当于默认此处数据可覆盖, 所以就默认清除了. 要注意,同时我们也可以进行清除, 处理完队头后, 对数组此处赋值一个特定的数值, 方便后续我们调试.