基于51单片机和16X16LED点阵屏(74HC138和74HC595驱动)的小游戏《贪吃蛇》
目录
- 系列文章目录
- 前言
- 一、效果展示
- 二、原理分析
- 三、各模块代码
- 1、定时器0
- 2、自制八位独立按键
- 3、点阵屏模块
- 四、主函数
- 总结
系列文章目录
前言
《贪吃蛇》,一款经典的、怀旧的小游戏,单片机入门必写程序。
以《贪吃蛇》为载体,熟悉各种屏幕的使用。
所用单片机:STC89C52RC
有两个版本:普中开发板矩阵按键版本和最小系统板自制独立按键版本。
本文代码对应的是最小系统板自制独立按键版本。
效果查看/操作演示:B站搜索“甘腾胜”或“gantengsheng”查看。
源代码下载:B站对应视频的简介有工程文件下载链接。
一、效果展示
(1)普中开发板矩阵按键版本
(2)最小系统板自制独立按键版本
二、原理分析
游戏原理可以参考以下这篇文章:
基于51单片机和8X8LED点阵屏(板载74HC595驱动)的普中开发板矩阵按键控制的小游戏《贪吃蛇》
这里主要说一下显示问题。
此16X16点阵屏模块采用2个74HC138和2个74HC595来驱动显示。74HC138选通某一行,送入2个字节数据到2个74HC595来控制这一行的显示。
芯片MAX7219内含自动扫描电路,驱动的点阵屏不会占用单片机的资源。
此点阵屏模块需要利用单片机的定时器来进行扫描显示,定时的时间太短,会频繁进中断函数,影响主函数的代码的执行,定时的时间太长,点阵屏又会出现闪烁的现象(总共有16行,每个LED灯接通的占空比为1/16)。所以需要找到一个平衡,既不会使点阵屏出现明显的闪烁,又不会影响主函数代码的执行。经过摸索,这个定时的时间大约是1ms多,效果不好的话可以继续调一下。
三、各模块代码
1、定时器0
h文件
#ifndef __TIMER0_H__
#define __TIMER0_H__void Timer0_Init(void);#endif
c文件
#include <REGX52.H>/*** @brief 定时器0初始化,1毫秒@12.000MHz* @param 无* @retval 无*/
void Timer0_Init(void)
{TMOD&=0xF0; //设置定时器模式(高四位不变,低四位清零)TMOD|=0x01; //设置定时器模式(通过低四位设为“定时器0工作方式1”的模式)TL0=0x18; //设置定时初值,定时1msTH0=0xFC; //设置定时初值,定时1msTF0=0; //清除TF0标志TR0=1; //定时器0开始计时ET0=1; //打开定时器0中断允许EA=1; //打开总中断PT0=0; //当PT0=0时,定时器0为低优先级,当PT0=1时,定时器0为高优先级
}/*定时器中断函数模板
void Timer0_Routine() interrupt 1 //定时器0中断函数
{static unsigned int T0Count; //定义静态变量TL0=0x18; //设置定时初值,定时1msTH0=0xFC; //设置定时初值,定时1msT0Count++;if(T0Count>=1000){T0Count=0;}
}
*/
2、自制八位独立按键
h文件
#ifndef __KEYSCAN_8_H__
#define __KEYSCAN_8_H__unsigned char Key(void);
void Key_Tick(void);#endif
c文件
#include <REGX52.H>sbit Key1=P1^0;
sbit Key2=P1^1;
sbit Key3=P1^2;
sbit Key4=P1^3;
sbit Key5=P1^4;
sbit Key6=P1^5;
sbit Key7=P1^6;
sbit Key8=P1^7;unsigned char KeyNumber;/*** @brief 获取独立按键键码* @param 无* @retval 按下按键的键码,范围:0,1~24,0表示无按键按下*/
unsigned char Key(void)
{unsigned char KeyTemp=0;KeyTemp=KeyNumber;KeyNumber=0; //主程序中获取键码值之后键码值清零,在下一次定时器扫描按键之前再次获取键码值,一定会返回0return KeyTemp;
}/*** @brief 获取当前按下按键的状态,无消抖及松手检测* @param 无* @retval 按键值,范围:0~8,无按键按下时返回值为0*/
unsigned char Key_GetState()
{unsigned char KeyValue=0;if(Key1==0){KeyValue=1;}if(Key2==0){KeyValue=2;}if(Key3==0){KeyValue=3;}if(Key4==0){KeyValue=4;}if(Key5==0){KeyValue=5;}if(Key6==0){KeyValue=6;}if(Key7==0){KeyValue=7;}if(Key8==0){KeyValue=8;}return KeyValue;
}/*** @brief 按键驱动函数,在中断中调用* @param 无* @retval 无*/
void Key_Tick(void)
{static unsigned char NowState,LastState;LastState=NowState; //按键状态更新NowState=Key_GetState(); //获取当前按键状态//如果上个时间点按键未按下,这个时间点按键按下,则是按下瞬间if(LastState==0){switch(NowState){case 1:KeyNumber=1;break;case 2:KeyNumber=2;break;case 3:KeyNumber=3;break;case 4:KeyNumber=4;break;case 5:KeyNumber=5;break;case 6:KeyNumber=6;break;case 7:KeyNumber=7;break;case 8:KeyNumber=8;break;default:break;}}//如果上个时间点按键按下,这个时间点按键按下,则是一直按住按键if(LastState && NowState){if(LastState==1 && NowState==1){KeyNumber=9;}if(LastState==2 && NowState==2){KeyNumber=10;}if(LastState==3 && NowState==3){KeyNumber=11;}if(LastState==4 && NowState==4){KeyNumber=12;}if(LastState==5 && NowState==5){KeyNumber=13;}if(LastState==6 && NowState==6){KeyNumber=14;}if(LastState==7 && NowState==7){KeyNumber=15;}if(LastState==8 && NowState==8){KeyNumber=16;}}//如果上个时间点按键按下,这个时间点按键未按下,则是松手瞬间if(NowState==0){switch(LastState){case 1:KeyNumber=17;break;case 2:KeyNumber=18;break;case 3:KeyNumber=19;break;case 4:KeyNumber=20;break;case 5:KeyNumber=21;break;case 6:KeyNumber=22;break;case 7:KeyNumber=23;break;case 8:KeyNumber=24;break;default:break;}}
}
3、点阵屏模块
h文件
#ifndef __MATRIXLED_16X16_74HC138_74HC595_H__
#define __MATRIXLED_16X16_74HC138_74HC595_H__void MatrixLED_Init(void);
void _74HC595_WriteByte(unsigned char Byte);
void _74HC595_StoreData(void);
void _74HC138_PickLine(unsigned char Line);
void MatrixLED_Tick(unsigned char Line,unsigned char State);
void MatrixLED_MoveLeft(unsigned char *Array,unsigned char Offset);
void MatrixLED_MoveUp(unsigned char *Array,unsigned char Offset);#endif
c文件
#include <REGX52.H>//引脚定义
sbit _74HC138_D=P2^7; //高位
sbit _74HC138_C=P2^6;
sbit _74HC138_B=P2^5;
sbit _74HC138_A=P2^4; //低位
sbit _74HC138_G=P2^3; //使能端,低电平有效
sbit _74HC595_DI=P2^2; //串行数据输入
sbit _74HC595_CLK=P2^1; //移位寄存器时钟输入,上升沿有效
sbit _74HC595_LAT=P2^0; //储存寄存器时钟输入,上升沿有效//取模设置要求:阳码(亮点为0),行列式取模,高位在右
unsigned char MatrixLEDBuffer[32]={ //MatrixLED缓存
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, //默认无显示
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
};/*** @brief MatrixLED初始化* @param 无* @retval 无*/
void MatrixLED_Init(void)
{_74HC595_CLK=0; //移位寄存器时钟信号初始化_74HC595_LAT=0; //储存寄存器时钟信号初始化
}/*** @brief 74HC595写入字节* @param Byte 需要写入的字节* @retval 无*/
void _74HC595_WriteByte(unsigned char Byte)
{unsigned char i;for(i=0;i<8;i++) //循环8次(8位移位寄存器){_74HC595_DI=Byte&(0x80>>i); //先高位后低位,先低位再高位也行,不过取模设置要更改,改成高位在右_74HC595_CLK=1; //CLK上升沿时,DI口的数据(电平)写入移位寄存器_74HC595_CLK=0;}
}/*** @brief 74HC595存储数据* @param 无* @retval 无*/
void _74HC595_StoreData(void)
{_74HC595_LAT=1; //LAT上升沿时,数据从移位寄存器转存储存寄存器_74HC595_LAT=0;
}/*** @brief 74HC138扫描显示MatrixLED* @param Line 要显示的行,范围:0~15(对应1~16行)* @retval 无*/
void _74HC138_PickLine(unsigned char Line)
{switch(Line){case 0:_74HC138_D=0;_74HC138_C=0;_74HC138_B=0;_74HC138_A=0;break;case 1:_74HC138_D=0;_74HC138_C=0;_74HC138_B=0;_74HC138_A=1;break;case 2:_74HC138_D=0;_74HC138_C=0;_74HC138_B=1;_74HC138_A=0;break;case 3:_74HC138_D=0;_74HC138_C=0;_74HC138_B=1;_74HC138_A=1;break;case 4:_74HC138_D=0;_74HC138_C=1;_74HC138_B=0;_74HC138_A=0;break;case 5:_74HC138_D=0;_74HC138_C=1;_74HC138_B=0;_74HC138_A=1;break;case 6:_74HC138_D=0;_74HC138_C=1;_74HC138_B=1;_74HC138_A=0;break;case 7:_74HC138_D=0;_74HC138_C=1;_74HC138_B=1;_74HC138_A=1;break;case 8:_74HC138_D=1;_74HC138_C=0;_74HC138_B=0;_74HC138_A=0;break;case 9:_74HC138_D=1;_74HC138_C=0;_74HC138_B=0;_74HC138_A=1;break;case 10:_74HC138_D=1;_74HC138_C=0;_74HC138_B=1;_74HC138_A=0;break;case 11:_74HC138_D=1;_74HC138_C=0;_74HC138_B=1;_74HC138_A=1;break;case 12:_74HC138_D=1;_74HC138_C=1;_74HC138_B=0;_74HC138_A=0;break;case 13:_74HC138_D=1;_74HC138_C=1;_74HC138_B=0;_74HC138_A=1;break;case 14:_74HC138_D=1;_74HC138_C=1;_74HC138_B=1;_74HC138_A=0;break;case 15:_74HC138_D=1;_74HC138_C=1;_74HC138_B=1;_74HC138_A=1;break;default:break;}
}/*** @brief 点阵屏驱动函数,定时器中断函数中使用* @param Line 要显示的行,范围:0~15(对应1~16行)* @param State 控制屏幕显不显示,范围:0~1,0:屏幕不显示,1:屏幕显示* @retval 无*/
void MatrixLED_Tick(unsigned char Line,unsigned char State)
{_74HC138_G=1;if(State==1){_74HC595_WriteByte(MatrixLEDBuffer[16+Line]);_74HC595_WriteByte(MatrixLEDBuffer[Line]);}else{_74HC595_WriteByte(0xFF);_74HC595_WriteByte(0xFF);}_74HC595_StoreData();_74HC138_PickLine(Line);_74HC138_G=0;
}/*** @brief 更新点阵屏的显示* @param Array 传递过来的数组的指针(地址),16*16汉字或16*16数字取模要求:阳码(亮点为0),行列式取模,高位在右* @param Offset 显示数组数据的偏移量,向左偏移Offset个像素* @retval 无*/
void MatrixLED_MoveLeft(unsigned char *Array,unsigned char Offset)
{unsigned char i,m,n;m=Offset/8;n=Offset%8;Array+=m*16;for(i=0;i<32;i++){MatrixLEDBuffer[i]=(*Array>>n)|(*(Array+16)<<(8-n)); //将偏移后的数据保存到点阵屏缓存中(高位在右)Array++;}
}/*** @brief 更新点阵屏的显示* @param Array 传递过来的数组的指针(地址),16*16汉字或16*16数字取模要求:阳码(亮点为0),行列式取模,高位在右* @param Offset 显示数组数据的偏移量,向上偏移Offset个像素* @retval 无*/
void MatrixLED_MoveUp(unsigned char *Array,unsigned char Offset)
{unsigned char i,m,n;m=Offset/16;n=Offset%16;Array+=m*32;Array+=n;for(i=0;i<16;i++){if(i<16-n){MatrixLEDBuffer[i]=*(Array);MatrixLEDBuffer[i+16]=*(Array+16);}else{MatrixLEDBuffer[i]=*(Array+16);MatrixLEDBuffer[i+16]=*(Array+32);}Array++;}
}
四、主函数
main.c
/*
by甘腾胜@20241219
效果查看/操作演示:可以在B站搜索“甘腾胜”或“gantengsheng”查看
单片机:STC89C52RC
晶振:12T@12.0000MHz
外设:自制独立按键、16X16LED点阵屏(74HC138和74HC595驱动)
原理分析:https://blog.csdn.net/gantengsheng/article/details/143581157
注意:经过测试,接P0口会导致显示不正常,原因可能是P0口的上拉电阻太大,导致上拉的速度太慢,所以接了P2口操作说明:(1)自制独立按键版本K7 K2 上:K7下:K6K8 K5 K4 K1 左:K8右:K5K6 K3 开始/暂停/继续:K1返回:K2(2)普中开发板矩阵按键版本S1 S2 S3 S4 上:S10 下:S14 S5 S6 S7 S8 左:S13右:S15S9 S10 S11 S12 开始/暂停/继续:S16返回:S12S13 S14 S15 S16 */#include <REGX52.H> //包含寄存器的定义
#include <STDLIB.H> //包含随机函数的声明
#include "Timer0.h" //包含工程目录下的头文件,相当于把头文件内容插入此处
#include "KeyScan_8.h"
#include "MatrixLED_16X16_74HC138_74HC595.h"unsigned char KeyNum; //存储获得的键码值
unsigned char Mode; //游戏模式,0:显示游戏名称“《贪吃蛇》”,1:显示汉字“难度选择”,//2:难度选择界面(数字范围是1~5),3:游戏进行模式,4:游戏结束全屏闪烁,//5:显示汉字“得分”,6:循环滚动显示得分,7:显示作者姓名和编程日期
unsigned char MoveSnakeFlag; //移动蛇身的标志,1:移动,0:不移动
unsigned char NowDirection=1; //蛇头移动的方向,1:向右,2:向上,3:向左,4:向下,游戏开始时默认向右移动
unsigned char LastDirection=1; //蛇头上一次移动的方向,1:向右,2:向上,3:向左,4:向下,游戏开始时默认向右移动(此处可以不赋初值)
unsigned char Length=2; //蛇的长度,初始值为2(此处可以不赋初值,因为进入游戏进行模式时还会赋值一次)
unsigned char Head=1; //保存整条蛇的数据的数组(共256个数据,数据索引为:0~255)中,蛇头对应的数据的索引,蛇的初始长度为2,//开始时只用了两个数据(数组的第1个数据和第2个数据),蛇头对应的是第2个数据(索引为1),Head的范围:0~255
unsigned char GameOverFlag; //游戏结束的标志,1:游戏结束,0:游戏未结束
unsigned char FlashFlag; //闪烁的标志,1:不显示,0:显示
unsigned int Food; //保存创造出来的食物的位置,高四位(范围:0~15)对应列(1~16列),低四位(范围:0~15)对应行(1~16行)//从左往右数,分别是1~16列,从上往下数,分别是1~16行
unsigned int Offset1; //偏移量,用来控制汉字或数字向左滚动显示(切换模式后清零)
unsigned int Offset2; //偏移量,用来控制难度对应的数字上下滚动显示(切换模式后不清零)
unsigned char RollFlag; //滚动的标志,1:滚动,0:不滚动
unsigned char RollUpFlag; //难度选择界面,数字向上滚动的标志,1:滚动,0:不滚动
unsigned char RollDownFlag; //难度选择界面,数字向下滚动的标志,1:滚动,0:不滚动
unsigned char RollCount; //上下滚动的计次
unsigned char ExecuteOnceFlag; //各模式中只执行一次的标志,1:执行,0:不执行
unsigned int SnakeMoveSpeed=1000; //蛇移动的速度,值越小,速度越快,上电默认1.00s移动一次,定时器定时1ms
unsigned int T0Count0,T0Count1,T0Count2,T0Count3,T0Count4; //定时器计数的变量
unsigned char PauseFlag; //暂停的标志,1:暂停,0:不暂停unsigned char pdata SnakeBody[256]; //点阵屏是16X16=256像素,需要用256个数据记录蛇身的数据//用pdata修饰是因为片内RAM不够用,所以保存到片外RAM
unsigned char DisplayBuffer[32]; //显示缓存,一个字节对应8个点,总共256个点,所以需要32个字节//阳码(亮点为0),行列式取模,高位在右//取模设置:阳码(亮点为0),行列式取模,高位在右(所有取模都必须按这个格式,否则需要修改函数)
unsigned char code Table1[]={ // “《贪吃蛇》”
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,/*" ", */
0xFF,0xFF,0xFF,0xFF,0x7F,0xBF,0xDF,0x6F,0xDF,0xBF,0x7F,0xFF,0xFF,0xFF,0xFF,0xFF,
0xF7,0xDB,0xED,0xF6,0xFB,0xFD,0xFE,0xFF,0xFE,0xFD,0xFB,0xF6,0xED,0xDB,0xF7,0xFF,/*"《",0*/
0x7F,0xBF,0xDF,0x6F,0xF3,0x0C,0xFF,0xFF,0x07,0xF7,0x77,0x77,0x77,0xBF,0xCF,0xF1,
0xFF,0xFE,0xFD,0xFB,0xE6,0x98,0xFD,0xFE,0xF0,0xF7,0xF7,0xF7,0xF7,0xF9,0xE7,0xDF,/*"贪",1*/
0xFF,0xFF,0x61,0x6D,0xAD,0xCD,0x6D,0xED,0xED,0xED,0xE1,0x6D,0xBF,0xBF,0x7F,0xFF,
0xFE,0xFE,0xFF,0x80,0xFF,0xFF,0xE0,0xEF,0xF7,0xF9,0xFE,0xFF,0xBF,0xBF,0x80,0xFF,/*"吃",2*/
0xF7,0xF7,0xF7,0x41,0x55,0x95,0xD5,0xD5,0xC1,0xF5,0xF7,0xD7,0x87,0xB8,0xFD,0xFF,
0xFB,0xF7,0xF7,0x80,0xBF,0xDF,0xFE,0xEE,0xF6,0xFA,0xFC,0xBE,0xBE,0xBE,0x81,0xFF,/*"蛇",3*/
0xEF,0xDB,0xB7,0x6F,0xDF,0xBF,0x7F,0xFF,0x7F,0xBF,0xDF,0x6F,0xB7,0xDB,0xEF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFE,0xFD,0xFB,0xF6,0xFB,0xFD,0xFE,0xFF,0xFF,0xFF,0xFF,0xFF,/*"》",4*/
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,/*" ", */
};unsigned char code Table2[]={ // “ 难度选择 1”
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,/*" ", */
0xFF,0xFF,0xC0,0x5F,0x5F,0x2D,0x4B,0x6B,0x77,0x77,0x6B,0x5B,0x5D,0x7E,0x7F,0x7F,
0xFA,0xF6,0xFE,0x80,0xF7,0xF7,0xC0,0xF7,0xF7,0xC0,0xF7,0xF7,0xF7,0x80,0xFF,0xFF,/*"难",0*/
0x7F,0xFF,0x03,0xBB,0xBB,0x03,0xBB,0xBB,0x3B,0xFB,0x0B,0xDB,0xBD,0x7D,0x9E,0xE3,
0xFF,0xFE,0x80,0xFB,0xFB,0xC0,0xFB,0xFB,0xF8,0xFF,0xF0,0xF7,0xFB,0xFC,0xF3,0x8F,/*"度",1*/
0xFF,0xBB,0xB7,0x37,0xDF,0xFF,0x10,0x77,0x77,0x77,0xB7,0xB7,0xD7,0xEB,0x1D,0xFF,
0xFD,0xFD,0xFD,0xE0,0xFD,0xFD,0xC0,0xFB,0xFB,0xFB,0xDB,0xDB,0xC7,0xFF,0x80,0xFF,/*"选",2*/
0xFB,0x1B,0xBB,0x7B,0xF0,0xFB,0x7B,0x9B,0xF3,0x38,0xFB,0xFB,0x1B,0xFB,0xFA,0xFD,
0xFF,0xE0,0xEF,0xF7,0xFA,0xFD,0xF2,0x8D,0xFD,0xE0,0xFD,0xFD,0xC0,0xFD,0xFD,0xFD,/*"择",3*/
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,/*" ", */
0xFF,0xFF,0xFF,0xFF,0x1F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x0F,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xE0,0xFF,0xFF,/*"1",0*/
};unsigned char code Table3[]={ //难度对应的数字:“1~5”,最后两行数据与前两行数据相同,是为了做成循环显示的效果0xFF,0xFF,0xFF,0xFF,0x1F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x0F,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xE0,0xFF,0xFF,/*"1",0*/
0xFF,0xFF,0xFF,0x0F,0xF3,0xE3,0xF7,0xFF,0xFF,0x7F,0x9F,0xEF,0xF3,0x03,0xFF,0xFF,
0xFF,0xFF,0xFF,0xF0,0xE7,0xCF,0xE7,0xE7,0xF9,0xFE,0xFF,0xDF,0xCF,0xE0,0xFF,0xFF,/*"2",1*/
0xFF,0xFF,0xFF,0x0F,0xF3,0xE3,0xFF,0xFF,0x7F,0xFF,0xFF,0xE3,0xF3,0x0F,0xFF,0xFF,
0xFF,0xFF,0xFF,0xF8,0xE7,0xE7,0xE7,0xF9,0xF0,0xE7,0xCF,0xCF,0xE7,0xF8,0xFF,0xFF,/*"3",2*/
0xFF,0xFF,0xFF,0xFF,0xFF,0x7F,0xBF,0xCF,0xF7,0xFB,0x01,0xFF,0xFF,0x3F,0xFF,0xFF,
0xFF,0xFF,0xFF,0xF3,0xF0,0xF1,0xF1,0xF1,0xF1,0xF1,0x80,0xF1,0xF1,0x80,0xFF,0xFF,/*"4",3*/
0xFF,0xFF,0xFF,0x07,0xF7,0xF7,0xF7,0x17,0xE7,0xFF,0xFF,0xE3,0xF3,0x0F,0xFF,0xFF,
0xFF,0xFF,0xFF,0xE0,0xFF,0xFF,0xFF,0xF0,0xE7,0xCF,0xCF,0xCF,0xE7,0xF8,0xFF,0xFF,/*"5",4*/
0xFF,0xFF,0xFF,0xFF,0x1F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x0F,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xE0,0xFF,0xFF,/*"1",0*/
};unsigned char code Table5[]={ // “得分”
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,/*" ", */
0xEF,0x2F,0xB7,0x3B,0xAD,0x2F,0xF7,0x33,0xF5,0x16,0xF7,0xB7,0x77,0xF7,0xF7,0xF7,
0xFF,0xE0,0xEF,0xE0,0xEF,0xE0,0xFF,0xC0,0xF7,0x80,0xF7,0xF7,0xF7,0xF7,0xF5,0xFB,/*"得",0*/
0xFF,0xDF,0xDF,0xEF,0xF7,0xFB,0xFD,0x06,0xDF,0xDF,0xDF,0xEF,0xEF,0xF7,0x7B,0xFD,
0xFD,0xFD,0xFB,0xFB,0xF7,0xEF,0xDF,0xB8,0xFB,0xFB,0xFB,0xFB,0xFB,0xFB,0xFD,0xFE,/*"分",1*/
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,/*" ", */
};unsigned char code Table6[]={ //分数字模:0~9,大小:宽8*高16
0xFF,0xFF,0xFF,0xE7,0xDB,0xBD,0xBD,0xBD,0xBD,0xBD,0xBD,0xBD,0xDB,0xE7,0xFF,0xFF,/*"0",0*/
0xFF,0xFF,0xFF,0xEF,0xE3,0xEF,0xEF,0xEF,0xEF,0xEF,0xEF,0xEF,0xEF,0x83,0xFF,0xFF,/*"1",1*/
0xFF,0xFF,0xFF,0xC3,0xBD,0xBD,0xBD,0xBF,0xDF,0xEF,0xF7,0xFB,0xBD,0x81,0xFF,0xFF,/*"2",2*/
0xFF,0xFF,0xFF,0xC3,0xBD,0xBD,0xBF,0xDF,0xE7,0xDF,0xBF,0xBD,0xBD,0xC3,0xFF,0xFF,/*"3",3*/
0xFF,0xFF,0xFF,0xDF,0xCF,0xCF,0xD7,0xDB,0xDB,0xDD,0x01,0xDF,0xDF,0x07,0xFF,0xFF,/*"4",4*/
0xFF,0xFF,0xFF,0x81,0xFD,0xFD,0xFD,0xE1,0xDD,0xBF,0xBF,0xBD,0xDD,0xE3,0xFF,0xFF,/*"5",5*/
0xFF,0xFF,0xFF,0xE7,0xDB,0xFD,0xFD,0xC5,0xB9,0xBD,0xBD,0xBD,0xBB,0xC7,0xFF,0xFF,/*"6",6*/
0xFF,0xFF,0xFF,0x81,0xBD,0xDF,0xDF,0xEF,0xEF,0xF7,0xF7,0xF7,0xF7,0xF7,0xFF,0xFF,/*"7",7*/
0xFF,0xFF,0xFF,0xC3,0xBD,0xBD,0xBD,0xDB,0xE7,0xDB,0xBD,0xBD,0xBD,0xC3,0xFF,0xFF,/*"8",8*/
0xFF,0xFF,0xFF,0xE3,0xDD,0xBD,0xBD,0xBD,0x9D,0xA3,0xBF,0xBF,0xDB,0xE7,0xFF,0xFF,/*"9",9*/
};unsigned char code Table7[]={ // “ by甘腾胜at20241202 ”
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,/*" ", */
0xFF,0xFF,0xFF,0xFF,0xFC,0xFD,0xFD,0xE5,0xD9,0xBD,0xBD,0xBD,0xD9,0xE5,0xFF,0xFF,/*"b",0*/
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x18,0xBD,0xDB,0xDB,0xE7,0xE7,0xF7,0xF7,0xF9,/*"y",1*/
0xEF,0xEF,0xEF,0xEF,0x01,0xEF,0xEF,0xEF,0xEF,0x0F,0xEF,0xEF,0xEF,0xEF,0x0F,0xEF,
0xF7,0xF7,0xF7,0xF7,0x80,0xF7,0xF7,0xF7,0xF7,0xF0,0xF7,0xF7,0xF7,0xF7,0xF0,0xF7,/*"甘",2*/
0xBF,0x61,0x6D,0x0D,0xED,0x01,0x6D,0xAD,0x4D,0xE1,0x6D,0x6D,0xED,0x2D,0xED,0xE6,
0xED,0xED,0xF5,0xC0,0xFD,0x80,0xF7,0xEF,0x90,0xF7,0xF7,0xC0,0xDF,0xD8,0xD7,0xEF,/*"腾",3*/
0xFF,0xE1,0x6D,0x6D,0x6D,0x61,0xAD,0xED,0xED,0x61,0xED,0xED,0xED,0xED,0x2D,0xE6,
0xFB,0xFB,0xFB,0xFB,0xC0,0xFB,0xFB,0xFB,0xFB,0xC0,0xFB,0xFB,0xFB,0xFB,0x80,0xFF,/*"胜",4*/
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xE3,0xDD,0xCF,0xD3,0xDD,0xCD,0x93,0xFF,0xFF,/*"a",5*/
0xFF,0xFF,0xFF,0xFF,0xFF,0xF7,0xF7,0xC1,0xF7,0xF7,0xF7,0xF7,0xB7,0xCF,0xFF,0xFF,/*"t",6*/
0xFF,0xFF,0xFF,0xC3,0xBD,0xBD,0xBD,0xBF,0xDF,0xEF,0xF7,0xFB,0xBD,0x81,0xFF,0xFF,/*"2",7*/
0xFF,0xFF,0xFF,0xE7,0xDB,0xBD,0xBD,0xBD,0xBD,0xBD,0xBD,0xBD,0xDB,0xE7,0xFF,0xFF,/*"0",8*/
0xFF,0xFF,0xFF,0xC3,0xBD,0xBD,0xBD,0xBF,0xDF,0xEF,0xF7,0xFB,0xBD,0x81,0xFF,0xFF,/*"2",9*/
0xFF,0xFF,0xFF,0xDF,0xCF,0xCF,0xD7,0xDB,0xDB,0xDD,0x01,0xDF,0xDF,0x07,0xFF,0xFF,/*"4",10*/
0xFF,0xFF,0xFF,0xEF,0xE3,0xEF,0xEF,0xEF,0xEF,0xEF,0xEF,0xEF,0xEF,0x83,0xFF,0xFF,/*"1",11*/
0xFF,0xFF,0xFF,0xC3,0xBD,0xBD,0xBD,0xBF,0xDF,0xEF,0xF7,0xFB,0xBD,0x81,0xFF,0xFF,/*"2",12*/
0xFF,0xFF,0xFF,0xEF,0xE3,0xEF,0xEF,0xEF,0xEF,0xEF,0xEF,0xEF,0xEF,0x83,0xFF,0xFF,/*"1",13*/
0xFF,0xFF,0xFF,0xE3,0xDD,0xBD,0xBD,0xBD,0x9D,0xA3,0xBF,0xBF,0xDB,0xE7,0xFF,0xFF,/*"9",14*/
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,/*" ", */
};/*** @brief 创造出随机位置的食物,数据的高四位(范围:0~15)代表食物所在的列(1~16),数据的低四位(范围:0~15)代表食物所在的行(1~16)* @brief 从左往右数,分别是1~16列,从上往下数,分别是1~16行* @param 无* @retval 创造出的食物位置的数据*/
unsigned char CreateFood(void)
{unsigned char FoodTemp;unsigned char i,j,m,n;m=rand()%16; //产生一个0~15的随机数n=rand()%16; //产生一个0~15的随机数for(j=0;j<16;j++) //产生一个随机位置,判断该位置是否是蛇身,如果不是,就返回该位置所对应的数据{ //如果该位置是蛇身的位置,则从该点向周围寻找不是蛇身的空位置for(i=0;i<16;i++){if( (DisplayBuffer[(n+j)%16+(m+i)%16/8*16] & (0x01<<(m+i)%8)) ){FoodTemp=(m+i)%16*16+(n+j)%16;break; //找到了空位置就退出循环}}}return FoodTemp;
}/*** @brief 更新显示缓存中的数据,例如:想要点亮第一行第十列的点,就需要让数组DisplayBuffer的* @brief 第17个数据(索引为16)的B1位(一个字节从最低位到最高位分别为B0~B7)变为0(阳码,亮点为0)* @brief 注意,更新了数组DisplayBuffer的数据后,还需要配合滚屏函数MatrixLED_MoveLeft更新屏幕的显示* @param Position,要更新的位置,范围:0~255,高四位(0~15)对应1~16列(从左往右数,分别是1~16列),* @param 低四位(0~15)对应1~16行(从上往下数,分别是1~16行)* @param State,要更新成的状态,范围:0~1,1:点亮,0:熄灭* @retval 无*/
void ConvertData(unsigned char Position,unsigned char State)
{if(State==1){DisplayBuffer[Position%16+Position/16/8*16] &= ~(0x01<<(Position/16%8));}else{DisplayBuffer[Position%16+Position/16/8*16] |= 0x01<<(Position/16%8);}
}/*** @brief 控制蛇的移动* @param 无* @retval 无*/
void MoveSnake(void)
{if(NowDirection==1) //如果向右移动{//移动前判断一下移动后是否撞墙,如果是,则游戏结束,游戏结束的标志置1if((SnakeBody[Head]/16)==15){GameOverFlag=1;}//SnakeBody数组中蛇头的下一个数据等于上一个数据加16(即高四位加1),即蛇头移动到了右边这一列else{SnakeBody[(Head+1)%256]=SnakeBody[Head]+16;}}if(NowDirection==2) //如果向上移动{if((SnakeBody[Head]%16)==0){GameOverFlag=1;}//SnakeBody数组中蛇头的下一个数据等于上一个数据减1(即低四位减1),即蛇头移动到了上边这一行else{SnakeBody[(Head+1)%256]=SnakeBody[Head]-1;}}if(NowDirection==3) //如果向左移动{if((SnakeBody[Head]/16)==0){GameOverFlag=1;}//SnakeBody数组中蛇头的下一个数据等于上一个数据减16(即高四位减1),即蛇头移动到了左边这一列else{SnakeBody[(Head+1)%256]=SnakeBody[Head]-16;}}if(NowDirection==4) //如果向下移动{if((SnakeBody[Head]%16)==15){GameOverFlag=1;}//SnakeBody数组中蛇头的下一个数据等于上一个数据加1(即低四位加1),即蛇头移动到了下边这一行else{SnakeBody[(Head+1)%256]=SnakeBody[Head]+1;}}if(GameOverFlag==0) //如果没撞墙{if(SnakeBody[(Head+1)%256]==Food) //判断蛇头移动后的位置是否是食物所在的位置{ //如果是Length++; //蛇身长度加1ConvertData(Food,1); //防止食物闪烁刚好不显示的时候进入此函数,导致蛇身一个点不显示Food=CreateFood(); //重新创造一个食物ConvertData(Food,1); //先更新显示缓存,在主循环中再更新屏幕的显示FlashFlag=0; //创造出新的食物时,食物暂不闪烁T0Count2=0; //定时器T0Count2重新计数}else if( !( (DisplayBuffer[SnakeBody[(Head+1)%256]%16+SnakeBody[(Head+1)%256]/16/8*16]) & (0x01<<SnakeBody[(Head+1)%256]/16%8) ) ){ //如果蛇头移动后的位置撞在蛇身上,则游戏结束GameOverFlag=1; //游戏结束的标志置1}else //如果蛇头移动后的位置不是食物,也不是撞墙,也不是撞到蛇身的话{//显示缓存数组DisplayBuffer中蛇头前进后蛇头的新位置对应的位更新数值ConvertData(SnakeBody[(Head+1)%256],1);//显示缓存数组DisplayBuffer中蛇尾的位置对应的位清0(蛇身移动,如果没有吃到食物,相当于蛇尾对应的点跑到了//蛇头的前一个点,变成了蛇头,原来的蛇头变成蛇身)整条蛇中间的数据不用操作ConvertData(SnakeBody[(Head+256-Length+1)%256],0);//数组SnakeBody中,蛇尾的数据清零,,//Head+256:+256是因为Head为255后再加1,就会变成了0,防止Head+256-Length+1为负数//循环使用SnakeBody数组中的256个数据,蛇头对应数组SnakeBody的第255个数据后,再移动一次,蛇头就来到了数组的第0个数据
// SnakeBody[(Head+256-Length+1)%256]=0; //其实可以不用清零}}Head++; //SnakeBody数组中,蛇头对应的数据的索引加1Head%=256; //蛇头变量Head是无符号字符型数据(范围是0~255,值为255后自增会自动变为0),所以这一行可以省略//如果Head是整型(int),则需要有这一行
}void main()
{unsigned int i;unsigned char Count=0; Timer0_Init(); //定时器初始化MatrixLED_Init(); //点阵屏初始化while(1){KeyNum=Key(); //获取键码值if(KeyNum) //如果有按键按下{srand(TL0); //以定时器0的低八位数据作为随机数的种子,用来产生真随机的数据if(Mode==7 && KeyNum==18) //如果是显示作者姓名和编程日期的界面,且按下了返回键K2(松手瞬间){Mode=2; //返回难度选择界面ExecuteOnceFlag=1; //各模式只执行一次代码的标志置1}if(Mode==6) //如果是循环滚动显示得分的界面{if(KeyNum==18) //如果按下了返回键K2(松手瞬间){Mode=2; //返回难度选择界面ExecuteOnceFlag=1; //各模式只执行一次代码的标志置1}else if(KeyNum==19) //如果按下了K3(松手瞬间){Mode=7; //切换到显示作者姓名和编程日期的界面Offset1=0; //滚动显示的偏移量清0}}if(Mode==5 && KeyNum==17) //如果是显示汉字“得分”的界面,且按下开始键K1(松手瞬间){Mode=6; //跳过显示汉字,切换到循环滚动显示得分的界面ExecuteOnceFlag=1;Offset1=0; //滚动显示的偏移量清0}if(Mode==4 && KeyNum==17) //如果游戏结束闪烁时按下开始键K1(松手瞬间){Mode=5; //切换到显示汉字“得分”的界面ExecuteOnceFlag=1;Offset1=0;}if(Mode==3) //如果是游戏进行模式{if(KeyNum==17) //按下K1暂停或继续(松手瞬间){PauseFlag=!PauseFlag;}if(PauseFlag==0) //如果不是暂停{ //按下瞬间、长按、松手瞬间都进行检测,这样控制方向更有效,防止按键没检测出来导致没能改变方向if((KeyNum==8 || KeyNum==16 || KeyNum==24) && LastDirection!=1){ //如果按了“左”键,且蛇头原来的移动方向不是向右NowDirection=3; //则方向蛇头方向改为向左}if((KeyNum==7 || KeyNum==15 || KeyNum==23) && LastDirection!=4){ //如果按了“上”键,且蛇头原来的移动方向不是向下NowDirection=2; //则方向蛇头方向改为向上}if((KeyNum==6 || KeyNum==14 || KeyNum==22) && LastDirection!=2){ //如果按了“下”键,且蛇头原来的移动方向不是向上NowDirection=4; //则方向蛇头方向改为向左}if((KeyNum==5 || KeyNum==13 || KeyNum==21) && LastDirection!=3){ //如果按了“右”键,且蛇头原来的移动方向不是向左NowDirection=1; //则方向蛇头方向改为向左}}}if(Mode==2) //如果是难度选择界面{if(KeyNum==23) //如果按了“上”键(松手瞬间){RollUpFlag=1; //数字向上滚动的标志置1}if(KeyNum==22) //如果按了“下”键(松手瞬间){RollDownFlag=1; //数字向下滚动的标志置1}if(KeyNum==17) //如果按了开始键(松手瞬间),则开始游戏{Mode=3; //切换到游戏模式ExecuteOnceFlag=1;}}if(KeyNum<=24 && KeyNum>=17) //如果按下任意按键(松手瞬间){//两个if的顺序不能调换,如果调换了,就从模式0直接跳到模式2了if(Mode==1){Mode=2;Offset1=0;ExecuteOnceFlag=1;} //跳过汉字“难度选择”的显示,切换到难度选择界面if(Mode==0){Mode=1;Offset1=0;ExecuteOnceFlag=1;} //跳过游戏名“《贪吃蛇》”的显示,切换到汉字“难度选择”的显示界面}}if(Mode==0) //如果是显示游戏名称“《贪吃蛇》”的模式{if(RollFlag) //如果滚动的标志RollFlag为1(定时器中每隔一段时间将此标志置1){RollFlag=0; //滚动的标志RollFlag清零MatrixLED_MoveLeft(Table1,Offset1); //向左滚动Offset1++; //每次向左移动一个像素Offset1%=96; //越界清零,循环滚动}}if(Mode==1) //如果是显示汉字“难度选择”的模式{if(RollFlag && Offset1<=96) //只向左滚动显示一次,不循环滚动显示{RollFlag=0;MatrixLED_MoveLeft(Table2,Offset1);Offset1++;}else if(Offset1>96) //显示数字“1”之后,自动切换到难度选择模式{Mode=2;Offset1=0;}}if(Mode==2) //如果是难度选择模式{if(ExecuteOnceFlag) //切换到该模式后,此if中的内容只执行1次{ExecuteOnceFlag=0;MatrixLED_MoveUp(Table3,Offset2); //显示难度对应的数字,范围:1~5}if(RollFlag && RollUpFlag) //如果滚动标志为1,且向上滚动的标志也为1{RollFlag=0; //定时器中每个50ms将RollFlag标志置1Offset2++; //向上移动一个像素Offset2%=80; //越界清零,总共5个数字,每个数字的高度是16,所以是5*16=80MatrixLED_MoveUp(Table3,Offset2); //更新显示RollCount++;if(RollCount==16) //移动了16个像素后停止移动{RollCount=0;RollUpFlag=0;Offset2=(Offset2/16)*16; //防止移动到一半的时候按下“上”或“下”按键导致数字没有在点阵屏中间//Offset2的值必须是16的整数倍switch(Offset2/16){case 0:SnakeMoveSpeed=1000;break; //难度1,1.00s移动1次case 1:SnakeMoveSpeed=750;break; //难度2,0.75s移动1次case 2:SnakeMoveSpeed=500;break; //难度3,0.50s移动1次case 3:SnakeMoveSpeed=250;break; //难度4,0.25s移动1次case 4:SnakeMoveSpeed=120;break; //难度5,0.12s移动1次default:break;}}}if(RollFlag && RollDownFlag) //如果滚动标志为1,且向下滚动的标志也为1{RollFlag=0;if(Offset2==0){Offset2=80;}Offset2--;MatrixLED_MoveUp(Table3,Offset2);RollCount++;if(RollCount==16){RollCount=0;RollDownFlag=0;Offset2=(Offset2/16)*16;switch(Offset2/16){case 0:SnakeMoveSpeed=1000;break; //难度1,1.00s移动1次case 1:SnakeMoveSpeed=750;break; //难度2,0.75s移动1次case 2:SnakeMoveSpeed=500;break; //难度3,0.50s移动1次case 3:SnakeMoveSpeed=250;break; //难度4,0.25s移动1次case 4:SnakeMoveSpeed=120;break; //难度5,0.12s移动1次default:break;}}}}if(Mode==3) //如果是游戏进行模式{if(ExecuteOnceFlag) //切换到该模式后,此if中的内容只执行1次{ //开始游戏后,所有数据初始化ExecuteOnceFlag=0;GameOverFlag=0; //游戏结束标志清零PauseFlag=0; //游戏暂停标志清零NowDirection=1; //蛇头默认向右移动LastDirection=1; //上一次蛇头默认向右移动Length=2; //蛇的初始长度为2Head=1; //蛇头对应数组中的第2个数据(索引为1)
// for(i=0;i<256;i++) //蛇身数据全部清零(其实可以不用清零)
// {
// SnakeBody[i]=0;
// }//写入蛇身初始的两个数据SnakeBody[0]=1*16+1; //蛇尾位置:第二行第二列SnakeBody[1]=2*16+1; //蛇头位置:第二行第三列for(i=0;i<32;i++) //显示缓存数据全部清空(阳码:亮点为0,所以所有字节的每一位置1){DisplayBuffer[i]=0xFF;}DisplayBuffer[1]=0xF9; //显示缓存数据全部清空后,写入蛇身初始的两个数据(阳码:亮点为0)Food=CreateFood(); //进入游戏前,先创造出一个食物ConvertData(Food,1); //更新显示缓存MatrixLED_MoveLeft(DisplayBuffer,0); //屏幕显示初始的蛇身及食物MoveSnakeFlag=0; //蛇移动的标志清零T0Count1=0; //定时器计数变量T0Count1清零,重新计数}if(PauseFlag) //如果暂停了{ConvertData(Food,1); //食物不闪烁,一直显示MatrixLED_MoveLeft(DisplayBuffer,0); //更新显示} else if(FlashFlag) //如果不暂停,且闪烁标志为1{ConvertData(Food,0); //不显示食物MatrixLED_MoveLeft(DisplayBuffer,0);}else //如果不暂停,且闪烁标志为0{ConvertData(Food,1); //显示食物MatrixLED_MoveLeft(DisplayBuffer,0);}if(MoveSnakeFlag && GameOverFlag==0 && PauseFlag==0){ //如果移动的标志为1,且不暂停,且游戏也没结束LastDirection=NowDirection; //保存上一次移动的方向,用于按键的判断(蛇不能往后移动)MoveSnakeFlag=0; //移动标志清零MoveSnake(); //移动一次MatrixLED_MoveLeft(DisplayBuffer,0); //MoveSnake函数中更改显示缓存的数据,这里用移屏函数更新屏幕显示}if(GameOverFlag==1) //如果游戏结束{ConvertData(Food,1);MatrixLED_MoveLeft(DisplayBuffer,0);Mode=4; //切换到全屏闪烁模式ExecuteOnceFlag=1;}}if(Mode==4) //如果是游戏结束全屏闪烁模式{//在中断中处理}if(Mode==5) //显示汉字“得分”{if(RollFlag && Offset1<=48) //只显示一次汉字“得分”{RollFlag=0;Count++;Count%=10;if(Count==0){MatrixLED_MoveLeft(Table5,Offset1);Offset1+=16;}}else if(Offset1>48) //显示结束后,自动切换到循环显示得分的模式{Mode=6;Offset1=0; //偏移量清零ExecuteOnceFlag=1;} }if(Mode==6) //如果是滚动显示得分模式{if(ExecuteOnceFlag){ExecuteOnceFlag=0;for(i=0;i<112;i++) //蛇身数据部分清零(SnakeBody前160个用来循环显示三位数得分){SnakeBody[i]=0xFF;}//将得分(即蛇身的长度)的百位、十位、个位的字模写入数组SnakeBody中for(i=0;i<16;i++){SnakeBody[32+i]=Table6[Length/100*16+i];}for(i=0;i<16;i++){SnakeBody[48+i]=Table6[Length/10%10*16+i];}for(i=0;i<16;i++){SnakeBody[64+i]=Table6[Length%10*16+i];}PauseFlag=0;}if(RollFlag) //如果滚动的标志为1{RollFlag=0;MatrixLED_MoveLeft(SnakeBody,Offset1);Offset1++;Offset1%=40; //循环滚动}}if(Mode==7) //如果是显示作者姓名和编程时间的模式{if(RollFlag) //如果滚动的标志为1{RollFlag=0;MatrixLED_MoveLeft(Table7,Offset1);Offset1++;Offset1%=160; //循环滚动}}}
}void Timer0_Routine() interrupt 1 //定时器0中断函数
{static unsigned char Line;TL0=0x18; //设置定时初值,定时1ms,晶振@12.0000MHzTH0=0xFC; //设置定时初值,定时1ms,晶振@12.0000MHzT0Count3++;T0Count4++;if(T0Count0>=20) //20ms扫描一次按键{T0Count0=0;Key_Tick();}T0Count0++;if(PauseFlag==0) //不暂停时,T0Count1和T0Count2才计数{T0Count1++;T0Count2++;}if(T0Count1>=SnakeMoveSpeed) //用来控制蛇移动的速度{T0Count1=0;MoveSnakeFlag=1;}if(T0Count2>=250) //0.25s取反闪烁标志FlashFlag的值{T0Count2=0;FlashFlag=!FlashFlag;}if(T0Count3>=50) //控制滚动的速度,50ms滚动一次{T0Count3=0;RollFlag=1;}if(T0Count4>=1){T0Count4=0;if(Mode==4 && FlashFlag){MatrixLED_Tick(Line,0);} //游戏结束,全屏闪烁else{MatrixLED_Tick(Line,1);} Line++;if(Line>15){Line=0;} //总共要扫描16行,Line的范围是:0~15}
}
总结
这个点阵屏模块比较占用单片机的资源,如果用速度较快的单片机则影响不大,如果是用89系列的单片机会感觉影响比较大,建议用MAX7219驱动的点阵屏,很好用。
相关文章:

基于51单片机和16X16LED点阵屏(74HC138和74HC595驱动)的小游戏《贪吃蛇》
目录 系列文章目录前言一、效果展示二、原理分析三、各模块代码1、定时器02、自制八位独立按键3、点阵屏模块 四、主函数总结 系列文章目录 前言 《贪吃蛇》,一款经典的、怀旧的小游戏,单片机入门必写程序。 以《贪吃蛇》为载体,熟悉各种屏…...

python中常用的内置函数介绍
python中常用的内置函数介绍 1. print()2. len()3. type()4. str(), int(), float()5. list(), tuple(), set(), dict()6. range()7. sum()8. max(), min()9. sorted()10. zip()11. enumerate()12. map()13. filter()14. any(), all()15. abs()16. pow()17. round()18. ord(), …...

【微服务】Spring Cloud Config解决的问题和案例
文章目录 强烈推荐引言解决问题1. 配置管理的集中化2. 配置的版本控制3. 环境特定配置4. 配置的动态刷新5. 安全管理敏感数据6. 配置的一致性 组件1. **配置服务器(Config Server)**2. **配置客户端(Config Client)** 配置示例配置…...

华为OD机试E卷 --最小的调整次数--24年OD统一考试(Java JS Python C C++)
文章目录 题目描述输入描述输出描述用例题目解析JS算法源码Java算法源码python算法源码c算法源码c++算法源码题目描述 有一个特异性的双端队列一,该队列可以从头部或尾部添加数据,但是只能从头部移出数据。 小A依次执行2n个指令往队列中添加数据和移出数据。其中n个指令是添…...

Oracle Dataguard(主库为 Oracle 11g 单节点)配置详解(2):配置主数据库
Oracle Dataguard(主库为 Oracle 11g 单节点)配置详解(2):配置主数据库 目录 Oracle Dataguard(主库为 Oracle 11g 单节点)配置详解(2):配置主数据库一、配置…...

慧集通iPaaS集成平台低代码训练-实践篇
练习使用帐号信息: 1.致远A8平台(请自行准备测试环境) 慧集通连接器配置相关信息 访问地址: rest账号:rest rest密码: OA账号: 2.云星空(请自行准备测试环境) 连接…...

TDengine 如何进行高效数据建模
1.背景 数据建模对于数据库建立后整体高效运行非常关键,不同建模方式,可能会产生相差几倍的性能差别 2. 建库 建模在建库阶段应考虑几下几点: 建多少库 根据业务情况确定建库个数,TDengine 不支持跨库查询,如果业…...

HarmonyOS NEXT应用开发实战:一分钟写一个网络接口,JsonFormat插件推荐
在开发鸿蒙操作系统应用时,网络接口的实现往往是一个繁琐且重复的过程。为了提高开发效率,坚果派(nutpi.net)特别推出了一个非常实用的插件——JsonFormat。这款插件的主要功能是将JSON格式的数据直接转换为arkts的结构定义,让我们在编写接口…...
基于动力学的MPC控制器设计盲点解析
文章目录 Apollo MPC控制器的设计架构误差模型和离散化预测模型推导目标函数和约束设计优化求解优化OSQP求解器参考文献 Apollo MPC控制器的设计架构 误差模型和离散化 状态变量和控制变量 1、Apollo MPC控制器中状态变量主要有如下6个 matrix_state_ Matrix::Zero(basic_stat…...

Java重要面试名词整理(十六):SpringBoot
由于SpringBoot和Spring、SpringMVC重合度较高,更多详细内容请参考https://blog.csdn.net/weixin_73195042/article/details/144632385 本文着重于SpringBoot的启动流程 文章目录 概念启动流程底层分析构造SpringApplication对象run(String... args)方法SpringBoo…...

在K8S中,如何部署kubesphere?
在Kubernetes集群中,对于一些基础能力较弱的群体来说K8S控制面板操作存在一定的难度,此时kubesphere可以有效的解决这类难题。以下是部署kubesphere的操作步骤: 操作部署: 1. 部署nfs共享存储目录 yum -y install nfs-server e…...

算法-查找缺失的数字
给定一个包含 [0, n] 中 n 个数的数组 nums ,找出 [0, n] 这个范围内没有出现在数组中的那个数。 示例 1: 输入:nums [3,0,1] 输出:2 解释:n 3,因为有 3 个数字,所以所有的数字都在范围 [0,3…...

antd-vue - - - - - a-date-picker限制选择范围
antd-vue - - - - - a-date-picker限制选择范围 1. 效果展示2. 代码展示 1. 效果展示 如图:限制选择范围为 今年 & 去年 的 月份. 2. 代码展示 <template><a-date-picker:disabledDate"disabledDate"picker"month"/> &l…...

计算机网络练习题
学习这么多啦,那就简单写几个选择题巩固一下吧! 1. 在IPv4分组各字段中,以下最适合携带隐藏信息的是(D) A、源IP地址 B、版本 C、TTL D、标识 2. OSI 参考模型中,数据链路层的主要功能是(…...

redis的集群模式与ELK基础
一、redis的集群模式 1.主从复制 (1)概述 主从模式:这是redis高可用的基础,哨兵和集群都是建立在此基础之上。 主从模式和数据库的主从模式是一样的,主负责写入,然后把写入的数据同步到从服务器ÿ…...

STM32-笔记18-呼吸灯
1、实验目的 使用定时器 4 通道 3 生成 PWM 波控制 LED1 ,实现呼吸灯效果。 频率:2kHz,PSC71,ARR499 利用定时器溢出公式 周期等于频率的倒数。故Tout 1/2KHZ;Ft 72MHZ PSC71(喜欢设置成Ft的倍数&…...

Vue3 + ElementPlus动态合并数据相同的单元格(超级详细版)
最近的新项目有个需求需要合并单元列表。ElementPlus 的 Table 提供了合并行或列的方法,可以参考一下https://element-plus.org/zh-CN/component/table.html 但项目中,后台数据返回格式和指定合并是动态且没有规律的,Element 的示例过于简单&…...

【JavaWeb后端学习笔记】MySQL的数据控制语言(Data Control Language,DCL)
MySQL DCL 1、管理用户2、控制权限 DCL英文全称是Data Control Language(数据控制语言),用来管理数据库用户、控制数据库访问权限。 1、管理用户 管理用户的操作都需要在MySQL自带的 mysql 数据库中进行。 -- 查询用户 -- 需要先切换到MyS…...

libvirt学习
文章目录 libvirt 简介节点、Hypervisor和域libvirt 安装和配置libvirt的XML配置文件libvirt APIMain libvirt APIsError handlingSpecial specific APIs 建立到Hypervisor的连接libvirt API使用编译libvirt工具virshvirt-clonevirt-dfvirt-imagevirt-installvirt-topvirt-what…...

STM32-笔记19-串口打印功能
复制项目文件夹03-流水灯,重命名为19-串口打印功能 打开项目 在主函数中,添加头文件、和串口初始化函数(设置波特率)和输出函数,如图所示: 软件部分就设置好了 下面是硬件部分 接线:使用USB…...

概率论与数理统计
概率论占比更多,三分之二左右 数理统计会少一些 事件之间的概率 ab互斥,不是ab独立 古典概型吃高中基础,考的不会很多 条件概率公式,要记 公式不要全记,很多有名称的公式是通过基础公式转换而来的 目的在于解决一…...

统信系统设置代理的问题
统信系统设置代理的问题 问题表现方式一方式二 问题表现 统信系统下有系统代理和应用代理两个代理。设置系统代理时,git不能经过代理拉取代码。但是设置应用代理时,可以用git通过代理拉代码。 这是系统代理,在这里设置 ip 端口,…...

TCP 为什么采用三次握手和四次挥手以及 TCP 和 UDP 的区别
1. TCP 为什么采用三次握手和四次挥手 采用三次握手的原因: 确认双方的收发能力。第一次握手,客户端发送 SYN 报文,告诉服务器自身具备发送数据的能力,第二次握手,服务器回应 SYN ACK 报文,表名自己既能…...

springboot配置并使用RestTemplate
目录 一、RestTemplate配置 1、将RestTemplate初始化为Bean 2、使用HttpClient作为RestTemplate客户端 (1)引入HttpClient依赖 (2)修改RestTemplate配置类 3、设置拦截器 (1)新增拦截器类 …...

人工智能-Python网络编程-TCP
1 TCP-概念版 服务端 import socket # 1 创建服务端套接字对象 # socket.AF_INET IPV4 # socket.SOCK_STREAM TCP # socket.SOCK_DGRAM UDP tcp_server_socket socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 2 绑定端口号 tcp_server_socket.bind((192.…...

【Java回顾】Day3 继承|Override/Ovverload|多态|抽象类|封装|接口|枚举
学习资料 菜鸟教程 https://www.runoob.com/java/java-interfaces.html 继承|Override/Ovverload|多态|抽象类|封装|接口|枚举 继承 创建分等级层次的类,子类继承父类的特征、行为、方法 class 父类{ } class 子类 extends 父类{ super(); }一些性质 Java 不支持…...

SpringMVC(四)响应
目录 数据处理及跳转 1. 结果跳转方式 ①.ModelAndView ②.ServletAPI 1、通过HttpServletResponse进行输出 2、通过HttpServletResponse实现请求转发 3、通过HttpServletResponse实现重定向 ③.SpringMVC 1.直接输出 2.请求转发 3.重定向 2.ResponseBody响应json数…...

vim 的基础使用
目录 一:vim 介绍二:vim 特点三:vim 配置四:vim 使用1、vim 语法格式2、vim 普通模式(1)保存退出(2)光标跳转(3)文本删除(4)文本查找&…...

关于flinkCDC监控mysql binlog时,datetime类型自动转换成时间戳类型问题
flinkCDC监控mysql binlog时,datetime类型自动转换成时间戳类型 问题解决1.自定义转换器类2.代码引用 结果 问题 flink版本:1.18.1,mysql版本:8.0.40 使用FlinkCDC的MySqlSource 连接mysql,对于datetime 类型字段&…...

基于Springboot校园失物招领系统【附源码】
基于Springboot校园失物招领系统 效果如下: 系统登陆页面 物品页面 系统首页面 失物招领管理页面 失物认领页面 宣传视频页面 物品挂失留言管理页面 宣传视频类型管理页面 研究背景 在校园环境中,失物招领是一个常见的问题。传统的失物招领方式主要依…...