当前位置: 首页 > news >正文

STM32之RS485与ModBus详解

一、RS485

1. RS485 概述

  • 连接与传输:基于硬件有线连接的数据传输方式,属串行(串行)通信,用于工业场景
  • 对比 RS232:RS232 电气稳定性差、易受干扰、传输距离短;RS485 稳定性好、传输距离远
  • 接线与连接:需 A、B 两根数据线,MCU 借差分线连 485 芯片保障数据稳定一致

2. 采用 RS485 的原因

  • 传输距离:低速率、满足布线要求时,可达 1200 米,还能借中继节点延长
  • 传输速度:最高 10Mbps(1.25MB/s ),但高速下传输距离会缩短
  • 多设备连接:理论单设备可连 32 个 485 设备,自定义地址(0x01 - 0xxx ),结合技术可扩至 128 台
  • 成本优势:芯片与设备成本低

3. RS485 工作(数据收发)

  • 基础逻辑:借 A、B 线收发数据,485 芯片依 MCU 时钟周期,调 A、B 电压差完成收发
  • 数据发送规则
    • 发数据 1:A 端子电压 - B 端子电压>200mV ~ 6V ,实际开发板常用>2V ~ 3.3V(受 3.3V 供电限制 )
    • 发数据 0:B 端子电压 - A 端子电压>2V ~ 3.3V
    • 注意:200mV 是判断 0/1 理论最低标准,导线压降可能致电压差不足 200mV ,引发数据丢失

4. RS485原理图分析

4.1 原理图分析连线关系和对应引脚

        本案例中,单片机上有485芯片里面的RS485_TX和RX引脚分别连接USART2_RX和USART2_TX引脚,最后通过引脚复用,通过PA2和PA3引脚连入MCU;而RS485——RE直接通过服用PD7引脚连入MCU。

4.2 开发流程分析

【引脚分析】

  • 当前使用的串口对应 USART2
    • USART2_TX ==> PA2 复用推挽模式
    • USART2_RX ==> PA3 浮空输入
  • 485 芯片数据发送模式和数据接收模式控制
    • RS485_RE --> PD7 推挽模式

代码实现过程

  • 时钟使能
    • USART2 GPIOA GPIOD
  • 引脚配置
    • PA2 复用推挽模式
    • PA3 浮空输入
    • PD7 推挽模式
  • 配置 USART2
    • 波特率,8N1,USART_TX | USART_RX
    • 中断使能 RXNE 和 IDLE,对应 USART2_IRQn
    • 中断函数 USART2_IRQHandler
  • 【重点】
    • 通过 USART2 进行数据发送操作,
      • 要求必须通过 PD7 设置为高电平输出,打开 485 发送数据模式,
      • 当前数据发送完成,将 PD7 设置为低电平输出,485 芯片进入数据接收模式。

4.3 代码示例

rs485.h:

#ifndef _RS485_H
#define _RS485_H
#include "stm32f10x.h"
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "usart1.h"
#include "systick.h"
#include "led.h"
#include "beep.h"
#define RS485_DATA_SIZE (256)
typedef struct rs485_data
{u8 data[RS485_DATA_SIZE]; // 接受数据缓冲区u8 flag; // 数据处理标志位u16 count; // 读取到的有效字节个数
} RS485_Data;
extern RS485_Data rs485_val;
void RS485_Init(u32 brr);
void RS485_SendByte(u8 byte);
void RS485_SendBuffer(u8 *buffer, u16 count);
void RS485_SendString(const char * str);
#endif

rs485.c:

