RS485、MODBUS

串口、UART、TTL、RS232、RS422、RS485之间的联系

串口指通过串行通信方式传输数据的接口,数据按位(bit)顺序传输。是一个物理或逻辑接口的概念。UART是串口通信的协议,RS232、RS422、RS485是定义物理层电气特性和接口的标准。

UART(通用异步收发器):一种串行通信协议,负责将并行数据转换为串行数据(或反向转换),定义数据帧格式(起始位、数据位、停止位等),不涉及电气特性。

  • image-20250809113951043

TTL是一种逻辑电平标准,通常用于数字电路。‘1’ 2.4v~3.3v ‘0’ 0~0.4v。适合短距离通信(通常几厘米到几米),不适合长距离传输。应用:微控制器、传感器等短距离 UART 通信(TTL 串口)。

CMOS是一种基于场效应晶体管(MOSFET)的**逻辑电平标准,广泛用于现代集成电路。‘1’ 0.7VDD ~ VDD ‘0’ 0V ~ 0.3VDD**,VDD取3.3V、5V等。功耗极低,抗干扰能力较强,输入阻抗高,输出驱动能力稍弱。应用:现代低功耗设备,如 3.3V 微控制器(CMOS 串口)。

RS232是一种串行通信物理层标准,定义了物理层接口和电平标准。“1” -3V ~ -15V,“0” +3V ~ +15V。单端点对点全双工通信,传输距离通常不超过15米。

RS422是一种差分信号(两条线表示一个信号,电压差判定逻辑)的串行通信物理层标准,抗干扰强,支持一对多点全双工通信(一个发送端连多个接收端,最多10个)。支持更远距离和更高速度的通信。传输距离可达1200米(速率降低时)。

**RS485是一种差分信号的串行通信物理层标准。**差分信号抗干扰,多点半双工通信,可组网(标准支持32个单元负载,部分设备支持128+个)。距离和速率与RS422类似。“1”:两线电压差 +(2~6)V “0”:两线电压差 -(2~6)V

TTL、CMOS 与 RS232、RS422、RS485 的区别

image-20250809114051179

TTL和CMOS是数字逻辑电路技术,可以说是逻辑电平标准,决定UART信号的电压范围,适合短距离通信。如芯片间或板级通信;

RS232、RS422、RS485是物理层标准,解决UART的长距离传输问题,不仅包括电平标准,还定义了信号传输方式(单端或差分)、连接方式(点对点或多点)等,适合不同场景。如设备间长距离数据传输。

TTL/CMOS电平的UART信号可通过转换芯片(如 MAX232、MAX490、MAX485)适配 RS232、RS422或 RS485标准

4.1 关于S485

RS485是隶属于OSI模型**物理层的电气特性标准,采用两线差分信号,支持半双工、多点通信。**

  • 电平标准:逻辑“1”:两线间电压差 +(2~6)V。 逻辑“0”:两线间电压差 -(2~6)V。接口电平低,不易损坏芯片。

  • 传输速率高、距离远:10米最高35Mbps,1200米可达100kbps;最长1200米;

  • 抗干扰强:差分传输+平衡驱动器,抗共模干扰,适合工业环境。

  • 支持节点多:标准支持32个,特制芯片可达128~400 个。

  • 连接方式:推荐线型或总线型结构,避免星型或环型;

    image-20250807010847005
    • 理想情况下,需在总线起止端(即主机和设备 4)各加入一个 120Ω 匹配电阻。
  • 示例:MAX3485—>一款RS-485/RS-422 收发器芯片,专为高速、抗干扰、多点通信设计

    • image-20250807012805147
    • 硬件连接
      • image-20241128060027449
      • image-20250807014250415 - image-20250807152702070
  • 组网功能强,支持多设备联网,可在串口协议基础上使用 MODBUS 协议。

  • ==增强RS485数据传输健壮性的方式==

    1、带屏蔽层的双绞线(抗干扰)

    2、总线连接–线型/总线型(所有设备通过一条双绞线依次串接(A/B 线))。 差分总线协议,星形/环形拓扑会导致阻抗不匹配,引发信号反射

    3、加终端电阻(抑制信号反射),标准值 120Ω。通信速率 > 500kbps 或 传输距离 > 50米 时必须添加。

    4、光耦隔离器,解决工业设备间的地电位差问题,防止通信误码和芯片损坏。

    当然可以,以下是对你原文的简化版总结,保留核心信息,便于快速理解:

