ModbusCRC16校验 示例代码

简介: 本文提供了ModbusCRC16校验的示例代码,包括计算CRC、添加CRC校验位、删除CRC校验位和比较CRC校验位等四种常用函数的实现,以及一个完整的示例代码。这些代码在Ubuntu20.04环境下经过测试并确认有效。示例代码采用C++编写,展示了如何使用这些函数来处理Modbus通信中的数据校验。

作者: Herman Ye @Galbot @Auromix
测试环境: Ubuntu20.04
更新日期: 2023/08/30
注1: @Auromix 是一个机器人爱好者开源组织。
注2: 本文在更新日期经过测试,确认有效。

笔者出于学习交流目的,
给出以下ModbusCRC16校验常用的四种函数以及完整示例代码:

1.计算CRC

注意: 此处在末尾进行了高低位交换,可根据需求删减代码交换高低位顺序。

/**
 * @brief Calculate the Modbus CRC-16 checksum for a given data vector.
 *
 * This function calculates the CRC-16 checksum using the Modbus protocol
 * polynomial for the provided data vector.
 *
 * @param data The vector of bytes for which to calculate the CRC-16 checksum.
 * @return The calculated CRC-16 checksum value as an unsigned short.
 */
unsigned short calculateModbusCRC16(const vector<uint8_t> &data)
{
   
    int length = data.size();
    unsigned short CRC = 0xFFFF; // Initial value
    for (int i = 0; i < length; i++)
    {
   
        CRC = CRC ^ data[i]; // XOR byte into the least significant byte of CRC
        for (int j = 0; j < 8; j++)
        {
   
            if (CRC & 1)
            {
   
                CRC >>= 1;
                CRC ^= 0xA001;
            }
            else
            {
   
                CRC >>= 1;
            }
        }
    }
    // Swap the bytes of the CRC to match Modbus convention
    unsigned short swappedCRC = ((CRC >> 8) & 0xFF) | ((CRC & 0xFF) << 8);
    return swappedCRC;
}

2.添加CRC校验位

注意: 此处进行了高低位交换,可根据需求删减代码交换高低位顺序。

/**
 * @brief Add Modbus CRC-16 to a data vector.
 * 
 * This function calculates the Modbus CRC-16 checksum for the provided data vector
 * and appends the high and low bytes of the CRC to the end of the data vector.
 * 
 * @param data The data vector to which the CRC will be added.
 */
void addModbusCRC16(vector<uint8_t> &data)
{
   
    // Calculate the CRC-16 checksum
    unsigned short crc = calculateModbusCRC16(data);

    // Append the high byte of CRC to the data vector
    data.push_back((crc >> 8) & 0xFF);

    // Append the low byte of CRC to the data vector
    data.push_back(crc & 0xFF);
}

3.删除CRC校验位

/**
 * @brief Remove Modbus CRC-16 from a vector of data.
 * 
 * This function takes a vector of data with Modbus CRC-16 at the end and removes
 * the CRC-16 bytes from the end of the vector, effectively stripping the CRC-16
 * checksum from the data.
 * 
 * @param dataWithCRC A reference to the vector containing the data with CRC-16.
 * 
 * @note This function does not perform any CRC-16 validation or calculation. It
 * assumes that the last two bytes of the vector represent the CRC-16 checksum
 * and removes them regardless of their validity.
 * 
 * @warning It is the responsibility of the caller to ensure that the input vector
 * has a length of at least 2, as this function does not perform length checking.
 * If the length is less than 2, an error message is printed to the standard error
 * stream, and no modifications are made to the input vector.
 */
void removeModbusCRC16(vector<uint8_t> &dataWithCRC) {
   
    int length = dataWithCRC.size();
    // Error check
    if (length < 2) {
   
        cerr << "Invalid data length" << endl;
        return;
    }
    // Delete CRC at the end
    dataWithCRC.resize(length - 2);
}

4.比较CRC校验位