#include "rs485.h"RS485_Data rs485_val = {0};void RS485_Init(u32 brr)
{// 1. 时钟使能 USART2 GPIOA GPIODRCC_APB2PeriphClockCmd(RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPDEN, ENABLE);RCC_APB1PeriphClockCmd(RCC_APB1ENR_USART2EN, ENABLE);// 2. GPIO PA2 配置 复用推挽GPIO_InitTypeDef GPIO_InitStructure = {0};GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);// GPIO PA3 配置 浮空输入GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;GPIO_Init(GPIOA, &GPIO_InitStructure);// GPIO PD7 配置 推挽GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOD, &GPIO_InitStructure);/// 3. USART2 配置USART_InitTypeDef USART_InitStructure = {0};USART_InitStructure.USART_BaudRate = brr;USART_InitStructure.USART_WordLength = USART_WordLength_8b;USART_InitStructure.USART_Parity = USART_Parity_No;USART_InitStructure.USART_StopBits = USART_StopBits_1;USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;USART_Init(USART2, &USART_InitStructure);USART_Cmd(USART2, ENABLE);// 4. USART2 串口中断配置USART_ITConfig(USART2, USART_IT_IDLE, ENABLE);USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);NVIC_InitTypeDef NVIC_InitStructure = {0};NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;NVIC_Init(&NVIC_InitStructure);
}void USART2_IRQHandler(void)
{u16 val = 0;/*检测到数据总线空闲,表示数据接收完毕,进行展示处理,同时处理当前中断标志位IDLE 清除要求1. 读取 USARTx->SR2. 读取 USARTx->DR不建议使用 void USART_ClearFlag(USART_TypeDef* USARTx, uint16_t USART_FLAG)*/if (USART_GetITStatus(USART2, USART_IT_IDLE) == SET){rs485_val.flag = 1;val = USART2->SR;val = USART2->DR;USART1_SendBuffer(rs485_val.data, rs485_val.count);}/*1. 处理 RXNE 中断接收数据缓冲区非空,需要进行接收数据处理2. 处理 IDLE 中断当前接收数据总线已空闲,数据接收完毕*//*如果 flag 为 1 表示以当前数据已进行后续处理,需要对当前占用的内存空间进行擦除操作,方便进入下一次数据接受*/if (rs485_val.flag){memset(&rs485_val, 0, sizeof(RS485_Data));}if (USART_GetITStatus(USART2, USART_IT_RXNE) == SET){// 从 USART3 读取数据,存储到 rs485_val 结构中,同时赋值 data 存储数据// count 累加有效数据个数rs485_val.data[rs485_val.count++] = USART_ReceiveData(USART2);// 如果数据已满,利用 USART1 发送数据到 PC 串口调试工具if (rs485_val.count == RS485_DATA_SIZE){// USART1_SendBuffer(esp8266_val.data, esp8266_val.count);rs485_val.flag = 1;USART1_SendBuffer(rs485_val.data, rs485_val.count);}}
}void RS485_SendByte(u8 byte)
{while (USART_GetFlagStatus(USART2, USART_FLAG_TC) == RESET);USART_SendData(USART2, byte);
}void RS485_SendBuffer(u8 *buffer, u16 count)
{// PD7 高电平GPIO_SetBits(GPIOD, GPIO_Pin_7);while (count--){RS485_SendByte(*buffer);buffer += 1;}// PD7 低电平while(USART_GetFlagStatus(USART2,USART_FLAG_TC) == RESET);GPIO_ResetBits(GPIOD,GPIO_Pin_7);
}void RS485_SendString(const char * str)
{// PD7 高电平GPIO_SetBits(GPIOD, GPIO_Pin_7);while (*str){RS485_SendByte(*str);str++;}while(USART_GetFlagStatus(USART2,USART_FLAG_TC) == RESET);// PD7 低电平GPIO_ResetBits(GPIOD, GPIO_Pin_7);
}

实现效果:

二、ModBus

1. ModBus 概述

  • 开放性:Modbus 协议是完全开放的,任何人都可以免费使用,不需要支付许可证费用。这使得它在工业自动化领域得到了广泛的应用,不同厂商的设备可以方便地实现互联互通。
  • 简单性:协议简单易懂,易于实现。它采用主从通信方式,通信规则明确,对于开发者来说,无论是硬件实现还是软件编程都相对容易上手。
  • 可靠性:在工业环境中,通信的可靠性至关重要。Modbus 协议具有一定的错误检测机制,例如奇偶校验、CRC(循环冗余校验)等,可以有效保证数据传输的准确性。
  • 灵活性:支持多种电气接口,如 RS - 232、RS - 485 等,还可以通过以太网进行通信(Modbus TCP)。同时,它可以应用于不同类型的设备,包括 PLC、传感器、执行器、变频器等。
Modbus 支持三种数据传输模式
  • Modbus RTU(Remote Terminal Unit):这是一种紧凑的、高效的传输模式,使用二进制编码表示数据。在 RTU 模式下,每个字节包含 8 位数据,通信效率较高,常用于串行通信(如 RS - 485)。
  • Modbus ASCII:采用 ASCII 字符编码表示数据,每个字节由两个 ASCII 字符组成。这种模式相对 RTU 模式数据量较大,但可读性强,适用于对数据可读性要求较高的场合。
  • Modbus TCP:基于 TCP/IP 协议的 Modbus 版本,通过以太网进行通信。它使用标准的 TCP 端口 502,通信速度快,适用于远程监控和大规模的工业自动化系统。

一、8421BCD 码的基础逻辑(先理解编码规则)

8421BCD 码用 4 位二进制对应 1 位十进制数,4 位的位权从高位到低位依次为 8、4、2、1,仅表示 0-9(若出现 1010-1111,属于无效码)。例如:

 
  • 十进制数 “25” → 拆分为 “2” 和 “5” → 分别编码:2(0010)、5(0101) → 8421BCD 码为 “0010 0101”(即十六进制0x25,二进制00100101)。
  • 十进制数 “100” → 拆分为 “1”“0”“0” → 编码为 “0001 0000 0000”(需 3 个 4 位组,对应 2 个字节 + 1 个 4 位组,实际传输时会补成完整字节,如0x01 0x00)。

二、结合三种 Modbus 模式的案例(均以 “传输十进制温度 25℃” 为例)

假设场景:温度传感器采集到温度为十进制 25℃,约定用 8421BCD 码传输,传感器作为 Modbus 从机(地址 0x01),PLC 作为主设备,读取传感器的输入寄存器(地址 0x0000)。

1. Modbus RTU 模式(二进制编码,高效紧凑)

Modbus RTU 以二进制字节为单位传输,数据帧格式为:从机地址 + 功能码 + 寄存器地址 + 寄存器数量 + 校验码(CRC16)(请求帧);从机地址 + 功能码 + 数据长度 + 数据 + 校验码(CRC16)(应答帧)。

 
  • 编码逻辑:十进制 25℃ → 8421BCD 码为0010 0101(1 个字节,十六进制0x25)。
  • 主设备请求帧(读从机 0x01 的输入寄存器 0x0000,读 1 个寄存器):
    从机地址功能码(读输入寄存器)寄存器起始地址(高字节)寄存器起始地址(低字节)寄存器数量(高字节)寄存器数量(低字节)CRC16 校验码(高字节)CRC16 校验码(低字节)
    0x010x040x000x000x000x010x600x00
    (注:功能码 0x04 用于读输入寄存器,CRC16 校验码通过工具计算得出)
  • 从机应答帧(返回温度数据):
    从机地址功能码数据长度(字节数)温度数据(8421BCD 码)CRC16 校验码(高字节)CRC16 校验码(低字节)
    0x010x040x020x00 0x250x980x39
    (注:输入寄存器为 16 位(2 字节),因此数据长度为 0x02,8421BCD 码0x25补高位 0x00,组成0x0025,主设备解析时忽略高位 0,得到0x25→十进制 25℃)
2. Modbus ASCII 模式(ASCII 编码,可读性强)

Modbus ASCII 将每个二进制字节转换为 2 个 ASCII 字符(0-9、A-F)传输,数据帧以:开头,以\r\n结尾,校验码为 LRC(纵向冗余校验)。

 
  • 编码逻辑:十进制 25℃→8421BCD 码0x25→转换为 ASCII 字符为 “25”;寄存器地址、功能码等也需转为 ASCII。
  • 主设备请求帧(读从机 0x01 的输入寄存器 0x0000,读 1 个寄存器):
    :010400000001F9\r\n
    • 解析::(帧起始)→01(从机地址 ASCII)→04(功能码 ASCII)→0000(寄存器地址 0x0000 的 ASCII)→0001(寄存器数量 1 的 ASCII)→F9(LRC 校验码 ASCII)→\r\n(帧结束)
  • 从机应答帧(返回温度数据):
    :01040200253C\r\n
    • 解析:01(从机地址)→04(功能码)→02(数据长度 2 字节的 ASCII)→0025(8421BCD 码0x0025的 ASCII)→3C(LRC 校验码)→\r\n;主设备将 “0025” 转为十六进制0x0025,再按 8421BCD 码解析为十进制 25℃。
3. Modbus TCP 模式(基于 TCP/IP,远程高速传输)

Modbus TCP 去掉了 RTU 的 CRC 校验和 ASCII 的 LRC 校验,增加 “MBAP 报文头”(用于 TCP 通信标识),数据部分与 RTU 类似(二进制编码),默认端口 502。

 
  • 编码逻辑:与 RTU 一致,十进制 25℃→8421BCD 码0x0025(16 位寄存器数据)。
  • 主设备请求帧(MBAP 头 + Modbus 数据):
    MBAP 头(6 字节)Modbus 数据(6 字节)
    事务处理标识(0x0001)从机地址(0x01)
    协议标识(0x0000,Modbus 协议)功能码(0x04)
    数据长度(0x0006,后续 Modbus 数据字节数)寄存器地址(0x0000)
    -寄存器数量(0x0001)
    (完整帧:00 01 00 00 00 06 01 04 00 00 00 01
  • 从机应答帧
    MBAP 头(6 字节)Modbus 数据(5 字节)
    事务处理标识(0x0001,与请求一致)从机地址(0x01)
    协议标识(0x0000)功能码(0x04)
    数据长度(0x0005,后续 Modbus 数据字节数)数据长度(0x02)
    -温度数据(0x00 0x25)
    (完整帧:00 01 00 00 00 05 01 04 02 00 25);主设备解析数据部分0x0025为 8421BCD 码,得到十进制 25℃。

2. ModBus 通信栈和数据帧

目前,使用下列情况实现 MODBUS:
以太网上的 TCP/IP。各种媒介(有线:EIA/TIA - 232 - E、EIA - 422、EIA/TIA - 485 - A;光纤、无线等等)上的异步串行传输。

  • ModBus 数据传递的标准格式
  • ADU: Application Data Unit 应用程序数据单元,是整个 ModBus 协议要求的数据传递完整数据包
    • 地址域:当前数据发送 / 接受目标接收设备的地址。
    • PDU : 协议数据单元 / 功能码数据单元,组成是功能码 + 数据
    • 差错校验 (CRC) : 针对于整个 ADU 数据的校验机制。
  • PDU: Protocol Data Unit 协议数据单元 / 功能码数据单元
    • 功能码:绝对当前 ModBus 协议内容具体功能模式,例如读,写操作
    • 数据:可以认为是 ModBus 有效载荷。有效数据。

3. RS485 一主多从结构和 ModBus 地址域

4. ModBus 数据类型

  • 离散量输入
    • 一般用于设备状态,例如设备开光状态,设备运行状态,状态仅有 0 和 1,程序无法控制【离散量输入】数据内容, 完全由硬件本身状态控制。
  • 线圈
    • LED 灯控制,Beep 控制,声光警告器控制,继电器,固态继电器。仅需要一位二进制既可以控制工作状态,例如 0 表示不工作,1 表示正常工作。同样可以读取设备工作状态。
  • 输入寄存器
    • 只读寄存器,一般对应传感器采样数据在当前设备中的存储位置,数据仅可以通过传感器采样分析方式修改,用户只能读取传感器反馈的数据内容,对应 2 个字节 (16 bit)。如果传感器采样数据较为复杂,可能会利用多组【输入寄存器】来描述数据内容。例如 数据高位 2 字节,数据低位 2 字节,精度 2 字节,指数范围 2 字节...
  • 保持寄存器
    • 可以进行写入数据控制,读取数据内容,例如车辆行驶模式设置 (纯电,混动,增程,运动,越野,雪地,自定义),设备工作状态。

一、离散量输入(Discrete Inputs)

  • 是什么:专门用来反映设备的 “开关状态类信息”,数据只有 0 或 1 两种,且只能从硬件读取,程序无法主动修改它的值 。
  • 空间与权限:占用 1bit 空间,权限是只读 ,就像设备状态的 “只读快照”。
  • 举例:工厂里的 “设备运行状态检测”,比如生产线上的传送带,电机的 “运行 / 停止” 是硬件状态(电机自己决定转或停 ),PLC 通过离散量输入读取:电机运行时,离散量输入值为 1;电机停止,值为 0 。再比如 “门禁系统里门的开关状态”,门打开,对应离散量输入是 1;门关闭,值为 0 ,PLC 只能读这个状态,没法直接改门是开还是关。

二、线圈(Coils)

  • 是什么:用来控制设备 “开关动作”,或者读取设备 “开关动作状态”,数据是 0 或 1 ,支持读写操作 。
  • 空间与权限:占用 1bit 空间,权限是读写 ,相当于设备的 “控制开关 + 状态反馈” 。
  • 举例:控制工厂里的 “LED 指示灯”,PLC 写 1 到对应线圈,灯亮;写 0 ,灯灭 。同时,也能读这个线圈的值,判断灯当前是亮(1 )还是灭(0 )。再比如 “控制继电器”,线圈值为 1 时,继电器吸合,接通电路;值为 0 时,继电器断开,通过读线圈也能知道继电器当前状态。

三、输入寄存器(Input Registers)

  • 是什么:存的是传感器等设备采集的 “模拟量数据(经转换后的数字量 )”,只能从硬件读,程序不能直接写 。一般是 16bit(2 字节 )大小,用来存更复杂的数值,像温度、压力等。
  • 空间与权限:占用 16bit 空间,权限是只读 ,是传感器数据的 “只读容器” 。
  • 举例:工厂里 “温度传感器测水温”,传感器把水温(比如 25℃ )转换成 16bit 的数字量(假设对应数值是 0x00FF ),存在输入寄存器里,PLC 只能读这个寄存器的值,再转换成实际温度显示或参与控制逻辑,没法直接往这个寄存器写数据改水温(水温由传感器硬件检测决定 )。如果传感器要传 “温度、湿度、气压” 一组复杂数据,可能会用多个输入寄存器,比如温度存在寄存器 1,湿度存在寄存器 2 等。

四、保持寄存器(Holding Registers)

  • 是什么:可读写,能存设备的 “状态参数、控制指令” 等,用途灵活,常用来设置设备工作模式、读写设备运行数据 。也是 16bit 大小。
  • 空间与权限:占用 16bit 空间,权限是读写 ,相当于设备的 “可配置参数库 + 状态存储区” 。
  • 举例:控制 “新能源汽车的行驶模式”,车辆的 PLC(或控制器 )里,保持寄存器存着模式值:写 1 设为纯电模式,写 2 设为混动模式 。同时,也能读这个寄存器,知道当前车处于什么模式。再比如 “工厂里变频器的频率设置”,往保持寄存器写 50Hz 对应的数值,变频器就按 50Hz 运行,读这个寄存器能知道当前设置的频率。

5. ModBus 功能码

  • ModBus 功能码绝对当前内容具体作用
    • 公共功能码【重点】
      • 要求所有支持 ModBus 协议通信的设备必须执行的功能码内容。例如 离散量输入读取,线圈读写操作
    • 用户定义功能码
      • 企业 / 个人,可以根据自身需求,自定义功能码,要求 ModBus 协议支持的两端
    • 保留功能码
      • 一般是用于较早期设备功能保持使用,目前逐步淘汰,或者不再使用。

        针对于离散量输入,线圈,输入寄存器和保持寄存器操作【功能码】

6. ModBus 数据模式

三种数据模式

  • ModBus RTU : 8421 BCD 码
  • ModBus ASCII
  • ModBus TCP

发送数据 15,利用 ModBus 方式发送

  • ModBus RTU 方式: 0001 0101
  • ModBus ASCII 方式: 0011 0001 0001 0101 按照字符 '1' 和字符 '5' 处理
  • RTU 8421BCD 码

案例:

对比项Modbus RTUModbus ASCIIModbus TCP
编码规则二进制编码(可兼容 8421 BCD 码)ASCII 字符编码(每个字节拆成 2 个 ASCII 字符)二进制编码(与 RTU 编码逻辑一致)
数据效率高(1 字节 = 8 位数据)低(1 字节数据→2 个 ASCII 字符,占 2 字节)高(同 RTU 编码,无额外字符转换)
传输载体串行总线(如 RS-485、RS-232)串行总线(如 RS-485、RS-232)以太网(TCP/IP 网络)
校验方式CRC16 校验(二进制校验,效率高)LRC 校验(ASCII 字符校验,可读性强)依赖 TCP 校验(无需额外 Modbus 校验)
典型场景工业现场短距离、多设备串行组网(如 PLC 与传感器)需人工调试 / 监控的简单串行场景(如现场临时诊断)远程监控、跨网络大规模系统(如云端 - 工厂)
数据表示示例(发送 15)二进制编码:0001 0101(若用 8421 BCD 码,150001 0101 直接对应)ASCII 编码:'1'(0011 0001) + '5'(0011 0101)→0011 0001 0011 0101二进制编码:0001 0101(与 RTU 编码一致,通过 TCP 报文传输)

8.  ModBus 实际数据帧分析

9. RS485 + ModBus 案例

9.1 开发分析

9.2 代码实现

modbus.h:

#ifndef _MODBUS_H
#define _MODBUS_H#include "stm32f10x.h"#include "rs485.h"#include "stdio.h"
#include "stdlib.h"
#include "string.h"typedef struct{float tem;  //温度float hum;   //湿度float bp;   //压强u32 lux;     //照度
}Sensor_Data;extern Sensor_Data sensor_data;/*** @brief ModBus 发送 03 功能码函数,对应功能是读取多个【输入寄存器】*        或者【保持寄存器】数据* * @param id       设备地址* @param addr     读取目标数据寄存器地址* @param data_len 读取的数据个数*/
void ModBus_Send03Cmd(u8 id,u16 addr,u16 data_len);void Analysis_ModBus_Tem_Hum(Sensor_Data *sensor_data);void Analysis_ModBus_BP_Lux(Sensor_Data *sensor_data);/*** @brief ModBus CRC 校验函数*/
uint16_t ModBus_CRC16(const uint8_t *data, uint16_t length);#endif

modbus.c:

#include "modbus.h"Sensor_Data sensor_data = {0};
extern RS485_Data rs485_val;void ModBus_Send03Cmd(u8 id,u16 addr,u16 data_len){//用于存储03功能码对应发送ModBus协议问询帧u8 modbus_03_buffer[8];//ModBus 协议组包,设备地址 + 03功能码modbus_03_buffer[0] = id;modbus_03_buffer[1] = 0x03;// 寄存器起始地址modbus_03_buffer[2] = addr / 256; // 寄存器地址高位modbus_03_buffer[3] = addr % 256; // 寄存器地址低位// 请求数据长度modbus_03_buffer[4] = data_len / 256; // 请求寄存器个数数据高位modbus_03_buffer[5] = data_len % 256; // 请求寄存器个数数据高位uint16_t crc = ModBus_CRC16(modbus_03_buffer, 6);// CRC 校验位modbus_03_buffer[6] =  crc % 256;// CRC 数据校验结果低位modbus_03_buffer[7] =  crc / 256;// CRC 数据校验结果高位for (int i = 0; i < 8; i++){printf("%02X ", modbus_03_buffer[i]);}printf("\r\n");// 调用 RS485_SendBuffer 发送组好的报文RS485_SendBuffer(modbus_03_buffer, 8);
}void Analysis_ModBus_Tem_Hum(Sensor_Data *sensor_data){// 1. 校验数据完整性与合法性// 至少需要 9 字节(地址1 + 功能码1 + 数据长度1 + 数据4 + CRC2 )if(rs485_val.count < 9){return;}//取出接收数据指针u8 *modbus_data = rs485_val.data;// 2. CRC 校验(排除最后 2 字节 CRC )uint16_t crc_calc = ModBus_CRC16(modbus_data, rs485_val.count - 2);//将高 8 位左移 8 位(腾出低 8 位空间),再与低 8 位进行 “或运算”,组合成一个 16 位的完整 CRC 值uint16_t crc_recv = (modbus_data[rs485_val.count - 1] << 8) | modbus_data[rs485_val.count - 2];if (crc_calc != crc_recv) {// CRC 校验失败,清空缓存memset(&rs485_val, 0, sizeof(RS485_Data));return;}// 3. 校验功能码(确保是 0x03 应答)if (modbus_data[1] != 0x03) {return;}// 4. 解析数据// 湿度:第 3 字节是数据长度(通常 0x04 ,对应 2 个寄存器、4 字节数据 )if (modbus_data[2] == 0x04) {// 湿度值 = (高 8 位 << 8) | 低 8 位 ,再转换为实际值short hum_raw = (modbus_data[3] << 8) | modbus_data[4]; sensor_data->hum = hum_raw * 0.1f;// 温度值 = (高 8 位 << 8) | 低 8 位 ,再转换为实际值short tem_raw = (modbus_data[5] << 8) | modbus_data[6]; sensor_data->tem = tem_raw * 0.1f;}// 5. 解析完成后,清空 rs485_val ,准备下一次接收memset(&rs485_val, 0, sizeof(RS485_Data));
}// 解析大气压值 + 光照强度(Lux)数据,根据文档格式修改
void Analysis_ModBus_BP_Lux(Sensor_Data *sensor_data) 
{// 1. 校验数据长度和基本合法性// 文档中大气压+光照问询帧应答,有效字节数等需结合实际,这里至少 9 字节(地址1+功能码1+数据长度1+数据n+CRC2 )if (rs485_val.count < 9) {return; }u8 *modbus_data = rs485_val.data;// 2. CRC 校验(排除最后 2 字节 CRC )uint16_t crc_calc = ModBus_CRC16(modbus_data, rs485_val.count - 2);uint16_t crc_recv = (modbus_data[rs485_val.count - 1] << 8) | modbus_data[rs485_val.count - 2];if (crc_calc != crc_recv) {memset(&rs485_val, 0, sizeof(RS485_Data));return;}// 3. 校验功能码(确保是 0x03 应答)if (modbus_data[1] != 0x03) {return;}// 4. 解析数据,根据文档定义://    - 大气压值:寄存器 505,实际值是寄存器值的 0.1 倍(文档说“实际值 10 倍” ,所以要除以 10 )//    - Lux 值:寄存器 506(高 16 位)、507(低 16 位),组合成 32 位实际值//    数据长度需根据返回判断,文档示例中“有效字节数 0x06”,即数据段 6 字节(对应 3 个寄存器,每个 2 字节 )if (modbus_data[2] == 0x06) { // 解析大气压值// 问询帧起始地址对应寄存器 505(地址 40506 ),数据段前 2 字节是大气压值(寄存器值)uint16_t bp_reg_val = (modbus_data[3] << 8) | modbus_data[4]; // 文档说“实际值 10 倍”,所以实际大气压 = 寄存器值 / 10.0 sensor_data->bp = bp_reg_val / 10.0f; // 解析 Lux 值:高 16 位(寄存器 506,modbus_data[5]、modbus_data[6] ) + 低 16 位(寄存器 507,modbus_data[7]、modbus_data[8] )// 组合成 32 位实际值(文档说“实际值”,假设无需额外倍数转换,直接组合 )uint32_t lux_high = (modbus_data[5] << 8) | modbus_data[6];uint32_t lux_low = (modbus_data[7] << 8) | modbus_data[8];uint32_t lux_reg_val = (lux_high << 16) | lux_low;sensor_data->lux = lux_reg_val; }// 5. 解析完成后,清空 rs485_val ,准备下一次接收memset(&rs485_val, 0, sizeof(RS485_Data));
}uint16_t ModBus_CRC16(const uint8_t *data, uint16_t length) 
{uint16_t crc = 0xFFFF;uint16_t i, j;for (i = 0; i < length; i++) {crc ^= (uint16_t)data[i];for (j = 0; j < 8; j++) {if (crc & 0x0001) {crc >>= 1;crc ^= 0xA001;} else {crc >>= 1;}}}return crc;
}

0voice · GitHub


文章转载自:

http://8T676NUN.fnrkh.cn
http://PrPuaClW.fnrkh.cn
http://tW9R8Thb.fnrkh.cn
http://4cul3gH6.fnrkh.cn
http://vavOjeZ6.fnrkh.cn
http://RB0ecQ6N.fnrkh.cn
http://HvAt9Z7Z.fnrkh.cn
http://W9tntx9e.fnrkh.cn
http://AM4Dyem1.fnrkh.cn
http://5sBtwJvU.fnrkh.cn
http://lyGnLED2.fnrkh.cn
http://K0kM8d00.fnrkh.cn
http://5YCAtu71.fnrkh.cn
http://0mRj1SDH.fnrkh.cn
http://b5W7Tp3Z.fnrkh.cn
http://lzcm40vn.fnrkh.cn
http://fMcpFAHK.fnrkh.cn
http://9NA8TBQk.fnrkh.cn
http://ooT4dk3k.fnrkh.cn
http://3AeVUFje.fnrkh.cn
http://AIeF5GiZ.fnrkh.cn
http://1hfm4fMi.fnrkh.cn
http://skBBIwKw.fnrkh.cn
http://b9e0N5Yf.fnrkh.cn
http://MRThMZXE.fnrkh.cn
http://dQJFCWwx.fnrkh.cn
http://uNsiOyfC.fnrkh.cn
http://Ndn2xPu0.fnrkh.cn
http://EpERjKSo.fnrkh.cn
http://ga8Ravqe.fnrkh.cn
http://www.dtcms.com/a/375829.html

相关文章:

  • DCDC输出
  • GitHub 项目提交完整流程(含常见问题与解决办法)
  • Day39 SQLite数据库操作与文本数据导入
  • python常用命令
  • 广东省省考备考(第九十五天9.9)——言语、资料分析、判断推理(强化训练)
  • MySQL问题8
  • 【AI】Jupyterlab中关于TensorFlow版本问题
  • Java 运行时异常与编译时异常以及异常是否会对数据库造成影响?
  • CosyVoice2简介
  • 新机快速搭建java开发环境过程记录
  • std::enable_shared_from_this
  • Spring Boot--Bean的扫描和注册
  • Pytorch基础入门3
  • ARM-指令集全解析:从基础到高阶应用
  • ARM 汇编学习
  • 今天继续昨天的正则表达式进行学习
  • Mysql集群——MHA高可用架构
  • 【一包通刷】晶晨S905L(B)/S905L2(B)/S905L3(B)-原机安卓4升级安卓7/安卓9-通刷包
  • SYSTEM 提权面板:提升文件运行权限的高效工具
  • 【Python】S1 基础篇 P6 用户交互与循环控制:构建动态交互程序
  • Java 数据类型详解
  • java常见SSL bug解决方案
  • JAVA stream().flatMap()
  • 【C++】string类 - 库中的常见使用
  • Go语言基础---数据类型间的故事
  • 金融量化指标--6InformationRatio信息比率
  • GPT Server 文档
  • CDN加速带来的安全隐患及应对方法
  • HCL Unica+:AI驱动的营销自动化与个性化平台
  • spring事务管理之@Transactional