一、UART的基本概念

1.1 什么是UART?

UART(Unervisal Async Recveiver Transmitter)是一种全双工、异步、串行通信协议。它只需要两根信号线:

  • TXD(Transmit Data):发送数据线

  • RXD(Receive Data):接收数据线

通信双方通过TXD和RXD交叉连接(即一方的TXD接另一方的RXD),实现数据的双向传输。

1.2 通信方式

        单工:通信时,数据接收方和发送方是固定的,通过一根信号线实现数据收发
数据传输的方向是单一的(只能由发送方发给接收方)

        半双工:通信时,数据接收方和发送方不是固定的(双方都可以发送或接收数据)
通过一根信号线实现数据收发,数据传输的方向可以是双向的(同一时刻,数据
传输的方向是单一的)

        例如:I2C:SCL 时钟线 SDA 数据线

全双工:通信时,数据接收方和发送方不是固定的(双方都可以发送或接收数据)
通过两根信号线实现数据收发,数据传输的方向是双向的
          例如:

           UART:串口发送数据时,遵循LSB 低位先行原则,先发送数据低位,再发送高位

           SPI:MOSI(Master output Slaver Input) MISO(Master Input Slaver Output) CS 片选线  SCLK 时钟线(四个共同组成SPI通信)

1.3 与CH340的关系

CH340(电平转换芯片)是一款常用的USB转串口芯片,它可以将电脑的USB信号转换为TTL电平的串口信号,从而实现电脑与单片机之间的串口通信。


二、串行通信 vs 并行通信

特性 串行通信 并行通信
传输方式 一根信号线逐位传输 多根信号线同时传输多位
传输速度 相对较低
成本 低,硬件简单 高,需要多根线
抗干扰 较强(尤其是差分传输) 较差,容易受电磁干扰
传输距离 远(如RS485可达千米) 近(通常不超过30米)

三、同步通信 vs 异步通信

  • 同步通信需要单独的时钟线(如SCLK)来同步数据,收发双方按照时钟节拍传输。优点是传输效率高,缺点是需要额外的时钟线。

  • 异步通信:不需要时钟线,但双方必须约定相同的波特率(即每秒传输的位数),以此实现位同步。UART就是典型的异步通信。


四、串口通信参数:

串口通信需要双方约定好以下参数,通常以类似“9600 8 N 1”的格式表示:

  • 波特率:每秒传输的位数,单位bps。常见值:2400、4800、9600、115200等。

  • 数据位:一帧中实际数据的位数,通常为8位(也可为5/6/7位)。

  • 校验位:用于简单的错误检测,可选:

    • N(None):无校验

    • O(Odd):奇校验,奇校验值为'1',若数据位中'1'的个数加上奇校验中的'1',保持'1'的总个数为奇数个,则代表奇校验通过

    • E(Even):偶校验,偶校验值为'0',若数据位中'1’的个数加上偶校验中的'0',保持'1‘的总个数为偶数个,则代表偶校验通过

  • 停止位:一帧结束的标志,通常为1位(也可为1.5或2位)。

示例9600 8 N 1 表示波特率9600,数据位8位,无校验,1位停止位。

9600波特率一秒传 9600/1+8+1(起始位+数据位+停止位) = 960个字节。


五、常用电平标准

在实际应用中,根据通信距离和环境干扰,需要选择合适的电平标准。

标准 电平定义 特点 应用场景
TTL 高电平:5V(或3.3V)
低电平:0V
直接用于单片机引脚,传输距离短 板级通信,如单片机与传感器
RS232 逻辑1:-3V ~ -15V
逻辑0:+3V ~ +15V
负逻辑,抗干扰能力较强 早期电脑串口,点对点通信
RS485 差分信号:A-B > +200mV 为1
A-B < -200mV 为0
差分传输,抗共模干扰强,支持多机 工业现场,远距离(上千米)

注意单片机通常输出TTL电平,如需与RS232设备通信,需使用MAX232等电平转换芯片;与RS485通信则需使用MAX485等转换芯片


六、数据校验方式

为了保证数据传输的可靠性,除了硬件校验位外,软件层面也常用以下校验方式:

6.1 奇偶校验(Parity Check)

  • 奇校验:保证一帧数据(包括校验位)中1的个数为奇数。若原始数据中1的个数为奇数,则校验位置0;若为偶数,则校验位置1。

  • 偶校验:保证1的个数为偶数。

  • 局限:只能检测奇数个位错误,无法检测偶数个位错误,也无法纠正错误。

6.2 累加和校验(Checksum)

发送方将数据字节累加,取低8位作为校验和附在数据末尾;接收方同样累加,比对校验和。简单但冲突概率较高。

6.3 CRC校验(Cyclic Redundancy Check)

循环冗余校验,通过多项式除法计算校验码,检错能力强,广泛应用于网络和存储。


七、51单片机串口编程基础

7.1 相关寄存器

  • SBUF:串口数据缓冲寄存器,写SBUF即启动发送,读SBUF即获取接收到的数据。

  • SCON:串口控制寄存器,用于设置工作方式、允许接收等。

  • PCON:电源控制寄存器,其SMOD位可控制波特率加倍。

  • TMOD:定时器模式寄存器,用于设置定时器1作为波特率发生器。

  • TH1/TL1:定时器1的初值寄存器,用于产生波特率。

7.2 波特率计算

