ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

8255A方式0实验复现:控制字、端口地址与数码管动态扫描

8255A方式0实验复现:控制字、端口地址与数码管动态扫描

简介:面向微机接口技术课程学习者的8255A基础实验报告,围绕Intel可编程并行接口芯片在方式0下的典型应用展开,适合正在做实验、写实验报告或准备课程设计的学生对照参考。文档完整记录三个实验:用A口输出学号后两位驱动8个LED静态显示;通过B口读取8个逻辑开关状态,再由A口回送到LED;借助段码与位选控制七段数码管,显示另一名同学的学号。每个实验均配有硬件连线图、程序流程图、带详细注释的汇编源代码、实验现象与结论以及问题分析,可直接作为报告模板与调试参照。整包仅1个docx文件,约141KB,轻量易查阅。目前已有809人学习下载。读者可从中理清8255A三个端口的分组与控制字设置思路,掌握方式0输入输出的初始化与读写流程,并借鉴实验现象的记录写法,减少连线与程序调试中的弯路。

1. 实验箱上电后,那份六页双面打印的报告到底能复现什么

实验箱上电,八个 LED 全灭,你手里那份微机接口 8255A 实验报告只有六页双面打印,三个实验的代码加起来不到一百行。可真到自己接线、改端口地址、调延时的时候,卡住的地方往往不在代码本身——控制字写错一位,现象就是灯全亮或者全灭;位选移位方向搞反,数码管就从右往左串。这份报告对应的是 8088 系统下 8255A 方式 0 的基础训练:A 口驱动 LED、B 口读开关、A/B 口配合做七段数码管动态扫描。它适合正在上微机接口课、需要把实验箱跑通的人,也适合隔了几年想快速回忆 280H~283H 地址分配和控制字位定义的人。下面按“原理立住—代码复现—时间账—避坑”的顺序拆,接线和代码都能直接照着来。

2. 8255A 的地址与控制字:先把 280H/283H 和 80H 讲透

代码只有几十行,但决定能不能跑通的是两件事:端口地址对不对、控制字每一位什么意思。这两件事错了,现象会非常“玄学”——灯全亮、全灭、或者只有一位乱闪,你盯着代码看半天也看不出问题。所以先不贴代码,把地址译码和控制字逐位拆开,后面三个实验才有落脚点。8255A 内部有 A、B、C 三个 8 位数据端口和一个控制寄存器,对外只用 A0、A1 两根地址线做片内选择,片选信号来自高位地址译码。实验箱把这片 8255A 映射到 280H 起始的四个连续地址,于是才有了代码里反复出现的 280H、281H、283H。

2.1 三个端口四个地址:280H~283H 是怎么分出来的

A0、A1 的组合直接决定选中哪个寄存器,这是硬件决定的,改不了。把这两根线理解成“寄存器编号”就行:

A1A0选中对象常见地址读/写用途
00A 口数据寄存器280H输出 LED 数据、输出位选
01B 口数据寄存器281H读开关、输出段码
10C 口数据寄存器282H本组实验未直接用
11控制寄存器283H写方式控制字

高位地址经译码后产生片选,只有片选有效时这四个地址才真正落到 8255A 上。实验箱上通常有地址跳线或固定的 I/O 译码电路,报告里默认就是 280H 这一组。你自己接线前先确认实验箱说明书上的基地址,如果不是 280H,代码里所有 DX 赋值都要跟着换,这是最常见的第一道坎。C 口在这三个实验里没有单独接线,但它在控制字里仍要参与配置,因为 A 组包含 C 口高四位、B 组包含 C 口低四位,配置时躲不开。

还有一点容易被忽略:地址是字节地址,端口是字节宽。8088 用OUT DX, AL一次送一个字节,正好对应 8 位 LED 或 8 位开关,不需要拆字操作。这也是方式 0 最适合入门实验的原因——没有握手、没有中断,写进去就输出,读进来就是当前电平。

2.2 控制字 D7~D0 逐位拆:80H、82H 到底在说什么

