本帖最后由 穿西装的强子 于 2025-6-20 09:55 编辑
1、ModbusRTU协议帧格式 字段 | 长度 | 说明 | 从机地址 | 1Byte | 1-247,0作为广播 | 功能码 | 1Byte | 0x03读单个或多个数寄存器据,0x06写单个寄存器数据,0x10写多个寄存器数据 | 数据位 | NByte | 寄存器数据,和读写数据长度有关 | CRC校验 | 2Byte | CRC-16,低位在前,高位在后 |
2、寄存器格式
功能码 0x01: 读取线圈数据;0x02: 读取离散输入数据;
0x03:读取N个保持寄存器数据;
0x04: 读取N个输入寄存器;
0x05: 写入单个线圈;
0x06:写入单个数据;
0x0F: 写入多个线圈;
0x10:写入多个数据
一般只用到:0x03/0x06/0x10这三个寄存器比较多,暂时以这三个寄存器进行手搓;
读写指令的数据格式与返回格式数据举例
读取 N个数据0x03 发送报文:01 03 01 91 00 01 D4 1B 反馈报文:01 03 02 00 3C B8 55 主机->从机数据: 从机->主机数据:
写入单个数据 0x06 发送报文:01 06 01 91 0C 80 DD 7B 反馈报文:01 06 01 91 0C 80 DD 7B 主机->从机数据: 从机->主机数据:
写入多个数据 0x10 发送报文:01 10 01 46 00 04 08 00 00 00 28 00 0000 29 1C 14 反馈报文:01 10 01 46 00 04 21 E3 主机->从机数据: 从机->主机数据:
3、CRC校验用直接计算法
#include <stdint.h>
uint16_t calculate_crc_direct(uint8_t *data, uint16_t len) {
uint16_t crc = 0xFFFF; // 初始值
uint16_t poly = 0xA001; // Modbus CRC多项式
while (len--) {
crc ^= *data++; // XOR输入数据与当前CRC的最低字节
for (uint8_t i = 8; i != 0; i--) { // 对每个位进行处理
if ((crc & 0x0001) != 0) { // 如果最低位是1,则右移一位并与多项式XOR
crc >>= 1;
crc ^= poly;
} else { // 如果最低位是0,则只右移一位
crc >>= 1;
}
}
}
return crc;
}
uint8_t data[] = {0x01, 0x03, 0x00, 0x13, 0x00, 0x02}; // Modbus数据帧示例(设备地址,功能码,起始地址,数量)
uint16_t crc;
crc = calculate_crc(data, sizeof(data)); // 使用计算CRC
串口不定长数据接收,使用串口idle功能,这个功能是在串口空闲的时候一直触发,所以要做处理,不能直接在中断检测后直接发信号量,判定数据长度>0和处理已经处理完成标志的时候传输信号量串口发送中断不占用任务运行时间
void USART1_IRQHandler(void)
{
uint8_t RxData = 0;
if ((RESET != USART_GetITStatus(USART1, USART_IT_PE)) ||
(RESET != USART_GetITStatus(USART1, USART_IT_ERR)))
{
USART_ReceiveData(USART1);
}
if (RESET != USART_GetITStatus(USART1, USART_IT_RXNE))
{
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
RxData = USART_ReceiveData(USART1);
USART_RxStruct.Buffer[USART_RxStruct.Length++] = RxData;
// USART_SendData(USART1, RxData);
}
if (RESET != USART_GetITStatus(USART1, USART_IT_TXE))
{
if (1 == USART_TxStruct.CompleteFlag)
{
USART_SendData(USART1, USART_TxStruct.Buffer[USART_TxStruct.CurrentCount++]);
if (USART_TxStruct.CurrentCount == USART_TxStruct.Length)
{
USART_TxStruct.CompleteFlag = 0;
USART_TxStruct.CurrentCount = 0;
USART_ITConfig(USART1, USART_IT_TXE, DISABLE);
}
}
}
if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET)
{
// 检测到空闲中断,表示一帧数据接收完成
USART_ReceiveData(USART1); // 读取数据寄存器清除中断
USART_ClearITPendingBit(USART1, USART_IT_IDLE);
if( USART_RxStruct.Length > 0 && USART_RxStruct.CompleteFlag == 0)
{
USART_RxStruct.CompleteFlag = 1;
xSemaphoreGive(xBinarySemaphore);
}
}
}
先创建一个modbus的任务,用来处理modbus协议
xTaskCreate( (TaskFunction_t) Task_modbus, // 任务函数
(const char * ) "Task_modbus", // 任务名
(uint16_t ) MODBUS_STK_SIZE, // 任务堆栈大小
(void* ) NULL, // 传递给任务函数的参数
(UBaseType_t ) MODBUS_TASK_PRIO, // 任务优先级
(TaskHandle_t* ) PrintfTask_Handler); // 任务句柄
modbus的crc校验功能函数,检查到非本id的和长度低于6的直接返回
对于crc校验错误的,是需要返回错误码的,这里暂时没处理,后面再统一处理
void ModBus_Protocol_Process(USART_RxTx_TypeDef *usart,USART_RxTx_TypeDef *tx)
{
uint8_t id = usart->Buffer[0];
if( id != DEVICES_ID || usart->Length < 6)
return;
uint16_t crc_check = calculate_crc_direct(usart->Buffer,usart->Length-2);
uint16_t crc_data = (usart->Buffer[usart->Length-1]<<8)|(usart->Buffer[usart->Length-2]);
if( crc_check == crc_data) // 校验成功
{
ModBus_Register_Process(usart,tx);
}
else // 校验错误返回
{
}
}
寄存器功能选择函数
void ModBus_Register_Process(USART_RxTx_TypeDef *usart,USART_RxTx_TypeDef *tx)
{
uint8_t cmd = usart->Buffer[1];
switch(cmd){
case 0x03:
ModBus_Register_MultiRead(usart,tx);
break;
case 0x06:
ModBus_Register_SingleWrite(usart,tx);
break;
case 0x10:
ModBus_Register_MultiWrite(usart,tx);
break;
default:
break;
}
}
0x03读寄存器处理函数
对寄存器地址的获取,寄存器长度的获取
判定长度是否超过了buffer空间,超过要返回错误码,此处也暂时未处理
未超过的时候返回寄存器数据,将modbus的id,0x03寄存器,数据长度,数据和校验一起上报
将tx->CompleteFlag 置1并使能TX中断发送,通过中断将数据发生出去
void ModBus_Register_MultiRead(USART_RxTx_TypeDef *usart,USART_RxTx_TypeDef *tx)
{
uint16_t register_addr = (usart->Buffer[2]<<8)|(usart->Buffer[3]);// 寄存器地址
uint16_t register_len = (usart->Buffer[4]<<8)|(usart->Buffer[5]);// 寄存器数据长度
if( (register_addr + register_len) >= 128)
{
} else {
tx->Length = 0;
tx->Buffer[tx->Length] = usart->Buffer[tx->Length++];
tx->Buffer[tx->Length] = usart->Buffer[tx->Length++];
tx->Buffer[tx->Length++] = register_len*2;
for( uint16_t i = 0; i < register_len;i++)
{
tx->Buffer[tx->Length++] = (modbus_data[register_addr+i]>>8);
tx->Buffer[tx->Length++] = modbus_data[register_addr+i];
}
uint16_t crc = calculate_crc_direct(tx->Buffer,tx->Length);
tx->Buffer[tx->Length++] = crc>>8;
tx->Buffer[tx->Length++] = crc;
tx->CompleteFlag = 1;
USART_ITConfig(USART2, USART_IT_TXE, ENABLE);
}
}
以下是测试结果
从地址为100的寄存器开始读20个,modbus寄存器数据初始化是128个,读出数据与内容一致,验证通过
for(uint16_t i = 0; i< 128; i++)
{
modbus_data[i] = i+1;
}
|