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

stm32F103RCT6+ModBus之RTU

Modbus通信协议简介

1. Modbus概述

Modbus是一种广泛应用于工业控制领域的通信协议,由Modicon公司(现为施耐德电气)于1979年发明。它是一种开放的、基于主从架构的通信协议,被广泛应用于工业自动化、楼宇自动化、能源管理等领域。Modbus具有以下特点:

  • 简单且健壮:协议结构简单,易于实现,且通信可靠
  • 开放性:作为一种公开的工业标准,不需要支付版权费用
  • 少量的报文类型:基本的报文类型较少,易于学习和使用
  • 主从架构:明确的主站和从站关系,避免冲突
  • 支持多种物理层:如RS-232、RS-485、TCP/IP等

2. Modbus通信模式

Modbus协议支持多种通信模式,主要包括:

  1. Modbus RTU:基于串行通信(如RS-232/RS-485),使用二进制编码方式,以提高传输效率。
  2. Modbus ASCII:基于串行通信,使用ASCII编码方式,易于调试但传输效率较低。
  3. Modbus TCP/IP:基于以太网通信,将Modbus帧封装在TCP报文中传输。

本项目主要实现的是Modbus RTU模式。

3. Modbus数据模型

Modbus定义了四种基本的数据表,每个表可能对应不同类型的对象:

数据表对象类型访问描述
线圈(Coils)单个位读/写离散输出量,可读可写
离散输入(Discrete Inputs)单个位只读离散输入量,只读
保持寄存器(Holding Registers)16位字读/写可读可写的16位寄存器
输入寄存器(Input Registers)16位字只读只读的16位寄存器

4. Modbus功能码

Modbus使用功能码来指示要执行的操作,常用功能码包括:

功能码描述操作的数据类型
0x01读线圈线圈(离散输出)
0x02读离散输入离散输入
0x03读保持寄存器保持寄存器
0x04读输入寄存器输入寄存器
0x05写单个线圈线圈
0x06写单个寄存器保持寄存器
0x0F写多个线圈线圈
0x10写多个寄存器保持寄存器

5. Modbus RTU帧格式

Modbus RTU的帧格式如下:

[从站地址] [功能码] [数据] [CRC校验(低字节)] [CRC校验(高字节)]
  • 从站地址:占1字节,范围0~247,其中0为广播地址
  • 功能码:占1字节,表示要执行的操作
  • 数据:长度不定,根据功能码不同而不同
  • CRC校验:占2字节,用于错误检测

6. 本项目实现的功能

本项目基于STM32F103RCT6实现了Modbus RTU从站功能,包括:

  1. 支持所有标准Modbus功能码(01、02、03、04、05、06、0F、10)
  2. 实现了线圈、离散输入、保持寄存器和输入寄存器四种数据类型
  3. 使用UART2(PA2/PA3)作为Modbus通信接口,默认波特率9600,8位数据位,无校验,1位停止位
  4. 从站地址为0x01

7. 如何使用

7.1 硬件连接

将Modbus总线连接到STM32的PA2(TX)和PA3(RX)引脚。如果使用RS-485通信,需要外接RS-485转换芯片(如MAX485)。

7.2 Modbus寄存器和线圈定义

本项目中定义的Modbus数据模型默认值如下:

  • 线圈(地址范围0-63):默认全部为0
  • 离散输入(地址范围0-63):默认全部为0
  • 保持寄存器(地址范围0-31):
    • 寄存器0 = 0x1234
    • 寄存器1 = 0x5678
    • 其余默认为0
  • 输入寄存器(地址范围0-31):
    • 寄存器0 = 0xABCD
    • 寄存器1 = 0xEF01
    • 其余默认为0

可以根据实际需求在modbus_rtu.c的Modbus_Init函数中修改这些默认值。

7.3 测试方法

可以使用Modbus调试工具(如Modbus Poll、ModScan等)作为主站,连接到STM32进行测试。

测试时请注意以下配置:

  • 通信参数:9600,8,N,1
  • 从站地址:1
  • Modbus模式:RTU

8. 扩展与修改

如需扩展或修改本项目,可以考虑以下方面:

  1. 增加寄存器数量:修改modbus_rtu.h中的MAX_COILS、MAX_DISCRETE_INPUTS、MAX_HOLDING_REGISTERS和MAX_INPUT_REGISTERS宏定义。
  2. 修改通信参数:在modbus_rtu.c的Modbus_Init函数中修改UART配置。
  3. 添加新功能码:在Modbus_ProcessMessage函数中添加新的case分支,并实现相应的处理函数。
  4. 集成实际功能:可以将实际的传感器数据写入输入寄存器,或者通过保持寄存器控制设备。

