不能错过的 Modebus-RTU 协议源码,助你一步到位

文章目录

    • 概要
    • 整体架构流程
    • 技术细节
    • 小结

概要

 本项目源码为个人攥写,包含所有常用的功能码和错误帧回复,51单片机和32单片机通用,感觉响应速度还不错就进行开源处理,实测在33.1776Mhz主频下,波特率为115200的情况下,回复速度为7-10ms,如下图:

详细的协议说明可以参考这个  Modbus 通讯协议 (RTU传输模式)_modbus rtu-CSDN博客

不想看长篇大论的就参考这个  值得收藏 Modbus RTU 协议详解-CSDN博客

整体架构流程

     代码框架已经定型,大抵是不需要再修改了,编写相应的功能逻辑就行了

    1.串口接收——》字节接收超时,且缓冲区内有数据,视为收到一帧数据

    2.处理过程——》开始处理时,不再接收数据,如果对速度有高要求,自行再开辟一个缓冲区,可以在数据处理过程中,继续接收数据。

    2.1——》检查地址是否为本机,若不对,则重新接收,不进行回复

    2.2——》检查功能是否正确,若不对,回复功能码错误帧,开始重新接收,正确则根据跳转到相应功能码的函数内

    2.3——》功能码处理过程中,先检查CRC是否正确,校验码不对则退出,根据协议要求,校验码和地址不对是不回复的直接退出。

    2.4——》然后检查寄存器地址是否正确,不正确就恢复非法的寄存器地址错误帧,正确就继续执行

    2.5——》然后检查寄存器数量是否正确,若错误则回复相应的错误功能码

    2.6——》最后检查数据是否正确,若错误则回复非法的数据值功能码

    3.数据处理 ——》经过重重验证,进行最终的数据处理,处理完成回复相应处理回复数据,然后一帧数据正确接收完成,开始下一个轮回。

技术细节

        话不多说上源码,源码内慢慢解释。

   建立c文件和头文件,文件名如下,包含你要使用的串口

#include "ModbusRTU/modbusRTU.h"
#include "Uart/uart.h"

        第一步,建立CRC的校验

         这里我提供两种方式的校验,一种是计算,一种是查表法,首选查表法,和计算法比较,查表的速度更快一些。

    查表法获取CRC:

const uchar code auchCRCHi[] = { 
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 
0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 
0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 
0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 
0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 
0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 
0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 
0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 
0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 
0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 
0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40 
}; 
/* CRC低位字节值表*/ 
const uchar code auchCRCLo[] = { 
0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 
0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 
0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, 
0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 
0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4, 
0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3, 
0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 
0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4, 
0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 
0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, 
0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED, 
0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26, 
0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 
0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, 
0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 
0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 
0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 
0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5, 
0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 
0x70, 0xB0, 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, 
0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, 
0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 
0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B, 
0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C, 
0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 
0x43, 0x83, 0x41, 0x81, 0x80, 0x40 
};
/*使用查表法计算CRC以提高效率*/
unsigned short crc16(uchar *puchMsg, uint usDataLen) 
{ 
	uchar uchCRCHi = 0xFF ; /* 高CRC字节初始化 */ 
	uchar uchCRCLo = 0xFF ; /* 低CRC 字节初始化 */ 
	unsigned long uIndex; /* CRC循环中的索引 */ 
	while (usDataLen--) /* 传输消息缓冲区 */ 
	{ 
		uIndex = uchCRCHi ^ *puchMsg++ ; /* 计算CRC */ 
		uchCRCHi = uchCRCLo ^ auchCRCHi[uIndex] ; 
		uchCRCLo = auchCRCLo[uIndex] ; 
	} 
	return (uchCRCHi << 8 | uchCRCLo) ; 
}

      计算法获取CRC :

/*以计算的方式获取CRC*/
unsigned short CRC_Create(unsigned char *buf, unsigned short len)
{
	unsigned short Rtu_CRC = 0XFFFF;		//16位crc寄存器预置
	unsigned char CRC_L, CRC_H; 
	unsigned char temp;
	unsigned short  i = 0, j = 0;		//计数
	
	for (i = 0; i < len; i++)			  //循环计算每个数据
	{
		temp = *buf & 0X00FF;				//将八位数据与crc寄存器亦或
		buf++;											//指针地址增加,指向下个数据
		Rtu_CRC ^= temp;					//将数据存入crc寄存器
		for (j = 0; j < 8; j++)		//循环计算数据的
		{
			if (Rtu_CRC & 0X0001)//判断右移出的是不是1,如果是1则与多项式进行异或。
			{
				Rtu_CRC >>= 1;//先将数据右移一位
				Rtu_CRC ^= 0XA001;//与上面的多项式进行异或
			}
			else//如果不是1,则直接移出
			{
				Rtu_CRC >>= 1;//直接移出
			}
		}
	}

	CRC_L = Rtu_CRC&0xff;//crc的低八位
	CRC_H = Rtu_CRC >> 8;//crc的高八位
	return ((CRC_L << 8) | CRC_H);
}

    使用时都是传递一个的指针和这个指针的长度,然后返回一个短整型的校验结果。

     第二步,在头文件中进行数据声明,寄存器地址的定义

     这份源码程序大小在8K左右,内存不足的单片机可以只使用部分功能码,只需要修改如下宏定义为 1 即可, 为 0 的时候不会编译相关代码。

#define   FuncCode01_ENABLE    (0)
#define   FuncCode02_ENABLE    (0)
#define   FuncCode03_ENABLE    (1)
#define   FuncCode04_ENABLE    (0)
#define   FuncCode05_ENABLE    (0)
#define   FuncCode06_ENABLE    (1)
#define   FuncCode0F_ENABLE    (0)
#define   FuncCode10_ENABLE    (0)

   这些也是按需修改的地方

#define   RtuFrameMaxLen      (32) //这个数值决定数据内存的大小,适当选择
#define   Rx_MaxByteTimeout    (2) //字节接收超时时间,我这里设定的是2ms,对于9600以上的波特率来说完全够用了,因为我做的485电路可以使用115200波特率,并且距离比较远,所以我一般不用低波特率,9600波特率一个字节1ms左右
#define   Rx_MaxTotal_Time    (30) //接收总时间 ,防止一种诡异造成的bug,按需修改长短

高速485通信电路设计传送门:高速远距离485通信电路设计,115200波特率,距离1500米。附485通信问题解决方案-CSDN博客

#ifndef __MODBUSRTU_H_
#define __MODBUSRTU_H_


/*本协议所有数据均采用大端模式*/
#define   RtuFrameMaxLen      (32) //单片机内存小时注意该数值不能过大,否则造成使用xdata空间溢出,程序异常
#define   Rx_MaxByteTimeout    (2) //字节接收超时时间,          
#define   Rx_MaxTotal_Time    (30) //接收总时间 
/*配置项目要使用的功能码,以节省内存空间*/
#define   FuncCode01_ENABLE    (0)
#define   FuncCode02_ENABLE    (0)
#define   FuncCode03_ENABLE    (1)
#define   FuncCode04_ENABLE    (0)
#define   FuncCode05_ENABLE    (0)
#define   FuncCode06_ENABLE    (1)
#define   FuncCode0F_ENABLE    (0)
#define   FuncCode10_ENABLE    (0)
/********************************************/
/*此区域定义寄存器地址*/
#define  ADDR_START         (0x0000)
/*线圈地址,可读可写,1个地址1个位,预留一百个输出线圈的地址*/
#define OUT_COIL_ADDR_START (0x0000)

#define OUT1_COIL_ADDR      (0x0000)
#define OUT2_COIL_ADDR      (0x0001)
#define OUT3_COIL_ADDR      (0x0002)
#define OUT4_COIL_ADDR      (0x0003)
#define OUT5_COIL_ADDR      (0x0004)
#define OUT6_COIL_ADDR      (0x0005)
#define OUT7_COIL_ADDR      (0x0006)
#define OUT8_COIL_ADDR      (0x0007)
#define OUT9_COIL_ADDR      (0x0008)
#define OUT10_COIL_ADDR     (0x0009)
#define OUT11_COIL_ADDR     (0x000A)
#define OUT12_COIL_ADDR     (0x000B)
#define OUT13_COIL_ADDR     (0x000C)
#define OUT14_COIL_ADDR     (0x000D)
#define OUT15_COIL_ADDR     (0x000E)
#define OUT16_COIL_ADDR     (0x000F)
#define OUT17_COIL_ADDR     (0x0010)
#define OUT18_COIL_ADDR     (0x0011)
#define OUT19_COIL_ADDR     (0x0012)
#define OUT20_COIL_ADDR     (0x0013)
#define OUT21_COIL_ADDR     (0x0014)
#define OUT22_COIL_ADDR     (0x0015)
#define OUT23_COIL_ADDR     (0x0016)
#define OUT24_COIL_ADDR     (0x0017)
#define OUT25_COIL_ADDR     (0x0018)
#define OUT26_COIL_ADDR     (0x0019)
#define OUT27_COIL_ADDR     (0x001A)
#define OUT28_COIL_ADDR     (0x001B)
#define OUT29_COIL_ADDR     (0x001C)
#define OUT30_COIL_ADDR     (0x001D)
#define OUT31_COIL_ADDR     (0x001E)
#define OUT32_COIL_ADDR     (0x001F)
#define OUT33_COIL_ADDR     (0x0020)
#define OUT34_COIL_ADDR     (0x0021)
#define OUT35_COIL_ADDR     (0x0022)
#define OUT36_COIL_ADDR     (0x0023)
#define OUT37_COIL_ADDR     (0x0024)
#define OUT38_COIL_ADDR     (0x0025)
#define OUT39_COIL_ADDR     (0x0026)
#define OUT40_COIL_ADDR     (0x0027)
#define OUT41_COIL_ADDR     (0x0028)
#define OUT42_COIL_ADDR     (0x0029)
#define OUT43_COIL_ADDR     (0x002A)

