整个工程链接见文章末尾
HNU小学期计算机系统设计与创新基础训练——加密信息存储与游戏操作系统——第二部分
六、 实现方式及关键代码
1. 变量定义及对应功能
宏定义
cstBaud1:设置波特率为9600;
cstAdcChs17:选择P1.7为A/D输入;
FOSC:晶振频率为11059200L;
T_ms:定时时间
cstAdcPower:ADC电源开关
cstAdcFlag:A/D转换结束标志
cstAdcstart:ADC转换开始标志,结束后会自动清零,下一次转换需要置位;
cstAdcSpeed90:ADC转换速度;
变量定义
duan、wei:段选信号与位选信号;
origin、error、open:初始界面、密码错误、密码正确界面的显示信息,分别显示XX-HELLO、XX-ERROR、XX-OPEN;
date:四个8位变量,一个变量代表两个数码管,高位数码管对应变量的高4位,低位数码 管对应显示变量的低4位;
flag1:用于数码管的位选,flag1为0则0号数码管亮;
flag2:用于修改信息对应数码管的小数点位,如果flag2 == flag1 说明此时的flag1位数码管 是被修改的位,小数点亮;
flag3:用于串口发送数据,串口通信模块需要传输四个8位数据,用flag3表示发送的是哪 一个数据,如果flag3 == 0x00,则发送date[0],并让flag3+1,接着发送后面的三个数 据;
addr:用于保存此时选择的信息是哪一条;
read_date:相当于date的存储,将24C02中的信息读出后不再变化,除非修改信息;
ui_state:用于当前界面的标识,是整个框架最重要的变量;
btKey3Flag:因为P1.7用于模拟AD口,因此不能将KEY3作为普通的按键使用,用该标记来 标记KEY3是否被按下,在导航按键处理函数中进行置位;
btBeepFlag:蜂鸣器蜂鸣标志,但该标志为1,则蜂鸣器开始发声;
x、s:用于导航按键修改信息时标志修改的位数,如果是高四位则x为0x10、低四位则x为 0x01,s的值为flag/2,表示更改的变量date
2. 定时器
初始化函数:
将计时器工作方式设定为0,16位自动重装计数,开启总中断并开启定时器0中断,设置定时器TH0、TL0的初值,最后打开定时器使其开始计数。
执行函数:
定时器0主要用于数码管和发光二极管的显示,在8个数码管顺序发光一次后使发光二极管发光,发光的二极管由当前界面ui_state决定。
另外数码管的显示由界面ui_state选择,根据不同的界面调用不同的函数来显示数据,为了简化代码没有写太多的界面函数而是复用了几个函数。如果此时flag2==flag1表示当前扫描到的数码管的小数点需要点亮,以此代表更改的数码管位,因此将P0首先置为0x80。
蜂鸣器的发声同样在定时器函数中,根据标志位判断是否发生,标志位的置为在主函数大循环中。
3. 数码管
初始化:
将P0口和P2.3设置为推免输出,确保数码管能够显示,同时再将蜂鸣器和P0初始化;
这仅仅是三个数码管段选函数之一,作用是根据数码管的位来显示不同的信息,如初始界面XX-HELLO,show传递的即为origin变量,内容为{‘–HELLO’},前面的XX表示选择的信息,直接传入date[0]即可,当按下KEY1确认信息后将date[0]赋给addr。
P0的赋值语句用 | 的原因是如果需要显示小数点位,那么就要将0x80与duan段选信号相与了,而在定时函数调用该段选函数前 P0 就已经赋予了0x80(如果flag1==flag2),flag1就是选择数码管显示位置的信号。
4. 导航按键
4.1 初始化
4.2 获取AD子函数:
没有将ADC_FLAG置1,用于判断A/D转换是否结束;while循环直到A/D转换结束,将ADC_FLAG位软件清0,为下一次获取AD值做准备;
最后返回ADC_RES寄存器中的转换结果;
4.3 获取按键值子函数:
获取AD值后根据值判断是否有键被按下,若有则进行一次按键延时消抖,后再次获得AD值,如果此时依然有键被按下那么获取AD值的最高三位,其它位直接清零,并且返回这三位即可,若没有按键按下则返回0x07;
导航按键的上键返回0x05、下键返回0x2、左键返回0x4、右键返回0x1、确认键返回0x3、开关按键3返回0x0,没按下返回0x07;
4.4 按键按下处理函数
注:左键与下键的处理与上建和右键类似;
处理函数最开始有一个等待导航按键被松开的过程,用ucNavKeyPast记下导航按键上一次被按下的状态,用ucNavKeyCurrent记下松开后的一个状态。
接下来用flag2来判断是哪一个数据被改变,用x来获取改变的大小(高位数码管为0x10,低位为0x01),用s来记录改变的是哪个数据(共date[0]-date[3])。
当接受的值是0x00时,表明KEY3被按下,因此将btKey3Flag标志置为1;
当接受的值为0x05时,表明是导航键上键,因此修改相应的数据,如果修改的数据已经是f时就将其置0,判断语句是判断修改的date数据的高位或低位,有不同的改变方式。
当接受的值为0x01时,表示是导航键右键,需要将flag2根据不同的信息显示进行改变,比如密码输入界面的密码是4-7四位数码管,那么当此时flag2已经是7时,按下右键需要将flag2置为4;
剩余的左键与下键的处理方法几乎相同,因此不再重复;
最后根据界面以及flag2的判断语句,判断是否位于信息选择界面,若是那么就令addr得到改变的date[0]的值。
5. 蜂鸣器
前面提到过蜂鸣器的发生模块是在定时器函数中,根据标志位判断是否发生,标志位的置为在主函数大循环中。
在密码输入界面,如果输入密码错误,则将ui_state置为0x02,让数码管显示ERROR,将btBeepFlag置1,延时200ms,此时蜂鸣器发声200ms,接下来将btBeepFlag置0,停止发声后再回到密码输入界面,即ui_state = 0x01。
6. 按键
按键主要运用在主函数的while循环中,用于各个页面的切换,比如上图在初始页面按下Key1键后进入信息选择界面,小数点出现(flag2变为0x01,也就是第二位数码管的小数点)。
按键的消抖方法都是用的图示中的延时消抖方法,当检测到按键按下时等待5ms然后再次检测是否按下按键,如果此时依然如此,那么按键就是被按下了。
7. 串口通信
7.1串口初始化
将AUXR辅助寄存器置为0x80|0x40,1T模式,且定时器1的速度是传统的12倍,不分频,T0x12为1。将SCON设置为允许接受模式,设置好TL1、TH1两个定时器1的寄存器的初值,打开定时器1,打开总中断,令串口1中断高优先级,先不打开ES串口中断,仅在需要时打开,初始化RI、TI接受和发送中断标志位。
7.2 发送数据函数
当btUart1SendBusy标志为0时将单个字符给UART1以发送到PC机,并将btUart1SendBusy标志再次设置为1;
7.3 串口1中断处理函数
最后的判断语句判断TI是否为1,若是代表发送数据,此时将btUart1SendBusy设置为0,发送数据通道打开,TI设置回0;
在接收数据方面根据flag3的值来决定是哪一个变量接受数据,如flag3为0的话那么date[0]接受这个字节的数据,接受结束再将RI置回0,flag3置成1,表示接受下一个数据;
RI在接收数据时是自动置1的。
8. 24C02读取与存储
8.1 初始化
令SDA与SCL都置为高电平,准备24C02的读取操作;
8.2 主机启动、停止信号
SCL为1时,SDA从高电平变为低电平表示开始信号,反之表示停止信号;
8.3 从机应答信号
若在一段时间内没有收到从期间的应答则主器件默认从器件已经收到数据而不再等待应答信号。即SDA若为1,延时一段时间后SCL从高电平转为低电平。
8.4 写/读字节
写字节(8位)根据SCL为0时SDA才可以改变的特性,将SCL先置为0后再令SDA收到CY数据,然后令SCL保持高电平一周期表示此时SDA上的数据有效,最后令SCL保持为0,SDA保持为1;
读字节是在SCL为1时用变量K读出SDA上的数据,延时一段时间表示数据有效,接下来令SCL为0,此时SDA上的数据改变,接下来再次接受数据,同样循环8次收到该字节后返回k。
8.5对24C02的地址写/读字节![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/3f7f9e119dac4df3ff79c5d33a9cea90.png)
根据基本原理中的读写时序,写出这两个函数;
写地址是先发出开始信号后将要选择的芯片发送到IIC总线上,接下来等待响应后将地址发送出去,最后就可以通过写字节函数写入字节;
读地址则是比写地址多了一个开始信号与选择芯片的发送,并且此时发送的信号的尾位为1表示读地址,最后读出字节并返回;
8.6 读写地址函数的应用
如:
在输入密码界面,如果此时的密码输入是正确的,那么读出该地址中的信息(密码在一个模块的0、1地址,信息在2、3、4、5地址,一个信息模块占6个地址空间)。
在修改密码/信息界面,如果按下Key1键确认修改,那么将输入完的信息(date[0]-date[3]是可以修改的信息)写入到相应的信息块的相应位置(密码位、信息位)。
9. 游戏控制(主要是上位机游戏程序中的串口部分)
9.1下位机相应部分
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210508173554400.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80Mzk3MzA4OQ==,size_16,color_FFFFFF,t_70)
上述左侧代码就是导航按键处理函数中的代码,如图,与游戏操作相关的部分就只有date_yx这一个变量,即在导航按键按下上下左右时,分别将date_yx置为1、2、3、4,不按下时该变量置为0。
右侧代码是主循环中的游戏操作部分,内置while循环,当按下key1退出游戏操作界面时令ES=0,关闭串口。否则循环执行导航按键检测函数,当date_yx变量不为0时发送给上位机。
9.2 上位机通信模块
上左侧为python串口的设置模块,右侧为根据串口读出的数据来进行响应的函数;
用多线程的方式打开串口,持续监视串口数据。
七、 功能流程与验证
1. 功能流程
2. 验证
2.1 初始界面与信息选择界面
信息选择界面的小数点出现表示可以进行修改(最左边的两位);
2.2 密码输入、密码错误、密码正确界面
输入密码界面(左图)小数点可以在四个数码管的任意一处表示修改该处的数字;
2.3 信息显示、信息修改、信息修改后界面
如中图修改信息,即小数点出现,修改成功后效果如右图,修改成功;
2.4 密码修改界面与修改成功界面(信息与密码修改)
![在这里插入图片描述](https://img-blog.csdnimg.cn/2021050817373576.png)
修改密码按下KEY1确认时进入右图所示界面表示修改成功;
2.5 串口通信(下位机接受后需要KEY2存储、上位机接受)
学习板按下Key3发送数据时,上位机正确接受到数据22 22 22 22;
当上位机发送数据时,学习板可以接收到11 11 11 11,按下Key2时才存储到24C04中。
2.6 游戏控制
下位机游戏界面沿用初始界面,当上位机打开游戏后,导航按键可以实时控制游戏角色进行移动。
2.7 小结
信息加密存储的所有功能均正常,基于串口通信的游戏操作模块也正常工作,至此创新设计完成。
八、 难点与解决办法
Q1:如何将密码和信息同时存入非易失存储器24C02,如何判断自己输入的密码是否与存储的密码正确匹配?
A1:将一条信息(4字节)与对应的密码(2字节)当作一个数据块共6字节进行存储,密码在前,信息在后;选择完信息x后,读出24C02的x6+0、x6+1地址处的 密码与输入的两个字节密码信息做比较,若相同则密码正确,否则密码错误。密码正确则读出x6+2、x6+3、x6+4、x6+5处的四字节信息;
Q2:数码管如何现实以及多级界面如何分配并且根据当前状态进行按键判断?
A2:设置一个变量ui_state来表示界面的状态,0代表初始界面、1代表信息选择界面、2代表密码输入界面、3代表信息显示、4代表密码修改等等,将各个状态变成各个数值;将界面的改变放在主函数的大循环中,当按下按键时根据当前状态来进入下一界面并完成界面初始化。
Q3:如何将基于串口通信的导航按键操作游戏模块加入到上位机游戏程序中?
A3:下位机在游戏操作界面监听导航按键的状态,根据导航按键被按下的方向向PC游戏软件发送不同的数据,发送1、2、3、4分别表示上下左右键被按下;上位机游戏软件(python实现)持续保持串口打开的状态,以多线程的方式持续监听从下位机传来的数据,并根据数据进行相应的响应。
Q4:如何通过导航按键来修改密码以及信息(精确到数码管上每一位数字所代表的四位bite的自由修改而不是一个字节的修改)?
A4:如在具体实现中所提到的,定义两个变量分别表示修改字节的步长和被修改的字节,根据flag2标志来设置步长与字节,flag%2=0的话表明修改的是flag/2这一字节的高四位,因此步长为0x10,否则修改低四位步长为0x10。
Q5:因为要使用导航按键进行数值的变化,那么最好是使用Key3的P1.7口作为虚拟AD口,但是我还是需要Key3的按键功能(按键实在太少而功能太多),那么该如何解决?
A5:我学习导航按键与数字按键综合控制数码管工程中的使用方法,为Key3单门设置一个btkey3flag,按下key3时虽然不能直接使用Key3=P1.7的按键消抖方法来实现,但同样可以在导航按键模块返回ADC等于0x00(也就是表示key3被按下)时将标志置1,这样就可以知道什么时候按下key3以及有没有按下key进而使用这个按键。
九、 总结
整个创新设计历时两个星期,直到16号才终于画下句号,在此感慨良多。因为我的设计是自己设计实现的,所以在编程与调试上的时间不短。遇到过很多的bug也解决了很多的难题,到现在终于可以松一口气说我完成了。
或许我也可以选择在网上查点代码,然后学习一下,模仿着写一下就提交上去,但我总觉得这不是自己的,所以我选择自己写、自己设计页面的显示、自己设计整个功能流程,虽然这很耗费时间,但最后做出的八百多行代码却让我觉得值得。
单片机的学习尤其是编程确实非常的难,因为有太多的寄存器需要去学习如何赋值、都代表了什么,有时候看STC的数据手册上的寄存器功能都会感到一头雾水,大段大段的文字、非常多的图片往往让人感到困扰,想要完全理解这太难了。因此,我只能变试边学,在板子上下载看一下,忽略掉那些无关紧要的东西,慢慢就渐入佳境了。
这次小学期的实训我感觉非常有意义,不仅学到了很多的新知识并将之运用到了实践上,而且了解到了这些简单的硬件的工作方式,真是受益匪浅。
工程仅供参考:
链接:pan.baidu.com/s/1X-U3GRId-OqyKzyDkcBhMg
提取码:7a46