【学习总结】使用STM32F103C8T6的限时问答机(systick,LCD1602,矩阵键盘,延时函数等的一些使用)

【前言】【新手向】
从大一来学校就接触了stm32系列的产品,在嵌入式设计,控制的方面有很大的兴趣,到现在来说,入学已经有三个月了,脱离了高考的体制,学习变得自由起来,但是由于对单品机完全没一点点基础,所以一边看【正点原子】的开发指南一边学C语言,可以说真的是从有到无,一步步积累,一点点建立起来的知识。这样的自学其实是蛮有难度的,也与以往的自学有所不同,这时对一个完全未曾了解过的领域的自学,首先在体系上是未知,其次因为stm32本身的难度就大(对于我这样的小小白来说),基本所有的专业词汇都是边查意思,边猜意思,一知半解的过来,在后面的一些应用上才慢慢反应他的意思。可以说对自己的三个月的评价还行。毕竟是第一次用32做东西,有什么问题还望不吝赐教。
【正文】
接下来将系统的总结一下,这次问答机的完成以及在其期间的学习内容。
【1.实现的功能】
1.能在屏幕上随机显示一个2位数或者是3位数的加或减的算式;
2.能用键盘读取用户输入的数值,并在屏幕上显示,同时判断输入的数值是否正确,并在屏幕上显示正误,并在2秒后显示下一个算式;
3.可设置1至9秒的限时回答时长;
4.在屏幕上显示回答问题正确和错误的次数;
5.可开启回答正确下一次限时时间缩短的模式;
6.键盘的具体功能:
【学习总结】使用STM32F103C8T6的限时问答机(systick,LCD1602,矩阵键盘,延时函数等的一些使用)_第1张图片
键盘的各个键的含义如下:
a.S为开关键,可以将输入界面跳转到欢迎界面
b.B为退格键可以清除屏幕上的光标的前一位,同时将存储的数值清除一位
c.D为清除键,可以将当前的输入界面的内容清零同时将储蓄的数值删除
d.C为确认键,将当前的输入值送去比较,得出结果,可不用等到限定时间到时后的自动比较
e.其余的键为对应键值
【2.功能的实现】
在显示模块上选择用的是LCD1602,使用比较简单而且满足需求;输入的按键选择使用矩阵键盘;在限时回答的方面使用Systick中断;因为MDK没有自带time.h库所以在随机数的产生上使用ADC模式读取空气中的噪声来产生随机数中的,这一部分的代码主要还是在网上抄来的,稍加修改就可以达到理想的效果;而对于延时函数的实现上也找了一些方法。
【2.1矩阵键盘】
【学习总结】使用STM32F103C8T6的限时问答机(systick,LCD1602,矩阵键盘,延时函数等的一些使用)_第2张图片
使用4*4的矩阵键盘,硬件连接问题不大,GPIO口使用的是PB15-12以及PB7-4这8个口来做行扫描以及列读取
具体的代码如下:
在Matrixkeyboard中一共有三个函数,分别是相关引脚初始化函数 void Key_Init(void) ,键值读取函数 u8 Key_Value_Scan(void) 以及按键是否按下的判定函数 int key_press(void) 这个函数在程序有着至关重要的作用

/*Matrixkeyboard.h*/
#ifndef __KEYBOARD_H
#define __KEYBOARD_H	 

void Key_Init(void);
u8 Key_Value_Scan(void);
int key_press(void);
 
#endif
/*Matrixkeyboard.c*/
/***********************************/
// 1, 使用的引脚口是GPIOB PB4 ~ PB7 PB12 ~PB15
// 2.  GPIOB | PB12 到 PB15 固定为推挽输出
// 3.  GPIOB | PB4 到 PB7 固定为下拉输入
// 4.  实现效果:
// 	   无按键被按下时;PB4 - PB7 为0,为低电平状态
// 	   有按键被按下时:PB12 - PB15 相应的引脚显示为高电平
/************************************/
 
#include "stm32f10x.h"       //导入官方头文件
#include "Systick.h"     //导入延时函数
#include "Matrix_keyboard.h"

void Key_Init(void)  //声明初始化矩阵键盘初始化函数
{
	GPIO_InitTypeDef GPIO_InitStructure;  //声明GPIO结构体,用于引脚定义
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_AFIO,ENABLE); //开启GPIOB时钟,使能时钟,
 
 /***初始化中复用端口“RCC_APB2Periph_AFIO”必须使能才可以关闭JTAG,RCC_APB2Periph_GPIOA可以在这儿使能也可以不在这儿使能***/
 	GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);     //禁用JTAG
 
 
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;    //定义推挽输出,对应的引脚是PB12 - PB15
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;   //速率 50MHz 标准
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12|GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15;  //引脚为 PB12 - PB15
	GPIO_Init(GPIOB,&GPIO_InitStructure);  //初始化
 
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;      //定义下拉输入, 对应的引脚为 PB4 - PB7
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7;
	GPIO_Init(GPIOB,&GPIO_InitStructure); //初始化
}