9. 常见问题与解决方法

  1. 通信无响应

    • 检查物理连接是否正确
    • 确认通信参数(波特率、校验位等)是否一致
    • 确认从站地址是否正确
  2. CRC错误

    • 检查通信线路是否有干扰
    • 降低通信速率
    • 确认主站的CRC计算方式是否正确
  3. 接收数据不完整

    • 可能是3.5个字符时间判断有误,尝试调整t35Timeout的值
    • 检查接收中断处理逻辑

10. 进一步学习资源

  • Modbus官方网站
  • Modbus协议规范
  • STM32 HAL库文档
  • FreeRTOS官方文档

11.项目源码

modbus_rtu.h文件

/**
 ******************************************************************************
* @file    modbus_rtu.h
* @brief   Modbus RTU协议实现头文件
******************************************************************************
*/

#ifndef __MODBUS_RTU_H
#define __MODBUS_RTU_H

#ifdef __cplusplus
extern "C" {
#endif

/* 包含头文件 */
#include "main.h"

#ifndef  USE_FREERTOS_COMPONENT
#else 
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#endif



/* 宏定义 */
#define MODBUS_RTU_SLAVE_ADDRESS    0x01        /* 从机地址 */
#define MODBUS_MAX_BUFFER_SIZE      256         /* 最大缓冲区大小 */

/* ModBus功能码 */
typedef enum {
    MODBUS_FC_READ_COILS               = 0x01,  /* 读线圈 */
    MODBUS_FC_READ_DISCRETE_INPUTS     = 0x02,  /* 读离散输入 */
    MODBUS_FC_READ_HOLDING_REGISTERS   = 0x03,  /* 读保持寄存器 */
    MODBUS_FC_READ_INPUT_REGISTERS     = 0x04,  /* 读输入寄存器 */
    MODBUS_FC_WRITE_SINGLE_COIL        = 0x05,  /* 写单个线圈 */
    MODBUS_FC_WRITE_SINGLE_REGISTER    = 0x06,  /* 写单个寄存器 */
    MODBUS_FC_WRITE_MULTIPLE_COILS     = 0x0F,  /* 写多个线圈 */
    MODBUS_FC_WRITE_MULTIPLE_REGISTERS = 0x10   /* 写多个寄存器 */
} ModbusFunctionCode;

/* Modbus错误码 */
typedef enum {
    MODBUS_EXCEPTION_ILLEGAL_FUNCTION     = 0x01, /* 非法功能 */
    MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS = 0x02, /* 非法数据地址 */
    MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE   = 0x03, /* 非法数据值 */
    MODBUS_EXCEPTION_SLAVE_DEVICE_FAILURE = 0x04, /* 从设备故障 */
    MODBUS_EXCEPTION_ACKNOWLEDGE          = 0x05, /* 确认 */
    MODBUS_EXCEPTION_SLAVE_DEVICE_BUSY    = 0x06, /* 从设备忙 */
    MODBUS_EXCEPTION_MEMORY_PARITY_ERROR  = 0x08, /* 内存奇偶错误 */
    MODBUS_EXCEPTION_GATEWAY_PATH_UNAVAILABLE = 0x0A, /* 网关路径不可用 */
    MODBUS_EXCEPTION_GATEWAY_TARGET_FAILED    = 0x0B  /* 网关目标设备无响应 */
} ModbusExceptionCode;

/* Modbus数据模型 */
#define MAX_COILS              64   /* 线圈数量 */
#define MAX_DISCRETE_INPUTS    64   /* 离散输入数量 */
#define MAX_INPUT_REGISTERS    32   /* 输入寄存器数量 */
#define MAX_HOLDING_REGISTERS  32   /* 保持寄存器数量 */

/* UART配置 */
#define MODBUS_UART            huart2
#define MODBUS_UART_TIMEOUT    1000

/* 函数原型 */
void Modbus_Init(void);

#ifndef  USE_FREERTOS_COMPONENT
void Modbus_app(void);
#else
void Modbus_Task(void *pvParameters);
#endif
uint16_t Modbus_CRC16(uint8_t *buffer, uint16_t length);
void Modbus_ProcessMessage(uint8_t *frame, uint16_t length);
void Modbus_SendException(uint8_t slaveAddr, uint8_t functionCode, ModbusExceptionCode exceptionCode);
void Modbus_SendResponse(uint8_t *response, uint16_t length);

/* 功能码处理函数 */
void Modbus_ReadCoils(uint8_t *frame, uint16_t length);
void Modbus_ReadDiscreteInputs(uint8_t *frame, uint16_t length);
void Modbus_ReadHoldingRegisters(uint8_t *frame, uint16_t length);
void Modbus_ReadInputRegisters(uint8_t *frame, uint16_t length);
void Modbus_WriteSingleCoil(uint8_t *frame, uint16_t length);
void Modbus_WriteSingleRegister(uint8_t *frame, uint16_t length);
void Modbus_WriteMultipleCoils(uint8_t *frame, uint16_t length);
void Modbus_WriteMultipleRegisters(uint8_t *frame, uint16_t length);

void Modbus_PrintData(uint8_t *buffer, uint16_t length, const char *description);

/* 外部变量 */
extern UART_HandleTypeDef huart2;

#ifdef __cplusplus
}
#endif

