51单片机课设,比赛计分器
本来是用汇编写的,但是汇编属实学的不怎么好,于是自学用C来写,效果还不错
比赛计分器
一、 需求分析(从用户角度 UML)
1.1 设计内容
本项目设计开发一款个比赛计分器,在重竞技比赛项目中(拳击,跆拳道),由3个裁判员对运动员评判。当一方运动员有效攻击后,裁判员立刻给这个运动员加分。如果有2个或2个以上裁判同时(一秒内)按键,则给该方运动员加一分。比赛期间(2分钟)得分多判定为胜利方。
1.2 设计目标
比赛计分器要实现的功能如下:
(1)能够提供三个裁判的评分按键,当一秒钟内有2个或者3个裁判按键,则有效分加1。
(2)显示1分钟倒计时,并可以按键控制实践走停,开始和结束时候有蜂鸣器响声,时间开始响1s,时间结束响1s。
(3)按下按键可以对两队进行警告,同时记录警告次数。
(4)对误判可以进行减分。
(5)数码管显示时间,得分,警告次数。
1.3实验平台和工具
实验平台为:keil
实验工具为:89c51单片机
1.3 设计的功能分析
单片机实现比赛计分器功能。使用单片机数码管,独立按键,蜂鸣器以及矩阵键盘四个模块。数码管用来显示时间倒计时,双方警告次数以及得分。蜂鸣器在开始时以及快结束前五秒时响1s。独立按键k1按下开始,k2按下暂停,k3按下第一队警告次数加1,k4按下第二队警告次数加1。矩阵按键s1,s2,s3,一秒内按下任意两个或三个,第一队得分加1,按下s4得分减1。矩阵按键s5,s6,s7,一秒内按下任意两个或三个,第二队得分加1,按下s8得分减1。
1.5 设计的非功能性分析
通过本程序的设计可设计出一个简易的比赛计分器,应用于实际生活中也是非常简易方便的。
二、 总体设计
2.1总体方案设计
(1)裁判按键设置
S1:提供裁判1对第一队的评分按键
S2:提供裁判2对第一队的评分按键
S3:提供裁判3对第一队的评分按键
S5:提供裁判1对第二队的评分按键
S6:提供裁判2对第二队的评分按键
S7:提供裁判3对第二队的评分按键
(2)评分判断设置
通过启动定时器T0来判断1s之内有几个裁判按键,设计一个50ms的定时器,中断20次后即为1s,若一秒钟内有2个或者3个裁判按键,则有效分加1;否则,不加分。
(3) 分数显示设置
通过P0口将分数显示至数码管。
(4) 减分设置
按下S4或S5第一队或第二队分数减1。
(5) 警告次数显示设置
按下K3或K4,第一队或第二队警告次数加1。
(6) 开始,暂停设置
按下K1开始,按下K2暂停
2.2 演示支持方案(如何利用开发板资源或外接模块,实现功能演示的方案,传感器(或模拟)演示方案)
在单片机下载完成后,51单片机的8位动态数码管全部显示为0,
一二个显示时间,三四个分别显示两队警告次数,五六个显示第一队得分,七八个显示第二队得分。
在按下独立按键K1后,第一二个数码管显示60并开始倒计时,同时蜂鸣器响1s,若按下独立按键K2便暂停并且要再次按下K1才能开始。当倒计时为5s时,蜂鸣器也再次响1s。在这60s期间,矩阵按键s1,s2,s3在1秒内按下任意两个第一队得分加一,若出现误判,按下矩阵按键s4使得分减一,若第一队出现犯规可按下独立按键K3便使警告次数加1。同理第二队矩阵按键s5,s6,s7在1秒内按下任意两个第二队得分加一,若出现误判,按下矩阵按键s8使得分减一,若第二队出现犯规可按下独立按键K4便使警告次数加1。
2.3 单片机资源分配设计(包括RAM资源分配,I/O资源分配)
没有具体的要求。
三、 模块设计与实现(介绍本课设主要模块的设计思路,实现方法,可以通过流程图,状态图,关键代码,显示效果等多种手段说明)
1.独立按键模块:
由图可知k1对应的为p3.1口,k2对应的为p3.0口,k3对应的为p3.2口,k4对应的为p3.3口;本课设中使用到的是k1和k2按键,由上图知按键的一端已经连接到GND,而在另一端则是通过按下来检测,当未被按下时p3口的前四位默认为高电平,即1。按下后即为0,其中的抖动可使用延时来消除;代码如图:
- 动态数码管:
首先是数码管的片选,本次课设采用的52单片机使用的是38译码器,即如上图中:通过p2.2、p2.3、p2.4三位2^3=8位来判断使用哪一个数码管工作(亮);例如当p2.2=1;p2.3=1;p2.4=0时,对应的十六进制数值为6,则从右往左数的第七个数码管会亮,即图中的LED7;具体对应关系如下图:
P2.2 |
P2.3 |
P2.4 |
LED(位) |
0 |
0 |
0 |
LED1 |
0 |
0 |
1 |
LED2 |
0 |
1 |
0 |
LED3 |
0 |
1 |
1 |
LED4 |
1 |
0 |
0 |
LED5 |
1 |
0 |
1 |
LED6 |
1 |
1 |
0 |
LED7 |
1 |
1 |
1 |
LED8 |
然后是数码管模块的数字显示,如下图:
数码管电路的接法有共阴和共阳两种,本次用的单片机里的数码管采用的是共阴接法。
从其中取出一个数码管进行分析
单个数码管:
此处为一个8位的数码管,想要数码管显示出想要的数字,则需要对数码管进行高低电平的设置1为高电平,0为低电平,分别对 a,b,c,d,e,f,g,dp进行1和0的编写;
如图中所示,数字2的 八位二进制就可以表示为 0101 1011,读数为从dp依次读到a,转化为十六进制则是0x5b。
- 定时器中断:
由该图可知定时器T0的中断入口为0BH,将中断的子程序入口设为0BH即可。由于本课设整体就是基于时间的累加,因此核心也是定时器的计时功能。在使用定时器0工作于方式1,中断使用定时器的溢出中断,设置TH0、TL0初值为0D8H、0F0H,即可产生每10ms一次的溢出中断,又定义R0为100,通过DJNZ指令达到使每过10ms*100=1s才执行对应的计时计费操作来实现功能。如图:
- 蜂鸣器模块:
蜂鸣器发声原理是电流通过电磁线圈,使电磁线圈产生磁场来驱动振动膜发声的,因此需要一定的电流才能驱动它,单片机IO引脚输出的电流较小,单片机输出的TTL电平基本上驱动不了蜂鸣器,因此需要增加一个电流放大的电路。S51增强型单片机实验板通过一个三极管C8550来放大驱动蜂鸣器。蜂鸣器的正极接到VCC(+5V)电源上面,蜂鸣器的负极接到三极管的发射极E,三极管的基级B经过限流电阻R1后由单片机的P1.5引脚控制,当P1.5输出高电平电磁式有源蜂鸣器,电磁式有源蜂鸣器产品质量好时,三极管T1截止,没有电流流过线圈,蜂鸣器不发声;当P1.5输出低电平时,三极管导通,这样蜂鸣器的电流形成回路,发出声音。因此,我们可以通过程序控制P1.5脚的电平来使蜂鸣器发出声音和关闭.程序中改变单片机P1.5引脚输出波形的频率,就可以调整控制蜂鸣器音调,产生各种不同音色、音调的声音。
四、 系统集成设计(与其它系统的通信、连接,和相互作用设计,参考结构,根据项目增减)
没有使用。
五、 系统测试
5.1 系统测试
5.2 单元测试
5.3非功能性测试
开始前;
开始时:
、
具体介绍:
六、 收获与总结
通过本次单片机课程设计,我更加理解了实践出真知的重要性,不仅要拥有足够的理论知识,同时也应该懂得用于实践,只有通过不断反复的测试与失败,才能获得成功。当然,这其中也有很多问题,第一、由于对课本理论的不熟悉导致编程出现错误。第二,这次课设是对我的学习态度的一次检验。这次课设所遇到的多半问题多数都是由于我们不够严谨,考虑问题全部全面,周不周到。第三,我认识到,无论做什么事情,只要你足够坚强,有足够的毅力与决心,有足够的挑战困难的勇气,就没有什么办不到的。
附录1原理图
附录2 源程序清单
#include <reg51.h>
#define uint unsigned int //无符号化
#define uchar unsigned char
uchar num = 0;
sbit led0 = P2^0; //位定义
sbit led2 = P2^2;
sbit led3 = P2^3;
sbit led4 = P2^4;
sbit beep = P1^5;
sbit K1 = P3^1;
sbit K2 = P3^0;
sbit K3 = P3^2;
sbit K4 = P3^3;
uchar DisplayData[8];
#define GPIO_KEY P1
uchar KeyValue1=0xff; //存放读取到的键值
uchar KeyValue2=0xff;
uchar KeyValue=0xff;
uint time_jsq1=0;
uint time_jsq2=0;
uchar flag_start1=0; //记录当前时间
uchar flag_start2=0;
uint time_start1=0; //记录加分开始时间
uint time_start11=0; //记录减分开始时间
uint time_start111=0;//记录警告开始时间
uint time_start2=0;
uint time_start22=0;
uint time_start222=0;
uint time_end1=0; //记录结束时间
uint time_end2=0;
uchar key_num1=-1; //记录第一次读取到的键值
uchar key_num11=-1; //记录第二次读取到的键值
uchar key_num2=-1;
uchar key_num22=-1;
uchar num_fenshu1=0; //记录分数
uchar num_fenshu2=0;
uint i=0,time=0;
uint j=0,k=0,A=0;
uint A1=0;
uint jinggao1=0,jinggao2=0;
void KeyDown(void);
uchar code duanxuan[] = {0x3f,0x06,0x5b,0x4f, //0,1,2,3
0x66,0x6d,0x7d,0x07, //4,5,6,7
0x7f,0x6f,0x77,0x7c, //8,9,A,B
0x39,0x5e,0x79,0x71}; //C,D,E,F
/*******************************************************************************
* 函数名: : fp
* 功能 : 蜂鸣器延时
*******************************************************************************/
void fp(uint i) //蜂鸣器延时
{
while(i--);
}
/*******************************************************************************
* 函数名: : delay
* 功能 : 延时函数,i=1大概1us
*******************************************************************************/
void delay(uint i)
{
while(i--);
}
/*******************************************************************************
* 函数名: : Display
* 功能 : 显示数码管
*******************************************************************************/
void Display(int j,int k,uchar m,uchar n,int a,int b) //xianshi
{
uchar ii;
DisplayData[0]=duanxuan[j]; //duanxuan[j];
DisplayData[1]=duanxuan[k]; //duanxuan[k];
DisplayData[2]=duanxuan[m%100/10]; //duanxuan[m%100/10];
DisplayData[3]=duanxuan[m%10]; //duanxuan[m%10];
DisplayData[4]=duanxuan[n%100/10];
DisplayData[5]=duanxuan[n%10];
DisplayData[6]=duanxuan[a];
DisplayData[7]=duanxuan[b];
for(ii=0;ii<8;ii++)
{
switch(ii)//位选点亮的灯管
{
case(0):led2=1,led3=1,led4=1;break; //1
case(1):led2=0,led3=1,led4=1;break; //2
case(2):led2=1,led3=1,led4=0;break; //5
case(3):led2=0,led3=1,led4=0;break; //6
case(4):led2=1,led3=0,led4=0;break; //7
case(5):led2=0,led3=0,led4=0;break; //8
case(6):led2=1,led3=0,led4=1;break; //3
case(7):led2=0,led3=0,led4=1;break; //4
}
P0=DisplayData[ii];//送到P0口
delay(50); //延时
P0=0x00;//消影
}
}
void main()
{
TMOD = 0X01;
TH0 = (65536-45872)/256; //高八位
TL0 = (65536-45872)%256; //低八位
EA = 1; //总开关
ET0 = 1; //定时器0
TR0 = 1;
led0 = 0;
if(K1==0)
time=60;
while(time!=0)
{
KeyDown();
if(num == 20)
{
num = 0;
time--;
j=time/10,k=time%10;
}
if(led0 == 0&&(time==60||time==5))
{
beep = ~beep;
fp(1);
}
Display(j,k,num_fenshu1,num_fenshu2,jinggao1,jinggao2);
if(KeyValue1!=0xff) //按键是否按下
{
if((KeyValue1==0||KeyValue1==1||KeyValue1==2)&&flag_start1==0&&A==0&&TR0==1)
{
flag_start1=1;
time_start1=time_jsq1;
key_num1=KeyValue1;
KeyValue1=0xff;
KeyValue=0xff;
}
if((KeyValue1==0||KeyValue1==1||KeyValue1==2)&&flag_start1==1&&key_num1!=KeyValue1&&key_num11==-1&&time_start1!=0&&TR0==1)
{
num_fenshu1=num_fenshu1+1; //分数加一
key_num11=KeyValue1;
}
if(KeyValue1==7&&time_jsq1-time_start11>=5&&num_fenshu1>0&&TR0==1)
{
time_start11=time_jsq1;
num_fenshu1=num_fenshu1-1; //减分
KeyValue1=0xff;
KeyValue=0xff;
}
}
if(time_jsq1-time_start1>20||TR0==0)//大于1s全部恢复初值进行下一次
{
A=0;
time_start1=0;
time_end1=0;
flag_start1=0;
key_num11=-1;
key_num1=-1;
KeyValue1=0xff;
KeyValue=0xff;
}
if(KeyValue2!=0xff) //同上
{
if((KeyValue2==3||KeyValue2==4||KeyValue2==5)&&flag_start2==0&&A1==0&&TR0==1)
{
flag_start2=1;
time_start2=time_jsq2;
key_num2=KeyValue2;
KeyValue2=0xff;
}
if((KeyValue2==3||KeyValue2==4||KeyValue2==5)&&flag_start2==1&&key_num2!=KeyValue2&&key_num22==-1&&time_start2!=0&&TR0==1)
{
num_fenshu2=num_fenshu2+1;
key_num22=KeyValue2;
}
if(KeyValue2==6&&time_jsq2-time_start22>=5&&num_fenshu2>0&&TR0==1)
{
time_start22=time_jsq2;
num_fenshu2=num_fenshu2-1;
KeyValue2=0xff;
}
}
if(time_jsq2-time_start2>20||TR0==0)
{
A1=0;
time_start2=0;
time_end2=0;
flag_start2=0;
key_num22=-1;
key_num2=-1;
KeyValue2=0xff;
}
if(K2==0)
{
beep = ~beep;
fp(10);
TR0=0;
}
if(K1==0)
{
beep = ~beep;
fp(10);
TR0=1;
}
if(K3==0&&time_jsq1-time_start111>=5&&TR0==1) //A队警告
{
time_start111=time_jsq1;
jinggao1=jinggao1+1;
if(jinggao1>9)
{
jinggao1=15;
break;
}
}
if(K4==0&&time_jsq2-time_start222>=5&&TR0==1) //B队警告
{
time_start222=time_jsq2;
jinggao2=jinggao2+1;
if(jinggao2>9)
{
jinggao2=15;
break;
}
}
}
for(i=1;i>=0;i++) //结束后显示
{
Display(j,k,num_fenshu1,num_fenshu2,jinggao1,jinggao2);
if(K1==0)
break;
}
}
/*******************************************************************************
* 函数名: : TR0_time
* 功能 : 定时器中断,每溢出一次0.05s
*******************************************************************************/
void TR0_time() interrupt 1 //dingshiqizhongduan
{
TH0 = (65536-45872)/256;
TL0 = (65536-45872)%256;
num++;
time_jsq1++;
time_jsq2++;
}
/*******************************************************************************
* 函数名 : KeyDown
* 功能 : 判断按下哪个按键
*******************************************************************************/
void KeyDown(void)
{
char a=0;
GPIO_KEY=0x0f;
if(GPIO_KEY!=0x0f)//判断是否按下
{
delay(10);//延时消抖
if(GPIO_KEY!=0x0f)//再次判断是否按下
{
//列
GPIO_KEY=0X0F;
switch(GPIO_KEY)
{
case(0X07): KeyValue=0;break;
case(0X0b): KeyValue=1;break;
case(0X0d): KeyValue=2;break;
case(0X0e): KeyValue=7;break;
}
//行
GPIO_KEY=0XF0;
switch(GPIO_KEY)
{
case(0X70): KeyValue1=KeyValue;break;
}
}
if(GPIO_KEY!=0x0f)//同上
{
delay(10);
if(GPIO_KEY!=0x0f)
{
GPIO_KEY=0X0F;//低四位全置1
switch(GPIO_KEY)
{
case(0X07): KeyValue2=0;break;
case(0X0b): KeyValue2=1;break;
case(0X0d): KeyValue2=2;break;
case(0X0e): KeyValue2=3;break;
}
GPIO_KEY=0XF0;
switch(GPIO_KEY)
{
case(0Xb0): KeyValue2=KeyValue2+3;break;
}
}
}
}
while((a<50)&&(GPIO_KEY!=0xf0)) //判断是否松开
{
a++;
}
}