4.2 MODBUS协议

RS-485、串口基础协议(UART)、MODBUS RTU三者的联系 MODBUS参考文章

RS-485(物理层)是一种串行通信标准,支持多点(32+)、长距离通信(<1200米),抗干扰能力强。

  • 例:差分信号(A/B线)抗共模干扰、多设备总线(可挂接多个从机)、收发切换(半双工通信,需控制方向)

串口基础协议(如UART,物理层、数据链路层):定义了串行通信的基本数据格式(波特率、数据位、停止位、校验位等),提供点对点短距(如RS-232)或多点长距(如RS-485)**的异步串行通信。**是一种协议。

**MODBUS协议(应用层、数据链路层):基于串口基础协议构建数据帧格式和主从通信逻辑,用于设备间的数据交互。**是一种应用层协议,运行在串口标准(如RS-232/485)、TCP/IP等物理层之上。

  • 数据帧格式:从机地址(1字节)、功能码(1字节)、数据(可变长度)、CRC16校验(2字节)

  • 主从通信:主站发送请求帧,从站响应,主站解析响应帧。

  • #1.  Modbus RTU:串口通信,二进制格式,效率高。
    #2.  Modbus ASCII:串口通信,ASCII码格式,效率低,适用于低通信量场景。
    #3.  Modbus TCP:以太网通信,基于 TCP/IP 协议
    

    OSI模型将网络通信划分为七层,每层负责不同的功能。从底层到高层依次为:物理层、数据链路层、网络层、传输层、会话层、表示层、应用层。

    OSI层级UARTRS-485Modbus RTU
    物理层TTL/CMOS电平;如需RS-232/RS-485需外接电平转换芯片差分双线电气标准依赖RS-485/RS-232等物理介质
    数据链路层UART帧格式(起始位/停止位/校验等)无定义(需使用UART等帧协议)串行帧格式(地址、功能码、数据、CRC)
    **网络层 **不适用不适用(多点地址由上层协议定义)不适用(点对点,无路由)
    **传输层 **不适用不适用无独立实现(字节序等编码规则属于应用层的一部分)
    **会话层 **不适用不适用无独立实现(请求-响应逻辑由应用层处理)
    **表示层 **不适用不适用不适用
    **应用层 **不适用不适用(由上层协议实现)Modbus协议通信逻辑(读写寄存器/线圈等)

ModBus是一种应用层主从式协议,通常采用串口通信(如RS-232C/RS-485),并使用主从结构:一个主站发起数据访问,多个从站响应。每个从站有唯一地址(1~247),其中地址0为广播地址(用于广播模式,无需从站响应)

  • 异步通讯、字节为单位,11位串口数据帧格式(如uart,含校验码)、n字节ModBus数据帧结构

  • 数据帧格式:从机地址(1字节)、功能码(1字节)、数据(可变长度)、CRC16校验(2字节)

  • 通信过程:主站发送请求帧,符合地址的从站接收命令,验证CRC。验证正确,返回响应帧(正常/异常)由主站接收解析处理;CRC错误,不返回任何数据

    • 异常响应帧仅在 CRC 正确、但请求内容无效(如非法功能码、地址)时返回。
  • 数据区:存放需要读取/写入的寄存器地址、数量、以及数据

MODBUS RTU数据帧格式:地址码、功能码、数据区和CRC校验。 Modbus协议规定为 大端序

  • 地址码1 字节指定目标从站的地址(1-247),0 为广播地址。
    功能码1 字节定义主机请求/从机响应的操作(读/写等)
    数据区N 字节数据内容或参数(高字节在前)
    CRC校验2 字节错误检测,16位冗余循环码,低字节在前
    • 帧间隔:帧之间有至少3.5 个字符时间的静默间隔(由波特率决定),用于帧同步

      • 1字符 = 11位(1起始位 + 8数据位 + 1停止位 + 1校验位)
        9600波特率下,字符时间(秒) = 11/波特率 ≈ 1ms,则帧间隔=3.5*1≈3.5ms
        

常用功能码

