stm32F103RCT6+ModBus之RTU
Modbus通信协议简介
1. Modbus概述
Modbus是一种广泛应用于工业控制领域的通信协议,由Modicon公司(现为施耐德电气)于1979年发明。它是一种开放的、基于主从架构的通信协议,被广泛应用于工业自动化、楼宇自动化、能源管理等领域。Modbus具有以下特点:
- 简单且健壮:协议结构简单,易于实现,且通信可靠
- 开放性:作为一种公开的工业标准,不需要支付版权费用
- 少量的报文类型:基本的报文类型较少,易于学习和使用
- 主从架构:明确的主站和从站关系,避免冲突
- 支持多种物理层:如RS-232、RS-485、TCP/IP等
2. Modbus通信模式
Modbus协议支持多种通信模式,主要包括:
- Modbus RTU:基于串行通信(如RS-232/RS-485),使用二进制编码方式,以提高传输效率。
- Modbus ASCII:基于串行通信,使用ASCII编码方式,易于调试但传输效率较低。
- 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从站功能,包括:
- 支持所有标准Modbus功能码(01、02、03、04、05、06、0F、10)
- 实现了线圈、离散输入、保持寄存器和输入寄存器四种数据类型
- 使用UART2(PA2/PA3)作为Modbus通信接口,默认波特率9600,8位数据位,无校验,1位停止位
- 从站地址为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. 扩展与修改
如需扩展或修改本项目,可以考虑以下方面:
- 增加寄存器数量:修改modbus_rtu.h中的MAX_COILS、MAX_DISCRETE_INPUTS、MAX_HOLDING_REGISTERS和MAX_INPUT_REGISTERS宏定义。
- 修改通信参数:在modbus_rtu.c的Modbus_Init函数中修改UART配置。
- 添加新功能码:在Modbus_ProcessMessage函数中添加新的case分支,并实现相应的处理函数。
- 集成实际功能:可以将实际的传感器数据写入输入寄存器,或者通过保持寄存器控制设备。
9. 常见问题与解决方法
-
通信无响应:
- 检查物理连接是否正确
- 确认通信参数(波特率、校验位等)是否一致
- 确认从站地址是否正确
-
CRC错误:
- 检查通信线路是否有干扰
- 降低通信速率
- 确认主站的CRC计算方式是否正确
-
接收数据不完整:
- 可能是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);
}