cubemx modbus从机搭建

目录

1. 硬件准备

2. CubeMX 配置步骤

3. 集成 Modbus 库

4. 编写应用代码

5. 测试通信


使用 CubeMX 实现 Modbus 从机(Slave)的开发主要涉及到以下几个步骤:

1. 硬件准备

你需要一块支持 Modbus 通信的 STM32 开发板,建议使用带有 USART 外设的 STM32 MCU,因为 Modbus 通常是通过 RS485 串口通信实现的。同时,RS485 通信还需要使用相应的驱动芯片(如 MAX485)来转换电平。

2. CubeMX 配置步骤

  • 打开 CubeMX 并创建一个新的 STM32 工程。

  • 选择芯片或开发板:选择你所使用的 STM32 芯片。

  • 配置串口(USART/RS485)

    • 打开串口(比如 USART1/USART2),并将模式设置为异步(Asynchronous)。
    • 设置波特率为 Modbus 标准支持的波特率(如 9600、115200等)。
    • 启用中断模式以处理接收和发送的数据。
  • 配置 GPIO 引脚:如果使用 RS485 通信,需要配置一个控制 DE(Driver Enable)引脚来控制 RS485 驱动器的发送和接收模式。通常,RS485 驱动器有 DE 和 RE 引脚(RE 可以与 DE 连接在一起)。配置一个 GPIO 引脚输出高电平时为发送,低电平时为接收。

  • 时钟配置:确保串口所需的时钟源已正确配置。

  • 生成代码:完成配置后,点击“Project -> Generate Code”生成代码。

3. 集成 Modbus 库

实现  03 06 和 10 三个功能码 读写保持寄存器

4. 编写应用代码

modbus.c

#include "modbus.h"
#include <stdint.h>
#include "stm32f1xx_hal.h"  // 需要包含 STM32 HAL 库头文件


UART_HandleTypeDef *g_huart;

// 保持寄存器数组
uint16_t holding_registers[REGISTER_COUNT] __attribute__((aligned(2))); // 确保内存对齐
uint8_t rx_buffer[RX_BUFFER_SIZE];
uint16_t rx_index = 0; // 当前接收到的字节索引

// CRC16 查找表
uint16_t crc16_table[256];




// 初始化 CRC16 查找表
void Modbus_InitCRC16Table() {
    uint16_t crc;
    for (uint16_t i = 0; i < 256; i++) {
        crc = i;
        for (uint8_t j = 8; j > 0; j--) {
            if (crc & 0x01) crc = (crc >> 1) ^ 0xA001;
            else crc >>= 1;
        }
        crc16_table[i] = crc;
    }
}

// 计算 CRC16 校验值
uint16_t Modbus_CRC16(uint8_t *data, uint16_t length) {
    uint16_t crc = 0xFFFF;
    for (uint16_t i = 0; i < length; i++) {
        uint8_t index = (crc ^ data[i]) & 0xFF;
        crc = (crc >> 8) ^ crc16_table[index];
    }
    return crc;
}

// 发送异常响应
void Modbus_SendException(uint8_t slave_addr, uint8_t function_code, uint8_t exception_code) {
    uint8_t response[5]; // 最大需要 5 字节(3 字节 + 2 字节 CRC)
    response[0] = slave_addr;
    response[1] = function_code | 0x80; // 异常响应功能码
    response[2] = exception_code;      // 异常码
    
    // 计算 CRC
    uint16_t response_crc = Modbus_CRC16(response, 3);
    response[3] = (response_crc >> 8) & 0xFF;
    response[4] = response_crc & 0xFF;
    
    // 确保不发送额外的换行符 \r\n
    HAL_UART_Transmit(g_huart, response, 5, HAL_MAX_DELAY);
}


