51單片機實現貪吃蛇(雙色點陣)

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
}																	

																 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章