#endif /* __MODBUS_RTU_H */ 

modbus_rtu.c文件

/**
  ******************************************************************************
  * @file    modbus_rtu.c
  * @brief   Modbus RTU协议实现源文件
  ******************************************************************************
  */

/* 包含头文件 */
#include "modbus_rtu.h"
#include "stdlib.h"
#include "string.h"
#include "stdio.h"


/* 私有变量 */
UART_HandleTypeDef huart2;

static uint32_t lastActivity = 0;
/* Modbus从机数据 */
static uint8_t coils[MAX_COILS / 8];                  /* 线圈 */
static uint8_t discreteInputs[MAX_DISCRETE_INPUTS / 8]; /* 离散输入 */
static uint16_t inputRegisters[MAX_INPUT_REGISTERS];    /* 输入寄存器 */
static uint16_t holdingRegisters[MAX_HOLDING_REGISTERS]; /* 保持寄存器 */

/* 接收和发送缓冲区 */
static uint8_t rxBuffer[MODBUS_MAX_BUFFER_SIZE];
static uint8_t txBuffer[MODBUS_MAX_BUFFER_SIZE];
static uint16_t rxCount = 0;

/**
  * @brief  初始化Modbus
  * @param  无
  * @retval 无
  */
void Modbus_Init(void)
{
	/* 配置Modbus使用的串口 */
	GPIO_InitTypeDef GPIO_InitStruct = {0};

	/* 使能USART2和GPIOA时钟 */
	__HAL_RCC_USART2_CLK_ENABLE();
	__HAL_RCC_GPIOA_CLK_ENABLE();

	/* 配置USART2 TX引脚(PA2) */
	GPIO_InitStruct.Pin = GPIO_PIN_2;
	GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
	HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

	/* 配置USART2 RX引脚(PA3) */
	GPIO_InitStruct.Pin = GPIO_PIN_3;
	GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
	GPIO_InitStruct.Pull = GPIO_NOPULL;
	HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

	/* 配置UART参数 */
	huart2.Instance = USART2;
	huart2.Init.BaudRate = 9600;
	huart2.Init.WordLength = UART_WORDLENGTH_8B;
	huart2.Init.StopBits = UART_STOPBITS_1;
	huart2.Init.Parity = UART_PARITY_NONE;
	huart2.Init.Mode = UART_MODE_TX_RX;
	huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
	huart2.Init.OverSampling = UART_OVERSAMPLING_16;
	HAL_UART_Init(&huart2);

	/* 配置USART2中断 */
	HAL_NVIC_SetPriority(USART2_IRQn, 5, 0);  /* 设置中断优先级,需要低于FreeRTOS的系统调用 */
	HAL_NVIC_EnableIRQ(USART2_IRQn);          /* 使能USART2中断 */

	/* 初始化Modbus数据区 */
	memset(coils, 0, sizeof(coils));
	memset(discreteInputs, 0, sizeof(discreteInputs));
	memset(inputRegisters, 0, sizeof(inputRegisters));
	memset(holdingRegisters, 0, sizeof(holdingRegisters));

	/* 设置一些测试数据 */
	holdingRegisters[0] = 0x1234;
	holdingRegisters[1] = 0x5678;
	inputRegisters[0] = 0xABCD;
	inputRegisters[1] = 0xEF01;

	/* 开始接收数据 */
	HAL_UART_Receive_IT(&huart2, &rxBuffer[0], 1);

	printf("Modbus RTU initialized on USART2 (9600-8-N-1)\r\n");
}