8255A 的方式控制字是一个字节,D7 是特征位。D7=1 表示这是方式设置字;D7=0 表示这是 C 口按位置位/复位字,两者不能混。实验里用到的都是 D7=1 的方式设置字,逐位含义如下:

位名称取值含义
D7特征位必须为 1,表示方式设置字
D6 D5A 组方式00=方式 0,01=方式 1,1x=方式 2
D4A 口方向1=输入,0=输出
D3C 口高四位方向1=输入,0=输出
D2B 组方式0=方式 0,1=方式 1
D1B 口方向1=输入,0=输出
D0C 口低四位方向1=输入,0=输出

注意 D4 和 D1 的方向定义:1 是输入,0 是输出,很多教材的表格排版容易看反,血泪经验是写代码前先默念一遍“1 进 0 出”。按这张表回看报告里的两个值:

  • 80H = 1000 0000B:A 组方式 0、A 口输出、C 高输出;B 组方式 0、B 口输出、C 低输出。整体全输出,对应 LED 静态显示实验。
  • 82H = 1000 0010B:A 组方式 0、A 口输出、C 高输出;B 组方式 0、B 口输入、C 低输出。A 出 B 进,对应开关量回显实验。
  • 报告里写的10000010B就是 82H,只是换成了二进制写法,本质一样。

数码管实验里用的10000000B又是 80H,和 LED 静态实验相同,因为段码和位选都从 A、B 口往外送,全部配置成输出即可。控制字只需要在程序开头写一次,循环体里不要再重复写,重复写不会报错,但会白白占掉指令周期,动态扫描时尤其要避免。

2.3 方式 0 的读写时序:为什么必须先写控制字再碰数据口

8255A 复位后所有端口默认是方式 0 输入,控制寄存器内容为 0。也就是说,上电后你不写控制字,A、B、C 三口全是输入,这时候往 280H 写数据是写不进去的,LED 不会有任何反应。这是初学者最常翻的车:代码里地址没错、数据也没错,就是灯不亮,原因就是漏了那三行初始化。

方式 0 是基本输入/输出,没有握手信号,OUT指令把数据送到数据总线,8255A 在写信号有效沿把数据锁进对应端口寄存器,引脚上立刻反映出来;IN指令则把引脚电平锁进端口寄存器再读到 AL。整个过程中 A0、A1 决定去哪个寄存器,片选决定访不访问这片芯片,读写信号决定方向。顺序上一定是:先往 283H 写控制字定方向,再往 280H/281H 读写数据。

还有一个细节:控制字写完之后到第一次读数据口之间,建议留几条指令的间隔。8088 的总线周期和 8255A 的建立时间在实验箱的低速环境里通常够用,但如果你把初始化放在循环里反复执行,偶发的方向切换毛刺会让输出闪一下。稳妥做法是初始化只做一次,主循环只做数据搬运。

3. 三个实验的代码复现:从 LED 静态到数码管动态扫描

原理清楚之后,三个实验其实是同一套骨架的三种变体:初始化 8255A、往数据口写或从数据口读、需要时加循环。差别只在控制字、数据来源和是否要动态刷新。下面按报告里的顺序逐个复现,代码保留原结构,注释补全关键参数,每段后面说明逻辑和可调的地方。汇编环境按 MASM 的段定义写,END START告诉汇编器入口标号。

3.1 LED 静态显示:把学号后两位变成一个字节写进 A 口

DATA SEGMENT ID DB 01H ; 学号后两位对应的二进制值,01H = 00000001B DATA ENDS CODE SEGMENT ASSUME CS:CODE, DS:DATA START: MOV AX, DATA MOV DS, AX ; 初始化数据段 MOV AL, 80H ; 控制字:A组方式0、A口输出、B组方式0、B口输出 MOV DX, 283H ; 控制寄存器地址 OUT DX, AL ; 写入控制字,确定端口方向 MOV AL, ID ; 取学号字节,直接寻址取变量内容 MOV DX, 280H ; A口数据地址 OUT DX, AL ; 送 A 口,8 个 LED 显示对应位 MOV AH, 4CH INT 21H ; 返回 DOS CODE ENDS END START

