摘要:本设计提出了一个基于ST89C52单片机的八路竞赛抢答器系统,旨在为各类知识竞赛活动提供一个高效、准确的抢答解决方案。该系统具备八个独立的抢答输入通道,允许八组参赛队伍参与竞赛。系统核心采用ST89C52单片机,通过与外部电路的集成实现其功能。每个抢答按键连接到微控制器的输入端口,确保任一按键被按下时,信号能迅速传递至ST89C52单片机。内部程序根据预设逻辑快速响应,通过锁存电路锁定首个有效抢答,防止后续按键操作干扰结果。同时,系统能够迅速驱动数码管显示抢答成功的队伍信息倒计时时间,确保现场人员能够即时了解抢答结果。此外,系统还集成了蜂鸣器提示模块,以在抢答成功时发出提示音,增强现场的互动性和反馈效果。该抢答器系统的设计不仅提高了竞赛的公正性和透明度,还通过即时反馈提升了竞赛的观赏性和参与感。
关键词:单片机;ST89C52;抢答器

一、设计任务、目的和要求

本设计任务的核心目标是构建一个高效、可靠的八路竞赛抢答器系统,该系统基于微控制器技术,能够为八组参赛者提供独立的抢答输入通道。系统的主要功能包括实时监测抢答按键状态、准确识别首个有效抢答、通过显示模块实时展示结果以及发出声音提示以增强现场氛围。此外,系统设计还特别强调了抗干扰能力,确保在多个按键几乎同时被按下的情况下,只有第一次有效的抢答会被系统锁定和响应,从而避免后续的误判。本设计追求的不仅是技术的先进性和功能的完善性,还包括系统的稳定性、未来功能的可扩展性、操作的安全性以及整体成本的经济性。通过这些综合的设计要求,我们旨在提供一个不仅能满足当前竞赛需求,而且具备未来发展潜力的抢答器系统,以期在各类知识竞赛中发挥关键作用,提升竞赛的公正性、透明度和观赏性。

二、总体方案设计

该系统以ST89C52单片机为核心,构建了输入、输出、存储、显示和声音提示五大模块,旨在实现快速响应、准确显示和声音反馈等功能。如图1所示,硬件设计涵盖了微控制器的选型、输入通道的独立性、显示与声音模块的清晰度以及电源的稳定性。软件设计包括主控制程序、抢答检测算法、显示控制程序、声音控制程序和锁存电路控制程序的开发。此外,系统的抗干扰设计通过硬件和软件滤波确保了在复杂电磁环境下的稳定性。最后,系统测试通过单元测试、集成测试和现场测试验证了设计的有效性和可靠性,确保了系统能够满足实际竞赛活动中对抢答器的严格要求。

三、理论基础

1.微控制器编程理论

主程序基于ST89C52微控制器平台,这是嵌入式系统设计中最常用的微控制器之一。ST89C52微控制器以其简单的结构、较低的成本和强大的处理能力而受到青睐。程序利用了ST89C52的特有指令集和寄存器配置,通过C语言编写,实现了对I/O端口的直接操作和对中断、定时器等硬件资源的管理。

2.输入/输出接口操作

程序中大量使用了对特定I/O端口的位寻址操作,这是ST89C52微控制器的一个显著特点。通过定义’sbit’关键字,程序直接操作单个位,控制LED灯的状态和读取按键输入。这种直接的位操作是嵌入式系统设计中常用的技术,它允许开发者以最低的资源消耗实现对硬件的精确控制。

3.存储技术

程序中包含了对EEPROM的读写操作,这是非易失性存储器的一种,能够在断电后保持数据。EEPROM的使用基于其独特的特性,即可以在程序运行时修改存储的数据,这对于保存和恢复抢答器的配置(如倒计时时间)至关重要。

4.定时器和延迟实现

程序中的’delayms’函数利用了软件延迟的技术来实现精确的时间控制。这种技术虽然简单,但在嵌入式系统中非常有效,尤其是在硬件定时器资源受限的情况下。此外,程序通过控制TR0寄存器来启动和停止ST89C52的硬件定时器,这是实现精确计时功能的关键。

5. 中断服务程序