u8 Key_Value_Scan(void)  //定义矩阵键盘的返回值,返回值对应相关功能,
{
	u8 KeyValue = 0;  //KeyValue返回的按键的对应(char)数值
	GPIO_Write(GPIOB, ((GPIOB->ODR & 0x0fff) | 0x1000 ));  //要让 PB15 - PB12 输出二进制的 0001 进行第四行的扫描
	switch(GPIOB->IDR & 0x00f0)  //对应的 PB4 - PB7 的值进行判断,输出不同的按键值
	{
		case 0x0010:KeyValue = 'C';  //对应于开关键switch确认输入值,并进行比较confirm
		break;
 
		case 0x0020:KeyValue = '-';	//负号
		break;
 
		case 0x0040:KeyValue = '0';
		break;
 
		case 0x0080:KeyValue = '0';
		break;
	}
 
	GPIO_Write(GPIOB, ((GPIOB->ODR & 0x0fff) | 0x2000));  //要让 PB15 - PB12 输出二进制的 0010 进行第三行的扫描
	switch(GPIOB->IDR & 0x00f0)  //对应的 PB4 - PB7 的值进行判断,输出不同的按键值
	{
		case 0x0010:KeyValue = 'D';  //清除输入delete
		break;
		
		case 0x0020:KeyValue = '3';
		break;
		
		case 0x0040:KeyValue = '2';
		break;
 
		case 0x0080:KeyValue = '1';
		break; 
	}
 
	GPIO_Write(GPIOB, ((GPIOB->ODR & 0x0fff) | 0x4000));  //要让 PB15 - PB12 输出二进制的 0100 进行第二行的扫描
	switch(GPIOB->IDR & 0x00f0)  //对应的 PB4 - PB7 的值进行判断,输出不同的按键值
	{
		case 0x0010:KeyValue = 'B'; //对应于退格键backspace
		break;
		
		case 0x0020:KeyValue = '6';
		break;
		
		case 0x0040:KeyValue = '5';
		break;
 
		case 0x0080:KeyValue = '4';
		break; 
	}
 
	GPIO_Write(GPIOB, ((GPIOB->ODR & 0x0fff) | 0x8000));  //要让 PB15 - PB12 输出二进制的 1000 进行第一行的扫描
	switch(GPIOB->IDR & 0x00f0)  //对应的 PB4 - PB7 的值进行判断,输出不同的按键值
	{
		case 0x0010:KeyValue = 'S';  //对应于开关键switch
		break;
		
		case 0x0020:KeyValue = '9';  
		break;
		
		case 0x0040:KeyValue = '8';
		break;
		
		case 0x0080:KeyValue = '7';
		break; 
	}

    return KeyValue;
}

int key_press() //判断是否有键按下
{
	GPIO_Write(GPIOB,((GPIOB->ODR & 0x0fff) | 0xf000));  //先让PB15 - PB12全部输出高
	GPIOB->IDR &= 0xff0f;
	if((GPIOB->IDR & 0x00f0) == 0x0000)  //先设置 PB4 - PB7 为0,如果没有按键按下,返回值为0
	{
		return 0;
	}
	else  //有按键按下
	{
		SysTick_Delay_Ms(5);  //延时5ms去抖动
		if((GPIOB->IDR & 0x00f0) == 0x0000)  //如果延时5ms后 PB4 - PB7 又全为0, 则刚刚是抖动产生的
		{
			return 0;  //所以还是返回 0
		}
		else
		return 1;//确实被按下了
	}
}

/*
//键盘样例(硬件连接)
		     PB7 PB6 PB5 PB4
	PB15   7	   8	  9	 S
	PB14   4	   5	  6	 B
	PB13   1	   2	  3	 D
	PB12   0	   0	   -     C
*/

在程序中有这样的代码:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_AFIO,ENABLE); //使能GPIOB及AFIO时钟
 /***初始化中复用端口“RCC_APB2Periph_AFIO”必须使能才可以关闭JTAG,RCC_APB2Periph_GPIOA可以在这儿使能也可以不在这儿使能***/
 GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);     //禁用JTAG

