51 单片机-串口通信

0. 时钟基础

时钟是串口通信的基石。51 单片机(如 STC89C51)内部所有时序逻辑,包括 CPU 取指、定时器计数、波特率生成等,都依赖统一的时钟信号驱动。理解时钟的来龙去脉,是正确配置串口的前提。

0.1 晶振与时钟周期

单片机通常外接石英晶振产生稳定的振荡信号,作为系统时钟源。以 STC89C51 为例,常见晶振频率为 11.0592 MHz。之所以常选用 11.0592 MHz,是因为该频率可以精确分频出 2400、4800、9600、115200 等常用串口波特率,从而减少通信误码。

由晶振频率可以推导出几个关键时间参数:

  • 时钟周期:晶振频率的倒数,即 1 / fosc。11.0592 MHz 晶振对应的时钟周期约为 0.09 微秒。
  • 机器周期:51 单片机中 12 个时钟周期构成 1 个机器周期,即 12 / fosc。11.0592 MHz 下约为 1.085 微秒。
  • 指令周期:执行一条指令所需的时间,通常为 1 到 4 个机器周期。

0.2 定时器与波特率的关系

串口波特率由定时器 1 作为波特率发生器产生。定时器 1 以机器周期为基准进行计数,当计数溢出时产生一次中断或触发重装载,从而形成稳定的波特率时钟。因此,晶振频率直接决定了定时器初值的计算

在方式 1(8 位数据位,无奇偶校验位)且波特率不加倍(SMOD = 0)时,波特率计算公式为:

c 复制代码
波特率 = (2^SMOD / 32) × (定时器1溢出率)
定时器1溢出率 = fosc / (12 × (256 - TH1))

将 fosc = 11.0592 MHz、SMOD = 0、目标波特率 2400 代入,可解得 TH1 = 0xF4,这正是示例代码中初值的由来。

1. 串口通信概述

串口通信(UART,Universal Asynchronous Receiver/Transmitter,串行异步收发器)是单片机与单片机之间、或单片机与其余主控之间进行通信的常用方式。它通过一根数据线逐位传输数据,实现设备之间的信息交换。

2. 引脚接线

UART 通信至少需要连接 3 根线:

  • RXD:接收数据线。
  • TXD:发送数据线。
  • GND:通信双方一定要共地。因为传输的是电压信号,电压信号是差值,所以基准电压 GND 一定要保持一致。

常见接线方式包括点对点通信,以及单片机与 PC 机之间的通信。

3. 传输特性

3.1 波特率

波特率是串口传输数据的速率,单位为 bps(bit per second),即每秒发送的比特数。常见波特率有 2400、4800、9600、115200 等。

3.2 串行

串口将数据按照字节逐位发送,先发 LSB(最低位)再发 MSB(最高位)。

3.3 异步

通信双方使用不同的时钟源来统一设置传输的波特率。虽然波特率一致,但时钟源会有不同的偏差,容易造成串口通信误码。

3.4 全双工

串口有 TX 和 RX 两根线实现数据收发,因此收发能够同时进行。

4. 串口通信时序

串口发送一个字节的时序为:1 位起始位 + 8 位数据位 + 1 位奇偶校验位(可省略)+ 1 位停止位。

  • 起始位:1 位,低电平,标志数据传输开始。
  • 数据位:通常为 8 位。
  • 校验位:可选,用于简单的错误检测。
  • 停止位:1 位或 2 位,高电平,标志一帧数据结束。

收发双方必须约定相同的帧格式,否则无法正确解析数据。

5. 串口使用流程

5.1 串口发送流程

  1. 设置串口工作模式为模式 1(8 位数据位,无奇偶校验位)。
  2. 设置波特率是否加倍(SMOD 为 0 不加倍,SMOD 为 1 加倍)。
  3. 根据公式设置波特率。
  4. 设置定时器 1 相关功能:将定时器 1 设置为 8 位自动重装载;将定时器 TH1 设置初值(根据公式换算);开启定时器 1。
  5. 发送数据。
  6. 关注 TI(数据发送完 1 个字节),需要手动清除标志位。

5.2 串口接收流程

  1. 设置串口工作模式为模式 1(8 位数据位,无奇偶校验位)。
  2. 设置串口使能接收开关。
  3. 设置波特率是否加倍(SMOD 为 0 不加倍,SMOD 为 1 加倍)。
  4. 根据公式设置波特率。
  5. 设置定时器 1 相关功能:将定时器 1 设置为 8 位自动重装载;将定时器 TH1 设置初值(根据公式换算);开启定时器 1。
  6. 编写中断处理函数。
  7. 开启中断开关。
  8. 等待中断到来。