// 处理 Modbus RTU 帧
void Modbus_ProcessFrame(uint8_t *frame, uint16_t length) {
    if (length < 8) return; // 帧长度至少为 8 字节

    uint8_t slave_addr = frame[0];
    uint8_t function_code = frame[1];
    uint16_t start_addr = (frame[2] << 8) | frame[3];
    uint16_t quantity = (frame[4] << 8) | frame[5];
    uint16_t received_crc = (frame[length - 2] << 8) | frame[length - 1];
    uint16_t calculated_crc = Modbus_CRC16(frame, length - 2);

    if (received_crc != calculated_crc) {
        return; // CRC 校验失败
    }
    if (slave_addr != slave_Addr) return; // 假设从机地址为 1

    uint8_t response[RX_BUFFER_SIZE]; // 最大响应长度为 256
    uint16_t response_length = 0;

    uint16_t value = 0;  // 确保初始化变量
    uint8_t byte_count = 0; // 确保初始化变量

    switch (function_code) {
        case 0x03: // 读多个保持寄存器
            if (start_addr + quantity > REGISTER_COUNT) {
                Modbus_SendException(slave_addr, function_code, 0x02); // 地址越界
                return;
            }
            response[0] = slave_addr;
            response[1] = function_code;
            response[2] = quantity * 2;
            response_length = 3;
            for (uint16_t i = 0; i < quantity; i++) {
                if (response_length + 2 > sizeof(response)) {
                    Modbus_SendException(slave_addr, function_code, 0x04); // 响应长度超过限制
                    return;
                }
                response[response_length++] = (holding_registers[start_addr + i] >> 8) & 0xFF;
                response[response_length++] = holding_registers[start_addr + i] & 0xFF;
            }
            break;
        case 0x06: // 写单个保持寄存器
            if (start_addr >= REGISTER_COUNT) {
                Modbus_SendException(slave_addr, function_code, 0x02); // 地址越界
                return;
            }
            value = (frame[4] << 8) | frame[5]; // 初始化 value
            holding_registers[start_addr] = value;
					 // 构造响应
            response[0] = slave_addr;
            response[1] = function_code;
            response[2] = (start_addr >> 8) & 0xFF; // 起始地址的高字节
            response[3] = start_addr & 0xFF;        // 起始地址的低字节
            response[4] = (value >> 8) & 0xFF;      // 值的高字节
            response[5] = value & 0xFF;             // 值的低字节
            response_length = 6; // 响应长度为6个字节
            break;
        case 0x10: // 写多个保持寄存器
            if (start_addr + quantity > REGISTER_COUNT) {
                Modbus_SendException(slave_addr, function_code, 0x02); // 地址越界
                return;
            }
            byte_count = frame[6]; // 初始化 byte_count
            if (byte_count != quantity * 2) {
                Modbus_SendException(slave_addr, function_code, 0x03); // 数据值不匹配
                return;
            }
            for (uint16_t i = 0; i < quantity; i++) {
                value = (frame[7 + i * 2] << 8) | frame[7 + i * 2 + 1];
                holding_registers[start_addr + i] = value;
            }
            response[0] = slave_addr;
            response[1] = function_code;
            response[2] = (start_addr >> 8) & 0xFF;
            response[3] = start_addr & 0xFF;
            response[4] = (quantity >> 8) & 0xFF;
            response[5] = quantity & 0xFF;
            response_length = 6;
            break;
        default:
            Modbus_SendException(slave_addr, function_code, 0x01); // 非法功能码
            return;
    }

    // 检查响应是否越界
    if (response_length >= sizeof(response)) {
        Modbus_SendException(slave_addr, function_code, 0x04); // 响应长度超过限制
        return;
    }

    uint16_t response_crc = Modbus_CRC16(response, response_length);
    response[response_length++] = (response_crc >> 8) & 0xFF;
    response[response_length++] = response_crc & 0xFF;
    HAL_UART_Transmit(g_huart, response, response_length, HAL_MAX_DELAY);
}

// UART 接收中断回调
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size) {
    if (huart->Instance == USART1) {
        // 处理接收到的帧
        Modbus_ProcessFrame(rx_buffer, Size-2);//换行符占两个字节

        // 重新启动 UART 接收
        HAL_UARTEx_ReceiveToIdle_IT(huart, rx_buffer, RX_BUFFER_SIZE);
    }
}

// 初始化 Modbus RTU 从机
void Modbus_Init(UART_HandleTypeDef *huart) {
    g_huart = huart;
    // 初始化保持寄存器
    for (uint16_t i = 0; i < REGISTER_COUNT; i++) {
        holding_registers[i] = 0;
    }
    // 初始化 CRC16 查找表
    Modbus_InitCRC16Table(); 
    HAL_UARTEx_ReceiveToIdle_IT(huart, rx_buffer, RX_BUFFER_SIZE); // 空闲中断接收
}

modbus.h

#ifndef __MODBUS_H
#define __MODBUS_H

#include "stm32f1xx_hal.h"

#define REGISTER_COUNT 10      //寄存器数量
#define RX_BUFFER_SIZE 256     //串口缓冲区
#define slave_Addr 0x01        //从机地址

// 初始化 Modbus RTU 从机
void Modbus_Init(UART_HandleTypeDef *huart);

// 处理接收到的 Modbus 帧
void Modbus_ProcessFrame(uint8_t *frame, uint16_t length);

// 发送异常响应
void Modbus_SendException(uint8_t slave_addr, uint8_t function_code, uint8_t exception_code);

// CRC16 计算
uint16_t Modbus_CRC16(uint8_t *data, uint16_t length);

// 初始化 CRC16 查找表
void Modbus_InitCRC16Table(void);

#endif

 main.c

int main(void)
{

  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_USART1_UART_Init();

  // 初始化 Modbus RTU 从机
  Modbus_Init(&huart1);



  while (1)
  {
   
  }

}

5. 测试通信

链接: https://pan.baidu.com/s/1oVUe8hagQnIaZ-fW1dU9uA?pwd=yyka 提取码: yyka 复制这段内容后打开百度网盘手机App,操作更方便哦

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

chem4111

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值