news 2026/9/19 19:33:20

基于AT89C51的八路抢答器设计与Keil/Proteus仿真调试

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于AT89C51的八路抢答器设计与Keil/Proteus仿真调试

简介:面向微机原理与接口技术课程设计的竞赛抢答器项目,是一份完整的课程设计文档,适合计算机、电子信息类专业学生完成同类综合实践时参考。文档围绕8路抢答器展开,覆盖总体设计、硬件电路、软件设计、仿真调试与源程序等模块,涉及AT89C51单片机、并行接口、七段数码管、译码电路、8253定时器及扬声器报警电路等核心内容。设计实现了逻辑开关K0—K7抢答输入、主持人开始键控制、选手编号锁存显示、违规检测与红绿指示灯提示,并给出抢答成功与违规时8253通道2不同计数值对应的鸣叫频率,以及30秒定时抢答等关键参数。资源共1个doc文件,大小691KB,便于直接查阅修改。已有437人学习下载,对该方向课设具有较好的参考价值。读者可借助完整方案和源程序梳理设计思路,也能为软硬件调试提供辅助。

1. 竞赛抢答器:把“谁先按下”变成硬事实的接口技术课设

八路抢答器的工程难点从来不在“检测按键”,而在两个选手几乎同时按下时,系统必须只认第一个,并且把这个结果锁住。这套基于AT89C51的方案,用P1口接8路抢答按键、P0/P2驱动三位七段数码管、T0定时器翻转P3.6驱动扬声器,把抢答锁存、违规判定、30秒抢答倒计时、60秒答题倒计时全部压缩在几百行汇编代码里。对微机原理与接口技术课设来说,它完整覆盖了并行I/O、定时器中断、动态扫描显示、按键消抖四个核心考点;对已经工作的人,值得拆的是它用软件锁存替代硬件锁存器、用T0翻转替代8253通道计数的等价替换思路。电路只有十几个元件,代码可以一行行读完,适合拿来复现。

2. 硬件方案与引脚资源:P1抢答、P0/P2显示、P3.6发声

2.1 AT89C51的资源分配与8253方案对比

这套设计选用AT89C51而不是74LS148加74LS279的纯硬件抢答器,原因在于题目同时要求抢答、违规检测、抢答倒计时、答题倒计时,纯逻辑电路搭起来相当复杂。AT89C51把这些统一到程序里,改规则只改代码,不用动电路。

任务书里给过一个参考指标:抢答成功时置8253通道2计数值为1000,扬声器频率为1MHz/1000=1kHz;失败时置2000,频率为0.5kHz。这是PC/XT体系里8253的用法,8253输入时钟1MHz,计数值直接决定输出方波频率。用51实现时8253被内部定时器T0替代,因为12MHz晶振经12分频后机器周期正好是1us,和8253的1MHz计数时钟一一对应,T0溢出中断里翻转P3.6就能得到同样效果。这个等价关系值得注意:8253计数值1000对应1ms溢出周期,换成T0初值就是65536-1000=64536,即FC18H。

引脚功能分配如下表,这张表是后面看代码和画原理图的索引,建议先对一遍。

引脚功能说明
P1.0~P1.7选手抢答按键按键接GND,按下为低电平,对应1~8号选手
P0.0~P0.7数码管段选共阴极段码输出,外接10k上拉排阻
P2.0~P2.2数码管位选低电平选中,三只数码管:编号、时间十位、时间个位
P3.0抢答开始键主持人按下后进入倒计时和抢答查询
P3.1停止/复位键中断当前倒计时,回到空闲状态
P3.2抢答时间调整进入抢答秒数设置
P3.3回答时间调整进入答题秒数设置
P3.4时间加1s调整状态下加1秒
P3.5时间减1s调整状态下减1秒
P3.6扬声器驱动经达林顿管放大后接喇叭

2.2 抢答按键为什么还要外接上拉

