基于STM32脉冲电源通信的Modbus RTU协议深度解析与实战案例

基于 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 读寄存器帧

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

    从站地址 功能代码 首寄存器地址 寄存器数 N CRC16
    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 写寄存器返回帧

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

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

    写寄存器返回帧示例:

    01H    10H    0005H    0005H    CrcL, CrcH
    

    四、功能代码

    4.1 功能代码列表

    功能代码 ModBus 名 功能名 广播 数量
    03H Read Holding Registers 读 N 个寄存器值 No 5
    10H Write Multiple Registers 写 N 个寄存器值 No 5

    4.2 功能代码说明

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

    5.1 寄存器地址表内容

    编号 参数符号 参数名 地址 类型 数据类型 数值范围 备注
    1 V 电压显示值 0 16 位无符号(World) 0 – 1200 (10 进制) 电压有一位小数点,读到 800 代表 80.0V
    2 I 电流显示值 1 16 位无符号(World) 0 – 2000 (10 进制) 电流有 2 个小数点,读 1500 代表 15.00A
    3 状态显示 2 16 位无符号(World) 0 – 4 (10 进制) 0——正常 1——过热
    2——过流 3——其它
    4 F 频率显示 3 16 位无符号(World) 额定值范围内(10 进制) 读到 6000 代表 6000HZ
    5 D 占空比显示 4 16 位无符号(World) 0 – 100 (10 进制) 读到 100 代表 100%
    6 FV 电压设置值 5 16 位无符号(World) 0 – 1200 (10 进制) 电压有一位小数点,写入 1000 代表 100.0V
    7 FI 电流设置值 6 16 位无符号(World) 0 – 2000 (10 进制) 电流有 2 个小数点,写入 1000 代 10.00A
    8 FF 频率设置 7 16 位无符号(World) 写入 3000 代表 3000HZ
    9 FD 占空比设置 8 16 位无符号(World) 0 – 100 写入 50 代表 50%
    10 RUN 启停命令 9 16 位无符号(World) 0 – 1 0 – 关机 1 – 开机
    11 备用 10 16 位无符号(World)
    12 VC/CC 稳压稳流 11 16 位无符号(World) 0 – 1 0 – 稳流 1 – 稳压
    13 脉冲个数设置 12 16 位无符号(World) 1 – 60000 写入 200 代表 200 个
    14 间隔时间 13 16 位无符号(World) 1 – 99999 写入 100 代表 100 秒
    15 循环次数 14 16 位无符号(World) 1 – 99999 写入 100 代表 100 次
    16 脉冲启动 15 16 位无符号(World) 0 – 1 0 – 关闭脉冲 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 协议以其简单、可靠的特点,为工业设备之间的通信提供了有效的解决方案。在实际应用中,需要根据具体需求进行通信参数的设置和错误处理,以确保通信的稳定性和准确性。同时,通过合理的硬件设计和软件编程,可以实现更复杂的工业控制和自动化应用。

    作者:#金毛

    物联沃分享整理
    物联沃-IOTWORD物联网 » 基于STM32脉冲电源通信的Modbus RTU协议深度解析与实战案例

    发表回复