Modbus通讯协议:从零开始,新手也能看懂

我第一次接触Modbus时,是做一个电梯的配件,还有后面做PDU也做过。

网上资料一大堆,但零零散散,我一开始也完全不知道从哪下手,我翻开官方文档,满眼的专业术语和表格,头都大了。帧结构是什么?CRC校验怎么算?完全摸不着头脑。

其实找个简单的代码示例,把基础概念弄明白,再去看理论的东西,贼简单。

后来我找到突破口:从Modbus RTU入手,先搞懂帧结构和功能码,其他的慢慢补。

这篇文章带你从零开始,拆解Modbus的核心概念。

1. 引言

先大概介绍下,在工业自动化领域,设备之间的“对话”是实现智能控制的核心。Modbus通讯协议作为一种简单、开放的协议,自1979年诞生以来,广泛应用于PLC、传感器、变频器等设备的数据交换。它的设计初衷是为工业现场提供一种高效的通讯方式,如今已成为自动化领域的“通用语言”。

除了PLC以外,将Modbus协议与单片机结合,不仅能让单片机轻松与其他设备通讯,还能扩展其在工业场景中的应用,降低硬件成本,提高定制化能力。

本文将带你从零开始认识Modbus,并通过单片机开发的实例,助你迈出实践第一步。

2. Modbus协议基础

2.1 历史与背景

Modbus协议由Modicon公司于1979年开发,最初用于其可编程逻辑控制器(PLC)的通讯。

随着时间推移,它因免费开放和易于实现而成为工业标准。如今,Modbus已发展出多个版本,适应不同场景,如串行通讯和网络通讯。

2.2 协议类型

Modbus有以下几种常见形式:

  • Modbus RTU:采用二进制格式,适用于串行通讯(如RS-485),效率高,是工业现场的主流选择。

  • Modbus ASCII:使用ASCII字符,便于调试,但数据冗余较多。

  • Modbus TCP/IP:基于以太网的版本,适合远程访问和现代网络应用。

初学者通常从Modbus RTU入手,因为它简单直接,且硬件支持广泛,在嵌入式领域用这种类型用得很多。

2.3 通讯模式

Modbus采用主从模式:

  • 主设备(Master):如PLC、单片机或上位机,负责发起请求。

  • 从设备(Slave):如传感器或执行器,接收并响应请求。

一次通讯由主设备发送请求帧开始,从设备处理后返回响应帧。这种单向发起的机制简单可靠。

2.4 数据模型

Modbus将数据分为四种类型,存储在不同的“寄存器”中:

这些寄存器就像设备的“内存单元”,主设备通过操作它们实现数据交换。

3. Modbus RTU详解

由于Modbus RTU是工业中最常用的版本,我们深入了解它的细节。

3.1 帧结构

Modbus RTU的通讯帧由以下部分组成:

  • 地址码(1字节):从设备的地址,取值1-247。

  • 功能码(1字节):指定操作类型,如读取或写入。

  • 数据区(长度可变):包含具体的操作数据。

  • CRC校验(2字节):循环冗余校验码,确保数据无误。

例如,一个读取寄存器的请求帧可能是:

01 03 00 00 00 02 C4 0B

  • 01:从设备地址

  • 03:功能码(读保持寄存器)

  • 00 00:起始地址

  • 00 02:读取2个寄存器

  • C4 0B:CRC校验

3.2 功能码

功能码定义了主设备要执行的操作,常见的有:

3.3 CRC校验

CRC(循环冗余校验)是Modbus RTU的关键,用于检测传输错误。发送方计算CRC并附加在帧尾,接收方重新计算并比对。单片机实现时,可用查表法快速完成计算。

这个算法也挺复杂的,分CRC16、CRC32,我们一般用CRC16就够了。

而且最关键的,网上其实有现成的算法代码,我第一次接触就傻乎乎的去研究公式,走了几个星期弯路,把我气的。

4. Modbus在单片机中的应用

以下从硬件、软件和实例三个方面展开。

4.1 硬件接口

单片机与Modbus设备的连接通常依赖:

  • UART:单片机的串行通讯模块,用于发送和接收数据。

  • RS-485:工业标准接口,支持长距离、多设备通讯。

典型硬件连接:

单片机UART TX/RX -> RS-485转换器 -> Modbus网络

4.2 软件实现

在单片机上实现Modbus,可以选择:

  • 开源库:如libmodbus,提供现成函数,适合快速开发。

  • 自行编写:从协议规范入手,灵活性更高,适合学习。