功能码功能功能描述请求帧正常响应帧异常响应帧
0x01读线圈状态读位(读Nbit),读取一组线圈的当前状态[Addr][01][起始bit位/线圈地址][线圈数量][CRC][Addr][01][字节数][线圈状态字节流][CRC][从机地址Addr][功能码81][异常码Ex][CRC]
0x02读离散输入读位(读Nbit)读取一组离散输入的当前状态同0x01同0x01[Addr][82][Ex][CRC]
0x03读保持寄存器读取一个或多个保持寄存器的值[Addr][03][起始寄存器地址][寄存器数量][CRC][Addr][03][字节数][寄存器数据流][CRC][Addr][83][Ex][CRC]
0x04读输入寄存器读取一个或多个只读输入寄存器的值同0x03同0x03[Addr][84][Ex][CRC]
0x05写单线圈写位(写1bit),写单个线圈状态(开或关)[Addr][05][线圈地址][写入值][CRC]
(FF00=ON, 0000=OFF)
同请求帧[Addr][85][Ex][CRC]
0x06写单寄存器(写半字),写入一个保持寄存器的值[Addr][06][寄存器地址][值][CRC]同请求帧[Addr][86][Ex][CRC]
0x0F写多线圈写入多个线圈状态[Addr][0F][起始线圈地址][数量][字节数][数据][CRC][Addr][0F][起始线圈地址][线圈数量][CRC][Addr][8F][Ex][CRC]
0x10写多寄存器多个保持寄存器值[Addr][10][起始寄存器地址][数量][字节数][数据][CRC][Addr][10][起始寄存器地址][寄存器数量][CRC][Addr][90][Ex][CRC]
  • 常见异常码:01=非法功能,02=非法地址,03=非法值,04=设备故障。
  • 线圈:1位布尔值0/1,表示开关状态(如开/关)
  • 输入保持寄存器:16位无符号整数,存储设备参数或数据。
  • 输入寄存器:16位无符号整数,只读,存储实时采集的数据(如温度、电压等实时数据)

5.示例

5.1 modbus主机

实现一个Modbus RTU主站,能够周期性发送读寄存器请求(功能码 03),接收并解析从机响应,存储寄存器数据

硬件初始化:初始化UART4(PC10-TX, PC11-RX,PC8作为RS485方向控制引脚–0=接收,1=发送)用于RS485通信

  • 初始化 STM32F10x 系统时钟、LED、UART(其他串口)、定时器(TIM2,周期 1ms)以及 UART4(波特率 9600,8 位数据,无校验,1 位停止位)。
  • 支持与 PC 上运行的 Modbus Slave 软件交互,通过 USB 转串口转换器实现电平转换和协议适配

Modbus主站功能:生成Modbus RTU请求帧(以03功能码读保持寄存器为例){从机地址, 功能码, 起始地址高字节, 起始地址低字节, 寄存器数量高字节, 寄存器数量低字节, CRC高字节, CRC低字节} CRC在线计算

  • 发送请求帧到从站设备(使用上位机软件Modbus Slave),接收并解析从站响应帧数据

数据处理:解析从站返回的寄存器数据(16位值),将解析结果存储在master_data数组中

现象:每500ms翻转LED1,表示程序运行状态。打印从机响应帧解析来的数据至串口助手

  • image-20250808104955524

Modbus Slave配置

  • image-20250808104724283

main.c

  • #include <stm32f10x.h>
    #include "led.h"
    #include "delay.h"
    #include "uart.h"//printf函数支持
    #include "timer.h"
    #include "modbus_master.h"
    int main(void) 
    {
        Systick_Init();
        Led_GPIO_Init();
        Uart_Init();
        Timer_Init(100, 720);
        Uart4_Init();//初始化UART4
    	//uint8_t modbus_read_request[8] = {0x02, 0x03, 0x00, 0x00, 0x00, 0x04, 0x44, 0x3A};
    	//示例:生成Modbus_RTU读多路寄存器输入寄存器请求帧,返回长度
    	uint8_t modbus_read_request[8];
    	u8 request_frame_length = modbus_generate_request_frame(0x02, 0x03, 0x0000, 0x0004, modbus_read_request);
    	while (1) 
    	{
    		//每500ms执行一次发送任务
           if (sendTask[0] > sendTask[1]) { 
                sendTask[0] = 0;    // 重置计数器
                LED1 = !LED1;       // 翻转LED1
    		   //向从机发送Modbus请求帧
                Uart4_SendStr(modbus_read_request, request_frame_length);
                //接收从机响应帧,进行数据帧解析
    		   if (uart4_rx_status & 0x8000) {
                    //modbus_frame_parse();//modbus协议帧解析
                }
    		    Flash_Test();
            }
            if (receiveTask[0] > receiveTask[1])
    			receiveTask[0] = 0;  // 重置计数器
        }
        return 0;
    }
    

