ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

竞赛抢答器设计:基于8255A与8086的微机接口实现

2026/9/19 9:05:22 拓冰建站 浏览量
竞赛抢答器设计:基于8255A与8086的微机接口实现 简介一份微机原理与接口技术课程设计报告面向计算机、电子类专业学生以竞赛抢答器为例完整展示基于AT89C51单片机的最小系统设计。资源共1个doc文档大小约691KB已有437人学习。报告从课程设计任务要求出发包含总体设计、硬件电路设计、软件设计、仿真调试、源程序与总结等模块覆盖八路抢答输入、优先编码与锁存、数码管显示、主持人控制、定时倒计时及扬声器报警等功能并具体给出抢答成功与违规时8253通道计数值设置、1KHz与0.5KHz鸣叫频率及12秒报警时间等实现细节。读者可借助该文档梳理方案论证、硬件搭建与程序调试的完整思路也可参考其架构和代码完成同类课程设计或实验。1. 竞赛抢答器在微机原理与接口技术里到底考什么拿到“竞赛抢答器”这个课程设计题目最常见的直觉是“八个按键接八个灯谁按下谁亮”。但放在微机原理与接口技术的语境里这个做法几乎拿不到分按键直接挂总线管不住输入缓冲多个键同时按下时逻辑混乱机械抖动会被当成多次抢答数码管还会被键扫描时序拖出闪烁。这一题真正要练的是把“按键检测 → 消抖确认 → 优先级仲裁 → 数字显示”串成一条可测试的链路用 8255A 这类可编程接口芯片把外设和 8086 总线隔离开。下面这套方案按 8086 最小系统加 8255A 展开从端口规划、消抖算法到状态机收尾直接落到 Proteus 仿真里验证。2. 用 8255A 把抢答键接到 CPU端口规划与地址译码2.1 为什么不用直连总线而是选用 8255A课程设计里常见的第一处错误是把按键直接接到 8086 的数据总线上然后用IN指令去读电平。8086 的数据总线是双向三态总线同一时刻只能有一个设备驱动它按键没有三态输出按下时是把引脚拉到固定电平松开时引脚悬空这会让总线上出现不确定状态甚至和正在访问存储器的时序冲突。更重要的是竞赛抢答器需要“输入缓冲 输出锁存”按键状态要被锁存住等待 CPU 读取显示数据也要在 CPU 不参与时持续保持。8255A 恰好把这两件事都封装好了在微机原理与接口技术的课程框架里它是比直连总线更合理、也更符合教学意图的选择。8255A 有三个 8 位端口可以分别配置为输入或输出。抢答器的输入量是若干路开关信号输出量是数码管段码和指示灯属于典型的“无握手静态信号”用方式 0基本输入输出就够了。方式 0 下没有选通信号和中断请求线初始化简单读端口拿到的就是引脚电平写端口输出的电平会一直锁存到下次写入。对抢答器来说我们既不需要双缓冲也不需要应答握手选方式 0 把有限的精力留给消抖与仲裁逻辑。2.2 端口分工A口输出、B口输出、C口输入下面是一份可以直接照做的端口分配方案。A 口接七段数码管的段码线工作方式设为输出B 口接位选线或状态指示灯也设为输出C 口低 4 位接 4 路抢答按键设为输入。C 口剩下 4 位可以接主持人开始键和复位键或者留作扩展。按键采用低有效接法按键一端接 C 口引脚另一端接地引脚外部接 10kΩ 上拉电阻到 5V这样没按下时读到高电平按下时读到低电平。8255A 端口方向用途接线对象A 口输出数码管段码共阴数码管 a ~ dpB 口输出位选 / 状态指示数码管位选端或 LEDC 口低 4 位输入抢答按键4 路按键 上拉电阻C 口高 4 位输出扩展指示灯主持人状态灯等端口地址由译码电路决定。8086 的 A0、A1 直接接 8255A 的 A0、A1其余地址线经过 74LS138 或简单逻辑译码产生片选信号。下面代码假设片选译码对应的基地址是 0200H那么 A、B、C 和控制寄存器的地址依次是 0200H ~ 0203H。这个基地址不是固定的不同开发板译码方式不同拿到实物板先查原理图或者用万用表量片选引脚再改代码里的 EQU 定义。; 8255A 端口地址定义基地址由译码器决定 PORT_A EQU 0200H PORT_B EQU 0201H PORT_C EQU 0202H PORT_CTL EQU 0203H ; 初始化 8255A控制字 81H 1000 0001B ; D71 表示控制字有效 ; D6D500 表示 A 组方式 0 ; D40 表示 A 口输出 ; D30 表示 C 口高 4 位输出 ; D20 表示 B 组方式 0 ; D10 表示 B 口输出 ; D01 表示 C 口低 4 位输入 MOV DX, PORT_CTL MOV AL, 81H OUT DX, AL ; 初始化显示输出先把数码管全部熄灭 MOV AL, 00H MOV DX, PORT_A OUT DX, AL MOV DX, PORT_B OUT DX, AL代码逻辑不复杂但控制字的每一位都要能对着口算出来。答辩时最常被问的就是“为什么是 81H 而不是 80H 或者 91H”关键就是 D0 这一位C 口低 4 位是输入D0 必须为 1。还要注意 8255A 复位后所有端口默认是输入状态此时向 A 口和 B 口写数据不会生效必须先写控制字再操作数据端口这个顺序不能颠倒。2.3 按键有效电平读端口之后为什么先取反在低有效按键接法下8255A 的 C 口读到 0xFF 表示没有任何按键按下。当某一路按下对应位变成 0其他位保持 1。这时候如果直接把读到的值拿去找“哪一位是 1”会发现一辈子都找不到。常见做法是读完后执行一次取反操作让按下的位变成 1松开和未接的位变成 0后续扫描逻辑只需要找第一个 1 就行。; 读取 C 口并转换成“按下位为 1”的格式 MOV DX, PORT_C IN AL, DX NOT AL ; 低有效转高有效这里有一个容易被忽略的点如果你把按键改成高有效接法按键接 VCC引脚经下拉电阻接地那么NOT AL必须删掉否则有效电平会反过来。课程设计报告中要把这个硬件约定写清楚因为后面消抖和仲裁的代码全部建立在“有效位为 1”这个约定之上。硬件接法变了软件第一行就要跟着变。3. 抢答核心逻辑键值表、软件消抖与优先级仲裁3.1 主循环轮询还是外部中断抢答器读键可以用两种方式主循环轮询 C 口或者用 8259A 把按键信号变成中断请求。课程设计我一般建议用轮询原因有两个。第一4 到 8 路抢答的主循环扫描周期可以控制在 10ms 以内对“抢答”这个场景已经足够快人手指的按下时间误差在几十毫秒以上轮询不会漏事件。第二轮询逻辑直白Proteus 仿真里每一步都可以单步跟踪消抖和仲裁过程一眼能看懂答辩时不用绕着弯解释中断服务程序和主程序之间的共享变量。中断方案的优点是 CPU 不必空转但 4 路按键共用一个中断源后服务程序里还是要回到端口读取状态做仲裁省掉的只是一段循环扫描时间换来的是共享数据区保护和嵌套中断问题。在课程设计的时间约束下轮询能把更多精力放到抢答器的核心问题上谁先按下、怎么确认、怎么锁存。3.2 消抖写进读键流程连续两次读取稳定机械按键按下和释放的瞬间触点会因弹性产生 5~10ms 的抖动电平在高低之间来回跳。如果不做处理一次按下可能被识别成十几次抢答数码管会像秒表一样乱跳。最简单的消抖办法是“第一次读到有效电平后延时 10ms 再读一次两次结果一致才确认”。关键点在于延时是为了避开抖动窗口不是为了凑时间。机械抖动集中在按下瞬间的前 5~10ms延时太短滤不干净延时太长又影响响应速度。10ms 是常见折中值如果按键老化严重可以提高到 20ms代价是系统对快速连按的响应变慢。抢答器场景下一生只抢一次20ms 完全可以接受。; 按键扫描出口 AL 有效按键位图1 表示按下 KEY_SCAN: MOV DX, PORT_C IN AL, DX NOT AL ; 低有效转高有效 MOV BL, AL ; 保存第一次读取结果 CALL DELAY_10MS ; 等待抖动消失 IN AL, DX NOT AL CMP AL, BL JNZ KEY_SCAN ; 两次不一致说明仍处于抖动或已释放 RET这段代码的逻辑是“连续两次稳定才算数”。第一次读到有效电平后不立即相信等 10ms 再读一次如果还是同样的按键组合才认为这是一次真实按下。两次不一致就重新开始扫描。DELAY_10MS 在 5MHz 时钟下大约是几千个空操作循环用汇编写多层嵌套循环或者调用现成的延时子程序都可以如果你的平台里有定时器用定时器计数实现延时精度更高。3.3 从按键位图到选手编号查表与逐位扫描消抖确认之后AL 里是一个位图比如 0000 0001 表示 1 号键按下0000 0010 表示 2 号键按下。但位图不能直接送去显示需要换算成选手编号。常见的做法是从最低位开始逐位右移每移动一次计数器加一遇到进位位为 1 时停止计数器的值加 1 就是选手编号。有效位图二进制值选手编号0000 000101H10000 001002H20000 010004H30000 100008H4; 入口 BL 有效按键位图出口 CL 选手编号(1~4) ; 若 BL 0则 ZF 标志被清除调用方需先判断是否有人抢答 KEY_NUMBER: MOV CL, 0 AGAIN: SHR BL, 1 ; 最低位移入 CF JC FOUND ; 遇到第一个 1认为是最小编号选手 INC CL CMP CL, 8 JB AGAIN FOUND: INC CL ; 从 0 基编号转换为选手编号 MOV DISPLAY_BUF, CL RET这段代码实现的是“低位优先仲裁”当两路按键同时有效时编号小的选手胜出。课程设计里大多数情况不需要更复杂的规则因为真实抢答中两次按下时间差只要超过消抖窗口位图里就只会保留先按下那条路的有效位。如果你要处理“同时按下算犯规”的赛制需要在仲裁前统计位图里 1 的个数超过一个就显示错误符号或重新开始。注意多路同时按下的物理情况很少但如果键位间距太近或者上拉电阻取值不当依然可能出现位图异常。建议在硬件上给每路按键单独接上拉电阻不要共用一颗否则一路按下会把相邻引脚拉低。4. 接口技术里的显示输出数码管动态扫描与刷新4.1 七段码表与显示缓冲区抢答结果要显示在数码管上。最直接的做法是把选手编号写入一个显示缓冲区变量 DISPLAY_BUF主程序每次刷新数码管时用这个编号去查七段码表再把查到的段码送到 A 口。七段码表以共阴数码管为准段码顺序是 a、b、c、d、e、f、g、dp高位到低位排列。显示数字共阴段码说明03FH无人抢答或超时106H1 号选手25BH2 号选手34FH3 号选手466H4 号选手56DH5 号选手67DH6 号选手707H7 号选手87FH8 号选手代码里把 DISPLAY_BUF 的 0 当“无抢答”处理显示 3FH 也就是数字 0。如果不想区分“0 号”和“无人抢答”可以把表首字节改成 00H 熄灭或者改成 40H 显示负号。我比较推荐显示 0原因是比赛前数码管全灭会让选手不知道系统是否上电显示 0 更直观。; 七段码表按显示数字 0~8 排列 SEG_TABLE: DB 3FH, 06H, 5BH, 4FH, 66H, 6DH, 7DH, 07H, 7FH ; 刷新数码管把 DISPLAY_BUF 的编号映射为段码 UPDATE_DISPLAY: MOV BX, OFFSET SEG_TABLE MOV AL, DISPLAY_BUF ; AL 选手编号或 0 XLAT ; AL SEG_TABLE[AL] MOV DX, PORT_A OUT DX, AL RETXLAT 是 8086 里专门用于查表的指令BX 指向表首地址AL 是表内偏移执行后 AL 变成该偏移处的数据。这里把 DISPLAY_BUF 当作表索引省去了编码转换的工作。注意 XLAT 只能处理 256 字节以内的表七段码表完全够用。4.2 动态扫描的刷新频率与位选码如果课程设计只要求显示一个选手编号上面这段代码已经够了。但很多题目会加一个“倒计时显示”或者“显示当前抢答状态”这时候要用到两位甚至四位数码管。动态扫描的原理是让所有数码管共用一组段码线轮到哪一位就把哪一位的公共端接通同时在这组段码线上输出该位的数据。人眼在刷新频率超过约 50Hz 时看不出闪烁工程上一般取 100~200Hz。每位数码管停留 1~2ms4 位一个刷新周期 4~8ms对应刷新率 125~250Hz。停留时间太短数码管亮度低太长能看出“扫过去”的感觉。位选码需要根据数码管是共阴还是共阳来设计共阴数码管位选端要输出高电平点亮共阳则相反。显示位数停留时间刷新率说明2 位2ms / 位250Hz亮度好推荐4 位1.5ms / 位约 166Hz信息量充足8 位1ms / 位125Hz亮度偏暗需要大电流驱动4.3 在抢答流程里把显示刷新和键扫描拆开动态扫描最容易踩的坑是显示刷新和按键扫描串在同一个循环里。按键扫描里有一个 10ms 的 DELAY_10MS如果这个延时期间不刷新显示数码管会灭 10ms再算上扫描、仲裁、显示输出肉眼能明显看到亮度抖动。我一般会用一个定时器中断作为显示时基用 8253/8254 定时器产生 1~2ms 的周期中断中断服务程序里只做一件事——切换位选并输出当前位的段码。主循环负责键扫描、消抖、仲裁和状态切换两者互不阻塞。; 定时器中断服务程序动态扫描切换一位 TIMER_ISR: PUSH AX PUSH DX ; 根据当前扫描位查 DISPLAY_BUF 得到段码 ; 向 A 口输出段码 ; 向 B 口输出位选码只点亮当前位 ; 修改扫描位指针指向下一位 POP DX POP AX IRET这个设计的核心价值是隔离显示刷新不再依赖主循环的执行速度主循环就算在消抖延时中停 10ms数码管也一直保持点亮。课程设计答辩时能把这个“为什么不用延时刷新”讲清楚相当于主动展示了定时器接口的学习成果比贴一段几百行的显示代码更有说服力。注意中断服务程序里不要做按键消抖也不要做仲裁。消抖需要跨多次中断累积状态放在中断里会让服务程序变长增加中断延迟。中断里只输出现成数据所有状态判断一律放主循环。5. 用状态机锁住抢答节奏状态表、自检与复核5.1 状态定义与转移表抢答器的主逻辑与其写成从上到下的一条长流程不如拆成状态机。状态机的好处是每个状态只处理自己的事非法操作不会让系统进入未知分支。下面是最小状态集合空闲、允许抢答、锁定显示。当前状态事件下一状态动作空闲主持人按下开始键允许抢答清除显示缓冲区允许扫描允许抢答某路按键有效锁定显示锁存选手编号到 DISPLAY_BUF允许抢答计时超时锁定显示DISPLAY_BUF 置 0锁定显示主持人按下复位键空闲清除标志停止扫码这个转移表把“开始”“抢答”“复位”三个动作完全隔开了。允许抢答状态只做键扫描锁定显示状态即使按键被按住也不会再次触发仲裁因为状态已经不在允许抢答的范围里了。主持人的开始键和复位键可以接在 C 口高位按低有效方式接入消抖逻辑与抢答键完全一致。5.2 用状态值实现转移分支状态变量可以定义为一个字节常量主循环每次循环开始先判断当前状态再执行对应分支。代码结构如下; 状态常量 ST_IDLE EQU 0 ST_ARMED EQU 1 ST_SHOW EQU 2 MAIN_LOOP: MOV AL, STATE ; STATE 是当前状态变量 CMP AL, ST_IDLE JZ DO_IDLE CMP AL, ST_ARMED JZ DO_ARMED CMP AL, ST_SHOW JZ DO_SHOW JMP MAIN_LOOP DO_IDLE: ; 读主持人开始键按下则 STATE ST_ARMED ; 同时显示 0 JMP MAIN_LOOP DO_ARMED: CALL KEY_SCAN ; 带消抖的键扫描 AND AL, 0FH ; 只保留低 4 位有效抢答键 JZ MAIN_LOOP ; 无人按下继续等待 CALL KEY_NUMBER ; 仲裁并写入 DISPLAY_BUF MOV STATE, ST_SHOW ; 进入锁定显示 JMP MAIN_LOOP DO_SHOW: ; 只刷新显示不再扫描抢答键 ; 检测复位键按下则 STATE ST_IDLE JMP MAIN_LOOP这段代码把状态转移收敛在几个 JMP 分支里每个状态的行为可以独立修改。允许抢答状态使用AND AL, 0FH是为了屏蔽掉 C 口高 4 位上可能接入的主持人按键避免高 4 位的状态干扰选手编号仲裁。如果做 8 路抢答这里应改成屏蔽高位的结果保持 8 位全部参与仲裁。5.3 对着时序图确认“按下一瞬”的行为状态机写完最后要做的是验证边界条件。先按住任意抢答键不放再给系统上电正常结果是不进入锁定状态因为开始键没按系统停在空闲态。然后按开始键再按某一路抢答键数码管显示对应编号后保持直到按复位键。最后测试两路同时按下看数码管是否稳定显示编号最小的那一路。Proteus 仿真里普通按键元件是理想开关不会产生真实抖动消抖代码可能一次就通过但这不能说明消抖逻辑正确。想验证消抖可以在按键引脚上用数字激励源发送一段 20ms 内多次翻转的脉冲模拟真实按键的抖动波形再观察状态机是否只在脉冲停止后才进入锁定显示。把这组激励保存成一个测试场景每次改完代码直接跑同一组输入比反复手工按键更可靠也更好向答辩老师说明测试方法。本文还有配套的精品资源点击获取