我们一般都是自己写,没用过开源库的。

实现步骤包括:

  1. 配置UART(波特率、数据位等)。

  2. 解析Modbus帧(地址、功能码、数据、CRC)。

  3. 处理功能码并响应。

4.3 示例项目:STM32实现Modbus从设备

让我们通过一个实例,看看如何用STM32单片机读取传感器数据并响应Modbus请求。

硬件准备
  • STM32F103开发板

  • RS-485模块

  • 温度传感器(如DS18B20,模拟输入寄存器)

项目目标
  • 单片机作为从设备,地址为1。

  • 支持功能码03(读保持寄存器),返回温度值。

代码实现

我们以STM32为例,实现一个简单的Modbus RTU从设备,功能是响应主设备的功能码03请求,返回一个固定的温度值(25.0°C)。

以下是伪代码,仅展示核心逻辑:

#include "stm32f10x.h"

// 模拟的温度值(单位:0.1°C,例如250表示25.0°C)
//小数点不方便传输,一般我们是扩大10倍,接收端再减少10倍
uint16_t temperature = 250;

// UART发送函数(伪代码)
void uart_send(uint8_t *data, int len) {
    // 这里是发送数据的代码,具体实现根据你的单片机调整
}

// UART接收函数(伪代码)
int uart_receive(uint8_t *buffer, int *len) {
    // 这里是接收数据的代码
    // 返回1表示收到数据,0表示没收到
    return 0; // 假设暂时没收到数据
}

// CRC校验函数(简化版,实际需要完整的CRC16计算)
uint16_t calc_crc(uint8_t *data, int len) {
    // 实际项目中,这里用CRC16算法计算校验值
    // 为了简单,这里返回固定值
    return 0xFFFF;
}

// 处理Modbus请求的主函数
void modbus_task() {
    uint8_t rx_buffer[20];  // 用来存接收到的数据
    int len = 0;            // 接收到的数据长度

    // 检查有没有收到数据
    if (uart_receive(rx_buffer, &len)) {
        // 解析收到的数据
        uint8_t addr = rx_buffer[0];  // 第1个字节是地址
        uint8_t func = rx_buffer[1];  // 第2个字节是功能码

        // 只处理地址为1的请求(我们的设备地址是1)
        if (addr == 0x01) {
            // 只支持功能码03(读保持寄存器)
            if (func == 0x03) {
                // 从收到的数据中取出起始地址和寄存器数量
                uint16_t start_addr = (rx_buffer[2] << 8) | rx_buffer[3];  // 第3、4字节是起始地址
                uint16_t reg_count = (rx_buffer[4] << 8) | rx_buffer[5];   // 第5、6字节是寄存器数量

                // 假设我们只支持读取地址0的1个寄存器
                if (start_addr == 0 && reg_count == 1) {
                    // 准备发送的响应数据
                    uint8_t tx_buffer[10];  // 响应数据缓冲区
                    tx_buffer[0] = 0x01;    // 地址(我们的设备地址)
                    tx_buffer[1] = 0x03;    // 功能码(读保持寄存器)
                    tx_buffer[2] = 2;       // 返回的数据字节数(温度值占2字节)
                    tx_buffer[3] = (temperature >> 8);  // 温度值的高字节
                    tx_buffer[4] = temperature;         // 温度值的低字节

                    // 计算CRC校验值
                    uint16_t crc = calc_crc(tx_buffer, 5);  // 前5字节参与校验
                    tx_buffer[5] = crc & 0xFF;              // CRC低字节
                    tx_buffer[6] = crc >> 8;                // CRC高字节

                    // 发送响应给主设备
                    uart_send(tx_buffer, 7);  // 总共7字节
                }
            }
        }
    }
}

// 主函数
int main() {
    // 初始化UART,设置波特率为9600
    uart_init(9600);

    // 一直循环处理Modbus请求
    while (1) {
        modbus_task();
    }
}

代码解释
1. 变量定义
  • temperature:一个简单的变量,存储温度值,设为250(表示25.0°C)。我们用它来模拟Modbus的保持寄存器。

2. UART函数
  • uart_send:发送数据的函数。这里是伪代码,实际中你需要用STM32的HAL库(比如HAL_UART_Transmit)实现。

  • uart_receive:接收数据的函数。返回1表示收到数据,0表示没收到。同样是伪代码,实际用HAL库实现。