因为在调试的过程中发现PB4上电后电平始终为高,查数据手册后发现PB4为JTRST口,是JTAG的复位端口,所以需要使能对应的AFIO,GPIO的时钟后关闭JTAG,PB4才可以当正常IO口使用。(如图)
【学习总结】使用STM32F103C8T6的限时问答机(systick,LCD1602,矩阵键盘,延时函数等的一些使用)_第3张图片
该图是调试过程中进入key_press()函数时的端口寄存器状态;在图中可以看到GPIOB->ODR的第15至第12位均为1,而在无任何按键按下的情况下,GPIOB->IDR的第7至第4位中第四位为1。

还需关注一点,这里的延时函数并未使用systick,只是while(),循环使程序空转达到延时,因为主要目的是等待硬件,所以没有必要准确的延时。

【2.2LCD1602】
在写LCD1602的驱动前一定要认真查看其相对应的指令手册以及时序图,LCD1602自带有显示字符的程序。
电路连接如图:
【学习总结】使用STM32F103C8T6的限时问答机(systick,LCD1602,矩阵键盘,延时函数等的一些使用)_第4张图片
1脚:VSS为接地;
2脚:VDD接5V电源VCC;
3脚:VO为液晶显示器对比度调整;VO是液晶显示的偏压信号,可接10K的3296精密电位器。或同样阻值的RM065/RM063蓝白可调电阻。
4脚:RS为寄存器选择,高电平时选择数据寄存器、低电平时选择指令寄存器;
5脚:R/W为读写信号线,高电平时进行读操作,低电平时进行写操作。当RS和RW共同为低电平时可以写入指令或者显示地址,当RS为低电平RW为高电平时可以读忙信号,当RS为高电平RW为低电平时可以写入数据;
6脚:E端为使能端,当E端由高电平跳变成低电平时,液晶模块执行命令;
7~14脚:D0~D7为8位双向数据线;
15脚:背光电源正极;
16脚:背光电源负极。

【学习总结】使用STM32F103C8T6的限时问答机(systick,LCD1602,矩阵键盘,延时函数等的一些使用)_第5张图片

【学习总结】使用STM32F103C8T6的限时问答机(systick,LCD1602,矩阵键盘,延时函数等的一些使用)_第6张图片
图如果不清楚网上有个比较详细的解释(出处忘记了);

一、1602里的存储器有三种:

CGROM、CGRAM、DDRAM。CGROM 保存了厂家生产时固化在LCM中的点阵型显示数据,CGRAM是留给用户自己定义点阵型显示数据的,DDRAM则是和显示屏的内容对应的。1602内部的 DDRAM有80字节,而显示屏上只有2行 ×16列,共32个字符,所以两者不完全一一对应。默认情况下,显示屏上第一行的内容对应DDRAM中80H到8FH的内容,第二行的内容对应DDRAM 中C0H到CFH的内容。DDRAM中90H到A7H、D0H到E7H的内容是不显示在显示屏上的,但是在滚动屏幕的情况下,这些内容就可能被滚动显示出来了。注:这里列举的DDRAM的地址准确来说应该是DDRAM地址+80H之后的值,因为在向数据总线写数据的时候,命令字的最高位总是为1。

二、1602使用三条控制线:

EN、RW、RS。其中EN起到类似片选和时钟线的作用,RW和RS指示了读、写的方向和内容。在读数据(或者Busy标志)期间,EN线必须保持高电平;而在写指令(或者数据)过程中,EN线上必须送出一个正脉冲。RW、RS的组合一共有四种情况,分别对应四种操作:

RS=0、RW=0——表示向LCM写入指令
RS=0、RW=1——表示读取Busy标志
RS=1、RW=0——表示向LCM写入数据
RS=1、RW=1——表示从LCM读取数据

三、LCD在使用的过程中,可以在RS=0、RW=0的情况下,向LCM写入一个字节的控制指令。使用的控制指令一共八个类别。有的类别又有几条不同的指令。具体的情况罗列在下:

①01H:清除DDRAM的所有单元,光标被移动到屏幕左上角。

②02H:DDRAM所有单元的内容不变,光标移至左上角。

③输入方式设置(EnterModeSet),这些指令规定了两个方面:一是写入一个DDRAM单元后,地址指针如何改变(加一还是减一);二是屏幕上的内容是否滚动。
04H:写入DDRAM后,地址指针减一,比如第一个字符写入8FH,则下一个字符会写入8EH;屏幕上的内容不滚动。
05H:写入DDRAM后,地址指针减一,同上一种情况;每一个字符写入以后,屏幕上的内容向右滚动一个字符位。
06H:写入DDRAM后,地址指针加一,比如第一个字符写入80H,则下一个字符会写入81H;屏幕上的内容也是不滚动。这应该是最常用的一种显示方式。
07H:写入DDRAM后,地址指针加一,同上一种情况;每一个字符写入以后,屏幕上的内容向左滚动一个字符位。