#define OUT_COIL_ADDR_END   (0x002A)//线圈的地址,根据项目实际情况修改大小
/*输入信号,只读,读离散输入寄存器,预留一百个开关量输入的地址*/
#define IN_COIL_ADDR_START  (0x0064)

#define IN1_COIL_ADDR       (0x0065)
#define IN2_COIL_ADDR       (0x0066)
#define IN3_COIL_ADDR       (0x0067)
#define IN4_COIL_ADDR       (0x0068)
#define IN5_COIL_ADDR       (0x0069)
#define IN6_COIL_ADDR       (0x006A)
#define IN7_COIL_ADDR       (0x006B)
#define IN8_COIL_ADDR       (0x006C)
#define IN9_COIL_ADDR       (0x006D)
#define IN10_COIL_ADDR      (0x006E)
#define IN11_COIL_ADDR      (0x006F)
#define IN12_COIL_ADDR      (0x0070)
#define IN13_COIL_ADDR      (0x0071)
#define IN14_COIL_ADDR      (0x0072)
#define IN15_COIL_ADDR      (0x0073)
#define IN16_COIL_ADDR      (0x0074)
#define IN17_COIL_ADDR      (0x0075)
#define IN18_COIL_ADDR      (0x0076)
#define IN19_COIL_ADDR      (0x0077)
#define IN20_COIL_ADDR      (0x0078)
#define IN21_COIL_ADDR      (0x0079)
#define IN22_COIL_ADDR      (0x007A)
#define IN23_COIL_ADDR      (0x007B)
#define IN24_COIL_ADDR      (0x007C)
#define IN25_COIL_ADDR      (0x007D)
#define IN26_COIL_ADDR      (0x007E)
#define IN27_COIL_ADDR      (0x007F)
#define IN28_COIL_ADDR      (0x0080)
#define IN29_COIL_ADDR      (0x0081)
#define IN30_COIL_ADDR      (0x0082)
#define IN31_COIL_ADDR      (0x0083)
#define IN32_COIL_ADDR      (0x0084)
#define IN33_COIL_ADDR      (0x0085)
#define IN34_COIL_ADDR      (0x0086)
#define IN35_COIL_ADDR      (0x0087)
#define IN36_COIL_ADDR      (0x0088)
#define IN37_COIL_ADDR      (0x0089)
#define IN38_COIL_ADDR      (0x008A)
#define IN39_COIL_ADDR      (0x008B)
#define IN40_COIL_ADDR      (0x008C)

#define IN_COIL_ADDR_END    (0x008C)

/*保持寄存器地址,1个地址1个字节,如果是浮点型数据则需要4个地址*/
#define HOLDING_REGISTER_ADDR_START     (0x00C8)

#define HOLDING_REGISTER_ADDR_1       (0x00C8)
#define HOLDING_REGISTER_ADDR_2       (0x00CA)
#define HOLDING_REGISTER_ADDR_3       (0x00CC)
#define HOLDING_REGISTER_ADDR_4       (0x00CE)
#define HOLDING_REGISTER_ADDR_5       (0x00D0)
#define HOLDING_REGISTER_ADDR_6       (0x00D2)
#define HOLDING_REGISTER_ADDR_7       (0x00D4)
#define HOLDING_REGISTER_ADDR_8       (0x00D6)
#define HOLDING_REGISTER_ADDR_9       (0x00D8)
#define HOLDING_REGISTER_ADDR_10      (0x00DA)
#define HOLDING_REGISTER_ADDR_11      (0x00DC)
#define HOLDING_REGISTER_ADDR_12      (0x00DE)
#define HOLDING_REGISTER_ADDR_13      (0x00E0)
#define HOLDING_REGISTER_ADDR_14      (0x00E2)
#define HOLDING_REGISTER_ADDR_15      (0x00E4)
#define HOLDING_REGISTER_ADDR_16      (0x00E6)
#define HOLDING_REGISTER_ADDR_17      (0x00E8)
#define HOLDING_REGISTER_ADDR_18      (0x00EA)
#define HOLDING_REGISTER_ADDR_19      (0x00EC)
#define HOLDING_REGISTER_ADDR_20      (0x00EE)
#define HOLDING_REGISTER_ADDR_21      (0x00F0)
#define HOLDING_REGISTER_ADDR_22      (0x00F2)
#define HOLDING_REGISTER_ADDR_23      (0x00F4)
#define HOLDING_REGISTER_ADDR_24      (0x00F6)
#define HOLDING_REGISTER_ADDR_25      (0x00F8)
#define HOLDING_REGISTER_ADDR_26      (0x00FA)
#define HOLDING_REGISTER_ADDR_27      (0x00FC)
#define HOLDING_REGISTER_ADDR_28      (0x00FE)
#define HOLDING_REGISTER_ADDR_29      (0x0100)
#define HOLDING_REGISTER_ADDR_30      (0x0102)
#define HOLDING_REGISTER_ADDR_31      (0x0104)
#define HOLDING_REGISTER_ADDR_32      (0x0106)
#define HOLDING_REGISTER_ADDR_33      (0x0108)
#define HOLDING_REGISTER_ADDR_34      (0x010A)
#define HOLDING_REGISTER_ADDR_35      (0x010C)
#define HOLDING_REGISTER_ADDR_36      (0x010E)
#define HOLDING_REGISTER_ADDR_37      (0x0110)
#define HOLDING_REGISTER_ADDR_38      (0x0112)
#define HOLDING_REGISTER_ADDR_39      (0x0114) //
#define HOLDING_REGISTER_ADDR_40      (0x0116)
#define HOLDING_REGISTER_ADDR_41      (0x0118)
#define HOLDING_REGISTER_ADDR_42      (0x011A)
#define HOLDING_REGISTER_ADDR_43      (0x011C)
#define HOLDING_REGISTER_ADDR_44      (0x011E)
#define HOLDING_REGISTER_ADDR_45      (0x0120)
#define HOLDING_REGISTER_ADDR_46      (0x0122)
#define HOLDING_REGISTER_ADDR_47      (0x0124)
#define HOLDING_REGISTER_ADDR_48      (0x0126)
#define HOLDING_REGISTER_ADDR_49      (0x0128)
#define HOLDING_REGISTER_ADDR_50      (0x012A)
#define HOLDING_REGISTER_ADDR_51      (0x012C)
#define HOLDING_REGISTER_ADDR_52      (0x012E)
#define HOLDING_REGISTER_ADDR_53      (0x0130)
#define HOLDING_REGISTER_ADDR_54      (0x0132)
#define HOLDING_REGISTER_ADDR_55      (0x0134)
#define HOLDING_REGISTER_ADDR_56      (0x0136)
#define HOLDING_REGISTER_ADDR_57      (0x0138)
#define HOLDING_REGISTER_ADDR_58      (0x013A)
#define HOLDING_REGISTER_ADDR_59      (0x013C) //
#define HOLDING_REGISTER_ADDR_60      (0x013E)
#define HOLDING_REGISTER_ADDR_61      (0x0140)
#define HOLDING_REGISTER_ADDR_62      (0x0142)
#define HOLDING_REGISTER_ADDR_63      (0x0144)
#define HOLDING_REGISTER_ADDR_64      (0x0146)
#define HOLDING_REGISTER_ADDR_65      (0x0148)
#define HOLDING_REGISTER_ADDR_66      (0x014A)
#define HOLDING_REGISTER_ADDR_67      (0x014C)
#define HOLDING_REGISTER_ADDR_68      (0x014E)
#define HOLDING_REGISTER_ADDR_69      (0x0150)
#define HOLDING_REGISTER_ADDR_70      (0x0152)
#define HOLDING_REGISTER_ADDR_71      (0x0154)
#define HOLDING_REGISTER_ADDR_72      (0x0156)
#define HOLDING_REGISTER_ADDR_73      (0x0158)
#define HOLDING_REGISTER_ADDR_74      (0x015A)
#define HOLDING_REGISTER_ADDR_75      (0x015C)
#define HOLDING_REGISTER_ADDR_76      (0x015E)
#define HOLDING_REGISTER_ADDR_77      (0x0160)
#define HOLDING_REGISTER_ADDR_78      (0x0162)
#define HOLDING_REGISTER_ADDR_79      (0x0164) //
#define HOLDING_REGISTER_ADDR_80      (0x0166)
#define HOLDING_REGISTER_ADDR_81      (0x0168)
#define HOLDING_REGISTER_ADDR_82      (0x016A)
#define HOLDING_REGISTER_ADDR_83      (0x016C)
#define HOLDING_REGISTER_ADDR_84      (0x016E)
#define HOLDING_REGISTER_ADDR_85      (0x0170)
#define HOLDING_REGISTER_ADDR_86      (0x0172)
#define HOLDING_REGISTER_ADDR_87      (0x0174)
#define HOLDING_REGISTER_ADDR_88      (0x0176)
#define HOLDING_REGISTER_ADDR_89      (0x0178)
#define HOLDING_REGISTER_ADDR_90      (0x017A)
#define HOLDING_REGISTER_ADDR_91      (0x017C)
#define HOLDING_REGISTER_ADDR_92      (0x017E)
#define HOLDING_REGISTER_ADDR_93      (0x0180)
#define HOLDING_REGISTER_ADDR_94      (0x0182)
#define HOLDING_REGISTER_ADDR_95      (0x0184)
#define HOLDING_REGISTER_ADDR_96      (0x0186)
#define HOLDING_REGISTER_ADDR_97      (0x0188)
#define HOLDING_REGISTER_ADDR_98      (0x018A)
#define HOLDING_REGISTER_ADDR_99      (0x018C) //
#define HOLDING_REGISTER_ADDR_100     (0x018E)
#define HOLDING_REGISTER_ADDR_101     (0x0190)
#define HOLDING_REGISTER_ADDR_102     (0x0192)
#define HOLDING_REGISTER_ADDR_103     (0x0184)
#define HOLDING_REGISTER_ADDR_104     (0x0196)
#define HOLDING_REGISTER_ADDR_105     (0x0198)
#define HOLDING_REGISTER_ADDR_106     (0x019A)
#define HOLDING_REGISTER_ADDR_107     (0x019C) 
#define HOLDING_REGISTER_ADDR_108     (0x019E)
#define HOLDING_REGISTER_ADDR_109     (0x01A0)
#define HOLDING_REGISTER_ADDR_110     (0x01A2)
#define HOLDING_REGISTER_ADDR_111     (0x01A4)
#define HOLDING_REGISTER_ADDR_112     (0x01A6)
#define HOLDING_REGISTER_ADDR_113     (0x01A8)
#define HOLDING_REGISTER_ADDR_114     (0x01AA)
#define HOLDING_REGISTER_ADDR_115     (0x01AC) 
#define HOLDING_REGISTER_ADDR_116     (0x01AE)
#define HOLDING_REGISTER_ADDR_117     (0x01B0)
#define HOLDING_REGISTER_ADDR_118     (0x01B2)
#define HOLDING_REGISTER_ADDR_119     (0x01B4)
#define HOLDING_REGISTER_ADDR_120     (0x01B6)
#define HOLDING_REGISTER_ADDR_121     (0x01B8)
#define HOLDING_REGISTER_ADDR_122     (0x01BA)
#define HOLDING_REGISTER_ADDR_123     (0x01BC)
#define HOLDING_REGISTER_ADDR_124     (0x01BE)
#define HOLDING_REGISTER_ADDR_125     (0x01C0)
#define HOLDING_REGISTER_ADDR_126     (0x01C2)
#define HOLDING_REGISTER_ADDR_127     (0x01C4) 
#define HOLDING_REGISTER_ADDR_128     (0x01C6)
#define HOLDING_REGISTER_ADDR_129     (0x01C8)
#define HOLDING_REGISTER_ADDR_130     (0x01CA)
#define HOLDING_REGISTER_ADDR_131     (0x01CC)
#define HOLDING_REGISTER_ADDR_132     (0x01CE)
#define HOLDING_REGISTER_ADDR_133     (0x01D0)
#define HOLDING_REGISTER_ADDR_134     (0x01D2)
#define HOLDING_REGISTER_ADDR_135     (0x01D4) 
#define HOLDING_REGISTER_ADDR_136     (0x01D6)
#define HOLDING_REGISTER_ADDR_137     (0x01D8)
#define HOLDING_REGISTER_ADDR_138     (0x01DA)
#define HOLDING_REGISTER_ADDR_139     (0x01DC)
#define HOLDING_REGISTER_ADDR_140     (0x01DE) 
#define HOLDING_REGISTER_ADDR_141     (0x01E0) 
#define HOLDING_REGISTER_ADDR_142     (0x01E0) //保持寄存器结束地址

