×
嵌入式 > 技术百科 > 详情

单片机中Delay延时函数的缺点与改善

发布时间:2020-06-17 发布时间:
|

最近通过专业综合实验接触单片机编程。对历程中的Delay函数有所疑问。

写笔记备忘一下。


问题的出现:不管是郭老师的单片机初学教程,还是各种开发板配套资料,一般都会用到类似函数来完成延时:
void Delay(unsigned int t)
{
unsigned int k;
while(t--)
{
for(k=0; k<80; k++)
;
}
}
例如在按键检测中,线delay一会儿来完成去抖动,检测按下后再delay一会儿来完成等待弹起。再比如在数码管显示中,控制第一个数码管显示,delay一会再控制第二个,否则就会导致整个数码管都被刷亮,一直显示8。再比如步进电机控制,先发110000再发011000之间用delay来控制转速,delay的久转的慢。
但是如果需要程序通过按键来控制步进电机的速度,同时将控制参数显示在数码管上。这时问题就出现了:在检测按键的时候电机不转了,数码管也不现实了,因为CPU在delay,在做没有用的空运算。同样的,控制电机转的时候数码管也不亮了,按键也不能检测了。控制数码管也是同理。可见历程中每个模块的代码都是不可复用的,不可扩展的。那么怎么写出可复用、可扩展的单片机程序呢。
问题的解决:先把这种方法起个名字就叫带计数的轮询模式吧。
CPU.h中:
unsigned int CPU_Count = 0; //全局变量CPU循环计数
main.cpp中:
#include 'CPU.h'
#include '... //其他设备控制
void main()
{
while(1)
{
CPU_Count ++ ;
ProcessingMotor(); //电机控制
ProcessingLED(); //LED显示
ProcessingKey(); //按键检测
}
}
下面看具体的ProcessingMotor的实现。
在Motor.h中
#define GPIO_MOTOR P1
unsigned char code FFW[8]={0xf1,0xf3,0xf2,0xf6,0xf4,0xfc,0xf8,0xf9}; //反转顺序
unsigned char code FFZ[8]={0xf9,0xf8,0xfc,0xf4,0xf6,0xf2,0xf3,0xf1}; //正转顺序
unsigned char Direction,Speed;
void Motor()
{
if(Speed == 0)
return;
if(CPU_Count%Speed == 0)
{
if(Direction==1)
GPIO_MOTOR = FFW[(CPU_Count/Speed)%8]&0x1f; //取数据
if(Direction==2)
GPIO_MOTOR = FFZ[(CPU_Count/Speed)%8]&0x1f;
}
}
我们可以通过CPU_Count%Speed==0来控制while循环几次来进行一次脉冲发送,同时可以通过(CPU_Count/Speed)%8的值来得到该发地几个波了。这样CPU就可以得到充分的利用。
LED的控制也是类似,这里就不再赘述。
可复用可扩展的按键检测
其实按键检测关键就在两个地方,一个是按键接在哪个引脚上,一个是按键按下应该响应那个函数。如果我们能通过一些后台机制,最终实现一个接口:connectButton(uchar PX,uchar PMask,(*SLOT)());以后再需要用到按键的检测,只需要写一个按键按下对应的函数,并调用connectButton即可。
那么怎么样实现这样一个借口呢?
首先我们使用这样一种检测按键的方法。在CPU的while循环中每次都去读按键的状态,如果按键按下测PressCount++;如果按键没有按下则unPressCount++;在pressCount加到一个临界值说明按键按下,同时将unPressCount清零。unPressCount加到一个临界值,说明按键弹起,将pressCount清零。
下面是代码实现:
typedef void(*SLOT)();
#define B_CNT 4
unsigned char listSize = 0;
typedef struct ButtonNode
{
unsigned char isPressed; //是否被按下
unsigned char px; // 标记P0等
unsigned char pmask; // 标记P0等第几个管脚
unsigned int pressCount; //cpu一次轮转中,如果被按下则加一
unsigned int unPressCount; //cpu一次轮转中,如果弹起则加一
SLOT func; //回调函数
}ButtonNode;
ButtonNode Button[B_CNT]
这里用的是结构体的数组来保存需要的检测的按键,其实最好用链表动态申请空间,因为用链表才能真正实现开闭性,不用对代码做任何修改。而我们这里为了节约内存,每次都要修改检测的按键个数B_CNT。
然后实现ProcessingKey,对保存的按键信息进行循环检测。
void ProcessingKey()
{
unsigned char num = CPU_Count%BUTTON_NUM;
unsigned char p;
if( num >= listSize)
return;
switch (Button[num].px)
{
case P0_X:
p = P0;
break;
case P1_X:
p = P1;
break;
case P2_X:
p = P2;
break;
case P3_X:
p = P3;
break;
}
if(!((p >> Button[num].pmask) & 1))
{
Button[num].pressCount ++ ;
if(Button[num].pressCount==150)
{
Button[num].pressCount=0;
if(Button[num].isPressed==0)
{
Button[num].func();
Button[num].isPressed = 1;
Button[num].unPressCount=0;
}
}
}else
{
Button[num].unPressCount ++;
if(Button[num].unPressCount==150)
{
Button[num].unPressCount=0;
if(Button[num].isPressed==1)
{
Button[num].isPressed = 0;
Button[num].pressCount=0;
}
}
}
}
这里我们使用150次作为临界值。
最后我们来实现最想要的接口connectButton:
void connectButton(unsigned char _px,unsigned char _pmask,SLOT _func)
{
Button[listSize].isPressed = 0;
Button[listSize].px = _px;
Button[listSize].pmask = _pmask;
Button[listSize].pressCount = 0;
Button[listSize].unPressCount = 0;
Button[listSize].func = _func;
listSize ++;
}
大功告成。该enjoy it了。
以后我们的单片机程序要使用某个按键按下执行一些内容时,只需要这样
实现按下后的响应
void onP3_0Pressed(){ ... }
调用connectButton(P3_X,0,onP3_0Pressed)
只要你使用的是计数轮询模式,那么按键检测就可以直接实现了。



『本文转载自网络,版权归原作者所有,如有侵权请联系删除』

热门文章 更多
分拣机器人的工作原理是什么