④屏幕开关、光标开关、闪烁开关。
08H、09H、0AH、0BH:关闭显示屏,实质上是不把DDRAM中的内容对应显示在屏幕上,对DDRAM的操作还是在进行的,执行这条指令,接着对 DDRAM进行写入,屏幕上没有任何内容,但是接着执行下面的某条指令,就能看到刚才屏幕关闭期间,对DDRAM操作的效果了。
0cH:打开显示屏,不显示光标,光标所在位置的字符不闪烁。
0dH:打开显示屏,不显示光标,光标所在位置的字符闪烁。
0eH:打开显示屏,显示光标,光标所在位置的字符不闪烁。
0fH:打开显示屏,显示光标,光标所在位置的字符闪烁。
关于光标的位置:光标所在的位置指示了下一个被写入的字符所处的位置,加入在写入下一个字符前没有通过指令设置DDRAM的地址,那么这个字符就应该显示在光标指定的地方。

⑤设置光标移动(本质就是AC的增加还是减少)、整体画面是否滚动。
10H:每输入一次该指令,AC就减一,对应了光标向左移动一格。整体的画面不滚动。
14H:每输入一次该指令,AC就加一,对应了光标向右移动一格。整体的画面不滚动。
18H:每输入一次该指令,整体的画面就向左滚动一个字符位。
1CH:每输入一次该指令,整体的画面就向右滚动一个字符位。画面在滚动的时候,每行的首尾是连在一起的,也就是每行的第一个字符,若左移25次,就会显示在该行的最后一格。在画面滚动的过程中,AC的值也是变化的。

⑥显示模式设定指令,设定了显示几行,显示什么样的点阵字符,数据总线占用几位。
20H:4位总线,单行显示,显示5×7的点阵字符。
24H:4位总线,单行显示,显示5×10的点阵字符。
28H:4位总线,双行显示,显示5×7的点阵字符。
2CH:4位总线,双行显示,显示5×10的点阵字符。
30H:8位总线,单行显示,显示5×7的点阵字符。
34H:8位总线,单行显示,显示5×10的点阵字符。
38H:8位总线,双行显示,显示5×7的点阵字符。这是最常用的一种模式。3CH:8位总线,双行显示,显示5×10的点阵字符。

接下来附上代码:

/*LCD1602.h*/
#ifndef __LCD1602_H
#define __LCD1602_H	 
#include "stm32f10x.h"
#define LCD_DATA_PORT GPIOA
 
//1602指令 / 数据 引脚
#define	LCD_RS_Set()	GPIO_SetBits(GPIOB, GPIO_Pin_1)
#define	LCD_RS_Clr()	GPIO_ResetBits(GPIOB, GPIO_Pin_1)
 
//1602读写引脚
#define	LCD_RW_Set()	GPIO_SetBits(GPIOB, GPIO_Pin_10)
#define	LCD_RW_Clr()	GPIO_ResetBits(GPIOB, GPIO_Pin_10)
 
//1602使能引脚
#define	LCD_EN_Set()	GPIO_SetBits(GPIOB, GPIO_Pin_11)
#define	LCD_EN_Clr()	GPIO_ResetBits(GPIOB, GPIO_Pin_11)
 
//1602数据端口	PD0~7
#define	DATAOUT(x)	GPIO_Write(LCD_DATA_PORT, x)
 
#define u8  unsigned char 
 
void GPIO_Configuration(void);
void LCD1602_Wait_Ready(void);
void LCD1602_Write_Cmd(u8 cmd);
void LCD1602_Write_Dat(u8 dat);
void LCD1602_ClearScreen(void);
void LCD1602_Set_Cursor(u8 x, u8 y);
void LCD1602_Show_Str(u8 x, u8 y, u8 *str);
void LCD1602_Init(void);
 
#endif

/*LCD1602.c*/
#include "LCD1602.h"
#include "Systick.h"
 
void GPIO_Configuration(void)
{
	GPIO_InitTypeDef	GPIO_InitStructure;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOA,ENABLE);//使能时钟
	//初始化8个数据接口
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;	
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;	
	GPIO_Init(LCD_DATA_PORT, &GPIO_InitStructure);				
        // RS RW E IO初始化
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1|GPIO_Pin_10|GPIO_Pin_11;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;	
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;	
	GPIO_Init(GPIOB, &GPIO_InitStructure);				
}
 