这段代码的骨架只有“初始化—取数—输出”三步。关键参数是ID:报告里写01,对应二进制00000001B,所以只有最低位为 1。现象描述“前七个不亮、最后一个亮”,说明实验箱上 LED7~LED0 与数据位 D7~D0 的对应关系是 D0 对应最右边那个灯,输出高电平点亮。你自己做的时候先确认 LED 是共阴还是共阳、高电平亮还是低电平亮,如果是低电平亮,输出值要取反,否则现象正好相反。

这里有个容易踩的坑:学号后两位是十进制数字,比如“01”“27”,不能直接当成 BCD 码往端口送。01H在汇编里是十六进制 1,对应的二进制是00000001B,这才是 LED 要的位模式。如果你写DB 01,多数汇编器按十进制处理,值还是 1,但换成DB 27就是十进制 27,二进制00011011B,和“学号 27”的 BCD 码0010 0111B不是一回事。正确做法是先把两位十进制学号转成二进制数,再送端口;报告里的01恰好数值小,两种理解碰巧一致,别被这个巧合带偏。

3.2 开关量回显:B 口读、A 口写的轮询骨架

DATA SEGMENT DATA ENDS CODE SEGMENT ASSUME CS:CODE, DS:DATA START: MOV AL, 82H ; 控制字:A口输出、B口输入、均为方式0 MOV DX, 283H OUT DX, AL ; 初始化方向 LP1: MOV DX, 281H ; B口数据地址 IN AL, DX ; 读 8 个开关状态到 AL MOV DX, 280H ; A口数据地址 OUT DX, AL ; 原样送 A 口驱动 LED JMP LP1 ; 无限轮询 CODE ENDS END START

控制字82H把 A 口设成输出、B 口设成输入,这是整个实验的核心。LP1循环里先IN后OUT,没有延时也没有判断,开关拨一下,下一次循环 LED 就跟着变,实时性取决于循环执行速度,8088 上这个循环是微秒级,肉眼看起来就是即时跟随。

参数上要注意两点。第一,B 口读进来的值是不是和 LED 位序一致。开关通常一端接地、一端接端口,拨到 ON 时读回 0 还是 1,取决于接线是下拉还是上拉。如果发现开关拨上和 LED 亮灭相反,别急着改代码,先看实验箱开关电路的有效电平,必要时在IN之后加一条NOT AL取反。第二,A 口输出和 B 口输入的地址别写反,281H 是 B 口数据口,不是控制口,报告里那句“写 8255A 控制 B 口地址”的注释是笔误,实际访问的是 B 口数据寄存器,控制口始终是 283H。

这个轮询结构可以直接扩展成“读 C 口某几位、判断后输出到 A 口”的小逻辑,比如用两个开关控制 LED 的流水方向。扩展时控制字要相应调整 C 口方向位,别只改数据口不改控制字。

3.3 七段数码管动态扫描:段码表、位选和延时三件套

DATA SEGMENT NO DB 0,0,1,5,0,7,0,2 ; 八位数码管要显示的学号,从最高位到最低位 LED DB 3FH,06H,5BH,4FH,66H,6DH,7DH,07H,7FH,6FH ; 共阴极 0~9 段码 DATA ENDS CODE SEGMENT ASSUME CS:CODE, DS:DATA START: MOV AX, DATA MOV DS, AX MOV AL, 80H ; A、B 口全输出,方式0 MOV DX, 283H OUT DX, AL FLAG0: MOV DI, 0 ; DI 指向学号数组当前位 MOV CL, 8 ; 共 8 位数码管 MOV AH, 10000000B ; 位选初值,B7=1 选中最高位数码管 FLAG1: MOV AL, NO[DI] ; 取当前位要显示的数字 0~9 LEA BX, LED ; BX 指向段码表基址 XLAT ; AL = [BX+AL],查表得到段码 MOV DX, 281H OUT DX, AL ; 段码送 B 口 MOV AL, AH MOV DX, 280H OUT DX, AL ; 位选送 A 口,只有一位为 1 MOV AL, CL ; 保存外层循环计数 MOV CX, 0FFH ; 延时初值,可调 FLAG2: LOOP FLAG2 ; 软件延时,让当前位亮够时间 MOV CL, AL ; 恢复外层计数 ROR AH, 1 ; 位选循环右移,换到下一位 INC DI ; 指向学号下一位 LOOP FLAG1 ; 8 位扫完一轮 JMP FLAG0 ; 从头再扫 CODE ENDS END START

