设计与开发

单片机 轮询 单片机之路—通过时间片轮询法解决按键失灵问题

小编 2025-07-29 设计与开发 23 0

单片机之路—通过时间片轮询法解决按键失灵问题

1、概述

(迟到的同学需要先看一下我上一篇独立按键操作的文章哦)上次文章中我们通过按键去控制流水灯的流水花式,我们发现按键会出现失灵的情况。原因是我们把按键扫描的执行任务优先级和执行流水灯的任务优先级放在一起了,互不能相互打断,但是,在我们的理解来说按键扫描的任务要比流水灯的任务优先级高才对。好比说,你正在写作业,你妈妈叫你写完作业后烧一壶开水。我们先要去听取并记住妈妈的指令,然后写完作业后才去执行烧开水的任务。在我们的程序中,妈妈下达的烧开水的指令就是用户按下按键通知程序执行完这次流水灯操作后去执行另外一种样式的流水灯。这个过程中程序需要先获取到按键的指令,并且记录下来,等执行完当前的任务后,再去执行新的任务。否则任务紧急任务(扫描按键)出现延时执行的情况,导致执行结果不是我们想要的。

2、时间片轮询法

显然的,按键扫描任务是一个要求实时性高的任务,所以在按键按下时,我们需要立即去查询当前的按键状态,并记录当前的按键状态。现在我们问题就是执行一次循环时间太长了,有没有办法使得执行一次循环更快,在很短的时间就能执行完一次循环,每个循环中都去检查一下按键的状态,这样就不会错过扫描按键的时机了。每一个时间片进行计数,计数到设定数值时,再去执行实时性要求不高的任务,比如流水灯切换任务。

时间片轮询法流程图

3、时间片轮询法在按键检测中的应用

我们开看在按键切换流水灯流水花式的程序中,怎么使用时间片轮询法。首先,在主循环之前定义一个按键状态记录的寄存器

unsigned char KeyNum = 0;

将主循环分成5ms每次的小循环,并且定义一个循环计数寄存器

void main()

{

unsigned char i;

unsigned char KeyNum = 0;

unsigned char Count = 0;

for(;;)

{

Count++;

if(Count >=250)

{

Count = 0; //防止溢出操作

}

delay_ms(5);

}

}

对计数进行判断,到设定计数执行对应的任务

void main()

{

unsigned char KeyNum = 0;//按键状态记录

unsigned char Count = 0;//计数记录

unsigned char LEDStatus= 1; //流水灯状态记录

for(;;)

{

Count++;

if(Count >200)

{

Count = 0; //计数清零

}

if((Count == 100)||(Count == 200))

{

if(KeyNum == 1)

{

//闪烁花式

if(Count == 100)

{

LedDisplay(0xAA);

}

else if(Count == 200)

{

LedDisplay(0x55);

}

}

else if(KeyNum == 2)

{

//流水花式

LedDisplay(~dat);

dat = 0x01<<LEDStatus;

LEDStatus++;

if(LEDStatus>=8)

{

LEDStatus = 0;

}

}

else

{

LedDisplay(0x00);

}

}

delay_ms(5);

}

}

4、总结

虽然这样能够实现功能,但是我们发现程序中需要定义很多的寄存器变量来保存程序的执行状态,搞得程序很繁琐,不够清爽。是否有一种方法可以让单片机在检测到触发事件时,自动记录程序的执行状态,并且去执行事件。执行完成事件后回来继续执行程序。实际是有的,那就是单片机的中断机制,我们可以通过单片机的中断机制实现程序的前后台系统。关注我,下次文章我带大家学习51单片机的外部中断。

干货|小论定时器玩法(时间轮询法)

经常来说,对于一些不复杂的单片机应用,而且对于内存和存储要求比较严格,又需要多分时去处理一些指定的任务,在无法使用RTOS的情况下,使用一个硬件定时器,来建立各种任务时间标志位,是比较通用的做法,但是随着时间标志位变量的增加,代码的维护以及简洁度却越来越艰难。

因此,楼主使用了一种类似线程管理的时间轮询方式(可能用词不当),来进行一个硬件定时器模拟多个软件定时器(以下就说明为线程吧),支持线程注册、注销、挂起、唤醒、处理等接口。在使用上,只需要引用两个接口,即可开始工作。 先说明下此接口文件的头文件的一些变量,THREAD_NUM_MAX是指能支持最大的线程数目,理论上可以达到四个字节长度数目。使用时候先初始化线程Thread_Init,然后把Thread_RunCheck放在硬件定时器上1ms查询一次当作时钟基准,然后Thread_Process放于while里面轮询查找线程标志。需要定时运行某个任务时候(函数),在进入while里面之前使用注册函数Thread_Login即可。主要退出这个函数使用Thread_Logout即可。下面会举个example来说明这些接口的使用。

接下来简单介绍下一些接口的实现。源码会放于附件,具体的可以自行分析理解。

首先来说明下两个结构体定义。首先第一个结构体是对线程状态封装,包括线程运行标识flag,当定时轮询到这个线程此标志位会置位,运行条件con包括单次执行运行条件,也就是说此线程只执行一次,还有多次执行条件,永久执行条件。此些条件在头文件的枚举定义。运行次数表示执行的次数,但为永久执行条件,此cnt赋值为0即可。frep为运行周期,即是多少毫秒运行一次,fun是线程函数的指针。thread_manage结构体是线程管理结构体,里面包含当前线程运行数和挂起数。在这里我们申请下一个静态线程管理实例变量。static struct thread_manage thread;所有的线程状态变量都在这里。

大概说明下线程是如何一个一个轮询执行的。主要在Thread_Process接口里面。

同样再来分析下线程注册接口,这是在初始化的时候需要用到。

线程运行条件核对,放在于1ms的硬件定时器中断或者回调中,以此轮询查找线程运行周期状态是否满足。

在这里使用的是nucle-L011板子,利用cube生成代码工程。使用两个外设,GPIO(LED)和usart,在这里我们新建两个线程,一个500ms点灯,一个1s串口发送数据。

生成的cube代码在tim.c文件上,开启定时器中断,并且写上自己的回调函数。

写好自己的线程函数

初始化以及注册线程。

以上图文内容均转载自订阅号:电子工程世界(微信搜索 eeworldbbs 关注)

欢迎微博@EEWORLD

如果您也写过此类原创干货欢迎将您的原创发至:bbs_service@eeworld.com.cn,一经入选,我们将帮你登上头条!

与更多行业内网友进行交流请登陆EEWorld论坛。

相关问答

单片机 的按键怎么外接-ZOL问答

一般单片机的外接按键有三种接法:1、每个I/O端口接一个按键:按键接另一端通常是接地,I/O口通常还应该接一个上拉电阻,有些单片机的I/O口可以配置成...

如何选用485转换器?

485转换器485转换器主要的作用是将单端的RS-232信号转换为平衡差分的RS-485或RS-422信号。RS-485、RS-422自动识别功能,使用更加简单中文名多功能转换器外...

猜你喜欢