MicroPython RS485 实战:驱动 MAX13487 芯片实现主从通信

1.RS485 通信基础知识

RS-232 通信协议具有如下缺点,导致难以在远距离和工业场景中使用:

  • 信号电平值较高​:RS-232 的信号电平通常在 ±12V 左右,甚至可能达到 ±15V;由于电平较高,容易损坏接口电路中的芯片,如果需要与低电压的 TTL 电路(如 5V 或 3.3V)连接,必须通过电平转换电路来保护设备并实现信号匹配
  • 传输速率较低​:RS-232 的波特率相对较低,特别是在异步传输情况下,最高速率只有 20Kbps,这种速率对于现代高速通信的需求来说是远远不够的,尤其是在需要快速数据交换的应用场合
  • 抗干扰性差​:RS-232 采用的是单端信号传输方式,即一根信号线和一根共地线构成信号回路,这种结构容易受到共模干扰的影响,使信号质量变差,抗噪声性能较弱,特别是在长距离传输时更为明显
  • 传输距离有限​:标准规定 RS-232 的最大传输距离为 50 英尺(约 15 米),在实践中传输距离一般不会超过 50 米,超过此距离后,信号会大幅衰减,无法保证通信的可靠性

因此,后续的工程师制定了 RS-485 通信协议,与 RS-232-C 接口相比,RS-485 在传输距离、抗干扰能力、多设备通信等方面有显著优势:

  • 差分信号传输​:RS-485 使用两根信号线(A 和 B),通过电压差表示“0”和“1”;这种差分传输方式可以有效抵抗电磁干扰,确保在噪声环境下的稳定通信
  • 多点通信​:RS-485 允许多个设备连接到同一总线上,典型情况下支持 32 个发送器和 32 个接收器,甚至通过中继器可以连接更多设备,这使得 RS-485 非常适合用于需要多设备互联的场景
  • 长距离传输​:RS-485 的最大传输距离可达 1200 米(大约 4000 英尺),并且传输速度与距离成反比;随着传输距离的缩短,数据传输速率可提高,最高可达到 10 Mbps

不同于 RS-232 或 TTL,RS-485 大部分应用以半双工方式运行,即同一时间内只能有一个设备发送数据,其他设备处于接收状态,因此,需要额外的协议来协调设备间的通信。

典型的 RS485 总线拓扑结构为一条主干线上串联多个设备,每个设备有独立的发送器和接收器,并共享一对通信线(A、B),我们往往使用双绞线来减少电磁干扰和噪声,同时,在长距离传输或高速传输时,在总线的两端通常会加上终端电阻(通常为 120 欧姆),以避免信号反射和干扰。

在 RS-485 通信的主从结构中,可以将不同设备分成两个角色:

  • 主设备Master) :负责控制整个通信过程,发起数据请求,向从设备发送命令和数据
  • 从设备Slaves) :每个从设备都有唯一的设备 ID(地址),在通信时被动响应主设备的请求,当主设备向其发送匹配的地址时,该从设备才会处理数据,并根据需要发送响应

其通信流程可以概括如下:

  • 主设备发送数据​:主设备通过 RS-485 总线发送数据,包括目标从设备的地址(从设备 ID)和具体的数据,A(+ 信号)和 B(-信号)两条线同时连接到所有从设备
  • 从设备监听​:所有从设备都在监听 A 和 B 线上的数据流,检查主设备发送的地址信息
  • 从设备选择​:当某个从设备检测到发送的数据包中的地址与自己的从设备 ID 匹配时,它会接收该数据包并进行相应处理
  • 从设备响应​:如果通信协议要求从设备返回数据(如状态信息、传感器数据等),该从设备会向主设备发送响应数据;RS-485 一般是半双工的,即同一时间内只能有一个设备发送数据,因此响应数据的发送需要按预定的时序进行
    image.png

除菊花链总线(将设备一个接一个串联起来,形成一个单一路径的通信链路)外,其他拓扑结构不推荐在 RS485 通信中使用,会导致严重的信号反射和干扰问题。若是必须使用星型等拓扑结构,则务必使用 RS-485 集线器或中继器来避免信号反射和干扰:

image.png