这段是三个实验里唯一需要“时间管理”的。段码送 B 口、位选送 A 口,同一时刻只有一位位选为 1,八个数码管轮流点亮,靠人眼视觉暂留拼成完整学号。XLAT指令用 AL 作索引、BX 作表基址,取出LED表里的段码,索引就是NO[DI]里的 0~9。ROR AH,1让位选从10000000B逐次右移到00000001B,对应从最高位数码管扫到最低位。

延时循环MOV CX,0FFH加LOOP FLAG2是报告里明确提到的关键点。不加延时,位选切换太快,前一位的段码还没从数码管上消失就切到下一位,会出现“后一位从影”,看起来像数字叠在一起。加上 255 次空循环后,每一位点亮时间拉长到肉眼无法分辨切换,显示就稳定了。这个值不是固定的,实验箱时钟、数码管余辉、环境亮度都会影响,0FFH不够就加到1FFH或2FFH,但加太大整屏会闪。

另外要提醒一个报告自身的不一致:NO数组第四位是5,而现象记录写的是“0、0、1、6、0、7、0、2”。按代码走,第四位应该显示 5。复现时以代码和段码表为准,报告里的现象记录可能是笔误,别为了对上文字去改数组,那样反而把正确的显示该坏了。

4. 动态扫描的时间账:延时循环、重影与刷新率估算

动态扫描能骗过眼睛,靠的是刷新率和占空比两个参数配合。刷新率太低会闪,占空比太低会暗,位选切换不留死区会串影。这一章把这三个量拆开算一遍,你调延时的时候心里就有数,而不是靠反复烧程序试。

4.1 重影从哪来:位选切换与段码保持的时间窗口

重影的本质是“该灭的时候没灭干净”。动态扫描的时序是:送段码、开位选、延时、关位选、换下一位。如果关位选和送新段码之间没有间隔,或者延时太短,前一位的段码还留在数据总线上,下一位的位选已经打开,于是下一位数码管短暂地显示了上一位的段码,看起来就是数字叠影。报告里加LOOP延时,拉长的是“开位选”的持续时间,让每一位亮得更久,相对地缩短了切换瞬间的占比,影就淡了。

更彻底的做法是“先关显示再换数据”:在ROR AH,1之前先把 A 口输出 0,让所有位选关闭,再送新段码、开新位选。这样切换瞬间屏是全黑的,人眼察觉不到,重影从根上消失。代价是亮度略降,因为每位多了一段熄灭时间。实验箱上数码管余辉通常够,先加延时、再加关显示,两步做完基本就干净了。

4.2 LOOP 延时的粗略估算与可调参数

8088 上LOOP指令的周期数按经典资料大约 17 个时钟周期,取 4.77MHz 主频估算,一次LOOP约 3.6 微秒,255 次约 0.9 毫秒。八位一轮约 7.3 毫秒,刷新率约 137Hz,远高于人眼闪烁阈值,所以看起来是稳定的。这个估算不精确,因为实验箱实际时钟、总线等待周期、指令预取都会影响,但量级是对的,用来判断“该加还是该减”足够了。