#ifndef USE_FREERTOS_COMPONENT
void Modbus_app(void)
{
    const uint32_t t35Timeout = 4; /* 3.5个字符时间,按9600波特率大约4ms */

    /* 初始化Modbus */
    Modbus_Init();

    for(;;)
    {
        /* 检查是否接收到数据,并且已经过了3.5个字符时间 */
        uint32_t currentTick = HAL_GetTick();

        if (rxCount > 0 && (currentTick - lastActivity) >= t35Timeout)
        {
            /* 有一个完整的帧,处理它 */
            if (rxCount >= 4) /* 最小Modbus帧长度:从机地址(1)+功能码(1)+CRC(2) */
            {
                /* 验证CRC */
                uint16_t crc = Modbus_CRC16(rxBuffer, rxCount - 2);
                uint16_t receivedCrc = (rxBuffer[rxCount - 1] << 8) | rxBuffer[rxCount - 2];

                if (crc == receivedCrc)
                {
                    /* CRC正确,检查是否是发给本机的 */
                    if (rxBuffer[0] == MODBUS_RTU_SLAVE_ADDRESS || rxBuffer[0] == 0) /* 0是广播地址 */
                    {
                        /* 处理Modbus消息 */
                        Modbus_ProcessMessage(rxBuffer, rxCount);
                        Modbus_PrintData(rxBuffer, rxCount, NULL);
                    }
                }
                else
                {
                printf("Modbus CRC error\r\n");
                }
            }

            /* 重置接收计数器,准备接收下一帧 */
            rxCount = 0;
            HAL_UART_Receive_IT(&huart2, &rxBuffer[0], 1);
        }else{

            //printf(" rxCount:%d currentTick - lastActivity:%d\r\n", rxCount, (currentTick - lastActivity));
        }
        //HAL_Delay(1);
    }
}
#else
/**
  * @brief  Modbus任务函数
  * @param  pvParameters 任务参数
  * @retval 无
  */
void Modbus_Task(void *pvParameters)
{
  uint32_t lastActivity = 0;
  const uint32_t t35Timeout = 4; /* 3.5个字符时间,按9600波特率大约4ms */
  
  /* 初始化Modbus */
  Modbus_Init();
  
  for(;;)
  {
    /* 检查是否接收到数据,并且已经过了3.5个字符时间 */
    uint32_t currentTick = xTaskGetTickCount();
    
    if (rxCount > 0 && (currentTick - lastActivity) >= t35Timeout)
    {
      /* 有一个完整的帧,处理它 */
      if (rxCount >= 4) /* 最小Modbus帧长度:从机地址(1)+功能码(1)+CRC(2) */
      {
        /* 验证CRC */
        uint16_t crc = Modbus_CRC16(rxBuffer, rxCount - 2);
        uint16_t receivedCrc = (rxBuffer[rxCount - 1] << 8) | rxBuffer[rxCount - 2];
        
        if (crc == receivedCrc)
        {
          /* CRC正确,检查是否是发给本机的 */
          if (rxBuffer[0] == MODBUS_RTU_SLAVE_ADDRESS || rxBuffer[0] == 0) /* 0是广播地址 */
          {
            /* 处理Modbus消息 */
            Modbus_ProcessMessage(rxBuffer, rxCount);
          }
        }
        else
        {
          printf("Modbus CRC error\r\n");
        }
      }
      
      /* 重置接收计数器,准备接收下一帧 */
      rxCount = 0;
      HAL_UART_Receive_IT(&huart2, &rxBuffer[0], 1);
    }
    
    /* 休眠一小段时间 */
    vTaskDelay(1);
  }
}

#endif
/**
  * @brief  计算Modbus CRC16校验
  * @param  buffer 数据缓冲区
  * @param  length 数据长度
  * @retval CRC16校验值
  */
uint16_t Modbus_CRC16(uint8_t *buffer, uint16_t length)
{
  uint16_t crc = 0xFFFF;
  uint16_t i;
  
  while (length--)
  {
    crc ^= *buffer++;
    for (i = 0; i < 8; i++)
    {
      if (crc & 0x0001)
      {
        crc >>= 1;
        crc ^= 0xA001;
      }
      else
      {
        crc >>= 1;
      }
    }
  }
  
  return crc;
}

/**
  * @brief  处理Modbus消息
  * @param  frame 数据帧
  * @param  length 数据长度
  * @retval 无
  */
void Modbus_ProcessMessage(uint8_t *frame, uint16_t length)
{
  uint8_t slaveAddr = frame[0];
  uint8_t functionCode = frame[1];
  
  /* 如果是广播地址,不需要响应 */
  if (slaveAddr == 0)
  {
    return;
  }
  
  /* 根据功能码调用相应的处理函数 */
  switch (functionCode)
  {
    case MODBUS_FC_READ_COILS:
      Modbus_ReadCoils(frame, length);
      break;
      
    case MODBUS_FC_READ_DISCRETE_INPUTS:
      Modbus_ReadDiscreteInputs(frame, length);
      break;
      
    case MODBUS_FC_READ_HOLDING_REGISTERS:
      Modbus_ReadHoldingRegisters(frame, length);
      break;
      
    case MODBUS_FC_READ_INPUT_REGISTERS:
      Modbus_ReadInputRegisters(frame, length);
      break;
      
    case MODBUS_FC_WRITE_SINGLE_COIL:
      Modbus_WriteSingleCoil(frame, length);
      break;
      
    case MODBUS_FC_WRITE_SINGLE_REGISTER:
      Modbus_WriteSingleRegister(frame, length);
      break;
      
    case MODBUS_FC_WRITE_MULTIPLE_COILS:
      Modbus_WriteMultipleCoils(frame, length);
      break;
      
    case MODBUS_FC_WRITE_MULTIPLE_REGISTERS:
      Modbus_WriteMultipleRegisters(frame, length);
      break;
      
    default:
      /* 不支持的功能码 */
      Modbus_SendException(slaveAddr, functionCode, MODBUS_EXCEPTION_ILLEGAL_FUNCTION);
      break;
  }
}