在使用 RS485 通信时,务必注意 RS-485 信号线与电源线不能绑在一起走线,这是因为电源线会产生较强的电磁干扰,对传输数据的信号线造成干扰;同时,RS-485 总线要求单点接地,即总线上只能有一个接地点,如果出现多点接地,将会导致地线之间的电位差异,进而引发共模干扰,影响通信的稳定性。

RS485 通信也可以进行全双工的数据传输,TX 和 RX 各自有独立的线路进行通信,但此时从设备之间无法直接通信,它们必须通过主设备进行数据传输。

2.MAX13487 芯片介绍

MAX13487E 是一款高性能、半双工的 RS-485/RS-422 兼容型收发器,具有 ±15kV 静电放电(ESD)保护,同时支持自动收发控制功能和电源关断模式,最大支持 500kbps 的通信速率,也支持在通电状态下安全插拔,避免数据错误,允许在同一总线上接入多达 128 个收发器。

其引脚功能如下所示:

在以下表格中,我们进行详细讲解:

引脚名称 对应连接引脚 功能介绍
RO MCU 串口输入引脚 (RX) 接收器输出引脚,根据 A 和 B 之间的差分电压决定输出数据是 0 还是 1。
RE MCU 控制引脚 (GPIO) 接收器输出使能引脚,将 RE 驱动为低电平可以启用 RO 输出;将 RE 驱动为高电平则交由 AutoDirection 电路控制接收器的启用与禁用。
SHDN MCU 控制引脚 (GPIO) 将 SHDN 驱动为高电平,设备正常工作;驱动为低电平,设备进入电源关断模式,降低功耗。
DI MCU 串口输出引脚 (TX) 驱动器输入引脚,根据 DI 的高低,决定 A 和 B 之间的差分电压,从而实现数据传输。
GND 连接到 MCU 地 地引脚,该引脚连接到电路的地,提供稳定的参考电压。
A 连接到总线 A 线 非反相信号输入/输出引脚
B 连接到总线 B 线 反相信号输入/输出引脚
VCC 连接到 +5V 电源 正电源引脚,提供 +5V 电源供电,VCC 需要通过一个 0.1µF 电容旁路到地,以滤除电源噪声。

MAX13487E 芯片内部结构如下所示:

主要工作单元为内部状态机、发送器、差分比较器和接收器,配合 A 相和 B 相信号外部的上拉电阻和下拉电阻,MAX13487 芯片通过监测数据输入(DI 引脚)和差分信号线(A、B 引脚)上的电压差可以轻松实现自动收发功能,而无需用户再使用额外的 GPIO 引脚控制收发。

MAX13487E 芯片典型应用电路如下图所示:

其中,MCU 对应串口的 TXD 引脚和 RXD 引脚通过光耦隔离单元连接到 MAX13487 芯片的 RO 和 DI 引脚上,MAX13487 芯片的 RE 和 SHDN 引脚都连接到高电平上,表示关断掉电模式和启动自动收发功能,差分信号输出/输入引脚中连接了一个终端电阻用于匹配通信总线的阻抗,同时 A 引脚通过一个上拉电阻连接到电源,B 引脚通过一个下拉电阻连接到地,从而产生一个小的差分电压,确保 A 引脚的电压高于 B 引脚,这样总线在空闲时可以保持逻辑“1”(即 A > B)。

当有多个设备使用 MAX13487 芯片进行 RS485 通信时,其连接拓扑图如下所示:

所有设备通过一对差分信号线(A、B)连接在一起,终端电阻通常安装在总线的两端。

这里,我们使用 USB 转 485 适配器来实现树莓派 Pico 外接 MAX13487 芯片与电脑主机的通信,这里 USB 转 485 适配器使用的是 FT232 芯片,我们需要安装对应驱动程序,对应驱动程序除了可以从 FTDI 的官方网站下载外:

https://ftdichip.com/drivers/vcp-drivers/

也可以在我们提供的资料包中找到:

左键双击,按照如下步骤进行安装:

3.应用实验

这里,我们在以下代码中,实现树莓派 Pico 外接 MAX13487 芯片与电脑主机的通信。