#define  ADDR_END                     (0x01E0)
/*此区域定义寄存器地址*/
/********************************************/
#define  REGISTER_MIN_ADDR								(ADDR_START)//寄存器的起始地址
#define  REGISTER_MAX_ADDR								(ADDR_END)  //寄存器的结束地址

/*离散变量是指其数值只能用自然数或整数单位计算的则为离散变量,离散量、开关量、数字量都是对同一类型信号的不同说法,0表示断开,1表示接通*/
#define  FuncCode_ReadCoilReg					(0x01)    //读取一个或多个连续线圈状态
#define  FuncCode_ReadDiscreteReg 		(0x02)		//读离散输入寄存器,读取一个或多个连续离散输入状态,只读的开关量
#define  FuncCode_ReadHoldReg					(0x03) 		//读保持寄存器,读取一个或多个保持寄存器数据
#define  FuncCode_ReadInputReg				(0x04)		//读输入寄存器,读取一个或多个连续输入寄存器数据,只读的模拟量
#define  FuncCode_WriteSingleCoilReg	(0x05)		//写单个线圈寄存器,	操作指定位置的线圈状态
#define  FuncCode_WriteSingleHoldReg	(0x06)		//写单个保持寄存器,把两个十六进制数据写入对应位置
#define  FuncCode_WriteMoreCoilReg		(0x0F)		//写多个线圈寄存器,操作多个连续线圈状态
#define  FuncCode_WriteMoreHoldReg		(0x10)		//写多个保持寄存器,把4*N个十六进制数据写入N个连续保持寄存器

#define   RtuFrameLen			(sizeof(RtuFrameFormat_OBJ))


/*Modbus485数据帧存储格式*/
typedef struct
{
//	unsigned char DevID;				   //设备Modbus ID(地址)
//	unsigned char FuncCode;				 //Modbus协-议功能码
//	unsigned char StartAddr[2]; 	//超始地址高低字节 
//	unsigned char RegNum[2]; 			//寄存器数量高低字节
//	unsigned char DataHL[2]; 			//接收单个线圈时数据高低位
	unsigned char DataLen;        //标识数据长度
	unsigned char Data[RtuFrameMaxLen];	
}RtuFrameFormat_OBJ;

typedef enum{
	RS485_MASTER_MODE = 1, 
	RS485_SLAVE_MODE  = 2
}MASTER_SLAVE_MODE;

typedef struct
{
 	 RtuFrameFormat_OBJ RtuFrameFormat;
	 unsigned char RtuFrameBuf[RtuFrameLen];
}RtuFrame_OBJ;

/*Modbus485通讯数据帧 控制结构体*/
typedef struct 
{
	RtuFrame_OBJ  FrameRx;   //读取帧
	RtuFrame_OBJ  FrameTx;  //接收帧
	unsigned char Frame_Rx_Step;//接收帧_步
	unsigned char Frame_DataIndex; //数据索引帧
	unsigned char Frame_Len;
	unsigned char SlaveID; 
	unsigned char Frame_type; // 标识帧类型, 0--表示非转发  1--表示转发
	unsigned char Rx_Flag;      
	unsigned char Rx_ByteTimeout; //计算字节间的接收超时时间
 	unsigned int  Rx_Total_Time;  //计算接收时消耗的总时间,
}Rtu_Frame_Ctrl_OBJ;

typedef enum
{
	Frame_Rx_DevID, //0,设备ID帧
	Frame_Rx_FuncCode,//1 功能码
	Frame_Rx_StartAddrH, //2 起始高地址
	Frame_Rx_StartAddrL,//3  起始低地址
	Frame_Rx_RegNumH,//4      
	Frame_Rx_RegNumL,//5
	Frame_Rx_DataH,//6
	Frame_Rx_DataL,	//7
	Frame_RX_DataLen,//8
	Frame_RX_Data,//9
	Frame_RX_RecvCRCH,   //0x0A       10
	Frame_RX_RecvCRCL,//11
	Reply_Frame_DataLen, // 主机模式下: 用于回复帧数据接收12
	Reply_Frame_Data,	   // 主机模式下: 用于回复帧数据接收13
	Frame_Rx_StepOver,//14
}FrameRxStep_OBJ;

  
typedef enum{
	NONE_PARITY = 0, 
	ODD_PARITY,  //奇校验
	EVEN_PARITY, //偶校验
}RS485_PARIATY;