调参时记住两个方向:延时加大,单点亮的时间变长、整屏刷新率下降,加到一轮超过 20 毫秒(刷新率低于 50Hz)就会开始闪;延时减小,刷新率上升但单点亮变短,太短会暗、会串影。工程上常用的是每位 1~2 毫秒、整屏刷新 60Hz 以上。报告里的0FFH落在 0.9 毫秒附近,是个合理的起点,觉得暗就往上加一档,觉得闪就往下减,别一次改一个数量级。

4.3 共阴极段码表的由来与校验方法

段码表3FH,06H,5BH,4FH,66H,6DH,7DH,07H,7FH,6FH对应共阴极接法,位定义是 D0=a、D1=b、D2=c、D3=d、D4=e、D5=f、D6=g、D7=dp,某位为 1 则该段亮。以数字 0 为例,a、b、c、d、e、f 六段亮、g 和 dp 灭,二进制0011 1111B就是 3FH。数字 1 只要 b、c 亮,0000 0110B是 06H。数字 6 需要 a、c、d、e、f、g 亮,0111 1101B是 7DH,和表里一致。

校验方法很简单:拿万用表二极管档,红表笔接公共端(共阴接法公共端接地),黑表笔依次碰 a~g 引脚,哪一段亮就记下对应的位。或者直接写一段只输出单个段码的死循环程序,看哪几段亮,反推位序。如果你手里的实验箱是共阳极,整张表要按位取反,3FH变成C0H,这一点接线前必须确认,接反了要么全亮要么全灭。

5. 避坑与排查:接线、控制字、延时三类翻车记录

这三个实验代码量不大,但翻车点很集中。下面五条是照着报告复现时最容易遇到的,按“现象→原因→解决”写,每条都对应一个具体的检查动作。先看现象定位到哪一类,再动手改,比盲目重写代码快得多。

5.1 五条现象到根因的排查记录

第一条:八个 LED 全不亮,代码检查不出错。现象是上电后无论怎么改数据,灯一点反应没有。原因多半是控制字没写或者写错地址,端口还停在复位默认的输入状态,OUT写不进去。解决:用调试器在OUT DX,AL(控制字那一条)后设断点,读 283H 确认写入值,同时确认DX是 283H 而不是 280H。地址写错一位,控制字就跑到 A 口去了,方向自然没配。

第二条:开关拨动,LED 亮灭和开关状态相反。现象是开关拨到 ON,对应 LED 灭;拨到 OFF,LED 亮。原因是开关电路的有效电平和 LED 驱动极性没对齐,IN读回来的 0/1 与预期反了。解决:先查实验箱开关是接地有效还是接高有效,再看 LED 是高电平亮还是低电平亮,两者相反时在IN后加NOT AL,或者改接线,不要靠猜。

第三条:数码管显示重影,数字叠在一起。现象是相邻两位的数字有残影,尤其亮段多的数字明显。原因是位选切换太快、延时不够,或者切换时没有先关显示。解决:先把MOV CX,0FFH加到1FFH看是否改善,改善了就说明是延时问题;如果还有影,在ROR AH,1前把 A 口输出 0 关掉所有位选,再送下一位。

第四条:数码管只亮一位,或者八位显示同一个数字。现象是位选没走起来,或者段码没跟着索引变。原因通常是ROR AH,1的方向或初值错了,或者XLAT前 AL 不是 0~9 的索引(比如把段码当索引用)。解决:单步看 AH 每次移位后的值,确认从10000000B逐次右移到00000001B;再确认NO[DI]取出来的是数字而不是段码,XLAT的索引必须在 0~9 范围内。

第五条:程序烧进去跑飞,或者端口完全没有输出。现象是实验箱无反应,调试器也停不下来。原因可能是段寄存器没初始化、END后面的入口标号写错、或者实验箱的地址跳线没接到 280H 这一组。解决:先确认MOV AX,DATA/MOV DS,AX两行在,END START的标号和代码段入口一致;再用调试器读 280H,如果读出来是 FFH 且写不进去,回头查硬件片选和跳线。

