modbus RTU 从机解析代码

本文详细介绍了Modbus通信协议的帧结构、错误定义及CRC校验过程。通过示例代码展示了如何解析接收到的数据帧,并针对不同命令进行响应处理。同时,讨论了为何不直接依赖CRC校验来确定数据完整性的原因。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

#define SLAVE_ID                0x01
#define ERR_FUNC				0x01//
#define ERR_ADDR				0x02//
#define	ERR_VALUE				0x03//
#define ERR_FAULT				0x04//
#define	ERR_CONFIRM				0x05//
#pragma pack(1)
typedef struct 
{
	uint8_t slave_id;
	uint8_t cmd;
	uint8_t data[0];
}modbus_frame_t;
typedef struct 
{
	uint8_t reg_addr[2];
	uint8_t value[2];
}modbus_body_t;
#define MODBUS_FRAME_HEAD  2
#pragma pack()
//modbus crc16校验
uint16_t mobus_crc16(const uint8_t *CRC_Buf,uint8_t CRC_Leni)
{
    uint8_t i,j;
    uint16_t CRC_Sumx;

    CRC_Sumx=0xffff;
    for(i=0;i<CRC_Leni;i++)
    {
        CRC_Sumx^=*(CRC_Buf+i);
         for(j=0;j<8;j++)
        {
            if(CRC_Sumx&0x01)
            {
                CRC_Sumx>>=1;
                CRC_Sumx^=0xA001;
            }
            else
            {
                CRC_Sumx>>=1;
            }
        }
    }
    return (CRC_Sumx);
}

modbus_frame_t*   get_modbus_frame(const uint8_t * in, uint32_t len)
{
	int i = 0,dlen =0;
	uint16_t crc_rec= 0;
	start_lbl:

	while (i < len)
	{ 
		if ((SLAVE_ID == in[i])||(0x00 == in[i]))
			break;
		i++;
	}
	if (len - i < 8)//小于基本长度
		return NULL;
	modbus_frame_t * pframe = ( modbus_frame_t *) &in[i];
	
	switch(pframe->cmd)
	{
		case 0x03:
		case 0x04:
		case 0x06:
			dlen = sizeof(modbus_body_t);
		break;
		case 0x10:
            uint16_t value = pframe->data[2]<<8 | pframe->data[3];
			if(value != pframe->data[4] >> 1)
				return NULL;
			dlen = pframe->data[4] + 1 + sizeof(modbus_body_t);
		break;
		case 0x15:
			dlen = pframe->data[0] + 1;
		break;
		default:
			return NULL;
	}
    if (i + MODBUS_FRAME_HEAD + dlen + 2 > len)
    {
        i++;
        goto start_lbl;
    }
	crc_rec = mobus_crc16((uint8_t *)pframe,MODBUS_FRAME_HEAD+dlen);
    if ((pframe->data[dlen] == (uint8_t)(crc_rec))||
        (pframe->data[dlen+1]==((uint8_t)(crc_rec>>8))))
    {
        pframe = (modbus_frame_t *)&in[i];
        return pframe;
    }
    i++; 
    goto start_lbl;
}
int code_ret03_body(uint16_t reg_addr,uint16_t value,uint8_t *data)
{
	if(value == 0)
		return -ERR_VALUE;
	int err =0;
	uint8_t len = 1;
	data[0] = value * 2;
	if(value >= 0xff)
		return -ERR_VALUE;
	memset(data+len ,0x00,value*2);
	for(uint16_t i = 0 ; i < value; i++)
	{
		err = your_func(reg_addr + i,data+len);//放入自己的函数,返回错误码。
		len +=2;
	}
	return err < 0 ? err : len;
	
}
int code_ret06_body(uint16_t reg_addr,uint16_t value, uint8_t *data)
{
	int err = your_func(reg_addr,value);//放入自己的函数,返回错误码。
	return err < 0 ? err : 4;
}
int code_ret10_body(uint16_t reg_addr,uint16_t value, uint8_t *data)
{
	int err =0;
	int len = 5;
	if(value >= 0xff)
		return -ERR_VALUE;
	for(uint8_t i = 0 ; i < value; i++)
	{
		err = your_func(reg_addr+i,data+len);//放入自己的函数,返回错误码。
		len += 2;
	}
	return err < 0 ? err : 4;
}
int code_modbus_ret_frame(modbus_frame_t *frame)
{
	modbus_body_t *body = (modbus_body_t *)frame->data;
	uint16_t reg_addr = body->reg_addr[0]<<8|body->reg_addr[1];//大端
	uint16_t value = body->value[0] <<8 | body->value[1];//大端
	switch(frame->cmd)
	{
		case 0x03:
		case 0x04:
			return code_ret03_body(reg_addr,value,frame->data);
		case 0x06:
			return code_ret06_body(reg_addr,value,frame->data);
		case 0x10:
			return code_ret10_body(reg_addr,value,frame->data);
		default:
			return 0;
	}
}
int code_modbus_err_body(int err,modbus_frame_t *frame)
{
	frame->cmd |=0x80;
	frame->data[0] = _abs(err);
	return 1;
}
uint32_t modbus_frame_handle(modbus_frame_t *frame)
{
	int len = 0;

	len = code_modbus_ret_frame(frame);
	if(len <= 0)
	{
		len = code_modbus_err_body(len,frame);
	}
	
	if(frame->slave_id == 0x00)
	{
		return 0;
	}			
	uint16_t crc = mobus_crc16((uint8_t *)frame,len+MODBUS_FRAME_HEAD);
	frame->data[len] = (uint8_t)crc;//大端
    frame->data[len + 1] = (uint8_t)(crc_rec>>8);//大端
	return len+4;
}

接收的数组和回复的数组是使用的同一个,注意防止内存溢出,所以需要一个大数组进行接收。

为什么不直接校验CRC判断等于0 ?因为这样操作可以对付长数组断断续续接收的问题,按需更改校验方式。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值