跳至主要內容

RS-485串口通信:简易指南与代码示例

sharebravery大约 4 分钟

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