找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 2690|回复: 0
打印 上一主题 下一主题
收起左侧

单片机--放下Delay,立地成佛

[复制链接]
跳转到指定楼层
楼主
ID:59284 发表于 2014-2-26 19:29 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
最近通过专业综合实验接触单片机编程。对历程中的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)
只要你使用的是计数轮询模式,那么按键检测就可以直接实现了。

分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏 分享淘帖 顶 踩
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表