1. 项目概述与核心设计思路
在嵌入式系统开发的入门阶段,基于8086微处理器的计算器设计是一个经典的练手项目。这个项目看似简单,却涵盖了从硬件接口控制到算法实现的完整开发流程。我选择使用Proteus 8.9进行仿真,配合Masm for Windows 2015汇编器,实现了一个具备四则运算和四位一体数码管显示功能的计算器系统。
核心硬件架构采用8086 CPU搭配8255A可编程并行接口芯片,这种组合在早期的工业控制系统中非常常见。8255A的三个8位端口(PA、PB、PC)分别控制数码管的段选、位选和键盘矩阵扫描,这种设计既节省IO资源又便于扩展。特别值得注意的是,我们将8255A的基地址设置为0x0600,这个地址选择考虑了8086系统的地址空间分配和后续可能的设备扩展。
2. 硬件电路设计与关键元件配置
2.1 8086最小系统搭建
在Proteus中搭建8086最小系统需要以下核心元件:
- 8086 CPU工作在5MHz主频
- 8284A时钟发生器
- 74HC373地址锁存器
- 62256 32KB RAM芯片
- 8255A PPI芯片
地址译码电路采用74HC138芯片,将A15-A10地址线译码后产生8255A的片选信号。这种部分译码方式虽然会存在地址重叠现象,但对于简单的教学项目已经足够。
2.2 8255A接口配置
8255A的初始化是整个硬件系统的关键。我们需要将其配置为:
- 端口A:模式0输出(控制数码管段选)
- 端口B:模式0输出(控制数码管位选)
- 端口C高四位:输入(键盘列检测)
- 端口C低四位:输出(键盘行扫描)
对应的控制字为10000001B(0x81),初始化代码如下:
assembly复制MOV DX, CTRL ; CTRL = 0606H
MOV AL, 81H ; 控制字
OUT DX, AL ; 写入控制寄存器
3. 键盘扫描模块实现
3.1 矩阵键盘布局设计
我们采用4x4矩阵键盘布局,按键定义如下:
code复制7 8 9 /
4 5 6 *
1 2 3 -
C 0 = +
键盘行线接8255A的PC0-PC3(输出),列线接PC4-PC7(输入)。这种设计仅占用一个8位端口就实现了16个按键的检测。
3.2 行反转法扫描实现
行反转法是矩阵键盘扫描的经典算法,其核心步骤如下:
- 先将所有行线置低电平,读取列线状态
- 如果有按键按下,相应列线会变为低电平
- 然后反转IO方向,将所有列线置低,读取行线状态
- 结合两次读取结果确定按键位置
具体实现代码:
assembly复制SCAN_KEY PROC NEAR
MOV AL, 0F0H
OUT PORT_C, AL ; PC0-3输出0,PC4-7输入
IN AL, PORT_C ; 读取列状态
AND AL, 0FH ; 屏蔽高四位
CMP AL, 0FH ; 检查是否有列线变低
JE NO_KEY ; 无按键则跳转
CALL DELAY_20MS ; 消抖延时
MOV AL, 0FH
OUT PORT_C, AL ; PC0-3输入,PC4-7输出0
IN AL, PORT_C ; 读取行状态
AND AL, 0F0H ; 屏蔽低四位
; 组合行列值并查表得到键值
...
NO_KEY:
RET
SCAN_KEY ENDP
注意:实际应用中需要添加去抖动处理,通常采用20ms左右的延时。在资源紧张的情况下,也可以使用计数器实现软件消抖。
4. 数码管显示驱动
4.1 动态扫描原理
四位一体共阴数码管采用动态扫描方式驱动,其工作原理是:
- 依次选中每一位数码管(通过位选信号)
- 输出对应位的段码数据
- 保持显示2-5ms后切换到下一位
- 以至少50Hz的频率循环刷新,利用人眼视觉暂留效应形成稳定显示
4.2 段码表与显示缓冲
共阴数码管的段码表(0-9)定义如下:
assembly复制SEG_TABLE DB 3FH, 06H, 5BH, 4FH, 66H, 6DH, 7DH, 07H, 7FH, 6FH
显示缓冲区设计为4字节,存储当前要显示的BCD码:
assembly复制DISP_BUFF DB 4 DUP(0) ; 显示缓冲区
POINT_FLAG DB 0 ; 小数点标志位
4.3 动态扫描实现代码
assembly复制DISPLAY PROC NEAR
PUSH CX
PUSH BX
MOV CX, 4 ; 4位数码管
MOV BL, 08H ; 位选初始值(最高位)
LEA SI, DISP_BUFF ; 显示缓冲区指针
NEXT_DIGIT:
MOV AL, [SI] ; 取显示数据
LEA DI, SEG_TABLE
XLAT ; 查表得到段码
; 处理小数点
TEST POINT_FLAG, BL
JZ NO_POINT
OR AL, 80H ; 点亮小数点段
NO_POINT:
OUT PORT_A, AL ; 输出段码
MOV AL, BL
OUT PORT_B, AL ; 输出位选
CALL DELAY_2MS ; 保持显示2ms
INC SI ; 指向下一位
SHR BL, 1 ; 位选右移
LOOP NEXT_DIGIT
POP BX
POP CX
RET
DISPLAY ENDP
提示:动态扫描的延时时间需要精确控制。时间太短会导致亮度不足,太长则会出现闪烁。实际调试时可以用示波器观察各信号波形。
5. 运算核心设计与实现
5.1 数据存储结构
计算器采用双缓冲结构存储运算数据:
assembly复制NUM_BUFF DB 8 DUP(0) ; 输入缓冲区(ASCII)
OPERAND1 DW 0 ; 第一操作数(二进制)
OPERAND2 DW 0 ; 第二操作数
OPERATOR DB 0 ; 运算符(+,-,*,/)
RESULT DD 0 ; 32位结果
STATUS DB 0 ; 状态标志
5.2 运算流程控制
计算器的工作状态机如下:
- 初始状态:等待输入第一个数
- 数字输入状态:将按键ASCII存入NUM_BUFF
- 运算符状态:转换NUM_BUFF为二进制存入OPERAND1,保存OPERATOR
- 第二数输入状态:同数字输入状态
- 等号状态:执行运算并显示结果
5.3 算术运算实现
加法运算相对简单:
assembly复制ADD_OP:
MOV AX, OPERAND1
ADD AX, OPERAND2
MOV WORD PTR RESULT, AX
JNC STORE_RESULT
MOV WORD PTR RESULT+2, 1 ; 处理进位
RET
乘法运算需要考虑16位×16位=32位结果:
assembly复制MUL_OP:
MOV AX, OPERAND1
IMUL OPERAND2
MOV WORD PTR RESULT, AX
MOV WORD PTR RESULT+2, DX
RET
除法运算需要特别处理除零错误:
assembly复制DIV_OP:
MOV BX, OPERAND2
CMP BX, 0
JNZ DO_DIVIDE
MOV STATUS, 0FFH ; 设置错误标志
RET
DO_DIVIDE:
XOR DX, DX
MOV AX, OPERAND1
IDIV BX
MOV WORD PTR RESULT, AX ; 商
MOV WORD PTR RESULT+2, DX ; 余数
RET
6. 系统调试与优化技巧
6.1 Proteus仿真常见问题
- 数码管显示暗淡:这是Proteus仿真的固有特性,实际硬件会更亮
- 按键响应不灵敏:检查消抖延时是否足够(建议20ms)
- 运算结果错误:使用Proteus内置调试器单步跟踪指令执行
6.2 实际硬件调试经验
- 电源滤波:在每片IC的VCC和GND之间添加0.1μF去耦电容
- 信号完整性:长距离信号线串联22Ω电阻减少振铃
- 数码管限流:段选线上串联200Ω电阻保护LED
- 键盘防抖:除了软件消抖,硬件上可以并联0.01μF电容
6.3 性能优化建议
- 将频繁调用的子程序(如显示扫描)放在内存低地址区域
- 使用查表法替代复杂计算(如将平方根结果预存为表格)
- 合理安排指令顺序,减少流水线停顿
- 对时间敏感代码禁用中断
7. 功能扩展与进阶改进
7.1 支持浮点运算
通过软件模拟实现浮点运算:
- 定义浮点格式(如IEEE 754简化版)
- 实现浮点加减乘除子程序
- 增加浮点显示处理
7.2 增加存储功能
利用8255A剩余端口扩展EEPROM(如24C02):
- 实现I2C协议模拟
- 添加M+、M-、MR等存储操作键
- 断电保存重要数据
7.3 改进用户界面
- 增加LCD显示模块(如1602A)
- 实现运算历史记录功能
- 添加音效反馈(使用8253定时器驱动蜂鸣器)
在完成这个项目的过程中,最深刻的体会是:汇编语言编程就像在钢丝上跳舞,每一个字节、每一个时钟周期都需要精打细算。当看到自己编写的代码成功驱动硬件完成预期功能时,那种成就感是高级语言开发难以比拟的。建议初学者在调试时养成注释每行代码的习惯,这会大大减少后期的调试难度。
