c51串口通信原理及实操
UART(外设)
(通信主机)之间进行通信?
GND:
并行通信:一次性将多条数据传输
优点:效率高
缺点:单片机引脚资源有限,并行通信方法占用大量的应用层资源
解决方法:按位序依次发送部分数据
串行通信:同一时刻传输一个bit
例:USB
串口通信既可以是全双工的,也可以是半双工或单工的,这取决于具体的硬件设计和通信协议。
-
全双工:最常见的串口(如RS-232、RS-422)支持全双工通信,即通信双方可以同时发送和接收数据。这是因为它们使用独立的发送线(TX)和接收线(RX),两条路信号传输互不干扰。例如,计算机与外部设备通过RS-232串口通信时,双方可同时收发数据。
-
半双工:某些串口设计(如RS-485)默认工作在半双工模式,发送和接收共用同一组线路,因此同一时间只能单向传输(要么发送,要么接收),需要通过控制信号切换方向。
-
单工:少数特殊场景下(如某些简单传感器的数据发送),串口可能被设计为单工模式,只能单向传输(要么只能发送,要么只能接收)。
半双工通信:
单工通信:
串口通信:
串口通信是一种特殊的串行通信,属于全双工通信
接收信号:RXD
发送信号:TXD
在串口通信中,RXD和TXD是最核心的两根数据线,用于明确数据的传输方向,具体含义如下:
1. RXD(Receive Data):接收信号线
- 功能:用于接收外部设备发送过来的数据。
- 例:当计算机与单片机通信时,计算机的RXD线连接单片机的TXD线,以接收单片机发送的数据;反之,单片机的RXD线连接计算机的TXD线,以接收计算机发送的指令。
2. TXD(Transmit Data):发送信号线
- 功能:用于向外部设备发送数据。
- 例:单片机通过自身的TXD线,将采集到的传感器数据发送到计算机的RXD线;计算机也通过自身的TXD线,将控制指令发送到单片机的RXD线。
&
空闲位:高电平
起始位:低电平
发送顺序:低位先行
奇偶校验正确率:50%
奇校验:
偶校验:
无校验:
停止位:高电平
波特率bps:1200,2400,4800,9600,115200…(bit/s)
传输1bit所需时间:1/波特率
根据所给参数计算每秒传输有效字节数:
-
波特率定义:9600波特率表示每秒传输9600个二进制位(bit)。
-
串口数据帧结构(按你的参数):
- 起始位:1bit(固定,用于标识数据开始)
- 数据位:8bit(约定的传输比特数)
- 校验位:0bit(n:无奇偶校验)
- 停止位:1bit(约定的停止位长度)
- 每帧总长度:1+8+0+1 = 10bit
-
每秒传输字节数计算:
- 1字节(Byte)= 8bit(数据位,不包含控制位)
- 每秒可传输的帧数:9600bit ÷ 10bit/帧 = 960帧
- 每帧包含1个有效字节(8bit数据位),因此每秒传输字节数 = 960字节
关键说明
- 停止位“不固定”是指可设置为1bit、1.5bit或2bit(根据协议约定),若你的场景中停止位为1bit,计算完全成立。
- 若停止位为2bit,结果则为9600÷(1+8+0+2)=872字节/秒,需根据实际配置调整。
综上,在“9600波特率、8数据位、无校验、1停止位”的配置下,每秒可传输960字节
————————————————————————————————————————————
1. 同步通信与异步通信的核心区别(以SCL为标志)
-
同步通信:
通信双方共用一条独立的时钟信号线(如I2C的SCL),发送方通过SCL线向接收方发送时钟信号,双方以该时钟的节拍同步数据传输(即“时钟同步”)。
例如:I2C、SPI协议,均通过SCL(或SCK)时钟线实现同步,数据在时钟的上升沿/下降沿被采样。 -
异步通信:
没有独立的时钟信号线(无SCL),通信双方需提前约定相同的波特率(数据传输速率),通过数据信号中的起始位、停止位等“约定规则”实现同步。
例如:串口通信(UART),发送方和接收方靠预设的波特率(如9600、115200)保持节奏一致,无需额外时钟线。
2. 串口通信的本质:异步串行全双工
- 异步:如上述,串口通信没有SCL时钟线,依赖波特率约定和数据帧格式(起始位、数据位、校验位、停止位)实现同步,属于异步通信。
- 串行:数据按位依次传输(而非并行传输多位),仅通过一根数据线(如TX/RX)即可完成单向数据传输。
- 全双工:标准串口(如RS-232)通过两根独立的数据线(TX发送线、RX接收线)实现双向通信,双方可同时发送和接收数据(如计算机与串口设备通信时,可一边发送指令,一边接收反馈)。
3. 对比I2C(含SCL/SDA)与串口通信
协议 | 时钟线(SCL) | 数据线(SDA/TX/RX) | 通信方式 | 典型应用场景 |
---|---|---|---|---|
I2C | 有(必需) | 1根(双向复用) | 同步串行半双工 | 芯片间短距离通信(如传感器与MCU) |
串口(UART) | 无 | 2根(TX发送、RX接收) | 异步串行全双工 | 设备间远距离通信(如PLC与上位机) |
简言之,SCL的有无是区分同步与异步通信的关键标志:有SCL(如I2C)为同步通信,无SCL(如串口)为异步通信。而串口通信凭借独立的收发线,同时具备了“全双工”能力,是工业控制、设备互联中最常用的异步串行通信方式。
以下是关于主机间通信及相关标准的清晰总结:
一、通信中的基础问题
单片机:使用TTL
- 核心问题:导线存在内阻,导致电压随传输距离增加而衰减,同时易产生串扰(信号相互干扰失真)。
- TTL电平限制:
- 电压值与芯片相关(如51单片机为5V,2440为3.3V)。
- 5V TTL通信距离通常仅10-20米,超过则信号不稳定。
二、RS232标准(解决短距离通信)
- 电平定义:
- 逻辑高电平:-3V ~ -15V
- 逻辑低电平:+3V ~ +15V
- 线路结构:收线(RX)、发线(TX)、地线(GND),共3根线。
- 通信方式:全双工(双方可同时收发数据)。
- 传输距离:理论20-30米。
三、RS485标准(解决长距离通信)
- 信号传输:通过A、B两根信号线,以两者的电压差识别信息:
- 正电压差(A > B):高电平
- 负电压差(A < B):低电平
- 电压范围:±7V ~ ±12V
- +7+12v:表1;-7-12v表0;
- 抗干扰能力:差分信号设计,抗干扰性强。
- 通信方式:半双工(同一时间只能单向传输)。
- 传输距离:可达1200米,适合大范围数据传输。
三种方案对比:
类型 | 通信方式 | 传输距离 | 抗干扰性 | 适用场景 |
---|---|---|---|---|
TTL | 全双工 | 10-20米 | 较弱 | 短距离板间通信 |
RS232 | 全双工 | 20-30米 | 中等 | 短距离设备连接 |
RS485 | 半双工 | 可达1200米 | 强 | 长距离总线通信 |
实操:
需要使用到定时器1
设置定时计数器1初值:
设置为8位自动重载模式
定时器1初值计算:
SMOD:0/1
focs:晶振频率: 单位MHZ
256-21*晶振频率(12)*106/32/设定的波特率(1200)/12=204;
——————————————————————————————
怎么计算不同串行口工作方式下的波特率?
TB8:奇偶校验
TI:手动清零,发送中断请求标志位,用于中断其他请求待信息发送完毕后清零
RI:手动清零,软件复位
总结:要做到发送数据:
1:打开中断开关(定时器一)
2:设置定时器一的工作模式
3:确定SCON下串行口的工作方式
4:修改SCON下的各位置一/零情况
收发数据寄存器:
写发送代码:
1200 n,8,1
1:确认工作方式:
8位:数据位比特数
9位:数据位比特数加上一比特的的奇偶校验位
所以我们选择方式一
为避免电路故障:
//1:初始化函数
void init_uart(void)
{unsigned char t;t=SCON;t &=~(3<<6);//先将第SM0和SM1清零t |=(1<<6);//使用方式一:SM1置1SCON=t;}
REN置1,
void init_uart(void)
{unsigned char t;t=SCON;t &=~(3<<6);//先将第SM0和SM1清零t |=(1<<6)|(1<<4);//使用方式一:SM1置1SCON=t;}
让SMOD置1,波特率加倍:
void init_uart(void)
{unsigned char t;t=SCON;t &=~(3<<6);//先将第SM0和SM1清零t |=(1<<6)|(1<<4);//使用方式一:SM1置1SCON=t;PCON |=(1<<7);//SMOD置1//设置定时器一:}
设置定时器一:
使用单片机晶振为11.0596MHZ
//1:初始化函数
void init_uart(void)
{//设置串口:unsigned char t;t=SCON;t &=~(3<<6);//先将第SM0和SM1清零t |=(1<<6)|(1<<4);//使用方式一:SM1置1SCON=t;PCON |=(1<<7);//SMOD置1//设置定时器一:t=TMOD;t&= ~(3<<4);t |= (2<<4);t &= ~(3 << 6);TMOD = t;TH1 = 208;TL1 = 208;TCON |= (1 << 6);}
发送数据:
void send_char(char ch)
{SBUF = ch;while((SCON & (1 << 1)) == 0); //TI为1时数据发送完毕,TI为0时数据正在发送SCON &= ~(1 << 1);//手动置0
}
总代码:发送字符
#include<reg52.h>
#include"delay.h"
//1:初始化函数
void init_uart(void)
{//设置串口:unsigned char t;t=SCON;t &=~(3<<6);//先将第SM0和SM1清零t |=(1<<6)|(1<<4);//使用方式一:SM1置1SCON=t;PCON |=(1<<7);//SMOD置1//设置定时器一:t=TMOD;t &= ~(3<<4);t |= (2<<4);t &= ~(3<< 6);TMOD = t;TH1 = 208; //256-2*11.0596*1000000/32/1200/12=208TL1 = 208;TCON |= (1 << 6); // 打开定时器t1的运行控制位,置1}void send_char(char ch)
{SBUF = ch;while((SCON & (1 << 1)) == 0); //TI为1时数据发送完毕,TI为0时数据正在发送SCON &= ~(1 << 1); //手动置0
}int main(void)
{init_uart();while(1){send_char('A');//65delay(0x9FFF); }return 0;
}
发送字符串:(不能使用printf,但是可以使用sprintf)
记得包string.h和strlen.h库
#include<reg52.h>
#include<stdio.h>
#include<string.h>
#include"delay.h"
//1:初始化函数
void init_uart(void)
{//设置串口:unsigned char t;t=SCON;t &=~(3<<6);//先将第SM0和SM1清零t |=(1<<6)|(1<<4);//使用方式一:SM1置1SCON=t;PCON |=(1<<7);//SMOD置1//设置定时器一:t=TMOD;t &= ~(3<<4);t |= (2<<4);t &= ~(3<< 6);TMOD = t;TH1 = 208; //256-2*11.0596*1000000/32/1200/12=208TL1 = 208;TCON |= (1 << 6); // 打开定时器t1的运行控制位,置1}void send_char(char ch)
{SBUF = ch;while((SCON & (1 << 1)) == 0); //TI为1时数据发送完毕,TI为0时数据正在发送SCON &= ~(1 << 1); //手动置0
}
void send_buff(const char *p,int len)
{while(len--){send_char(*p++);}
}
int main(void)
{const char *s="HELLO WORLD!";int n=10,m=20;xdata char buffer[32];init_uart();while(1){// send_char('A');//65sprintf(buffer,"%d+%d=%d我去",m,n,m+n);send_buff(buffer,strlen(buffer));delay(0x9FFF); }return 0;
}
51单片机:大端字节序
接收数据:
IE |= (1<<7)|(1<<4); //打开允许中断寄存器
void uart_handler(void) interrupt 4
{if((SCON & (1<<0)) != 0){P2 = SBUF;SCON &= ~ (1<<0);//手动(软件)置0}
}
#include<reg52.h>
#include<stdio.h>
#include<string.h>
#include"delay.h"
//1:初始化函数
void init_uart(void)
{//设置串口:unsigned char t;t=SCON;t &=~(3<<6);//先将第SM0和SM1清零t |=(1<<6)|(1<<4);//使用方式一:SM1置1SCON=t;PCON |=(1<<7);//SMOD置1IE |= (1<<7)|(1<<4); //打开允许中断寄存器//设置定时器一:t=TMOD;t &= ~(3<<4);t |= (2<<4);t &= ~(3<< 6);TMOD = t;TH1 = 208; //256-2*11.0596*1000000/32/1200/12=208TL1 = 208;TCON |= (1 << 6); // 打开定时器t1的运行控制位,置1}void uart_handler(void) interrupt 4
{if((SCON & (1<<0)) != 0){P2 = SBUF;SCON &= ~ (1<<0);//手动(软件)置0}
}void send_char(char ch)
{SBUF = ch;while((SCON & (1 << 1)) == 0); //TI为1时数据发送完毕,TI为0时数据正在发送SCON &= ~(1 << 1); //手动置0
}
void send_buff(const char *p,int len)
{while(len--){send_char(*p++);}
}
int main(void)
{int a,b,c,d,f,g;const char *s="HELLO WORLD!";int n=10,m=20;xdata char buffer[32];a=sizeof(int); //2b=sizeof(char); //1c=sizeof(short); //2d=sizeof(long); //4
// e=sizeof(longlong);//f=sizeof(float); // 4g=sizeof(double);// 4init_uart();while(1){// send_char('A');//65sprintf(buffer,"size=%d\n",g);send_buff(buffer,strlen(buffer));delay(0x9FFF); }return 0;
}
主从应答:
#include<reg52.h>
#include<stdio.h>
#include<string.h>
#include"delay.h"
//1:初始化函数
void init_uart(void)
{//设置串口:unsigned char t;t=SCON;t &=~(3<<6);//先将第SM0和SM1清零t |=(1<<6)|(1<<4);//使用方式一:SM1置1SCON=t;PCON |=(1<<7);//SMOD置1IE |= (1<<7)|(1<<4); //打开允许中断寄存器//设置定时器一:t=TMOD;t &= ~(3<<4);t |= (2<<4);t &= ~(3<< 6);TMOD = t;TH1 = 208; //256-2*11.0596*1000000/32/1200/12=208TL1 = 208;TCON |= (1 << 6); // 打开定时器t1的运行控制位,置1}//定义片外缓冲区
xdata char rcv_buffer[64];
int pos = 0;//接收字符个数void uart_handler(void) interrupt 4
{if((SCON & (1<<0)) != 0){rcv_buffer[pos++] = SBUF;SCON &= ~ (1<<0);}
}void send_char(char ch)
{SBUF = ch;while((SCON & (1 << 1)) == 0); //TI为1时数据发送完毕,TI为0时数据正在发送SCON &= ~(1 << 1); //手动置0
}
void send_buff(const char *p,int len)
{while(len--){send_char(*p++);}
}
int main(void)
{init_uart();while(1){// send_char('A');//65if(pos !=0){delay(0xFFFF);send_buff(rcv_buffer,pos);}}return 0;
}
规定应答:
xdata char rcv_buffer[64]={0};//初始化
memset(rcv_buffer,0,sizeof(rcv_buffer));//记得清空缓冲区!!!
#include<reg52.h>
#include<stdio.h>
#include<string.h>
#include"delay.h"
//1:初始化函数
void init_uart(void)
{//设置串口:unsigned char t;t=SCON;t &=~(3<<6);//先将第SM0和SM1清零t |=(1<<6)|(1<<4);//使用方式一:SM1置1SCON=t;PCON |=(1<<7);//SMOD置1IE |= (1<<7)|(1<<4); //打开允许中断寄存器//设置定时器一:t=TMOD;t &= ~(3<<4);t |= (2<<4);t &= ~(3<< 6);TMOD = t;TH1 = 208; //256-2*11.0596*1000000/32/1200/12=208TL1 = 208;TCON |= (1 << 6); // 打开定时器t1的运行控制位,置1}//定义片外缓冲区
xdata char rcv_buffer[64]={0};
int pos = 0;//接收字符个数void uart_handler(void) interrupt 4
{if((SCON & (1<<0)) != 0){rcv_buffer[pos++] = SBUF;SCON &= ~ (1<<0);}
}void send_char(char ch)
{SBUF = ch;while((SCON & (1 << 1)) == 0); //TI为1时数据发送完毕,TI为0时数据正在发送SCON &= ~(1 << 1); //手动置0
}
void send_buff(const char *p,int len)
{while(len--){send_char(*p++);}
}
int main(void)
{init_uart();while(1){// send_char('A');//65if(pos !=0){delay(0xFFFF);if(strcmp(rcv_buffer,"china")==0){send_buff("ok",2);}else if(strcmp(rcv_buffer,"hello")==0){send_buff("confirm",7);}pos = 0;memset(rcv_buffer,0,sizeof(rcv_buffer));//记得清空缓冲区!!!}}return 0;
}
传递数据时常用hex:十六进制数传递
modbus协议:
通常上位机一个,下位机若干个