/* 检测液晶忙不忙 */
void LCD1602_Wait_Ready(void)
{
	int8_t sta;
	
	DATAOUT(0xff);    //PA端口全部置1
	LCD_RS_Clr();     //RS 0
	LCD_RW_Set();     //RW 1
	do
	{
		LCD_EN_Set();   //EN 1
		SysTick_Delay_Ms(1);	//延时1MS
 		sta = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_7);//读取状态字
		LCD_EN_Clr();  //EN  0
	}while(sta & 0x80);//bit7等于1表示忙,重复检测到其为0停止
}
 
/*向1602写入一字节命令,cmd-待写入命令 */
//写入指令PA4-7 RS-0  RW-0
void LCD1602_Write_Cmd(u8 cmd)
{
	LCD1602_Wait_Ready();  //判断忙不忙
	LCD_RS_Clr();   
	LCD_RW_Clr();
	DATAOUT(cmd);  //传八位
	LCD_EN_Set();
	LCD_EN_Clr();
}
 
/* 向1602写入一字节数据*/
void LCD1602_Write_Dat(u8 dat)
{
	LCD1602_Wait_Ready(); //判断忙不忙
	LCD_RS_Set();   //1
	LCD_RW_Clr();   //0
	
	DATAOUT(dat);   //传8位数据
	LCD_EN_Set();  
	LCD_EN_Clr();

}
 
/*清屏*/
void LCD1602_ClearScreen(void)
{
	LCD1602_Write_Cmd(0x01);
	
}
 
/* 设置RAM起始地址,即光标位置,(X,Y)对应屏幕上的字符坐标 *//*X-列,Y—行*/
void LCD1602_Set_Cursor(u8 x, u8 y)
{
	u8 addr;
	
	if (y == 0)
		addr = 0x00 + x;
	else
		addr = 0x40 + x;
	LCD1602_Write_Cmd(addr | 0x80);
}
 
/* 在液晶上显示字符串,(X,Y)-对应屏幕上的其实坐标,str-字符串指针 */
void LCD1602_Show_Str(u8 x, u8 y, u8 *str)
{
	LCD1602_Set_Cursor(x, y);
	while(*str != '\0')
	{
		LCD1602_Write_Dat(*str++);
	}
}
 
/* 初始化液晶 */
void LCD1602_Init(void)
{
	LCD1602_Write_Cmd(0x38);	//16*2显示,5*7点阵,8位数据口  	0011 1000
 	LCD1602_Write_Cmd(0x0F);	//开显示,光标闪烁_F|光标不闪_C 	0000 1111
	LCD1602_Write_Cmd(0x06);	//文字不动,地址自动+1						0000 0110
	LCD1602_Write_Cmd(0x01);	//清屏													0000 0001
}

/*
硬件连接
RS---B1
RW---B10
E----B11
D0---A0
D1---A1
D2---A2
D3---A3
D4---A4
D5---A5
D6---A6
D7---A7
*/

封装好了这些函数就可以使用了;

【2.3程序设计】
主函数如下:
第一次使用STM32,程序写的很冗杂并且没有什么技巧,尽请批评指正;

/**************限时问答器**************/
/*硬件连接在对应驱动的C文件末尾*/
/**************************************/
#include "Systick.h"
#include "LCD1602.h"
#include "stm32f10x.h"
#include "Matrix_keyboard.h"
#include 
#include 
#include 
#include 
#include "ADC.h"
#include "interesting.h"

#define time_interval_1s 100
volatile unsigned long time_delay; //延时时间,定义为全局变量
void systick_open_and_interval_seconds(float nseconds); //设置限定的回答时间
int rand_generate_2(void);                            //2位随机数生成函数
int rand_generate_3(void);                            //3位随机数生成函数
char opeator_generate(void);                          //运算符生成函数
void key_judge(u8 key_scan_value, char *str);         //对键值的初判函数
void Int_To_Str(int x, char *Str);                    //把整数转化为字符串
int char_to_int(char *str);                           //把字符串转化为整数