定时器1作为波特率发生器为例,常用模式2(8位自动重装)。波特率计算公式为:

波特率 = (2^SMOD / 32) × (定时器1溢出率)

其中定时器1溢出率 = 晶振频率 / (12 × (256 - TH1))

假设晶振11.0592MHz,设置SMOD=0(不加倍),波特率=9600,则:

9600 = (1/32) × (11059200 / (12 × (256 - TH1)))
解得:TH1 = 256 - 11059200 / (12 × 32 × 9600) ≈ 256 - 3 = 253 (0xFD)

若需2400bps,则TH1=232(SMOD=1)。

7.3 定时器1初始化代码示例

// 串口初始化
void uart_init(void)
{
	// 1. 配置串口工作模式:SM0 0 SM1 1 8位UART模式
	SCON &= ~(3 << 6);
	SCON |= (1 << 6);    // SM1
	SCON &= ~(1 << 7);   // SM0
	    	
    // 2. 允许串口接收数据
	SCON |= (1 << 4);

	// 3. PCON 波特率加倍
	PCON &= ~(3 << 6);
	PCON &= ~(1 << 6);
	PCON |= (1 << 7);

	// 4. 配置定时器1工作模式:8位自动重装载
	TMOD &= ~(0x0F << 4);   
	TMOD |= (1 << 5);

	// 5. 设置定时器1初值:2400bps
	TL1 = 232;
	TH1 = 232;

	// 6. 允许定时器1开始计数
	TCON |= (1 << 6);

	// 7. 允许CPU响应所有中断
	IE |= (1 << 7);

	// 8. 允许串口产生中断
	IE |= (1 << 4);
}

7.4 发送(轮询)与接收(zhon)

  • 发送(轮询):将数据写入SBUF,等待发送完成标志TI置1。

// 发送单个字符
void uart_sendchar(char ch)
{
	SBUF = ch;

	// 判断是否发送完8位数据
	while ((SCON & (1 << 1)) == 0);  //当T1为0是还在发送,为1是跳出循环即发送完成

	SCON &= ~(1 << 1);//手动把T1置为0
}

// 发送字符串
void uart_sendstr(const char *p)  	
{
   	while (*p)
	{
		uart_sendchar(*p++);
	}
}

// 发送指定长度数据
void uart_sendbuff(const char *p, int len)
{
	while (len--)
	{
		uart_sendchar(*p++);	
	}
	
}
  • 接收(中断):当RI置1时,从SBUF读取数据。

// 串口接收中断服务函数
void uart_handler(void)		interrupt 4
{
	if ((SCON & (1 << 0)) == 1)//接受完成后R1会置为1
	{
		if (pos < 32)
		{
			recv_buffer[pos++] = SBUF;
			recv_buffer[pos] = 0;
		}
	}
		
	SCON &= ~(1 << 0);//手动把R1置为0
}

注意:发送或者接收完成之后都要手动把T1或者R1置为0


八、Modbus协议简介

Modbus是一种广泛应用于工业控制的应用层协议,基于主从架构,通常运行在串口上。

示例:

示例中校验码=前面字节的总和

我们假设功能码为:

01:控制led

02:控制数码管

03:控制蜂鸣器

04:温度采集

数据位用来调整该功能的各种设置

解码Modbus协议:

#define DEV_ADDRESS	0x01
// 解析获得功能码
int parse(void)
{
	int ret = 0;
	int i = 0;
	int sum = 0;
	
	if ((unsigned char)recv_buffer[0] == 0xAA && (unsigned char)recv_buffer[6] == 0xBB)
	{
		if ((unsigned char)recv_buffer[1] == DEV_ADDRESS)
		{
			 for (i = 0; i < 5; i++)
			 {
			 	sum += recv_buffer[i];
			 }
			 if (sum == recv_buffer[5])
			 {
			 	ret =  recv_buffer[2];
			 }
		}
	}	

	return ret;
}

注意:if(sum == recv_buffer[i])这个条件不能使用unsigned char,因为如果相加的数大于两个字节,校验码只有两位如果是无符号会补0就和sum不一样大了

从机应答处理:

#define DEV_ADDRESS	0x01
void callback(void)//从机给主机发信息
{ 
	int i = 0;
	int sum = 0;
    xdata char send_buffer[10];
    
    memcpy(send_buffer,recv_buffer,7);//AA 01 01 42 00 EE BB
    send_buffer[2] |= (1<<7);    //0000 0001 |1000 0000  = 0x81
   
	
	if ((unsigned char)send_buffer[0] == 0xAA && (unsigned char)send_buffer[6] == 0xBB)
	{
		if ((unsigned char)send_buffer[1] == DEV_ADDRESS)
		{
			 for (i = 0; i < 5; i++)
			 {
			 	sum += send_buffer[i];
			 }
			
			send_buffer[5] = sum;
			
		}
	}	

    uart_sendbuff(send_buffer,7);
}

功能码的最高位是数据流向位功能码的最高位是数据流向位,所以应答把功能码最高位,置为1。

解码后通过switch(功能码recv_buffer[2])来实现不同的功能,通过数据位1的值(recv_buffer[3])来设置功能


九、总结

        本文从UART的基础概念出发,详细介绍了串行通信的对比、通信参数、电平标准、校验方式,并结合51单片机给出了串口初始化和收发代码,最后简单介绍了Modbus协议。

Logo

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

更多推荐