P1.0~P1.7作为输入口时,标准51的P1口是准双向口,内部自带弱上拉,理论上按键接地就能读到低电平。但原电路里每个按键仍外接10k电阻到+5V,原因有两个:一是外部上拉提供更确定的高电平,避免按键引线较长时引入的噪声被误判;二是按键按下瞬间的抖动会产生电平毛刺,外部上拉的阻值配合软件延时消抖,比只靠内部弱上拉稳定得多。

按键的接法是引脚一端接P1口,另一端接地,10k电阻在引脚和+5V之间。注意这里不是串联,如果串进回路,按下后P1口电位反而被拉高,读不到低电平。这个接法在Proteus里直接用BUTTON元件,一端接P1,另一端接地即可。

2.3 动态扫描显示与刷新周期

显示部分用三只共阴数码管,P0口输出段码,P2.0/P2.1/P2.2分别做位选。动态扫描的原理是同一时刻只点亮一位,轮询速度足够快时人眼看到的就是三个数字同时亮。DISPLAY子程序的顺序是:先送R3的段码、P2输出0xFE选中第一位,延时;再送R5的段码、P2输出0xFD选中第二位,延时;最后送R4的段码、P2输出0xFB选中第三位。

共阴数码管段码表是标准0~9:3FH、06H、5BH、4FH、66H、6DH、7DH、07H、7FH、6FH。原代码里还定义了0BH对应73H,用来显示字母P,空闲状态时屏幕显示PPP。

这里要提醒扫描延时的取值。DELAY子程序是两层循环,外层32H=18次,内层33H=0xAF=175次,DJNZ每条指令2个机器周期,算下来每一位显示大约6.4ms,三轮扫描接近19ms,刷新率刚过50Hz,肉眼看会有轻微闪烁。如果主循环里再插入扬声器发声这类长延时,单轮显示会被拉得更长,闪烁会更明显。解决办法是把33H的初值调小,或者把DISPLAY挪进T1中断按固定节拍驱动,后面的排错章节再展开。

2.4 扬声器驱动电路

P3.6输出的是方波,不能直接驱动喇叭,原理图里用两个小功率三极管接成达林顿结构,把电流放大到足以推动8Ω小喇叭。电阻R是限流电阻,防止基极电流过大。如果手边没有对管,用ULN2003代替也可以,P3.6进ULN2003的输入,输出端串喇叭接+5V。

提示:P3.6的翻转频率由T0初值决定,电路本身没有选频功能,音调高低完全由软件控制,这正是把发声功能交给定时器而不是硬件振荡器的原因。

3. 程序结构:T0发声、T1计时、主循环状态查询

3.1 定时器初始化:TMOD=11H与两个初值

程序初始化是理解整份源码的入口,核心是T0和T1两个定时器,分工是T0管发声、T1管计时。

MAIN: MOV R1,#30 ; 抢答时间默认 30s MOV R2,#60 ; 答题时间默认 60s MOV TMOD,#11H ; T0、T1 都设为模式 1,16 位定时器 MOV TH0,#0F0H ; T0 初值高 8 位 MOV TL0,#0FFH ; T0 初值低 8 位,约 3.84ms 溢出一次 MOV TH1,#3CH ; T1 初值高 8 位 MOV TL1,#0B0H ; 3CB0H=15536,溢出周期 50ms SETB EA ; 开总中断 SETB ET0 ; 允许 T0 中断 SETB ET1 ; 允许 T1 中断 SETB TR0 ; 启动 T0 SETB TR1 ; 启动 T1

TMOD=0x11表示T0和T1都工作在模式1,即16位定时器,溢出后需要软件重装初值。为什么不用模式2的8位自动重装:模式2最大定时只有256个机器周期,也就是256us,T1要定时50ms,必须用16位模式。计算过程是:12MHz晶振下机器周期为12/12MHz=1us,要定时50000us,初值N=65536-50000=15536,即0x3CB0,写入TH1和TL1。每次T1中断后,服务程序里重新写入3CB0H,溢出周期才能稳定在50ms。