modbus_master.c

  • /*PC10  UART4_Tx 复用推挽输出
      PC11  UART4_Rx 浮空/上拉输入
      PC8	RS485EN  通用推挽输出  0输入 1输出
      软件环境:RS485接口 modbus-RTU协议 PC安装上位机软件Modbus Slave
      硬件环境:单片机stm32RCT6(含RS485模块)、外接另一个RSRS485模块、通过USB转串口转换器
      与PC上的Modbus Slave软件进行交互
      USB转串口转换器:实现电脑与STM32之间的电平转换、协议适配和电气隔离*/
    #include <stdio.h>   // 也包含NULL定义
    #include "modbus.h"
    
    uint8_t uart4_rx_buffer[UART4_RX_BUFFER_SIZE];// 接收缓冲区
    volatile uint16_t uart4_rx_status;//接收状态:设Bit15=完成,Bit14:0=计数
    
    void Uart4_Gpio_Init(void) {
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
        GPIO_InitTypeDef gpio_init = {0};
        // PC10: UART4_Tx 复用推挽输出
        gpio_init.GPIO_Mode = GPIO_Mode_AF_PP;
        gpio_init.GPIO_Pin = GPIO_Pin_10;
        gpio_init.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(GPIOC, &gpio_init);
        // PC11: UART4_Rx 浮空输入
        gpio_init.GPIO_Mode = GPIO_Mode_IN_FLOATING;
        gpio_init.GPIO_Pin = GPIO_Pin_11;
        GPIO_Init(GPIOC, &gpio_init);
        // PC8: RS485EN 通用推挽输出
        gpio_init.GPIO_Mode = GPIO_Mode_Out_PP;
        gpio_init.GPIO_Pin = GPIO_Pin_8;
        GPIO_Init(GPIOC, &gpio_init);
        // 默认接收模式
    	RS485_MODE = 0;
    }
    
    // 初始化 UART4
    void Uart4_Init(void) {
    	//1.初始化GPIO,开启USART4时钟
        Uart4_Gpio_Init();
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_UART4, ENABLE);
        //2.配置UART4,设置波特率、流控制、启用发送和接收模式、奇偶校验位、停止位、数据位数等
        USART_InitTypeDef usart_init = {0};
        usart_init.USART_BaudRate = 9600;
        usart_init.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
        usart_init.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
        usart_init.USART_Parity = USART_Parity_No;
        usart_init.USART_StopBits = USART_StopBits_1;
        usart_init.USART_WordLength = USART_WordLength_8b;
        USART_Init(UART4, &usart_init);
        USART_Cmd(UART4, ENABLE);// 使能 UART4
        //3.中断配置
        NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
        NVIC_InitTypeDef nvic_init = {0};
        nvic_init.NVIC_IRQChannel = UART4_IRQn;
        nvic_init.NVIC_IRQChannelPreemptionPriority = 1;
        nvic_init.NVIC_IRQChannelSubPriority = 1;
        nvic_init.NVIC_IRQChannelCmd = ENABLE;
        NVIC_Init(&nvic_init);//misc.c
        USART_ITConfig(UART4, USART_IT_RXNE, ENABLE);// 使能 RXNE 中断
        USART_ITConfig(UART4, USART_IT_IDLE, ENABLE);// 使能 IDLE 中断
    }
    
    //UART4字符串发送函数
    void Uart4_SendStr(uint8_t *data, uint8_t length) {
        RS485_MODE = 1; // 发送模式
        while (length--) {
            while (!(UART4->SR & 0x40)); // 等待发送完成
            UART4->DR = *data++;
        }
        while (!(UART4->SR & 0x40)); // 确保最后一个字节发送完成
        RS485_MODE = 0; // 接收模式
    }
    
    //生成ModbusRTU请求帧
    //参数:从机地址、功能码、读取寄存器的起始地址、请求数据的长度、指向输出请求帧缓冲区的指针(至少8字节)
    uint8_t modbus_generate_request_frame(uint8_t slave_address, uint8_t function_code, 
    uint16_t start_register,uint16_t register_count, uint8_t *request) {
        // 检查参数有效性
        if (request == NULL || slave_address == 0 || register_count == 0 || register_count > 125) {
            return 0; // 参数无效,返回0
        }
        // 填充请求帧
        request[0] = slave_address;           // 从机地址
        request[1] = function_code;          // 功能码
        request[2] = (start_register >> 8);  // 起始寄存器地址高字节
        request[3] = (start_register & 0xFF); // 起始寄存器地址低字节
        request[4] = (register_count >> 8);  // 寄存器数量高字节
        request[5] = (register_count & 0xFF); // 寄存器数量低字节
        // 计算CRC16校验码
        uint16_t crc = modbus_crc16(request, 6);
        request[6] = (crc >> 8);             // CRC高字节
        request[7] = (crc & 0xFF);           // CRC低字节
        return 8; // 返回帧长度
    }
    
    uint16_t master_data[10];//Modbus接收数据缓冲区,存储从机发来的寄存器数据
    //解析Modbus数据帧中的寄存器数据
    void modbus_data_parse(void) {
        uint8_t frame_pos = 3;//从机响应帧0/1/2为从机地址/功能码/读取字节数
        uint16_t data_length = uart4_rx_buffer[2];//数据长度
        if (data_length > 20 || data_length % 2 != 0) return;//获取数据长度是否合法
        // 解析字节对至接收数据缓冲区
        for (uint16_t i = 0; i < data_length / 2; i++) {
            master_data[i] = (uart4_rx_buffer[frame_pos++] << 8);
            master_data[i] |= uart4_rx_buffer[frame_pos++];
        }
    	// 输出解析结果(便于观察,调试用)
        printf("%x %x %x %x\n", master_data[0], master_data[1], 
               master_data[2], master_data[3]);
    }
    
    //解析Modbus RTU数据帧
    void modbus_frame_parse(void) {
        uint16_t frame_length = uart4_rx_status & 0x7FFF;//数据帧长度
    	//验证CRC校验
        uint16_t crcRx = (uart4_rx_buffer[frame_length - 2] << 8) | uart4_rx_buffer[frame_length - 1];
        uint16_t crcTx = modbus_crc16(uart4_rx_buffer, frame_length - 2);
        //检查设备ID和功能码
        if (crcRx == crcTx) {
            uint8_t idx = 0;
            if (MODBUS_DEVICE_ID == uart4_rx_buffer[idx++]) {
                if (MODBUS_FUNCTION_CODE == uart4_rx_buffer[idx]) {
                    switch (uart4_rx_buffer[idx]) {
                        case 0: break;
                        case 1: break;
                        case 2: break;
                        case 3: modbus_data_parse(); break;
                        default: break;
                    }
                }
            }
        }
    	//清空缓冲区并重置状态
        memset(uart4_rx_buffer, 0, frame_length);
        uart4_rx_status = 0;
    }
    
    //UART4中断服务函数
    void UART4_IRQHandler(void) {
    	//处理RXNE中断:接收数据并存入缓冲区
        if(USART_GetITStatus(UART4, USART_IT_RXNE)){ //读取接收到的数据,自动清除RXNE标志
            uint8_t data = (uint8_t)USART_ReceiveData(UART4);
            uart4_rx_buffer[uart4_rx_status++] = data;
    		//检查缓冲区是否溢出
            if ((uart4_rx_status & 0x3FFF) > UART4_RX_BUFFER_SIZE - 1) 
                uart4_rx_status = 0; // 缓冲区溢出重置
            USART_ClearITPendingBit(UART4, USART_IT_RXNE);
        }
        //处理总线空闲中断
        if(USART_GetITStatus(UART4, USART_IT_IDLE)){
            uart4_rx_status |= 0x8000;//设置接收完成标志(Bit15)
            UART4->SR; UART4->DR; //清除IDLE中断
        }
    }
    
    //查表法计算CRC16  高效
    //CRC高位字节值表
    const u8 auchCRCHi[] = { 
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 
    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 
    0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 
    0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 
    0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 
    0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 
    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 
    0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 
    0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 
    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 
    0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 
    0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 
    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 
    0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 
    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 
    0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 
    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 
    0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 
    0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 
    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40 
    } ; 
    //CRC低位字节值表
    const u8 auchCRCLo[] = { 
    0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 
    0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 
    0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, 
    0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 
    0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4, 
    0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3, 
    0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 
    0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4, 
    0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 
    0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, 
    0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED, 
    0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26, 
    0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 
    0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, 
    0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 
    0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 
    0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 
    0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5, 
    0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 
    0x70, 0xB0, 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, 
    0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, 
    0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 
    0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B, 
    0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C, 
    0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 
    0x43, 0x83, 0x41, 0x81, 0x80, 0x40 
    } ; 
    
    //查表法计算计算Modbus RTU的CRC16校验码
    u16 modbus_crc16(u8 *puchMsg, u16 usDataLen) 
    { 
        u8 uchCRCHi = 0xFF ; // CRC高位字节初始值
        u8 uchCRCLo = 0xFF ;  
        u32 uIndex ; 
        while (usDataLen--) // 逐字节处理数据
        { 
            uIndex = uchCRCHi ^ *puchMsg++ ; 
            uchCRCHi = uchCRCLo ^ auchCRCHi[uIndex] ; 
            uchCRCLo = auchCRCLo[uIndex] ; 
        } 
        return (uchCRCHi << 8 | uchCRCLo) ; // 返回16位CRC值(高位字节在前)
    }
    
    • #ifndef _MODBUS_RTU_MASTER_H
      #define _MODBUS_RTU_MASTER_H
      
      #include "stm32f10x.h"
      #include "sys.h"
      
      #define UART4_RX_BUFFER_SIZE 200//UART4接收缓冲区大小
      #define RS485_MODE PCout(8) //RS485模式控制引脚(PC8),0=接收,1=发送,默认接收
      #define MODBUS_DEVICE_ID 0x02
      #define MODBUS_FUNCTION_CODE 0x03
      
      extern uint8_t uart4_rx_buffer[UART4_RX_BUFFER_SIZE];// 接收缓冲区
      extern volatile uint16_t uart4_rx_status;//接收状态:设Bit15=完成,Bit14:0=计数
      
      //typedef struct {
      //    uint8_t slave_address;    // 从机地址
      //    uint8_t function_code;   // 功能码
      //    uint16_t start_register; // 起始寄存器地址
      //    uint16_t register_count; // 寄存器数量
      //    uint16_t crc;            // CRC校验
      //} ModbusRTU_Frame;
      
      void Uart4_Init(void);//初始化UART4
      void Uart4_SendStr(uint8_t *data, uint8_t length);//通过UART4发送数据
      uint16_t modbus_crc16(uint8_t *data, uint16_t length);//查表法计算CRC16
      uint8_t modbus_generate_request_frame(uint8_t slave_address, uint8_t function_code, 
      uint16_t start_register,uint16_t register_count, uint8_t *request);//生成Modbus RTU请求帧
      void modbus_data_parse(void);//解析响应数据帧中的寄存器数据,并存入主/本机的寄存器
      void modbus_frame_parse(void);////解析Modbus RTU从机响应数据帧,验证CRC并处理功能码
      
      #endif
      