在下面实验中,我们需要将 风雅一号板-Grove接口 扩展板插入到 风雅一号板-串口电平转换板 上,同时打开 RS485-SW 拨码开关上 RS485-TXDRS485-RXDRS485-EN 选项,我们使用串口外设 0 与 风雅一号板-串口电平转换板上 MAX13487 芯片连接,设置树莓派 Pico 接收引脚为 GPIO17,发送引脚为 GPIO16,:

接着,我们将 风雅一号板-串口电平转换板RS485 Interface 接口上 RS485-ARS485-B 引脚连接到适配器的 T/R+T/R- 引脚上,同时将二者的 GND 连接,并将适配器通过 USB 接口连接到电脑,实物连接图如下所示:

在以下例程中,我们通过串口控制 RS485 模块发送和接收数据,并通过收发控制引脚切换工作模式以确保数据流的正常传输。

示例代码如下,在配套资料的文件夹:elegance-devkit v1\Demo\12 UART_RS485 中:

# Python env   : MicroPython v1.23.0
# -*- coding: utf-8 -*-        
# @Time    : 2024/9/23 下午3:53   
# @Author  : 李清水            
# @File    : main.py       
# @Description : UART类实验,RS485串口通信

# ======================================== 导入相关模块 =========================================

# 硬件相关的模块
from machine import UART, Pin
# 时间相关的模块
import time

# ======================================== 全局变量 ============================================

# UART配置参数
UART_TX_PIN : int = 16       # 发送引脚 (GPIO16)
UART_RX_PIN : int = 17       # 接收引脚 (GPIO17)
CONTROL_PIN : int = 18       # 收发控制引脚 (GPIO18)
BAUD_RATE   : int = 115200   # 波特率

# 设备ID
DEVICE_ID   : int = 1

# ======================================== 功能函数 ============================================

# ======================================== 自定义类 ============================================

# 自定义RS485通信类
class RS485Communication:
    """
​    RS485通信类,用于通过RS485协议进行数据的发送和接收。

​    该类实现了通过RS485协议的串口通信,包含了数据的发送、接收以及接收数据的处理。通过控制收发模式的引脚切换,
​    可以在发送数据和接收数据之间切换。该类用于设备间的点对点通信或多点通信,确保数据流的正常传输和设备间的正确通信。

​    Attributes:
​        uart (UART): 串口对象,表示通过串口进行数据的发送和接收。
​        device_id (int): 设备的唯一标识ID,用于区分不同设备之间的通信数据。
​        control_pin (Pin): 控制引脚,用于切换收发模式。当发送数据时,将控制引脚设置为发送模式;接收数据时,设置为接收模式。

​    Methods:
​        send_data(data: str) -> None:
​            发送数据,将设备ID与待发送数据一起发送,并切换到发送模式。

​        receive_data() -> Union[bytes, None]:
​            接收数据,检查是否有待接收的数据,并返回接收到的数据。如果没有数据,则返回None。

​        process_received_data(received: bytes) -> None:
​            处理接收到的数据。将数据解码并与设备ID进行比较,如果数据属于当前设备,则进行处理;否则忽略。
​    """

​    ​def __init__(self, uart: UART, device_id: int, control_pin: int):
        """
​        初始化RS485通信对象。

​        通过指定的串口对象、设备ID和控制引脚初始化RS485通信对象。

​        Args:
​            uart (UART): 串口对象,使用指定的UART端口进行数据传输。
​            device_id (int): 设备的ID,用于标识该设备。
​            control_pin (int): 收发控制引脚,用于切换串口收发模式。
​        """
​        ​# 设置串口对象
        self.uart = uart
        # 设置设备ID
        self.device_id = device_id
        # 设置收发控制引脚
        self.control_pin = Pin(control_pin, Pin.OUT)
        # 初始为接收模式
        self.control_pin.value(0)

    def send_data(self, data: str) -> None:
        """
​        发送数据。

​        通过串口发送带设备ID的数据,并切换收发控制引脚为发送模式。

​        Args:
​            data (str): 待发送的数据。

​        Returns:
​            None
​        """

​        ​# 设置为发送模式
        self.control_pin.value(1)
        # 添加设备ID和换行符
        message = f"{self.device_id}:{data}\r\n"
        # 发送数据
        self.uart.write(message)
        # 等待数据发送完成
        time.sleep(0.1)
        # 切换回接收模式
        self.control_pin.value(0)

    def receive_data(self) -> bytes:
        """
​        接收数据。

​        检查是否有可接收的数据,若有则读取数据。

​        Returns:
​            bytes: 接收到的数据,如果没有数据则返回 None。
​        """

