RS-485串口通信:简易指南与代码示例
RS-485串口通信:简易指南与代码示例
1. RS-485介绍
RS-485是一种物理层通信标准,定义了电气特性、传输速率、线路拓扑等细节
RS485是一种广泛应用于工业领域的半双工串行通信协议。RS485可以支持多达32个设备在同一总线上通信。RS485的最大传输速率可以达到10Mbps,但实际应用中,速率和传输距离成反比关系。
RS485使用差分信号传输,这意味着它通过一对互补信号线(通常标记为A和B)传输数据。
在RS485总线的两端需要连接终端电阻(通常为120欧姆),以匹配线路阻抗,防止信号反射。
RS-485与RS-232的差异只体现在物理层上,它们的协议层是相同的,也是使用串口数据包的形式传输数据。
2. RS485为何更抗干扰
这里说一下为什么RS485会更为抗干扰:
我们知道普通的 TTL 串口只适合短距离传输。TTL 通信是通过电压的高低变化来传输数据的,由于其单端传输方式,对静电和电磁干扰非常敏感,容易受到干扰而导致信号周期混乱,从而造成数据错误。因此,TTL 适合的传输距离通常较短。
而RS485使用两根互相扭绞的线*(A 和 B 线)*来传输数据,当一根线上的电压升高时另一根线上的电压会相应降低,这就是前面所说的差分信号传输,接收端可以通过对比两根线的电压差来过滤干扰信号。这样就可以有效抵消外部电磁干扰。
总线的两端加上的终端电阻也可以减少信号反射和误码率。
3. 简单的通信测试
接线时记住A对A,B对B
查看波特率
stty -F /dev/ttyACM0 # ttyACM0 替换为自己的串口
设置波特率
stty -F /dev/ttyACM0 115200
接收端
cat /dev/ttyACM0
发送端
echo "Hello, RS485" > /dev/ttyACM0
4. 回环测试
在硬件上,普通的TTL串口信号通过一个转换芯片(这种芯片可以将TTL电平的单端信号转换为RS-485电平的差分信号)处理后可以转换为RS-485信号。
我进行了单机测试,将A和B端连接在一起进行回环测试,结果证实RS-485确实不能进行回环通信。
5. 使用C程序测试RS-485
5.1 发送端 sender.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <string.h>
#define SERIAL_PORT "/dev/ttyACM0"
#define BAUDRATE B115200 // 波特率,与串口模块配置一致
#define DELAY_US 1000000 // 发送间隔,单位微秒
int main()
{
int fd;
struct termios options;
// 打开串口
fd = open(SERIAL_PORT, O_RDWR | O_NOCTTY | O_NDELAY);
if (fd == -1)
{
perror("open_port: Unable to open port");
return 1;
}
// 获取当前串口配置
if (tcgetattr(fd, &options) != 0)
{
perror("tcgetattr failed");
close(fd);
return 1;
}
// 设置波特率
cfsetispeed(&options, BAUDRATE);
cfsetospeed(&options, BAUDRATE);
// 无校验,8位数据位,1位停止位
options.c_cflag &= ~PARENB;
options.c_cflag &= ~CSTOPB;
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS8;
// 设置为本地连接,使能接收
options.c_cflag |= (CLOCAL | CREAD);
// 设置为原始模式
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
// 禁用软件流控制
options.c_iflag &= ~(IXON | IXOFF | IXANY);
// 禁用硬件流控制
options.c_cflag &= ~CRTSCTS;
// 设置新的串口设置
if (tcsetattr(fd, TCSANOW, &options) != 0)
{
perror("tcsetattr failed");
close(fd);
return 1;
}
// 循环发送数据
char write_buffer[256];
while (1)
{
// 获取用户输入
printf("Enter message to send (max 255 characters): ");
fgets(write_buffer, sizeof(write_buffer), stdin);
// 移除换行符
write_buffer[strcspn(write_buffer, "\n")] = 0;
// 发送数据
int bytes_written = write(fd, write_buffer, strlen(write_buffer));
if (bytes_written < 0)
{
perror("write failed");
close(fd);
return 1;
}
printf("Sent: %s\n", write_buffer);
usleep(DELAY_US); // 发送间隔
}
// 关闭串口
close(fd);
return 0;
}
5.2 接收端 recriver.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <string.h>
#define SERIAL_PORT "/dev/ttyACM0"
#define BAUDRATE B115200 // 波特率,与串口模块配置一致
int main()
{
int fd;
struct termios options;
// 打开串口
fd = open(SERIAL_PORT, O_RDWR);
if (fd == -1)
{
perror("open_port: Unable to open port");
return 1;
}
// 获取当前串口配置
if (tcgetattr(fd, &options) != 0)
{
perror("tcgetattr failed");
close(fd);
return 1;
}
// 设置波特率
cfsetispeed(&options, BAUDRATE);
cfsetospeed(&options, BAUDRATE);
// 无校验,8位数据位,1位停止位
options.c_cflag &= ~PARENB;
options.c_cflag &= ~CSTOPB;
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS8;
// 设置为本地连接,使能接收
options.c_cflag |= (CLOCAL | CREAD);
// 设置为原始模式
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
// 禁用软件流控制
options.c_iflag &= ~(IXON | IXOFF | IXANY);
// 禁用硬件流控制
options.c_cflag &= ~CRTSCTS;
// 设置新的串口设置
if (tcsetattr(fd, TCSANOW, &options) != 0)
{
perror("tcsetattr failed");
close(fd);
return 1;
}
// 循环接收数据
while (1)
{
char read_buffer[256];
int bytes_read = read(fd, read_buffer, sizeof(read_buffer) - 1);
if (bytes_read < 0)
{
perror("read failed");
close(fd);
return 1;
}
read_buffer[bytes_read] = '\0';
printf("Received: %s\n", read_buffer);
}
// 关闭串口
close(fd);
return 0;
}
5.3 Makefile
为了方便编译,这里附上一个简单的Makefile
CC = arm-linux-gnueabihf-gcc
CFLAGS = -Wall
all: sender receiver
sender: sender.c
$(CC) $(CFLAGS) -o sender sender.c
receiver: receiver.c
$(CC) $(CFLAGS) -o receiver receiver.c
clean:
rm -f sender receiver
先运行接收端再运行发送端
send:
receive:
可以进行进一步的优化,将SERIAL_PORT
等由外部传参而入,这样方便对多个串口进行测试。
参考资料:
https://doc.embedfire.com/mcu/stm32/f407batianhu/std/zh/latest/book/RS485.html
END