Modbus一个工业上常用的通讯协议、一种通讯约定。Modbus协议包括RTU、ASCII、TCP。其中MODBUS-RTU最常用,我们所学习的就是MODBUS-RTU。
一、报文
先来看一则MODBUS-RTU的报文:01 06 00 01 00 17 98 04
01 | 06 | 00 01 | 00 17 | 98 04 |
从机地址 | 功能码 | 数据地址 | 数据 | CRC校验 |
意思就是:把数据0x0017写入从机地址01中的数据地址为0x0001的寄存器里。
二、CRC校验
循环冗余校验是一种根据网上数据包或计算机文件等数据产生简短固定位数校验码的一种散列函数,主要用来检测或校验数据传输或者保存后可能出现的错误。生成的数字在传输或者存储之前计算出来并且附加到数据后面,然后接收方进行检验确定数据是否发生变化。一般来说,循环冗余校验的值都是32位的整数。由于本函数易于用二进制的计算机硬件使用、容易进行数学分析并且尤其善于检测传输通道干扰引起的错误,因此获得广泛应用。
此处不做研究,代码网上有。
三、功能码
每一个功能码对应实现的功能不同,常用的有以下几种:
01 (0x01) | 读线圈 |
02 (0x02) | 读离散量输入 |
03 (0x03) | 读保持寄存器 |
04 (0x04) | 读输入寄存器 |
05 (0x05) | 写单个线圈 |
06 (0x06) | 写单个寄存器 |
15 (0x0F) | 写多个线圈 |
16 (0x10) | 写多个寄存器 |
其中最经常用到的就是 03 和 06 ,故对这两个功能码做深入学习。
四、代码
使用电脑作为主机,STM32F103ZET6开发板作为从机,串口作为modbus协议传输通道,核心代码如下:
// modbus报文传输完成执行函数 void modbus_ok(void) { // usart_flag串口接收报文完毕标志位 if(usart_flag) { modbus_handle(uart_buff, 8); } } // modbus报文处理函数 void modbus_handle(unsigned char *buf, unsigned char len) { unsigned char i; unsigned char cnt; unsigned int crc; unsigned char crch, crcl; //本例中的从机地址设定为0x01, if (buf[0] != 0x01) { printf("error:从机地址不为0x01\n"); usart_flag = 0; // 清楚标志位 return; //直接退出,即丢弃本帧数据不做任何处理 } //地址相符时,再对本帧数据进行校验 // modbus_CRC16()循环冗余校验函数 crc = modbus_CRC16(buf, len-2); //计算CRC校验值 crch = crc >> 8; // 取高八位 crcl = crc & 0xFF; // 取低八位 if ((buf[len-2]!=crch) || (buf[len-1]!=crcl)) { printf("error:CRC校验失败\n"); usart_flag = 0; return; //如CRC校验不符时直接退出 } //地址和校验字均相符后,解析功能码,执行相关操作 switch (buf[1]) { case 0x03: //读取一个或连续的寄存器 //只支持0x0000~0x0005 if ((buf[2]==0x00) && (buf[3]<=0x05)) { i = buf[3]; //提取寄存器地址 cnt = buf[5]; //提取待读取的寄存器数量 buf[2] = cnt*2; //读取数据的字节数,为寄存器数*2 len = 3; //帧前部已有地址、功能码、字节数共3个字节 while (cnt--) { buf[len++] = 0x00; //寄存器高字节补0 buf[len++] = regGroup[i++]; //寄存器低字节 } //重新计算CRC校验值 赋值给后两位 crc = modbus_CRC16(buf, len); crch = crc >> 8; crcl = crc & 0xFF; buf[len++] = crch; buf[len++] = crcl; break; } //寄存器地址不被支持时,返回错误码 else { buf[1] = 0x83; //功能码最高位置1 buf[2] = 0x02; //设置异常码为02-无效地址 len = 3; break; } case 0x06: //写入单个寄存器 if ((buf[2]==0x00) && (buf[3]<=0x05)) { i = buf[3]; //提取寄存器地址 //往指定地址写入数据 regGroup[]数组存放寄存器初始化数据 switch(i) { case 0x00: regGroup[0] = buf[5]; break; case 0x01: regGroup[1] = buf[5]; break; case 0x02: regGroup[2] = buf[5]; break; case 0x03: regGroup[3] = buf[5]; break; case 0x04: regGroup[4] = buf[5]; break; case 0x05: regGroup[5] = buf[5]; break; } //重新计算CRC校验值 赋值给后两位 crc = modbus_CRC16(buf, len-2); crch = crc >> 8; crcl = crc & 0xFF; buf[len-2] = crch; buf[len-1] = crcl; } } char x = 0; printf("从机返回:"); for(x = 0; x < len; x++) { printf("%02X", buf[x]); } printf("\n"); usart_flag = 0; }
时间有限,测试的写在下一次文章里,请静候下回分解......