typedef enum{
	MASTER_MODE=1, //主机模式
  SLAVE_MODE=2,	 //从机模式
}RS485_WORKMOD;  //区分主机和从机模式,以便拓展


typedef struct {
	unsigned char setFlag;  //设置状态
	unsigned int  baudrate;  //波特率
	unsigned char databit;  //数据位
	unsigned char stopbit;  //停止位
	unsigned char paritybit; //奇偶校验位
	unsigned char masterSlaveMode; //主/从机模式
	unsigned char slaveAddr;       //主从机地址
} RS485_CTRL_OBJ;



extern Rtu_Frame_Ctrl_OBJ RtuFrame_Ctrl;//协议帧控制
extern RS485_CTRL_OBJ     RS485_Ctrl;


void  Modebus_RtuData_Receive(unsigned char uart_data) ;
unsigned short crc16(unsigned char *puchMsg, unsigned int usDataLen) ;
void MODbus_Osu(void);




#endif
        第三步,编写串口接收函数,并定义结构体变量


Rtu_Frame_Ctrl_OBJ RtuFrame_Ctrl = {0};//协议帧控制
RS485_CTRL_OBJ     RS485_Ctrl={0};


/*******************************************************************************
* 函 数 名         : Modebus_RtuData_Receive()
* 函数功能		     : 存储串口接收的数据
* 输    入         : rxdat,传递串口的接收缓冲区的数据
* 输    出         : 
*******************************************************************************/
void  Modebus_RtuData_Receive(unsigned char rxdat)   
{ 
  if(RtuFrame_Ctrl.Rx_Flag == 0){ 
    //处理期间不接收数据,后续可根据需要拓展队列和缓冲区,以提高响应的速度。
	  RtuFrame_Ctrl.FrameRx.RtuFrameBuf[RtuFrame_Ctrl.Frame_DataIndex++] = rxdat;
    if(RtuFrame_Ctrl.Frame_DataIndex >= RtuFrameMaxLen-1){
      RtuFrame_Ctrl.Frame_DataIndex=0;
      RtuFrame_Ctrl.Rx_ByteTimeout=0;
    }//缓冲区防溢出,此处清零,按需拓展缓冲队列
    else{
     RtuFrame_Ctrl.Rx_ByteTimeout=0;//连续接收到数据则清零字节的时间
    }
  }
}

      串口函数在使用时只需要把串口缓冲区的寄存器作为参数传递进来即可,51接收操作:

      Modebus_RtuData_Receive( S2BUF );   

      32接收操作:

      Modebus_RtuData_Receive( USART2->RDR & 0xff );

      
  第四步,编写主函数中的处理函数
/*

                            _ooOoo_
                           o8888888o
                           88" . "88
                           (| -_- |)
                           O\  =  /O
                        ____/`---'\____
                      .'  \\|     |//  `.
                     /  \\|||  :  |||//  \
                    /  _||||| -:- |||||-  \
                    |   | \\\  -  /// |   |
                    | \_|  ''\---/''  |   |
                    \_  .-\__  `-` ___/-. /
                  ___`. .' _/--.--\_  `. . __
               ."" '<  `.___\_<|>_/___.'  >'"".
              | | :  `- \`.;`\ _ /`;.`/ - ` : | |
              \_ _\ `-.   \_ __\ /__ _/   .-` /  /
         ======`-.____`-.___\_____/___.-`____.-'======
                            `=---='
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                     无极之道在我内心延续!
*/

unsigned char FuncCode_IsRange(unsigned char funcCode)
{
		switch(funcCode)
		{
			case FuncCode_ReadCoilReg :       //读取线圈寄存器
			case FuncCode_ReadDiscreteReg :   //读离散输入寄存器
			case FuncCode_ReadHoldReg :       //读保持寄存器
			case FuncCode_ReadInputReg :      //读输入寄存器
			case FuncCode_WriteSingleCoilReg ://写单个线圈寄存器
			case FuncCode_WriteSingleHoldReg ://写单个保持寄存器
			case FuncCode_WriteMoreCoilReg :  //写多个线圈寄存器
			case FuncCode_WriteMoreHoldReg :	//写多个保持寄存器
					 return 1; 
			default: 
					 return 0; 
		}
}

void RtuFuncCode_Handle(void)/
{
  unsigned short addr   = 0;//保存接收到的地址
  unsigned char Illegal_error=0;
  /*接收完成对数据进行解析*/
    if(RS485_Ctrl.slaveAddr == RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_DevID]){ //检查地址
      if(FuncCode_IsRange(RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_FuncCode])  != 0){ //检查功能码
        addr = ((unsigned short)(RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_StartAddrH]<<8)| RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_StartAddrL]);
        if(addr <= REGISTER_MAX_ADDR && addr >= REGISTER_MIN_ADDR){
            /*解析完成对功能码进行处理*/
            switch(RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_FuncCode]) 
            {      
              case FuncCode_ReadCoilReg: 
                #if (FuncCode01_ENABLE == 1)
                Illegal_error = FuncCode01_Handle(addr);
                #endif
                break;        /*读取线圈状态,读取一个或多个连续线圈状态*/
              case FuncCode_ReadDiscreteReg:
                #if (FuncCode02_ENABLE == 1)
                Illegal_error = FuncCode02_Handle(addr);
                #endif
                break;        /*读取离散量输入,读取一个或多个连续离散输入状态*/
              case FuncCode_ReadHoldReg:
                #if (FuncCode03_ENABLE == 1)
                Illegal_error = FuncCode03_Handle(addr);
                #endif
              break;       /*读取保持寄存器,读取一个或多个保持寄存器数据*/
              case FuncCode_ReadInputReg: 
                #if (FuncCode04_ENABLE == 1)
                Illegal_error = FuncCode04_Handle(addr);
                #endif
                break;       /*读取输入寄存器,读取一个或多个连续输入寄存器数据*/
              case FuncCode_WriteSingleCoilReg:
                #if (FuncCode05_ENABLE == 1)
                Illegal_error = FuncCode05_Handle(addr);
                #endif
                break;       /*写单个单线圈,操作指定位置的线圈状态*/ 
              case FuncCode_WriteSingleHoldReg:
                #if (FuncCode06_ENABLE == 1)
                Illegal_error = FuncCode06_Handle(addr);
                #endif
                break;       /*设置单个寄存器,把两个十六进制数据写入对应位置 */ 
              case FuncCode_WriteMoreCoilReg:
                #if (FuncCode0F_ENABLE == 1)
                Illegal_error = FuncCode0F_Handle(addr);
                #endif
                break;       /*写多个线圈,操作多个连续线圈状态*/ 
              case FuncCode_WriteMoreHoldReg: 
                #if (FuncCode10_ENABLE == 1)
                Illegal_error = FuncCode10_Handle(addr);
                #endif
                break;       /*预置多寄存器,把4*N个十六进制数据写入N个连续保持寄存器*/ 	
              default:
                  break;			
            }
          /*处理完成,可以重新接收数据*/

        }else{//回复非法的寄存器地址,并从头接收
           Illegal_error=2;
        }
      }else{ //回复非法功能码,并从头接收
        Illegal_error=1;
        return;
      }     
    }else{ //地址错误,不回复,重新接收
      goto exti;
    }
    switch(Illegal_error){
      case 0: break;//校验错误,或者正常执行
      case 1: Illegal_Error_01();break;//非法功能码
      case 2: Illegal_Error_02();break;//非法寄存器地址
      case 3: Illegal_Error_03();break;//非法的数据值,查询数据区的值是从机不允许的值
      case 4: Illegal_Error_04(); break;//从机故障,从机执行主机请求动作时出现不可恢复的错误
      case 5: break;//确认,从机已经接收请求处理数据,但需要较长的处理时间,为避免主机出现超时错误而发送该确认响应,主机以此再发送一个查询程序完成未决定从机是否已完成处理
      case 6: break;//从机设备忙碌,从机正忙于处理一个长时程序命令,请求主机在从机空闲时发送信息
      case 7: break;//否定,从机不能执行查询要求的程序功能时,该代码使用十进制13或14代码,向主机返回一个不成功的编程请求信息,主机应请求诊断从机的错误信息
      case 8: break;//内存奇偶校验错误 ,从机读扩展内存中的数据时,发现有奇偶校验错误,主机按从机的要求重新发送数据请求
      default: break;      
    }
    exti:
    RtuFrame_Ctrl.Rx_ByteTimeout = RtuFrame_Ctrl.Frame_DataIndex = RtuFrame_Ctrl.Rx_Total_Time = RtuFrame_Ctrl.Rx_Flag=0; 
//    if(0){//回复非法的数据值
//      RtuFrame_Ctrl.Rx_ByteTimeout = RtuFrame_Ctrl.Frame_DataIndex = RtuFrame_Ctrl.Rx_Total_Time = RtuFrame_Ctrl.Rx_Flag=0;    
//    }
}