/****************全局变量********************/
char input_str[10] = {'\0'}; //存放输入的数字 第[0]项为符号为 ‘-’为负 ‘0’为正 每次循环前需进行初始化
volatile int real_value;  //产生的算式的答案
volatile int R;                //正确的个数
volatile int W;				//错误的个数
volatile int cursor_position = 10;
volatile int mask_restart = 0;					//重启标记
volatile int mask_interupt = 0;					//中断标记
volatile float mask_time_decrease=0;//时间递减标记
volatile int seconds = 0; //限时回答的秒数
/********************************主程序开始*********************************/
 int main()
{
    char KEY;        //保存键值
    
		int digits=0;

    int a = 0, b = 0; //产生的两个随机数
    char operate;     //定义预算符
		
		volatile int input;
	
    int i = 0; //计数用的值

    char str1[17] = {'\0'}; //存放各种数据或字符
    char str2[17] = {'\0'}; //存放各种数据或字符
    char str3[17] = {'\0'}; //存放各种数据或字符

    char strR[17] = {'\0'}; //存放正确个数的字符串(用于LCD输出)
    char strW[17] = {'\0'}; //存放错误个数的字符串(用于LCD输出)
		Adc_Init(); //ADC初始化
here:
    mask_restart = 0;

    R = 0, W = 0;   //正确错误的次数
    real_value = 0; //标准答案
    /********初始化函数******/
    input_str[0] = '0';
    for (i = 1; i < 10; i++)
        input_str[i] = '\0';
    i = 0;                 //同时计数变量i归零
    /************************/
    GPIO_Configuration();  //配置LCD IO口
    Key_Init();            //初始化键盘引脚
    LCD1602_Init();        //初始化LCD

    LCD1602_ClearScreen(); //LCD清屏

    LCD1602_Show_Str(0, 0, (unsigned char *)"-----HELLO------");
    LCD1602_Show_Str(0, 1, (unsigned char *)"----WINDOWS-----");
		
    while (!key_press()){};
		while (key_press()){};
		amination1();
		
    LCD1602_ClearScreen(); //LCD清屏

    LCD1602_Show_Str(0, 0, (unsigned char *)"Select Time");
    LCD1602_Show_Str(0, 1, (unsigned char *)"Interval:");
		LCD1602_Set_Cursor(10, 1);
    while (1)                       //检测选择的按键值
    {
        if (key_press())
        {
					KEY=Key_Value_Scan();
          seconds = (int)(KEY - '0');
          sprintf(str1, "%c", KEY);
          LCD1602_Show_Str(10, 1, (unsigned char *)str1);
          while (key_press())
              ;
          break;
        }
    }

    while (!key_press()){};
		while (key_press()){};
			
		LCD1602_ClearScreen(); //LCD清屏

    LCD1602_Show_Str(0, 0, (unsigned char *)"Select Digits");
    LCD1602_Show_Str(0, 1, (unsigned char *)"2 or 3:");
		LCD1602_Set_Cursor(10, 1);
    while (1)                       //检测选择的按键值
    {
        if (key_press())
        {
					KEY=Key_Value_Scan();
          digits = (int)(KEY - '0');
          sprintf(str1, "%c", KEY);
          LCD1602_Show_Str(10, 1, (unsigned char *)str1);
          while (key_press())
              ;
          break;
        }
    }

    while (!key_press()){};
		while (key_press()){};
			
		LCD1602_ClearScreen(); //LCD清屏

    LCD1602_Show_Str(0, 0, (unsigned char *)"Press 1 To Open");
    LCD1602_Show_Str(0, 1, (unsigned char *)"T-Decrease:");
		LCD1602_Set_Cursor(12, 1);
    while (1)                       //检测选择的按键值
    {
        if (key_press())
        {
					KEY=Key_Value_Scan();
          mask_time_decrease = ((KEY == '1')? 1 : 0);
          sprintf(str1, "%c", KEY);
          LCD1602_Show_Str(12, 1, (unsigned char *)str1);
          while (key_press())
              ;
          break;
        }
    }

    while (!key_press()){};
		while (key_press()){};
			
    LCD1602_ClearScreen(); //LCD清屏

    LCD1602_Show_Str(0, 0, (unsigned char *)"----G A M E-----");
    LCD1602_Show_Str(0, 1, (unsigned char *)"---S T A R T----");
    SysTick_Delay_Ms(300);
	amination2();
		
    while(1)
    {		
				Key_Init();//防止软件自带初始化 
				
				if(digits==2)
        {
					a = rand_generate_2();
					b = rand_generate_2();
				}
				else 
				{
					a = rand_generate_3();
					b = rand_generate_3();
				}
				
        operate = opeator_generate();

        if (operate == '+')
            real_value = a + b;
        if (operate == '-')
            real_value = a - b;

        Int_To_Str(a, str1);
        Int_To_Str(b, str2);

        sprintf(str3, "%s%c%s=", str1, operate, str2);
        sprintf(strR, "R:%d", R);
        sprintf(strW, "W:%d", W);

        LCD1602_ClearScreen(); //LCD清屏
        LCD1602_Show_Str(0, 0, (unsigned char *)strR);
        LCD1602_Show_Str(0, 1, (unsigned char *)strW);
        LCD1602_Show_Str(8, 0, (unsigned char *)str3); //显示算式

    
        mask_interupt = 0;
        systick_open_and_interval_seconds(seconds); //设置限制的回答时间
        cursor_position = 10;                       //设置光标的起始位置,方便计数
        LCD1602_Set_Cursor(cursor_position, 1);

        while(1)
        {
            if (mask_interupt == 1)
                goto loop;
            if (mask_restart == 1)
                goto here;
            if (key_press())
            {
                KEY = Key_Value_Scan();
							
                key_judge(KEY, input_str); //判断是否为4个指令输入
                if (KEY == 'C')
                    goto compare;
                if (KEY == 'S')
                    goto here;
                if (KEY != 'C' && KEY != 'B' && KEY != 'D' && KEY != 'S')
                {
                    sprintf(str1, "%c", KEY);
                    LCD1602_Show_Str(cursor_position, 1, (unsigned char *)str1);

                    if (cursor_position == 10)
                    {
                        if (KEY == '-')
												{
													input_str[0] = '-';
                          input_str[cursor_position - 9] = '0';
												}
                        else
                        {
                            input_str[0] = '0';
                            input_str[cursor_position - 9] = KEY;
                        }
                    }
                    else
                        input_str[cursor_position - 9] = KEY;
                
                cursor_position++;
                          
                }
                while (key_press())
                    ;
            }
        }
        compare:
				if (strlen(input_str) == 1)
        {
            W++;
            sprintf(strW, "W:%d", W);
            LCD1602_Show_Str(0, 1, (unsigned char *)strW);
						
						LCD1602_Show_Str(10, 1, (unsigned char *)"WRONG ");
						SysTick_Delay_Ms(200);
						LCD1602_Show_Str(10, 1, (unsigned char *)"      ");
					
            LCD1602_Set_Cursor(10, 1);
        }
        else
        {		
					input = char_to_int(input_str);
					
          switch ((input == real_value))
					{
						case 1:
						R++;
						sprintf(strR, "R:%d", R);
						LCD1602_Show_Str(0, 0, (unsigned char *)strR);
						
						LCD1602_Show_Str(9, 1, (unsigned char *)"CORRECT");
						SysTick_Delay_Ms(200);
						LCD1602_Show_Str(9, 1, (unsigned char *)"       ");
						
						LCD1602_Set_Cursor(10, 1);
						/***************************/
						if(mask_time_decrease==1)
						{if((seconds-0.5)>0)
								seconds-=0.5;}
						/***************************/
						break;
						case 0:
						W++;
						sprintf(strW, "W:%d", W);
						LCD1602_Show_Str(0, 1, (unsigned char *)strW);
						
						LCD1602_Show_Str(10, 1, (unsigned char *)"WRONG ");
						SysTick_Delay_Ms(200);
						LCD1602_Show_Str(10, 1, (unsigned char *)"      ");
						
						LCD1602_Set_Cursor(10, 1);
						break;
					}
        }
        if(W>=5)
        {
            SysTick->CTRL &= 0x00; //关闭定时器
            SysTick->VAL = 0X00;   //清空计数器
            LCD1602_ClearScreen(); //清屏,准备显示结束语

            LCD1602_Show_Str(2, 0, (unsigned char *)"YOU DIED");
            LCD1602_Show_Str(0, 1, (unsigned char *)"PRESS TO RESTART");
            while (!key_press()){};
						while (key_press()){};
            goto here;
        }
    loop:
        mask_interupt = 0;
        input_str[0] = '0';
        for (i = 1; i < 10; i++)
            input_str[i] = '\0';
        i = 0; //同时计数变量i归零
    }
}