5.2 上电自检顺序:先灯后开关再数码管

接线之前先跑一遍自检,能省掉一半的来回。第一步只接 LED 和 A 口,跑 3.1 的静态显示,确认灯能按位点亮,说明地址、控制字、数据通路都通。第二步接开关和 B 口,跑 3.2 的回显,确认IN能读到变化,说明输入方向配置正确。第三步再上数码管,先只接一位、固定显示一个数字,确认段码和位选都对,再扩到八位做扫描。这个顺序的好处是每一阶段只引入一个新变量,出问题时排查范围小。

自检时把控制字的值写在纸上贴在实验箱旁边,80H对应全输出、82H对应 A 出 B 进,每次改代码先对一眼,比事后猜省事。

5.3 用调试器单步看端口,比反复改代码快

现象不对时,第一反应不应该是改代码重烧,而是单步看端口。在控制字写入后停住,读 283H 确认值;在数据口OUT后停住,用调试器的端口查看功能读 280H/281H,看写进去的值和读回来的值是否一致。如果写 280H 读回来不变,说明片选或地址有问题;如果写进去变了但灯不亮,问题在接线或 LED 极性。单步还能看到ROR AH,1每一步 AH 的实际值,位选错位一眼就能发现。这套方法比“改一个参数烧一次”快得多,尤其是动态扫描这种时序相关的现象,静态观察比反复试错可靠。

6. 从实验报告到可复用骨架:子程序化与上机验收清单

三个实验跑通之后,代码还是散的:初始化一段、输出一段、扫描一段,换个实验就要复制粘贴改地址。把它们收成子程序,下次做 8255A 相关的课设或小项目,直接调用就行。下面给一个最小骨架,初始化、字节输出、数码管扫描各一个入口,参数用寄存器传,地址用常量集中定义,改基地址只改一处。

6.1 收成三个子程序

; 常量集中定义,换实验箱只改这里 P8255_A EQU 280H P8255_B EQU 281H P8255_CTL EQU 283H ; 初始化:A口输出、B口输入、方式0 INIT_8255 PROC MOV DX, P8255_CTL MOV AL, 82H OUT DX, AL RET INIT_8255 ENDP ; A口输出一个字节,入口 AL=数据 OUT_A PROC MOV DX, P8255_A OUT DX, AL RET OUT_A ENDP ; B口读一个字节,出口 AL=数据 IN_B PROC MOV DX, P8255_B IN AL, DX RET IN_B ENDP

骨架的价值在于把“地址”和“方向”从业务代码里剥离出来。INIT_8255只在程序开头调一次,后面所有输出都走OUT_A或直接写数据口,读开关走IN_B。数码管扫描可以在此基础上加一个SCAN_ONCE子程序,把 3.3 里FLAG1那一段包进去,主程序只负责循环调用和更新显示缓冲区。这样改学号、改显示内容只动数据段,不动时序逻辑。

6.2 上机验收清单

检查项预期现象判据
控制字写入283H 读回 80H 或 82H读回值等于写入值
A 口输出写 01H 后最右 LED 亮位序与接线一致
B 口输入拨动任一开关,读回值对应位翻转无抖动、无粘连
段码表固定显示 8,七段全亮无缺段、无多段
位选移位单步看 AH 从 80H 到 01H八位依次选中
刷新效果八位学号稳定不闪、无重影距离 30cm 目视无叠影

验收时按表逐项打勾,任何一项不过就回到对应章节查,不要跳步。尤其是位选移位那一项,单步看 AH 的值比看屏幕可靠,屏幕会骗人,寄存器不会。

从那以后我每次上实验箱,都先把 283H 的控制字写一遍、用调试器读回确认,再动手接数据线,这个习惯帮我省掉了至少一半的“灯不亮”排查时间。动态扫描的延时值我也习惯先按 0FFH 起步、单步看一轮时间再调,而不是一次改到 2FFH 然后对着闪烁的屏发愣。希望帮到你。

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

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进