R1保存抢答时间初值30,R2保存答题时间初值60。这两个寄存器是整份程序里被调整最频繁的变量,所有加减秒的按键程序改的都是它们。

3.2 T0翻转P3.6:发声频率与初值的关系

T0的中断服务程序很短,每次溢出判断RING标志位,为1时翻转P3.6:

T0INT: MOV TH0,#0F0H ; 重装初值 MOV TL0,#0FFH JNB RING,OUT ; RING=0 不发声 CPL P3.6 ; RING=1 翻转 P3.6,输出方波 OUT: RETI

RING是响铃标志位,位于位寻址区。置位后P3.6不断翻转,输出音频方波;清零后停在当前电平,不再发声。

发声频率由T0初值决定,公式为 f = 500000 / (65536 - N),单位Hz。这其实就是“机器周期1us、每次中断翻转半个周期”的折算结果。原代码给的F0FFH按公式算是130Hz左右,实际听到的是低频“嗡嗡”而不是清晰的“嘀嘀”。任务书要求的两个频率按表取值:

频率溢出周期16位初值十六进制
约130Hz(原代码F0FFH)3841us61695F0FFH
0.5kHz 报警音1000us64536FC18H
1kHz 成功提示音500us65036FE0CH
2kHz250us65286FF06H

要改成1kHz提示音,把T0初值改成FE0CH,中断服务程序里的重装值要同步改,否则只在第一次溢出后生效。成功音和违规音的差异就落在FC18H与FE0CH这一对初值上。

3.3 主循环状态机:空闲、倒计时、答题、违规

主循环是一个不断查询按键、刷新显示的大循环,运行路径可以看成四状态状态机:

状态进入条件显示内容行为
空闲上电或按下停止PPP查询P3.0开始键,同时查P1口违规
抢答倒计时主持人按P3.0编号位熄灭,时间从30s倒数每秒递减R6,查询P1口选手按键
答题倒计时选手按下抢答键显示选手编号,时间从60s倒数锁定P1口,不再查询抢答
违规锁定未开始时P1有按键PP与编号交替闪烁报警,直到按下停止

COUNT子程序是抢答和答题共用的倒计时入口。进入前把秒数放入R6,R3放选手编号,抢答阶段R3为0AH表示熄灭编号位。循环体做的事是:把R6除以10拆出十位和个位存入30H、31H,再搬到R5、R4交给DISPLAY;判断是否到最后5秒;检测停止键P3.1;如果是抢答阶段调用ACCOUT查询P1口。

ACCOUT是查询有效抢答的子程序,一组JNB依次判断P1.0到P1.7,查到低电平就跳到对应的TRUE1~TRUE8。TRUE子程序把R2的答题时间送入R6,把选手编号写入R3,再清掉OK标志。此后COUNT循环经过ACCOUT时不再查询抢答,实现“只认第一个按下的人”。整套锁存不是靠芯片,而是靠OK这个软件标志完成的。

3.4 倒计时与最后5秒断续报警

计时基础是T1中断,每50ms溢出一次:

T1INT: MOV TH1,#3CH MOV TL1,#0B0H INC R0 RETI

R0是50ms计数单元,COUNT主循环里判断R0的值。CJNE A,#14H是比较是否到20,到20次就是1秒,执行DEC R6把剩余秒数减1,再清零R0重新累计。R0等于10时先进一次半秒处理,用来制造断续报警:CLR RING让声音停、显示灭,到1秒时SETB RING恢复声音。整体逻辑如下:

MOV A,R6 SUBB A,#07H JNC LARGER ; 剩余秒数大于5,走正常倒计时 MOV A,R0 CJNE A,#0AH,FULL ; 不到0.5s,继续 CLR RING ; 0.5s 到,停响 AJMP CHECK FULL: CJNE A,#14H,CHECK ; 不到1s,继续 SETB RING ; 1s 到,响 MOV R0,#00H DEC R6 ; 倒计时减1秒 CHECK: JNB P3.1,QUIT ; 停止键按下,退出 ACALL DISPLAY AJMP RECOUNT

