【前言】【新手向】
从大一来学校就接触了stm32系列的产品,在嵌入式设计,控制的方面有很大的兴趣,到现在来说,入学已经有三个月了,脱离了高考的体制,学习变得自由起来,但是由于对单品机完全没一点点基础,所以一边看【正点原子】的开发指南一边学C语言,可以说真的是从有到无,一步步积累,一点点建立起来的知识。这样的自学其实是蛮有难度的,也与以往的自学有所不同,这时对一个完全未曾了解过的领域的自学,首先在体系上是未知,其次因为stm32本身的难度就大(对于我这样的小小白来说),基本所有的专业词汇都是边查意思,边猜意思,一知半解的过来,在后面的一些应用上才慢慢反应他的意思。可以说对自己的三个月的评价还行。毕竟是第一次用32做东西,有什么问题还望不吝赐教。
【正文】
接下来将系统的总结一下,这次问答机的完成以及在其期间的学习内容。
【1.实现的功能】
1.能在屏幕上随机显示一个2位数或者是3位数的加或减的算式;
2.能用键盘读取用户输入的数值,并在屏幕上显示,同时判断输入的数值是否正确,并在屏幕上显示正误,并在2秒后显示下一个算式;
3.可设置1至9秒的限时回答时长;
4.在屏幕上显示回答问题正确和错误的次数;
5.可开启回答正确下一次限时时间缩短的模式;
6.键盘的具体功能:
键盘的各个键的含义如下:
a.S为开关键,可以将输入界面跳转到欢迎界面
b.B为退格键可以清除屏幕上的光标的前一位,同时将存储的数值清除一位
c.D为清除键,可以将当前的输入界面的内容清零同时将储蓄的数值删除
d.C为确认键,将当前的输入值送去比较,得出结果,可不用等到限定时间到时后的自动比较
e.其余的键为对应键值
【2.功能的实现】
在显示模块上选择用的是LCD1602,使用比较简单而且满足需求;输入的按键选择使用矩阵键盘;在限时回答的方面使用Systick中断;因为MDK没有自带time.h库所以在随机数的产生上使用ADC模式读取空气中的噪声来产生随机数中的,这一部分的代码主要还是在网上抄来的,稍加修改就可以达到理想的效果;而对于延时函数的实现上也找了一些方法。
【2.1矩阵键盘】
使用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口使用。(如图)
该图是调试过程中进入key_press()函数时的端口寄存器状态;在图中可以看到GPIOB->ODR的第15至第12位均为1,而在无任何按键按下的情况下,GPIOB->IDR的第7至第4位中第四位为1。
还需关注一点,这里的延时函数并未使用systick,只是while(),循环使程序空转达到延时,因为主要目的是等待硬件,所以没有必要准确的延时。
【2.2LCD1602】
在写LCD1602的驱动前一定要认真查看其相对应的指令手册以及时序图,LCD1602自带有显示字符的程序。
电路连接如图:
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脚:背光电源负极。
图如果不清楚网上有个比较详细的解释(出处忘记了);
一、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 <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#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()){};
}
}
}
【未完待续】
【延时的一些方法】