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

Modbus RTU协议详解:基于 STM32 与脉冲电源的通信项目实例

基于 STM32 与脉冲电源的 Modbus RTU 通信项目详解

本文内容是工作中实际遇到的一个项目

一、引言

在工业控制和自动化领域,设备之间的可靠通信至关重要。Modbus RTU 协议作为一种广泛应用的串行通信协议,因其简单、高效且开放的特性,成为连接各种工业设备的理想选择。本文将围绕使用 STM32 单片机与脉冲电源通过 Modbus RTU 协议进行通信的项目展开,详细介绍 Modbus RTU 协议的原理、通信设置、消息帧格式、功能代码以及寄存器地址表等内容,并结合实际项目进行说明。

二、Modbus RTU 协议概述

2.1 基本概念

Modbus 是一种主 - 从式协议,在本项目中,上位机作为主站发起通信请求,整流机(脉冲电源)作为从站进行应答。采用 RTU(十六进制数)传输模式,数据以二进制代码形式传输,通过 CRC16 循环冗余校验确保数据传输的准确性。

2.2 通信口设置

2.2.1 通讯方式

采用异步串行通讯接口 RS - 485,这种接口具有抗干扰能力强、传输距离远等优点,适合工业现场环境。

2.2.2 波特率

默认值为 19200bps,且可在 4800 - 115200 的范围内进行设置。波特率决定了数据传输的速率,需要根据实际通信距离和设备性能进行选择。

2.2.3 字节数据格式
  • 起始位:1 位,用于标识数据帧的开始。
  • 数据位:可设置为 7 位或 8 位,本项目默认采用 8 位数据位,能传输更多的数据信息。
  • 停止位:可设置为 1 位或 2 位,本项目默认采用 1 位停止位,用于标识数据帧的结束。
  • 校验位:可设置为偶校验、无校验或奇校验,本项目默认采用偶校验,用于检测数据传输过程中是否发生错误。

字节数据格式如下:

1	*	*	*	*	*	*	*	*	1	*
起始位               数据位(从低到高)                    校验位   停止位

三、消息帧格式

3.1 读寄存器帧

主站向从站发送读寄存器请求时,消息帧格式如下:

从站地址功能代码首寄存器地址寄存器数 NCRC16
1 字节1 字节2 字节2 字节2 字节

例如,要读取从站地址为 01H 的设备中,从地址 0000H 开始的 5 个寄存器的值,读寄存器帧如下:

01H    03H    0000H    0005H    CrcL, CrcH

3.2 读寄存器返回帧

从站接收到读寄存器请求并处理后,向主站返回的消息帧格式如下:

从站地址功能代码字节数寄存器数据CRC16
1 字节1 字节1 字节N * 2 字节2 字节

假设成功读取 5 个寄存器的值,读寄存器返回帧如下:

01H    03H    0AH    [寄存器数据]    CrcL, CrcH

其中,字节数为 0AH(即 10 字节,因为每个寄存器占 2 字节,5 个寄存器共 10 字节)。

3.3 写寄存器帧

主站向从站发送写寄存器请求时,消息帧格式如下:

从站地址功能代码首寄存器地址寄存器数 N字节数寄存器数据CRC16
1 字节1 字节2 字节2 字节1 字节N * 2 字节2 字节

例如,要向从站地址为 01H 的设备中,从地址 0005H 开始的 5 个寄存器写入数据,写寄存器帧如下:

01H    10H    0005H    0005H    0AH    [寄存器数据]    CrcL, CrcH

3.4 写寄存器返回帧

从站接收到写寄存器请求并处理成功后,向主站返回的消息帧格式如下:

从站地址功能代码首寄存器地址寄存器数 NCRC16
1 字节1 字节2 字节2 字节2 字节

写寄存器返回帧示例:

01H    10H    0005H    0005H    CrcL, CrcH

四、功能代码

4.1 功能代码列表

功能代码ModBus 名功能名广播数量
03HRead Holding Registers读 N 个寄存器值No5
10HWrite Multiple Registers写 N 个寄存器值No5

4.2 功能代码说明

  • 03H:用于主站向从站读取多个保持寄存器的值。主站指定起始寄存器地址和要读取的寄存器数量,从站将相应寄存器的值返回给主站。
  • 10H:用于主站向从站写入多个保持寄存器的值。主站指定起始寄存器地址、要写入的寄存器数量和具体的寄存器数据,从站将数据写入相应的寄存器。

五、寄存器地址表

5.1 寄存器地址表内容