/**
  * @brief  发送Modbus异常响应
  * @param  slaveAddr 从机地址
  * @param  functionCode 功能码
  * @param  exceptionCode 异常码
  * @retval 无
  */
void Modbus_SendException(uint8_t slaveAddr, uint8_t functionCode, ModbusExceptionCode exceptionCode)
{
  uint16_t crc;
  
  txBuffer[0] = slaveAddr;
  txBuffer[1] = functionCode | 0x80; /* 异常响应功能码 = 功能码 | 0x80 */
  txBuffer[2] = exceptionCode;
  
  /* 计算CRC */
  crc = Modbus_CRC16(txBuffer, 3);
  txBuffer[3] = crc & 0xFF;
  txBuffer[4] = crc >> 8;
  
  /* 发送异常帧 */
  Modbus_SendResponse(txBuffer, 5);
}

/**
  * @brief  发送Modbus响应
  * @param  response 响应数据
  * @param  length 数据长度
  * @retval 无
  */
void Modbus_SendResponse(uint8_t *response, uint16_t length)
{
  HAL_UART_Transmit(&huart2, response, length, MODBUS_UART_TIMEOUT);
}

/**
  * @brief  读线圈状态处理
  * @param  frame 请求帧
  * @param  length 帧长度
  * @retval 无
  */
void Modbus_ReadCoils(uint8_t *frame, uint16_t length)
{
  uint8_t slaveAddr = frame[0];
  uint16_t startAddress = (frame[2] << 8) | frame[3];
  uint16_t coilCount = (frame[4] << 8) | frame[5];
  uint16_t byteCount = (coilCount + 7) / 8; /* 计算需要的字节数 */
  uint16_t i, j;
  uint16_t crc;
  
  /* 检查地址和数量是否有效 */
  if (startAddress + coilCount > MAX_COILS || coilCount == 0 || coilCount > 2000)
  {
    Modbus_SendException(slaveAddr, MODBUS_FC_READ_COILS, MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS);
    return;
  }
  
  /* 构建响应 */
  txBuffer[0] = slaveAddr;
  txBuffer[1] = MODBUS_FC_READ_COILS;
  txBuffer[2] = byteCount;
  
  /* 填充线圈状态数据 */
  memset(&txBuffer[3], 0, byteCount);
  for (i = 0; i < coilCount; i++)
  {
    uint16_t byteIndex = i / 8;
    uint8_t bitIndex = i % 8;
    
    /* 获取线圈状态 */
    if ((coils[(startAddress + i) / 8] >> ((startAddress + i) % 8)) & 0x01)
    {
      txBuffer[3 + byteIndex] |= (1 << bitIndex);
    }
  }
  
  /* 计算CRC */
  crc = Modbus_CRC16(txBuffer, 3 + byteCount);
  txBuffer[3 + byteCount] = crc & 0xFF;
  txBuffer[3 + byteCount + 1] = crc >> 8;
  
  /* 发送响应 */
  Modbus_SendResponse(txBuffer, 3 + byteCount + 2);
}

/**
  * @brief  读离散输入状态处理
  * @param  frame 请求帧
  * @param  length 帧长度
  * @retval 无
  */
void Modbus_ReadDiscreteInputs(uint8_t *frame, uint16_t length)
{
  uint8_t slaveAddr = frame[0];
  uint16_t startAddress = (frame[2] << 8) | frame[3];
  uint16_t inputCount = (frame[4] << 8) | frame[5];
  uint16_t byteCount = (inputCount + 7) / 8; /* 计算需要的字节数 */
  uint16_t i, j;
  uint16_t crc;
  
  /* 检查地址和数量是否有效 */
  if (startAddress + inputCount > MAX_DISCRETE_INPUTS || inputCount == 0 || inputCount > 2000)
  {
    Modbus_SendException(slaveAddr, MODBUS_FC_READ_DISCRETE_INPUTS, MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS);
    return;
  }
  
  /* 构建响应 */
  txBuffer[0] = slaveAddr;
  txBuffer[1] = MODBUS_FC_READ_DISCRETE_INPUTS;
  txBuffer[2] = byteCount;
  
  /* 填充离散输入状态数据 */
  memset(&txBuffer[3], 0, byteCount);
  for (i = 0; i < inputCount; i++)
  {
    uint16_t byteIndex = i / 8;
    uint8_t bitIndex = i % 8;
    
    /* 获取离散输入状态 */
    if ((discreteInputs[(startAddress + i) / 8] >> ((startAddress + i) % 8)) & 0x01)
    {
      txBuffer[3 + byteIndex] |= (1 << bitIndex);
    }
  }
  
  /* 计算CRC */
  crc = Modbus_CRC16(txBuffer, 3 + byteCount);
  txBuffer[3 + byteCount] = crc & 0xFF;
  txBuffer[3 + byteCount + 1] = crc >> 8;
  
  /* 发送响应 */
  Modbus_SendResponse(txBuffer, 3 + byteCount + 2);
}

