Day44 51单片机UART串行通信 软件模拟UART + 硬件UART回显
day44 51单片机UART串行通信 软件模拟UART + 硬件UART回显
📌 项目目标
通过两个完整工程,掌握 51单片机串行通信的两种实现方式:
- 软件模拟UART —— 利用定时器+GPIO精确控制时序,模拟串口协议发送数据;
- 硬件UART回显系统 —— 使用51内置串口控制器,实现接收-发送闭环验证。
两者均通过 STC-ISP串口助手 验证通信结果,是嵌入式开发中串口调试的核心技能。
🧩 工程一:软件模拟UART发送字符串 “hello”
✅ 核心思想:用定时器中断实现精确位延时,通过P3.1引脚逐位模拟UART协议发送ASCII字符。
#include <reg51.h> // 引入标准51单片机寄存器定义文件sbit UART_TXD = P3^1; // 定义P3.1引脚为UART数据发送端口,用于输出串行数据(硬件TXD引脚,兼容外部串口设备)// 延时函数:通过双重循环结构实现基础延时,参数i控制延时长度(非精确,用于主循环节奏控制)
void delay(unsigned int i)
{unsigned int num = i; // 将输入参数i暂存至局部变量numint j = 0; // 外层循环计数器for(j = 0; j < 10; j++) // 外层循环执行10次{num = i; // 每轮内层循环前重载初始值while(num--); // 内层空循环,通过递减计数实现延时(粗略毫秒级)}
}unsigned char delay_flag = 0; // 全局延时完成标志位,由定时器中断置位(用于精确微秒级等待)// 定时器0初始化函数:配置为模式1(16位定时器),并开启中断(用于生成精确位时间)
void timer0_init(void)
{unsigned char mod = 0; // 临时变量,用于修改TMOD寄存器mod = TMOD; // 读取当前TMOD值mod &= ~(0xf << 0); // 清除定时器0的模式控制位(低4位)mod |= (0x1 << 0); // 设置定时器0为模式1(16位定时器)TMOD = mod; // 写回配置值ET0 = 1; // 使能定时器0中断EA = 1; // 开启总中断允许(全局中断开关)
}// UART位延时函数:通过定时器0中断实现固定时间间隔(约50μs,对应600bps波特率)
void uart_delay(void)
{unsigned char mod = 0; // 保留局部变量定义(未使用,兼容性保留)delay_flag = 0; // 清除延时标志,准备等待中断触发TH0 = (65536 - 760) / 256 ; // 装载定时器0高8位初值(机器周期=1μs,760周期≈760μs,实际用于600bps≈1667μs/位,此处为简化演示)TL0 = (65536 - 760) % 256 ; // 装载定时器0低8位初值TR0 = 1; // 启动定时器0while(!delay_flag); // 等待中断服务程序将标志位置1(阻塞等待,实现精确延时)
}// UART数据发送函数:支持无校验、偶校验、奇校验三种模式
// 参数说明:dat = 待发送字节,check = 0(无校验)、1(偶校验)、2(奇校验)
void uart_send(unsigned char dat, unsigned char check)
{unsigned char i = 0; // 位计数器,用于遍历8位数据unsigned char num = 0; // 统计数据中“1”的位数,用于校验计算UART_TXD = 1; // 空闲状态,输出高电平(线路空闲)UART_TXD = 0; // 发送起始位(低电平,通知接收方开始接收)uart_delay(); // 延时,保持起始位宽度(约1位时间)uart_delay(); // 二次延时,确保位宽稳定(增强抗干扰)// 逐位发送数据(从bit0到bit7,低位在前,符合UART标准)for(i = 0; i < 8; i++){if(dat & (0x1 << i)) // 判断当前位是否为1(位测试){UART_TXD = 1; // 发送高电平num++; // 记录“1”的个数(用于后续校验位计算)}else{UART_TXD = 0; // 发送低电平}uart_delay(); // 保持当前位电平宽度(1位时间)uart_delay(); // 二次延时,确保稳定(增强容错)}// 校验位发送(根据check参数选择校验方式,本工程实际使用无校验)if(check == 1) // 偶校验模式{if(0 == (num % 2)) // 若“1”的个数为偶数UART_TXD = 1; // 发送高电平作为校验位elseUART_TXD = 0; // 发送低电平作为校验位uart_delay(); // 校验位延时uart_delay(); // 二次延时}else if(check == 2) // 奇校验模式{if(num % 2) // 若“1”的个数为奇数UART_TXD = 1; // 发送高电平作为校验位elseUART_TXD = 0; // 发送低电平作为校验位uart_delay(); // 校验位延时uart_delay(); // 二次延时}UART_TXD = 1; // 发送停止位(高电平,标志帧结束)uart_delay(); // 停止位延时uart_delay(); // 二次延时,确保帧结束稳定(接收方采样窗口)
}// 主程序入口
void main(void)
{unsigned char dat = 0; // 保留变量,未使用(兼容性保留)char buf[10] = {"hello"}; // 定义待发送字符串缓冲区(含终止符\0,共6字节)unsigned char i = 0; // 循环索引变量P2 = 0x55; // 初始化P2口输出模式(01010101,用于LED状态指示)timer0_init(); // 初始化定时器0,用于位延时(波特率时基)while(1) // 主循环:持续发送数据(无限循环){for(i = 0; i < 5; i++) // 依次发送"hello"中的5个字符(跳过\0){uart_send(buf[i], 0); // 调用发送函数,无校验模式(check=0)}delay(1000); // 主循环延时,控制发送节奏(约几毫秒,防止发送过快)P2 = ~P2; // 翻转P2口电平,指示程序运行状态(LED闪烁)}
}// 定时器0中断服务函数(中断号1)
void timer0_handler(void) interrupt 1
{delay_flag = 1; // 中断触发,置位延时完成标志(唤醒uart_delay中的while循环)
}
✅ 工程一运行理想结果
单片机通过 P3.1 引脚持续发送字符串 “hello”,串口助手接收到重复的 “hello” 数据流,并在接收缓冲区中显示为连续的 “hellohellohello…” 字符串。
📊 具体表现:
- 波特率:约600bps(由定时器初值760推算,实际位宽≈1667μs);
- 帧结构:1起始位 + 8数据位 + 0校验位 + 1停止位 = 10位/字符;
- 发送内容:每轮循环发送5字节:
'h' 'e' 'l' 'l' 'o'
; - 接收显示:串口助手显示连续
hellohellohello...
(无换行符); - 状态指示:P2口LED以固定频率闪烁(每发送完一次"hello"翻转一次);
- 调试验证:STC-ISP串口助手“操作成功”,接收字节数持续增长(如563字节)。
🧩 工程二:硬件UART串口回显系统(9600bps)
✅ 核心思想:使用51内置串口控制器,配置定时器1为波特率发生器,实现接收即回显功能,验证串口通路。
/** 功能 : STC89C5x 最小串口回显工程* 硬件 : 11.0592 MHz 晶振 → 9600 bps(工业标准晶振,精确匹配常用波特率)*/#include <reg51.h>/*------- 引脚定义 -------*/
sbit UART_TXD = P3^1; // 模拟 UART 发送脚(本工程未使用模拟发送,保留兼容性定义)/*------- 软件延时函数 -------*/
// 粗略延时,主频 11.0592 MHz 下约 1 ms@i≈120(用于超时等待)
void delay(unsigned int i)
{unsigned int num = i;int j = 0;for(j = 0; j < 10; j++){num = i;while(num--);}
}/*------- 定时器 0 相关 -------*/
unsigned char delay_flag = 0; // 1:表示 50 µs 到点(uart_delay 用,为软件UART预留时基)/* 定时器 0 初始化:模式 1(16 位定时),用于产生 50 µs 基准(本工程未用于串口,为扩展预留) */
void timer0_init(void)
{unsigned char mod = 0;mod = TMOD; // 读原寄存器mod &= ~(0xf << 0); // 清 T0 位(定时器0控制位)mod |= (0x1 << 0); // 设 T0 为模式 1(16位定时器)TMOD = mod;ET0 = 1; // 允许 T0 中断(使能定时器0中断)EA = 1; // 开总中断(全局中断允许)
}/* 阻塞 50 µs(9600 bps 一位时间≈104μs,此处760周期≈760μs,仅为示例占位) */
void uart_delay(void)
{delay_flag = 0;TH0 = (65536 - 760) / 256; // 760 个机器周期≈760 µs(12MHz晶振下)TL0 = (65536 - 760) % 256;TR0 = 1; // 启动定时器0while(!delay_flag); // 等待中断置位(阻塞等待)
}/*------- UART 底层收发 -------*/
// 发送 1 字节(查询 TI)—— 使用硬件串口发送寄存器SBUF
void uart_send_byte(unsigned char dat)
{TI = 0; // 清发送标志(必须手动清除,硬件不自动清零)SBUF = dat; // 写入发送缓冲寄存器,硬件自动添加起始位、停止位while(!TI); // 等待发送完成标志置位(查询方式,阻塞等待)
}// 接收 1 字节(带 2 ms 超时)—— 使用硬件串口接收寄存器SBUF
// 返回 0 成功,-1 超时(防止程序卡死在等待接收)
char uart_recv_byte(unsigned char * dat)
{unsigned char num = 2; // 约 2 ms 超时(调用delay()约2次)while((!RI) && num--) // RI=0表示未收到,等待或超时{delay(); // 1 ms 级延时(粗略延时函数)}if(num < 0) // 超时判断(num递减至负数)return -1; // 超时返回错误码*dat = SBUF; // 从接收缓冲寄存器读取数据RI = 0; // 清接收标志(必须手动清除)return 0; // 成功返回0
}/*------- UART 初始化 -------*/
// 9600 bps @11.0592 MHz,8-N-1,允许接收(标准配置)
void uart_init(void)
{unsigned char mod = 0;mod = TMOD;SM0 = 0; // 串口模式0:8位UART(方式1)SM1 = 1; // 串口模式1:8位UART(方式1)REN = 1; // 允许接收(接收使能)PCON &= ~(1 << 7); // SMOD=0,波特率不倍速(SMOD位清零)mod &= ~(0xf << 4); // 清 T1 位(定时器1控制位)mod |= (0x2 << 4); // T1 模式 2(8 位自动重装,用作波特率发生器)TMOD = mod;TH1 = 0xfd; // 9600 bps 初值(11.0592MHz晶振下标准值)TL1 = 0xfd; // 自动重装值 = TH1TR1 = 1; // 启动 T1(启动波特率发生器)
}/*------- 主函数 -------*/
void main(void)
{unsigned char dat = 0; // 接收数据暂存变量char ret = 0; // 接收函数返回值(0成功,-1超时)char buf[10] = {"hello\n\r"}; // 仅占位,未使用(兼容性保留)unsigned char i = 0; // 仅占位,未使用P2 = 0x55; // 初始 LED 状态(01010101)timer0_init(); // 初始化 50 µs 时基(为软件UART预留)uart_init(); // 初始化硬件串口(核心配置)while(1){ret = uart_recv_byte(&dat); // 尝试接收1字节(带超时)if(ret != -1) // 若接收成功(未超时)uart_send_byte(dat); // 立即回显该字节(原样发送回去)P2 = ~P2; // 指示系统运行(每轮循环翻转P2,LED闪烁)}
}/*------- 中断服务 -------*/
// 定时器 0 溢出:50 µs 到点(本工程未用于串口,为软件UART扩展预留)
void timer0_handler(void) interrupt 1
{delay_flag = 1; // 唤醒 uart_delay(置位标志,解除阻塞)
}
✅ 工程二运行理想结果
单片机进入“回显模式”:通过串口助手发送任意字符(如 ‘A’),单片机立即原样返回该字符,实现“你说什么,我回什么”的闭环通信验证。
📊 具体表现:
- 波特率:9600bps(精确匹配11.0592MHz晶振);
- 帧格式:8数据位、无校验、1停止位(8-N-1);
- 发送方式:查询TI标志,阻塞等待发送完成;
- 接收方式:查询RI标志,带2ms超时防卡死;
- 功能表现:
- 串口助手发送 “Hello!” → 单片机回显 “Hello!”;
- 发送 “123” → 回显 “123”;
- 无输入时,程序不阻塞,P2口持续闪烁;
- 状态指示:P2口LED持续闪烁,证明主循环正常运行;
- 调试意义:最小闭环系统,验证串口收发硬件功能正常。
📚 知识体系归纳(双工程对比 + 核心概念)
知识模块 | 软件模拟UART(工程一) | 硬件UART(工程二) | 通用知识点说明 |
---|---|---|---|
波特率产生 | 定时器0中断 + uart_delay() 模拟位时间 | 定时器1模式2自动重装,TH1=0xFD → 9600bps | 波特率 = 1 / 位时间;低速容忍误差大,高速需精确晶振 |
帧结构 | 1起始 + 8数据 + [校验] + 1停止(可配奇偶校验) | 1起始 + 8数据 + 0校验 + 1停止(8-N-1) | 标准异步串行帧,接收方靠起始位同步 |
发送机制 | GPIO逐位翻转 + 精确延时 | 写SBUF → 硬件自动加起止位 → 等待TI置位 | 硬件发送更稳定高效;软件发送灵活但占资源 |
接收机制 | 本工程未实现接收 | 查询RI → 读SBUF → 清RI,带超时机制 | 接收通常用中断更高效,本工程用查询防卡死 |
校验支持 | 支持无/奇/偶校验(通过check参数) | 无校验(SM0=0, SM1=1固定8-N-1) | 工业环境常用奇偶校验增强可靠性 |
延时实现 | 定时器0中断置标志 + while等待(精确微秒级) | 主循环用粗略delay();串口位时由硬件自动控制 | 精确延时必须用定时器;粗略延时可用循环 |
运行指示 | P2 = ~P2 每发送完"hello"翻转一次 | P2 = ~P2 每轮主循环翻转一次(持续闪烁) | LED状态指示是嵌入式调试基础手段 |
调试工具 | STC-ISP串口助手(支持低速600bps) | 通用串口助手(9600bps通用波特率) | STC-ISP对低速支持更好,通用助手需标准波特率 |
适用场景 | 无硬件串口、低速通信、教学演示 | 标准产品开发、高速稳定通信 | 软件UART是应急/学习方案,硬件UART是生产方案 |
🎯 核心知识点一句话总结
通过定时器中断精确控制GPIO电平翻转可软件模拟UART协议实现低速串行通信,而利用51单片机内置串口控制器配合定时器1波特率发生器可实现高效稳定的硬件UART收发,两者结合掌握串行通信底层原理与工程实践,是嵌入式开发的必备技能。
🧭 串行口通信四要素(通用)
- 波特率(Baud Rate) —— 每秒传输的位数(如9600bps),决定通信速度;
- 数据位(Data Bits) —— 每帧有效数据长度(通常8位);
- 校验位(Parity Bit) —— 奇校验/偶校验/无校验,用于简单错误检测;
- 停止位(Stop Bit) —— 帧结束标志(通常1位);
- 收发数据操作方法 —— 初始化 → 发送(uart_send)→ 接收(uart_recv)→ 处理。
✅ 最终运行效果总结
- 工程一(软件UART):持续发送“hello”,串口助手显示“hellohellohello…”,P2口周期闪烁;
- 工程二(硬件UART回显):发送任意字符立即原样返回,实现“你说我复读”,P2口持续闪烁;
- 共同点:均通过STC-ISP或通用串口助手验证,P2口LED证明程序存活,定时器中断机制确保时序精确;
- 教学价值:从零构建通信协议(软件)→ 使用硬件外设(硬件),完整覆盖串口通信知识体系。