狀態(tài)機(jī)是軟件編程中的重要概念,比這個(gè)概念更重要的是對(duì)它的靈活應(yīng)用。在一個(gè)思路清晰而且高效的程序中,必然有狀態(tài)機(jī)的身影浮現(xiàn)。例如,一個(gè)按鍵命令解析程序就可以被看做狀態(tài)機(jī):本來在A狀態(tài)下,觸發(fā)一個(gè)按鍵后切換到了B狀態(tài);再觸發(fā)另一個(gè)鍵后切換到C狀態(tài),或者返回到A狀態(tài)。這就是最簡單的按鍵狀態(tài)機(jī)的例子。實(shí)際的按鍵解析程序會(huì)比這更復(fù)雜,但這并不影響我們對(duì)狀態(tài)機(jī)的認(rèn)識(shí)。
進(jìn)一步看,擊鍵動(dòng)作本身可以看做一個(gè)狀態(tài)機(jī)。一個(gè)擊鍵動(dòng)作包含按下、抖動(dòng)、釋放等狀態(tài)。其實(shí)狀態(tài)機(jī)的思想不單只是用在按鍵方面,數(shù)碼管顯示動(dòng)態(tài)掃描、LED燈亮滅都存在狀態(tài)機(jī)的思想。使用狀態(tài)機(jī)思想進(jìn)行單片機(jī)編程,比較通用的方法就是使用switch的選擇性分支語句來進(jìn)行狀態(tài)跳轉(zhuǎn)。
通過計(jì)數(shù)器這個(gè)實(shí)驗(yàn)向大家展示狀態(tài)機(jī)的思想。
上圖是proteus仿真圖,時(shí)間每過1s計(jì)數(shù)器值自動(dòng)加1,K1啟動(dòng)和停止計(jì)數(shù)器,K2選擇要修改的位,K3當(dāng)前位加1,K4當(dāng)前位減1。
完整代碼如下:
#include<reg51.h>
typedef unsigned char UINT8;
typedef unsigned int UINT16;
typedef unsigned long UINT32;
typedef char INT8;
typedef int INT16;
typedef long INT32;
#define TIMER0_INITIAL_VALUE 5000 //5ms定時(shí)
#define SEG_PORT P0 //數(shù)碼管占用的IO口
#define KEY_PORT P1 //按鍵占用的IO口
#define KEY_MASK 0x0F //按鍵掩碼
#define KEY_SEARCH_STATUS 0 //查詢按鍵狀態(tài)
#define KEY_ACK_STATUS 1 //確認(rèn)按鍵狀態(tài)
#define KEY_REALEASE_STATUS 2 //釋放按鍵狀態(tài)
#define KEY1 1 //按鍵1鍵值
#define KEY2 2 //按鍵2鍵值
#define KEY3 3 //按鍵3鍵值
#define KEY4 4 //按鍵4鍵值
#define HIGH 1
#define LOW 0
#define ON 1
#define OFF 0
sbit DATA = P0^4;
sbit CLK = P0^5;
UINT8 Timer0IRQEvent = 0; //定時(shí)器0中斷事件
UINT8 Time1SecEvent = 0; //1s定時(shí)事件
UINT8 TimeCount = 0; //定時(shí)器0計(jì)數(shù)器,用于計(jì)數(shù)產(chǎn)生1s定時(shí)事件
UINT8 SegCurPosMark = 0; //被選中的數(shù)碼管
UINT16 CounterValue = 0; //計(jì)數(shù)器
UINT8 SegCurSel = 0; //當(dāng)前選中的數(shù)碼管
UINT8 SegBuf[4] = {0};
code UINT8 SegCode[10] = {~0x3F,~0x06,~0x5B,~0x4F,~0x66,~0x6D,~0x7D,~0x07,~0x7F,~0x6F};
code UINT8 SegSelTbl[4] = {0xFE,0xFD,0xFB,0xF7};
UINT8 bSetTime = 0; //標(biāo)志位:是否設(shè)置計(jì)數(shù)值
void LS164_DATA(unsigned char x)
{
if(x)
{
DATA = 1;
}
else
{
DATA = 0;
}
}
void LS164_CLK(unsigned char x)
{
if(x)
{
CLK = 1;
}
else
{
CLK = 0;
}
}
/**********************************************************
*函數(shù)名稱:LS164Send
*輸 入:byte單個(gè)字節(jié)
*輸 出:無
*功 能:74LS164發(fā)送單個(gè)字節(jié)
***********************************************************/
void LS164Send(UINT8 byte)
{
UINT8 j;
for(j=0;j<=7;j++)
{
if(byte&(1<<(7-j)))
{
LS164_DATA(HIGH);
}
else
{
LS164_DATA(LOW);
}
LS164_CLK(LOW);
LS164_CLK(HIGH);
}
}
/**********************************************************
*函數(shù)名稱:SegRefreshDisplayBuf
*輸 入:無
*輸 出:無
*功 能:數(shù)碼管刷新顯示緩存
***********************************************************/
void SegRefreshDisplayBuf(void)
{
SegBuf[0] = CounterValue%10;
SegBuf[1] = CounterValue/10%10;
SegBuf[2] = CounterValue/100%10;
SegBuf[3] = CounterValue/1000%10;
}
/**********************************************************
*函數(shù)名稱:SegDisplay
*輸 入:無
*輸 出:無
*功 能:數(shù)碼管顯示數(shù)據(jù)
***********************************************************/
void SegDisplay(void)
{
UINT8 t;
SEG_PORT = 0x0F; //熄滅所有數(shù)碼管
if(bSetTime) //檢查是否設(shè)置計(jì)數(shù)值
{
if(SegCurSel == SegCurPosMark)
{
t = SegCode[SegBuf[SegCurSel]] & 0x7F; //加上小數(shù)點(diǎn)
}
else
{
t = SegCode[SegBuf[SegCurSel]]; //正常顯示當(dāng)前數(shù)值
}
}
else
{
t = SegCode[SegBuf[SegCurSel]]; //正常顯示當(dāng)前數(shù)值
}
LS164Send(t);
SEG_PORT = SegSelTbl[SegCurSel]; //點(diǎn)亮當(dāng)前要顯示的數(shù)碼管
if(++SegCurSel >= 4)
{
SegCurSel = 0;
}
}
/**********************************************************
*函數(shù)名稱:TimerInit
*輸 入:無
*輸 出:無
*功 能:定時(shí)器初始化
***********************************************************/
void TimerInit(void)
{
TH0 = (65536 - TIMER0_INITIAL_VALUE)/256;
TL0 = (65536 - TIMER0_INITIAL_VALUE)%256;
TMOD = 0x01;
}
/**********************************************************
*函數(shù)名稱:Timer0Start
*輸 入:無
*輸 出:無
*功 能:定時(shí)器啟動(dòng)
***********************************************************/
void Timer0Start(void)
{
TR0 = 1;
ET0 = 1;
}
/**********************************************************
*函數(shù)名稱:Timer0Stop
*輸 入:無
*輸 出:無
*功 能:定時(shí)器停止
***********************************************************/
void Timer0Stop(void)
{
TR0 = 0;
ET0 = 0;
}
/**********************************************************
*函數(shù)名稱:PortInit
*輸 入:無
*輸 出:無
*功 能:I/O初始化
***********************************************************/
void PortInit(void)
{
P0 = P1 = P2 = P3 = 0xFF;
}
/**********************************************************
*函數(shù)名稱:KeyRead
*輸 入:無
*輸 出:當(dāng)前按下的按鍵
*功 能:讀取按鍵值
***********************************************************/
UINT8 KeyRead(void)
{
//KeyStatus:靜態(tài)變量,保存按鍵狀態(tài)
//keyCurPress:靜態(tài)變量,保存當(dāng)前按鍵的鍵值
static UINT8 KeyStatus = KEY_SEARCH_STATUS,KeyCurPress = 0;
UINT8 KeyValue;
UINT8 i = 0;
KeyValue = (~KEY_PORT) & KEY_MASK;
switch(KeyStatus)
{
case KEY_SEARCH_STATUS: //按鍵查詢狀態(tài)
{
if(KeyValue)
{
KeyStatus = KEY_ACK_STATUS; //按鍵下一個(gè)狀態(tài)為確認(rèn)狀態(tài)
}
return 0;
}
break;
case KEY_ACK_STATUS: //按鍵確認(rèn)狀態(tài)
{
if(!KeyValue)
{
KeyStatus = KEY_SEARCH_STATUS;
}
else
{
for(i=0;i<4;i++)
{
if(KeyValue&(1<<i))
{
KeyCurPress = KEY1 + i;
break;
}
}
KeyStatus = KEY_REALEASE_STATUS;
}
return 0;
}
break;
case KEY_REALEASE_STATUS: //按鍵釋放狀態(tài)
{
if(!KeyValue)
{
KeyStatus = KEY_SEARCH_STATUS;
return KeyCurPress;
}
return 0;
}
default:
return 0;
break;
}
}
/**********************************************************
*函數(shù)名稱:main
*輸 入:無
*輸 出:無
*功 能:函數(shù)主題
***********************************************************/
void main(void)
{
PortInit();
TimerInit();
Timer0Start();
SegRefreshDisplayBuf();
EA = 1;
while(1)
{
SegRefreshDisplayBuf();
if(Timer0IRQEvent)
{
Timer0IRQEvent = 0;
switch(KeyRead())
{
case KEY1:
{
bSetTime = ~bSetTime;
SegCurPosMark = 0;
}
break;
case KEY2:
{
if(++SegCurPosMark>=4)
{
SegCurPosMark = 0;
}
}
break;
case KEY3:
{
if(!bSetTime)
break;
if(CounterValue>=9999)
CounterValue = 0;
if (SegCurPosMark == 0)
CounterValue += 1;
else if(SegCurPosMark == 1)
CounterValue += 10;
else if(SegCurPosMark == 2)
CounterValue += 100;
else
CounterValue += 1000;
}
break;
case KEY4:
{
if(!bSetTime)
break;
if(CounterValue<=0)
CounterValue = 9999;
if (SegCurPosMark == 0)
CounterValue -= 1;
else if(SegCurPosMark == 1)
CounterValue -= 10;
else if(SegCurPosMark == 2)
CounterValue -= 100;
else
CounterValue -= 1000;
}
break;
default:
break;
}
}
else if(Time1SecEvent)
{
Time1SecEvent = 0;
if(!bSetTime)
{
if(++CounterValue>=9999)
{
CounterValue = 0;
}
}
}
}
}
/**********************************************************
*函數(shù)名稱:Timer0IRQ
*輸 入:無
*輸 出:無
*功 能:定時(shí)器中斷函數(shù)
***********************************************************/
void Timer0IRQ(void) interrupt 1
{
TH0 = (65536 - TIMER0_INITIAL_VALUE)/256;
TL0 = (65536 - TIMER0_INITIAL_VALUE)%256;
Timer0IRQEvent = 1;
SegDisplay();
if(++TimeCount >= 200)
{
TimeCount = 0;
Time1SecEvent = 1;
}
}