尽管主程序中没有直接的中断服务程序代码,但通过包含’INTERRUPT.h’头文件,程序预期将使用ST89C52的中断系统来响应外部事件,如按键输入。中断处理是嵌入式系统中实现实时响应的关键技术,它允许系统在执行常规任务的同时,快速响应外部事件。

6. 数码管显示控制

程序通过包含’SMG.h’头文件,预期将控制数码管显示。数码管作为七段显示设备,其控制涉及到数字逻辑和驱动技术,程序通过发送正确的信号到数码管的各个段,来显示特定的数字或字符。

7. 抢答逻辑处理

程序的核心在于抢答逻辑的处理,这涉及到状态机的概念。程序通过一系列状态变量(如’zhu_f’、’qiang_f’、’wan_f’)来跟踪当前的抢答状态,并根据这些状态来决定如何响应按键输入。状态机是控制理论中的一个基本概念,它在嵌入式系统中被广泛用于管理复杂的状态转换。

四、测试与分析

1. 测试目标与方法

本节的测试目标是验证抢答器系统的功能完整性和性能指标。测试方法包括硬件功能测试、软件逻辑测试和系统集成测试,以确保每个组件和整体系统都能稳定运行。

2. 硬件与软件测试

**硬件测试:**检查按键响应、LED显示和EEPROM数据保持,确保硬件组件正常工作。
**软件测试:**通过模拟操作验证功能逻辑,包括按键识别、显示更新和声音提示的准确性和及时性。

3. 性能评估

性能评估关注系统的响应时间和稳定性。通过实际场景模拟,测量系统从接收抢答信号到显示结果的总时间,并监控长时间运行下的系统表现。

4. 结果与优化

测试结果将用于分析系统性能,并识别任何潜在问题。基于分析,提出必要的优化措施,如改进硬件布局、优化软件算法等,以提升系统的整体性能和用户体验。
通过这些测试与分析,我们能够确保抢答器系统在实际应用中的高效性和可靠性,并为后续改进提供依据。

五、总结

1. 系统设计与实现

本项目成功设计并实现了一个基于ST89C52微控制器的八路竞赛抢答器系统,满足了竞赛活动中快速准确判断抢答情况的需求。系统通过模块化编程和硬件抽象技术,实现了抢答信号的精确捕捉、实时结果显示和声音提示,同时利用EEPROM实现了数据的非易失性存储。这些功能的实现,确保了系统在实际应用中的可靠性和有效性。

2. 性能测试与稳定性

在性能测试阶段,系统展现了出色的响应速度和稳定性。软件消抖技术和硬件滤波电路的结合有效提升了系统的抗干扰能力,保证了在连续操作中的准确性和稳定性。测试结果表明,系统能够在毫秒级别内识别首个有效的抢答信号,并迅速将结果反馈给现场,满足了竞赛的实时性要求。

3. 后续工作与展望

尽管系统已经实现了设计目标并表现出良好的性能,但仍有改进和优化的空间。未来的工作可以集中在进一步优化用户界面、探索更高效的数据处理算法、增强系统的扩展性以适应更多参赛队伍,以及提高系统的智能化水平。我们相信,通过不断的测试和优化,该系统将在实际应用中发挥更大的作用。

附录

1.main.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
#include<reg52.h>	
#include<SMG.h> //数码管显示相关的函数和定义
#include<EEPROM.h> //包含EEPROM读写操作的函数和定义
#include<INTERRUPT.h> //包含中断服务程序的函数和定义

#define uchar unsigned char
#define uint unsigned int


sbit key_zhu=P2^2;
sbit key_jia=P2^1;
sbit key_jian=P2^0;

sbit key_1=P1^1;
sbit key_2=P1^3;
sbit key_3=P1^5;
sbit key_4=P1^7;
sbit key_5=P3^1;
sbit key_6=P3^3;
sbit key_7=P3^5;
sbit key_8=P3^7;

sbit led_1=P1^0;
sbit led_2=P1^2;
sbit led_3=P1^4;
sbit led_4=P1^6;
sbit led_5=P3^0;
sbit led_6=P3^2;
sbit led_7=P3^4;
sbit led_8=P3^6;

uchar zhu_f=0;
uchar qiang_f=0;
uchar wan_f=0;
uchar time=30;
uchar T0_num;
uchar number;


void delayms(uint ms)
{
unsigned char i=100,j;
for(;ms;ms--)
{
while(--i)
{
j=10;
while(--j);
}
}
}