原代码里SUBB A,#07H之前没有显式CLR C,汇编中SUBB会把上一次运算的进位标志也减进去,第一次执行时进位不确定,判断会偏差一个数。稳妥做法是在SUBB前加CLR C。这个细节在仿真中偶尔表现为最后5秒提前或延后提醒,原因就在这里。

3.5 违规检测与数据锁存

主持人未按开始键时,主循环的NEXT段执行一组JNB P1.x查询,走的是FALSE1~FALSE8入口。每个FALSE子程序先把犯规号数写入R3,再跳到ERROR公共程序。

ERROR的处理逻辑值得单独看:

ERROR: MOV R0,#00H MOV TH1,#3CH MOV TL1,#0B0H MOV 34H,R3 ; 犯规号数暂存 34H HERE: MOV A,R0 CJNE A,#0AH,FLASH ; 0.5s 到则灭 CLR RING MOV R3,#0AH ; 熄灭编号位 MOV R4,#0AH MOV R5,#0AH AJMP CHECK1 FLASH: CJNE A,#14H,CHECK1 ; 1s 到则响 SETB RING MOV R0,#00H MOV R3,34H ; 取回犯规号数 MOV R5,#0BH MOV R4,#0BH ; 显示 PP CHECK1: JNB P3.1,QUIT1 ; 停止键退出 ACALL DISPLAY AJMP HERE

34H在RAM里暂存犯规号数,因为0.5s到1s之间R3被改写为0AH,不暂存就找不回号数了。显示效果是“PP加犯规号数”与“全灭”以0.5秒为节拍交替,RING同步在响与停之间切换,形成视觉和听觉同时报警。退出条件是P3.1按下,进入QUIT1后清RING和OK,回到START空闲状态。

这个违规分支还承担了“主持人未按复位又未按开始,选手抢答”的情况:只要P3.0没进过倒计时,P1口任何一个低电平都会被FALSE系列捕获。规则上所有违规都走同一个ERROR入口,只是号数不同。

4. Keil+Proteus仿真调试:从HEX到跑通的检查清单

4.1 Keil工程配置与HEX生成

先建工程,Keil uVision里新建项目,芯片选ATMEL的AT89C51,源文件用汇编编写,保存为asm后缀。编译前在Options for Target里切到Output页,勾选Create HEX File。这一步常被跳过:不勾选HEX,Proteus里芯片加载的是空文件,仿真看起来完全没反应。

汇编写代码时入口结构要写清楚:

ORG 0000H AJMP MAIN ORG 0003H AJMP INT0SUB ORG 000BH AJMP T0INT ORG 0013H AJMP INT1SUB ORG 001BH AJMP T1INT ORG 0040H MAIN:

ORG伪指令决定代码在ROM中的位置。0003H是INT0中断入口,000BH是T0,0013H是INT1,001BH是T1。四个中断向量之间间隔8字节,只能放一条AJMP跳转到实际服务程序。漏掉这些向量,中断一进来程序就跑飞。

4.2 Proteus连线与加载HEX

Proteus元件表需要AT89C51、CRYSTAL、CAP(30pF)、RES(10k)、BUTTON、7SEG-MPX3-CC,发声部分用SOUNDER或SPEAKER。连线时最容易被忽略的是EA引脚,必须接高电平,否则单片机执行外部程序存储器而不是内部Flash,现象是程序完全不运行。

双击AT89C51元件,在Program File里选编译出的hex文件,Clock Frequency填12MHz。复位电路用经典10uF电容串10k电阻接到RST,晶振两端各接30pF电容到地。P0口外接10k上拉排阻,这是动态扫描显示正常工作的前提,不加上拉的话段码高电平驱动能力不够,数码管亮度会明显偏低。