/**
 * @brief Compare Modbus CRC-16
 * 
 * This function compares the CRC-16 checksum in the given data with the calculated CRC-16 checksum
 * of the data without the CRC bytes. If they match, it indicates that the data is intact and has not
 * been corrupted during transmission.
 * 
 * @param dataWithCRC A vector containing the data along with the CRC-16 checksum bytes.
 * @return True if the calculated CRC-16 matches the original CRC-16, indicating data integrity.
 *         False if the data length is invalid or if the CRCs do not match.
 */
bool compareModbusCRC16(const vector<uint8_t> &dataWithCRC) {
   
    int length = dataWithCRC.size();
    // Error check
    if (length < 2) {
   
        cerr << "Invalid data length" << endl;
        return false;
    }

    // Get data without CRC
    vector<uint8_t> dataWithoutCRC(dataWithCRC.begin(), dataWithCRC.end() - 2);

    // Calculate CRC-16 checksum
    unsigned short calculatedCRC = calculateModbusCRC16(dataWithoutCRC);

    // Get original CRC-16 checksum from the last two bytes of the data
    unsigned short originalCRC = (dataWithCRC[length - 2] << 8) | dataWithCRC[length - 1];

    return originalCRC == calculatedCRC;
}

5.完整示例代码

#include <iostream>
#include <vector>
#include <iomanip>
using namespace std;


/**
 * @brief Calculate the Modbus CRC-16 checksum for a given data vector.
 *
 * This function calculates the CRC-16 checksum using the Modbus protocol
 * polynomial for the provided data vector.
 *
 * @param data The vector of bytes for which to calculate the CRC-16 checksum.
 * @return The calculated CRC-16 checksum value as an unsigned short.
 */
unsigned short calculateModbusCRC16(const vector<uint8_t> &data)
{
   
    int length = data.size();
    unsigned short CRC = 0xFFFF; // Initial value
    for (int i = 0; i < length; i++)
    {
   
        CRC = CRC ^ data[i]; // XOR byte into the least significant byte of CRC
        for (int j = 0; j < 8; j++)
        {
   
            if (CRC & 1)
            {
   
                CRC >>= 1;
                CRC ^= 0xA001;
            }
            else
            {
   
                CRC >>= 1;
            }
        }
    }
    // Swap the bytes of the CRC to match Modbus convention
    unsigned short swappedCRC = ((CRC >> 8) & 0xFF) | ((CRC & 0xFF) << 8);
    return swappedCRC;
}


/**
 * @brief Add Modbus CRC-16 to a data vector.
 * 
 * This function calculates the Modbus CRC-16 checksum for the provided data vector
 * and appends the high and low bytes of the CRC to the end of the data vector.
 * 
 * @param data The data vector to which the CRC will be added.
 */
void addModbusCRC16(vector<uint8_t> &data)
{
   
    // Calculate the CRC-16 checksum
    unsigned short crc = calculateModbusCRC16(data);

    // Append the high byte of CRC to the data vector
    data.push_back((crc >> 8) & 0xFF);

    // Append the low byte of CRC to the data vector
    data.push_back(crc & 0xFF);
}

/**
 * @brief Remove Modbus CRC-16 from a vector of data.
 * 
 * This function takes a vector of data with Modbus CRC-16 at the end and removes
 * the CRC-16 bytes from the end of the vector, effectively stripping the CRC-16
 * checksum from the data.
 * 
 * @param dataWithCRC A reference to the vector containing the data with CRC-16.
 * 
 * @note This function does not perform any CRC-16 validation or calculation. It
 * assumes that the last two bytes of the vector represent the CRC-16 checksum
 * and removes them regardless of their validity.
 * 
 * @warning It is the responsibility of the caller to ensure that the input vector
 * has a length of at least 2, as this function does not perform length checking.
 * If the length is less than 2, an error message is printed to the standard error
 * stream, and no modifications are made to the input vector.
 */
void removeModbusCRC16(vector<uint8_t> &dataWithCRC) {
   
    int length = dataWithCRC.size();
    // Error check
    if (length < 2) {
   
        cerr << "Invalid data length" << endl;
        return;
    }
    // Delete CRC at the end
    dataWithCRC.resize(length - 2);
}