/*
一步一步接收和校验数据,遇到错误数据就丢弃可行但有瑕疵,影响效率且某些极端情况下会出bug,
使用接收超时的方式接收一帧完整的数据再进行处理也可行但同样具有瑕疵,容易造成丢包,
数据错误时会影响其后面所有的数据处理,导致一直收不到正确的数据,无法处理出现数据雨的情况。

  规定每一个字节的接收超时时间不超过2ms,否则视为接收满了一帧数据,接收缓冲区标志置为1,
  开始处理,处理期间打开第二个缓冲区,继续接收数据,这种方式应该时最为合理的,去处理每一个字节的接收超时时间
  这样就可以校验功能码是否错误。
  485modbusRtu不应该存在铺天盖地的数据雨
  如果有这种情况,一般都是总线上受到了信号反射的干扰(经本人实践检验得知),
  此时需要一个接收时间的计时,超过此时间仍然存在不间断的数据,则记录总线错误,关闭对外数据的响应


  每当收到一个字节的数据就清零字节间时间计数,对总的接收时间进行计数
*/
void MODbus_Osu(void){
  
  if(RtuFrame_Ctrl.Frame_DataIndex!=0){
   if(RtuFrame_Ctrl.Rx_ByteTimeout++ > Rx_MaxByteTimeout){ //若字节间的超时时间大于4ms,且收到了数据,则视为接收满了一帧数据
     RtuFrame_Ctrl.Rx_Flag=1;
     RtuFrame_Ctrl.Rx_Total_Time = RtuFrame_Ctrl.Frame_DataIndex = RtuFrame_Ctrl.Rx_ByteTimeout =0;
   }else{ //否则累计数据接收时间
     if(RtuFrame_Ctrl.Rx_Total_Time++ >= Rx_MaxTotal_Time ){
     /*数据接收过程中,连续接收时间不允许超过接收总时间,否则视为总线错误,通讯受到强烈干扰,出现数据雨,错误处理自行编辑,此处只是重新接收*/
        RtuFrame_Ctrl.Rx_ByteTimeout = RtuFrame_Ctrl.Frame_DataIndex = RtuFrame_Ctrl.Rx_Total_Time = RtuFrame_Ctrl.Rx_Flag=0;
//       	memset(RtuFrame_Ctrl.FrameRx.RtuFrameBuf,0,sizeof(RtuFrame_Ctrl.FrameRx.RtuFrameBuf));
     }
   }
  }
  if(RtuFrame_Ctrl.Rx_Flag == 1){
    RtuFuncCode_Handle();
  }
}

     这段函数我使用了一条goto语句,当地址都错误的时候,说明这一段数据,是不属于自己的,或者说是不正确的,为了节约时间,我是用goto跳过switch的判断,直接开始重新接收数据。 

第五步,编写功能码处理函数
——01功能码

#if (FuncCode01_ENABLE == 1)
char FuncCode01_Handle(unsigned short addr){
  unsigned short crcData; 
  unsigned short crcData2; 
  unsigned short receive_regNum;
   char i,j; 
//  unsigned char regNum= OUT_COIL_ADDR_END+1-OUT_COIL_ADDR_START;//定义线圈的寄存器总数量
  /*01功能码主机发送格式举例:01 01 00 01 00 01 AC 0A
            从机回复格式举例:01 01 01 01 90 48
  线圈指令每一个地址只需要一个bit就能表示,故8个地址对应一个字节
  */
  i=j=0;
  crcData2 = ((unsigned short)(RtuFrame_Ctrl.FrameRx.RtuFrameBuf[6] <<8) | RtuFrame_Ctrl.FrameRx.RtuFrameBuf[7]);
  crcData = crc16(RtuFrame_Ctrl.FrameRx.RtuFrameBuf,6);
  if(crcData == crcData2){ //校验通过
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen=0;
    memset(RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data,0,sizeof(RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data));
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++] = RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_DevID];
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++] = RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_FuncCode];
     if(addr >= OUT_COIL_ADDR_START && addr <= OUT_COIL_ADDR_END){ //地址有效
       receive_regNum = ((unsigned short)RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_RegNumH]<<8 | RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_RegNumL]);
       if((receive_regNum > OUT_COIL_ADDR_END+1-addr) || receive_regNum <=0){ //指定寄存器数据超过数量限制  
         return 3;//非法的数据值,寄存器数量错误
       }
       if(receive_regNum%8 == 0){
       RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen] = receive_regNum/8;//计算要返回的字节大小
       }
       else{
       RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen] = receive_regNum/8+1;
       }     
      /*从起始地址开始读指定寄存器的数量*/
       for(i=0;i> 8;
      RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++]= crcData & 0xff;
      SendData4(RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data,RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen,2);
    }
     else{
      return 2;//非法的寄存器地址
    }
  }
  return 0;//正常执行,或者校验码错误,不回复
}
#endif

    上面这段代码中,需要根据自己的项目地址和数据的名称,就是我注释掉的这一段。   RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen] |= (((RELAY_Ctrl.Status[i+addr] == RELAY_ON)? 0x01 : 0x00)<<(j++)); 

还有这个:

     if(addr >= OUT_COIL_ADDR_START && addr <= OUT_COIL_ADDR_END){ //地址有效
       receive_regNum = ((unsignedshort)RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_RegNumH]<<8 | RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_RegNumL]);
       if((receive_regNum >
OUT_COIL_ADDR_END+1-addr) || receive_regNum <=0){ //指定寄存器数据超过数量限制  
         return 3;//非法的数据值,寄存器数量错误
       }

OUT_COIL_ADDR_START 这些是地址的宏定义,在.H文件中,目前这个框架我预留了100个可读可写的输出线圈,和100个只读输入线圈,所以保持寄存器的地址是从  0X00C8  开始的

另外要说明的是,03的保持寄存器和04输入寄存器,都是读的模拟量,也就是字节数据,02离散寄存器和01的线圈,都是属于开关量,一个位表示一个状态。

     红色部分是需要根据实际情况修改的,RELAY_Ctrl.Status 是一个数组记录着每一个线圈的输出状态,RELAY_ON是一个表示输出打开的宏定义,修改的话只需要改这两个就可以了。

 ——编写功能码处理函数——02功能码

#if (FuncCode02_ENABLE == 1)
char FuncCode02_Handle(unsigned short addr){
  unsigned short crcData; 
  unsigned short crcData2; 
  unsigned short receive_regNum;
  unsigned char i,j; 
  crcData2 = ((unsigned short)(RtuFrame_Ctrl.FrameRx.RtuFrameBuf[6] <<8) | RtuFrame_Ctrl.FrameRx.RtuFrameBuf[7]);
  crcData = crc16(RtuFrame_Ctrl.FrameRx.RtuFrameBuf,6);
  if(crcData == crcData2){ //校验通过
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen=0;
    memset(RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data,0,sizeof(RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data));
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++] = RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_DevID];
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++] = RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_FuncCode];
   if(addr >= IN_COIL_ADDR_START && addr <= IN_COIL_ADDR_END){ //地址有效
     if(addr >= IN1_COIL_ADDR && addr <= IN20_COIL_ADDR){ //地址有效
       receive_regNum = ((unsigned short)RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_RegNumH]<<8 | RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_RegNumL]);
       if((receive_regNum > IN20_COIL_ADDR+1-addr) || receive_regNum <=0){ //指定寄存器数据超过数量限制  
         return 3;
       }
       if(receive_regNum%8 == 0){
         RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen] = receive_regNum/8;//计算要返回的字节大小
       }
       else{
         RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen] = receive_regNum/8+1;
       }
      /*从起始地址开始读指定寄存器的数量*/
       addr=addr-IN1_COIL_ADDR;
       for(i=0;i= IN21_COIL_ADDR && addr <= IN40_COIL_ADDR){
       receive_regNum = ((unsigned short)RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_RegNumH]<<8 | RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_RegNumL]);
       if((receive_regNum > IN20_COIL_ADDR+1-addr) || receive_regNum <=0){ //指定寄存器数据超过数量限制  
         return 3;
       }
       if(receive_regNum%8 == 0){
         RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen] = receive_regNum/8;//计算要返回的字节大小
       }
       else{
         RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen] = receive_regNum/8+1;
       }
      /*从起始地址开始读指定寄存器的数量*/
       addr=addr-IN21_COIL_ADDR;
       for(i=0;i> 8;
      RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++]= crcData & 0xff;
      SendData4(RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data,RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen,2);
     }
     else{
       return 2;
    }
     
  }
  return 0;
}
#endif

       02功能码是读离散寄存器,只读位,离散表示的也是开关量,因为只不过叫法不同,据说当初这个协议在创造时是为PLC而生的,离散变量是他们那一行的术语。           RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen]   |=   (((HC165_IN_Ctrl.Bit_Status[i+addr] == HC165_IN_EXIST)? 0x01 : 0x00)<<(j++));

