历史上的今天
返回首页

历史上的今天

今天是:2024年10月06日(星期日)

2018年10月06日 | 单片机中Delay延时函数的缺点与改善

2018-10-06 来源:eefocus

最近通过专业综合实验接触单片机编程。对历程中的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)
只要你使用的是计数轮询模式,那么按键检测就可以直接实现了。


推荐阅读

史海拾趣

Chicago Miniature公司的发展小趣事

为了满足不同客户的需求,CML不断扩展产品线,从最初的光电子产品逐渐拓展到显示器、开关、继电器等多个领域。通过多样化的产品策略,公司成功吸引了更多客户,并扩大了市场份额。同时,CML还积极开拓国际市场,将产品出口到全球多个国家和地区,进一步提升了公司的知名度和影响力。

固得沃克(GOODWORK)公司的发展小趣事

在追求经济效益的同时,固得沃克也高度重视环境保护和社会责任。公司所有产品均通过了美国UL、SGS欧盟ROHS及REACH环保认证,确保产品符合国际环保标准。这一举措不仅提升了公司的品牌形象,也为公司拓展国际市场提供了有力支持。近年来,固得沃克的产品已广泛应用于汽车电子、新能源、绿色照明等多个领域,并远销海外多个国家和地区。

Astema公司的发展小趣事

随着技术的不断成熟,Astema开始积极拓展市场。公司制定了一系列市场策略,包括与大型电子设备制造商建立合作关系、参加国际电子展等,以扩大品牌影响力。通过这些努力,Astema逐渐打开了国际市场的大门,其产品远销海外,市场份额稳步提升。

意华(CZT)公司的发展小趣事

为了实现公司的长远发展,意华于2010年开始筹备上市工作。经过多年的准备和努力,意华成功在资本市场上市,为公司的发展注入了新的活力。上市后,意华通过资本市场融资和资本运作,加速了公司的扩张和技术创新步伐。同时,公司也更加注重规范运作和透明度提升,赢得了投资者的信任和支持。

Anpec(茂达)公司的发展小趣事

Anpec(茂达电子)自1997年创立之初,就肩负着提升国内电源模拟设计环境、建立自主模拟及电源相关产业的使命。初期,公司规模虽小,但志向远大。面对国内外市场的激烈竞争,茂达电子凭借对技术的执着追求和对市场的敏锐洞察,逐步在模拟集成电路设计领域站稳脚跟。

歌尔(Goertek)公司的发展小趣事

随着技术的不断发展,茂达电子在产品研发上不断取得突破。公司投入大量人力、物力进行技术研发,并与多家国内外著名大学和研究机构合作,共同推进模拟集成电路设计的创新。经过不懈的努力,茂达电子成功推出了一系列世界领先的半导体器件产品,赢得了市场的广泛认可。

问答坊 | AI 解惑

图像处理

希望对大家有帮助 呵呵   加油 谢谢了哈…

查看全部问答>

一点点想法

看到论坛首页的热门板块的排名 忽然想把一些不太热的炒起来 有兴趣的回帖…

查看全部问答>

心电监护仪的使用与维修浅谈

杨伟文   (北京大学深圳医院,广东深圳518036) 随着电子技术的飞速发展,心电监护仪的设计开发日益趋向高度智能化、高度集成化发展,其开发成本、生产成本以及使用成本比起以前都有很大的降低。特别是近年来以迈瑞公司为代表的国产心电 ...…

查看全部问答>

为什么跳蚤市场没有东西卖呢?

如题!第一眼看见跳蚤板块的时候 想到的就是大家用来交换或者出售自己闲置的物品的平台  捡来看了半天 没找到 不过这里面介绍的一些小玩意倒是蛮有创意的…

查看全部问答>

想学linux嵌入式不知道怎么入门?

我现在是做java开发的,想学嵌入式不知道怎么入门?希望能给点意见。。比如先学什么,再学什么,最后学什么? 另外Linux会操作,原理不懂。大学时候操作系统也学的不好,是不是一定要学操作系统原来?c,c++学的还可以。硬件知识几乎完全不懂。…

查看全部问答>

EVC XML文件的读写

下载网上的CMarkup类,在EVC下实现了XML文件的写入和读取,可是使用该类在VC6.0下编写的程序生成的xml文件下载到ppc2003中去读取不了,有没有人碰到类似的问题?难道EVC下的程序只能读取通过EVC编写并生成的XML文件吗?…

查看全部问答>

寻找元器件

哪个仿真软件元件库里有MC14066…

查看全部问答>

申请使用Spartan®-6 FPGA LX9 MicroBoard

申请使用Spartan®-6 FPGA LX9 MicroBoard ,想了解Spartan®-6 FPGA LX9 MicroBoard ;熟悉FPGA的开发流程;熟悉巩固HDL语言(VHDL的);学习Nios+IP核。 还未开始使用:所见所得后续传上!联系方式:QQ:740182380 邮箱:gongwuxi1988@hot ...…

查看全部问答>

stm32f4xx的DSP和外设库使用说明

stm32f4xx的DSP和外设库使用说明 …

查看全部问答>