基于51单片机和LCD1602、矩阵按键的小游戏《猜数字》
目录
- 系列文章目录
- 前言
- 一、效果展示
- 二、原理分析
- 三、各模块代码
- 1、LCD1602
- 2、矩阵按键
- 3、定时器0
- 四、主函数
- 总结
系列文章目录
前言
用的是普中A2开发板,用到板上的矩阵按键,还需要外接一个LCD1602液晶显示屏。
【单片机】STC89C52RC
【频率】12T@11.0592MHz
效果查看/操作演示:B站搜索“甘腾胜”或“gantengsheng”查看。
源代码下载:B站对应视频的简介有工程文件下载链接。
一、效果展示
二、原理分析
1、数字的输入与清除
输入数字时,通过十进制的移位和加法,将刚输入的数字实时保存到int变量中。
2、随机数的产生
设置的最大范围是:1~9999,我发现当范围较大时,虽然每次获取键码时用了定时器0的低八位做种子,但发现产生的随机数比较集中,集中在其中的一个小范围内。然后我将随机数的产生的算式弄复杂一些,就比较随机了,但还是有一定的伪随机性。道友有更好的办法请跟我说一下,谢谢了!
3、随机数的范围
由于屏幕大小有限,所以限定随机数的范围是1~9999,最多四位数。设定范围的时候要注意,最大值要比最小值大2以上,如果不合理,则需要重置后重新输入。
4、按键的检测
矩阵按键通过行列扫描检测,利用定时器0,每隔20ms检测一次,防止阻塞程序运行。
三、各模块代码
1、LCD1602
h文件
#ifndef __LCD1602_H__
#define __LCD1602_H__void LCD_WriteCommand(unsigned char Command);
void LCD_WriteData(unsigned char Data);
void LCD_SetCursor(unsigned char Line,unsigned char Column);
void LCD_Init();
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char);
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String);
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length);
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_Clear(void);
void LCD_IsShowCursor(unsigned char State);
void LCD_Show16Char(unsigned char Line,unsigned char Column,char *String);#endif
c文件
#include <REGX52.H>/*引脚配置*/
sbit LCD_RS=P2^6;
sbit LCD_RW=P2^5;
sbit LCD_EN=P2^7;
#define LCD_DataPort P0/*函数定义*/
/*** 函 数:LCD1602私有延时函数,延时约50us* 参 数:无* 返 回 值:无*/
void LCD_Delay50us(void) //12T@11.0592MHz
{unsigned char i;i=20;while(--i);
}/*** 函 数:LCD1602私有延时函数,延时约2ms* 参 数:无* 返 回 值:无*/
void LCD_Delay2ms(void) //12T@11.0592MHz
{unsigned char i,j;i=4;j=146;do{while(--j);}while(--i);
}/*** 函 数:LCD1602写指令* 参 数:Command 要写入的指令* 返 回 值:无*/
void LCD_WriteCommand(unsigned char Command)
{LCD_RS=0;LCD_RW=0;LCD_DataPort=Command;LCD_EN=1;LCD_Delay50us();LCD_EN=0;LCD_Delay50us();
}/*** 函 数:LCD1602写数据* 参 数:Data 要写入的数据* 返 回 值:无*/
void LCD_WriteData(unsigned char Data)
{LCD_RS=1;LCD_RW=0;LCD_DataPort=Data;LCD_EN=1;LCD_Delay50us();LCD_EN=0;LCD_Delay50us();
}/*** 函 数:LCD1602设置光标位置* 参 数:Line 行位置,范围:1~2* 参 数:Column 列位置,范围:1~16* 返 回 值:无*/
void LCD_SetCursor(unsigned char Line,unsigned char Column)
{if(Line==1){LCD_WriteCommand(0x80|(Column-1));}else if(Line==2){LCD_WriteCommand(0x80|(Column-1+0x40));}
}/*** 函 数:LCD1602初始化函数* 参 数:无* 返 回 值:无*/
void LCD_Init()
{LCD_WriteCommand(0x38); //八位数据接口,两行显示,5*7点阵LCD_WriteCommand(0x0C); //显示开,光标关,闪烁关LCD_WriteCommand(0x06); //数据读写操作后,光标自动加一,画面不动LCD_WriteCommand(0x01); //光标复位,清屏LCD_Delay2ms(); //清屏指令执行需要较长时间,需要较长的延时
}/*** 函 数:在LCD1602指定位置上显示一个字符* 参 数:Line 行位置,范围:1~2* 参 数:Column 列位置,范围:1~16* 参 数:Char 要显示的字符* 返 回 值:无*/
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char)
{LCD_SetCursor(Line,Column);LCD_WriteData(Char);
}/*** 函 数:在LCD1602指定位置开始显示所给字符串* 参 数:Line 起始行位置,范围:1~2* 参 数:Column 起始列位置,范围:1~16* 参 数:String 要显示的字符串* 返 回 值:无*/
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String)
{unsigned char i;LCD_SetCursor(Line,Column);for(i=0;String[i]!='\0';i++){LCD_WriteData(String[i]);}
}/*** 函 数:返回值=X的Y次方*/
int LCD_Pow(int X,int Y)
{unsigned char i;int Result=1;for(i=0;i<Y;i++){Result*=X;}return Result;
}/*** 函 数:在LCD1602指定位置开始显示所给数字* 参 数:Line 起始行位置,范围:1~2* 参 数:Column 起始列位置,范围:1~16* 参 数:Number 要显示的数字,范围:0~65535* 参 数:Length 要显示数字的长度,范围:1~5* 返 回 值:无*/
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{unsigned char i;LCD_SetCursor(Line,Column);for(i=Length;i>0;i--){LCD_WriteData(Number/LCD_Pow(10,i-1)%10+'0');}
}/*** 函 数:在LCD1602指定位置开始以有符号十进制显示所给数字* 参 数:Line 起始行位置,范围:1~2* 参 数:Column 起始列位置,范围:1~16* 参 数:Number 要显示的数字,范围:-32768~32767* 参 数:Length 要显示数字的长度,范围:1~5* 返 回 值:无*/
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length)
{unsigned char i;unsigned int Number1;LCD_SetCursor(Line,Column);if(Number>=0){LCD_WriteData('+');Number1=Number;}else{LCD_WriteData('-');Number1=-Number;}for(i=Length;i>0;i--){LCD_WriteData(Number1/LCD_Pow(10,i-1)%10+'0');}
}/*** 函 数:在LCD1602指定位置开始以十六进制显示所给数字* 参 数:Line 起始行位置,范围:1~2* 参 数:Column 起始列位置,范围:1~16* 参 数:Number 要显示的数字,范围:0~0xFFFF* 参 数:Length 要显示数字的长度,范围:1~4* 返 回 值:无*/
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{unsigned char i,SingleNumber;LCD_SetCursor(Line,Column);for(i=Length;i>0;i--){SingleNumber=Number/LCD_Pow(16,i-1)%16;if(SingleNumber<10){LCD_WriteData(SingleNumber+'0');}else{LCD_WriteData(SingleNumber-10+'A');}}
}/*** 函 数:在LCD1602指定位置开始以二进制显示所给数字* 参 数:Line 起始行位置,范围:1~2* 参 数:Column 起始列位置,范围:1~16* 参 数:Number 要显示的数字,范围:0~1111 1111 1111 1111* 参 数:Length 要显示数字的长度,范围:1~16* 返 回 值:无*/
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{unsigned char i;LCD_SetCursor(Line,Column);for(i=Length;i>0;i--){LCD_WriteData(Number/LCD_Pow(2,i-1)%2+'0');}
}/*** 函 数:LCD1602的光标复位,清屏* 参 数:无* 返 回 值:无*/
void LCD_Clear(void)
{LCD_WriteCommand(0x01);LCD_Delay2ms();
}/*** 函 数:LCD1602设置光标是否显示* 参 数:State 光标状态,1:显示,0:不显示* 返 回 值:无*/
void LCD_IsShowCursor(unsigned char State)
{if(State){LCD_WriteCommand(0x0E);}else{LCD_WriteCommand(0x0C);}
}
2、矩阵按键
h文件
#ifndef __MATRIXKEYSCAN_H__
#define __MATRIXKEYSCAN_H__unsigned char MatrixKey(void);
void MatrixKey_Tick(void);#endif
c文件
#include <REGX52.H>#define Matrix_Port P1 //矩阵按键接口unsigned char KeyNumber;/*** 函 数:获取矩阵按键键码* 参 数:无* 返 回 值:按下按键的键码,范围:0~48,0表示无按键按下* 说 明:在下一次检测按键之前,第二次获取键码一定会返回0*/
unsigned char MatrixKey(void)
{unsigned char KeyTemp=0;KeyTemp=KeyNumber;KeyNumber=0;return KeyTemp;
}/*** 函 数:检测当前按下的按键,无消抖及松手检测* 参 数:无* 返 回 值:按下按键的键值,范围:0~16,无按键按下时返回值为0*/
unsigned char Key_GetState()
{unsigned char KeyValue=0;unsigned char i=0;Matrix_Port=0x0F; //给所有行赋值0,列全为1i=2;while(i--); //适当延时,延时5usif(Matrix_Port!=0x0F){Matrix_Port=0x0F; //测试列i=2;while(i--);switch(Matrix_Port) //所有行拉低,检测哪一列按下{case 0x07:KeyValue=1;break;case 0x0B:KeyValue=2;break;case 0x0D:KeyValue=3;break;case 0x0E:KeyValue=4;break;default:break;}Matrix_Port=0xF0; //测试行i=2;while(i--);switch(Matrix_Port) //所有列拉低,检测哪一行按下{case 0x70:KeyValue=KeyValue;break;case 0xB0:KeyValue=KeyValue+4;break;case 0xD0:KeyValue=KeyValue+8;break;case 0xE0:KeyValue=KeyValue+12;break;default:break;}}else{KeyValue=0;}return KeyValue;
}/*** 函 数:矩阵按键驱动函数,在中断中调用* 参 数:无* 返 回 值:无*/
void MatrixKey_Tick(void)
{static unsigned char NowState,LastState;static unsigned int KeyCount;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;case 9:KeyNumber=9;break;case 10:KeyNumber=10;break;case 11:KeyNumber=11;break;case 12:KeyNumber=12;break;case 13:KeyNumber=13;break;case 14:KeyNumber=14;break;case 15:KeyNumber=15;break;case 16:KeyNumber=16;break;default:break;}}//如果上个时间点按键按下,这个时间点按键还是按下,则是长按if(LastState && NowState){KeyCount++;if(KeyCount%50==0) //定时器中断函数中每隔20ms检测一次按键{ //长按后每隔1s返回一次长按的键码if (LastState== 1 && NowState== 1){KeyNumber=17;}else if(LastState== 2 && NowState== 2){KeyNumber=18;}else if(LastState== 3 && NowState== 3){KeyNumber=19;}else if(LastState== 4 && NowState== 4){KeyNumber=20;}else if(LastState== 5 && NowState== 5){KeyNumber=21;}else if(LastState== 6 && NowState== 6){KeyNumber=22;}else if(LastState== 7 && NowState== 7){KeyNumber=23;}else if(LastState== 8 && NowState== 8){KeyNumber=24;}else if(LastState== 9 && NowState== 9){KeyNumber=25;}else if(LastState==10 && NowState==10){KeyNumber=26;}else if(LastState==11 && NowState==11){KeyNumber=27;}else if(LastState==12 && NowState==12){KeyNumber=28;}else if(LastState==13 && NowState==13){KeyNumber=29;}else if(LastState==14 && NowState==14){KeyNumber=30;}else if(LastState==15 && NowState==15){KeyNumber=31;}else if(LastState==16 && NowState==16){KeyNumber=32;}}}else{KeyCount=0;}//如果上个时间点按键按下,这个时间点按键未按下,则是松手瞬间if(NowState==0){switch(LastState){case 1:KeyNumber=33;break;case 2:KeyNumber=34;break;case 3:KeyNumber=35;break;case 4:KeyNumber=36;break;case 5:KeyNumber=37;break;case 6:KeyNumber=38;break;case 7:KeyNumber=39;break;case 8:KeyNumber=40;break;case 9:KeyNumber=41;break;case 10:KeyNumber=42;break;case 11:KeyNumber=43;break;case 12:KeyNumber=44;break;case 13:KeyNumber=45;break;case 14:KeyNumber=46;break;case 15:KeyNumber=47;break;case 16:KeyNumber=48;break;default:break;}}}
3、定时器0
h文件
#ifndef __TIMER0_H__
#define __TIMER0_H__void Timer0_Init(void);#endif
c文件
#include <REGX52.H>/*** 函 数:定时器0初始化* 参 数:无* 返 回 值:无*/
void Timer0_Init(void)
{
// AUXR&=0x7F; //定时器时钟12T模式(STC89C52RC是12T单片机,无需设置)TMOD&=0xF0; //设置定时器模式(高四位不变,低四位清零)TMOD|=0x01; //设置定时器模式(通过低四位设为16位不自动重装)TL0=0x00; //设置定时初值,定时10ms,12T@11.0592MHzTH0=0xDC; //设置定时初值,定时10ms,12T@11.0592MHzTF0=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=0x00; //设置定时初值,定时10ms,12T@11.0592MHzTH0=0xDC; //设置定时初值,定时10ms,12T@11.0592MHzT0Count++;if(T0Count>=1000){T0Count=0;}
}
*/
四、主函数
main.c
/*by甘腾胜@20250423
【效果查看/操作演示】B站搜索“甘腾胜”或“gantengsheng”查看
【单片机】STC89C52RC
【频率】12T@11.0592MHz
【外设】LCD1602、矩阵按键
【简单的原理分析】https://blog.csdn.net/gantengsheng/article/details/143581157
【注意】点阵屏旁边的跳线帽接三根排针中的右边两根
【操作】S1 S2 S3 S4S5 S6 S7 S8S9 S10 S11 S12S13 S14 S15 S16S1~S9:数字1~9
S10:数字0
S11:清空/返回
S12:确认键(1)输入错误可按S11(松手瞬间)清空后重新输入
(2)按S12(松手瞬间)表示确认完成输入
(3)猜对数字后按S11(松手瞬间)返回设置范围的界面
*/#include <REGX52.H> //51单片机头文件
#include "LCD1602.h" //LCD1602液晶显示屏
#include "MatrixKeyScan.h" //矩阵按键
#include "Timer0.h" //定时器0
#include <STDLIB.H> //随机函数unsigned char KeyNum; //存储获得的键码
unsigned char Mode; //模式,0:显示游戏英文名,1:设置数字范围界面,2:猜数字过程,3:猜对后的屏幕显示
unsigned char OnceFlag=1; //某些前提下只执行一次的标志(各模式中用来判断是否要初始化),1:执行,0:不执行
unsigned char InputLength; //输入位数,范围:1~4
unsigned char LastInputLength; //上一次的输入位数,范围:1~4,用来判断是否有按下数字键0~9
bit ConfirmFlag; //确认按键按下的标志,1:确认按键按下,0:确认按键未按下
unsigned char NowNumber; //刚刚输入的数字按键对应的数字,范围:0~9
bit SetMinFlag; //正在设置数字范围最小值的标志,1:正在设置,0:没有在设置
bit SetMaxFlag; //正在设置数字范围最大值的标志,1:正在设置,0:没有在设置
unsigned int MinNumber; //所设置的数字范围的最小值,范围:1~9997
unsigned int MaxNumber; //所设置的数字范围的最大值,范围:3~9999
unsigned char MinLength; //最小值的位数,范围:1~4
unsigned char MaxLength; //最大值的位数,范围:1~4
bit CheckFlag; //设置完范围后检查范围是否合理的标志,1:检查,0:不检查
bit ReenterFlag; //重新输入的标志,1:重新输入,0:不重新输入
unsigned int PlayerNumber; //玩家输入的数字,范围:2~9998
unsigned char PlayerLength; //玩家输入数字的位数,范围:1~4
unsigned int RandomNumber; //产生的随机数,范围:MinNumber<RandomNumber<MaxNumber
unsigned char RandomLength; //随机数的位数,范围:1~4
unsigned int GuessTimes; //玩家猜测的次数
unsigned char GuessLength; //玩家猜测的次数的位数,范围:1~4/*** 函 数:延时函数,延时约xms毫秒* 参 数:xms 延时的时间,范围:0~65535* 返 回 值:无*/
void Delay(unsigned int xms) //12T@11.0592MHz
{unsigned char i,j;while(xms--){i=2;j=199;do{while(--j);}while(--i);}
}/*** 函 数:主函数(有且仅有一个)* 参 数:无* 返 回 值:无* 说 明:主函数是程序执行的起点,负责执行整个程序的主要逻辑*/
void main()
{Timer0_Init(); //定时器0初始化LCD_Init(); //LCD1602初始化while(1){KeyNum=MatrixKey(); //获取键码if(KeyNum) //如果有按键按下{if(Mode==0) //如果是显示游戏名称的界面{if(KeyNum>=33 && KeyNum<=48) //如果按下任意按键(松手瞬间){Mode=1;OnceFlag=1;}}else if(Mode==1) //如果是设置数字范围的界面{if(KeyNum>=33 && KeyNum<=42) //如果按下“0~9”(松手瞬间),即按下S1~S10,S10为0{if(InputLength<4) //限制输入的最大位数为4{if(InputLength!=0 || KeyNum!=42) //第一个数字不能为0{NowNumber=(KeyNum-32)%10;InputLength++;}}}if(KeyNum==27) //如果按下S11(长按超1s){OnceFlag=1; //重置}if(KeyNum==43) //如果按下S11(松手瞬间),则清空,重新输入{ReenterFlag=1;}if(KeyNum==44) //如果按下S12(松手瞬间),表示确认{if(InputLength){ConfirmFlag=1;} //按了数字键,才能确认}}else if(Mode==2) //如果是正在猜数字{if(KeyNum>=33 && KeyNum<=42) //如果按下“0~9”(松手瞬间),即按下S1~S10,S10为0{if(InputLength<4) //限制输入的最大位数为4{if(InputLength!=0 || KeyNum!=42) //第一个数字不能为0{NowNumber=(KeyNum-32)%10;InputLength++;}}}if(KeyNum==27) //如果按下S11(长按超1s){Mode=1; //返回设置范围的界面OnceFlag=1;}if(KeyNum==43) //如果按下S11(松手瞬间),则清空,重新输入{ReenterFlag=1;}if(KeyNum==44) //如果按下S12(松手瞬间),表示确认{if(InputLength){ConfirmFlag=1;} //按了数字键,才能确认}}else if(Mode==3) //如果是猜对之后的屏幕显示{if(KeyNum==43) //如果按下S11(松手瞬间){Mode=1; //返回设置范围的界面OnceFlag=1;}}}if(Mode==0) //显示游戏名称{if(OnceFlag){OnceFlag=0; //标志置0/*初始化*/LCD_Clear(); //LCD1602清屏LCD_ShowString(1,3,"Guess Number"); //显示游戏英文名}}else if(Mode==1) //设置要猜的数字的范围{if(OnceFlag){OnceFlag=0;//初始化LastInputLength=0; //上一次输入的位数清零InputLength=0; //输入的位数清零ConfirmFlag=0; //确认标志清零CheckFlag=0; //检查的标志置0ReenterFlag=0; //重新输入的标志置0SetMinFlag=1; //正在设置最小值的标志置1SetMaxFlag=0; //正在设置最大值的标志置0MinNumber=0; //最小值清零MaxNumber=0; //最大值清零MinLength=0; //最小值的位数清零MaxLength=0; //最大值的位数清零GuessTimes=0; //猜数字的次数清零LCD_Clear(); //清屏LCD_ShowString(1,1,"Set Range");LCD_ShowString(2,1,"Min:");LCD_ShowString(2,9,"Max:"); //显示英文提示LCD_SetCursor(2,5); //设置光标在第2行第5列LCD_IsShowCursor(1); //显示光标}if(LastInputLength!=InputLength) //如果按了S1~S10(0~9),S10为0{LastInputLength=InputLength; //更新上一次的输入长度if(SetMaxFlag) //如果正在设置最大值{LCD_ShowNum(2,12+InputLength,NowNumber,1); //将所按的数字显示在屏幕上MaxNumber*=10;MaxNumber+=NowNumber; //将刚刚所按的数字键对应的数字保存到变量MaxNumber中MaxLength++; //最大数字的位数增加一}if(SetMinFlag) //如果正在设置最小值{LCD_ShowNum(2,4+InputLength,NowNumber,1); //将所按的数字显示在屏幕上MinNumber*=10;MinNumber+=NowNumber;MinLength++;}}if(ConfirmFlag || InputLength==4) //如果按了确认键,或者输入长度达到4{if(SetMaxFlag) //如果是正在设置最大值{SetMaxFlag=0; //正在设置最大值的标志置0ConfirmFlag=0; //确认的标志置0LastInputLength=0;InputLength=0;CheckFlag=1; //检查范围是否合理的标志置1}if(SetMinFlag) //如果是正在设置最小值{SetMinFlag=0; //正在设置最小值的标志置0ConfirmFlag=0;LastInputLength=0;InputLength=0;LCD_SetCursor(2,13); //光标设置在第2行第13列SetMaxFlag=1; //正在设置最大值的标志置1}}if(CheckFlag) //如果检查范围是否合理的标志为真{CheckFlag=0; //标志置0if(MinNumber+1<MaxNumber) //如果最小值要比最大值小,且最小值和最大值要至少相差2{ //说明设置得合理Mode=2; //切换到玩家输入数字猜测的界面OnceFlag=1;}else //如果不合理{OnceFlag=1; //重置,即初始化LCD_ShowString(1,14,"ERR"); //第一行后三个位置显示错误的英文的前三个字母Delay(1000); //延时1s}}if(ReenterFlag) //如果重新输入的标志为真{ReenterFlag=0; //标志置0if(SetMinFlag) //如果是正在设置最小值{LastInputLength=0;InputLength=0;MinNumber=0;MinLength=0;LCD_ShowString(2,5," "); //清空显示LCD_SetCursor(2,5); //光标设置在第2行第5列}if(SetMaxFlag) //如果是正在设置最大值{LastInputLength=0;InputLength=0;MaxNumber=0;MaxLength=0;LCD_ShowString(2,13," "); //清空显示LCD_SetCursor(2,13); //光标设置在第2行第13列}}}else if(Mode==2) //猜数字过程{if(OnceFlag){OnceFlag=0;//初始化LastInputLength=0;InputLength=0;ConfirmFlag=0;srand(TL0); //每次进入此if,以定时器低八位做随机数的种子//RandomNumber=(rand()%(MaxNumber-MinNumber-1)+MinNumber+1); //这样也可以,但是发现产生的随机数很集中//按下面这样,利用定时器0的高低八位来使计算复杂一些,产生的随机数更加随机RandomNumber=((rand()+TH0*TL0+TL0/TH0)*(TH0-TL0)%(MaxNumber-MinNumber-1)+MinNumber+1);//产生一个所设置范围内的随机数(不包含最小值和最大值)LCD_Clear();LCD_ShowString(1,1,"Range:");LCD_ShowNum(1,8,MinNumber,MinLength);LCD_ShowChar(1,8+MinLength,'~'); //显示一个右箭头→LCD_ShowNum(1,9+MinLength,MaxNumber,MaxLength);LCD_SetCursor(2,1); //设置光标在第2行第1列}if(LastInputLength!=InputLength){LastInputLength=InputLength;LCD_ShowNum(2,InputLength,NowNumber,1);PlayerNumber*=10;PlayerNumber+=NowNumber;PlayerLength++;}if(ConfirmFlag || InputLength==4){ConfirmFlag=0;LastInputLength=0;InputLength=0;GuessTimes++; //每比较一次,猜的次数增加一if(PlayerNumber==RandomNumber) //如果输入的数字跟随机数字相等{ //说明猜对了Mode=3; //切换到下一模式OnceFlag=1;}else if(PlayerNumber<RandomNumber && PlayerNumber>MinNumber) //如果输入数字小于随机数,且大于范围最小值{MinNumber=PlayerNumber;MinLength=PlayerLength;}else if(PlayerNumber>RandomNumber && PlayerNumber<MaxNumber) //如果输入数字大于随机数,且小于范围最大值{MaxNumber=PlayerNumber;MaxLength=PlayerLength;}else //如果不是以上情况,说明输入的数字不在范围内{GuessTimes--; //输入错误不算次数LCD_ShowString(2,14,"ERR"); //第2行最后三个位置显示错误的英文的前三个字母Delay(1000); //延时1sLCD_ShowString(2,14," "); //清空三个英文字母的显示}PlayerNumber=0; //玩家输入的数字清0PlayerLength=0; //玩家输入数字的位数清零LCD_ShowString(1,8," "); //第1行右半屏清空显示LCD_ShowNum(1,8,MinNumber,MinLength);LCD_ShowChar(1,8+MinLength,'~');LCD_ShowNum(1,9+MinLength,MaxNumber,MaxLength); //根据玩家输入的数字显示缩小后的范围LCD_SetCursor(2,1);LastInputLength=0;InputLength=0;LCD_ShowString(2,1," "); //清空玩家输入的数字LCD_SetCursor(2,1); //设置光标在第2行第1列}if(ReenterFlag){ReenterFlag=0;LastInputLength=0;InputLength=0;PlayerNumber=0; //玩家输入的数字清0PlayerLength=0; //玩家输入数字的位数清零LCD_ShowString(2,1," ");LCD_SetCursor(2,1);}}else if(Mode==3) //猜中之后的屏幕显示{if(OnceFlag){OnceFlag=0;LCD_Clear();LCD_ShowString(1,1,"Yes!");LCD_ShowString(1,6,"Number:");if(RandomNumber/1000){RandomLength=4;}else if(RandomNumber/100){RandomLength=3;}else if(RandomNumber/10){RandomLength=2;}else{RandomLength=1;}LCD_ShowNum(1,13,RandomNumber,RandomLength);LCD_ShowString(2,1,"Times:");if(GuessTimes/1000){GuessLength=4;}else if(GuessTimes/100){GuessLength=3;}else if(GuessTimes/10){GuessLength=2;}else{GuessLength=1;}LCD_ShowNum(2,7,GuessTimes,GuessLength);LCD_IsShowCursor(0);}}}
}/*** 函 数:定时器0中断函数* 参 数:无* 返 回 值:无*/
void Timer0_Routine() interrupt 1
{static unsigned char T0Count1; //定时器0计数变量TL0=0x00; //设置定时初值,定时10ms,12T@11.0592MHzTH0=0xDC; //设置定时初值,定时10ms,12T@11.0592MHzT0Count1++;if(T0Count1>=2) //每隔20ms检测一次按键{T0Count1=0;MatrixKey_Tick();}
}
总结
根据设置的范围产生真随机数这一块,感觉还需要改进一下。
相关文章:
基于51单片机和LCD1602、矩阵按键的小游戏《猜数字》
目录 系列文章目录前言一、效果展示二、原理分析三、各模块代码1、LCD16022、矩阵按键3、定时器0 四、主函数总结 系列文章目录 前言 用的是普中A2开发板,用到板上的矩阵按键,还需要外接一个LCD1602液晶显示屏。 【单片机】STC89C52RC 【频率】12T11.05…...
从广义线性回归推导出Softmax:理解多分类问题的核心
文章目录 引言:从回归到分类广义线性模型回顾从二分类到多分类Softmax函数的推导建模多类概率基于最大熵原理具体推导步骤Softmax函数的数学形式 Softmax回归模型参数的可辨识性 最大似然估计与交叉熵损失似然函数交叉熵损失梯度计算 Softmax回归的实现要点数值稳定…...
传奇各版本迭代时间及内容变化,屠龙/嗜魂法杖/逍遥扇第一次出现的时间和版本
【早期经典版本】 1.10 三英雄传说:2001 年 9 月 28 日热血传奇正式开启公测,这是传奇的第一个版本。游戏中白天与黑夜和现实同步,升级慢,怪物爆率低,玩家需要靠捡垃圾卖金币维持游戏开销,遇到高级别法师…...
云计算-私有云-私有云运维开发
三、私有云运维开发(15) 使用自动化运维工具 Ansible 完成系统的自动化部署与管理。 基于 OpenStack APIs 与SDK,开发私有云运维程序 1.OpenStack Python运维开发:实现镜像管理(7分) 编写Python代…...
hadoop存储数据文件原理
Hadoop是一个开源的分布式计算框架,可以用于存储和处理大规模数据集。Hadoop的存储系统基于Hadoop Distributed File System(HDFS),它的主要原理如下: 数据切块:当用户向HDFS中存储一个文件时,该…...
spring2.x详解介绍
一、核心架构升级 Spring 2.x 是 Spring 框架的重要迭代版本(2006-2009年间发布),其核心改进体现在 模块化设计 和 轻量化配置 上。相较于 1.x 版本,2.x 通过以下方式重构了架构: XML Schema 支持:弃用 D…...
探索Grok-3的高级用法:功能与应用详解
引言 随着人工智能技术的迅猛发展,xAI推出的Grok-3模型以其卓越的性能和创新功能,成为AI领域的新标杆。Grok-3不仅在计算能力上实现了十倍提升,还引入了多种高级模式和实时数据处理能力,适用于学术研究、技术分析、市场洞察等多场…...
PyTorch_张量转换为numpy数组
使用 tensor.numpy 函数可以将张量转换为 ndarray 数组,但是共享内存,可以使用 copy 函数避免共享。共享内存会导致张量或者numpy中的其中一个更改后,另外一个会受到影响。 代码 import torch # 张量转换为 numpy 数组 def test01():data_te…...
什么是“原子变量”?
原子变量(std::atomic)在C++中是一个非常关键的机制,特别是在多线程编程中保持数据安全和避免竞争条件。它的设计目标就是让一段操作在多线程环境下变得“原子性”,即不可被中断,保证操作的完整与一致。 一、什么是“原子变量”? 简单来说: 普通变量:在多线程环境中,…...
[Linux开发工具]gcc/g++
C语言文件编译运行 gcc code.c -o mycode gcc -o mycode code.c 预处理 汇编 编译 链接 预处理(进行宏替换/去注释/条件编译/头文件展开) gcc -E code.c -o code.i -E ->从现在开始进行程序的编译,当我们 的程序预处理完毕后,翻译工作,就停下来 code.i预处理之后的结果 …...
【Mytais系列】Type模块:类型转换
MyBatis 的 类型系统(Type System) 是框架处理 Java 类型与数据库类型之间映射的核心模块,它通过 类型处理器(TypeHandler)、类型别名(TypeAlias) 和 类型转换器 等机制,实现了数据库…...
C++类_虚基类
在 C 里,虚基类是用来解决菱形继承问题的。菱形继承问题是指当一个派生类从两个或更多基类派生,而这些基类又从同一个基类派生时,派生类会包含多份间接基类的数据副本,这可能会引发数据冗余和二义性问题。虚基类可以保证在派生类中…...
【自然语言处理与大模型】使用Xtuner进行模型合并与导出
在上一篇文章中我为大家介绍了Xtuner框架如何进行QLoRA微调,这个框架在微调过后会得到适配器权重文件,它的后缀是.pth。但原模型都是huggingface模型,其后缀是safetensors。所以呢我们在使用这个框架导出模型之前要合并,合并之前要…...
数据结构4.0
大家好,今天是栈的知识点~ 目录 一、栈的概念 1.0 栈的概念 2.0 概念区分 二、栈的方法 1.0 MyStack方法: 2.0 将元素压入栈顶 3.0 移除并返回栈顶元素 4.0 返回栈顶元素但不移除 三、栈的题目 1.0括号匹配 2.0逆波兰表达式求值 3.0 出栈…...
SMT贴片检验标准核心要点与实施规范
内容概要 SMT贴片检验标准是确保电子产品组装质量的核心框架,其核心要点覆盖从原材料到成品的全流程工艺控制。该标准体系以焊点质量、元件定位精度及锡膏印刷检测为技术基线,结合IPC-A-610电子组装验收规范,对PCBA加工中的缺陷类型、判定阈…...
探索 C++23 std::to_underlying:枚举底层值获取的利器
文章目录 引言基本概念作用使用示例与之前方法的对比在 C23 中的意义总结 引言 在 C 的发展历程中,每一个新版本都带来了许多令人期待的新特性和改进,以提升代码的安全性、可读性和可维护性。C23 作为其中的一个重要版本,也不例外。其中&…...
PyTorch学习之张量(Tensor)(一)
1. 张量的基本概念 1.1. 定义与特性 张量是PyTorch中最基础的数据结构,可视为多维数组的泛化形式,支持标量(0维)、向量(1维)、矩阵(2维)及更高维度的数据存储。其核心特性包括&…...
理解数学概念——支集(支持)(support)
1. 支集(support)的定义 在数学中,一个实函数 f 的支集(support)是函数的不被映射到 0 的元素域(即定义域)的子集。若 f 的(定义)域(domain)是一个拓扑空间(即符合拓扑的集合),则 f 的支集则定义为包含( f 的元素域中)不被映射到0的所有点之最小闭集…...
Python 部分内置函数及其用法详解
在 Python 编程的世界里,内置函数是我们强大的 “工具箱”,它们提供了丰富而便捷的功能,帮助我们高效地完成各种任务。本文将带你深入了解这些常用内置函数及其用法,通过简单易懂的实例,让你轻松掌握它们。 一、数据类…...
[蓝桥杯真题题目及解析]2025年C++b组
移动距离(填空)** 小明初始在二维平面的原点,他想前往坐标 (233,666)。在移动过程中,他只能采用以下两种移动方式,并且这两种移动方式可以交替、不限次数地使用: 水平向右移动,即沿着 x 轴正方…...
yolov5 train笔记4 roboflow
How to Train a YOLOv5 Model On a Custom Dataset Sign in to Roboflow https://www.youtube.com/watch?vr3Ke7ZEh2Qo 他的ai懂中文的 还是得训练,明天再搞 https://www.youtube.com/watch?vEmYCpbFQ5wo&t2s 很香但是我没有马内...
工作记录 2015-06-01
工作记录 2015-06-01 序号 工作 相关人员 1 修改了FnetFax 修改了iConverter 修改了iCDA 郝 FNSR识别引擎 统计了最近几个星期0223医院的打字和录音的时间比。上周的比值是3.42,是近8个星期中最低的值。和05/03/2015 - 05/09/2015的3.74相比,下降…...
创意Python爱心代码分享
在代码的世界里,程序员以独特方式书写浪漫。他们精心打造的一个个 demo,宛如熠熠星辰。这些 demo 不仅是技术结晶,更饱含对编程的热爱与执着。从简洁的算法示例到复杂的系统雏形,每一行代码都凝聚着思考与智慧。它们被无私分享&am…...
【RAG】向量?知识库的底层原理:向量数据库の技术鉴赏 | HNSW(导航小世界)、LSH、K-means
一、向量化表示的核心概念 1.1 特征空间与向量表示 多维特征表示:通过多个特征维度(如体型、毛发长度、鼻子长短等)描述对象,每个对象对应高维空间中的一个坐标点,来表示狗这个对象,这样可以区分出不同种…...
降维大合集
1. 主成分分析(PCA,Principal Component Analysis) 基本原理 PCA 是一种线性降维方法,其核心思想是: 找到数据中方差最大的方向(称为主成分),并将数据投影到这些方向上。 利用正交变…...
AWS上构建基于自然语言和LINDO API的线性规划与非线性规划的优化计算系统
我想要实现一个通过使用C#、Semantic Kernel库、OpenAI GPT 4的API和附件文档里提到的LINDO API 15.0实现通过中文自然语言提示词中包含LATEX代码输入到系统,通过LINDO API 15.0线性规划与非线性规划的优化计算程序输出计算结果和必要步骤的应用,结果用中…...
26考研 | 王道 | 计算机网络 | 第三章 数据链路层
26考研 | 王道 | 第三章 数据链路层 数据链路层所处的地位 数据链路层 使用 物理层 提供的“比特传输”服务数据链路层 为 网络层 提供服务,将网络层的 IP数据报(分组)封装成帧,传输给下一个相邻结点物理链路:传输介质…...
学习黑客资产威胁分析贴
第一天作业: 完成作业奖励: 🎁 奖励 1 ── Week 2《Web 渗透手册》预览 Day主题关键目标练手靶场 / 工具1HTTP 基础 & Burp 入门抓包、改包、重放PortSwigger Academy:“HTTP basics”2SQL 注入原理手工注入 sqlmapDVWA →…...
CSS元素动画篇:基于当前位置的变换动画(合集篇)
CSS元素动画篇:基于当前位置的变换动画(合集篇) 前言位移效果类元素动画水平抖动效果效果预览代码实现 垂直抖动效果效果预览代码实现 摇头动画效果效果预览代码实现 点头动画效果效果预览代码实现 旋转效果类元素动画摇摆动画效果效果预览代…...
Spring 容器相关的核心注解
以下是 Spring 容器中用于 Bean 管理、依赖注入、配置控制 的关键注解,按功能分类说明: 1. Bean 声明与注册 注解作用示例Component通用注解,标记一个类为 Spring Bean(自动扫描注册) Compo…...
经典算法 最小生成树(prim算法)
最小生成树 题目描述 给定一个 n 个点 m 条边的无向图,图中可能存在重边和自环,边权可能为负数。 求最小生成树的树边权重之和。如果最小生成树不存在,则输出 impossible。 给定一张边带权的无向图 G (V, E),其中:…...
机器学习中的分类和回归问题
1. 分类问题 机器学习中的分类问题是一种监督学习任务,其核心目标是将数据样本分配到预定义的离散类别中,例如判断邮件是否为垃圾邮件、识别图像中的物体类型等。 分类通过已知标签的训练数据(如带类别标注的样本)学习特征与类别…...
pip命令
安装&卸载 -- 安装numpy pip install numpy1.26.4 -- 从索引安装(自定义源) pip install package_name --index-url https://custom_url -- 安装本地文件或目录 pip install /path/to/package.whl pip install D:\Downloads\transformers-4.40.0-py…...
n8n工作流自动化平台的实操:Cannot find module ‘iconv-lite‘
解决问题: 1.在可视化界面,执行const iconv require(iconv-lite);,报Cannot find module iconv-lite [line 2]错误; 查看module的路径 进入docker容器 #docker exec -it n8n /bin/sh 构建一个test.js,并写入如何代码 vi tes…...
AIGC时代——语义化AI驱动器:提示词的未来图景与技术深潜
文章目录 一、技术范式重构:从指令集到语义认知网络1.1 多模态语义解析器的进化路径1.2 提示词工程的认知分层 二、交互革命:从提示词到意图理解2.1 自然语言交互的认知进化2.2 专业领域的认知增强 三、未来技术图谱:2025-2030演进路线3.1 20…...
基于Springboot高校网上缴费综合务系统【附源码】
基于Springboot高校网上缴费综合务系统 效果如下: 系统登陆页面 个人中心页面 论坛交流页面 发表评论页面 付款页面 教师缴费页面 新增缴费类型页面 审核页面 研究背景 随着高校信息化建设进程的加速,传统手工缴费模式因效率低、错误率高、管理成本高…...
返回倒数第k个节点题解
这题要用到快慢指针的思想。 1.定义两个指针,一个快指针,一个慢指针,初始都指向头结点 2.先让快指针往后走k步,也就是移动k个节点,这个时候快指针比慢指针领先k 3.现在让快慢指针同时往后移动,两指针之间…...
《操作系统精髓与设计原理》第4章课后题答案-线程、对称多处理器和微内核
1.表3.5列出了在一个没有线程的操作系统中进程控制块的基本元素。对于多线程系统,这些元素中哪些可能属于线程控制块,哪些可能属于进程控制块? 对于不同的系统来说通常是不同的,但一般来说,进程是资源的所有者…...
《ATPL地面培训教材13:飞行原理》——第4章:亚音速气流
翻译:刘远贺;工具:Cursor & Claude 3.7;过程稿 第4章:亚音速气流 目录 翼型术语气流基础二维气流总结习题答案 翼型术语 翼型 一种能够以较高效率产生升力的特殊形状。 弦线 连接翼型前缘和后缘曲率中心的直…...
5月3日星期六今日早报简报微语报早读
5月3日星期六,农历四月初六,早报#微语早读。 1、五一假期多地政府食堂对外开放:部分机关食堂饭菜“秒没”; 2、2025年五一档电影新片票房破3亿; 3、首日5金!中国队夺得跳水世界杯总决赛混合团体冠军&…...
2024 虚拟电厂与大电网三道防线的关系探讨【附全文阅读】
本文围绕虚拟电厂与大电网三道防线展开探讨。大电网三道防线包括第一道防线的预防性控制和继电保护、第二道防线的稳控系统、第三道防线的失步解列及频率电压紧急控制装置 ,新型电力系统建设对第三道防线带来频率稳定等挑战。当前新型配电网第三道防线建设存在问题&…...
【c++】模板详解
目录 泛型编程模板的使用函数模板函数模板的本质函数模板的实例化显式实例化隐式实例化 函数模板的模板参数的匹配原则 类模板类模板的本质类模板的实例化 非类型模板参数模板特化函数模板特化类模板特化类模板全特化类模板偏特化(半特化) 模板分离编译t…...
【Linux】驱动开发方法
使用Petalinux学习驱动开发时的一些经验。 部分图片和经验来源于网络,若有侵权麻烦联系我删除,主要是做笔记的时候忘记写来源了,做完笔记很久才写博客。 专栏目录:记录自己的嵌入式学习之路-CSDN博客 目录 1 基础——字符设备驱动 1.1 分配设备号(驱动入口使用)…...
BUUCTF——禁止套娃
BUUCTF——禁止套娃 进入靶场 一个近乎空白的页面 看一下框架 没什么有用的信息,扫个目录吧 只扫出来给flag.php,但是0B,估计又是个空网站 拼接访问一下 果然又是什么都没有 没有突破口 githack找找看看也没有源码吧 <?php include …...
Spring MVC @RequestBody 注解怎么用?接收什么格式的数据?
RequestBody 注解的作用 RequestBody 将方法上的参数绑定到 HTTP 请求的 Body(请求体)的内容上。 当客户端发送一个包含数据的请求体(通常在 POST, PUT, PATCH 请求中)时,RequestBody 告诉 Spring MVC 读取这个请求体…...
线性DP(动态规划)
线性DP的概念(视频) 学习线性DP之前,请确保已经对递推有所了解。 一、概念 1、动态规划 不要去看网上的各种概念,什么无后效性,什么空间换时间,会越看越晕。从做题的角度去理解就好了,动态规划…...
Qt中实现工厂模式
在Qt中实现工厂模式可以通过多种方式,具体选择取决于需求和场景。以下是几种常见的实现方法: 1. 简单工厂模式通过一个工厂类根据参数创建不同对象。cppclass Shape {public: virtual void draw() 0; virtual ~Shape() default;};class Circle : publ…...
基于 Dify + vLLM插件 + Qwen3 构建问答机器人Docker版
前提条件 硬件要求: 推荐 NVIDIA GPU (至少 16GB 显存,Qwen3 可能需要更多) 至少 32GB 内存 足够的存储空间 (Qwen3 模型文件较大) 软件要求: Docker 和 Docker Compose Python 3.8 CUDA 和 cuDNN (与你的 GPU 兼容的版本) 安装步骤…...
【Linux】Linux应用开发小经验
基于Petalinux工具链的Linux应用开发小经验,未完待续... 部分图片和经验来源于网络,若有侵权麻烦联系我删除,主要是做笔记的时候忘记写来源了,做完笔记很久才写博客。 专栏目录:记录自己的嵌入式学习之路-CSDN博客 目录…...
第39课 绘制原理图——绘制命令在哪里?
绘制原理图符号的命令在哪里? 在新建完原理图之后,我们就可以在原理图上绘制各种相关的符号了。 我们基本会从以下的两个地方,找到绘制各种符号的命令: 菜单栏中的“放置”菜单; 悬浮于设计窗口中的快速工具条 在初…...