
简介一份面向51单片机学习者与开发者的仿真实例演示如何通过8255并行接口芯片扩展单片机I/O端口解决实际应用中引脚不足的问题。实例以C语言编写控制程序配合Proteus搭建硬件电路并完成联调适合电子类专业学生进行课程设计或接口技术入门。资源包共14个文件涵盖Proteus仿真图DSN、Keil工程源码C/UV2、编译输出HEX/LST/OBJ以及备份文件DBK/PWI等压缩后仅51KB结构清晰、便于直接打开学习。已有199人浏览学习。通过学习该实例可重点掌握8255的端口工作模式配置、控制字设置、C语言层面的I/O读写操作以及Proteus中加载代码和观察波形的方法。项目虽小但流程完整能帮助读者建立从硬件设计到软件编程再到仿真验证的完整思路为后续更复杂的接口扩展应用打下基础。1. 用8255做接口扩展到底在解决什么问题用51单片机做课程设计或小项目时最让人头疼的不是C语言语法而是IO口不够用。一个典型场景P0接LEDP1接键盘P2接数码管段码P3还要接位选和外部中断程序写完了引脚却愣是插不下。这时候最常见的扩展方案是给51加一片8255把P0数据总线引到8255的D0-D7再用A0、A1和RD/WR做端口选择一下子多出24个IO。8255带给你的不只是“多几个引脚”它把单片机的总线读写引入接口扩展对8255写数据本质就是对外部RAM做写操作。这篇文章顺着“寄存器原理 → C语言驱动 → Proteus仿真 → 键盘扫描实战 → 时序调试”的顺序把这套方案讲透新手照着能跑老手也能看到地址映射和时序上的坑。2. 8255端口结构与C语言寄存器编程基础2.1 从引脚到控制字把三个端口拆成寄存器视图8255内部有三个8位端口PA、PB、PCPC口又分高四位和低四位可以分别设置方向。要让8255工作不是直接往引脚塞数据而是先通过控制字寄存器告诉它“哪个端口输入哪个端口输出”。这个控制字本身也占一个端口地址由A1、A0两根地址线的状态决定。A1A0选中寄存器用途00PA8位数据输入/输出01PB8位数据输入/输出10PC8位数据输入/输出或拆成高低4位11CWR控制字寄存器只写不读A0、A1来自51单片机低8位地址总线。CS片选信号负责决定8255在单片机地址空间里的位置只有CS为低电平时A0、A1和RD/WR才真正生效。也就是说8255对单片机来说就是四个连续排列的存储单元只是最后一个单元不是RAM而是控制字寄存器。控制字的位定义是D7必须为1表示“方式定义有效”D6、D5决定A组工作方式D4决定PA方向D3决定PC高四位方向D2决定B组工作方式D1决定PB方向D0决定PC低四位方向。比如将控制字设为0x80二进制是10000000含义就是PA输出PB输出PC高四位输出PC低四位输出全部工作在方式0。这也是最简单、最常用的初始化方式。2.2 用C语言写一组8255底层驱动在Keil C51里访问外部扩展RAM一般用XBYTE宏它把地址和数据类型封装成“直接可读写的外部存储单元”。因为8255占用的是xdata空间所以写成这样#include reg51.h #include absacc.h #define PA XBYTE[0x0000] // A10, A00 #define PB XBYTE[0x0001] // A10, A01 #define PC XBYTE[0x0002] // A11, A00 #define CWR XBYTE[0x0003] // A11, A01 void main(void) { CWR 0x80; // 方式0PA/PB/PC全部输出 while (1) { PA 0x00; // PA口全部输出低电平 PA 0xFF; // 然后全部拉高观察电平变化 } }这段代码做的事情是先把控制字0x80送入CWR让8255三个端口都处于输出状态然后在主循环里交替往PA口写0x00和0xFF。逻辑说明第一个赋值语句不是“给一个变量赋值”而是向外部地址0x0003发起一次写操作8255在内部把D0-D7上的0x80锁存到控制寄存器从而改变所有端口的方向。之后对PA的写操作同样会驱动P0数据总线配合WR信号把数据送到8255的PA输出锁存器。参数说明地址偏移0x0000、0x0001、0x0002、0x0003不是随手写的。它们依赖A1、A0和CS的连接方式如果CS接到P2.7那么P2.70时8255才被选中地址高位小于0x8000都会命中所以低两位0x0000-0x0003就够用。如果你的板子上CS经过译码器接到P2.6基地址就要换成0x4000-0x4003。这个映射关系必须在写代码前先画出来否则编译通过但硬件上不工作。2.3 常见误写把8255当普通变量直接赋值不少人在C语言里写完#define PA XBYTE[0x0000]之后直接写PA 1;以为像P11一样只让PA最低位输出高电平。实际上PA是一个8位端口PA 1会把整个端口写成0x01也就是PA0输出高其余七位输出低。所以想让某一位单独动作要么写完整字节要么先读回原值再按位操作。8255在输出方式下不具有读回功能直接PA | 0x01这种“读改写”指令在C51里会产生一个对外部地址的读周期而8255输出端口不返回数据读回来的值是不可靠的。解决方法是把每次要输出的整个字节用一个软件备份变量维护需要修改某一位时先改备份变量再把整个变量赋值给PA。另一个典型误用是忽略PC口高低四位的方向独立性。控制字0x82表示PA输出、PB输入、PC输出但如果把PC高四位和低四位当成一个整体方向就会在电路上产生“输入引脚当输出用”的错误。PC口的双向能力只在位级和半字节级有效不能单独控制某一位。这个约束在稍后的键盘扫描例子里会直接影响按键列的接法。3. Proteus仿真环境下搭建8255最小电路与C语言驱动实现3.1 元件选型与引脚连接AT89C518255组合Proteus里搭8255仿真电路不需要外接晶振和复位电路因为仿真模型内置了这些功能但为了接近真实板子建议把AT89C51、8255、74LS373、LED、排阻都放进来。核心连接原则是51的P0口既做数据总线又做低8位地址总线所以必须先经过74LS373锁存地址再用P0直接连8255的D0-D7。Proteus元件库内名称用途51单片机AT89C51主控并行接口芯片8255IO扩展地址锁存器74LS373分离P0口地址/数据信号LED指示灯LED-RED观察PA/PB输出数字按键BUTTON键盘扫描输入连线要点P0口接74LS373的D输入51的ALE接74LS373的LE锁存控制373的Q输出接8255的A0、A1和片选译码逻辑。P2.7直接接8255的CSP2.7引脚地址最高位当地址高字节小于0x80时CS为低8255被选中。RD、WR引脚分别接8255的RD、WR这两个信号在仿真里不能接反否则读操作会变成写操作。8255的RESET最好和51的复位输出接在一起保证程序下载后8255被复位到初始状态。3.2 用流水灯验证扩展IO方向搭好电路后先跑一个最小测试程序不做键盘扫描不接显示只用PA口驱动8个LED验证8255的初始化是否真让端口工作了。流水灯代码#include reg51.h #include absacc.h #define PA XBYTE[0x0000] #define CWR XBYTE[0x0003] void delay_ms(unsigned int n) { unsigned int i, j; for (i 0; i n; i) for (j 0; j 125; j); } void main(void) { unsigned char led 0xFE; CWR 0x80; // 三个端口全部方式0输出 while (1) { PA led; // 点亮对应的LED delay_ms(200); led (led 1) | 0x01; if (led 0xFF) led 0xFE; } }逻辑说明初始值0xFE对应11111110PA0输出低电平LED0点亮。每次循环左移一位最低位补1使低电平逐个向右移动形成跑马灯效果。led 0xFF表示已经移过8位此时重置回0xFE循环重新开始。整个过程只操作8255的PA口没有绕开控制字所以能直接验证PA口方向是否设置成功。参数说明delay_ms(200)里的200是软件延时毫秒数Proteus仿真速度受主机性能影响实际延时可能比真实芯片偏大或偏小。如果仿真里LED闪烁速度异常先不要改延时优先确认单片机运行频率设置AT89C51的仿真模型默认12MHz但快速仿真模式下所有外设都跟着加速。P2.7接CS时在程序里不要同时使用其他外部RAM地址否则此时8255也会被选中两个器件驱动同一组数据总线会造成冲突。3.3 时序检查为什么CS比WR早有效很多人在Proteus里发现IO口方向一直都是0xFF或者LED全亮不受控第一反应是程序没烧进去其实问题往往在8255的读写时序。8255要求有效的CS、A0、A1信号先建立然后WR或RD信号再发生变化数据总线上才能正确锁存。51单片机访问xdata时P2口在整个读写周期内保持地址稳定P0先输出低8位地址由ALE锁存然后电总线自动切换为数据。这说明从CPU内部时序上看CS和A0、A1确实比WR早有效但前提是373锁存器连接正确。检查时序可以这样操作在Proteus中放置一个LOGIC ANALYSER把CS、WR、RD三根线接进去然后设置仿真步进比普通运行慢。当程序执行到第二行CWR 0x80时会看到CS先变成低电平约几十纳秒后WR出现一个低脉冲数据线上的0x80必须在WR上升沿附近保持稳定。如果CS和WR同时变化通常是P2.7接错了位置或者373的LE没有接对。Proteus仿真不像实物那样暴露毛刺但地址重叠和总线冲突仍然能通过电压探针看出来。4. 8255工作方式0的键盘扫描与LED数码管显示实战4.1 把键盘矩阵接到PA口数码管接到PB/PC方式0是8255最基本的输入输出模式A组、B组都不需要应答信号适合做键盘扫描和简单的数码管显示。这里用PA口驱动4行键盘行线PB口读4列按键状态PC口控制4位数码管的位选。方向如下PA输出PB输入PC整体输出。对应控制字0x82。为什么不是0x80因为PB必须读按键状态所以D1位要置1。0x80的二进制是10000000PB被设置成输出这时往里读数据会一直读到无效值。0x82的二进制是10000010PA、PC输出PB输入PC高四位和低四位都输出。注意PC口方向不能单独控制某一位但可以在方式0下把PC整体设为输出所以位选信号全由PC输出是可行的。硬件连接时4根行线分别接PA0-PA34根列线接PB0-PB3每根列线到地接一个10k下拉电阻。为什么用下拉而不是上拉扫描时PA输出低电平没有按键按下的行线为高阻PB读取到高电平一旦某键按下对应行线的低电平通过按键传到列线PB对应位被拉低。如果列线接上拉到VCC按键按下拉低到地逻辑也可以但这要求PB口内部带上拉8255的输入引脚没有内部上拉所以仿真里需要外部器件。4.2 键盘扫描与动态显示的C语言实现#include reg51.h #include absacc.h #define PA XBYTE[0x0000] #define PB XBYTE[0x0001] #define PC XBYTE[0x0002] #define CWR XBYTE[0x0003] unsigned char code row_code[4] {0xFE, 0xFD, 0xFB, 0xF7}; unsigned char code seg_code[10] {0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8, 0x80, 0x90}; unsigned char code bit_code[4] {0xFE, 0xFD, 0xFB, 0xF7}; void delay_ms(unsigned int n) { unsigned int i, j; for (i 0; i n; i) for (j 0; j 125; j); } unsigned char scan_key(void) { unsigned char i, col; for (i 0; i 4; i) { PA row_code[i]; col PB 0x0F; if (col ! 0x0F) { delay_ms(10); col PB 0x0F; if (col ! 0x0F) { if (col 0x07) return i * 4 0; if (col 0x0B) return i * 4 1; if (col 0x0D) return i * 4 2; if (col 0x0E) return i * 4 3; } } } return 0xFF; } void display_num(unsigned char num) { unsigned char a 0, b 0, c 0, d 0; if (num 0xFF) return; d num % 10; c (num / 10) % 10; b (num / 100) % 10; a (num / 1000) % 10; PC bit_code[3]; PB seg_code[a]; delay_ms(2); PC bit_code[2]; PB seg_code[b]; delay_ms(2); PC bit_code[1]; PB seg_code[c]; delay_ms(2); PC bit_code[0]; PB seg_code[d]; delay_ms(2); } void main(void) { unsigned char key 0xFF; CWR 0x82; while (1) { key scan_key(); if (key ! 0xFF) { while (key scan_key()); display_num(key); } } }逻辑说明scan_key函数依次把PA口的行码设为0xFE、0xFD、0xFB、0xF7每次只让一行输出低电平。然后读取PB低四位如果某一位为0说明这一列的按键把行线上的低电平引到了PB引脚。二次判断和10ms延时是为了消抖防止按键弹跳导致一次按下被识别成多次。返回值i * 4 0到i * 4 3把行和列位置编码成0到15的键值方便后面查表显示。参数说明行码0xFE对应11111110也就是PA0低电平这一行被扫描。PB 0x0F只保留列线对应的低四位高四位是PC口的输出电平不参与按键判断。列值0x07表示PB0、PB1、PB2为低只有PB3为高意味着按键在第4列0x0B、0x0D、0x0E分别对应11111011、11111101、11111110对应第2、3、4列。这种编码方式容量靠列值如果按键矩阵不是4x4需要调整掩码和判断分支。4.3 动态显示刷新频率与按键响应的取舍上面的显示函数在每次调用时依次刷新4位数码管每一位点亮2ms后切到下一位。整体刷新周期约8ms看起来数码管稳定不闪烁。主循环里先扫描键盘再刷新显示所以每次按键扫描之间会插入显示刷新时间扫描周期会比单纯按键程序长。对4x4矩阵来说只要行扫描速度在1ms左右按键不会漏识别。8255在这里体现出优势PB输入时读回的是引脚电平不是锁存值所以按键状态是实时的PC输出时位选信号被8255锁存即使CPU去读PB也不会影响位选输出电平。如果用P0直接驱动数码管位选P0在读取外部RAM时会切换地址/数据状态数码管会闪。换到8255后数据总线上的变化不会直接出现在PC口上这就是接口扩展带来的隔离效果。5. 通过Proteus时序验证和调试8255的实用技巧调试8255时我最先做的一件事是在初始化后立刻把控制字读回来。虽然CWR寄存器是只写的无法直接读但可以用PA口做回环验证PA输出一个已知字节用导线把PA的8个引脚接到PB的8个输入引脚然后让程序读PB并和原值比较。如果读回不一致说明PA方向、PB方向或CS地址中至少有一处配置错误。现象可能原因检查位置PA输出一直为0xFFCS未拉低8255未选中P2.7与CS连线PA输出正确但PB读回全1PB被设为输出或下拉电阻没接控制字0x82列线电阻控制字写不进去端口方向不变8255 RESET持续为高RESET是否与51复位连接电平变化但LED微亮8255输出驱动能力不足LED限流电阻是否过大在Proteus虚拟示波器里观察WR信号时可以配合Keil的断点功能。在CWR 0x80那行设置断点单步执行到赋值语句前先查看P2、P0、ALE的状态执行后再看8255的CS、WR、D0-D7引脚电平。8255数据手册里要求的“地址建立时间先于写信号”在Proteus里可以直观看到CS拉低和WR拉低的间隔。如果两者几乎同时变化检查373的锁存地址是否提前于WR到达必要时把373的输出直接连到8255而不经过中间信号。最后一个小技巧是把所有8255寄存器访问封装成带volatile的指针并且在主程序里用一个全局结构体管理软硬件备份。比如要控制数码管位选时先修改备份变量再一次性写入PC口避免读改写造成的输出毛刺。把“写备份 → 写8255”变成固定模式能减少大部分Proteus仿真里的诡异现象。所以把控制字0x82写进CWR后才能放心操作PB输入地址偏移和片选映射要写在源码注释第一行。本文还有配套的精品资源点击获取