void systick_open_and_interval_seconds(float nseconds)
{
    SysTick_Config(SystemCoreClock / 1000);

    time_delay = time_interval_1s * nseconds;
}

int rand_generate_2(void) //产生随机一个2位数
{
    u16 iSeed = 0; 
		iSeed = Get_Adc_Average(ADC_Channel_8, 3); 
    srand(iSeed);
    return rand() % 90 + 10;
}

int rand_generate_3(void) //产生随机一个3位数
{
    u16 iSeed = 0; 
		iSeed = Get_Adc_Average(ADC_Channel_8, 3); 
    srand(iSeed);
    return rand() % 900 + 100;
}

char opeator_generate(void)
{
    static int m = 0;
    m++;
    srand(m);
    if (rand() % 2)
        return '+';
    else
        return '-';
}

void key_judge(u8 key_scan_value, char *str)
{
    int i;
    switch (key_scan_value)
    {
    case 'S':
        SysTick->CTRL &= 0x00; //关闭定时器
        SysTick->VAL = 0X00;   //清空计数器
        break;
    case 'B':
        if (cursor_position != 10)
        {
            LCD1602_Set_Cursor(--cursor_position, 1);                   //光标移至退格的位
            LCD1602_Show_Str(cursor_position, 1, (unsigned char *)" "); //将该位清除,同时光标自动+1(这并不是老子想要的)
            LCD1602_Set_Cursor(cursor_position, 1);                     //将光标重新返回删除位
            str[cursor_position - 9] = '\0';
        }
        break;
    case 'D':
        str[0] = '0';
        for (i = 1; i < 10; i++)
            str[i] = '\0';
        cursor_position = 10;
        LCD1602_Show_Str(10, 1, (unsigned char *)"      ");
        LCD1602_Set_Cursor(cursor_position, 1);
        break;
    case 'C':
        SysTick->CTRL &= 0xfc; //关闭定时器
        SysTick->VAL = 0X00;   //清空计数器
        break;
    }
}