5.2 modbus从机

实现一个Modbus RTU从站,能够接收主机的读保持寄存器请求(功能码 03),从预定义的slave_data数组中返回寄存器数据,并生成符合Modbus RTU协议的响应帧发送回主机(Modbus Poll)。

硬件初始化:略

Modbus RTU从站功能:收到主站请求后,自动校验帧格式并生成响应帧{从机地址, 功能码, 字节数, 数据..., CRC高字节, CRC低字节}。

数据处理:从slave_data数组(包含预设寄存器数据,如 0x11, 0x22, 0x33, 0x44 等)读取指定起始地址和数量的寄存器数据,用于生成响应帧。

现象:每500ms翻转LED1,表示程序运行状态。打印从机响应帧的内容至串口助手。

  • image-20250808110754777

Modbus Pull配置

  • image-20250808112446675

main.c

  • #include <stm32f10x.h>
    #include "led.h"
    #include "delay.h"
    #include "uart.h"//printf函数支持
    #include "timer.h"
    #include "modbus_slave.h"
    int main(void) 
    {
        Systick_Init();
        Led_GPIO_Init();
        Uart_Init();
        Timer_Init(100, 720);
        Uart4_Init();//初始化UART4
    	while (1) 
    	{
    		//每500ms执行一次发送任务
           if (sendTask[0] > sendTask[1]) { 
                sendTask[0] = 0;    // 重置计数器
                LED1 = !LED1;       // 翻转LED1
                //接收主机请求帧,检查是否接收到完整数据帧,并进行数据处理,生成响应数据帧
    		   if (uart4_rx_status & 0x8000) {
                    modbus_frame_parse();//modbus协议帧解析
                }
    		    Flash_Test();
            }
            if (receiveTask[0] > receiveTask[1])
    			receiveTask[0] = 0;  // 重置计数器
        }
        return 0;
    }
    