以这个为例同样要修改也只是标红的部分,程序逻辑和01功能码是一样的。还有一点要注意的就是地址和寄存器的对应关系,设定的寄存器数要和你的数组数量对应起来

   if(addr >= IN1_COIL_ADDR && addr <= IN20_COIL_ADDR){ //地址有效  

   if((receive_regNum > IN20_COIL_ADDR+1-addr) || receive_regNum <=0){  

   这两句的这个地址提一下,它是一个宏定义分配的地址,分别用于检查地址是否正确和寄存器的数量是否正确,它具体值取决于你项目中的线圈数量,比如说我这个这个项目有3个74HC165芯片来采集输入信号,3个级联可以采集24路,但是项目只需要采集20路即可,所以这个地址是20个,起始地址和结束地址自己定义,但是它们相差的数量一定要是20个,对于开关量来说,一个地址提供1个位的状态,一个字节可以读取8个位的状态,所以01,02,05,0F都是按位来进行控制和回复的。

——编写功能码处理函数——03功能码

#if (FuncCode03_ENABLE == 1)
char FuncCode03_Handle(unsigned short addr){
  unsigned short crcData; 
  unsigned short crcData2; 
  unsigned short receive_regNum;
  unsigned short i;
  crcData2 = ((unsigned short)(RtuFrame_Ctrl.FrameRx.RtuFrameBuf[6] <<8) | RtuFrame_Ctrl.FrameRx.RtuFrameBuf[7]);
  crcData = crc16(RtuFrame_Ctrl.FrameRx.RtuFrameBuf,6);
  if(crcData == crcData2){ //校验通过
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen=0;
//    memset(RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data,0,sizeof(RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data));
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++] = RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_DevID];
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++] = RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_FuncCode];   

     receive_regNum = ((unsigned short)RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_RegNumH]<<8 | RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_RegNumL]);
     /*计算要返回的字节大小*/
     RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++] = receive_regNum*2;
      /*读交流电温度*/
    if(addr >= HOLDING_REGISTER_ADDR_1 && addr <= HOLDING_REGISTER_ADDR_1){ 
       if((receive_regNum > HOLDING_REGISTER_ADDR_1+1-addr) || receive_regNum <=0){ //指定寄存器数据超过数量限制  
         return 3;
       } 
        /*从起始地址开始读指定寄存器的数量*/
         for(i=0;i>8;//取高位       
           RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++] = volt_read&0xff;//取低位
         }   
     }
    /*此处添加寄存器地址范围的功能 ,读取定时器1的设定值*/
     else if(addr >= HOLDING_REGISTER_ADDR_6 && addr <= HOLDING_REGISTER_ADDR_6){
        if((receive_regNum > HOLDING_REGISTER_ADDR_6+1-addr) || receive_regNum <=0){ //指定寄存器数据超过数量限制  
          return 3;
        }  
        
         for(i=0;i>8; 
           RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++] = Timer_GetCounter(1)&0xff;
         }           
     }
     /*此处添加寄存器地址范围的功能*/
//     else if(addr >= HOLDING_REGISTER_ADDR_20 && addr <= HOLDING_REGISTER_ADDR_40){
//        if((receive_regNum > HOLDING_REGISTER_ADDR_40+1-addr) || receive_regNum <=0){ 
//          return 3;
//        }
//          addr = (addr - HOLDING_REGISTER_ADDR_20)>>1;
//         for(i=0;i>8;//取巴氏消毒温度值的高位       
//           RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++] = MRTD3011_Temperature_Ctrl.Temperature_Set_2[i + addr]&0xff;//取巴氏消毒温度值的低位
//         }           
//     }
  
     else{
      return 2;
    }
       crcData = crc16(RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data,RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen);
       RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++]= crcData >> 8;
       RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++]= crcData & 0xff;
       SendData4(RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data,RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen,2);  
  }
    return 0;
}
#endif

   要根据实际情况修改的地方,就是起始地址和结束地址,以及你的项目中的内存数据,举例:

     else if(addr >= HOLDING_REGISTER_ADDR_6 && addr <= HOLDING_REGISTER_ADDR_6){
        if((receive_regNum > HOLDING_REGISTER_ADDR_6+1-addr) || receive_regNum <=0){ //指定寄存器数据超过数量限制  
          return 3;
        }  


         for(i=0;i            RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++] = Timer_GetCounter(1)>>8; 
           RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++] = Timer_GetCounter(1)&0xff;
         }           
     }

        读多个寄存器的方式:

//     else if(addr >= HOLDING_REGISTER_ADDR_20 && addr <= HOLDING_REGISTER_ADDR_40){
//        if((receive_regNum > HOLDING_REGISTER_ADDR_40+1-addr) || receive_regNum <=0){ 
//          return 3;
//        }
//          addr = (addr - HOLDING_REGISTER_ADDR_20)>>1;
//         for(i=0;i //           RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++] = MRTD3011_Temperature_Ctrl.Temperature_Set_2[i + addr]>>8;//取巴氏消毒温度值的高位       
//           RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++] = MRTD3011_Temperature_Ctrl.Temperature_Set_2[i + addr]&0xff;//取巴氏消毒温度值的低位
//         }           
//     }

      我标为红色的地方就是在移植时需要根据项目实际情况修改的。

      有一段忘了说,在回复数据的时候,为什么说它51和32都是通用的,就算当你从51移植到32时

SendData4(RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data,RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen,2); 

    只需要修改串口的回复函数,和串口的接收函数,即可,我这个是自己写的51的串口发送函数:具体实现代码:

void UartSend(char dat)
{
    while (busy_1);
    busy_1 = 1;
    SBUF = dat;
}
void Uart2Send(char dat)
{
    while (busy_2);
    busy_2 = 1;
    S2BUF = dat;
}
void Uart3Send(char dat)
{
    while ( busy_3 );
	   busy_3 = 1; 
     S3BUF = dat;
}

void Uart4Send(char dat)
{
    while (busy_4);
	   busy_4 = 1; 
    S4BUF = dat;
}


void SendData4(unsigned char *p,unsigned char len ,uchar num )
{
        unsigned char i;
        for(i=0;i

        这样一个函数写完51的所有串口,在使用中比较方便。

     32串口发送函数实现源码:仅供参考,按需使用和修改

/*******************************************************************************
* 函 数 名: UART_Send1Data 
* 函数功能: 串口发送单个字节
* 输    入: 
					 USARTx:串口通道选择 ——> USART1-UART5 usart 和uart是有区别的,自行百度
           Byte:  要发送的字节
* 输    出: 无
*******************************************************************************/ 
void UART_Send1Data(USART_TypeDef * USARTx ,uint8_t Byte)
{
	USART_SendData(USARTx, Byte);
	while (USART_GetFlagStatus(USARTx, USART_FLAG_TXE) == RESET);
}
/*******************************************************************************
* 函 数 名: UART_Send_Len_Data 
* 函数功能: 串口发送多个字节
* 输    入: 
					 USARTx:串口通道选择 ——> USART1-UART5 usart 和uart是有区别的,自行百度
           Array: 要发送数据的指针
           Length:指针长度
* 输    出: 无
*******************************************************************************/ 
void UART_Send_Len_Data(USART_TypeDef * USARTx ,uint8_t *Array, uint16_t Length)
{
	uint16_t i;
	for (i = 0; i < Length; i ++)
	{
		UART_Send1Data(USARTx,Array[i]);
	}
}

void SendData(unsigned char *p,unsigned char len ,uint8_t num )
{
		unsigned char i;
		for(i=0;i
——编写功能码处理函数——04功能码

#if (FuncCode04_ENABLE == 1)
char FuncCode04_Handle(unsigned short addr){
  unsigned short crcData; 
  unsigned short crcData2; 
  unsigned short receive_regNum;
  unsigned char  i=0;
  crcData2 = ((unsigned short)(RtuFrame_Ctrl.FrameRx.RtuFrameBuf[6] <<8) | RtuFrame_Ctrl.FrameRx.RtuFrameBuf[7]);
  crcData = crc16(RtuFrame_Ctrl.FrameRx.RtuFrameBuf,6);
  if(crcData == crcData2){ //校验通过
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen=0;
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++] = RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_DevID];
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++] = RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_FuncCode];  
    receive_regNum = ((unsigned short)RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_RegNumH]<<8 | RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_RegNumL]);   
    /*计算要返回的字节大小*/
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++] = receive_regNum*2;    
   if(addr >= HOLDING_REGISTER_ADDR_80 && addr <= HOLDING_REGISTER_ADDR_100){ //读实时温度

       if((receive_regNum > HOLDING_REGISTER_ADDR_100+1-addr) || receive_regNum <=0){ //指定寄存器数据超过数量限制  
         return 3;
       } 
       addr = (addr - HOLDING_REGISTER_ADDR_80)>>1;
       /*从起始地址开始读指定寄存器的数量*/
         for(i=0;i>8;//取温度值的高位       
           RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++] = MRTD3011_Temperature_Ctrl.Temperature[i + addr]&0xff;//取温度值的低位
         }  
           crcData = crc16(RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data,RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen);
           RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++]= crcData >> 8;
           RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++]= crcData & 0xff;
           SendData4(RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data,RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen,2);         
     }
     else{
      return 2;
    }
  }
    return 0;
}
#endif
——编写功能码处理函数——05功能码