void Int_To_Str(int x, char *Str)
{
    int t;
    char *Ptr, Buf[5]; //定义一个字符串数组和字符串指针,
    int i = 0;         //用于计数
    Ptr = Str;         //内部指针指向外部指针,进行参数传递,是属于源参数传递(通过地址),
    if (x < 10)        // 当整数小于10,转换为0x格式
    {
        *Ptr++ = '0'; //单个数字前面补“0”
        *Ptr++ = x + 0x30;
    }
    else
    {
        while (x > 0)
        {
            t = x % 10;
            x = x / 10;
            Buf[i++] = t + 0x30; // 通过计算把数字编成ASCII码形式
        }
        i--;
        for (; i >= 0; i--) // 将得到的字符串倒序
        {
            *(Ptr++) = Buf[i];
        }
    }
    *Ptr = '\0';
}

int char_to_int(char *str)
{
    int i;
    int lenth = strlen(str) - 2;
    int sum=0;
    for (i = 1; i < strlen(str); i++)
    {
        sum += (int)(str[i] - '0') * (int)pow(10, (float)(lenth--));
    }
    if (str[0] == '-')
        sum = -sum;
    return (int)sum;
}

void SysTick_Handler(void)
{
    char strR[17] = {'\0'}; //存放正确个数的字符串(用于LCD输出)
    char strW[17] = {'\0'}; //存放错误个数的字符串(用于LCD输出)
    u8 str1[] = "YOU DIED";
    u8 str2[] = "PRESS TO RESTART";
    if (time_delay != 0)
        time_delay--;
    if (time_delay == 0)
    {
        SysTick->CTRL &= 0x00; //关闭定时器
        SysTick->VAL = 0X00;   //清空计数器
        mask_interupt = 1;
        if (strlen(input_str) == 1)
        {
            W++;
            sprintf(strW, "W:%d", W);
            LCD1602_Show_Str(0, 1, (unsigned char *)strW);
						
						LCD1602_Show_Str(10, 1, (unsigned char *)"WRONG ");
						SysTick_Delay_Ms(200);
						LCD1602_Show_Str(10, 1, (unsigned char *)"      ");
						
            LCD1602_Set_Cursor(10, 1);
        }
        else
        {
            if (char_to_int(input_str) == real_value)
            {
                R++;
                sprintf(strR, "R:%d", R);
                LCD1602_Show_Str(0, 0, (unsigned char *)strR);
								
								LCD1602_Show_Str(9, 1, (unsigned char *)"CORRECT");
								SysTick_Delay_Ms(200);
								LCD1602_Show_Str(9, 1, (unsigned char *)"       ");
							
                LCD1602_Set_Cursor(10, 1);
							/***************************/
								if(mask_time_decrease==1)
								{if((seconds-0.5)>0)
										seconds-=0.5;}
							/***************************/
            }
            else
            {
                W++;
                sprintf(strW, "W:%d", W);
                LCD1602_Show_Str(0, 1, (unsigned char *)strW);
								
								LCD1602_Show_Str(10, 1, (unsigned char *)"WRONG ");				
								SysTick_Delay_Ms(200);
								LCD1602_Show_Str(10, 1, (unsigned char *)"      ");
							
                LCD1602_Set_Cursor(10, 1);
            }
        }
        if(W>=5)
        {
						mask_interupt = 0;
            LCD1602_ClearScreen(); //清屏,准备显示结束语
            LCD1602_Show_Str(2, 0, str1);
            LCD1602_Show_Str(0, 1, str2);
            mask_restart = 1;
						SysTick->CTRL &= 0x00; //关闭定时器
						SysTick->VAL = 0X00;   //清空计数器
            while (!key_press()){};
						while (key_press()){};
        }
    }
}

【未完待续】
【延时的一些方法】

你可能感兴趣的:(STM32学习)