3. CRC函数
  • calc_crc:计算CRC校验值。这里为了简单,返回固定值0xFFFF。实际项目中,需要用CRC16算法(查表法或计算法)来实现。

给大家分享我产品上一直在用的CRC16校验算法:

const unsigned short wCRCTalbeAbs[] =
{
        0x0000, 0xCC01, 0xD801, 0x1400, 0xF001, 0x3C00, 0x2800, 0xE401, 0xA001, 0x6C00, 0x7800, 0xB401, 0x5000, 0x9C01, 0x8801, 0x4400,
};

unsigned short mt_api_crc16(unsigned char *ptr, unsigned int len) 
{
        unsigned short wCRC = 0xFFFF;
        unsigned short i;
        unsigned char chChar;
        unsigned char temp[2];
        for (i = 0; i < len; i++)
        {
                chChar = *ptr++;
                wCRC = wCRCTalbeAbs[(chChar ^ wCRC) & 15] ^ (wCRC >> 4);
                wCRC = wCRCTalbeAbs[((chChar >> 4) ^ wCRC) & 15] ^ (wCRC >> 4);
        }
        temp[0] = wCRC&0xFF; 
        temp[1] = (wCRC>>8)&0xFF;
        wCRC = (temp[0]<<8)|temp[1];
        return wCRC;
}

再教大家一个技巧,怎么验证CRC16校验码是否正确?

把校验码和数据再进行一次CRC16的计算,得出来新的CRC16校验码是0,就代表正确。

4. Modbus任务函数(modbus_task)

这个函数是代码的核心,处理Modbus通信的步骤:

  • 接收数据:用rx_buffer存储收到的数据。

  • 解析数据

    • addr:第1个字节是设备地址,我们只处理地址为1的请求。

    • func:第2个字节是功能码,我们只支持03(读保持寄存器)。

    • start_addr:第3、4字节表示起始地址。

    • reg_count:第5、6字节表示要读的寄存器数量。

  • 检查请求:只支持读取地址0的1个寄存器。

  • 构造响应

    • tx_buffer[0]:设备地址(1)。

    • tx_buffer[1]:功能码(3)。

    • tx_buffer[2]:返回的数据字节数(温度值占2字节)。

    • tx_buffer[3]tx_buffer[4]:温度值的高低字节。

    • tx_buffer[5]tx_buffer[6]:CRC校验值。

  • 发送响应:把7字节数据发出去。

5. 主函数(main)
  • 初始化UART,设置波特率为9600(Modbus RTU常用波特率)。

  • 无限循环调用modbus_task,不断处理请求。

测试方法
  1. 用电脑上的Modbus Poll软件模拟主设备,发送请求:

    01 03 00 00 00 01 84 0A
    1. 01:设备地址

    2. 03:功能码

    3. 00 00:起始地址0

    4. 00 01:读取1个寄存器

    5. 84 0A:CRC校验

  1. 单片机应该返回:

    01 03 02 00 FA XX XX
    1. 01:设备地址

    2. 03:功能码

    3. 02:数据字节数

    4. 00 FA:温度值250(16进制表示25.0°C)

    5. XX XX:CRC校验值(简化版是0xFFFF)

4.4 调试与测试

开发中,调试工具必不可少:

  • Modbus Poll:模拟主设备,测试请求。

  • Modbus Slave:模拟从设备,验证逻辑。

  • 串口助手:查看原始数据。

5. 常见问题与解决方案

5.1 通讯故障

  • 地址冲突:多个从设备地址相同。解决:分配唯一地址。

  • CRC错误:传输干扰或计算错误。解决:检查线路和代码。

  • 超时:从设备未响应。解决:确认电源和参数。

5.2 调试技巧

  • 用示波器检查信号。

  • 分步测试,从简单功能开始。

Modbus协议简单实用,是工业自动化的入门钥匙,如果有帮助,记得安排三连啊!


最近很多粉丝问我单片机怎么学,我根据自己从业十年经验,累积耗时一个月,精心整理一份「单

片机最佳学习路径+单片机入门到高级教程+工具包」全部无偿分享给铁粉!!!

除此以外,再含泪分享我压箱底的22个热门开源项目,包含源码+原理图+PCB+说明文档,让你迅速进阶成高手

教程资料包和详细的学习路径可以看我下面这篇文章的开头

单片机入门到高级开挂学习路径(附教程+工具)

单片机入门到高级开挂学习路径(附教程+工具)

单片机入门到高级开挂学习路径(附教程+工具)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值