第12章 串行通信接口与异步传输技术
本章将详细探讨串行通信接口的原理、标准及实现技术,从基础理论到实际应用,帮助读者全面掌握串行通信的关键知识。
12.1 串行数据传输基本原理
串行通信是计算机与外部设备之间最常用的通信方式之一。与并行通信相比,串行通信使用较少的信号线,通过按顺序传输每一位数据实现信息交换,具有布线简单、抗干扰能力强、传输距离远等优势。
12.1.1 串行与并行通信对比
串行通信和并行通信是两种基本的数据传输方式,各有优缺点:
c
/*
串行通信 vs 并行通信对比:
串行通信:
- 数据按位顺序发送
- 使用少量信号线(通常1-4根)
- 可靠性高,适合长距离传输
- 布线成本低
- 传输速度相对较慢
并行通信:
- 多位数据同时发送
- 使用多根信号线(8位、16位、32位等)
- 受干扰影响大,适合短距离传输
- 布线成本高
- 传输速度相对较快
*/
12.1.2 同步与异步串行通信
串行通信可分为同步通信和异步通信两种基本模式:
c
#include <stdio.h>
void explain_serial_communication() {
printf("串行通信的两种基本模式:
");
printf("1. 同步通信
");
printf(" - 发送方和接收方共享时钟信号
");
printf(" - 需要额外的时钟线或在数据中嵌入时钟信号
");
printf(" - 数据连续传输,无需起始位和停止位
");
printf(" - 传输效率高,适合大量数据传输
");
printf(" - 常见协议: SPI, I²C, HDLC
");
printf("2. 异步通信
");
printf(" - 发送方和接收方使用独立时钟
");
printf(" - 通过起始位和停止位标识数据帧
");
printf(" - 不连续传输,适合突发性数据
");
printf(" - 实现简单,硬件要求低
");
printf(" - 常见协议: RS-232C, RS-485, UART
");
printf("异步通信数据帧格式: [起始位(1位) | 数据位(5-9位) | 奇偶校验位(0-1位) | 停止位(1-2位)]
");
}
int main() {
explain_serial_communication();
return 0;
}
12.1.3 波特率与数据传输速率
串行通信中,波特率是衡量数据传输速度的重要参数,定义为每秒传输的码元数。在二进制传输中,波特率通常等同于比特率。
c
#include <stdio.h>
void explain_baud_rate() {
printf("波特率与数据传输速率:
");
printf("波特率定义: 每秒传输的码元数
");
printf("常用波特率: 300, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200 bps
");
// 计算实际数据传输率
int baud_rate = 9600; // 波特率9600bps
int data_bits = 8; // 8位数据位
int start_bits = 1; // 1位起始位
int stop_bits = 1; // 1位停止位
int parity_bits = 0; // 无奇偶校验位
int total_bits_per_character = start_bits + data_bits + parity_bits + stop_bits;
double characters_per_second = (double)baud_rate / total_bits_per_character;
double bytes_per_second = characters_per_second;
printf("以9600bps波特率传输标准ASCII字符 (8-N-1格式):
");
printf("每个字符总位数: %d位
", total_bits_per_character);
printf("理论传输速率: %.2f字符/秒 (约%.2f字节/秒)
", characters_per_second, bytes_per_second);
printf("传输1KB数据需要时间: %.2f秒
", 1024 / bytes_per_second);
}
int main() {
explain_baud_rate();
return 0;
}
12.1.4 串行通信的应用场景
串行通信在现代计算机和嵌入式系统中有广泛的应用:
计算机外设连接(鼠标、键盘、打印机等)嵌入式系统通信工业自动化控制网络设备之间的通信无线通信系统传感器数据采集调试和编程接口
12.2 RS-232C串行通信标准详解
RS-232C是最广泛使用的串行通信标准之一,定义了数据终端设备(DTE)和数据通信设备(DCE)之间的接口特性。
12.2.1 RS-232C接口信号定义
RS-232C接口定义了多个信号线,用于数据传输和流控制:
c
#include <stdio.h>
void explain_rs232_signals() {
printf("RS-232C主要信号线及其功能:
");
printf("数据信号:
");
printf(" TD (Transmitted Data): 发送数据线,DTE向DCE传输数据
");
printf(" RD (Received Data): 接收数据线,DCE向DTE传输数据
");
printf("握手信号:
");
printf(" RTS (Request To Send): 请求发送,DTE通知DCE准备发送数据
");
printf(" CTS (Clear To Send): 清除发送,DCE通知DTE可以发送数据
");
printf(" DSR (Data Set Ready): 数据设备就绪,DCE通知DTE它已就绪
");
printf(" DTR (Data Terminal Ready): 数据终端就绪,DTE通知DCE它已就绪
");
printf(" DCD (Data Carrier Detect): 数据载波检测,DCE通知DTE远程载波已检测到
");
printf(" RI (Ring Indicator): 振铃指示,DCE通知DTE有呼叫到达
");
printf("接地信号:
");
printf(" SG (Signal Ground): 信号地,所有信号的公共参考电位
");
printf(" FG (Frame Ground): 机架地,连接到设备外壳
");
}
int main() {
explain_rs232_signals();
return 0;
}
12.2.2 RS-232C连接方式与电缆
RS-232C接口通常使用DB-9或DB-25连接器,根据应用场景有不同的连接方式:
c
#include <stdio.h>
void explain_rs232_connections() {
printf("RS-232C连接方式与电缆:
");
printf("标准连接器类型:
");
printf(" DB-9: 9针连接器,常见于PC和小型设备
");
printf(" DB-25: 25针连接器,完整实现所有RS-232C信号
");
printf("DB-9针脚定义 (DTE视角):
");
printf(" 1 - DCD (Data Carrier Detect)
");
printf(" 2 - RD (Received Data)
");
printf(" 3 - TD (Transmitted Data)
");
printf(" 4 - DTR (Data Terminal Ready)
");
printf(" 5 - SG (Signal Ground)
");
printf(" 6 - DSR (Data Set Ready)
");
printf(" 7 - RTS (Request To Send)
");
printf(" 8 - CTS (Clear To Send)
");
printf(" 9 - RI (Ring Indicator)
");
printf("常见连接类型:
");
printf(" 1. 标准DTE到DCE连接: 直通电缆,针对针连接
");
printf(" 2. DTE到DTE连接 (零调制解调器): 交叉电缆,交换收发数据线和握手信号
");
printf(" 3. 最小连接: 仅使用TD、RD和GND,不进行硬件流控制
");
printf("
零调制解调器电缆连接关系:
");
printf(" DTE1-TD (3) <-------> (2) RD-DTE2
");
printf(" DTE1-RD (2) <-------> (3) TD-DTE2
");
printf(" DTE1-RTS(7) <-------> (8) CTS-DTE2
");
printf(" DTE1-CTS(8) <-------> (7) RTS-DTE2
");
printf(" DTE1-DTR(4) <-------> (6) DSR-DTE2
");
printf(" DTE1-DSR(6) <-------> (4) DTR-DTE2
");
printf(" DTE1-GND(5) <-------> (5) GND-DTE2
");
}
int main() {
explain_rs232_connections();
return 0;
}
12.2.3 RS-232C电气特性与传输距离
RS-232C标准定义了严格的电气特性,确保通信可靠性:
c
#include <stdio.h>
void explain_rs232_electrical_characteristics() {
printf("RS-232C电气特性:
");
printf("逻辑电平:
");
printf(" 逻辑'1'(MARK): -3V至-15V
");
printf(" 逻辑'0'(SPACE): +3V至+15V
");
printf(" 不确定区域: -3V至+3V
");
printf("特点:
");
printf(" 1. 负逻辑系统: 负电压表示逻辑'1',正电压表示逻辑'0'
");
printf(" 2. 电压摆幅大: 典型为±12V,提高抗噪声能力
");
printf(" 3. 驱动器必须能承受短路和过载
");
printf("传输距离限制:
");
printf(" 最大标准传输距离: 约15米(50英尺)
");
printf(" 低波特率下可达50米左右
");
printf(" 超过标准距离需使用线路驱动器或RS-422/485等标准
");
printf("传输速率:
");
printf(" 最大推荐波特率: 20kbps
");
printf(" 实际应用中常用115.2kbps甚至更高
");
}
int main() {
explain_rs232_electrical_characteristics();
return 0;
}
12.2.4 程序示例:简单RS-232C通信
以下是使用C语言在Linux系统上进行简单RS-232C通信的示例:
c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <errno.h>
// 初始化串口
int init_serial_port(const char *port, int baud_rate) {
int fd;
struct termios options;
// 打开串口设备
fd = open(port, O_RDWR | O_NOCTTY | O_NDELAY);
if (fd == -1) {
perror("无法打开串口");
return -1;
}
// 获取当前串口参数
if (tcgetattr(fd, &options) != 0) {
perror("获取串口参数失败");
close(fd);
return -1;
}
// 设置波特率
speed_t speed;
switch (baud_rate) {
case 9600: speed = B9600; break;
case 19200: speed = B19200; break;
case 38400: speed = B38400; break;
case 57600: speed = B57600; break;
case 115200: speed = B115200; break;
default: speed = B9600; break;
}
cfsetispeed(&options, speed);
cfsetospeed(&options, speed);
// 设置控制模式
options.c_cflag |= (CLOCAL | CREAD); // 启用接收器并忽略调制解调器控制线
options.c_cflag &= ~PARENB; // 无奇偶校验
options.c_cflag &= ~CSTOPB; // 1个停止位
options.c_cflag &= ~CSIZE; // 清除位大小标志
options.c_cflag |= CS8; // 8位数据位
// 设置输入模式为原始输入
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
// 设置输出模式为原始输出
options.c_oflag &= ~OPOST;
// 设置不使用软件流控制
options.c_iflag &= ~(IXON | IXOFF | IXANY);
// 应用新的串口参数
if (tcsetattr(fd, TCSANOW, &options) != 0) {
perror("设置串口参数失败");
close(fd);
return -1;
}
return fd;
}
// 发送数据
int send_data(int fd, const char *data, size_t len) {
return write(fd, data, len);
}
// 接收数据
int receive_data(int fd, char *buffer, size_t max_len) {
return read(fd, buffer, max_len);
}
int main() {
const char *port = "/dev/ttyS0"; // 串口设备名,在Windows上可能是"COM1"
int baud_rate = 9600; // 波特率
char send_buffer[256]; // 发送缓冲区
char recv_buffer[256]; // 接收缓冲区
int fd; // 串口文件描述符
printf("RS-232C通信示例程序
");
// 初始化串口
fd = init_serial_port(port, baud_rate);
if (fd < 0) {
printf("串口初始化失败
");
return -1;
}
printf("串口%s已打开,波特率%d
", port, baud_rate);
while (1) {
// 获取用户输入
printf("请输入要发送的数据(输入'exit'退出): ");
fgets(send_buffer, sizeof(send_buffer), stdin);
// 去除换行符
send_buffer[strcspn(send_buffer, "
")] = 0;
// 检查是否退出
if (strcmp(send_buffer, "exit") == 0) {
break;
}
// 发送数据
int sent = send_data(fd, send_buffer, strlen(send_buffer));
if (sent < 0) {
perror("发送数据失败");
} else {
printf("已发送%d字节数据
", sent);
}
// 短暂等待接收响应
usleep(100000); // 100ms
// 接收数据
memset(recv_buffer, 0, sizeof(recv_buffer));
int received = receive_data(fd, recv_buffer, sizeof(recv_buffer) - 1);
if (received > 0) {
printf("收到响应: %s
", recv_buffer);
} else if (received == 0) {
printf("无响应数据
");
} else {
perror("接收数据失败");
}
}
// 关闭串口
close(fd);
printf("串口已关闭
");
return 0;
}
12.3 8250/16550通用异步收发器(UART)
UART(Universal Asynchronous Receiver/Transmitter)是实现异步串行通信的关键硬件,8250和16550是PC体系结构中最常用的UART芯片。
12.3.1 8250/16550内部架构与功能
UART芯片内部包含多个功能模块,协同工作完成异步串行通信:
c
#include <stdio.h>
typedef struct {
char *name;
char *description;
} ComponentInfo;
void explain_uart_architecture() {
printf("8250/16550 UART内部架构与功能:
");
ComponentInfo components[] = {
{"发送器", "将并行数据转换为串行数据流,添加起始位、停止位和校验位"},
{"接收器", "将串行数据流转换为并行数据,检测起始位、停止位和校验位"},
{"波特率发生器", "产生定时时钟信号,控制数据传输速率"},
{"FIFO缓冲器", "16550的特性,用于数据缓冲,提高传输效率(8250无此功能)"},
{"控制寄存器", "控制UART的工作模式和参数"},
{"状态寄存器", "反映UART的当前工作状态"},
{"中断控制器", "管理各种中断事件,如发送完成、接收到数据、线路状态变化等"}
};
printf("主要功能组件:
");
for (int i = 0; i < sizeof(components)/sizeof(ComponentInfo); i++) {
printf(" %d. %s: %s
", i+1, components[i].name, components[i].description);
}
printf("
8250与16550的主要区别:
");
printf(" - 16550增加了16字节FIFO缓冲区,减少CPU干预次数
");
printf(" - 16550具有更高的最大波特率
");
printf(" - 16550提供更好的错误处理能力
");
printf(" - 16550支持更多的中断模式
");
}
int main() {
explain_uart_architecture();
return 0;
}
12.3.2 8250/16550寄存器详解
UART芯片通过一系列寄存器进行控制和状态监测:
c
#include <stdio.h>
void explain_uart_registers() {
printf("8250/16550 UART寄存器详解:
");
// 基本I/O地址偏移量
printf("标准I/O端口地址(COM1为例):
");
printf(" 基址: 0x3F8
");
printf("寄存器偏移量和功能 (相对于基址):
");
printf(" +0: 发送/接收缓冲寄存器 (THR/RBR)
");
printf(" - 读操作: 接收缓冲寄存器(RBR)
");
printf(" - 写操作: 发送保持寄存器(THR)
");
printf(" +0: 除数锁存器低字节 (DLL) [当DLAB=1]
");
printf(" - 波特率发生器除数的低8位
");
printf(" +1: 中断使能寄存器 (IER)
");
printf(" - 位0: 接收数据有效中断使能
");
printf(" - 位1: 发送保持寄存器空中断使能
");
printf(" - 位2: 接收线状态中断使能
");
printf(" - 位3: 调制解调器状态中断使能
");
printf(" +1: 除数锁存器高字节 (DLM) [当DLAB=1]
");
printf(" - 波特率发生器除数的高8位
");
printf(" +2: 中断识别寄存器 (IIR) [只读]
");
printf(" - 位0: 0=有中断挂起, 1=无中断挂起
");
printf(" - 位1-2: 中断源识别
");
printf(" - 位3-5: 16550 FIFO状态
");
printf(" - 位6-7: 16550 FIFO使能标志
");
printf(" +2: FIFO控制寄存器 (FCR) [只写]
");
printf(" - 位0: FIFO使能
");
printf(" - 位1: 清除接收FIFO
");
printf(" - 位2: 清除发送FIFO
");
printf(" - 位3: DMA模式选择
");
printf(" - 位4-5: 保留
");
printf(" - 位6-7: 接收FIFO触发级别
");
printf(" +3: 线路控制寄存器 (LCR)
");
printf(" - 位0-1: 字长选择 (00=5位, 01=6位, 10=7位, 11=8位)
");
printf(" - 位2: 停止位数量 (0=1位, 1=1.5或2位)
");
printf(" - 位3: 奇偶校验使能
");
printf(" - 位4: 偶校验选择
");
printf(" - 位5: 粘滞奇偶位
");
printf(" - 位6: 断开发送
");
printf(" - 位7: 除数锁存器访问位(DLAB)
");
printf(" +4: 调制解调器控制寄存器 (MCR)
");
printf(" - 位0: 数据终端就绪(DTR)
");
printf(" - 位1: 请求发送(RTS)
");
printf(" - 位2: 辅助输出1(OUT1)
");
printf(" - 位3: 辅助输出2(OUT2)
");
printf(" - 位4: 回路测试模式
");
printf(" +5: 线路状态寄存器 (LSR) [只读]
");
printf(" - 位0: 数据准备好(DR)
");
printf(" - 位1: 覆盖错误(OE)
");
printf(" - 位2: 奇偶校验错误(PE)
");
printf(" - 位3: 帧错误(FE)
");
printf(" - 位4: 断开检测(BI)
");
printf(" - 位5: 发送保持寄存器空(THRE)
");
printf(" - 位6: 发送移位寄存器空(TSRE)
");
printf(" - 位7: 接收FIFO出错(16550)
");
printf(" +6: 调制解调器状态寄存器 (MSR) [只读]
");
printf(" - 位0: 收到清除发送(CTS)变化
");
printf(" - 位1: 收到数据设备就绪(DSR)变化
");
printf(" - 位2: 收到振铃指示(RI)后沿
");
printf(" - 位3: 收到数据载波检测(DCD)变化
");
printf(" - 位4: 清除发送(CTS)状态
");
printf(" - 位5: 数据设备就绪(DSR)状态
");
printf(" - 位6: 振铃指示(RI)状态
");
printf(" - 位7: 数据载波检测(DCD)状态
");
printf(" +7: 暂存器 (SCR)
");
printf(" - 通用读写寄存器,无特定功能
");
}
int main() {
explain_uart_registers();
return 0;
}
12.3.3 波特率设置与计算方法
UART的波特率通过设置分频系数确定:
c
#include <stdio.h>
// 计算波特率除数
unsigned short calculate_baud_divisor(unsigned int baud_rate, unsigned int crystal_freq) {
// 默认UART时钟频率为1.8432MHz
if (crystal_freq == 0) {
crystal_freq = 1843200;
}
// 计算除数 (crystal_freq / (16 * baud_rate))
return (unsigned short)(crystal_freq / (16 * baud_rate));
}
// 根据除数计算实际波特率
double calculate_actual_baud_rate(unsigned short divisor, unsigned int crystal_freq) {
if (crystal_freq == 0) {
crystal_freq = 1843200;
}
return (double)crystal_freq / (16.0 * divisor);
}
void demonstrate_baud_rate_calculation() {
printf("UART波特率计算示例:
");
printf("波特率计算公式: Baud Rate = Crystal_Frequency / (16 * Divisor)
");
printf("分频系数计算公式: Divisor = Crystal_Frequency / (16 * Baud_Rate)
");
printf("常见波特率的分频系数 (1.8432MHz晶振):
");
unsigned int crystal_freq = 1843200; // 1.8432MHz
unsigned int standard_bauds[] = {300, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200};
printf("%-10s %-15s %-15s %-10s
", "波特率", "理论分频系数", "实际分频值", "实际波特率");
printf("--------------------------------------------------------
");
for (int i = 0; i < sizeof(standard_bauds)/sizeof(unsigned int); i++) {
double exact_divisor = (double)crystal_freq / (16.0 * standard_bauds[i]);
unsigned short actual_divisor = calculate_baud_divisor(standard_bauds[i], crystal_freq);
double actual_baud = calculate_actual_baud_rate(actual_divisor, crystal_freq);
printf("%-10d %-15.4f %-15d %-10.2f
",
standard_bauds[i], exact_divisor, actual_divisor, actual_baud);
}
printf("
设置波特率的步骤:
");
printf("1. 设置LCR寄存器的DLAB位为1 (允许访问除数锁存器)
");
printf("2. 将波特率除数的低8位写入DLL (偏移量+0)
");
printf("3. 将波特率除数的高8位写入DLM (偏移量+1)
");
printf("4. 将LCR寄存器的DLAB位恢复为0
");
printf("设置9600波特率的C语言代码:
");
printf(" // 假设UART基址为0x3F8
");
printf(" outb(0x3F8 + 3, inb(0x3F8 + 3) | 0x80); // 设置DLAB=1
");
printf(" outb(0x3F8 + 0, 0x0C); // DLL = 12 (低字节)
");
printf(" outb(0x3F8 + 1, 0x00); // DLM = 0 (高字节)
");
printf(" outb(0x3F8 + 3, inb(0x3F8 + 3) & 0x7F); // 清除DLAB
");
}
int main() {
demonstrate_baud_rate_calculation();
return 0;
}
12.3.4 8250/16550的FIFO特性
16550 UART的主要改进是添加了16字节的FIFO缓冲区,显著提高了性能:
c
#include <stdio.h>
void explain_fifo_features() {
printf("8250/16550 UART的FIFO特性:
");
printf("FIFO缓冲区简介:
");
printf(" - 16550增加的16字节FIFO缓冲区,分为发送FIFO和接收FIFO
");
printf(" - 发送FIFO: 可存储16个待发送字符
");
printf(" - 接收FIFO: 可缓存16个接收到的字符
");
printf("FIFO优势:
");
printf(" 1. 减少CPU干预频率,降低处理负担
");
printf(" 2. 提高高速传输时的可靠性
");
printf(" 3. 减少数据丢失风险
");
printf(" 4. 提供更长的中断延迟时间容限
");
printf("FIFO触发级别(接收FIFO):
");
printf(" - 可配置触发级别: 1, 4, 8, 14字节
");
printf(" - 当接收字符达到触发级别时产生中断
");
printf(" - 较低触发级别: 响应更快,但中断更频繁
");
printf(" - 较高触发级别: 中断频率低,但延迟增加
");
printf("FIFO控制寄存器(FCR)配置示例:
");
printf(" // 启用FIFO,清空发送和接收FIFO,设置接收触发级别为8字节
");
printf(" outb(0x3F8 + 2, 0xC7); // 11000111B
");
printf(" // 位分析:
");
printf(" // 位0: 1 (启用FIFO)
");
printf(" // 位1: 1 (清除接收FIFO)
");
printf(" // 位2: 1 (清除发送FIFO)
");
printf(" // 位3: 0 (DMA模式选择)
");
printf(" // 位6-7: 10 (接收触发级别=8字节)
");
printf("没有FIFO的8250与16550对比:
");
printf(" 8250 (无FIFO):
");
printf(" - 每收到一个字符即产生中断
");
printf(" - 高速率下容易丢失数据
");
printf(" - CPU负担重
");
printf(" 16550 (有FIFO):
");
printf(" - 可批量处理多个字符
");
printf(" - 更高的数据吞吐能力
");
printf(" - 支持更高波特率
");
}
int main() {
explain_fifo_features();
return 0;
}
12.4 异步串行通信适配器编程实现
异步串行通信适配器是连接计算机与串行设备的桥梁,包括硬件接口电路和软件驱动程序。
12.4.1 串行通信适配器的接口电路设计
串行通信适配器需要将UART的TTL电平转换为RS-232C标准所需的电平:
c
#include <stdio.h>
void explain_serial_adapter_circuit() {
printf("串行通信适配器接口电路设计:
");
printf("主要电路组成部分:
");
printf(" 1. UART芯片 (8250/16550系列)
");
printf(" 2. 电平转换器 (MAX232等)
");
printf(" 3. 连接器 (DB9/DB25)
");
printf(" 4. 晶振电路
");
printf(" 5. 电源及滤波电路
");
printf("电平转换器功能:
");
printf(" - 将UART的TTL电平(0/+5V)转换为RS-232C电平(±12V)
");
printf(" - 典型芯片: MAX232、MAX233、MAX3232等
");
printf(" - 通常集成了电荷泵电路,仅需单一+5V供电
");
printf("典型串行适配器接口电路描述:
");
printf(" 1. UART的TXD引脚连接到MAX232的T1IN引脚
");
printf(" 2. MAX232的T1OUT引脚连接到DB9连接器的TD引脚(3号针)
");
printf(" 3. DB9连接器的RD引脚(2号针)连接到MAX232的R1IN引脚
");
printf(" 4. MAX232的R1OUT引脚连接到UART的RXD引脚
");
printf(" 5. 类似地连接其他信号线(RTS/CTS/DTR/DSR等)
");
printf(" 6. 为MAX232添加必要的电荷泵电容(通常为1μF)
");
printf("完整的RS-232C适配器还需要处理:
");
printf(" - 过压保护电路
");
printf(" - 静电防护
");
printf(" - 光电隔离(某些工业应用)
");
printf(" - 信号滤波
");
}
int main() {
explain_serial_adapter_circuit();
return 0;
}
12.4.2 UART初始化与参数设置
在使用UART进行通信前,需要对其进行正确初始化:
c
#include <stdio.h>
#include <stdint.h>
// 模拟I/O函数
void outb(uint16_t port, uint8_t value) {
printf("向端口 0x%04X 写入: 0x%02X
", port, value);
}
uint8_t inb(uint16_t port) {
// 模拟端口读取,实际应用中会从硬件读取
uint8_t simulated_values[] = {0x00, 0x60, 0x00, 0x03, 0x00, 0x60, 0x00, 0x00};
uint8_t value = simulated_values[port & 0x07]; // 简单模拟
printf("从端口 0x%04X 读取: 0x%02X
", port, value);
return value;
}
// UART初始化函数
void init_uart(uint16_t base_addr, uint32_t baud_rate, uint8_t data_bits, char parity, uint8_t stop_bits) {
uint16_t divisor;
uint8_t lcr_value = 0;
printf("
UART初始化 (基址: 0x%04X):
", base_addr);
// 计算波特率除数 (假设16550的时钟为1.8432MHz)
divisor = 115200 / baud_rate;
printf("设置波特率: %d (除数: %d)
", baud_rate, divisor);
// 配置线路控制寄存器 (LCR)
// 设置数据位
switch (data_bits) {
case 5: lcr_value |= 0x00; break;
case 6: lcr_value |= 0x01; break;
case 7: lcr_value |= 0x02; break;
case 8: lcr_value |= 0x03; break;
default: lcr_value |= 0x03; break; // 默认8位
}
// 设置停止位
if (stop_bits == 2) {
lcr_value |= 0x04; // 设置第2位
}
// 设置奇偶校验
switch (parity) {
case 'N': // 无校验
break;
case 'O': // 奇校验
lcr_value |= 0x08; // 使能奇偶校验
break;
case 'E': // 偶校验
lcr_value |= 0x18; // 使能奇偶校验并选择偶校验
break;
case 'M': // 标志位(Mark)
lcr_value |= 0x28; // 使能奇偶校验并设置粘滞奇偶位
break;
case 'S': // 空白位(Space)
lcr_value |= 0x38; // 使能奇偶校验、选择偶校验并设置粘滞奇偶位
break;
}
// 禁用中断
outb(base_addr + 1, 0x00);
// 设置DLAB位以访问除数锁存器
outb(base_addr + 3, lcr_value | 0x80);
// 设置波特率除数
outb(base_addr + 0, divisor & 0xFF); // 低字节
outb(base_addr + 1, (divisor >> 8) & 0xFF); // 高字节
// 恢复LCR,清除DLAB位
outb(base_addr + 3, lcr_value);
// 启用FIFO (针对16550)
// 启用FIFO、清空接收和发送FIFO、设置接收触发级别为14字节
outb(base_addr + 2, 0xC7);
// 设置调制解调器控制寄存器 (MCR)
// 启用DTR、RTS和OUT2
outb(base_addr + 4, 0x0B);
// 清空接收缓冲区
while (inb(base_addr + 5) & 0x01) {
inb(base_addr + 0);
}
// 启用中断
// 0x0F = 启用所有中断 (接收、发送、线状态、调制解调器状态)
outb(base_addr + 1, 0x0F);
printf("UART初始化完成: %d波特率, %d数据位, %c校验, %d停止位
",
baud_rate, data_bits, parity, stop_bits);
}
int main() {
printf("UART初始化与参数设置示例
");
// 初始化COM1 (0x3F8), 9600波特率, 8数据位, 无校验, 1停止位
init_uart(0x3F8, 9600, 8, 'N', 1);
// 初始化COM2 (0x2F8), 115200波特率, 8数据位, 偶校验, 1停止位
init_uart(0x2F8, 115200, 8, 'E', 1);
return 0;
}
12.4.3 串行通信的数据收发程序
以下是完整的串行通信数据收发程序示例:
c
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
// UART寄存器偏移量定义
#define UART_THR 0 // 发送保持寄存器 / 接收缓冲寄存器
#define UART_IER 1 // 中断使能寄存器
#define UART_IIR 2 // 中断识别寄存器
#define UART_FCR 2 // FIFO控制寄存器
#define UART_LCR 3 // 线路控制寄存器
#define UART_MCR 4 // 调制解调器控制寄存器
#define UART_LSR 5 // 线路状态寄存器
#define UART_MSR 6 // 调制解调器状态寄存器
#define UART_SCR 7 // 暂存寄存器
// 线路状态寄存器位定义
#define LSR_DR 0x01 // 数据就绪
#define LSR_OE 0x02 // 覆盖错误
#define LSR_PE 0x04 // 奇偶校验错误
#define LSR_FE 0x08 // 帧错误
#define LSR_BI 0x10 // 断开指示
#define LSR_THRE 0x20 // 发送保持寄存器空
#define LSR_TEMT 0x40 // 发送器空
#define LSR_RXFE 0x80 // 接收FIFO错误
// 模拟端口I/O函数
void outb(uint16_t port, uint8_t value) {
printf("写端口 0x%04X: 0x%02X
", port, value);
}
uint8_t inb(uint16_t port) {
static uint8_t lsr_value = 0x60; // THRE | TEMT初始化为1
static uint8_t simulated_data = 0x41; // 'A'
uint8_t value;
// 简单模拟UART行为
if ((port & 0x07) == UART_LSR) {
// 模拟线路状态变化
// 每次读取LSR时有50%概率设置DR位,模拟接收到数据
if (rand() % 2) {
lsr_value |= LSR_DR;
} else {
lsr_value &= ~LSR_DR;
}
value = lsr_value;
} else if ((port & 0x07) == UART_THR && (lsr_value & LSR_DR)) {
// 模拟接收数据
value = simulated_data++;
if (simulated_data > 'Z') simulated_data = 'A';
lsr_value &= ~LSR_DR; // 清除DR位
} else {
value = 0;
}
printf("读端口 0x%04X: 0x%02X
", port, value);
return value;
}
// 判断发送缓冲区是否为空
bool is_transmit_empty(uint16_t base_addr) {
return (inb(base_addr + UART_LSR) & LSR_THRE) != 0;
}
// 判断是否有接收到的数据可读
bool is_data_available(uint16_t base_addr) {
return (inb(base_addr + UART_LSR) & LSR_DR) != 0;
}
// 发送单个字符
void uart_send_char(uint16_t base_addr, char c) {
// 等待发送缓冲区为空
while (!is_transmit_empty(base_addr)) {
printf("等待发送缓冲区空...
");
}
// 发送字符
outb(base_addr + UART_THR, (uint8_t)c);
printf("已发送字符: '%c' (0x%02X)
", c, (uint8_t)c);
}
// 发送字符串
void uart_send_string(uint16_t base_addr, const char *str) {
printf("
开始发送字符串: "%s"
", str);
while (*str) {
uart_send_char(base_addr, *str++);
}
printf("字符串发送完成
");
}
// 接收单个字符 (非阻塞)
int uart_receive_char(uint16_t base_addr) {
if (is_data_available(base_addr)) {
uint8_t c = inb(base_addr + UART_THR);
printf("接收到字符: '%c' (0x%02X)
", c, c);
return c;
}
return -1; // 无数据可读
}
// 接收字符串 (超时版本)
int uart_receive_string(uint16_t base_addr, char *buffer, int max_len, int timeout_cycles) {
int received = 0;
int timeout_counter = 0;
printf("
开始接收数据 (最大长度: %d, 超时周期: %d)...
", max_len, timeout_cycles);
while (received < max_len - 1) {
int c = uart_receive_char(base_addr);
if (c >= 0) {
// 收到数据
buffer[received++] = (char)c;
timeout_counter = 0; // 重置超时计数器
// 如果收到回车或换行符,结束接收
if (c == '
' || c == '
') {
break;
}
} else {
// 无数据,增加超时计数
timeout_counter++;
if (timeout_counter >= timeout_cycles) {
printf("接收超时
");
break;
}
}
}
// 添加字符串结束符
if (received < max_len) {
buffer[received] = '';
} else {
buffer[max_len - 1] = '';
}
printf("接收完成,共接收 %d 字节: "%s"
", received, buffer);
return received;
}
// 带错误处理的UART通信
bool uart_communicate(uint16_t base_addr, const char *send_data, char *receive_buffer, int buffer_size) {
uint8_t lsr;
int error_count = 0;
printf("
开始UART通信...
");
// 检查线路状态
lsr = inb(base_addr + UART_LSR);
if (lsr & (LSR_OE | LSR_PE | LSR_FE | LSR_BI)) {
printf("通信开始前检测到线路错误: 0x%02X
", lsr);
if (lsr & LSR_OE) printf(" - 覆盖错误
");
if (lsr & LSR_PE) printf(" - 奇偶校验错误
");
if (lsr & LSR_FE) printf(" - 帧错误
");
if (lsr & LSR_BI) printf(" - 断开指示
");
// 读取接收缓冲区以清除错误
inb(base_addr + UART_THR);
error_count++;
}
// 发送数据
uart_send_string(base_addr, send_data);
// 等待一段时间后接收响应
printf("等待响应...
");
for (int i = 0; i < 10; i++) {
printf(".");
}
printf("
");
// 接收响应数据
int received = uart_receive_string(base_addr, receive_buffer, buffer_size, 1000);
// 再次检查线路状态
lsr = inb(base_addr + UART_LSR);
if (lsr & (LSR_OE | LSR_PE | LSR_FE | LSR_BI)) {
printf("接收数据后检测到线路错误: 0x%02X
", lsr);
error_count++;
}
printf("通信完成,错误计数: %d
", error_count);
return error_count == 0 && received > 0;
}
void demonstrate_uart_communication() {
uint16_t com1_addr = 0x3F8;
char receive_buffer[256];
printf("UART通信演示
");
printf("=============
");
// 发送AT命令 (常用于调制解调器通信)
if (uart_communicate(com1_addr, "AT
", receive_buffer, sizeof(receive_buffer))) {
printf("命令执行成功,收到响应: "%s"
", receive_buffer);
} else {
printf("命令执行失败
");
}
// 发送查询命令
if (uart_communicate(com1_addr, "AT+VERSION?
", receive_buffer, sizeof(receive_buffer))) {
printf("查询版本成功,收到响应: "%s"
", receive_buffer);
} else {
printf("查询版本失败
");
}
}
int main() {
demonstrate_uart_communication();
return 0;
}
12.4.4 中断驱动的串行通信实现
基于中断的串行通信能更高效地处理数据传输,减少CPU轮询开销:
c
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>
// UART寄存器定义
#define UART_THR 0 // 发送保持寄存器
#define UART_RBR 0 // 接收缓冲寄存器
#define UART_IER 1 // 中断使能寄存器
#define UART_IIR 2 // 中断识别寄存器
#define UART_FCR 2 // FIFO控制寄存器
#define UART_LCR 3 // 线路控制寄存器
#define UART_MCR 4 // 调制解调器控制寄存器
#define UART_LSR 5 // 线路状态寄存器
#define UART_MSR 6 // 调制解调器状态寄存器
// 中断使能寄存器位定义
#define IER_RDI 0x01 // 接收数据可用中断
#define IER_THRI 0x02 // 发送保持寄存器空中断
#define IER_RLSI 0x04 // 接收线路状态中断
#define IER_MSI 0x08 // 调制解调器状态中断
// 中断识别寄存器位定义
#define IIR_NO_INT 0x01 // 无中断挂起
#define IIR_MASK 0x0F // 中断识别掩码
#define IIR_MSI 0x00 // 调制解调器状态中断
#define IIR_THRI 0x02 // 发送保持寄存器空中断
#define IIR_RDI 0x04 // 接收数据可用中断
#define IIR_RLSI 0x06 // 接收线路状态中断
#define IIR_TIMEOUT 0x0C // 字符超时中断
// 线路状态寄存器位定义
#define LSR_DR 0x01 // 数据就绪
#define LSR_OE 0x02 // 覆盖错误
#define LSR_PE 0x04 // 奇偶校验错误
#define LSR_FE 0x08 // 帧错误
#define LSR_BI 0x10 // 断开指示
#define LSR_THRE 0x20 // 发送保持寄存器空
#define LSR_TEMT 0x40 // 发送器空
#define LSR_FIFOE 0x80 // FIFO错误
// 串口缓冲区大小
#define UART_BUFFER_SIZE 256
// 串口数据结构
typedef struct {
uint16_t base_addr; // 基地址
// 接收缓冲区
unsigned char rx_buffer[UART_BUFFER_SIZE];
unsigned int rx_head; // 接收缓冲区头
unsigned int rx_tail; // 接收缓冲区尾
// 发送缓冲区
unsigned char tx_buffer[UART_BUFFER_SIZE];
unsigned int tx_head; // 发送缓冲区头
unsigned int tx_tail; // 发送缓冲区尾
bool tx_busy; // 发送忙标志
// 错误计数
unsigned int rx_errors; // 接收错误计数
unsigned int rx_overflow; // 接收缓冲区溢出计数
} UartPort;
// 全局UART端口实例
UartPort com_ports[4];
// 模拟端口I/O函数
void outb(uint16_t port, uint8_t value) {
printf("写端口 0x%04X: 0x%02X
", port, value);
}
uint8_t inb(uint16_t port) {
// 模拟返回一些合理的值,实际应用中会从硬件读取
uint8_t simulated_values[] = {0x00, 0x00, 0x00, 0x03, 0x00, 0x60, 0x00, 0x00};
uint8_t value = simulated_values[port & 0x07]; // 简单模拟
printf("从端口 0x%04X 读取: 0x%02X
", port, value);
return value;
}
// 初始化UART端口
void uart_init(int port_num, uint16_t base_addr) {
if (port_num < 0 || port_num > 3) {
printf("错误: 端口号无效 (%d)
", port_num);
return;
}
printf("
初始化UART端口 %d (基址: 0x%04X)
", port_num, base_addr);
UartPort *port = &com_ports[port_num];
port->base_addr = base_addr;
port->rx_head = 0;
port->rx_tail = 0;
port->tx_head = 0;
port->tx_tail = 0;
port->tx_busy = false;
port->rx_errors = 0;
port->rx_overflow = 0;
// 关闭中断
outb(base_addr + UART_IER, 0x00);
// 设置波特率 (假设为9600)
outb(base_addr + UART_LCR, 0x80); // 设置DLAB位
outb(base_addr + 0, 0x0C); // 9600波特率的低字节 (12)
outb(base_addr + 1, 0x00); // 高字节 (0)
// 设置数据格式: 8N1 (8位数据,无校验,1位停止位)
outb(base_addr + UART_LCR, 0x03);
// 启用FIFO,清除接收和发送FIFO,设置14字节触发级别
outb(base_addr + UART_FCR, 0xC7);
// 设置DTR, RTS和OUT2
outb(base_addr + UART_MCR, 0x0B);
// 清空接收缓冲区
while (inb(base_addr + UART_LSR) & LSR_DR) {
inb(base_addr + UART_RBR);
}
// 启用中断
outb(base_addr + UART_IER, IER_RDI | IER_THRI | IER_RLSI);
printf("UART端口 %d 初始化完成
", port_num);
}
// UART中断处理函数
void uart_interrupt_handler(int port_num) {
if (port_num < 0 || port_num > 3) return;
UartPort *port = &com_ports[port_num];
uint16_t base_addr = port->base_addr;
printf("
UART中断处理 (端口 %d)
", port_num);
// 处理所有挂起的中断
while (1) {
uint8_t iir = inb(base_addr + UART_IIR);
// 检查是否还有中断挂起
if (iir & IIR_NO_INT) {
printf("没有更多中断挂起
");
break;
}
// 获取中断类型
uint8_t int_type = iir & IIR_MASK;
switch (int_type) {
case IIR_RLSI: {
// 接收线路状态中断
printf("接收线路状态中断
");
uint8_t lsr = inb(base_addr + UART_LSR);
if (lsr & LSR_OE) {
printf(" 覆盖错误
");
port->rx_errors++;
}
if (lsr & LSR_PE) {
printf(" 奇偶校验错误
");
port->rx_errors++;
}
if (lsr & LSR_FE) {
printf(" 帧错误
");
port->rx_errors++;
}
if (lsr & LSR_BI) {
printf(" 断开指示
");
port->rx_errors++;
}
break;
}
case IIR_RDI:
case IIR_TIMEOUT: {
// 接收数据可用中断或字符超时中断
printf("接收数据中断
");
// 处理所有可用数据
while (inb(base_addr + UART_LSR) & LSR_DR) {
uint8_t data = inb(base_addr + UART_RBR);
// 计算下一个接收头位置
unsigned int next_rx_head = (port->rx_head + 1) % UART_BUFFER_SIZE;
// 检查接收缓冲区是否已满
if (next_rx_head != port->rx_tail) {
// 缓冲区未满,存储数据
port->rx_buffer[port->rx_head] = data;
port->rx_head = next_rx_head;
printf(" 接收到数据: 0x%02X ('%c')
", data,
(data >= 32 && data < 127) ? data : '.');
} else {
// 接收缓冲区溢出
port->rx_overflow++;
printf(" 接收缓冲区溢出
");
}
}
break;
}
case IIR_THRI: {
// 发送保持寄存器空中断
printf("发送保持寄存器空中断
");
// 如果发送缓冲区为空,禁用THRI中断
if (port->tx_head == port->tx_tail) {
port->tx_busy = false;
printf(" 发送缓冲区为空,禁用THRI中断
");
outb(base_addr + UART_IER, IER_RDI | IER_RLSI);
} else {
// 有数据要发送
uint8_t data = port->tx_buffer[port->tx_tail];
port->tx_tail = (port->tx_tail + 1) % UART_BUFFER_SIZE;
// 发送字符
outb(base_addr + UART_THR, data);
printf(" 发送数据: 0x%02X ('%c')
", data,
(data >= 32 && data < 127) ? data : '.');
}
break;
}
case IIR_MSI: {
// 调制解调器状态中断
printf("调制解调器状态中断
");
uint8_t msr = inb(base_addr + UART_MSR);
printf(" MSR值: 0x%02X
", msr);
break;
}
default:
printf("未知中断类型: 0x%02X
", int_type);
break;
}
}
}
// 发送单个字符 (非阻塞)
bool uart_send_char(int port_num, char c) {
if (port_num < 0 || port_num > 3) return false;
UartPort *port = &com_ports[port_num];
uint16_t base_addr = port->base_addr;
// 计算下一个发送头位置
unsigned int next_tx_head = (port->tx_head + 1) % UART_BUFFER_SIZE;
// 检查发送缓冲区是否已满
if (next_tx_head == port->tx_tail) {
printf("发送缓冲区已满,无法发送字符 '%c'
", c);
return false;
}
// 在发送缓冲区中存储字符
port->tx_buffer[port->tx_head] = (unsigned char)c;
port->tx_head = next_tx_head;
// 如果发送器当前不忙,启动发送
if (!port->tx_busy) {
// 检查发送保持寄存器是否为空
if (inb(base_addr + UART_LSR) & LSR_THRE) {
// 直接发送
uint8_t data = port->tx_buffer[port->tx_tail];
port->tx_tail = (port->tx_tail + 1) % UART_BUFFER_SIZE;
outb(base_addr + UART_THR, data);
printf("立即发送字符: '%c'
", data);
}
// 启用发送中断
outb(base_addr + UART_IER, IER_RDI | IER_THRI | IER_RLSI);
port->tx_busy = true;
}
return true;
}
// 发送字符串 (非阻塞)
bool uart_send_string(int port_num, const char *str) {
if (port_num < 0 || port_num > 3) return false;
if (!str) return false;
printf("
开始发送字符串: "%s"
", str);
bool success = true;
while (*str && success) {
success = uart_send_char(port_num, *str++);
}
return success;
}
// 读取单个字符 (非阻塞)
int uart_read_char(int port_num) {
if (port_num < 0 || port_num > 3) return -1;
UartPort *port = &com_ports[port_num];
// 检查接收缓冲区是否为空
if (port->rx_head == port->rx_tail) {
return -1; // 无数据可读
}
// 读取字符
unsigned char c = port->rx_buffer[port->rx_tail];
port->rx_tail = (port->rx_tail + 1) % UART_BUFFER_SIZE;
return c;
}
// 读取字符串,直到收到特定结束字符或达到最大长度
int uart_read_string(int port_num, char *buffer, int max_len, char terminator) {
if (port_num < 0 || port_num > 3 || !buffer || max_len <= 0) {
return -1;
}
int count = 0;
int c;
printf("
读取字符串 (终止符: '%c', 最大长度: %d)
", terminator, max_len);
while (count < max_len - 1) {
c = uart_read_char(port_num);
if (c < 0) {
// 无数据可读,模拟中断处理
printf("等待数据...
");
// 模拟接收到一些数据
if (rand() % 3 == 0) {
UartPort *port = &com_ports[port_num];
unsigned char simulated_data = 'A' + (rand() % 26);
unsigned int next_rx_head = (port->rx_head + 1) % UART_BUFFER_SIZE;
if (next_rx_head != port->rx_tail) {
port->rx_buffer[port->rx_head] = simulated_data;
port->rx_head = next_rx_head;
printf("模拟接收到数据: '%c'
", simulated_data);
}
}
// 模拟中断处理
uart_interrupt_handler(port_num);
continue;
}
printf("读取到字符: '%c'
", c);
buffer[count++] = (char)c;
// 检查终止符
if (c == terminator) {
break;
}
}
// 添加字符串结束符
buffer[count] = '';
printf("读取完成,读取了 %d 个字符: "%s"
", count, buffer);
return count;
}
// 模拟中断驱动的UART通信
void demonstrate_interrupt_driven_uart() {
printf("中断驱动的UART通信演示
");
printf("=======================
");
// 初始化COM1
uart_init(0, 0x3F8);
// 发送命令
uart_send_string(0, "AT
");
// 模拟中断处理
for (int i = 0; i < 5; i++) {
uart_interrupt_handler(0);
}
// 接收响应
char response[128];
uart_read_string(0, response, sizeof(response), '
');
// 发送另一个命令
uart_send_string(0, "AT+VERSION?
");
// 模拟更多中断处理
for (int i = 0; i < 8; i++) {
uart_interrupt_handler(0);
}
// 再次接收响应
uart_read_string(0, response, sizeof(response), '
');
// 显示错误统计
printf("
UART统计信息:
");
printf(" 接收错误: %u
", com_ports[0].rx_errors);
printf(" 缓冲区溢出: %u
", com_ports[0].rx_overflow);
}
int main() {
demonstrate_interrupt_driven_uart();
return 0;
}
12.5 异步串行通信的高级应用
异步串行通信在多种场景下有着广泛应用,从简单的点对点通信到复杂的多设备网络。
12.5.1 通信协议封装与帧格式
在实际应用中,通常需要在基本串行通信之上定义协议格式:
c
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
// 通信帧结构定义
#define FRAME_SOF 0xAA // 帧起始标志
#define FRAME_EOF 0x55 // 帧结束标志
#define MAX_PAYLOAD_SIZE 128 // 最大负载大小
typedef enum {
CMD_READ_DATA = 0x01,
CMD_WRITE_DATA = 0x02,
CMD_SYSTEM_STATUS = 0x03,
CMD_RESET = 0x04,
CMD_ACK = 0xAC,
CMD_NACK = 0xED
} CommandCode;
typedef struct {
uint8_t sof; // 帧起始标志
uint8_t cmd; // 命令码
uint8_t addr; // 地址
uint8_t length; // 有效负载长度
uint8_t payload[MAX_PAYLOAD_SIZE]; // 有效负载
uint8_t checksum; // 校验和
uint8_t eof; // 帧结束标志
} CommFrame;
// 计算校验和 (简单的异或校验)
uint8_t calculate_checksum(const CommFrame *frame) {
uint8_t checksum = frame->cmd ^ frame->addr ^ frame->length;
for (int i = 0; i < frame->length; i++) {
checksum ^= frame->payload[i];
}
return checksum;
}
// 构建通信帧
void build_frame(CommFrame *frame, uint8_t cmd, uint8_t addr, const uint8_t *payload, uint8_t length) {
// 确保负载长度不超过最大值
if (length > MAX_PAYLOAD_SIZE) {
length = MAX_PAYLOAD_SIZE;
}
// 设置帧字段
frame->sof = FRAME_SOF;
frame->cmd = cmd;
frame->addr = addr;
frame->length = length;
// 复制负载数据
if (payload && length > 0) {
memcpy(frame->payload, payload, length);
}
// 计算校验和
frame->checksum = calculate_checksum(frame);
// 设置帧结束标志
frame->eof = FRAME_EOF;
}
// 将帧序列化为字节流
int serialize_frame(const CommFrame *frame, uint8_t *buffer, int buffer_size) {
int min_size = 5 + frame->length; // SOF + CMD + ADDR + LEN + Payload + CKS + EOF
if (!buffer || buffer_size < min_size) {
return -1; // 缓冲区太小
}
int offset = 0;
// 添加帧头
buffer[offset++] = frame->sof;
buffer[offset++] = frame->cmd;
buffer[offset++] = frame->addr;
buffer[offset++] = frame->length;
// 添加负载
for (int i = 0; i < frame->length; i++) {
buffer[offset++] = frame->payload[i];
}
// 添加校验和和帧尾
buffer[offset++] = frame->checksum;
buffer[offset++] = frame->eof;
return offset; // 返回总字节数
}
// 从字节流解析帧
bool parse_frame(CommFrame *frame, const uint8_t *buffer, int length) {
if (!frame || !buffer || length < 5) {
return false; // 数据不足
}
int offset = 0;
// 检查起始标志
if (buffer[offset++] != FRAME_SOF) {
return false; // 无效的起始标志
}
// 解析命令和地址
frame->sof = FRAME_SOF;
frame->cmd = buffer[offset++];
frame->addr = buffer[offset++];
// 解析负载长度
frame->length = buffer[offset++];
// 检查总长度是否足够
if (length < 5 + frame->length) {
return false; // 数据不足
}
// 复制负载
for (int i = 0; i < frame->length; i++) {
frame->payload[i] = buffer[offset++];
}
// 获取校验和
frame->checksum = buffer[offset++];
// 验证校验和
uint8_t calculated_checksum = calculate_checksum(frame);
if (calculated_checksum != frame->checksum) {
printf("校验和错误: 计算值=0x%02X, 接收值=0x%02X
",
calculated_checksum, frame->checksum);
return false; // 校验和错误
}
// 检查结束标志
if (buffer[offset] != FRAME_EOF) {
return false; // 无效的结束标志
}
frame->eof = buffer[offset];
return true; // 成功解析
}
void print_frame(const CommFrame *frame) {
printf("通信帧内容:
");
printf(" SOF: 0x%02X
", frame->sof);
printf(" CMD: 0x%02X (", frame->cmd);
switch (frame->cmd) {
case CMD_READ_DATA: printf("读数据)
"); break;
case CMD_WRITE_DATA: printf("写数据)
"); break;
case CMD_SYSTEM_STATUS: printf("系统状态)
"); break;
case CMD_RESET: printf("复位)
"); break;
case CMD_ACK: printf("确认)
"); break;
case CMD_NACK: printf("否认)
"); break;
default: printf("未知)
"); break;
}
printf(" ADDR: 0x%02X
", frame->addr);
printf(" LEN: %d
", frame->length);
printf(" Payload: ");
for (int i = 0; i < frame->length; i++) {
printf("%02X ", frame->payload[i]);
}
printf("
");
printf(" Checksum: 0x%02X
", frame->checksum);
printf(" EOF: 0x%02X
", frame->eof);
}
void demonstrate_protocol_framing() {
printf("通信协议帧格式示例
");
printf("==================
");
// 创建一个读取温度的命令帧
CommFrame tx_frame;
uint8_t payload[] = {0x01, 0x02}; // 示例负载
build_frame(&tx_frame, CMD_READ_DATA, 0x10, payload, sizeof(payload));
printf("发送帧:
");
print_frame(&tx_frame);
// 序列化帧
uint8_t buffer[256];
int tx_length = serialize_frame(&tx_frame, buffer, sizeof(buffer));
printf("
序列化帧为 %d 字节:
", tx_length);
for (int i = 0; i < tx_length; i++) {
printf("%02X ", buffer[i]);
}
printf("
");
// 模拟数据传输
printf("模拟数据传输...
");
// 解析帧
CommFrame rx_frame;
if (parse_frame(&rx_frame, buffer, tx_length)) {
printf("成功解析接收帧:
");
print_frame(&rx_frame);
} else {
printf("解析帧失败
");
}
// 模拟响应
printf("
生成响应帧...
");
uint8_t resp_payload[] = {0x22, 0x33, 0x44}; // 示例响应数据
CommFrame resp_frame;
build_frame(&resp_frame, CMD_ACK, 0x10, resp_payload, sizeof(resp_payload));
printf("
响应帧:
");
print_frame(&resp_frame);
}
int main() {
demonstrate_protocol_framing();
return 0;
}
12.5.2 多设备串行总线网络
串行接口也可以实现多设备组网,如RS-485网络:
c
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>
// RS-485网络定义
#define MAX_NODES 32 // RS-485网络最大节点数
#define MASTER_ADDRESS 0x00 // 主站地址
#define BROADCAST_ADDR 0xFF // 广播地址
// 通信帧格式
typedef struct {
uint8_t dest_addr; // 目的地址
uint8_t src_addr; // 源地址
uint8_t function_code; // 功能码
uint8_t data_length; // 数据长度
uint8_t data[256]; // 数据
uint16_t crc; // CRC校验
} Rs485Frame;
// 模拟节点
typedef struct {
uint8_t address; // 节点地址
bool online; // 在线状态
uint8_t registers[16]; // 模拟寄存器
} Rs485Node;
// 全局节点列表
Rs485Node nodes[MAX_NODES];
// 模拟CRC计算
uint16_t calculate_crc(const uint8_t *buffer, int length) {
uint16_t crc = 0xFFFF;
for (int i = 0; i < length; i++) {
crc ^= buffer[i];
for (int j = 0; j < 8; j++) {
if (crc & 0x0001) {
crc >>= 1;
crc ^= 0xA001; // 多项式 0x8005 (反转 0xA001)
} else {
crc >>= 1;
}
}
}
return crc;
}
// 序列化帧
int serialize_rs485_frame(const Rs485Frame *frame, uint8_t *buffer, int buffer_size) {
if (!buffer || buffer_size < 5 + frame->data_length) {
return -1;
}
int offset = 0;
buffer[offset++] = frame->dest_addr;
buffer[offset++] = frame->src_addr;
buffer[offset++] = frame->function_code;
buffer[offset++] = frame->data_length;
for (int i = 0; i < frame->data_length; i++) {
buffer[offset++] = frame->data[i];
}
// 计算CRC
uint16_t crc = calculate_crc(buffer, offset);
buffer[offset++] = crc & 0xFF; // 低字节
buffer[offset++] = (crc >> 8) & 0xFF; // 高字节
return offset;
}
// 解析帧
bool parse_rs485_frame(Rs485Frame *frame, const uint8_t *buffer, int length) {
if (!buffer || length < 6) { // 至少包含地址、功能码、长度、CRC
return false;
}
int offset = 0;
frame->dest_addr = buffer[offset++];
frame->src_addr = buffer[offset++];
frame->function_code = buffer[offset++];
frame->data_length = buffer[offset++];
if (length < 6 + frame->data_length) {
return false; // 数据不足
}
for (int i = 0; i < frame->data_length; i++) {
frame->data[i] = buffer[offset++];
}
// 获取CRC
uint16_t received_crc = buffer[offset] | (buffer[offset + 1] << 8);
// 验证CRC
uint16_t calculated_crc = calculate_crc(buffer, offset);
if (calculated_crc != received_crc) {
printf("CRC错误: 计算=0x%04X, 接收=0x%04X
", calculated_crc, received_crc);
return false;
}
frame->crc = received_crc;
return true;
}
// 初始化RS-485网络
void init_rs485_network() {
printf("初始化RS-485网络...
");
// 创建一些模拟节点
for (int i = 0; i < MAX_NODES; i++) {
nodes[i].address = i + 1;
nodes[i].online = (i < 5); // 前5个节点在线
// 初始化寄存器
for (int r = 0; r < 16; r++) {
nodes[i].registers[r] = rand() % 256;
}
}
printf("网络初始化完成,共 %d 个节点 (%d 个在线)
",
MAX_NODES, 5);
}
// 寻找节点
Rs485Node* find_node(uint8_t address) {
for (int i = 0; i < MAX_NODES; i++) {
if (nodes[i].address == address && nodes[i].online) {
return &nodes[i];
}
}
return NULL;
}
// 处理接收到的帧
bool process_frame(Rs485Frame *rx_frame, Rs485Frame *tx_frame) {
// 检查地址
if (rx_frame->dest_addr != MASTER_ADDRESS) {
printf("错误: 目标地址不是主站
");
return false;
}
// 查找源节点
Rs485Node *node = find_node(rx_frame->src_addr);
if (!node) {
printf("错误: 未找到源节点 (地址: 0x%02X)
", rx_frame->src_addr);
return false;
}
// 准备响应帧
tx_frame->dest_addr = rx_frame->src_addr;
tx_frame->src_addr = MASTER_ADDRESS;
tx_frame->function_code = rx_frame->function_code;
// 处理不同的功能码
switch (rx_frame->function_code) {
case 0x01: { // 读寄存器
printf("处理读寄存器请求...
");
uint8_t reg_addr = rx_frame->data[0];
uint8_t reg_count = rx_frame->data[1];
if (reg_addr + reg_count > 16) {
// 错误: 超出寄存器范围
tx_frame->function_code |= 0x80; // 错误响应
tx_frame->data_length = 1;
tx_frame->data[0] = 0x02; // 地址错误
} else {
tx_frame->data_length = reg_count;
for (int i = 0; i < reg_count; i++) {
tx_frame->data[i] = node->registers[reg_addr + i];
}
}
break;
}
case 0x02: { // 写寄存器
printf("处理写寄存器请求...
");
uint8_t reg_addr = rx_frame->data[0];
uint8_t reg_value = rx_frame->data[1];
if (reg_addr >= 16) {
// 错误: 超出寄存器范围
tx_frame->function_code |= 0x80; // 错误响应
tx_frame->data_length = 1;
tx_frame->data[0] = 0x02; // 地址错误
} else {
node->registers[reg_addr] = reg_value;
tx_frame->data_length = 2;
tx_frame->data[0] = reg_addr;
tx_frame->data[1] = reg_value;
}
break;
}
default:
// 不支持的功能码
tx_frame->function_code |= 0x80; // 错误响应
tx_frame->data_length = 1;
tx_frame->data[0] = 0x01; // 不支持的功能码
break;
}
// 计算CRC (在序列化时完成)
return true;
}
// 模拟发送帧
void send_frame(const Rs485Frame *frame) {
printf("
发送帧:
");
printf(" 目的地址: 0x%02X
", frame->dest_addr);
printf(" 源地址: 0x%02X
", frame->src_addr);
printf(" 功能码: 0x%02X
", frame->function_code);
printf(" 数据长度: %d
", frame->data_length);
printf(" 数据: ");
for (int i = 0; i < frame->data_length; i++) {
printf("%02X ", frame->data[i]);
}
printf("
");
// 序列化帧
uint8_t buffer[256];
int length = serialize_rs485_frame(frame, buffer, sizeof(buffer));
printf(" 序列化为 %d 字节: ", length);
for (int i = 0; i < length; i++) {
printf("%02X ", buffer[i]);
}
printf("
");
}
// 模拟RS-485主站发送命令并接收响应
void master_command(uint8_t node_addr, uint8_t function_code, uint8_t *data, int data_length) {
printf("
主站向节点 0x%02X 发送 0x%02X 命令
", node_addr, function_code);
// 构造命令帧
Rs485Frame command;
command.dest_addr = node_addr;
command.src_addr = MASTER_ADDRESS;
command.function_code = function_code;
command.data_length = data_length;
memcpy(command.data, data, data_length);
// 发送命令
send_frame(&command);
// 模拟节点处理命令并响应
printf("
模拟节点处理命令...
");
// 交换源地址和目的地址以模拟节点响应
Rs485Frame response;
response.dest_addr = MASTER_ADDRESS;
response.src_addr = node_addr;
response.function_code = function_code;
// 模拟不同功能码的响应
switch (function_code) {
case 0x01: { // 读寄存器
printf("节点生成读寄存器响应...
");
Rs485Node *node = find_node(node_addr);
if (!node) {
printf("节点不在线,无响应
");
return;
}
uint8_t reg_addr = data[0];
uint8_t reg_count = data[1];
response.data_length = reg_count;
for (int i = 0; i < reg_count; i++) {
response.data[i] = node->registers[reg_addr + i];
}
break;
}
case 0x02: { // 写寄存器
printf("节点生成写寄存器响应...
");
Rs485Node *node = find_node(node_addr);
if (!node) {
printf("节点不在线,无响应
");
return;
}
uint8_t reg_addr = data[0];
uint8_t reg_value = data[1];
node->registers[reg_addr] = reg_value;
response.data_length = 2;
response.data[0] = reg_addr;
response.data[1] = reg_value;
break;
}
default:
printf("不支持的功能码,节点返回错误
");
response.function_code |= 0x80; // 错误响应
response.data_length = 1;
response.data[0] = 0x01; // 不支持的功能码
break;
}
// 发送响应
send_frame(&response);
// 主站处理响应
printf("
主站处理响应...
");
if (response.function_code & 0x80) {
// 错误响应
printf("收到错误响应,错误码: 0x%02X
", response.data[0]);
} else {
// 正常响应
printf("收到正常响应,数据长度: %d
", response.data_length);
printf("响应数据: ");
for (int i = 0; i < response.data_length; i++) {
printf("%02X ", response.data[i]);
}
printf("
");
}
}
void demonstrate_rs485_network() {
printf("RS-485网络通信示例
");
printf("=================
");
// 初始化网络
init_rs485_network();
// 读取节点2的寄存器0-3
printf("
示例1: 读取节点2的寄存器
");
uint8_t read_cmd[2] = {0, 4}; // 起始地址, 数量
master_command(2, 0x01, read_cmd, 2);
// 写入节点3的寄存器5
printf("
示例2: 写入节点3的寄存器
");
uint8_t write_cmd[2] = {5, 0x42}; // 寄存器地址, 值
master_command(3, 0x02, write_cmd, 2);
// 读取节点1的寄存器8-9
printf("
示例3: 读取节点1的寄存器
");
uint8_t read_cmd2[2] = {8, 2}; // 起始地址, 数量
master_command(1, 0x01, read_cmd2, 2);
// 发送不支持的命令
printf("
示例4: 发送不支持的命令
");
uint8_t unsupported_cmd[1] = {0};
master_command(2, 0x07, unsupported_cmd, 1);
// 尝试访问不在线的节点
printf("
示例5: 访问不在线的节点
");
master_command(10, 0x01, read_cmd, 2);
}
int main() {
demonstrate_rs485_network();
return 0;
}
12.5.3 流控制与错误处理
高效可靠的串行通信需要合理的流控制和错误处理机制:
c
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <time.h>
// 模拟UART寄存器定义
#define UART_BASE 0x3F8 // COM1基地址
#define UART_THR 0 // 发送保持寄存器
#define UART_RBR 0 // 接收缓冲寄存器
#define UART_IER 1 // 中断使能寄存器
#define UART_LCR 3 // 线路控制寄存器
#define UART_MCR 4 // 调制解调器控制寄存器
#define UART_LSR 5 // 线路状态寄存器
#define UART_MSR 6 // 调制解调器状态寄存器
// 线路状态寄存器位定义
#define LSR_DR 0x01 // 数据就绪
#define LSR_OE 0x02 // 覆盖错误
#define LSR_PE 0x04 // 奇偶校验错误
#define LSR_FE 0x08 // 帧错误
#define LSR_BI 0x10 // 断开指示
#define LSR_THRE 0x20 // 发送保持寄存器空
#define LSR_TEMT 0x40 // 发送器空
// 调制解调器控制寄存器位定义
#define MCR_DTR 0x01 // 数据终端就绪
#define MCR_RTS 0x02 // 请求发送
#define MCR_OUT1 0x04 // 辅助输出1
#define MCR_OUT2 0x08 // 辅助输出2
#define MCR_LOOP 0x10 // 回环模式
// 调制解调器状态寄存器位定义
#define MSR_CTS 0x10 // 清除发送
#define MSR_DSR 0x20 // 数据设备就绪
#define MSR_RI 0x40 // 振铃指示
#define MSR_DCD 0x80 // 数据载波检测
// 流控制类型
typedef enum {
FLOW_NONE, // 无流控制
FLOW_XON_XOFF, // 软件流控制 (XON/XOFF)
FLOW_RTS_CTS, // 硬件流控制 (RTS/CTS)
FLOW_DTR_DSR // 硬件流控制 (DTR/DSR)
} FlowControlType;
// XON/XOFF字符
#define XON 0x11 // DC1 (设备控制1)
#define XOFF 0x13 // DC3 (设备控制3)
// 错误类型
typedef enum {
ERR_NONE, // 无错误
ERR_TIMEOUT, // 超时
ERR_OVERRUN, // 溢出错误
ERR_PARITY, // 奇偶校验错误
ERR_FRAMING, // 帧错误
ERR_BREAK, // 线路断开
ERR_BUFFER_FULL, // 缓冲区已满
ERR_FLOW_CONTROL // 流控制中止
} ErrorType;
// 通信参数结构
typedef struct {
uint16_t port_base; // 端口基地址
uint32_t baud_rate; // 波特率
uint8_t data_bits; // 数据位 (5-8)
uint8_t stop_bits; // 停止位 (1-2)
char parity; // 奇偶校验 ('N', 'E', 'O', 'M', 'S')
FlowControlType flow_control; // 流控制类型
int timeout_ms; // 操作超时 (毫秒)
bool xon_state; // XON/XOFF状态 (true=XON)
} UartConfig;
// 错误信息结构
typedef struct {
ErrorType type; // 错误类型
char message[128]; // 错误消息
time_t timestamp; // 错误时间戳
} UartError;
// 模拟I/O操作
uint8_t inb(uint16_t port) {
// 简单模拟,在实际应用中会从硬件读取
static int state = 0;
uint8_t value = 0;
switch (port & 0x07) {
case UART_LSR:
// 线路状态寄存器
state = (state + 1) % 8;
// 始终将THRE位置为1,表示发送缓冲区为空
value = LSR_THRE | LSR_TEMT;
// 模拟某些错误条件
if (state == 3) value |= LSR_OE; // 覆盖错误
if (state == 5) value |= LSR_PE; // 奇偶校验错误
if (state == 7) value |= LSR_FE; // 帧错误
// 模拟数据接收
if (state % 2 == 0) value |= LSR_DR; // 数据就绪
break;
case UART_MSR:
// 调制解调器状态寄存器
// 模拟硬件流控制信号
state = (state + 1) % 6;
// 通常情况下所有信号都是活跃的
value = MSR_CTS | MSR_DSR | MSR_DCD;
// 模拟CTS偶尔变为非活跃
if (state == 2) value &= ~MSR_CTS;
// 模拟DSR偶尔变为非活跃
if (state == 4) value &= ~MSR_DSR;
break;
case UART_RBR:
// 接收缓冲寄存器
// 返回一些模拟数据
value = 'A' + (state % 26);
break;
}
printf("读端口 0x%04X: 0x%02X
", port, value);
return value;
}
void outb(uint16_t port, uint8_t value) {
printf("写端口 0x%04X: 0x%02X
", port, value);
}
// 初始化UART配置
void init_uart_config(UartConfig *config) {
config->port_base = UART_BASE;
config->baud_rate = 9600;
config->data_bits = 8;
config->stop_bits = 1;
config->parity = 'N';
config->flow_control = FLOW_NONE;
config->timeout_ms = 1000; // 1秒超时
config->xon_state = true; // 初始状态为XON
}
// 发送单个字节,带流控制
bool send_byte_with_flow_control(UartConfig *config, uint8_t byte, UartError *error) {
time_t start_time = time(NULL);
bool success = false;
printf("
发送字节 0x%02X ('%c'):
",
byte, (byte >= 32 && byte < 127) ? byte : '.');
while (time(NULL) - start_time < config->timeout_ms / 1000) {
// 检查发送缓冲区是否为空
if (!(inb(config->port_base + UART_LSR) & LSR_THRE)) {
// 发送缓冲区满,等待
printf("发送缓冲区满,等待...
");
continue;
}
// 检查硬件流控制
if (config->flow_control == FLOW_RTS_CTS) {
// 检查CTS信号
if (!(inb(config->port_base + UART_MSR) & MSR_CTS)) {
printf("CTS信号非活跃,等待...
");
continue;
}
}
else if (config->flow_control == FLOW_DTR_DSR) {
// 检查DSR信号
if (!(inb(config->port_base + UART_MSR) & MSR_DSR)) {
printf("DSR信号非活跃,等待...
");
continue;
}
}
// 检查软件流控制
if (config->flow_control == FLOW_XON_XOFF && !config->xon_state) {
printf("已收到XOFF,暂停发送
");
if (error) {
error->type = ERR_FLOW_CONTROL;
strcpy(error->message, "软件流控制中止传输");
error->timestamp = time(NULL);
}
return false;
}
// 发送字节
outb(config->port_base + UART_THR, byte);
printf("成功发送字节
");
success = true;
break;
}
if (!success) {
if (error) {
error->type = ERR_TIMEOUT;
strcpy(error->message, "发送操作超时");
error->timestamp = time(NULL);
}
}
return success;
}
// 接收单个字节,带流控制
int receive_byte_with_flow_control(UartConfig *config, UartError *error) {
time_t start_time = time(NULL);
int received_byte = -1;
printf("
接收字节:
");
while (time(NULL) - start_time < config->timeout_ms / 1000) {
// 检查线路状态
uint8_t lsr = inb(config->port_base + UART_LSR);
// 检查错误
if (lsr & LSR_OE) {
printf("检测到覆盖错误
");
if (error) {
error->type = ERR_OVERRUN;
strcpy(error->message, "接收缓冲区覆盖错误");
error->timestamp = time(NULL);
}
}
if (lsr & LSR_PE) {
printf("检测到奇偶校验错误
");
if (error) {
error->type = ERR_PARITY;
strcpy(error->message, "接收到奇偶校验错误");
error->timestamp = time(NULL);
}
}
if (lsr & LSR_FE) {
printf("检测到帧错误
");
if (error) {
error->type = ERR_FRAMING;
strcpy(error->message, "接收到帧错误");
error->timestamp = time(NULL);
}
}
if (lsr & LSR_BI) {
printf("检测到线路断开
");
if (error) {
error->type = ERR_BREAK;
strcpy(error->message, "检测到线路断开");
error->timestamp = time(NULL);
}
}
// 检查数据是否就绪
if (lsr & LSR_DR) {
// 数据就绪,读取字节
received_byte = inb(config->port_base + UART_RBR);
printf("接收到字节 0x%02X ('%c')
",
received_byte, (received_byte >= 32 && received_byte < 127) ? received_byte : '.');
// 处理XON/XOFF流控制
if (config->flow_control == FLOW_XON_XOFF) {
if (received_byte == XON) {
printf("接收到XON,恢复传输
");
config->xon_state = true;
continue; // 不返回XON字符
}
else if (received_byte == XOFF) {
printf("接收到XOFF,暂停传输
");
config->xon_state = false;
continue; // 不返回XOFF字符
}
}
break;
}
// 控制RTS信号 (如果使用RTS/CTS流控制)
if (config->flow_control == FLOW_RTS_CTS) {
// 模拟接收缓冲区状态
bool buffer_almost_full = (rand() % 10) == 0;
if (buffer_almost_full) {
// 接收缓冲区即将满,取消RTS信号
outb(config->port_base + UART_MCR, inb(config->port_base + UART_MCR) & ~MCR_RTS);
printf("接收缓冲区即将满,取消RTS信号
");
} else {
// 接收缓冲区有足够空间,置位RTS信号
outb(config->port_base + UART_MCR, inb(config->port_base + UART_MCR) | MCR_RTS);
}
}
}
if (received_byte == -1) {
printf("接收操作超时
");
if (error) {
error->type = ERR_TIMEOUT;
strcpy(error->message, "接收操作超时");
error->timestamp = time(NULL);
}
}
return received_byte;
}
// 发送软件流控制信号
void send_flow_control_signal(UartConfig *config, bool xon) {
printf("
发送%s信号
", xon ? "XON" : "XOFF");
// 发送XON或XOFF字符
uint8_t signal = xon ? XON : XOFF;
outb(config->port_base + UART_THR, signal);
}
// 发送字符串,带错误处理和重试
bool send_string_with_retry(UartConfig *config, const char *str, int max_retries) {
UartError error;
int retries = 0;
size_t len = strlen(str);
size_t pos = 0;
printf("
发送字符串: "%s"
", str);
while (pos < len) {
bool success = send_byte_with_flow_control(config, str[pos], &error);
if (success) {
// 发送成功,继续下一个字节
pos++;
retries = 0; // 重置重试计数
} else {
// 发送失败,检查错误类型
printf("发送错误: %s
", error.message);
// 流控制中止不重试
if (error.type == ERR_FLOW_CONTROL) {
printf("流控制中止,等待恢复...
");
// 等待一段时间
printf("等待500ms...
");
// 模拟XON恢复
if (rand() % 2) {
printf("模拟接收XON信号
");
config->xon_state = true;
}
} else {
// 超时或其他错误,尝试重试
retries++;
if (retries > max_retries) {
printf("达到最大重试次数 (%d), 中止发送
", max_retries);
return false;
}
printf("重试 %d/%d...
", retries, max_retries);
}
}
}
printf("字符串发送完成
");
return true;
}
// 接收字符串,带错误处理和超时
int receive_string_with_timeout(UartConfig *config, char *buffer, int max_len, char terminator) {
UartError error;
int count = 0;
printf("
接收字符串 (终止符: '%c', 最大长度: %d):
", terminator, max_len);
while (count < max_len - 1) {
int byte = receive_byte_with_flow_control(config, &error);
if (byte >= 0) {
buffer[count++] = (char)byte;
// 检查终止符
if (byte == terminator) {
break;
}
// 检查接收缓冲区状态 (软件流控制)
if (config->flow_control == FLOW_XON_XOFF) {
// 模拟缓冲区临界状态
bool buffer_almost_full = (count > 0) && (count % 10 == 8);
bool buffer_has_space = (count > 0) && (count % 10 == 0);
if (buffer_almost_full) {
// 发送XOFF
send_flow_control_signal(config, false);
} else if (buffer_has_space) {
// 发送XON
send_flow_control_signal(config, true);
}
}
} else {
// 接收错误
printf("接收错误: %s
", error.message);
// 如果是超时错误,并且已经接收了一些数据,则返回
if (error.type == ERR_TIMEOUT && count > 0) {
printf("接收超时但已收到部分数据
");
break;
}
// 如果是严重错误,中止接收
if (error.type != ERR_TIMEOUT) {
printf("接收到严重错误,中止接收
");
return -1;
}
}
}
// 添加字符串结束符
if (count < max_len) {
buffer[count] = '';
} else {
buffer[max_len - 1] = '';
}
printf("接收到字符串: "%s"
", buffer);
return count;
}
// 打印错误信息
void print_error(const UartError *error) {
if (!error) return;
printf("
串行通信错误信息:
");
printf(" 错误类型: ");
switch (error->type) {
case ERR_NONE: printf("无错误
"); break;
case ERR_TIMEOUT: printf("超时
"); break;
case ERR_OVERRUN: printf("溢出错误
"); break;
case ERR_PARITY: printf("奇偶校验错误
"); break;
case ERR_FRAMING: printf("帧错误
"); break;
case ERR_BREAK: printf("线路断开
"); break;
case ERR_BUFFER_FULL: printf("缓冲区已满
"); break;
case ERR_FLOW_CONTROL: printf("流控制中止
"); break;
default: printf("未知错误
"); break;
}
printf(" 错误消息: %s
", error->message);
printf(" 时间戳: %s", ctime(&error->timestamp));
}
void demonstrate_flow_control() {
printf("串行通信流控制与错误处理演示
");
printf("==========================
");
UartConfig config;
UartError error;
char buffer[256];
// 初始化配置
init_uart_config(&config);
// 测试不同流控制方式
// 1. 无流控制
printf("
测试1: 无流控制
");
config.flow_control = FLOW_NONE;
send_string_with_retry(&config, "Hello World!", 3);
receive_string_with_timeout(&config, buffer, sizeof(buffer), '
');
// 2. 软件流控制 (XON/XOFF)
printf("
测试2: 软件流控制 (XON/XOFF)
");
config.flow_control = FLOW_XON_XOFF;
send_string_with_retry(&config, "Testing XON/XOFF flow control", 3);
receive_string_with_timeout(&config, buffer, sizeof(buffer), '
');
// 模拟接收XOFF
printf("
模拟接收XOFF,然后发送数据:
");
config.xon_state = false;
if (!send_string_with_retry(&config, "This should wait for XON", 3)) {
printf("发送中止,等待XON
");
}
// 3. 硬件流控制 (RTS/CTS)
printf("
测试3: 硬件流控制 (RTS/CTS)
");
config.flow_control = FLOW_RTS_CTS;
send_string_with_retry(&config, "Testing RTS/CTS flow control", 3);
receive_string_with_timeout(&config, buffer, sizeof(buffer), '.');
// 4. 错误处理演示
printf("
测试4: 错误处理演示
");
// 模拟各种错误并处理
error.type = ERR_PARITY;
strcpy(error.message, "奇偶校验错误:接收到的数据不符合配置的奇偶校验规则");
error.timestamp = time(NULL);
print_error(&error);
error.type = ERR_FRAMING;
strcpy(error.message, "帧错误:没有检测到有效的停止位");
error.timestamp = time(NULL);
print_error(&error);
error.type = ERR_TIMEOUT;
strcpy(error.message, "操作超时:在指定时间内未完成操作");
error.timestamp = time(NULL);
print_error(&error);
}
int main() {
demonstrate_flow_control();
return 0;
}
练习题12
解释串行通信和并行通信的区别,并分析各自的优缺点。
RS-232C标准中,数据传输使用负逻辑,请解释这种设计的原因及优势。
计算使用8250/16550 UART,在晶振频率为1.8432MHz时,如何设置波特率为19200的分频值。
在异步串行通信中,为什么需要起始位和停止位?没有它们会导致什么问题?
使用C语言编写一个函数,实现通过串行端口发送一个字符串,并处理可能出现的超时错误。
比较16550和8250 UART芯片的主要区别,并说明16550的FIFO缓冲区如何提高性能。
设计一个基于RS-485的多设备通信网络,描述其拓扑结构、地址分配和通信协议。
在串行通信中,硬件流控制和软件流控制有何区别?各自适用于什么场景?
编写一个异步串行通信程序,能够实现双向数据传输,并处理通信中可能出现的错误情况。
简述如何使用中断方式进行串行通信,与轮询方式相比有哪些优势?