/**
 * @brief Compare Modbus CRC-16
 * 
 * This function compares the CRC-16 checksum in the given data with the calculated CRC-16 checksum
 * of the data without the CRC bytes. If they match, it indicates that the data is intact and has not
 * been corrupted during transmission.
 * 
 * @param dataWithCRC A vector containing the data along with the CRC-16 checksum bytes.
 * @return True if the calculated CRC-16 matches the original CRC-16, indicating data integrity.
 *         False if the data length is invalid or if the CRCs do not match.
 */
bool compareModbusCRC16(const vector<uint8_t> &dataWithCRC) {
   
    int length = dataWithCRC.size();
    // Error check
    if (length < 2) {
   
        cerr << "Invalid data length" << endl;
        return false;
    }

    // Get data without CRC
    vector<uint8_t> dataWithoutCRC(dataWithCRC.begin(), dataWithCRC.end() - 2);

    // Calculate CRC-16 checksum
    unsigned short calculatedCRC = calculateModbusCRC16(dataWithoutCRC);

    // Get original CRC-16 checksum from the last two bytes of the data
    unsigned short originalCRC = (dataWithCRC[length - 2] << 8) | dataWithCRC[length - 1];

    return originalCRC == calculatedCRC;
}

int main() {
   
    // Example data 1
    vector<uint8_t> deviceData1 = {
   
        0x01, 0x10, 0x00, 0x02, 0x00, 0x06, 0x0C, 0x41, 0x20,
        0x00, 0x00, 0x42, 0xC8, 0x00, 0x00, 0x42, 0x48, 0x00, 0x00,0x84, 0xC1
    }; // Example CRC: 0x84, 0xC1

    // Print original data
    cout << "Original data 1: ";
    for (uint8_t byte : deviceData1) {
   
        cout << hex << uppercase << setw(2) << setfill('0') << (int)byte << " ";
    }
    cout << endl;
    bool comparedResult=compareModbusCRC16(deviceData1);
    if (comparedResult)
        cout<<"Compared result: "<<"TRUE"<<endl;
    else
        cout<<"Compared result: "<<"FALSE"<<endl;

    // Example data 2
    cout<<endl;
    vector<uint8_t> deviceData2 = {
   
        0x01, 0x06, 0x00, 0x00, 0x01, 0x02, 0x02
    };// Example CRC: 0xDA, 0xC7

    cout << "Original data 2: ";
    for (uint8_t byte : deviceData2) {
   
        cout << hex << uppercase << setw(2) << setfill('0') << (int)byte << " ";
    }
    cout << endl;

    // Add CRC and print modified data
    addModbusCRC16(deviceData2);
    cout << "Add CRC to original data 2: ";
    for (uint8_t byte : deviceData2) {
   
        cout << hex << uppercase << setw(2) << setfill('0') << (int)byte << " ";
    }
    cout << endl;

    // Remove CRC from data
    removeModbusCRC16(deviceData2);
    cout << "Remove CRC from modified data 2: ";
    for (uint8_t byte : deviceData2) {
   
        cout << hex << uppercase << setw(2) << setfill('0') << (int)byte << " ";
    }
    cout << endl;    
    return 0;
}
相关实践学习
基于Hologres轻量实时的高性能OLAP分析
本教程基于GitHub Archive公开数据集,通过DataWorks将GitHub中的项⽬、行为等20多种事件类型数据实时采集至Hologres进行分析,同时使用DataV内置模板,快速搭建实时可视化数据大屏,从开发者、项⽬、编程语⾔等多个维度了解GitHub实时数据变化情况。
阿里云实时数仓实战 - 用户行为数仓搭建
课程简介 1)学习搭建一个数据仓库的过程,理解数据在整个数仓架构的从采集、存储、计算、输出、展示的整个业务流程。 2)整个数仓体系完全搭建在阿里云架构上,理解并学会运用各个服务组件,了解各个组件之间如何配合联动。 3&nbsp;)前置知识要求:熟练掌握 SQL 语法熟悉 Linux 命令,对 Hadoop 大数据体系有一定的了解 &nbsp; 课程大纲 第一章&nbsp;了解数据仓库概念 初步了解数据仓库是干什么的 第二章&nbsp;按照企业开发的标准去搭建一个数据仓库 数据仓库的需求是什么 架构 怎么选型怎么购买服务器 第三章&nbsp;数据生成模块 用户形成数据的一个准备 按照企业的标准,准备了十一张用户行为表 方便使用 第四章&nbsp;采集模块的搭建 购买阿里云服务器 安装 JDK 安装 Flume 第五章&nbsp;用户行为数据仓库 严格按照企业的标准开发 第六章&nbsp;搭建业务数仓理论基础和对表的分类同步 第七章&nbsp;业务数仓的搭建&nbsp; 业务行为数仓效果图&nbsp;&nbsp;
目录
相关文章
|
存储 开发框架 算法
【串口通信】使用C++和Qt设计和实现串口协议解析器(一)
【串口通信】使用C++和Qt设计和实现串口协议解析器
3217 0
|
传感器 Linux
在Linux中使用libmodbus库进行Modbus RTU主从机通信
Modbus RTU是一种常见的工业通信协议,用于在自动化系统中传输数据。libmodbus是一个流行的C库,用于在Linux系统上实现Modbus通信。本文将介绍如何使用libmodbus库在Linux上创建Modbus RTU主从机通信的示例代码。
5882 0
|
存储 传感器 移动开发
嵌入式系统中详解 Modbus 通信协议(清晰易懂)
嵌入式系统中详解 Modbus 通信协议(清晰易懂)
3763 1
|
12月前
|
C#
LiveCharts 直方图详解,安装和使用,以及常用属性的说明
本文介绍了LiveCharts在WPF中的应用,包括安装方法、基本使用和直方图(LineSeries)的常用属性说明。安装LiveCharts通过NuGet包管理器进行,使用时需在XAML文件中引入相应的命名空间。文章还提供了直方图的属性详解和综合示例,包括线条样式、坐标轴标签、图例位置等设置,以及如何自定义数据点形状。
LiveCharts 直方图详解,安装和使用,以及常用属性的说明
|
调度 开发者
【Freertos基础入门】2个Freertos的Delay函数
【Freertos基础入门】2个Freertos的Delay函数
1523 1
|
测试技术 C# 开发者
“代码守护者:详解WPF开发中的单元测试策略与实践——从选择测试框架到编写模拟对象,全方位保障你的应用程序质量”
【8月更文挑战第31天】单元测试是确保软件质量的关键实践,尤其在复杂的WPF应用中更为重要。通过为每个小模块编写独立测试用例,可以验证代码的功能正确性并在早期发现错误。本文将介绍如何在WPF项目中引入单元测试,并通过具体示例演示其实施过程。首先选择合适的测试框架如NUnit或xUnit.net,并利用Moq模拟框架隔离外部依赖。接着,通过一个简单的WPF应用程序示例,展示如何模拟`IUserRepository`接口并验证`MainViewModel`加载用户数据的正确性。这有助于确保代码质量和未来的重构与扩展。
614 0
|
容器
Qt6学习笔记七(ToolButton、RadioButton、GroupBox、CheckBox、ListWidget、TreeWidget、TableWidget)
Qt6学习笔记七(ToolButton、RadioButton、GroupBox、CheckBox、ListWidget、TreeWidget、TableWidget)
715 0
|
API UED
【Qt 学习笔记】Qt窗口 | 状态栏 | QStatusBar的使用及说明
【Qt 学习笔记】Qt窗口 | 状态栏 | QStatusBar的使用及说明
1922 4
|
SQL 数据库 数据库管理
Qt操作Sqlite类封装,及命令行导入csv文件到Sqlite数据库
Qt操作Sqlite类封装,及命令行导入csv文件到Sqlite数据库