day44 51单片机UART串行通信 软件模拟UART + 硬件UART回显


📌 项目目标

通过两个完整工程,掌握 51单片机串行通信的两种实现方式

  1. 软件模拟UART —— 利用定时器+GPIO精确控制时序,模拟串口协议发送数据;
  2. 硬件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暂存至局部变量num
	int 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;                         // 启动定时器0

	while(!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;            // 发送高电平作为校验位
		else
			UART_TXD = 0;            // 发送低电平作为校验位
		uart_delay();                // 校验位延时
		uart_delay();                // 二次延时
	}
	else if(check == 2)              // 奇校验模式
	{
	 	if(num % 2)                  // 若“1”的个数为奇数
			UART_TXD = 1;            // 发送高电平作为校验位
		else
			UART_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;                     // 启动定时器0
    while(!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;     // 自动重装值 = TH1
    TR1 = 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收发,两者结合掌握串行通信底层原理与工程实践,是嵌入式开发的必备技能。


🧭 串行口通信四要素(通用)

  1. 波特率(Baud Rate) —— 每秒传输的位数(如9600bps),决定通信速度;
  2. 数据位(Data Bits) —— 每帧有效数据长度(通常8位);
  3. 校验位(Parity Bit) —— 奇校验/偶校验/无校验,用于简单错误检测;
  4. 停止位(Stop Bit) —— 帧结束标志(通常1位);
  5. 收发数据操作方法 —— 初始化 → 发送(uart_send)→ 接收(uart_recv)→ 处理。

✅ 最终运行效果总结

  • 工程一(软件UART):持续发送“hello”,串口助手显示“hellohellohello…”,P2口周期闪烁;
  • 工程二(硬件UART回显):发送任意字符立即原样返回,实现“你说我复读”,P2口持续闪烁;
  • 共同点:均通过STC-ISP或通用串口助手验证,P2口LED证明程序存活,定时器中断机制确保时序精确;
  • 教学价值:从零构建通信协议(软件)→ 使用硬件外设(硬件),完整覆盖串口通信知识体系。
Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