modbus_slave.c

  • /*PC10  UART4_Tx 复用推挽输出
      PC11  UART4_Rx 浮空/上拉输入
      PC8	RS485EN  通用推挽输出  0输入 1输出
      软件环境:RS485接口 modbus-RTU协议 PC安装上位机软件Modbus Poll
      硬件环境:单片机stm32RCT6(含RS485模块)、外接另一个RSRS485模块、通过USB转串口转换器
      与PC上的Modbus Poll软件进行交互*/
    #include "modbus_slave.h"
    #include "uart.h"
    uint8_t uart4_rx_buffer[UART4_RX_BUFFER_SIZE];// 接收缓冲区
    volatile uint16_t uart4_rx_status;//接收状态:设Bit15=完成,Bit14:0=计数
    
    void Uart4_Gpio_Init(void) {
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
        GPIO_InitTypeDef gpio_init = {0};
        // PC10: UART4_Tx 复用推挽输出
        gpio_init.GPIO_Mode = GPIO_Mode_AF_PP;
        gpio_init.GPIO_Pin = GPIO_Pin_10;
        gpio_init.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(GPIOC, &gpio_init);
        // PC11: UART4_Rx 浮空输入
        gpio_init.GPIO_Mode = GPIO_Mode_IN_FLOATING;
        gpio_init.GPIO_Pin = GPIO_Pin_11;
        GPIO_Init(GPIOC, &gpio_init);
        // PC8: RS485EN 通用推挽输出
        gpio_init.GPIO_Mode = GPIO_Mode_Out_PP;
        gpio_init.GPIO_Pin = GPIO_Pin_8;
        GPIO_Init(GPIOC, &gpio_init);
        // 默认接收模式
    	RS485_MODE = 0;
    }
    
    // 初始化 UART4
    void Uart4_Init(void) {
    	//1.初始化GPIO,开启USART4时钟
        Uart4_Gpio_Init();
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_UART4, ENABLE);
        //2.配置UART4,设置波特率、流控制、启用发送和接收模式、奇偶校验位、停止位、数据位数等
        USART_InitTypeDef usart_init = {0};
        usart_init.USART_BaudRate = 9600;
        usart_init.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
        usart_init.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
        usart_init.USART_Parity = USART_Parity_No;
        usart_init.USART_StopBits = USART_StopBits_1;
        usart_init.USART_WordLength = USART_WordLength_8b;
        USART_Init(UART4, &usart_init);
        USART_Cmd(UART4, ENABLE);// 使能 UART4
        //3.中断配置
        NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
        NVIC_InitTypeDef nvic_init = {0};
        nvic_init.NVIC_IRQChannel = UART4_IRQn;
        nvic_init.NVIC_IRQChannelPreemptionPriority = 1;
        nvic_init.NVIC_IRQChannelSubPriority = 1;
        nvic_init.NVIC_IRQChannelCmd = ENABLE;
        NVIC_Init(&nvic_init);//misc.c
        USART_ITConfig(UART4, USART_IT_RXNE, ENABLE);// 使能 RXNE 中断
        USART_ITConfig(UART4, USART_IT_IDLE, ENABLE);// 使能 IDLE 中断
    }
    
    //UART4字符串发送函数
    void Uart4_SendStr(uint8_t *data, uint8_t length) {
        RS485_MODE = 1; // 发送模式
        while (length--) {
            while (!(UART4->SR & 0x40)); // 等待发送完成
            UART4->DR = *data++;
        }
        while (!(UART4->SR & 0x40)); // 确保最后一个字节发送完成
        RS485_MODE = 0; // 接收模式
    }
    
    u8 modbus_response_fram[150] = {0x02,0x03,0x08};//从机响应数据帧,已包含地址、功能码、字节数
    u16 slave_data[10]={0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88,0x99};//从机寄存器数据,可被主机读写
    //生成响应数据帧,若主机发送的是03功能码,发送的是从机寄存器数据
    void modbus_generate_response_frame(void)
    {
    	u8 frame_pos=3;
    	u16 start_addr = (uart4_rx_buffer[2] << 8) | uart4_rx_buffer[3]; // 起始寄存器地址;
    	u16 dataLength = 2*(((u16)uart4_rx_buffer[4]<<8) | (u16)uart4_rx_buffer[5]);//2*主机要读取的寄存器数(主机发送帧的第5、6个字节)==字节数
    	for(u16 i=0;i<dataLength/2;i++)
    	{
    		modbus_response_fram[frame_pos++]=(slave_data[start_addr]>>8)& 0xff; 
    		modbus_response_fram[frame_pos++]=slave_data[start_addr] & 0xff;
    		start_addr++;
    	}
    	u16 crc = modbus_crc16(modbus_response_fram,frame_pos);//计算校验码
    	modbus_response_fram[frame_pos++]=crc>>8;//前8位校验码
    	modbus_response_fram[frame_pos++]=crc & 0xff;//后8位
    	Uart4_SendStr(modbus_response_fram,frame_pos);//发送响应数据帧
    	for (int i = 0; i < frame_pos; i++) //02 03 08 00 33 00 44 00 55 00 66 F9 A5
    	{
    		printf("%02X ", modbus_response_fram[i]); // 打印16进制,空格分隔
    	}
    	printf("\n"); // 换行
    	//printf("%X",modbus_crc16(modbus_response_fram,sdpos-2));检验码无问题,F9A5
    }
    
    #define slave_id  0x02
    #define function_id  0x03
    //主机Modbus协议(数据帧)解析
    void modbus_frame_parse(void)
    {
    	u16 frame_length = uart4_rx_status & 0x7fff;//数据帧长度
    	u16 crcRx = (u16)uart4_rx_buffer[frame_length-2]<<8 | (u16)uart4_rx_buffer[frame_length-1];//接收主机发送来的校验码
    	u16 crcTx = modbus_crc16(uart4_rx_buffer,frame_length-2);//从机接收后再次计算校验码验证
    	u8 idx = 0;
    	if(crcRx == crcTx)//CRC验证
    	{
    		if(slave_id == uart4_rx_buffer[idx++])//验证地址
    		{
    			if(function_id == uart4_rx_buffer[idx])//验证功能号
    			{
    				switch(function_id)
    				{
    					case 0: break;
    					case 1: break;
    					case 2: break;
    					case 3: modbus_generate_response_frame(); break;
    					default:break;
    				}
    			}
    			
    		}
    	}
    	memset(uart4_rx_buffer, 0, frame_length);
        uart4_rx_status = 0;
    }
    
    //UART4中断服务函数
    void UART4_IRQHandler(void) {
    	//处理RXNE中断:接收数据并存入缓冲区
        if(USART_GetITStatus(UART4, USART_IT_RXNE)){ //读取接收到的数据,自动清除RXNE标志
            uint8_t data = (uint8_t)USART_ReceiveData(UART4);
            uart4_rx_buffer[uart4_rx_status++] = data;
    		//检查缓冲区是否溢出
            if ((uart4_rx_status & 0x7FFF) > UART4_RX_BUFFER_SIZE - 1) 
                uart4_rx_status = 0; // 缓冲区溢出重置
            USART_ClearITPendingBit(UART4, USART_IT_RXNE);
        }
        //处理总线空闲中断
        if(USART_GetITStatus(UART4, USART_IT_IDLE)){
            uart4_rx_status |= 0x8000;//设置接收完成标志(Bit15)
            UART4->SR; UART4->DR; //清除IDLE中断
        }
    }
     
    //查表法计算CRC16  高效--同上,略
    
    • #ifndef _MODBUS_SLAVE_H
      #define _MODBUS_SLAVE_H
      #include "stm32f10x.h"
      #include "sys.h"
      
      #define UART4_RX_BUFFER_SIZE 200//UART4接收缓冲区大小
      #define RS485_MODE PCout(8) //RS485模式控制引脚(PC8),0=接收,1=发送,默认接收
      #define MODBUS_DEVICE_ID 0x02
      #define MODBUS_FUNCTION_CODE 0x03
      
      extern uint8_t uart4_rx_buffer[UART4_RX_BUFFER_SIZE]; // 接收缓冲区
      extern volatile uint16_t uart4_rx_status; // 接收状态:Bit15=完成,Bit14=收到0x0D,
      extern u8 modbus_response_fram[150];// 从机响应数据缓冲区(作为响应数据帧)
      
      void Uart4_SendStr(u8 arr[], u8 len);//通过UART4发送数据
      void Uart4_Init();//初始化UART4
      u16 modbus_crc16(u8 *puchMsg, u16 usDataLen);//查表法计算CRC16
      void modbus_generate_response_frame(void);//生成响应数据帧
      void modbus_frame_parse(void);//解析Modbus RTU主机请求数据帧,验证CRC并处理功能码
      
      #endif
      
Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