编号参数符号参数名地址类型数据类型数值范围备注
1V电压显示值016 位无符号(World)0 - 1200 (10 进制)电压有一位小数点,读到 800 代表 80.0V
2I电流显示值116 位无符号(World)0 - 2000 (10 进制)电流有 2 个小数点,读 1500 代表 15.00A
3状态显示216 位无符号(World)0 - 4 (10 进制)0——正常 1——过热
2——过流 3——其它
4F频率显示316 位无符号(World)额定值范围内(10 进制)读到 6000 代表 6000HZ
5D占空比显示416 位无符号(World)0 - 100 (10 进制)读到 100 代表 100%
6FV电压设置值516 位无符号(World)0 - 1200 (10 进制)电压有一位小数点,写入 1000 代表 100.0V
7FI电流设置值616 位无符号(World)0 - 2000 (10 进制)电流有 2 个小数点,写入 1000 代 10.00A
8FF频率设置716 位无符号(World)写入 3000 代表 3000HZ
9FD占空比设置816 位无符号(World)0 - 100写入 50 代表 50%
10RUN启停命令916 位无符号(World)0 - 10 - 关机 1 - 开机
11备用1016 位无符号(World)
12VC/CC稳压稳流1116 位无符号(World)0 - 10 - 稳流 1 - 稳压
13脉冲个数设置1216 位无符号(World)1 - 60000写入 200 代表 200 个
14间隔时间1316 位无符号(World)1 - 99999写入 100 代表 100 秒
15循环次数1416 位无符号(World)1 - 99999写入 100 代表 100 次
16脉冲启动1516 位无符号(World)0 - 10 - 关闭脉冲 1 - 启动脉冲

5.2 寄存器地址表说明

  • 数据类型:所有寄存器的数据类型均为 16 位无符号整型(两字节)。
  • 小数点处理:通信传输中带小数点的数据全部用整数代替,例如 27.9 代替为 279。
  • 数据传输顺序:全部寄存器数据在传输过程中用十六进制数表示,先传高字节,再传低字节。例如,传送 279 时,先传 01H,再传 17H。

六、CRC16 校验

6.1 校验原理

CRC16 是一种循环冗余校验方法,用于检测数据在传输过程中是否发生错误。在本项目中,CRC16 校验从从站地址到数据区最后一个字节,计算多项式码为 A001(hex)。

6.2 校验计算示例

在 STM32 中实现 CRC16 校验计算可以通过以下代码示例:

#include <stdint.h>

// CRC16 计算函数
uint16_t crc16(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;
}

使用示例:

uint8_t message[] = {0x01, 0x03, 0x00, 0x00, 0x00, 0x05};
uint16_t crc = crc16(message, sizeof(message));
uint8_t crcL = (uint8_t)(crc & 0xFF);
uint8_t crcH = (uint8_t)(crc >> 8);

七、STM32 + KEIL5 + 固件库举例说明 ModbusRTU 协议

以下是使用STM32标准固件库实现Modbus RTU协议通信的详细步骤和代码示例。

1. 硬件连接

与使用HAL库时的硬件连接方式相同,将STM32的串口(如USART1)与RS - 485转换器连接,RS - 485转换器的A、B线连接到Modbus RTU从站设备的对应接口,同时确保所有设备共地。

2. 工程准备

在Keil等开发环境中创建一个基于STM32标准固件库的工程,将标准固件库文件添加到工程中。

3. 代码实现

3.1 串口初始化

使用标准固件库函数对串口进行初始化,设置通信参数。

#include "stm32f10x.h"

// 串口初始化函数
void USART1_Init(void) {
    GPIO_InitTypeDef GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

    // 使能GPIOA和USART1时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE);

    // 配置USART1 Tx (PA9)为复用推挽输出
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    // 配置USART1 Rx (PA10)为浮空输入
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    // USART1配置
    USART_InitStructure.USART_BaudRate = 19200;
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
    USART_InitStructure.USART_Parity = USART_Parity_No;
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
    USART_Init(USART1, &USART_InitStructure);

    // 使能USART1接收中断
    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);

    // 使能USART1
    USART_Cmd(USART1, ENABLE);

    // 配置NVIC
    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}
3.2 CRC16校验函数

实现CRC16校验函数,用于计算和验证消息帧的CRC校验值。

#include <stdint.h>

// CRC16计算函数
uint16_t crc16(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;
}
3.3 发送读寄存器请求

编写函数来发送读寄存器请求消息帧,并计算CRC校验值。

// 发送读寄存器请求
void sendReadRequest(uint8_t slaveAddress, uint16_t startAddress, uint16_t registerCount) {
    uint8_t request[8];
    request[0] = slaveAddress;
    request[1] = 0x03;
    request[2] = (uint8_t)(startAddress >> 8);
    request[3] = (uint8_t)(startAddress & 0xFF);
    request[4] = (uint8_t)(registerCount >> 8);
    request[5] = (uint8_t)(registerCount & 0xFF);

    uint16_t crc = crc16(request, 6);
    request[6] = (uint8_t)(crc & 0xFF);
    request[7] = (uint8_t)(crc >> 8);

    for (int i = 0; i < 8; i++) {
        while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
        USART_SendData(USART1, request[i]);
    }
}
3.4 接收并处理响应