/**
  * @brief  读保持寄存器处理
  * @param  frame 请求帧
  * @param  length 帧长度
  * @retval 无
  */
void Modbus_ReadHoldingRegisters(uint8_t *frame, uint16_t length)
{
  uint8_t slaveAddr = frame[0];
  uint16_t startAddress = (frame[2] << 8) | frame[3];
  uint16_t regCount = (frame[4] << 8) | frame[5];
  uint16_t byteCount = regCount * 2; /* 每个寄存器2个字节 */
  uint16_t i;
  uint16_t crc;
  
  /* 检查地址和数量是否有效 */
  if (startAddress + regCount > MAX_HOLDING_REGISTERS || regCount == 0 || regCount > 125)
  {
    Modbus_SendException(slaveAddr, MODBUS_FC_READ_HOLDING_REGISTERS, MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS);
    return;
  }
  
  /* 构建响应 */
  txBuffer[0] = slaveAddr;
  txBuffer[1] = MODBUS_FC_READ_HOLDING_REGISTERS;
  txBuffer[2] = byteCount;
  
  /* 填充寄存器数据 */
  for (i = 0; i < regCount; i++)
  {
    txBuffer[3 + i * 2] = holdingRegisters[startAddress + i] >> 8; /* 高字节 */
    txBuffer[3 + i * 2 + 1] = holdingRegisters[startAddress + i] & 0xFF; /* 低字节 */
  }
  
  /* 计算CRC */
  crc = Modbus_CRC16(txBuffer, 3 + byteCount);
  txBuffer[3 + byteCount] = crc & 0xFF;
  txBuffer[3 + byteCount + 1] = crc >> 8;
  
  /* 发送响应 */
  Modbus_SendResponse(txBuffer, 3 + byteCount + 2);
}

/**
  * @brief  读输入寄存器处理
  * @param  frame 请求帧
  * @param  length 帧长度
  * @retval 无
  */
void Modbus_ReadInputRegisters(uint8_t *frame, uint16_t length)
{
  uint8_t slaveAddr = frame[0];
  uint16_t startAddress = (frame[2] << 8) | frame[3];
  uint16_t regCount = (frame[4] << 8) | frame[5];
  uint16_t byteCount = regCount * 2; /* 每个寄存器2个字节 */
  uint16_t i;
  uint16_t crc;
  
  /* 检查地址和数量是否有效 */
  if (startAddress + regCount > MAX_INPUT_REGISTERS || regCount == 0 || regCount > 125)
  {
    Modbus_SendException(slaveAddr, MODBUS_FC_READ_INPUT_REGISTERS, MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS);
    return;
  }
  
  /* 构建响应 */
  txBuffer[0] = slaveAddr;
  txBuffer[1] = MODBUS_FC_READ_INPUT_REGISTERS;
  txBuffer[2] = byteCount;
  
  /* 填充寄存器数据 */
  for (i = 0; i < regCount; i++)
  {
    txBuffer[3 + i * 2] = inputRegisters[startAddress + i] >> 8; /* 高字节 */
    txBuffer[3 + i * 2 + 1] = inputRegisters[startAddress + i] & 0xFF; /* 低字节 */
  }
  
  /* 计算CRC */
  crc = Modbus_CRC16(txBuffer, 3 + byteCount);
  txBuffer[3 + byteCount] = crc & 0xFF;
  txBuffer[3 + byteCount + 1] = crc >> 8;
  
  /* 发送响应 */
  Modbus_SendResponse(txBuffer, 3 + byteCount + 2);
}

/**
  * @brief  写单个线圈处理
  * @param  frame 请求帧
  * @param  length 帧长度
  * @retval 无
  */