​        ​# 检查是否有可接收的数据
        if self.uart.any():
            # 读取接收到的数据
            return self.uart.read()
        # 如果没有数据,返回None
        return None

    def process_received_data(self, received: bytes) -> None:
        """
​        处理接收到的数据。

​        将接收到的字节数据解码并根据设备ID进行处理。如果接收到的数据不是自己的ID,
​        将打印该数据。若接收的数据格式不正确,则忽略。

​        Args:
​            received (bytes): 接收到的字节数据。

​        Returns:
​            None

​        Raises:
​            ValueError: 如果数据格式不正确,抛出ValueError异常。
​        """

​        ​# 解码数据并去除空格
        decoded_data = received.decode('utf-8').strip()
        # 按行分割数据,处理每一条消息
        messages = decoded_data.split('\r\n')

        # 遍历消息
        for msg in messages:
            # 如果消息不为空,进行处理
            if msg:
                # 分割设备ID和消息
                try:
                    sender_id, message = msg.split(':', 1)
                    # 如果发送者ID不是自己的ID
                    if sender_id != str(self.device_id):
                        # 打印接收到的消息
                        print("Received:", message)
                except ValueError:
                    # 如果数据格式不正确,忽略该消息
                    print("Received invalid message format:", msg)

# ======================================== 初始化配置 ==========================================

# 上电延时3s
time.sleep(3)
# 打印调试消息
print("FreakStudio: RS485 Communication with PC Demo")

# 创建串口对象,设置波特率为115200
uart: UART = UART(0, BAUD_RATE)
# 初始化uart对象,波特率为115200,数据位为8,无校验位,停止位为1
# 设置接收引脚为GPIO9,发送引脚为GPIO8
# 设置串口超时时间为100ms
uart.init(baudrate  = BAUD_RATE,
          bits      = 8,
          parity    = None,
          stop      = 1,
          tx        = UART_TX_PIN,
          rx        = UART_RX_PIN,
          timeout   = 100)

# 创建RS485通信对象
rs485: RS485Communication = RS485Communication(uart, DEVICE_ID, CONTROL_PIN)

# ========================================  主程序  ===========================================

# 循环执行
while True:
    # 发送测试数据
    rs485.send_data("Pico RS485 Test")

    # 接收数据
    received = rs485.receive_data()
    # 如果接收到数据
    if received:
        # 处理接收到的数据
        rs485.process_received_data(received)

    # 延时1秒
    time.sleep(1)

在以上代码中,我们自定义了一个 RS485Communication 类完成 RS485 通信,它使用设备 ID 来检查接收到的数据是否为自己发送的内容,核心方法工作原理如下:

  • 在发送数据时,将控制引脚设置为高电平,切换 MAX13487 芯片为发送模式;然后将设备 ID 和数据组合成消息,并发送,发送完成后,等待 100 毫秒以确保数据被发送,随后切换回接收模式
  • 在接收数据时,解码接收到的数据,并按行分割,遍历每条消息,分割设备 ID 和消息内容;若是发送者 ID 与本设备 ID 不匹配,则打印接收到的消息

这里,使用设备 ID 来辨识消息来源并对接收数据进行解码的原因除了 RS485 通信通常应用于一主多从的通信场景外,同时也是避免 RS-485 通信中的回声效应(Echo),即信号传输过程中的反射导致的一种干扰,这可能导致我们在一个设备进行 RS485 数据发送后,在接收数据时接收到之前发送过的数据。

烧录代码,打开终端,打开串口助手,设置其为字符显示,并且在发送数据时选择发送新行:

可以看到,树莓派 Pico 定时向电脑端发送数据,当我们使用电脑发送数据给 Pico 时,要注意格式如下:

<设备ID>:<数据内容>\r\n

这里,我们发送的数据为:2:Hello Raspberry Pi Pico

可以看到,树莓派 Pico 成功接收,若是我们发送的数据格式不对,那么树莓派 Pico 会抛出异常,在终端打印对应数据:


posted @ 2026-08-31 14:57  FreakStudio  阅读(7)  评论(0)    收藏  举报