在串口接收中断服务函数中接收从站的响应消息帧,并验证CRC校验值。

#define BUFFER_SIZE 256
uint8_t receiveBuffer[BUFFER_SIZE];
uint8_t receiveIndex = 0;

// 串口1中断服务函数
void USART1_IRQHandler(void) {
    if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) {
        receiveBuffer[receiveIndex++] = USART_ReceiveData(USART1);

        // 这里可以根据实际情况添加接收完成判断条件
        // 例如,根据消息帧长度判断

        USART_ClearITPendingBit(USART1, USART_IT_RXNE);
    }
}

// 处理接收到的响应
void processResponse() {
    // 验证CRC校验
    uint16_t receivedCrc = (uint16_t)(receiveBuffer[receiveIndex - 2]) | ((uint16_t)(receiveBuffer[receiveIndex - 1]) << 8);
    uint16_t calculatedCrc = crc16(receiveBuffer, receiveIndex - 2);

    if (receivedCrc == calculatedCrc) {
        // 处理响应数据
        // 这里可以根据响应消息帧的格式解析数据
        // 例如,获取寄存器数据等
    } else {
        // CRC校验失败
    }
    receiveIndex = 0; // 清空接收缓冲区
}

4. 主函数调用示例

int main(void) {
    USART1_Init();

    // 发送读寄存器请求
    sendReadRequest(0x01, 0x0000, 0x0005);

    while (1) {
        // 可以在合适的时机调用processResponse函数处理接收到的响应
        if (receiveIndex > 0) {
            processResponse();
        }
    }
}

5. 关键部分解释

  • 串口初始化USART1_Init 函数使用标准固件库的GPIO和USART初始化函数对串口进行配置,设置波特率、数据位、停止位、校验位等参数,并使能接收中断。
  • CRC16校验crc16 函数根据Modbus RTU协议的规则计算CRC校验值,用于保证消息帧的完整性。
  • 消息帧发送sendReadRequest 函数构建读寄存器请求消息帧,计算CRC校验值,并通过串口发送出去。
  • 消息帧接收:在 USART1_IRQHandler 中断服务函数中接收从站的响应消息帧,将数据存储在 receiveBuffer 中。processResponse 函数用于处理接收到的响应,验证CRC校验值并解析数据。

通过以上步骤,可以使用STM32标准固件库实现基于Modbus RTU协议的通信。需要注意的是,实际应用中可能需要根据具体情况对代码进行调整和优化,例如添加超时处理、错误处理等功能。

八、总结

通过本项目,深入了解 Modbus RTU 协议的原理、通信设置、消息帧格式、功能代码和寄存器地址表等内容,并使用 STM32 单片机实现与脉冲电源的通信。Modbus RTU 协议以其简单、可靠的特点,为工业设备之间的通信提供了有效的解决方案。在实际应用中,需要根据具体需求进行通信参数的设置和错误处理,以确保通信的稳定性和准确性。同时,通过合理的硬件设计和软件编程,可以实现更复杂的工业控制和自动化应用。

相关文章:

  • vscode关闭仓库后如何打开
  • Adobe Genuine Service Alert 一直弹窗,老是一直弹窗【解决方法】
  • 微信小程序:实现多功能表格效果,例如滚动效果、宽度自定义、多选、行内编辑等功能
  • PostgreSQL16 的双向逻辑复制
  • Android实现简易计算器
  • Go执行当前package下的所有方法
  • 侯捷C++课程学习笔记:详解智能指针(三)
  • Feign中@RequestBody 与 @RequestParam 的区别
  • Vue3:组件通信方式
  • 暴力破解Excel受保护的单元格密码
  • 大数据学习(59)-DataX执行机制
  • 云原生性能测试全解析:如何构建高效稳定的现代应用?
  • 【数据结构】-哈夫曼树以及其应用
  • 基于ESP32的桌面小屏幕实战[8]:任务创建
  • package.json 依赖包约束及快速删除node_modules
  • 【GOOGLE插件】chrome.runtime.sendNativeMessage与本地应用交互
  • 爬虫案例十三js逆向模拟登录中大网校
  • 使用OpenCV和MediaPipe库——抽烟检测(姿态监控)
  • 【大模型技术】怎么用agent和prompt工程实现用户的要求?
  • c++ 中的float和double 的区别 开发过程中使用哪个更好
  • 19国入境团抵沪并游玩,老外震惊:“怎么能有这么多人?”
  • 浙江一教师被指殴打并威胁小学生,教育局通报涉事人被行拘
  • 一个留美学生的思想转向——裘毓麐的《游美闻见录》及其他
  • 新修订的《餐饮业促进和经营管理办法》公布,商务部解读
  • 探秘多维魅力,长江经济带、珠三角媒体总编辑岳阳行启动
  • 对谈|“大礼议”:嘉靖皇帝的礼法困境与权力博弈