void bajing()
{
buzz=0;
delayms(10);
buzz=1;
delayms(10);
}


uchar qianda()
{
uchar temp=0;
if(key_1==0)
temp=1;
else
if(key_2==0)
temp=2;
else
if(key_3==0)
temp=3;
else
if(key_4==0)
temp=4;
else
if(key_5==0)
temp=5;
else
if(key_6==0)
temp=6;
else
if(key_7==0)
temp=7;
else
if(key_8==0)
temp=8;
return temp;
}

void led_kongzhi(uchar num,bit k,bit mode) //num是led编号,k=0灯亮,k=1灯灭,mode是否所有灯熄灭
{
if(mode==0)
{
led_1=1;
led_2=1;
led_3=1;
led_4=1;
led_5=1;
led_6=1;
led_7=1;
led_8=1;
}
if(k==1)
{
switch (num)
{
case 1:led_1=0;
break;
case 2:led_2=0;
break;
case 3:led_3=0;
break;
case 4:led_4=0;
break;
case 5:led_5=0;
break;
case 6:led_6=0;
break;
case 7:led_7=0;
break;
case 8:led_8=0;
break;
}
}
else
{
switch (num)
{
case 1:led_1=1;
break;
case 2:led_2=1;
break;
case 3:led_3=1;
break;
case 4:led_4=1;
break;
case 5:led_5=1;
break;
case 6:led_6=1;
break;
case 7:led_7=1;
break;
case 8:led_8=1;
break;
}
}
}

void scan(void)
{
uchar qiangda_num=0; //用于存储抢答按钮按下时的编号

if(key_zhu==0) // key_zhu 是否被按下
{
delayms(7);
if(key_zhu==0&&zhu_f!=2&&qiang_f==0&&wan_f==0)
{
zhu_f++;
if(zhu_f==2)
{
TR0=1;
wan_f=0;
bajing();
}
}
else
if(key_zhu==0&&qiang_f==1)
{
buzz=1;
qiang_f=0;
number=0;
time=EEPROM_read(0x2000);
led_kongzhi(0,0,0);
}
else
if(key_zhu==0&&wan_f==1)
{
buzz=1;
wan_f=0;
number=0;
time=EEPROM_read(0x2000);
led_kongzhi(0,0,0);
}
else
if(key_zhu==0&&zhu_f==2)
{
TR0=0;
zhu_f=1;
bajing();
}
while(!key_zhu);
}


if(key_jia==0&&zhu_f==1&&qiang_f==0) //增加时间键(key_jia)扫描
{
delayms(7); //如果 key_jia 被按下,并且 zhu_f == 1、qiang_f == 0,则增加 time 的值,并更新EEPROM和显示。
if(key_jia==0)
{
if(time<99)
time++;
EEPROM_delete(0x2000);
EEPROM_write(0x2000,time);
display(time/10,time%10,10,0);
}
}


if(key_jian==0&&zhu_f==1&&qiang_f==0) //减少时间键(key_jian)扫描:
{
delayms(7); //如果 key_jian 被按下,并且 zhu_f == 1、qiang_f == 0,则减少 time 的值,并更新EEPROM和显示。
if(key_jian==0)
{
if(time!=0)
time--;
EEPROM_delete(0x2000);
EEPROM_write(0x2000,time);
display(time/10,time%10,10,0);
}
}

qiangda_num=qianda();
if(qiangda_num!=0) //抢答键(qianda)扫描:
{
if(zhu_f!=2&&qiang_f==0&&wan_f==0)
{
zhu_f=1;
buzz=0;
qiang_f=1;
number=qiangda_num;
led_kongzhi(number,1,0);
}
else
if(zhu_f==2&&wan_f==0)
{
wan_f=1;
zhu_f=0;
TR0=0;
number=qiangda_num;
led_kongzhi(number,1,0);
bajing();
}
}
}


void main()
{
uchar i=0;
T0_init(); //初始化定时器0
if(key_jian==0) //检查按键(减键是否按下)
{
delayms(100);
if(key_jian==0)
{
EEPROM_delete(0x2000);
EEPROM_write(0x2000,30);
}
}
time=EEPROM_read(0x2000);
led_kongzhi(0,0,0); //使所以灯熄灭
while(1)
{
scan(); //不断检查按键的状态
if(zhu_f==0&&wan_f==0)
display(10,10,10,10); //显示函数
else
if(zhu_f==0&&wan_f==1)
display(time/10,time%10,10,number);
else
if(zhu_f!=0)
{
if(qiang_f==1)
display(11,11,10,number);
else
display(time/10,time%10,10,number);
}
}
}