#if (FuncCode05_ENABLE == 1)
char FuncCode05_Handle(unsigned short addr){
  /*
主机发送:01 05 00 00 00 00 CD CA 
从机返回:01 05 00 00 00 00 cd ca  
  */
  unsigned short crcData; 
  unsigned short crcData2; 
  unsigned short receive_data;
  unsigned char  i=0;
  crcData2 = ((unsigned short)(RtuFrame_Ctrl.FrameRx.RtuFrameBuf[6] <<8) | RtuFrame_Ctrl.FrameRx.RtuFrameBuf[7]);
  crcData = crc16(RtuFrame_Ctrl.FrameRx.RtuFrameBuf,6);
  if(crcData == crcData2){ //校验通过
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen=0;
//    memset(RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data,0,sizeof(RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data));
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++] = RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_DevID];
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++] = RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_FuncCode];
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++] = RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_StartAddrH];
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++] = RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_StartAddrL]; 
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++] = RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_RegNumH];
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++] = RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_RegNumL];   
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++] = RtuFrame_Ctrl.FrameRx.RtuFrameBuf[6];
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++] = RtuFrame_Ctrl.FrameRx.RtuFrameBuf[7];       
     if(addr >= OUT_COIL_ADDR_START && addr <= OUT_COIL_ADDR_END){ //地址有效
       receive_data = ((unsigned short)RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_RegNumH]<<8 | RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_RegNumL]);  
       if(receive_data == RELAY_ON){receive_data = RELAY_OFF;}else{receive_data = RELAY_ON;}//本项目继电器是低电平有效,但是数据要发0应该关闭,故取反
       OUT_ONOFF((unsigned char)addr,receive_data);
       SendData4(RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data,RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen,2); //正常处理完成就回复  
     }
     else{
      return 2;
    }
  }
    return 0;
}
#endif

——编写功能码处理函数——06功能码

#if (FuncCode06_ENABLE == 1)
char FuncCode06_Handle(unsigned short addr){
  unsigned short crcData; 
  unsigned short crcData2; 
  unsigned int   receive_data;
  crcData2 = ((unsigned short)(RtuFrame_Ctrl.FrameRx.RtuFrameBuf[6] <<8) | RtuFrame_Ctrl.FrameRx.RtuFrameBuf[7]);
  crcData = crc16(RtuFrame_Ctrl.FrameRx.RtuFrameBuf,6);
  if(crcData == crcData2){ //校验通过
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen=0;
//    memset(RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data,0,sizeof(RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data));
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++] = RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_DevID];
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++] = RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_FuncCode];   
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++] = RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_StartAddrH];
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++] = RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_StartAddrL];
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++] = RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_RegNumH];
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++] = RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_RegNumL];
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++] = RtuFrame_Ctrl.FrameRx.RtuFrameBuf[6];
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++] = RtuFrame_Ctrl.FrameRx.RtuFrameBuf[7];
    
    receive_data = ((unsigned short)RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_RegNumH]<<8 | RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_RegNumL]);    

    switch(addr){
      case HOLDING_REGISTER_ADDR_1:{
         /*此处添加寄存器地址范围的功能,*/
  //        if((receive_data > 9999) || receive_data <=0){ //设置该寄存器数据的范围大小
  //         return 3;
  //        } 
        }break;
      case HOLDING_REGISTER_ADDR_2:{
         /*写可控硅斩波输出的电压值*/
          if(receive_data < volt_read){ //设置电压小于输入电压,根据预测算法进行调压
            
            TCVR_Timer_algorithm(receive_data);
            TCVR_Ctrl.Out_cmd = 1;// 
          }
          else{ //否则满幅输出
            TCVR_Ctrl.Out_cmd=0;//关闭过零检测的程序扫描
            TIMER1_START_CTRL(TIME_STOP);
            OUT1_ON;
            OUT2_ON;
          }      
        }break;
      case HOLDING_REGISTER_ADDR_3:{
        /*此处添加寄存器地址范围的功能,写修正系数,将输入的整数除以1000转化为浮点数*/
           if(receive_data & 0x8000){ //检查符号位是否为1
             receive_data&=0x7fff;
             TCVR_Ctrl.Integration_coefficient = (float)-receive_data/1000.0; 
           }else
           {
             TCVR_Ctrl.Integration_coefficient = (float)receive_data/1000.0; 
           }     
        }break;
      case HOLDING_REGISTER_ADDR_4:{
//         Time_Ctrl.time_x[addr-HOLDING_REGISTER_ADDR_4] = receive_data;
        }break; 
      case HOLDING_REGISTER_ADDR_5:{
        /*写输入电压与220V的偏离系数*/
          if(receive_data & 0x8000){ //检查符号位是否为1
             receive_data &= 0x7fff;
             TCVR_Ctrl.Input_voltage_deviation_coefficient  = (float)-receive_data/1000.0; 
           }else
           {
             TCVR_Ctrl.Input_voltage_deviation_coefficient  = (float)receive_data/1000.0; 
           } 
        }break;  
      case HOLDING_REGISTER_ADDR_6:{
         /*设置定时器1的定时值*/
        Timer_SetCounter(1,receive_data);
        }break;      
      default: return 2;
    }  
    SendData4(RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data,RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen,2); //正常处理完成就回复
  }
    return 0;
}
#endif

   //        if((receive_data > 9999) || receive_data <=0){ //设置该寄存器数据的范围大小
  //         return 3;
  //        } 

      这个地方我注释掉了,在使用中,可以用来限制传递的数据的大小和范围,不在这个条件内,就直接返回一个非法的数据值的回复帧。

如果你的数据是数组类型的范围值,那么可以使用以下方式

      /*温奶上限温度设置地址*/
     if(addr >= HOLDING_REGISTER_ADDR_80&& addr <= HOLDING_REGISTER_ADDR_100){ 
       addr = (addr-HOLDING_REGISTER_ADDR_80)>>1;
        MRTD3011_Temperature_Ctrl.Temperature_Set[addr] = receive_data;
     } 

——编写功能码处理函数——0F功能码

#if (FuncCode0F_ENABLE == 1)
char FuncCode0F_Handle(unsigned short addr){
  /*
写从线圈编号0开始的10个线圈:0-3线圈写1,4-7线圈写0,8-9线圈写1(0F 03):
主机发送:01 0F 00 00 00 0A 02 0F 03 A0 C9 //
从机返回:01 0f 00 00 00 0a d5 cc          //数据区回复已写的线圈数量
  */
  unsigned short crcData; 
  unsigned short crcData2; 
  unsigned short receive_regNum;
  unsigned short crc_Position;
  unsigned char  rx_byte_regnum;
  unsigned char  write_data[12]; //(12)*8=96,支持96个线圈的数据
  unsigned char i,j,k=0;
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen=0;
//    memset(RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data,0,sizeof(RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data));
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++] = RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_DevID];
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++] = RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_FuncCode]; 
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++] = RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_StartAddrH];
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++] = RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_StartAddrL]; 
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++] = RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_RegNumH];
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++] = RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_RegNumL]; //回复要写的线圈数量    
     if(addr >= OUT_COIL_ADDR_START && addr <= OUT_COIL_ADDR_END){ //地址有效
       receive_regNum = ((unsigned short)RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_RegNumH]<<8 | RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_RegNumL]);
       if((receive_regNum > OUT_COIL_ADDR_END+1-addr) || receive_regNum <=0){ //指定寄存器数据超过数量限制  
         return 3;                                                            //视为寄存器数据错误
       }
       if(receive_regNum%8 == 0){
         if (RtuFrame_Ctrl.FrameRx.RtuFrameBuf[6] != receive_regNum/8){ //非法字节数量,返回非法的数据值的错误回复帧
           return 3;
         }else{
          rx_byte_regnum = RtuFrame_Ctrl.FrameRx.RtuFrameBuf[6];
         }
       }
       else{
         if (RtuFrame_Ctrl.FrameRx.RtuFrameBuf[6] != receive_regNum/8+1){
         return 3;
         }else{
          rx_byte_regnum = RtuFrame_Ctrl.FrameRx.RtuFrameBuf[6];
         }
       }
       crc_Position = rx_byte_regnum+7;
       crcData2 = ((unsigned short)(RtuFrame_Ctrl.FrameRx.RtuFrameBuf[crc_Position] <<8) | RtuFrame_Ctrl.FrameRx.RtuFrameBuf[crc_Position+1]);
       crcData = crc16(RtuFrame_Ctrl.FrameRx.RtuFrameBuf,crc_Position);
       if(crcData == crcData2){ //校验通过
         for(j=0;j> 8;
         RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++] = crcData & 0xff;
         SendData4(RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data,RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen,2); 
       }else{
       return 0;
       }         
     }
     else{
      return 2;
    }

    return 0;
}
#endif
——编写功能码处理函数——10功能码

