在微处理器系统的实际开发中,我们经常面临一个挑战:如何在 CPU 与外部设备之间建立高效、可靠的通信桥梁。虽然并行通信速度很快,但在长距离传输中,高昂的线缆成本和信号干扰让人头疼。这时,串行通信成为了我们的首选。而 8251 USART(通用同步/异步接收发送器)正是为此而生的经典芯片。今天,我们将深入探讨 8251 的内部机制,看看它是如何优雅地处理数据转换,以及我们如何在代码层面驾驭它。
前置知识:
在开始之前,建议你先了解一下 8259 PIC 的基本概念,因为中断处理往往是配合串行通信使用的。
8251 是什么?
简单来说,8251 是一个可编程通信接口芯片。它的核心任务是充当微处理器与外设之间的“翻译官”。我们利用它将 CPU 内部的并行数据转换为外设能理解的串行数据,反之亦然。这种转换对于调制解调器、终端、打印机等外设至关重要。
它的工作流程通常包含以下几个步骤:
- 接收: 从外设接收串行数据流。
- 转换: 将这些串行位重新组装成并行字节。
- 传输: 将组装好的并行数据发送给 CPU。
同样的流程也反向适用于 CPU 发送数据给外设:从微处理器接收并行数据 -> 转换为串行形式 -> 传输到外部设备。
8251 的内部架构:深入核心
要精通 8251,我们不能只停留在表面。让我们打开“引擎盖”,看看它的内部框图主要由哪些模块构成,以及它们是如何协同工作的。
#### 1. 数据总线缓冲器
这是 8251 与系统数据总线的接口。你可以把它想象成一个“中转站”。它帮助连接 8251 的内部总线与系统的外部数据总线。无论是 CPU 发出的控制字、要发送的数据,还是 CPU 从 8251 读取的状态信息,都必须经过这个缓冲器。它的存在确保了 8251 能够与 CPU 的数据总线无缝对接。
#### 2. 读/写控制逻辑
这是整个芯片的“大脑”或控制中心。它管理着数据的流向和设备的操作模式。这个逻辑单元根据输入的信号(如 CS, WR, RD, C/D)来决定执行什么操作。
- 操作选择机制: 通过组合控制信号,该单元会选择三个关键寄存器之一进行操作:
* 数据缓冲寄存器: 存放实际要发送或接收的数据。
* 控制寄存器: 存放由 CPU 编程的指令,决定波特率、字符长度等。
* 状态寄存器: CPU 可以读取这个寄存器来检查 8251 的当前状态(比如是否有错误,是否准备好接收数据)。
#### 3. 调制解调器控制
这一模块是为了与调制解调器或其他外设直接连接而设计的。它处理握手信号,确保双方都准备好了。
以下是几个关键的低电平有效引脚,我们需要特别留意:
- DSR (Data Set Ready): 输入信号。告诉 8251,调制解调器或外设已经准备好了。
- DTR (Data Terminal Ready): 输出信号。CPU 通过 8251 发出此信号,告诉外设:“终端(也就是我们的计算机)准备好了”。
- CTS (Clear to Send): 输入信号。这是数据发送的控制闸门。只有当 CTS 有效时,发送器才能发送数据。
- RTS (Request to Send): 输出信号。用于通知调制解调器,终端请求发送数据。
#### 4. 发送缓冲器与发送控制
发送部分负责将数据从并行格式“压缩”成串行位流。
- 发送缓冲器: 它本质上是一个并串转换器。它接收 CPU 发来的并行字节,将其转换为串行信号,并通过 TXD 引脚传输出去。
- TXD (Transmitter Data): 这是串行数据的输出线。当 TXD 为高电平时,可能处于空闲状态或发送逻辑 ‘1‘(具体取决于协议)。
- TXRDY (Transmitter Ready): 这是一个输出信号(或状态位)。当它为真时,就像 8251 在对 CPU 喊:“我现在的缓冲区空了,你可以给我发新数据了!”
- TXEMPTY (Transmitter Empty): 这个信号表示发送器已经完全空了,连最后一位数据都发出去了。这通常用于在双工通信中切换方向或停止传输。
- TXC (Transmitter Clock): 输入引脚。它控制着数据发送的速率。波特率通常是 TXC 频率的倍数(如 1倍、64倍等),具体取决于模式设定。
#### 5. 接收缓冲器与接收控制
接收部分的工作则是逆过程,并负责检测错误。
- 接收缓冲器: 负责接收串行数据并将其组装成并行数据,供 CPU 读取。
- RXD (Receiver Data): 串行数据的输入引脚。
- RXRDY (Receiver Ready): 输出信号。当 8251 接收到一个完整的字符并准备好被 CPU 取走时,该信号置位。这通常连接到 CPU 的中断请求线。
- RXC (Receiver Clock): 控制接收速率的输入时钟。
- SYNDET/BD (Sync Detect/Break Detect): 这是一个双功能引脚。在同步模式下,它用于检测同步字符;在异步模式下,它可以用来检测“断点”信号。
实战代码示例与编程指南
了解了硬件结构后,让我们通过代码来看看如何实际驱动它。在编写代码时,我们需要遵循严格的初始化序列。
#### 场景 1:初始化 8251
在使用 8251 之前,我们必须先对它进行“配置”。这通常包括向控制口写入模式指令和命令指令。
; 假设 8251 的数据口地址为 80H,控制口地址为 81H
; 步骤 1: 写入模式指令
; 这里我们需要定义:波特率因子、字符长度、校验位、停止位
; 示例配置:波特率因子为16 (异步), 8位数据, 无校验, 1位停止位
; 对应的二进制控制字:0100 1111 (4FH)
MVI A, 4FH
OUT 81H ; 将模式字写入控制口
; 步骤 2: 写入命令指令
; 配置:使能发送器、使能接收器、复位错误标志
; 对应的二进制控制字:0001 0101 (15H)
MVI A, 15H
OUT 81H ; 将命令字写入控制口
; 此时 8251 已准备好工作
代码解析:
在这个例子中,我们首先发送了 INLINECODEaab40f1b。为什么要这样做?在硬件层面上,8251 内部有一个状态机。上电后,它期待第一个字节是模式指令。INLINECODEcf095a55 告诉它:“嘿,我们要用异步模式,波特率是时钟的 1/16,数据是 8 位的。”
紧接着,我们发送 INLINECODEc09f4665。这是命令指令。如果不发送这个,发送器和接收器可能处于关闭状态。INLINECODE7d17c3ec 就像按下了“启动”按钮,并清除了任何可能存在的旧错误标志。
#### 场景 2:发送一个字符
现在 8251 已经初始化好了,让我们尝试向终端发送一个字符 ‘A‘。
; 函数:发送字符 ‘A‘ 到串口
SEND_CHAR:
MVI A, ‘A‘ ; 将 ‘A‘ 的 ASCII 码加载到累加器
CHECK_TX_READY:
IN 81H ; 读取状态寄存器
ANI 01H ; 检查第 0 位 (TXRDY - Transmitter Ready)
JZ CHECK_TX_READY ; 如果 TXRDY 为 0,继续等待(轮询)
; 如果 TXRDY 为 1,发送器准备好
OUT 80H ; 将字符数据写入数据口
RET ; 返回
代码解析:
这里我们展示了最基础的轮询方式。INLINECODE073058dc 读取状态口。通过 INLINECODE0dfab9b0(AND Immediate 01H),我们屏蔽了其他位,只关心第 0 位(TXRDY)。如果发送器忙(比如正在把前一个数据移位出去),TXRDY 就是 0,我们就死循环等待。这是一种最简单但效率稍低的同步方式。
#### 场景 3:接收数据并处理
接收数据通常需要处理错误,比如奇偶校验错误或溢出错误。
; 函数:接收字符并回显
RECEIVE_CHAR:
; 1. 检查接收器是否准备好
IN 81H ; 读取状态寄存器
ANI 02H ; 检查第 1 位 (RXRDY - Receiver Ready)
JZ RECEIVE_CHAR ; 如果没收到数据,继续等待
; 2. 检查是否有错误
; 状态寄存器第 2、3、4 位分别代表奇偶错、溢出错、帧错
IN 81H
ANI 38H ; 0011 1000 (检查 Bit 3,4,5)
JNZ ERROR_HANDLER; 如果有错误,跳转到错误处理
; 3. 读取数据
IN 80H ; 从数据口读取字符
; 此时 A 寄存器中保存了接收到的字符
; 4. 简单的回显操作 - 发送回去
PUSH PSW ; 保存接收到的字符
CALL SEND_CHAR ; 调用上面的发送函数(假设已修改为通用函数)
POP PSW ; 恢复环境
RET
ERROR_HANDLER:
; 在这里进行错误复位或记录
MVI A, 10H ; 发送复位错误命令
OUT 81H
RET
实战见解:
在实际应用中,你可能会遇到“溢出错误”。这通常发生在 CPU 处理上一个数据太慢,而 8251 又收到了新数据的时候。上面的代码通过检查状态位展示了如何防范这种情况。一旦检测到错误,写入特定的命令字(如 10H)可以复位错误标志,让芯片恢复工作。
#### 场景 4:中断驱动模式
虽然上面的轮询代码很简单,但在现代系统中,我们更倾向于使用中断来提高 CPU 效率。这需要将 8251 的 TXRDY 或 RXRDY 引脚连接到 8259 PIC(中断控制器)。
; 假设 RXRDY 连接到了 IRQ3
; 这是一个伪代码级别的中断服务程序 (ISR)
SERIAL_ISR:
PUSH PSW ; 保护寄存器
PUSH B
IN 81H ; 读取状态,判断是发送还是接收中断
; 注意:这取决于硬件连接,如果只有接收中断
ANI 02H
JZ ISR_END ; 如果不是接收就绪,退出
IN 80H ; 读取数据
STA BUFFER_PTR ; 存储到内存缓冲区
ISR_END:
POP B
POP PSW
EI ; 重新开中断
RET ; 从中断返回
8251 USART 的优缺点:权衡的艺术
在项目选型时,我们需要客观地评估工具的优劣。8251 作为一个经典的芯片,既有它的辉煌之处,也有时代的局限性。
#### 优点:
- 多功能性: 8251 最大的卖点在于它的灵活性。它可以通过编程支持同步(如 IBM 的 Bi-Sync)和异步(如我们常用的 RS-232)两种模式。这种“双模”能力在当时极大地简化了系统设计。
- 错误检测: 它内置了基本的错误检测机制,主要包括奇偶校验、帧错误检测和溢出检测。虽然不如现代的 CRC 复杂,但在低速通信中足以保证数据的准确性。
- 流量控制: 支持 CTS 和 RTS 等标准硬件流控信号。这对于防止高速发送时低速接收端“吃不消”导致的数据丢失至关重要。
- 兼容性: 它与许多经典的微处理器(如 8080, 8085, Z80)兼容,总线接口逻辑非常简单,甚至能直接连接到某些 8 位数据总线上。
- 易用性: 相比于用分立逻辑电路搭建串行接口,8251 的编程相对简单,只需要几个端口 OUT/IN 指令即可控制。
#### 缺点:
- 速度有限: 这是物理层面的限制。8251 的设计年代决定了它通常工作在几百 kbps 以下(如常见的 19.2 kbps 或 115.2 kbps)。对于现在动辄 Mbps 级别的 USB 或以太网通信,它完全无法胜任。
- 缓冲区大小有限: 8251 内部通常只有单字节的缓冲。这意味着如果 CPU 没有在下一个字节到达前读取当前字节,就会发生溢出错误。这对中断响应时间或轮询速度提出了较高要求。
- 编程复杂度: 虽然基本的 I/O 简单,但要正确配置其同步模式或错误处理,需要深厚的硬件知识。时序问题是新手最容易踩的坑。
- 成本与集成度: 虽然单颗芯片便宜,但在现代设计中,使用独立的 UART 芯片增加了板级空间和成本。现代微控制器(如 STM32, AVR)大多将 UART 集成在芯片内部,不再需要外挂 8251。
- 功能局限: 它不支持 DMA(直接内存访问)传输,所有的数据都必须经过 CPU 中转。这使得它在处理大量数据传输时会占用大量 CPU 资源。
实际应用中的最佳实践与常见陷阱
在多年的嵌入式开发经验中,我们总结了一些使用 8251 或类似 UART 芯片的“避坑指南”:
- 时序是关键: 在初始化时,务必确保先发送模式指令,再发送命令指令。如果顺序错了,芯片会进入未知状态。最简单的解决方法?系统上电时先复位(RESET)芯片。
- 不要忽视时钟: TXC 和 RXC 的频率必须与波特率设置严格匹配。例如,如果配置为 16 倍波特率因子且需要 9600 bps 的速度,那么你的时钟引脚必须输入 $9600 \times 16 = 153.6 \text{ kHz}$ 的信号。误差过大会导致乱码。
- 处理回显: 在调试串口终端时,如果配置为本地不回显,你需要自己在代码里将 RXD 收到的数据发回 TXD。否则,用户敲键盘时屏幕上什么都不会显示,会被误以为死机了。
- 利用状态轮询: 在极端关键的任务中,不要完全依赖中断。有时简单的“查询状态位 – 读取数据”循环比复杂的中断系统更稳定,尤其是在干扰较强的工业环境中。
总结与后续步骤
在本文中,我们深入剖析了 8251 USART 这一经典的微处理器接口芯片。从它的内部数据总线缓冲器到调制解调器控制逻辑,从基本的数据并串转换到实际的汇编代码实现,我们见证了它如何通过简单的寄存器操作实现复杂的通信任务。
虽然现代技术已经超越了 8251 的物理限制,但理解它对于掌握计算机底层通信原理、总线时序以及 I/O 控制依然具有不可替代的教育价值。
作为接下来的学习步骤,建议你尝试:
- 搭建一个简单的 8051 或 8086 仿真电路,连接一个虚拟的 8251。
- 编写一个程序,实现键盘输入并回显到屏幕(全双工回显测试)。
- 尝试修改波特率因子,观察数据传输速率的变化。
希望这篇指南能帮助你更好地理解微处理器的串行通信世界。动手试试吧,你会发现底层硬件编程的乐趣!