void Modbus_WriteSingleCoil(uint8_t *frame, uint16_t length)
{
  uint8_t slaveAddr = frame[0];
  uint16_t coilAddress = (frame[2] << 8) | frame[3];
  uint16_t coilValue = (frame[4] << 8) | frame[5];
  uint16_t crc;
  
  /* 检查地址是否有效 */
  if (coilAddress >= MAX_COILS)
  {
    Modbus_SendException(slaveAddr, MODBUS_FC_WRITE_SINGLE_COIL, MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS);
    return;
  }
  
  /* 检查值是否有效 (0x0000或0xFF00) */
  if (coilValue != 0x0000 && coilValue != 0xFF00)
  {
    Modbus_SendException(slaveAddr, MODBUS_FC_WRITE_SINGLE_COIL, MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE);
    return;
  }
  
  /* 设置线圈状态 */
  uint8_t bitMask = 1 << (coilAddress % 8);
  if (coilValue == 0xFF00)
  {
    /* 设置线圈为ON */
    coils[coilAddress / 8] |= bitMask;
  }
  else
  {
    /* 设置线圈为OFF */
    coils[coilAddress / 8] &= ~bitMask;
  }
  
  /* 构建响应(与请求相同) */
  memcpy(txBuffer, frame, 6);
  
  /* 计算CRC */
  crc = Modbus_CRC16(txBuffer, 6);
  txBuffer[6] = crc & 0xFF;
  txBuffer[7] = crc >> 8;
  
  /* 发送响应 */
  Modbus_SendResponse(txBuffer, 8);
}

/**
  * @brief  写单个寄存器处理
  * @param  frame 请求帧
  * @param  length 帧长度
  * @retval 无
  */
void Modbus_WriteSingleRegister(uint8_t *frame, uint16_t length)
{
  uint8_t slaveAddr = frame[0];
  uint16_t regAddress = (frame[2] << 8) | frame[3];
  uint16_t regValue = (frame[4] << 8) | frame[5];
  uint16_t crc;
  
  /* 检查地址是否有效 */
  if (regAddress >= MAX_HOLDING_REGISTERS)
  {
    Modbus_SendException(slaveAddr, MODBUS_FC_WRITE_SINGLE_REGISTER, MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS);
    return;
  }
  
  /* 设置寄存器值 */
  holdingRegisters[regAddress] = regValue;
  
  /* 构建响应(与请求相同) */
  memcpy(txBuffer, frame, 6);
  
  /* 计算CRC */
  crc = Modbus_CRC16(txBuffer, 6);
  txBuffer[6] = crc & 0xFF;
  txBuffer[7] = crc >> 8;
  
  /* 发送响应 */
  Modbus_SendResponse(txBuffer, 8);
}

/**
  * @brief  写多个线圈处理
  * @param  frame 请求帧
  * @param  length 帧长度
  * @retval 无
  */
void Modbus_WriteMultipleCoils(uint8_t *frame, uint16_t length)
{
  uint8_t slaveAddr = frame[0];
  uint16_t startAddress = (frame[2] << 8) | frame[3];
  uint16_t coilCount = (frame[4] << 8) | frame[5];
  uint8_t byteCount = frame[6];
  uint16_t i;
  uint16_t crc;
  
  /* 检查地址和数量是否有效 */
  if (startAddress + coilCount > MAX_COILS || coilCount == 0 || coilCount > 1968 ||
      byteCount != (coilCount + 7) / 8)
  {
    Modbus_SendException(slaveAddr, MODBUS_FC_WRITE_MULTIPLE_COILS, MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS);
    return;
  }
  
  /* 设置线圈状态 */
  for (i = 0; i < coilCount; i++)
  {
    uint16_t byteIndex = i / 8;
    uint8_t bitIndex = i % 8;
    uint8_t bitMask = 1 << bitIndex;
    
    /* 清除旧值 */
    coils[(startAddress + i) / 8] &= ~(1 << ((startAddress + i) % 8));
    
    /* 设置新值 */
    if (frame[7 + byteIndex] & bitMask)
    {
      coils[(startAddress + i) / 8] |= (1 << ((startAddress + i) % 8));
    }
  }
  
  /* 构建响应 */
  txBuffer[0] = slaveAddr;
  txBuffer[1] = MODBUS_FC_WRITE_MULTIPLE_COILS;
  txBuffer[2] = startAddress >> 8;
  txBuffer[3] = startAddress & 0xFF;
  txBuffer[4] = coilCount >> 8;
  txBuffer[5] = coilCount & 0xFF;
  
  /* 计算CRC */
  crc = Modbus_CRC16(txBuffer, 6);
  txBuffer[6] = crc & 0xFF;
  txBuffer[7] = crc >> 8;
  
  /* 发送响应 */
  Modbus_SendResponse(txBuffer, 8);
}

/**
  * @brief  写多个寄存器处理
  * @param  frame 请求帧
  * @param  length 帧长度
  * @retval 无
  */