6. 实践示例:51 单片机串口发送

下面以 STC89C51 芯片(11.0592 MHz 晶振)为例,演示如何初始化串口并循环发送字符串。该实验使用定时器 1 作为波特率发生器,波特率设置为 2400。

c 复制代码
#include <reg52.h>
#include <intrins.h>

void Delay1000ms(void)	//@11.0592MHz
{
	unsigned char data i, j, k;

	_nop_();
	i = 8;
	j = 1;
	k = 243;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}

void UartInit(void)
{
	//设置串口为方式1
	SCON &= ~(0x3 << 6);
	SCON |= (0x1 << 6);
	
	//允许接收
	REN = 0;
	
	//波特率不加倍
	PCON &= ~(0x1 << 7);
	
	//定时器1设置为8位自动重装载模式
	TMOD &= ~(0x3 << 4);
	TMOD |= 0x1 << 5;
	
	//根据波特率2400配置TH1初值0xF4
	TH1 = 0xF4;
	
	//开启定时器
	TR1 = 1;
	
	return;
}

void UartSendByte(unsigned char TmpByte)
{
	SBUF = TmpByte;
	
	while (0 == TI);
	TI = 0;
	
	return;
}

void UartSendString(char *pstr)
{
	while (*pstr != '\0')
	{
		UartSendByte(*pstr);
		pstr++;
	}
	
	return;
}

int main(void)
{
	//初始化串口波特率为2400
	UartInit();
	
	while (1)
	{
		UartSendString("hello world");
		Delay1000ms();
	}
	
	return 0;
}

该实验的关键在于:波特率由定时器 1 的初值决定。在 11.0592 MHz 晶振下,将 TH1 配置为 0xF4,即可得到 2400 波特率。主循环中每隔 1 秒通过串口发送一次 "hello world"。

6.1 实践示例:51 单片机串口接收与命令控制

下面以 STC89C51 芯片(11.0592 MHz 晶振)为例,演示如何通过串口接收上位机发送的命令,并控制 LED、蜂鸣器和继电器。该实验使用定时器 1 作为波特率发生器,波特率设置为 2400,同时开启串口接收中断。

c 复制代码
#include <reg52.h>
#include <intrins.h>
#include <string.h>

sbit Buzzer = P2^1;
sbit Relay = P2^7;

unsigned char Duty = 0;
unsigned char ReloadTH0 = 0;
unsigned char ReloadTL0 = 0;

volatile unsigned char RecvBuff[20] = {0};
volatile unsigned char RecvLen = 0;
volatile unsigned char IsCommandLine = 0;

//串口接收中断处理函数
void UartHandler(void) interrupt 4
{
	if (RI)
	{
		RecvBuff[RecvLen] = SBUF;
		RI = 0;
		RecvLen++;
		if ('\n' == RecvBuff[RecvLen-1])
		{
			IsCommandLine = 1;
		}
	}
	return;
}

void Delay1000ms(void)	//@11.0592MHz
{
	unsigned char data i, j, k;

	_nop_();
	i = 8;
	j = 1;
	k = 243;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}

void UartInit(void)
{
	//设置串口为方式1
	SCON &= ~(0x3 << 6);
	SCON |= (0x1 << 6);
	
	//允许接收
	REN = 1;
	
	//波特率不加倍
	PCON &= ~(0x1 << 7);
	
	//定时器1设置为8位自动重装载模式
	TMOD &= ~(0x3 << 4);
	TMOD |= 0x1 << 5;
	
	//根据波特率2400配置TH1初值0xF4
	TH1 = 0xF4;
	
	//开启定时器
	TR1 = 1;
	
	//开启中断
	ES = 1;
	EA = 1;
	
	return;
}

void UartSendByte(unsigned char TmpByte)
{
	SBUF = TmpByte;
	
	while (0 == TI);
	TI = 0;
	
	return;
}

void UartSendString(char *pstr)
{
	while (*pstr != '\0')
	{
		UartSendByte(*pstr);
		pstr++;
	}
	
	return;
}

void Timer0Handler(void) interrupt 1
{
	static unsigned char curcnt = 0;
	
	TH0 = ReloadTH0;
	TL0 = ReloadTL0;
	
	curcnt += 10;
	if (curcnt >= 100)
	{
		curcnt = 0;
	}
	
	if (curcnt > Duty)
	{
		Buzzer = 0;
	}
	else
	{
		Buzzer = 1;
	}
	
	return;
}

