最近通過專業綜合實驗接觸單片機編程。對歷程中的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)
只要你使用的是計數輪詢模式,那么按鍵檢測就可以直接實現了。
|