一. 实验所需模块
4×4矩阵键盘,8×8点阵,定时器0
二. 模块简介
4×4矩阵键盘
矩阵键盘采用行和列扫描的方式进行判断那个按键被按下了
先进行扫描或先进行列扫描都可以
行扫描的时候,先将行所在的引脚置一,列所在的引脚置0,然后判断行所在引脚的电平的值,随后进行列扫描
8×8点阵
8×8点阵共由64个发光二极管组成,且每个发光二极管是放置在行线和列线的交叉点上。
当某一行置1,某一列置0时,对应的二极管就会点亮。
与动态数码类似,利用视觉残留效果,即可实现动态依次点亮的效果
定时器0
16位定时器由高8位和低8位的两个寄存器组成TH0和TL0。
每来一个脉冲,定时器的值就会加一,当达到最大的时候,就会溢出清零,并产生中断信号。
预先设置TH0和TL0的值,可以得到我们所需的生产中断信号的间隔。
三. 点亮一个灯
这个很简单,就是把某一行线置1,并且把某一列线置0,其他的行线全部为0,列线全部为1.
void Led(u8 dat,u8 dat2) //dat为行线值,dat2为列线值 { u16 i; RCLKS = 1; SRCLK = 1; for(i=0;i<8;i++) { //行线的输入是由74HC595模块输入的, //通过时钟的上升沿把每一位的数据输入即可,高位优先 SET = dat>>7; dat = dat <<1; SRCLK = 0; SRCLK =1; } RCLKS = 0; RCLKS = 1; P0 = dat2; //列线由P0端口输入 }
这里令dat = 0x20,dat2=fb的时候,一个发光二极管就被点亮啦!
四. 一个灯的移动
前面我们已经知道了,行线可以控制那一行亮那一行不亮,列线也同样如此。
假设当我们的列线不变,行线左移一位或者右移一位,是不是我们的灯向上或者向下移动了一个!行线不变时,列线的移动同样如此,但是要注意,列线是为0点亮,每一次左移或右移,低位或高位就会多出来一个0,这时,我们还需要加上0x01或者0x80以消掉这个多出来的0
1.向上移动
列线不变,行线左移一位即可
if(Rows[0] != 0x80) //防止移过头了 Rows[0] <<=1;
2.向下移动
列线不变,行线右移一位即可
if(Rows[0] != 0x01) Rows[0]>>=1;
3.向左移动
行线不变,列线左移一位加0x01即可
if(Cols[0] != 0x7F) { Cols[0] <<=1; Cols[0] += 1; }
4.向右移动
行线不变,列线右移一位加0x80即可
if(Cols[0] != 0xFE) { Cols[0] >>=1; Cols[0] += 0x80; }
自此,一个灯的上下左右的移动我们已经完成了,接下来蛇的移动了
五. 贪吃蛇
认真看玩过贪吃蛇的朋友应该都清楚,蛇身是随着蛇头的移动而移动的。其中不难发现。当前蛇的某一节的位置是它前面一节蛇的上一个状态的位置,知道了这个规律之后,我们就可以很方便地更新蛇的状态了。
由于8×8点阵显示有限,我就只设置了蛇的最大长度为5,防止闪烁。
1.蛇的存储
这里我采取的是数组的方式,存储每一节的行线值和列线值
u8 Rows[5]; //行线值 u8 Cols[5]; //列线值
2.蛇的更新
前面说过,蛇的当前蛇的某一节的位置是它前面一节蛇的上一个状态的位置,也就是说,Rows[n-1] = Rows[n],Cols[n-1] = Cols[n] 当然这里的Rows[n],和Cols[n]是上一个状态的值了
void Up(u8 len) //当前蛇的长度 { u8 i; u8 tmp = Rows[0]; u8 rr,cc_1,cc_2; if(Rows[0] != 0x80) Rows[0] <<=1; cc_1 = Cols[0]; //后一节依次等于前一节的上一个状态的值 for(i=1;i<len;i++) { rr = Rows[i]; // 保存上一个状态 Rows[i] =tmp; //Rows[i]为当前状态 tmp = rr; cc_2 = Cols[i]; // 保存上一个状态 Cols[i] = cc_1; cc_1 = cc_2; } }
这里就只拿了向上移动的代码作为参考,其他方向的移动类型,基本上一样,这里蛇的移动也完成了。
3.食物
食物的行线与列线同样用两个变量存储 食物的位置是随机的,这里我们用rand产生随机数并对8求余数, 这样就得到了0-7的随机数,然后对0x01进行相应的左移,就得到了 选定的行,取反就得到了选定的列。
Food_col = ~(0x01 << rand()%8); Food_row = 0x01 << rand()%8;
4.吃到食物
这也是很好办的事,只需要判断蛇头的Row和Col是否等于食物的Row和Col, 如果相等,则更新食物的位置,蛇身加一.
if((Rows[0] == Food_row) && (Cols[0] == Food_col)) // 判断是否吃到食物 { if(len<5) //判断蛇长是否达到最大值 { Rows[len] = Rows[len-1]; Cols[len] = Cols[len-1]; len++; } //随机更新食物的位置 Food_col = ~(0x01 << rand()%8); Food_row = 0x01 << rand()%8; }
自此,贪吃蛇可以说已经基本完成了,剩下的就是用键盘来控制蛇的移动方向了
六. 蛇方向的控制
方向的控制我把它放在中断里面,这样可以解决如下问题
如果不用延迟,蛇移动得太快,根本看不清楚蛇的移动。
如果用延迟来控制蛇移动的速度,那么键盘的读取就会变动不灵敏,
要按下许久之后,才能读取,游戏效果不好。
因此我决定把它放在定时器中断中,通过设定初值,以极小的时间差,来读取键盘的内容,即可达到实时的效果,而且延时对这个完全没有干扰。
void Time_0() interrupt 1 { u8 key; key = KEY(); if(key != -1) { if(key == 1) { //Up(len); direct = 0; delay(10000); // 不用也没有关系,不会造成影响 } if(key == 9) { //Down(len); direct = 1; delay(10000); } if(key == 4) { //Left(len); direct = 2; delay(10000); } if(key == 6) { //Right(len); direct = 3; delay(10000); } } //防止p过大 if(p>8*100) p = 0; p++; //每8次移动一下,这是合理的,由于每一次的时间差极小 //8次加起来的时间差也是非常小的, //该时间差小于先后按下两个不同方向的时间差,因为这是合理的 if(p%8 == 0) { switch(direct) { case 0: Up(len);break; case 1: Down(len);break; case 2: Left(len);break; case 3: Right(len);break; } } TH0 = 0x0c; TL0 = 0x0c; }
七. 最后就是main函数啦
这部分也是非常简单的。
void main() { u8 i; //u8 len = 1; // 初始化长度,这个放到全局变量中了 u8 Food_row = 0x20; u8 Food_col = 0xfb; //u8 direct = 0;// 初始化方向,这个放到全局变量中了 Rows[0] = Row; //初始化,蛇头的位置 Cols[0] = Col; Init_time_0(); //配置定时器0,开启中断 while(1) { //判断蛇头是否到达食物的位置 if((Rows[0] == Food_row) && (Cols[0] == Food_col)) { if(len<5) { Rows[len] = Rows[len-1]; Cols[len] = Cols[len-1]; len++; } //随机更新食物的位置 Food_col = ~(0x01 << rand()%8); Food_row = 0x01 << rand()%8; } Led(Food_row,Food_col); //显示食物 for(i=0;i<len;i++) Led(Rows[i],Cols[i]); // 显示蛇 //这里用延时来控制速度的话,容易产生闪烁。 } }
至此贪吃蛇就完成啦!!!!
Thank for your reading !!!
公众号:FPGA之旅