#if (FuncCode10_ENABLE == 1)
char FuncCode10_Handle(unsigned short addr){
  
  unsigned short crcData; 
  unsigned short crcData2; 
  unsigned short receive_regNum;
  unsigned short crc_Position;
  unsigned char i,j=0;
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen=0;
//    memset(RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data,0,sizeof(RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data));
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++] = RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_DevID];
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++] = RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_FuncCode]; 
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++] = RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_StartAddrH];
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++] = RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_StartAddrL]; 
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++] = RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_RegNumH];
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++] = RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_RegNumL]; //回复要写的寄存器数量    
    crc_Position = RtuFrame_Ctrl.FrameRx.RtuFrameBuf[6]+7;
    crcData2 = ((unsigned short)(RtuFrame_Ctrl.FrameRx.RtuFrameBuf[crc_Position] <<8) | RtuFrame_Ctrl.FrameRx.RtuFrameBuf[crc_Position+1]);
    crcData = crc16(RtuFrame_Ctrl.FrameRx.RtuFrameBuf,crc_Position);
    receive_regNum = ((unsigned short)RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_RegNumH]<<8 | RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_RegNumL]);
   if(crcData == crcData2){ //校验通过
     /*此处添加寄存器地址范围的功能*/
     if(addr >= MRTD_TEMPERATURE_SET1_ADDR && addr <= MRTD_TEMPERATURE_SET20_ADDR){ //地址有效   
       if((receive_regNum > MRTD_TEMPERATURE_SET20_ADDR+1-addr) || receive_regNum <=0){ //指定寄存器数据超过数量限制  
         return 3;                                                                    //视为寄存器数据错误
       }
       if(receive_regNum*2  != RtuFrame_Ctrl.FrameRx.RtuFrameBuf[6]){ //寄存器数量和字节数对不上,视为非法的数据
           return 3;
       }
         for(i=0;i<=receive_regNum;i++){
          MRTD3011_Temperature_Ctrl.Temperature_Set[i + addr - MRTD_TEMPERATURE_SET_ADDR_START] = (unsigned short)RtuFrame_Ctrl.FrameRx.RtuFrameBuf[7+(i*2)]<<8 |RtuFrame_Ctrl.FrameRx.RtuFrameBuf[7+(i*2+1)];      
         }          
     }
     /*此处添加寄存器地址范围的功能*/
     else if(addr >= MRTD_TEMPERATURE_SET21_ADDR && addr <= MRTD_TEMPERATURE_SET40_ADDR){       
        if((receive_regNum > MRTD_TEMPERATURE_SET40_ADDR+1-addr) || receive_regNum <=0){ //指定寄存器数据超过数量限制  
         return 3;                                                                    //视为寄存器数据错误
        }
        if(receive_regNum*2  != RtuFrame_Ctrl.FrameRx.RtuFrameBuf[6]){ //寄存器数量和字节数对不上,视为非法的数据
           return 3;
        }
         for(i=0;i<=receive_regNum;i++){
          MRTD3011_Temperature_Ctrl.Temperature_Set_3[i + addr - MRTD_TEMPERATURE_SET_ADDR_START] = (unsigned short)RtuFrame_Ctrl.FrameRx.RtuFrameBuf[7+(i*2)]<<8 |RtuFrame_Ctrl.FrameRx.RtuFrameBuf[7+(i*2+1)];      
         }       
     }
     /*此处添加寄存器地址范围的功能*/
     else if(addr >= MRTD_TEMPERATURE_SET41_ADDR && addr <= MRTD_TEMPERATURE_SET60_ADDR){
        if((receive_regNum > MRTD_TEMPERATURE_SET60_ADDR+1-addr) || receive_regNum <=0){ //指定寄存器数据超过数量限制  
         return 3;                                                                    //视为寄存器数据错误
        }
        if(receive_regNum*2  != RtuFrame_Ctrl.FrameRx.RtuFrameBuf[6]){ //寄存器数量和字节数对不上,视为非法的数据
           return 3;
        }
         for(i=0;i<=receive_regNum;i++){
          MRTD3011_Temperature_Ctrl.Temperature_Set_2[i + addr - MRTD_TEMPERATURE_SET_ADDR_START] = (unsigned short)RtuFrame_Ctrl.FrameRx.RtuFrameBuf[7+(i*2)]<<8 |RtuFrame_Ctrl.FrameRx.RtuFrameBuf[7+(i*2+1)];      
         }       
     }
      /*此处添加寄存器地址范围的功能*/
     else if(addr >= MRTD_TEMPERATURE_SET61_ADDR && addr <= MRTD_TEMPERATURE_SET80_ADDR){
        if((receive_regNum > MRTD_TEMPERATURE_SET80_ADDR+1-addr) || receive_regNum <=0){ //指定寄存器数据超过数量限制  
         return 3;                                                                    //视为寄存器数据错误
        }
        if(receive_regNum*2  != RtuFrame_Ctrl.FrameRx.RtuFrameBuf[6]){ //寄存器数量和字节数对不上,视为非法的数据
           return 3;
        }
         for(i=0;i<=receive_regNum;i++){
          MRTD3011_Temperature_Ctrl.Temperature_Set_4[i + addr - MRTD_TEMPERATURE_SET_ADDR_START] = (unsigned short)RtuFrame_Ctrl.FrameRx.RtuFrameBuf[7+(i*2)]<<8 |RtuFrame_Ctrl.FrameRx.RtuFrameBuf[7+(i*2+1)];      
         } 
         for(i=0;i<=receive_regNum;i++){
          Time_Ctrl.time_x[i + addr - MRTD_TEMPERATURE_SET_ADDR_START] = (unsigned short)RtuFrame_Ctrl.FrameRx.RtuFrameBuf[7+(i*2)]<<8 |RtuFrame_Ctrl.FrameRx.RtuFrameBuf[7+(i*2+1)];      
         }       
     }
     /*此处添加寄存器地址范围的功能*/
     else if(addr >= TIME_SET1_ADDR && addr <= DISINFECT_ADDR){
        if((receive_regNum > DISINFECT_ADDR+1-addr) || receive_regNum <=0){ //指定寄存器数据超过数量限制  
         return 3;                                                                    //视为寄存器数据错误
        }
        if(receive_regNum*2  != RtuFrame_Ctrl.FrameRx.RtuFrameBuf[6]){ //寄存器数量和字节数对不上,视为非法的数据
           return 3;
        }
         for(i=0;i<=receive_regNum;i++){
          Time_Ctrl.time_x[i + addr - MRTD_TEMPERATURE_SET_ADDR_START] = (unsigned short)RtuFrame_Ctrl.FrameRx.RtuFrameBuf[7+(i*2)]<<8 |RtuFrame_Ctrl.FrameRx.RtuFrameBuf[7+(i*2+1)];      
         }       
     }
     else{ //传递的寄存器地址本项目中没有,回复寄存器地址错误
      return 2;
    }
      crcData = crc16(RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data,RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen);
      RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++] = crcData >> 8;
      RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++] = crcData & 0xff;
      SendData4(RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data,RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen,2);  
   } 
  return 0;
}
#endif
第六步,编写错误帧回复函数 

void Illegal_Error_01(void){
    unsigned short crcData; 
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen=0;
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++] = RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_DevID];
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++] = RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_FuncCode];
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++] = 0x01;
    crcData = crc16(RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data,RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen);
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++]= crcData >> 8;
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++]= crcData & 0xff;
    SendData4(RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data,RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen,2);    
}
void Illegal_Error_02(void){
    unsigned short crcData; 
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen=0;
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++] = RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_DevID];
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++] = RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_FuncCode];
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++] = 0x02;
    crcData = crc16(RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data,RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen);
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++]= crcData >> 8;
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++]= crcData & 0xff;
    SendData4(RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data,RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen,2);    
}
void Illegal_Error_03(void){
    unsigned short crcData; 
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen=0;
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++] = RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_DevID];
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++] = RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_FuncCode];
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++] = 0x03;
    crcData = crc16(RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data,RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen);
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++]= crcData >> 8;
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++]= crcData & 0xff;
    SendData4(RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data,RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen,2);    
}
void Illegal_Error_04(void){
    unsigned short crcData; 
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen=0;
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++] = RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_DevID];
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++] = RtuFrame_Ctrl.FrameRx.RtuFrameBuf[Frame_Rx_FuncCode];
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++] = 0x04;
    crcData = crc16(RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data,RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen);
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++]= crcData >> 8;
    RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data[RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen++]= crcData & 0xff;
    SendData4(RtuFrame_Ctrl.FrameTx.RtuFrameFormat.Data,RtuFrame_Ctrl.FrameTx.RtuFrameFormat.DataLen,2);    
}

  常用的功能码到这里就结束了,虽然这并不是ModBus-RTU协议的全貌,但也满足了百分之90的使用场合了 。

小结

  本文篇幅较长,能看到这里的都很有有毅力,有恒心,未来必成大佬!

你可能感兴趣的:(485通信,单片机,c语言,信息与通信)