0 要求介绍
使用51单片机设计一个思路抢答器
正常抢答:主持人按下开始按钮后数码显示管显示最先按下按钮台号,蜂鸣器发出音响,其它抢答按钮无效;主持人按下开始按钮之前,若选手按下抢答器,抢答违规,显示器显示违规台号,违规指示灯亮,蜂鸣器发出音响,其他按钮不起作用
各台数字显示的消除,蜂鸣器音响及违规指示灯的关断,都要通过主持人按复位按钮
选手答题时间限制:正常抢答下,从按下抢答按钮开始规定时间内,选手没有回答完毕,则作超时处理,违规指示灯亮,显示器显示违规台号,蜂鸣器发出音响
可方便地修改规定答题时间
使用的单片机型号为:普中51-单核-A2(其他51单片机可能引脚和时钟频率不同,需更改一些定义)
1 硬件需求分析
1.1 独立按键
由于本设计为四路抢答器所以适合使用独立按键,且后续设计讲解和代码也使用独立按键,也可以更改为其他触发方式。独立按钮的四个按键分别对应选手1、选手2、选手3、选手4,按键按下时触发对应选手台号抢答
独立按键示意图
通过原理图可以得知其对应的引脚分别为:K1->P31 K2->P30 K3->P32 K4->P33
独立按键原理图
1.2 矩阵键盘
在设计要求中有裁判控制,分析可以得出裁判的职能有:设置倒计时时间、复位抢答、确认开始抢答。根据裁判职能大约需要4个键,分别为:倒计时分钟设置键、倒计时秒设置键、复位键、确认键,由于独立按键已被占用所以使用矩阵键盘中的部分按键来实现。本设计和后续代码中按键的对应关系为:倒计时分钟设置键->S1 倒计时秒设置键->S2 复位键->S3 确认键->S4
矩阵键盘
矩阵键盘原理图
1.3 蜂鸣器
设计需求中多处需要使用到蜂鸣器这一装置,通过原理图可以得知蜂鸣器通过五线四项步进电机模块转接,其对应引脚为P15
蜂鸣器原理图
1.4 数码管
通过数码管显示倒计时、台号、警告等相关数据
动态数码管模块
数码管通过74HC138编译器来确定每次点亮的数码管编号,从原理图可以得知74HC138有3个输入引脚分别为P22 P23 P24,对3个引脚施加不同的电平可以控制亮数码管,假设数码管从左到右依次为1号-8号,则3个引脚的电平信号对应点亮的数码管如下表所示:
P22引脚电平 |
P23引脚电平 |
P24引脚电平 |
点亮的数码管编号 |
0 |
0 |
0 |
1号 |
0 |
0 |
1 |
2号 |
0 |
1 |
0 |
3号 |
0 |
1 |
1 |
4号 |
1 |
0 |
0 |
5号 |
1 |
0 |
1 |
6号 |
1 |
1 |
0 |
7号 |
1 |
1 |
1 |
8号 |
74HC138译码模块原理图
动态数码模块
2 程序逻辑分析
程序逻辑如图所示
3 代码编写
3.1 系统基础配置
3.1.1 public.h
#ifndef _public_H
#define _public_H
#include "reg52.h"
typedef unsigned int u16; //对系统默认数据类型进行重定义
typedef unsigned char u8;
void delay_10us(u16 ten_us); //定义微秒延时
void delay_ms(u16 ms); //定义毫秒延时
#endif
3.1.2 public.c
#include "public.h"
/*******************************************************************************
* 函 数 名 : delay_10us
* 函数功能 : 延时函数,ten_us=1时,大约延时10us
* 输 入 : ten_us
* 输 出 : 无
*******************************************************************************/
void delay_10us(u16 ten_us)
{
while(ten_us--);
}
/*******************************************************************************
* 函 数 名 : delay_ms
* 函数功能 : ms延时函数,ms=1时,大约延时1ms
* 输 入 : ms:ms延时时间
* 输 出 : 无
*******************************************************************************/
void delay_ms(u16 ms)
{
u16 i,j;
for(i=ms;i>0;i--)
for(j=110;j>0;j--);
}
3.2 按键代码编写
3.2.1 Key.h
#ifndef _key_H
#define _key_H
#include "public.h"
//使用宏定义矩阵按键控制口
#define KEY_MATRIX_PORT P1
//定义独立按键控制脚
sbit KEY1=P3^1;
sbit KEY2=P3^0;
sbit KEY3=P3^2;
sbit KEY4=P3^3;
//使用宏定义独立按键按下的键值
#define PLEAR_1 1 //选手1
#define PLEAR_2 2 //选手2
#define PLEAR_3 3 //选手3
#define PLEAR_4 4 //选手4
#define KEY_UNPRESS 0 //无选手按键
//使用宏定义矩阵按键按下的键值
#define MINUP 1 //分钟时间+1
#define SECUP 2 //秒时间+1
#define REST 3 //重置按键
#define ENTER 4 //确认、开始按键
u8 key_scan(u8 mode); //独立按键按下检测
u8 key_matrix_ranks_scan(void); //矩阵键盘按下检测
#endif
3.2.2 Key.c
#include "key.h"
/*******************************************************************************
* 函 数 名 : key_scan
* 函数功能 : 检测独立按键是否按下,按下则返回对应键值
* 输 入 : mode=0:单次扫描按键
mode=1:连续扫描按键
* 输 出 : KEY1_PRESS:K1按下
KEY2_PRESS:K2按下
KEY3_PRESS:K3按下
KEY4_PRESS:K4按下
KEY_UNPRESS:未有按键按下
*******************************************************************************/
u8 key_scan(u8 mode)
{
static u8 key=1;
if(mode)key=1;//连续扫描按键
if(key==1&&(KEY1==0||KEY2==0||KEY3==0||KEY4==0))//任意按键按下
{
delay_10us(1000);//消抖
key=0;
if(KEY1==0)
return PLEAR_1;
else if(KEY2==0)
return PLEAR_2;
else if(KEY3==0)
return PLEAR_3;
else if(KEY4==0)
return PLEAR_4;
}
else if(KEY1==1&&KEY2==1&&KEY3==1&&KEY4==1) //无按键按下
{
key=1;
}
return KEY_UNPRESS;
}
/*******************************************************************************
* 函 数 名 : key_matrix_ranks_scan
* 函数功能 : 使用行列式扫描方法,检测矩阵按键是否按下,按下则返回对应键值
* 输 入 : 无
* 输 出 : key_value:1-16,对应S1-S16键,
0:按键未按下
*******************************************************************************/
u8 key_matrix_ranks_scan(void)
{
u8 key_value=0;
KEY_MATRIX_PORT=0xf7;//给第一列赋值0,其余全为1
if(KEY_MATRIX_PORT!=0xf7)//判断第一列按键是否按下
{
delay_10us(1000);//消抖
switch(KEY_MATRIX_PORT)//保存第一列按键按下后的键值
{
case 0x77: key_value=1;break;
case 0xb7: key_value=5;break;
case 0xd7: key_value=9;break;
case 0xe7: key_value=13;break;
}
}
while(KEY_MATRIX_PORT!=0xf7);//等待按键松开
KEY_MATRIX_PORT=0xfb;//给第二列赋值0,其余全为1
if(KEY_MATRIX_PORT!=0xfb)//判断第二列按键是否按下
{
delay_10us(1000);//消抖
switch(KEY_MATRIX_PORT)//保存第二列按键按下后的键值
{
case 0x7b: key_value=2;break;
case 0xbb: key_value=6;break;
case 0xdb: key_value=10;break;
case 0xeb: key_value=14;break;
}
}
while(KEY_MATRIX_PORT!=0xfb);//等待按键松开
KEY_MATRIX_PORT=0xfd;//给第三列赋值0,其余全为1
if(KEY_MATRIX_PORT!=0xfd)//判断第三列按键是否按下
{
delay_10us(1000);//消抖
switch(KEY_MATRIX_PORT)//保存第三列按键按下后的键值
{
case 0x7d: key_value=3;break;
case 0xbd: key_value=7;break;
case 0xdd: key_value=11;break;
case 0xed: key_value=15;break;
}
}
while(KEY_MATRIX_PORT!=0xfd);//等待按键松开
KEY_MATRIX_PORT=0xfe;//给第四列赋值0,其余全为1
if(KEY_MATRIX_PORT!=0xfe)//判断第四列按键是否按下
{
delay_10us(1000);//消抖
switch(KEY_MATRIX_PORT)//保存第四列按键按下后的键值
{
case 0x7e: key_value=4;break;
case 0xbe: key_value=8;break;
case 0xde: key_value=12;break;
case 0xee: key_value=16;break;
}
}
while(KEY_MATRIX_PORT!=0xfe);//等待按键松开
return key_value;
}
3.3 蜂鸣器代码编写
3.2.1 Beep.h
#ifndef _beep_H
#define _beep_H
#include "public.h"
//管脚定义
sbit BEEP=P1^5;
//蜂鸣器报警函数
void beep_alarm(u16 time,u16 fre);
#endif
3.2.2 Beep.c
#include "beep.h"
/*******************************************************************************
* 函 数 名 : beep_alarm
* 函数功能 : 蜂鸣器报警函数
* 输 入 : time:报警持续时间
fre:报警频率
* 输 出 : 无
*******************************************************************************/
void beep_alarm(u16 time,u16 fre)
{
while(time--)
{
BEEP=!BEEP;
delay_10us(fre);
}
}
3.3 数码管代码编写
3.3.1 smg.h
#ifndef _smg_H
#define _smg_H
#include "public.h"
#define SMG_A_DP_PORT P0 //使用宏定义数码管段码口
//对字母的宏定义
#define A 0x77
#define B 0x7c
#define C 0x39
#define D 0x5e
#define E 0x79
#define F 0x71
#define G 0x3d
#define H 0x76
#define I 0x0f
#define J 0x0e
#define K 0x75
#define L 0x38
#define M 0x37
#define N 0x54
#define O 0x5c
#define P 0x73
#define Q 0x67
#define R 0x31
#define S 0x49
#define T 0x78
#define U 0x3e
#define V 0x1c
#define W 0x7e
#define X 0x64
#define Y 0x6e
#define Z 0x59
//定义数码管位选信号控制脚
sbit LSA=P2^2;
sbit LSB=P2^3;
sbit LSC=P2^4;
//储存数字字符串定义
extern u8 gsmg_code[17];
//数码管显示推送函数
void smg_display(u8 dat[],u8 pos);
#endif
3.3.2 smg.c
#include "smg.h"
//共阴极数码管显示0~9的段码数据
u8 gsmg_code[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};
/*******************************************************************************
* 函 数 名 : smg_display
* 函数功能 : 动态数码管显示
* 输 入 : dat:要显示的数据
pos:从左开始第几个位置开始显示,范围1-8
* 输 出 : 无
*******************************************************************************/
void smg_display(u8 dat[],u8 pos)
{
u8 i=0;
u8 pos_temp=pos-1;
for(i=pos_temp;i<8;i++)
{
switch(i)//位选
{
case 0: LSC=1;LSB=1;LSA=1;break;
case 1: LSC=1;LSB=1;LSA=0;break;
case 2: LSC=1;LSB=0;LSA=1;break;
case 3: LSC=1;LSB=0;LSA=0;break;
case 4: LSC=0;LSB=1;LSA=1;break;
case 5: LSC=0;LSB=1;LSA=0;break;
case 6: LSC=0;LSB=0;LSA=1;break;
case 7: LSC=0;LSB=0;LSA=0;break;
}
SMG_A_DP_PORT=dat[i-pos_temp];//传送段选数据
delay_10us(100);//延时一段时间,等待显示稳定
SMG_A_DP_PORT=0x00;//消音
}
}
3.4 时钟代码编写(用于精准倒计时)
3.4.1 time.h
#ifndef _time_H
#define _time_H
#include "public.h"
//变量声明
extern u8 gmsec;//毫秒级定义
extern u16 gsec;//秒级定义
extern u16 last_gsec;//剩余时间存储
//时钟初始化
void time0_init(void);
//倒计时开始函数
void time0_start(u16 time_sec);
//倒计时停止函数
void time0_stop(void);
#endif
3.4.2 time.c
#include "time.h"
//变量定义
u8 gmsec=0;//ms级定义
u16 gsec=0;//秒定义
u16 last_gsec=0;//剩余时间存储
/*******************************************************************************
* 函 数 名 : time0_init
* 函数功能 : 定时器0中断配置函数,通过设置TH和TL即可确定定时时间
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void time0_init(void)
{
TMOD|=0X01;//选择为定时器0模式,工作方式1
TH0=0XDC; //给定时器赋初值,定时10ms
TL0=0X00;
ET0=1;//打开定时器0中断允许
EA=1;//打开总中断
TR0=0;//关闭定时器
}
/*******************************************************************************
* 函 数 名 : time0_start
* 函数功能 : 定时器0打开
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void time0_start(u16 time_sec)
{
TR0=1;//打开定时器
gsec = time_sec;//倒计时设定
}
/*******************************************************************************
* 函 数 名 : time0_stop
* 函数功能 : 定时器0关闭
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void time0_stop(void)
{
TR0=0;//关闭定时器
TH0=0X00;
TL0=0X00;
last_gsec = gsec;
gsec=0;
gmsec=0;//重置计时
}
void time0() interrupt 1 //定时器0中断函数
{
TH0=0XDC; //给定时器赋初值,定时10ms
TL0=0X00;
gmsec++;//10ms加1次
if(gmsec==100)//定时1秒
{
gmsec=0;
gsec--;//对秒计数
}
}
3.5 主程序代码编写
3.5.1 main.c
#include <reg52.h>
#include "time.h"
#include "key.h"
#include "smg.h"
#include "beep.h"
#include "public.h"
/*******************************************************************************
* 函 数 名 : answer_time
* 函数功能 : 选手回答倒计时,若成功暂停回答,若失败开启蜂鸣器
* 输 入 : error_plear上一阶段选手台号,time_sec倒计时时间
* 输 出 : 无
*******************************************************************************/
u8 answer_time(u8 error_plear,u16 time_sec)
{
u8 key = 0;
u8 time_buf[8];
if(error_plear == 0)//如果有选手提前抢答,直接跳过倒计时过程
{
return 0;
}
time0_start(time_sec);//开始计时
while (1)
{
key = key_matrix_ranks_scan();//获取矩阵按键按下的按键
if(key == ENTER)//回答正确或错误停止计时
{
time0_stop();//停止计时
while (1)
{
key = key_matrix_ranks_scan();//获取矩阵按键按下的按键
if(key == REST)//如果裁判按下重置则退出抢答成功剩余计时展示
{
return 0;
}
time_buf[0]=gsmg_code[error_plear];//选手编号
time_buf[1]=0x40;
time_buf[2]=0x40;
time_buf[3]=0x40;
time_buf[4]=gsmg_code[last_gsec/1000];
time_buf[5]=gsmg_code[(last_gsec%1000)/100];
time_buf[6]=gsmg_code[(last_gsec%100)/10];
time_buf[7]=gsmg_code[last_gsec%10]; //秒显示
smg_display(time_buf,1);//显示
}
}
else if(gsec <= 0)//倒计时结束
{
time0_stop();
while (1)
{
key = key_matrix_ranks_scan();//获取矩阵按键按下的按键
if(key == REST)//如果裁判按下重置则退出倒计时结束展示
{
return 0;
}
time_buf[0]=gsmg_code[error_plear];//选手编号
time_buf[1]=0x40;
time_buf[2]=0x40;
time_buf[3]=0x40;
time_buf[4]=gsmg_code[0];
time_buf[5]=gsmg_code[0];
time_buf[6]=gsmg_code[0];
time_buf[7]=gsmg_code[0];
smg_display(time_buf,1);//显示
beep_alarm(10,10);//蜂鸣器响
}
return 0;
}
else//刷新剩余时间
{
time_buf[0]=gsmg_code[error_plear];//选手编号
time_buf[1]=0x40;
time_buf[2]=0x40;
time_buf[3]=0x40;
time_buf[4]=gsmg_code[gsec/1000];
time_buf[5]=gsmg_code[(gsec%1000)/100];
time_buf[6]=gsmg_code[(gsec%100)/10];
time_buf[7]=gsmg_code[gsec%10];
smg_display(time_buf,1);//显示
}
}
}
/*******************************************************************************
* 函 数 名 : answer
* 函数功能 : 获取抢答成功的选手台号
* 输 入 : ready阶段的选手台号
* 输 出 : 若抢答成功返回选手台号,若上一阶段有选手提前抢答返回0
*******************************************************************************/
u8 answer(u8 error_plear)
{
u8 key = 0;
if(error_plear != 0)//如果有选手提前抢答,直接跳过抢答过程
{
return 0;
}
while (1)
{
key = key_scan(0);
if(key != 0)//有选手抢答成功返回选手编号
{
return key;
}
}
}
/*******************************************************************************
* 函 数 名 : ready
* 函数功能 : 判断是否有选手提前抢答
* 输 入 : 无
* 输 出 : 如果有选手抢答返回选手台号,若无返回0
*******************************************************************************/
u8 ready(void)
{
u8 key = 0;
u8 key_plear = 0;
u8 ready_buf[8];
ready_buf[0] = R;
ready_buf[1] = E;
ready_buf[2] = A;
ready_buf[3] = D;
ready_buf[4] = Y;
ready_buf[5] = 0x40;
ready_buf[6] = 0x40;
ready_buf[7] = 0x40;
while (1)
{
key = key_matrix_ranks_scan();//获取矩阵按键按下的按键
key_plear = key_scan(0);//检测是否选手提前抢答
if (key == ENTER)//裁判按下开始健
{
break;
}
else if(key_plear != 0)//有选手提前抢答
{
while(1)
{
key = key_matrix_ranks_scan();//获取矩阵按键按下的按键
if(key == REST)//如果裁判按下重置则退出提前抢答选手展示
{
break;
}
ready_buf[0] = gsmg_code[key_plear];
ready_buf[1] = 0x40;
ready_buf[2] = 0x40;
ready_buf[3] = E;
ready_buf[4] = R;
ready_buf[5] = R;
ready_buf[6] = O;
ready_buf[7] = R;
smg_display(ready_buf,1);//显示提前抢答选手和错误信息
beep_alarm(10,10);
}
return key_plear;
}
else//无选手抢答且裁判未按开始建
{
smg_display(ready_buf,1);//显示'READY'
}
}
return 0;
}
/*******************************************************************************
* 函 数 名 : countdown_t
* 函数功能 : 获取倒计时时间
* 输 入 : 无
* 输 出 : 倒计时时间
*******************************************************************************/
u16 countdown_t(void)
{
u8 key = 0;
u8 time_buf[8];//储存倒计时
u8 min = 0;//倒计时分定义
u16 sec = 0;//倒计时秒定义
while (1)
{
key = key_matrix_ranks_scan();//获取矩阵按键按下的按键
if(key == MINUP && min < 99)//分钟倒计时+1
{
min++;
beep_alarm(1000,10);//短暂提示音
}
else if(key == SECUP && sec < 99)//秒倒计时+1
{
sec++;
beep_alarm(1000,10);//短暂提示音
}
else if(key == REST)//重置倒计时
{
min = 0;
sec = 0;
beep_alarm(1000,10);//短暂提示音
}
else if(key == ENTER)//倒计时时间确定
{
beep_alarm(1000,10);//短暂提示音
break;
}
time_buf[0] = gsmg_code[min/10];
time_buf[1] = gsmg_code[min%10];//分
time_buf[2] = 0x40;//-
time_buf[3] = gsmg_code[sec/10];
time_buf[4] = gsmg_code[sec%10];//秒
time_buf[5] = 0x40;//-
time_buf[6] = 0x40;//-
time_buf[7] = 0x40;//-
smg_display(time_buf,1);//显示
}
sec = sec + min*60;
return sec;
}
/*******************************************************************************
* 函 数 名 : main
* 函数功能 : 主程序运行
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void main()
{
u8 answer_plear; //抢答选手
u16 time_sec; //倒计时时间
time0_init();
while(1)
{
time_sec = 0; //重置倒计时时间
answer_plear = 0; //重置成功抢答的选手
time_sec = countdown_t(); //获取倒计时时间
answer_plear = ready(); //准备函数
answer_plear = answer(answer_plear); //返回抢答成功的选手
answer_time(answer_plear,time_sec);
}
}
如有勘误欢迎指出,有疑问可在评论区回复,有时间将会就行解答😀文章来源:https://www.toymoban.com/news/detail-473526.html
开源文件地址:https://github.com/NBUT-NJY/Four-way-answerer.git文章来源地址https://www.toymoban.com/news/detail-473526.html
到了这里,关于基于51单片机的四路抢答器设计的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!