2.EEPROM.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
/*************************************************************
单片机内部EEPROM头文件

实现功能:单片机内部EEPROM的控制

补充说明:
***************************************************************/
#ifndef _EEPROM_H_
#define _EEPROM_H_
#include<reg52.h>
#include<intrins.h>

#define uchar unsigned char
#define uint unsigned int

/*****************STC内部EEPROM控制寄存器定义*******************/
sfr ISP_DATA = 0xe2;
sfr ISP_ADDRH = 0xe3;
sfr ISP_ADDRL = 0xe4;
sfr ISP_CMD = 0xe5;
sfr ISP_TRIG = 0xe6;
sfr ISP_CONTR = 0xe7;


/*******************STC内部EEPROM函数定义***********************/
void EEPROM_delete(uint addr); //擦除一个扇区
void EEPROM_write(uint addr,uchar dat);//往addr地址写入数据
uchar EEPROM_read(uint addr); //向addr地址读取一个数据
void ISP_off(); //关闭ISP操作
/***************************************************************
函数:擦除某一扇区(每个扇区512字节)
入口:addr = 某一扇区首地址
***************************************************************/
void EEPROM_delete(uint addr)
{
// 打开 IAP 功能(ISP_CONTR.7)=1:允许编程改变Flash, 设置Flash操作等待时间
// 0x83(晶振<5M) 0x82(晶振<10M) 0x81(晶振<20M) 0x80(晶振<40M)
ISP_CONTR = 0x81;
ISP_CMD = 0x03; // 用户可以对"Data Flash/EEPROM区"进行扇区擦除
ISP_ADDRL = addr; // ISP/IAP操作时的地址寄存器低八位,
ISP_ADDRH = addr>>8; // ISP/IAP操作时的地址寄存器高八位。
EA =0;
ISP_TRIG = 0x46; // 在ISPEN(ISP_CONTR.7)=1时,对ISP_TRIG先写入46h,
ISP_TRIG = 0xB9; // 再写入B9h,ISP/IAP命令才会生效。
_nop_();
EA =1;
ISP_off(); // 关闭ISP/IAP
}
/***************************************************************
函数:写一字节
入口:addr = 扇区单元地址 , dat = 待写入数据
***************************************************************/
void EEPROM_write(uint addr,uchar dat)
{
ISP_CONTR = 0x81;
ISP_CMD = 0x02; // 用户可以对"Data Flash/EEPROM区"进行字节编程
ISP_ADDRL = addr;
ISP_ADDRH = addr>>8;
ISP_DATA = dat; // 数据进ISP_DATA
EA = 0;
ISP_TRIG = 0x46;
ISP_TRIG = 0xB9;
_nop_();
EA =1;
ISP_off(); // 关闭ISP/IAP
}
/***************************************************************
函数:读一字节
入口:addr = 扇区单元地址
出口:dat = 读出的数据
***************************************************************/
uchar EEPROM_read(uint addr)
{
uchar dat;

ISP_CONTR = 0x81;
ISP_CMD = 0x01; // 用户可以对"Data Flash/EEPROM区"进行字节读
ISP_ADDRL = addr;
ISP_ADDRH = addr>>8;
EA = 0;
ISP_TRIG = 0x46;
ISP_TRIG = 0xB9;
_nop_();
dat = ISP_DATA; // 取出数据
ISP_off(); // 关闭ISP/IAP
EA =1;
return dat;
}
/***************************************************************
函数:关闭ISP/IAP操作
***************************************************************/
void ISP_off()
{
ISP_CONTR = 0; // 关闭IAP功能
ISP_CMD = 0; // 待机模式,无ISP操作
ISP_TRIG = 0; // 关闭IAP功能, 清与ISP有关的特殊功能寄存器
ISP_ADDRH = 0;
ISP_ADDRL = 0;
}
/**********************************************************************************************
STC89C52RC内部EEPROM详细地址表:
第一扇区 第二扇区 第三扇区 第四扇区
起始地址 结束地址 起始地址 结束地址 起始地址 结束地址 起始地址 结束地址
2000h 21FFh 2200h 23FFh 2400h 25FFh 2600h 27FFH

第五扇区 第六扇区 第七扇区 第八扇区
起始地址 结束地址 起始地址 结束地址 起始地址 结束地址 起始地址 结束地址
2800h 29FFh 2A00h 2BFFh 2C00h 2DFFh 2E00h 2FFFh
**********************************************************************************************/
#endif