4.3 仿真排错表

把调试中常见的现象整理成表,按优先级排查:

现象可能原因排查手段
数码管全不亮P0没上拉;EA未接高;HEX没加载量P0口电平,确认HEX路径,检查EA接+5V
显示闪烁动态扫描周期太长;DELAY延时过大调小DELAY的33H初值,确认P2.0~P2.2全接上
某路按键没反应按键没接地;消抖延时与主循环冲突按下时量P1引脚是否变低
一按开始就判违规开始键误接地;主循环查询顺序错误单独测P3.0电平,确认开始后才进入COUNT
喇叭不响或音调太低T0初值不对;RING没置位示波器看P3.6是否有方波,对照3.2节改初值
倒计时不规律T1中断重装初值丢失;R0清零时机错检查T1INT是否每次重装3CB0H

最后提一个原代码里值得注意的隐患:INT0SUB这类时间调整子程序是从主循环用ACALL进入的,但子程序末尾用的是RETI。RETI是中断返回指令,会从堆栈弹出额外的程序状态字并重新开放中断,用错会导致返回地址错乱、程序跑飞。如果子程序是被主循环查询调用的,末尾应该用RET。把这几处RETI改成RET后,时间调整功能在仿真里会更稳定,这个bug在特定分支才会暴露。

5. 扩展:把八路抢答器改成可配置的多组竞赛系统

5.1 参数从“改初值”变成“运行时配置”

R1、R2里的30秒和60秒是直接写在MOV指令里的,改一次时间要重新编译烧录。常见做法是留出一段RAM区存放配置,开机先从EEPROM读入,调整按键修改的是RAM里的值,退出时再写回。AT89C51读写AT24C02这类I2C芯片需要软件模拟时序,SCL占用一个空闲口,SDA要注意与P3.6的发声功能冲突,优先选P1口剩余引脚或扩展8255解决。

5.2 用逻辑分析仪验证发声频率和倒计时精度

验证方法很直接:逻辑分析仪探头接P3.6,抓一段方波测周期,1kHz对应1000us周期,0.5kHz对应2000us,误差超过±5%就查晶振频率和T0重装初值。倒计时精度用T1验证:T1每50ms中断一次,把某个空闲口在中断里翻转,一分钟应数到1200个方波周期,偏差超过1秒就检查TH1/TL1重装值是否每次都被正确写入。

5.3 状态机改造:把散落的标志位换成状态字

原程序用OK、RING、R0等多个分散标志描述运行状态,逻辑链长,扩展时容易漏改。改成显式状态机是成本最低的重构:定义一个STATE寄存器,0=空闲、1=抢答倒计时、2=答题倒计时、3=违规、4=时间调整,每个状态对应一个跳转表入口。改造后用一条MOV STATE,#N切换状态,后续要加扣分显示、加分管理,只需要新增状态,不碰原有中断代码。

提示:改造时先保留T1的50ms计时框架,把显示和按键查询统一挂到主循环的DISPLAY节拍下,避免出现倒计时和显示不同步这类难查的问题。

如果只改一处,把违规后的停机条件从“主持人按停止键”改成“软件计时5秒自动复位”:在ERROR的CHECK1分支里加一个R7计数器,每个1秒周期加1,到5就跳QUIT1。这样违规后系统自己恢复,不用主持人干预,也更接近真实竞赛流程。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/19 19:31:15

Dify+Trae自动化生成数据清洗脚本

简介:本资源是一份面向Python开发者、数据分析师与数据科学家的实战型技术指南,聚焦利用大模型自动化生成数据预处理脚本的核心方法论。通过Trae调用Dify API构建「脚本生成助手」,实现从原始数据格式到目标格式的智能转换,覆盖Pr…

作者头像 李华
网站建设 2026/9/19 19:29:27

DeepSeek V4 灰度 API 报 401?TaoToken 这样改统一接口

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华