void SetPwmFreq(unsigned long freq, unsigned char TmpDuty)
{
	unsigned long value = 0;
	
	value = 65536UL - ((1000000UL / freq) / 10UL / 1UL);
	ReloadTH0 = value / 256;
	ReloadTL0 = value % 256;
	TH0 = ReloadTH0;
	TL0 = ReloadTL0;
	
	Buzzer = 1;
	Duty = TmpDuty;
	
	return;
}

void Timer0Init(void)
{
	TMOD &= ~0x3;
	TMOD |= 0x1;
	
	SetPwmFreq(1000, 50);
	
	ET0 = 1;
	EA = 1;
	
	return;
}

int main(void)
{
	//初始化串口波特率为2400
	UartInit();
	Timer0Init();
	
	while (1)
	{
		if (!IsCommandLine)
		{
			continue;
		}
		
		if (0 == strcmp(RecvBuff, "LEDON\r\n"))
		{
			P2 = 0x00;
		}
		else if (0 == strcmp(RecvBuff, "LEDOFF\r\n"))
		{
			P2 = 0xFF;
		}
		else if (0 == strcmp(RecvBuff, "BEEPON\r\n"))
		{
			TR0 = 1;
		}
		else if (0 == strcmp(RecvBuff, "BEEPOFF\r\n"))
		{
			TR0 = 0;
		}
		else if (0 == strcmp(RecvBuff, "RELAYON\r\n"))
		{
			Relay = 0;
		}
		else if (0 == strcmp(RecvBuff, "RELAYOFF\r\n"))
		{
			Relay = 1;
		}
		
		UartSendString("ok\r\n");
		
		memset(RecvBuff, 0, sizeof(RecvBuff));
		RecvLen = 0;
		IsCommandLine = 0;
	}
	
	return 0;
}

该实验的关键在于:串口接收采用中断方式 。当上位机发送一帧以 \r\n 结尾的命令时,中断服务函数逐字节存入缓冲区,并在收到换行符后置位 IsCommandLine 标志。主循环检测到该标志后,通过 strcmp 匹配命令并执行对应操作,最后回发 ok 确认。

常用命令如下:

  • LEDON:点亮 LED(P2 口输出低电平)。
  • LEDOFF:熄灭 LED(P2 口输出高电平)。
  • BEEPON:开启蜂鸣器(启动定时器 0 产生 PWM)。
  • BEEPOFF:关闭蜂鸣器(停止定时器 0)。
  • RELAYON:吸合继电器(Relay 输出低电平)。
  • RELAYOFF:断开继电器(Relay 输出高电平)。

7. 常见问题与排查

  • 乱码:通常是波特率不匹配或时钟配置错误,检查双方波特率是否一致。
  • 无数据:检查时钟是否使能、引脚复用是否正确、电平是否匹配。
  • 偶发误码:可能是时钟误差偏大或信号干扰,尝试降低波特率或使用屏蔽线。
  • 高速通信不稳定:检查过采样配置和时钟精度,必要时使用外部高精度晶振。

8. 总结

串口通信看似简单,但背后离不开时钟系统的支撑。从波特率的生成、数据位的采样,到收发双方的同步,时钟贯穿始终。理解时钟与串口的关系,不仅能帮助我们正确配置硬件,还能在遇到通信异常时快速定位问题。

相关推荐
kkkkkkkkkk_Z3 小时前
单片机|学习日记:单片机底层架构与寄存器操作全解析
linux·笔记·单片机·学习·51单片机
辰域电子1 天前
STM32项目开源:智能插座控制系统(代码 + 原理图 + 仿真)
stm32·单片机·嵌入式硬件·开源·51单片机·proteus
ouynagda1 天前
DS18B20单总线温度传感器原理与51单片机驱动
51单片机
myy-learn2 天前
【51 单片机入门】点亮第一个 LED|模块化编程 led.c(完整源码 + 踩坑)
51单片机
llilian_162 天前
北斗授时卡同步解决方案 gnss授时卡 计算机时间同步板卡
大数据·网络·人工智能·功能测试·51单片机
老当益壮梁奶奶2 天前
ARM 汇编学习笔记(二)启动代码、指令、函数调用与 C 混合编程
c语言·arm开发·单片机·嵌入式硬件·51单片机
myy-learn2 天前
51单片机-中断-晶体管
51单片机
myy-learn2 天前
51单片机-按键LED-数码管定时
51单片机
HRTOS2 天前
HRTOS Debug:8051实时操作系统资源状态监控与分析
单片机·嵌入式硬件·51单片机