1.硬件
德菲來 51開發板,雙色點陣
2.基本流程
蛇身用二維數組存儲,上下左右通過dx,dy組合,有4個使能位(上行時不能向下),HC595鎖存發數據。
功能:暫停、加減速、上下左右(keyscan()裏掃描改變狀態)、變色、食物長時間隨機消失
使用了兩個定時器,T0:發送點碼,2ms。 T1:keyscan(),time檢測食物存活時間。65ms
單片機
3.代碼
#include<reg52.h> //包含頭文件,一般情況不需要改動,頭文件包含特殊功能寄存器的定義
#include <intrins.h>
/**硬件端口定義**/
sbit LATCH= P1^0; //鎖存
sbit SRCLK= P1^1; //時鐘
sbit SER = P1^2; //數據
sbit LATCH_B= P2^2; //鎖存,公共端
sbit SRCLK_B= P2^1; //時鐘
sbit SER_B= P2^0; //數據
sbit LED=P1^4; //結束提示燈
sbit key1=P3^0; //上
sbit key2=P3^1; //下
sbit key3=P3^2; //左
sbit key4=P3^3; //右
sbit key5=P3^4; //暫停
sbit key6=P1^5;
/**全局變量定義**/
unsigned char x[30],y[30]; //蛇身座標
unsigned char speed=10; //控制速度變量
unsigned char dx=0,dy=-1; //控制轉向變量,初始化爲向下運動。上:-1 0 ,下:1 0 ,左 0 1 ,右 0 -1
bit stop_start,inverse,time; //開始/暫停標誌位,顏色顯示標誌位
bit up=1,down=1,left=0,right=1;//上下左右使能控制位,如避免向上運動時啓動向下操作
unsigned char tab[8]; //顯示緩衝數組
unsigned char segout[8]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80}; //8列掃描
int lim=0;
/**us延時函數,晶振12MHZ,大致延時 2*t us**/
void DelayUs2x(unsigned char t)
{
while(--t);
}
/**ms延時函數,晶振12MHZ,大致延時 t ms**/
void DelayMs(int t) //大致延時1mS
{
while(t--)
{
DelayUs2x(245);
DelayUs2x(245);
}
}
/**發送字節,寫入數據原理,SRCLK 輸入時鐘信號,爲輸入數據提供時間基準,跟隨時鐘信號輸入對應的
數據信號。這部分僅僅發送數據,沒有鎖存輸出部分。鎖存部分在所有數據傳輸完畢後執行**/
void SendByte(unsigned char dat)
{
unsigned char i;
for(i=0;i<8;i++)
{
SRCLK=0;
SER=dat&0x80; //取首位
dat<<=1;
SRCLK=1;
}
}
/**發送雙字節程序,595級聯,n個595,就需要發送n字節後鎖存。同時控制 2 種顏色就必須同時寫入 2 個字節**/
void Send2Byte(unsigned char dat1,unsigned char dat2)
{
SendByte(dat1);
SendByte(dat2);
}
/**595鎖存程序,595級聯發送數據後,鎖存有效,這個鎖存僅僅針對 2 種顏色控制的 HC595 有效,**/
void Out595(void)
{
LATCH=0;
_nop_(); //增加上升沿時間
LATCH=1;
}
/**發送位碼字節程序使用另外一片單獨595,公共端控制用 HC595 數據發送與鎖存,由於只使用 1 個 HC595,沒有級聯,
可以在一個函數中直接輸入數據並鎖存**/
void SendSeg(unsigned char dat)
{
unsigned char i;
for(i=0;i<8;i++) //發送字節
{
SRCLK_B=0;
SER_B=dat&0x80;
dat<<=1;
SRCLK_B=1;
}
LATCH_B=0; //鎖存
_nop_();
LATCH_B=1;
}
/**按鍵掃描函數**/
void key_scan()
{
switch(P3) //上下左右停加減,P3.0-P3.4,P3.6-3.7
{
case 0xfe: if(up) //up
{
dx=-1;dy=0; //執行向上功能
down=0;left=1;right=1; //向下功能失效,其他功能可用
}
break;
case 0xfd: if(down) //down
{dx=1;dy=0;up=0;left=1;right=1;} //
break;
case 0xfb: if(left) //left
{dx=0;dy=1;down=1;up=1;right=0;} //
break;
case 0xf7: if(right) //right
{dx=0;dy=-1;down=1;left=0;up=1;} //
break;
case 0xef: //暫停/開始鍵
DelayMs(10); //延時去抖
if(P3==0xef) //再次確認按鍵是否按下
stop_start=~stop_start; //暫停/開始標誌位取反(按一下暫停再按一下開始)
while(P3==0xef); //等待按鍵釋放
break;
case 0xbf:
DelayMs(10); //延時去抖
if(P3==0xbf) //再次確認按鍵是否按下
speed+=2; //速度增大,實際減速
while(P3==0xbf); //等待按鍵釋放
break;
case 0x7f: DelayMs(10); //延時去抖
if(P3==0x7f) //再次確認按鍵是否按下
speed-=2; //速度減小,實際速度增加
while(P3==0x7f); //等待按鍵釋放
break;
default: break;
}
}
/**清除顯示緩衝區,即清屏**/
void clr_ram(void)
{
unsigned char i;
for(i = 0; i < 8; i++)
tab[i] = 0x00; //逐行清除數組內容
}
/** 畫點函數,擦點或者繪點
點陣左上角座標爲(0, 0) 左下角座標爲(7, 7)
橫座標爲x:0~7 縱座標爲y:0~7
k = 1 --繪點 k = 0 --擦點 **/
void point1(unsigned char x, unsigned char y, bit k)
{
if(k) tab[y] |= 0x01 << x; //保留原始點,繪製新點,先定位x的行,在這一列與或
else tab[y] &= ~(0x01 << x); //保留其它點,只擦其中一個點
}
/**定時器0初始化*/
void T0_init(void)
{
TMOD|= 0x01;
TH0 = 0xf8; //方式1,計數值爲(65536-63542=2000)=2ms
TL0 = 0x36;
//IE |= 0x82; //中斷開放寄存器,ETO=1
EA=1;ET0=1;
TR0 = 1;
}
/**定時器1初始化*/
void T1_init(void)
{
TMOD|= 0x01;
TH1 = 0x00; //65ms
TL1 = 0x00;
// IE |= 0x88; //IE1,ET1=1
EA=1;ET1=1;
TR1 = 1;
}
/**主程序**/
void main()
{
unsigned char i=0,foodx, foody; //食物座標
unsigned char num=3; //蛇長度初始3
bit food,over; //食物和結束標誌位
//IT0 = 1; //外部中斷0(即P3^2腳)選擇邊沿觸發,下降沿有效
//EX0 = 1; //打開定時器中斷0
T0_init(); //定時器0初始化
T1_init(); //定時器1初始化
stop_start=0; //開始/暫停標誌位置,0爲開始
while(1)
{
x[0] += dx; y[0] += dy; //根據dxdy不同的值來使蛇頭移動
x[0] &= 0x07; y[0] &= 0x07; //作用穿牆,x或y加到8時變爲0,-1(ff)->7
if(time)
{ time=0;
point1(foodx, foody, 0); //10秒未吃到,食物更新
food = 0;
}
if(!food ) //放置食物,0被吃
{
again: foodx = TL0&0x07; //隨機取食物座標,0~7,但不會超過7
foody = TH0&0x07;
for(i = 0; i < num; i++)
{
if(foodx==x[i]&&foody==y[i]) //若食物與蛇身重疊,
goto again; //則重放食物。
}
//inverse=0; //顏色標誌位置0,顯示紅色
//inverse=1;
point1(foodx, foody, 1); //顯示食物
food = 1; //置食物標誌位
}
if(x[0] == foodx && y[0] == foody) //吃到食物
{
num++; //蛇長增加1節
food = 0; //清食物標誌位
inverse=~inverse; //變色
}
for(;stop_start;); //按下暫停鍵程序在此進入死循環,1代表暫停
//顏色標誌位置1,顯示綠色
for(i = 0; i < num; i++) //顯示蛇身
point1(x[i], y[i], 1);
point1(x[i], y[i], 0); //清蛇尾
for(i = 1; i < num; i++) //判斷是否自撞
{
if((x[0]==x[i])&&(y[0]==y[i]))
over = 1; //置結束標誌位
}
for(i=0;i<speed;i++)
{
DelayMs(15); //蛇運動速度,暫停顯示蛇身
}
for(i = 0; i < num; i++) //蛇移動蛇身
{
x[num-i] = x[num-i-1]; //除蛇頭外均移動一格
y[num-i] = y[num-i-1];
}
if(over) //判斷是否結束
{
for(i=0;i<10;i++) //LED閃5次
{
LED=~LED;
DelayMs(100);
}
clr_ram(); //清除屏幕
num = 3; //重新設定蛇長
point1(foodx, foody, 1); //重新放置食物
x[0] = 0; y[0] = 0; //起點位置
dx=0;dy=-1; up=1,down=1,left=0,right=1; //向右運動
over = 0; //清除結束標誌
}
}
}
/**定時器1中斷服務**/
void T1_intservice(void) interrupt 3
{
TH1 = 0x00;
TL1 = 0x00;
key_scan();
lim++;
if(lim >= 200) //10秒
{
lim = 0;
time = 1;
}
}
/**定時器0中斷服務**/
void T0_intservice(void) interrupt 1
{
static unsigned char n; //定義靜態變量
TR1 = 0; //關閉定時器1
TH0 = 0xf8;
TL0 = 0x36; //重裝初值,2ms
SendSeg(segout[n]); //發送列碼(相當於數碼管中的位碼)
if(inverse)
Send2Byte(0xff,~tab[n]);//發送點碼(相當於數碼管中的段碼),顯示綠色,交換兩個量可改變顏色
else
Send2Byte(~tab[n],0xff);//發送點碼(相當於數碼管中的段碼),顯示紅色,交換兩個量可改變顏色
Out595(); //595鎖存程序
DelayMs(1);
Send2Byte(0xff,0xff); //防止重影
Out595();
n++; if(n == 8) n = 0; //循環掃描
TR1 = 1; //打開定時器1
}