3.INTERRUPT.H

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
/*************************************************************
单片机中断头文件

实现功能:单片机中断的控制控制

补充说明:
***************************************************************/
#ifndef _INTERRUPT_H_
#define _INTERRUPT_H_
#include<reg52.h>

#define uchar unsigned char
#define uint unsigned int

/**********************引脚定义************************/
sbit buzz=P2^3; ///蜂鸣器

/**********************变量定义************************/
extern uchar zhu_f; //外部变量
extern uchar wan_f;
extern uchar time;
uchar T0_num;

/*********************************************************
函数名称:void T0_init()
函数作用:定时器0初始化函数
参数说明:
*********************************************************/
void T0_init()
{
EA=1; //开总中断
ET0=1; //定时器T0中断允许
TMOD=0x01; //使用定时器T0的模式1
TH0=(65536-50000)/256; //定时器T0的高8位赋初值
TL0=(65536-50000)%256; //定时器T0的低8位赋初值
TR0=0; //关闭定时器
}

/*********************************************************
函数名称:void T0_interrupt(void) interrupt 1 using 0
函数作用:定时器0中断处理函数
参数说明:
*********************************************************/
void T0_interrupt(void) interrupt 1 using 0
{
TH0=(65536-50000)/256; //定时器T0的高8位重新赋初值
TL0=(65536-50000)%256; //定时器T0的低8位重新赋初值

T0_num++; //中断次数+1
if(time<=5) //比赛最后5S,蜂鸣器发出滴滴鸣叫声
buzz=~buzz;
if(T0_num==20) //中断20次,表示20*50ms=1S
{
T0_num=0; //重置中断次数
time--; //比赛时间-1S
}
if(time==0) //比赛时间倒计时为0S时
{
TR0=0; //关闭定时器
zhu_f=0; //重新回到开始状态
buzz=1; //关闭蜂鸣器
wan_f=1; //标记本次抢答完成
}
}
#endif

4.SMG.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
/*************************************************************
数码管头文件

实现功能:数码管的控制

补充说明:
***************************************************************/
#ifndef _SMG_H_
#define _SMG_H_
#include<reg52.h>

#define uchar unsigned char
#define uint unsigned int

/*****************数码管引脚定义*******************/
#define duan P0
sbit w1=P2^4;
sbit w2=P2^5;
sbit w3=P2^6;
sbit w4=P2^7;

/*****************数码管变量定义*******************/
uchar code wei1[]={0xa0,0xbb,0x62,0x2a,0x39,0x2c,0x24,0xba,0x20,0x28,0x7f,0x74}; //数码管显示段码
uchar code wei2[]={0x80,0x9b,0x42,0x0a,0x19,0x0c,0x04,0x9a,0x00,0x08,0x5f,0x74}; //数码管显示段码,带小数点
/*****************数码管函数定义*********************/
void Delay(unsigned int num); //延时函数
void display(uchar a,uchar b,uchar c,uchar d); //显示函数

/********************************************************
函数名称:void Delay(unsigned int num)
函数作用:US延时函数
参数说明:
********************************************************/
void Delay(unsigned int num)
{
while( --num ) ;
}

/********************************************************
函数名称:void display(uchar a,uchar b,uchar c,uchar d)
函数作用:数码管显示函数
参数说明:a:百位,b:十位,c:个位,d:十分位
********************************************************/
void display(uchar a,uchar b,uchar c,uchar d)
{
duan=wei1[a];//不带小数点
w1=0;
Delay(300);
w1=1;

duan=wei1[b];//不带小数点
w2=0;
Delay(300);
w2=1;

duan=wei1[c];//带小数点
w3=0;
Delay(300);
w3=1;

duan=wei1[d];//不带小数点
w4=0;
Delay(300);
w4=1;
}

#endif