void Modbus_WriteMultipleRegisters(uint8_t *frame, uint16_t length)
{
  uint8_t slaveAddr = frame[0];
  uint16_t startAddress = (frame[2] << 8) | frame[3];
  uint16_t regCount = (frame[4] << 8) | frame[5];
  uint8_t byteCount = frame[6];
  uint16_t i;
  uint16_t crc;
  
  /* 检查地址和数量是否有效 */
  if (startAddress + regCount > MAX_HOLDING_REGISTERS || regCount == 0 || regCount > 123 ||
      byteCount != regCount * 2)
  {
    Modbus_SendException(slaveAddr, MODBUS_FC_WRITE_MULTIPLE_REGISTERS, MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS);
    return;
  }
  
  /* 设置寄存器值 */
  for (i = 0; i < regCount; i++)
  {
    holdingRegisters[startAddress + i] = (frame[7 + i * 2] << 8) | frame[7 + i * 2 + 1];
  }
  
  /* 构建响应 */
  txBuffer[0] = slaveAddr;
  txBuffer[1] = MODBUS_FC_WRITE_MULTIPLE_REGISTERS;
  txBuffer[2] = startAddress >> 8;
  txBuffer[3] = startAddress & 0xFF;
  txBuffer[4] = regCount >> 8;
  txBuffer[5] = regCount & 0xFF;
  
  /* 计算CRC */
  crc = Modbus_CRC16(txBuffer, 6);
  txBuffer[6] = crc & 0xFF;
  txBuffer[7] = crc >> 8;
  
  /* 发送响应 */
  Modbus_SendResponse(txBuffer, 8);
}

/**
  * @brief  UART接收完成回调函数
  * @param  huart UART句柄
  * @retval 无
  */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  if (huart->Instance == USART2)
  {

    lastActivity = HAL_GetTick();
    if (rxCount < MODBUS_MAX_BUFFER_SIZE)
    {
      /* 继续接收下一个字节 */
      rxCount++;
      HAL_UART_Receive_IT(&huart2, &rxBuffer[rxCount], 1);
    }
    else
    {
      /* 缓冲区已满,重置接收 */
      rxCount = 0;
      HAL_UART_Receive_IT(&huart2, &rxBuffer[0], 1);
    }

    //printf("recv data:%s \r\n", rxBuffer);
  }
} 


/**
  * @brief  打印Modbus数据
  * @param  buffer 数据缓冲区
  * @param  length 数据长度
  * @param  description 数据描述
  * @retval 无
  */
void Modbus_PrintData(uint8_t *buffer, uint16_t length, const char *description)
{
    printf("%s,长度: %d 字节\r\n", description, length);
    printf("数据内容(HEX): ");
    for (uint16_t i = 0; i < length; i++)
    {
        printf("%02X ", buffer[i]);
    }
    printf("\r\n");
}

12.注意事项

需要在stm32f1xxx_it.c文件中定义UART2的接收中断

extern UART_HandleTypeDef huart2;
void USART2_IRQHandler(void)
{
  HAL_UART_IRQHandler(&huart2);
} 

13.测试结果

在这里插入图片描述

相关文章:

  • SpringBoot学生宿舍管理系统的设计与开发
  • 谷粒商城:性能压测JVM堆区
  • 前端学习笔记(三)——ant-design vue表单传递数据到父页面
  • 【从零开始学习计算机科学】数据库系统(十)XML、XPATH、XQuery与XML数据库
  • 在 Ubuntu 上安装和配置 Docker 的完整指南
  • 计算机网络-网络存储技术
  • Java随机数生成终极指南:数组存储到Math.random()与Random类的深度对比
  • 【金字塔原理】如何有效提升思考和表达能力
  • docker基本应用和相关指令
  • 6. JavaScript 数组方法
  • Nature最新报道:分析四大主流AI工具、性能测评、推荐使用场景
  • Vue3中slot(插槽)的作用
  • 【Pandas】pandas Series last_valid_index
  • 计算机网络——DHCP实验
  • 使用 Excel 实现绩效看板的自动化
  • chrome浏览器拓展插件捕获页面的响应体内容
  • 深度学习知识:softlabel策略
  • chrome浏览器插件拓展捕获页面的响应体内容
  • AI+办公 Task2
  • Nacos入门实战(二)配置中心及配置实战
  • 传奇做网站空间/郑州网络推广服务
  • 日ip 1万 网站 值多少钱/网站怎么收录到百度
  • 自己怎么开网站做销售/发表文章的平台有哪些
  • 网站开发 视频播放器/鄂州seo
  • 织梦 大型综合旅游网站 源码/百度新闻官网首页
  • 广告公司网站模版/网页制作教程步骤