概述
计算机系统设计与创新基础训练创新设计《基于485通信的双人井字棋对战》
这是《计算机系统设计与创新基础训练创新设计》课程设计,实现的是一个《基于485通信的双人井字棋对战》小游戏,你需要两根杜邦线和两块STCb学习板。今天花了十分钟写了这篇博客用于<开源>,时间比较仓促,希望程序能够带给读者快乐,而不是压在我电脑里尘封数年直至消失。
- 展示:Bilibili链接地址
- 代码链接奉上:Tic-tac-toe games代码地址
- gitHub也可以看看:Tic-tac-toe games GitHub地址
- 如果您觉得我的程序不错,可以留下一个赞
一.设计概述:
1.设计目的
为了进一步熟悉单片机开发,学习单片机项目的流程与设计方法,培养自己的动手能力和创新能力,利用现有的STC-B学习板设备,融合结合数码管滚动显示,流水灯功能,电子音乐功能,导航键功能和蜂鸣器功能,485通信功能,将3x3的棋盘滚动地显示在一行地数码管上,实现井字棋单机小游戏和双人对战功能。
2.功能概述:
- ①数码管布局:
在数码管中央棋盘的一行,第一颗数码管显示行号,第二颗和第8颗数码管显示对战双方A或B,3和7颗数码管显示棋盘左右边界,456三颗数码管显示3x3棋盘。通过导航键可以上下滚动查看井字棋任意行,上下左右移动闪烁光标确定位置,按下则落子下棋。
- ②游戏开始:
游戏开始前,播放《超级玛丽》开机音乐, 数码管滚动显示字母《PLAY CHESS》表示游戏名称,K2可以选择玩家A或B,按下K1游戏开始,进入游戏棋盘界面。
- ③游戏中:
通过控制导航键上下滚动选择数码管的任意一行,行号显示在第一个数码管上,操作的玩家信息将会用小数点标注;左右控制光标位置,光标闪烁,显示被选中的位置,按下导航键落子下棋;操作过程配有按键提示音效,并且A,B双方玩家的按键提示音不同,加以区分。
- ④游戏结束:
当棋盘存在三点一线或者平局将会触发游戏结束,数码管闪烁,显示赢家信息,胜利一方播放胜利的音乐,表示游戏胜利,失败一方播放失败的音乐,表示游戏失败。游戏全程按下K2键将会触发得分显示,将双方的得分显示在数码管上。
- ⑤双人对战功能:
用杜邦线连接两块板子,双方可以用A,B两个符号在棋盘上下棋,两人通过485通信进行数据交互,实现实时双人对战。下棋过程中除查看比分外,所有的操作动作都会被传输到对手,实时更新棋盘。游戏采取三局两胜利制,当三局游戏结束,会自动关闭游戏循环,显示滚动字样《PLAY CHESS》
二.模块实现:
1.数码管布局和动态扫描:
- (1)整体布局
好的布局往往给编码带来方便,我将8颗数码管布局从左到右编码为0,4,6,1,2,3,7,5 ,这样有两个好处:
①棋盘Bord的的数组下标为1,2,3,这样在以后的处理中会更加便捷
②位选数组可以方便的设置编码顺序,比如说uchar dig[]={0,4,6,1,2,3,7,5};数码管位选,实现上没有难度,给代码编写带来方便
因此根据这种编码方式,可以得到右上图的映射方法,从而设计:
数码管位选数组:
uchar code dig[]={0,3,4,5,1,7,2,6}; //数码管位选
棋盘数组:
uchar Board[4][8]={ //棋盘
{0,0,0,0,0x0a,0xb,0x13,0x14},
{1,0,0,0,0x0a,0xb,0x13,0x14},
{2,0,0,0,0x0a,0xb,0x12,0x12},
{3,0,0,0,0x0a,0xb,0x10,0x11}};
比分板
uchar scoreBoard[8]={0,0,0x12,0xa,0xb,0x12,0,0}; //比分板
- (2)动态扫描显示棋盘/比分板/滚动字样
数码管显示功能易于实现,主要是设置动态扫描,扫描的同时选定位选和段选信号即可,所以说,实现的时候重点在于如何切换显示内容,也就是段选信号输入?我在前面已经设计了三个布局,分别是棋盘/比分板/滚动字样,他们存在三个数组中,当检测到控制信号有效的时候,按条件切换显示内容:
digIndex++; //数码管的数组下标
if(digIndex==8){
digIndex=0; //下标归零
count++;
}
if(sbtKey2 == 0 && (start||over||gameNum)) //开始或者结束的时候可以查看棋盘
displayScore(); //3局开始后,被按下,显示比分板
else if((!start&&!over&&gameNum==0&&!isSelect)||(start&&over)) //未开始或者已经全局结束
displayRoll(arrRoll); //显示滚动字母
else
displayChessBoard(); //显示棋盘
2.按键功能实现
(1)Key1游戏开始功能:
在T0定时器中监听Key1,当按键按下并且start=0并且已经选择了游戏玩家角色,则可以提前开始开始:
void T0_Process() interrupt 1 //中断
{
//检测按键Key1游戏开始
if(sbtKey1==0 && (!start) && (!over)) //按键按下并且start=0并且已经选择了游戏玩家角色,则可以提前开始开始
{
tick4();
if(sbtKey1==0){
while( !sbtKey1 );
start=1; //key1按下,游戏开始
datas=0x01; //设置Key1数据,记住,玩家信息在sendData中添加
sendDatas(); //发送数据
}
}
}
(2)Key2游戏角色选择功能:
游戏选择功能难点在于,如何根据控制信号来选择玩家,当游戏未开始,且游戏局数为0的时候,Key2将会选择玩家,这个时候才能去调用函数selectCharacter();选择玩家,此时将会更新棋盘第二行,显示所选择的玩家信息。如果选择信号设置错误了,那么将会导致棋盘错误更新,显示棋盘落子信息,而不是所选角色。
if( sbtKey2 == 0 && gameNum==0 && (!start) && (!over)) //当游戏未开始,且游戏局数为0的时候,Key2将会选择玩家
{
Delay5ms(); //延时消抖
tick4();
if( sbtKey2 == 0 )
{
while( !sbtKey2 ); //等待K1放开
selectCharacter();
isSelect=1;
datas=0x02; //发送key2
sendDatas(); //直接发送数据
}
}
(3)导航键控制方向和落子:
导航按键的功能是移动光标,控制上下左右,用于定位,当按下确定键的时候可以选中他的位置。导航键上下左右实现方法:
①只需要改变(x,y)的值就可以改变棋子坐标,随着数码管动态扫描就自动改变了
②当导航按键被按下的时候,获取此时的ADC值
③用一个case语句进行分支,对ADC值进行判断,从而可以判断处方向,从而改变<x,y>的值:
左:x--,右: x++,上:y--,下:y++
④注意要对x,y的范围进行限定,他们都因该在范围<1,2,3>,为了更加接近显示体验,我不设置滚动,相反,当x,y到达边界的时候,按下导航键试图越界将不被允许
⑤将整个导航检测函数放在中断中处理还是放到 while(1){} 循环内,核心功能如下:
switch( ucNavKeyPast )
{
case 0x01: //右
if(y!=3) y++;break;
case 0x02: //下
if(x!=3) x++;break;
case 0x03: //里
break;
case 0x04:
if(y!=1) y--;break; //左
case 0x05:
if(x!=1) x--;break;
}
3.游戏开始和结束
(1)游戏开始:
游戏开始的程序需要完成播放结束音乐,等待玩家再次开始游戏,闪烁数码管和更新棋盘的功能。对于播放音乐,可以直接调用PlayMusic函数即可,对于等待游戏结束可以用while(1)循环直接判断start是否为1
/*---------游戏开始----------------*/
void gameStart(){
PlayMusic(190,arrMusicBM); //播放开机音乐
while(!start); //等待游戏开始
Delay5ms();
tick3();
//闪烁数码管
//重新定时为45ms
TH0 = ( 65535 - 45000 ) / 256; //定时器0的高八位设置
TL0 = ( 65535 - 45000 ) % 256; //定时器0的低八位设置,这里总体就是设置定时器0的初始值是1ms
//重新定时为45ms
TH0 = ( 65535 - 1000 ) / 256; //定时器0的高八位设置
TL0 = ( 65535 - 1000 ) % 256; //定时器0的低八位设置,这里总体就是设置定时器0的初始值是1ms
//更新棋盘
refreshChessBoard();
}
4.音乐播放
电子音乐通过蜂鸣器发声,根据音谱对音调和节拍进行编码,编码的时候
①音符的十位代表是低中高八度,1代表高八度,2代表中八度,3代表高八度
②个位代表简谱的音符,例如0x15代表低八度的S0,0x21代表中八度的DO。
③节拍则是代表音长,例如:0x10代表一拍,0x20代表两拍,0x08代表1/2拍
④音符的十位代表是低八度,中八度还是高八度,1代表低八度,2代表中八度,3代表高八度
⑤个位代表简谱的音符,例如0x15代表低八度的S0,0x21代表中八度的DO。
⑥节拍则是代表音长,例如:0x10代表一拍,0x20代表两拍,0x0代表1/2拍,根据音谱对电子音乐进行编码:
(1)超级玛丽结束音乐:
uchar code arrMusicFail[] ={
0x21,0x08,0x24,0x10,0x24,0x08,0x24,0x08,0x24,0x08,0x23,0x08,0x22,0x09,0x21,0x08,0x00,0x00
};
(2)超级玛丽BM音乐:
0x23,0x04,0x23,0x08,0x23,0x08,0x21,0x04,0x23,0x08,0x25,0x10,0x15,0x10,
0x21,0x08,0xff,0x04,0x15,0x04,0xff,0x08,0x13,0x08,0xff,0x04,0x16,0x08,0x17,0x04,0xff,0x04,0x17,0x04,
0x16,0x08,0x15,0x06,0x23,0x05,0x25,0x05,0x26,0x08,0x24,0x04,0x25,0x04,0xff,0x04,0x23,0x08,0x21,0x04,
0x22,0x04,0x17,0x08,0xff,0x04,0x21,0x08,0xff,0x04,0x15,0x04,0xff,0x08,0x13,0x08,0xff,0x04,0x16,0x08,
0x17,0x04,0xff,0x04,0x17,0x04,0x16,0x08,0x15,0x06,0x23,0x05,0x25,0x05,0x26,0x08,0x24,0x04,0x25,0x04,
0xff,0x04,0x23,0x08,0x21,0x04,0x22,0x04,0x17,0x08,0xff,0x04,0xff,0x08,0x25,0x04,0x24,0x04,0x24,0x04,
0x23,0x08,0x23,0x04,0xff,0x04,0x15,0x04,0x16,0x04,0x21,0x04,0xff,0x04,0x16,0x04,0x21,0x04,0x22,0x04,
0xff,0x08,0x25,0x04,0x24,0x04,0x24,0x04,0x23,0x08,0x23,0x04,0xff,0x04,0x31,0x08,0x31,0x04,0x31,0x08,
0xff,0x08,0xff,0x08,0x25,0x04,0x24,0x04,0x24,0x04,0x23,0x08,0x23,0x04,0xff,0x04,0x15,0x04,0x16,0x04,
0x21,0x04,0xff,0x04,0x16,0x04,0x21,0x04,0x22,0x04,0xff,0x08,0x25,0x08,0xff,0x04,0x24,0x08,0xff,0x04,
0x23,0x08,0xff,0x08,0xff,0x10,0x00,0x00
(3)超级玛丽吃蘑菇音乐:
uchar code arrMusicSuccess[] ={
0x23,0x04,0x23,0x08,0x23,0x04,0xff,0x04,0x21,0x04,0x23,0x08,0x25,0x10,0x00,0x00
};
5.485 通信数据发送和接收
(1)传输信息表示:
这里为了减少数据传输的量,我将按键进行编码发送。data=<tag,K1,K2,右,下,确认,左,上>,一共8位,可以封装成一个uchar向量,从而大大减小发送量:
Tag | 上 | 左 | 按下 | 下 | 右 | Key2 | Key1 |
---|
- Tag:标识位,用来确定显示的来源机器,如果是来源于0x0a,则Tag=0,否则Tag=0
- 上下左右确认表示导航内容
- Key1,Key2分别表示按键Key1,Key2被按下
将按键进行编码带来了许多便利,但是也有一些问题,类似于网页Web技术中的ajax,可以局部刷新页面,但是我只传输1个8位的向量编码,而不是整个状态,一旦某个过程缺失将导致严重后果。
(2)角色身份选择:
传输一个数据,右移7位选出标识位tag,当这个tag与自身的myPlayer的最低为相同的时候,说明要进行互斥操作,因此,将myPlayer进行取反操作,因此,可以设置自己的角色与对方互斥
if(!((datas>>7)^(myPlayer&0x01))){
if(start) return; //如果交互数据来自自己,那么拒绝接收
else{
myPlayer^=0x01; //异或,保持玩家互斥
}
}
(3)角色选择同步:
在一开始,我没有合理地设置角色同步,导致一方设置角色,另一方无法看到,这个时候体验感将会极差,所以我将代码进行了改进,设置Key2数据接收地时候,更新数码管数组,显示玩家选择信息:
同样地在一开始,我一直被游戏结束功能所困扰,在getData函数接收到数据的时候,我判断棋盘是否导致游戏结束,并且设置over标志位,根据start和over进行gameOver调用,但是这里出现了两个进程的冲突:
①接收函数调用getData
/*---------串口2中断处理程序,数据接收---------*/
void Uart2_Process( void ) interrupt 8 using 1
{
if( S2CON & cstUart2Ri ) //无校验&接收中断请求标志位
{
datas = S2BUF ; //从串口中接收数据暂存
S2CON &= ~cstUart2Ri; //接收中断标志位清0
getData();
}
if( S2CON & cstUart2Ti ) //无校验&发送中断请求标志位
{
btSendBusy = 0 ; //清除忙信号
S2CON &= ~cstUart2Ti ; //发送中断标志位清0
}
}
②getData调用gameOver
void getData()
{
gameOver();
}
③我们忽略了一个问题,就是串口会占用计时器T2,但是,gateData中gameOver会调用函数PlayMusic和设置T0的即使时间,在网上查找相关资料得到,串口和定时器2是不能同时工作的,所以说进程PlayMusic和getData冲突,导致程序出错。因此我想到的方法是在NavKey_Process中判断:
/*---------导航按键处理子函数--------*/
void NavKey_Process()
{
............
if(over&&start){
gameOver();
}
Delay100ms();
}
④实现效果:
两块单片机在游戏结束的时候同时发出结束音乐
三.程序功能测试
(1)数码管显示功能:
在数码管中央棋盘的一行,第一颗数码管显示行号,第二颗和第8颗数码管显示对战双方A或B,3和7颗数码管显示棋盘左右边界,456三颗数码管显示3x3棋盘。通过导航键可以上下滚动查看井字棋任意行,上下左右移动闪烁光标确定位置,按下则落子下棋。
(2)玩家选择功能
当游戏开始的时候,按下Key2选择角色,被选择的角色被显示在了数码管中央,此是左侧的玩家信息保持和选择玩家一致,也就是说,左侧的玩家,是本地玩家的信息。按下Key1键确认选择,按下Key1键游戏开始。
(3)双人对战功能:
用杜邦线连接两块板子,双方可以用A,B两个符号在棋盘上下棋,两人通过485通信进行数据交互,实现实时双人对战。下棋过程中除查看比分外,所有的操作动作都会被传输到对手,实时更新棋盘。游戏采取三局两胜利制,当三局游戏结束,会自动关闭游戏循环,显示滚动字样《PLAY CHESS》
(4)数码管滚动显示功能:
在程序下载好之后,可以看到PLAY CHESS字样在数码管上平稳滚动显示,显示当前游戏名称。
(5)比分显示功能
程序下载好之后,点击K1游戏开始,按下KEY2可以看到显示当前比分信息。
四.程序设计总结:
这一次设计我进一步熟悉单片机开发,学习单片机项目的流程与设计方法,培养了自己的动手能力和创新能力,利用现有的STC-B学习班设备,融合结合数码管滚动显示,流水灯功能,电子音乐功能,导航键功能和蜂鸣器功能,485通信功能,将3x3的棋盘滚动地显示在一行地数码管上,实现了井字棋单机小游戏和双人对战功能。其中收获的新知识主要是:
(1)定时器组成
- MCS51系列的单片机通常有2个16位可编程定时/计数器,即定时器0和1。(T0/T1)
- MCS52系列还有一个定时/计数器2。可编程的意思是指其功能(如工作模式、定时时间、启动方式等)可由指令来确定和改变。通常都是赋值指令给相关的寄存器。
- 与定时/计数器相关的有两个特殊功能寄存器(模式控制寄存器TMOD和控制寄存器TCON)。
- 且定时器往往在中断中使用,以便当时间到了完成相应处理。
- MCS51单片机定时/计数器工作原理示意图如图
(2)定时器赋初始值计算:
如果需要需要定时为50MS,则计算方式如下
如果晶振是12MHZ:
则机器周期为12MHz除以12,就是1MHz,每秒1000000次机器周期,那么50ms就是50000次机器周期。
65536-50000=15536(3cb0),TH0=0x3c,TL0=0xb0。1ms就是1000次机器周期。(65536-1000)/256是高位,(65536-1000)%256是低位。
TMOD寄存器的设置依据如图所示:
(3)中断工作原理
中断的概念: CPU在处理某一事件A时,发生了另一事件B请求CPU迅速去处理(中断发生);CPU暂时中断当前的工作,转去处理事件B(中断响应和中断服务)待CPU将事件B处理完毕后,再回到原来事件A被中断的地方继续处理事件A(中断返回),这一过程称为中断。MCS51中断信息如表2所示。
MCS51单片机中断信息表
与中断相关的寄存器包括:
① 中断允许控制寄存器IE:
在用到中断时,必须要开总中断EA,即EA=1。
- EX0(EX1):外部中断允许控制位。EX0=1开外部0号中断,EX0=0关闭外部0号中断。
- ET0(ET1):定时中断允许控制位。ET0=1,开内部定时器0号中断;ET0=0关闭定时器中断0号开关.
- ES: 串口中断允许控制位。ES=1,开串口中断;ES=0 关闭串口中断。
② 中断优先控制寄存器IP
说明:
- PS:串行口中断优先级控制位。PS=1设定串行口为高优先级中断;PS=0为低优先级中断。
- PT1:T1中断优先级控制位。PT1=1设定定时器T1为高优先级中断;PT1=0为低优先级中断。
- 外部中断1优先级控制位。PX1=1设定定时器外部中断1为高优先级中断;PX1=0为低优先级中断
- PT0:T0中断优先级控制位。PT0=1设定定时器T0为高优先级中断;PT0=0为低优先级中断。
- PX0:外部中断0优先级控制位。PX0=1设定定时器外部中断0为高优先级中断;PX0=0为低优先级中断。
(3)传输信息表示创新:
这里为了减少数据传输的量,我将按键进行编码发送。data=<tag,K1,K2,右,下,确认,左,上>,一共8位,可以封装成一个uchar向量,从而大大减小发送量:
Tag | 上 | 左 | 按下 | 下 | 右 | Key2 | Key1 |
---|
- Tag:标识位,用来确定显示的来源机器,如果是来源于0x0a,则Tag=0,否则Tag=0
- 上下左右确认表示导航内容
- Key1,Key2分别表示按键Key1,Key2被按下
将按键进行编码带来了许多便利,但是也有一些问题,类似于网页Web技术中的ajax,可以局部刷新页面,但是我只传输1个8位的向量编码,而不是整个状态,一旦某个过程缺失将导致严重后果。
(4)设计感悟:
本次实验中我用到了融合结合数码管滚动显示,流水灯功能,电子音乐功能,导航键功能和蜂鸣器功能,485通信功能,将3x3的棋盘滚动地显示在一行地数码管上,整个游戏花费了一个星期,从一开始看不懂定时器,到完成作品,到帮别人debug都是一步步过来。体会了从陌生,到认识,到熟知,到应用的过程,最让我印象深刻的是学习过程中,利用现有资源,快速总结方法。我认为最有效的方法是看到什么不懂的,就立马总结起来写到Typora里面,不会的时候拿出来查阅,时间一久,就立马知道该从哪去找方法,去哪里找知识,很快就能熟练掌握。也很感谢我的舍友帮我解决了电子音乐的编码问题。超级马里奥的失败胜利音乐没有电子谱,所以只能通过人耳翻译,仔细听音符,音调和设置节拍,最后才整合得到了电子普,将其编码写入,通过蜂鸣器播放。我想,这门课的设计目的我应该达到了,熟悉了整套的单片机设计方法,知道一块芯片的生产,零件如何焊接,到编码应用,很感谢课程的教学老师。
最后
以上就是冷静蜜蜂为你收集整理的stc-b单片机项目《基于485通信的双人井字棋对战》计算机系统设计与创新基础训练创新设计《基于485通信的双人井字棋对战》一.设计概述:二.模块实现:三.程序功能测试四.程序设计总结:的全部内容,希望文章能够帮你解决stc-b单片机项目《基于485通信的双人井字棋对战》计算机系统设计与创新基础训练创新设计《基于485通信的双人井字棋对战》一.设计概述:二.模块实现:三.程序功能测试四.程序设计总结:所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复