单片机作为从机设备
(以stm32F103开发板为例)
一、解析串口2接收的数据
将从串口2接收到的温湿度数据中的有效数据解析提取出来,将十进制数转换为16进制数并保存到对应缓冲区
#define SIZE 10 //缓冲区大小
u16 Temperature[SIZE]={0}; //温度数据缓冲区
u16 Humidity[SIZE]={0}; //湿度数据缓冲区
/**
* 描述 : 将从串口2接收到的数据中的有效数据解析提取出来
* 参数 : cmd--字符串指令
* 返回 : 无
*/
void DHT11_Rx_data(char* cmd)
{
int i=0;
int wendu=0;
int shidu=0;
memset(rxbuff,0,sizeof(rxbuff));
while(i<10) //读取十个温湿度数据到相应数组中进行保存
{
Usart2_SendString(cmd); //发送指令
delay_ms(1000);
sscanf(rxbuff,"get_data,24,28,40,70,%d,%d,close,close,manual,manual",&wendu,&shidu); //将温湿度解析转换出来
Temperature[i]=data_deal(wendu); //将十进制数转为十六进制数
Humidity[i]=data_deal(shidu);
// printf("te[%d]=%x,hu[%d]=%x\r\n",i,Temperature[i],i,Humidity[i]);
i++;
}
}
/**
* 描述 : 将十进制数转换两个字节的16进制数
* 参数 : value--十进制数
* 返回 : 16进制数
*/
u16 data_deal(int value)
{
int i=0;
int j=0;
int k=0;
i=value/256; //高八位 温湿度达不到256,所以高八位不用分高四位低四位
j=value/16; //低八位中的高四位
k=value%16; //低八位中的低四位
return (i<<8|j<<4|k);
}
二、设置从机设备ID及地址
设备作为从机使用时肯定有自己专门的设备地址和相关的寄存器,先定义一个寄存器:主机读数据和写数据时操作的寄存器
如下模拟了三个从机设备ID及地址
//设备ID
#define Req_add 0x02 //模拟数据设备ID
#define Temperature_add 0x04 //温度设备ID
#define Humidity_add 0x05 //湿度设备ID
//设备对应数据地址
#define SIZE 10 //缓冲区大小
u16 Temperature[SIZE]={0}; //温度数据缓冲区
u16 Humidity[SIZE]={0}; //湿度数据缓冲区
u16 Reg[8]={0x0001,
0x0012,
0x0013,
0x0004,
0x0025,
0x0036,
0x0007,
0X0008,
};//reg是提前定义好的寄存器和寄存器数据,要读取和改写的部分内容
本设备作为从机时的地址
/**
* 描述 : Modbus初始化函数
* 参数 : addr--从机设备地址
* 返回 : 无
*/
void Modbus_Init(u8 addr)
{
modbus.myadd=addr; //从机设备地址
modbus.timrun=0; //modbus定时器停止计算
}
三、主机问询从机
1、端口连接
这里主机使用的是ModBus调试助手
(1)选择串行端口
(2)485连接端口
(3)串口参数配置---波特率、数据位、奇偶校验、停止位
(4)RTU帧格式
(5)超时时间和帧间隔时间(实际就是每个窗口之间的发送间隔),超时时间可以按默认1s就行,Modbus国标规定是200ms到1s。
帧间隔时间按默认20ms设置即可。因串口2发送指令间隔为1秒采集到的数据才稳定,数据缓存区大小为10,故这里超时时间
设置为10秒。
设置完点OK即可完成设置进行连接。
2、配置窗口信息
当前窗口配置----从机ID号为0x05(湿度)
功能码0x03(读取寄存器数据)
地址码0x0000(从数组中下标为0的数据开始读取)
读取寄存器个数0x0009
点击OK,主机向从机发送问询帧-----ID号为0x05的设备,读取地址码为0x0000开始的九个寄存器数据
最后两个字节为CRC校验码
四、从机应答主机
1、ModBus报文RTU帧
在 RTU 模式,报文帧由时长至少为 3.5 个字符时间的空闲间隔区分。在后续的部分,这个时间区间被称作 t3.5。
整个报文帧必须以连续的字符流发送。如果两个字符之间的空闲间隔大于 1.5 个字符时间,则报文帧被认为不完整应该被接收节点丢弃。
RTU 接收驱动程序的实现,由于 t1.5 和 t3.5 的定时,隐含着大量的对中断的管理。在高通信速率下,这导致 CPU 负担加重。
因此,在通信速率等于或低于 19200 Bps 时,这两个定时必须严格遵守;
对于波特率大于 19200 Bps 的情形,应该使用 2 个定时的固定值:建议的字符间超时时间(t1.5)为 750µs,帧间的超时时间 (t1.5) 为 1.750ms。
2、从机接收数据帧
只有当一帧数据接收完毕时才进行数据处理
struct {
//作为从机时使用
u8 myadd; //本设备从机地址
u8 rcbuf[100]; //modbus接受缓冲区
u8 timout; //modbus数据持续时间
u8 recount; //modbus端口接收到的数据个数
u8 timrun; //modbus定时器是否计时标志
u8 reflag; //modbus一帧数据接受完成标志位
u8 sendbuf[100]; //modbus发送缓冲区
}modbus;
/**
* 描述 : 串口3中断服务函数
* 参数 : 无
* 返回 : 无
*/
void USART3_IRQHandler(void)
{
uint8_t ucTemp;
if (USART_GetITStatus(USART3,USART_IT_RXNE)!=RESET) //判断是否有数据接收
{
USART_ClearITPendingBit(USART3,USART_IT_RXNE);
ucTemp = USART_ReceiveData( USART3 ); //将接收的一个字节保存
modbus.rcbuf[modbus.recount++]=ucTemp; //保存到MODBUS的接收缓存区
USART_SendData(USART1,ucTemp);
modbus.timout=0; //串口接收数据的过程中,定时器不计时
if(modbus.recount==1) //收到主机发来的一帧数据的第一字节
{
modbus.timrun=1; //启动定时
}
}
}
/**
* 描述 : 定时器3初始化
* 参数 : 无
* 返回 : 无
*/
void tim3_Config(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
//定时器的基本配置,对72Mz进行7200分频,重载值10延时间隔--得到1ms延时
TIM_TimeBaseStructure.TIM_Prescaler=7200;
TIM_TimeBaseStructure.TIM_Period=10;
TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;
TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseStructure);
TIM_ClearFlag(TIM3,TIM_FLAG_Update);
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE); //TIM更新中断源中断使能
TIM_Cmd(TIM3,ENABLE);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
NVIC_InitStructure.NVIC_IRQChannel=TIM3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=3;
NVIC_Init(&NVIC_InitStructure);
}
/**
* 描述 : MODBUS定时器中断函数
* 参数 : 无
* 返回 : 无
*/
void TIM3_IRQHandler(void) //定时器中断函数
{
if(TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET )
{
if(modbus.timrun!=0) //串口发送数据是否结束,结束就让定时器定时
{
modbus.timout++; //定时器定时1毫秒,并开始记时
if(modbus.timout>=8) //间隔时间达到了时间,假设为8T,实际3.5T即可
{
modbus.timrun=0; //关闭定时器--停止定时
modbus.reflag=1; //收到一帧数据,开始处理数据
}
}
TIM_ClearITPendingBit(TIM3 , TIM_FLAG_Update);//清除中断标志位
}
}
3、事件处理总体函数
当一帧数据接收完毕时:
(1)首先判断自主计算的CRC校验位和接收到数据的校验位是否一致
(2)其次判断从机地址是不是自己的地址
(3)数据传输正确且从机地址正确的情况下再根据不同的功能码去执行对应的函数操作
0x03读取寄存器数据
0x06写入一个寄存器数据
0x10写入多个寄存器数据
/**
* 描述 : Modbus事件处理函数
* 参数 : 无
* 返回 : 无
*/
void Modbus_Event(void)
{
u16 crc,rccrc;//crc和接收到的crc
//没有收到数据包
if(modbus.reflag == 0) //如果接收未完成则返回空
{
return;
}
//收到数据包(接收完成)
//通过读到的数据帧计算CRC
//参数1是数组首地址,参数2是要计算的长度(除了CRC校验位其余全算)
crc = CRC16_MudBus(&modbus.rcbuf[0],modbus.recount-2); //获取CRC校验位
// 读取数据帧的CRC
rccrc = modbus.rcbuf[modbus.recount-2]*256+modbus.rcbuf[modbus.recount-1];//计算读取的CRC校验位
//等价于下面这条语句
//rccrc=modbus.rcbuf[modbus.recount-1]|(((u16)modbus.rcbuf[modbus.recount-2])<<8);//获取接收到的CRC
if(crc == rccrc) //CRC检验成功 开始分析包
{
if(modbus.rcbuf[0] == modbus.myadd) // 检查地址是否时自己的地址
{
switch(modbus.rcbuf[1]) //分析modbus功能码
{
case 0: break;
case 1: break;
case 2: break;
case 3: Modbus_Func3(); break;//这是读取寄存器的数据
case 4: break;
case 5: break;
case 6: Modbus_Func6(); break;//这是写入单个寄存器数据
case 7: break;
case 8: break;
case 9: break;
case 16: Modbus_Func16(); break;//写入多个寄存器数据
}
}
else if(modbus.rcbuf[0] == 0) //广播地址不予回应
{
}
}
memset(modbus.rcbuf,0,sizeof(modbus.rcbuf));
modbus.recount = 0;//接收计数清零
modbus.reflag = 0; //接收标志清零
}
4、CRC校验----查表法
/* CRC 高位字节值表 */
static const uint8_t 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低位字节值表*/
static const uint8_t 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
};
/**
* 描述 : 计算CRC16
* 输入 : puchMsg---数据地址,usDataLen---数据长度
* 返回 : 校验值
*/
uint16_t CRC16_MudBus(uint8_t *puchMsg, uint8_t usDataLen)
{
uint8_t uchCRCHi = 0xFF ; // 高CRC字节初始化
uint8_t uchCRCLo = 0xFF ; // 低CRC 字节初始化
uint8_t uIndex ; // CRC循环中的索引
while (usDataLen--) // 传输消息缓冲区
{
uIndex = uchCRCHi ^ *puchMsg++ ; // 计算CRC
uchCRCHi = uchCRCLo ^ auchCRCHi[uIndex];
uchCRCLo = auchCRCLo[uIndex];
}
return (uchCRCHi << 8 | uchCRCLo); // MODBUS 规定高位在前
}
5、功能码0x03--被主机寻址后读取对应的寄存器数据
第一个字节是从机地址
第二个字节是功能码
第三个字节是我要给主机返回几个字节的数据
第四个字节开始对应寄存器的具体内容(每个寄存器占2个字节)
第n个字节具体数据内容结束
对前面所有字节进行CRC校验计算并将CRC计算的数据追加到数组的结尾
数据封装完毕之后将封装好的数组数据发送出去
/**
* 描述 : Modbus 3号功能码函数---Modbus 主机读取寄存器值
* 参数 : 无
* 返回 : 无
*/
void Modbus_Func3(void)
{
u16 Regadd,Reglen,crc;
u8 i,j;
//得到要读取寄存器的首地址
Regadd = modbus.rcbuf[2]*256+modbus.rcbuf[3];//读取的首地址
//得到要读取寄存器的数据长度
Reglen = modbus.rcbuf[4]*256+modbus.rcbuf[5];//读取的寄存器个数
//发送回应数据包
// memset(modbus.sendbuf,0,sizeof(modbus.sendbuf));
i = 0;
modbus.sendbuf[i++] = modbus.myadd; //ID号:发送本机设备地址
modbus.sendbuf[i++] = 0x03; //发送功能码
modbus.sendbuf[i++] = ((Reglen*2)%256); //返回字节个数
if(modbus.myadd==0x02)
{
for(j=0;j<Reglen;j++) //返回数据
{
//reg是提前定义好的16位数组(模仿寄存器)
modbus.sendbuf[i++] = Reg[Regadd+j]/256;//高位数据
modbus.sendbuf[i++] = Reg[Regadd+j]%256;//低位数据
}
}
else if(modbus.myadd==0x04)
{
for(j=0;j<Reglen;j++) //返回数据
{
//Temperature是温度数据缓冲区
modbus.sendbuf[i++] = Temperature[Regadd+j]/256;//高位数据
modbus.sendbuf[i++] = Temperature[Regadd+j]%256;//低位数据
}
}
else if(modbus.myadd==0x05)
{
for(j=0;j<Reglen;j++) //返回数据
{
//Humidity是湿度数据缓冲区
modbus.sendbuf[i++] = Humidity[Regadd+j]/256;//高位数据
modbus.sendbuf[i++] = Humidity[Regadd+j]%256;//低位数据
}
}
crc = CRC16_MudBus(modbus.sendbuf,i); //计算要返回数据的CRC
modbus.sendbuf[i++] = crc/256;//校验位高位
modbus.sendbuf[i++] = crc%256;//校验位低位
//数据包打包完成
// 开始返回Modbus数据
RS485_TX_ENABLE;//这是开启485发送
for(j=0;j<=i;j++)//发送数据
{
Modbus_Send_Byte(modbus.sendbuf[j]);
}
RS485_RX_ENABLE;//这里是关闭485发送
}
6、功能码0x06–被主机寻址后向某个寄存器中写入数据
从机接收从机的指令往1个寄存器中写入数据
第一个字节是从机地址
第二个字节是功能码
第3-4个字节是要写入的地址
第5-6个字节是要写入的数据
对前面所有字节进行CRC校验计算并将CRC计算的数据追加到数组的结尾
从机返回数组填充内容:将接收到的数据原路返回即可
/**
* 描述 : Modbus 6号功能码函数---Modbus 主机写入寄存器值
* 参数 : 无
* 返回 : 无
*/
void Modbus_Func6(void)
{
u16 Regadd;//地址16位
u16 val;//值
u16 i,crc,j;
i=0;
Regadd=modbus.rcbuf[2]*256+modbus.rcbuf[3]; //得到要修改的地址
val=modbus.rcbuf[4]*256+modbus.rcbuf[5]; //修改后的值(要写入的数据)
Reg[Regadd]=val; //修改本设备相应的寄存器
//以下为回应主机
modbus.sendbuf[i++]=modbus.myadd;//本设备地址
modbus.sendbuf[i++]=0x06; //功能码
modbus.sendbuf[i++]=Regadd/256;//写入的地址
modbus.sendbuf[i++]=Regadd%256;
modbus.sendbuf[i++]=val/256;//写入的数值
modbus.sendbuf[i++]=val%256;
crc=CRC16_MudBus(modbus.sendbuf,i);//获取crc校验位
modbus.sendbuf[i++]=crc/256; //crc校验位加入包中
modbus.sendbuf[i++]=crc%256;
//数据发送包打包完毕
RS485_TX_ENABLE;;//使能485控制端(启动发送)
for(j=0;j<=i;j++)
{
Modbus_Send_Byte(modbus.sendbuf[j]);
}
RS485_RX_ENABLE;//失能485控制端(改为接收)
}
7、将主函数中的ID号改为模拟设备ID号,同时注释第十五行(函数内有十秒延时)
int main(void)
{
// 来到这里的时候,系统的时钟已经被配置成72M。
systick_Init();
// Led_Config();
usart1_Config(115200); //串口1初始化,在串口助手打印数据
printf("底层配置初始化完成\r\n");
usart2_Config(); //串口2初始化
usart3_Config(); //串口3初始化
tim3_Config(); //定时器3初始化
Modbus_Init(Req_add); Modbus初始化
while(1)
{
// DHT11_Rx_data("get_data\0"); //串口2向协调器发送指令获取数据
Modbus_Event(); // Modbus事件处理函数
}
}
8、功能码0x10-被主机寻址后向多个寄存器中写入数据
向多个寄存器中写入数据
按照主机的指令往设备的寄存器地址中写入数据
第1个字节是本设备地址(从机)
第2个字节是功能码0X06
第3、4个字节是写入数据的起始地址
第5、6个字节是写入的寄存器个数
第7个字节是写入的字节个数(字节个数=寄存器个数*2)
第8个字节开始是要写入的数据
从机要返回的数据:只需要把前6个字节装入数组,再对这6个字节进行CRC校验,将计算的数值追加到数组末尾再发送出去即可
void Modbus_Func16(void)
{
u16 Regadd;//地址16位
u16 Reglen;
u16 i,crc,j;
Regadd=modbus.rcbuf[2]*256+modbus.rcbuf[3]; //要修改内容的起始地址
Reglen = modbus.rcbuf[4]*256+modbus.rcbuf[5];//读取的寄存器个数
for(i=0;i<Reglen;i++)//往寄存器中写入数据
{
//接收数组的第七位开始是数据
Reg[Regadd+i]=modbus.rcbuf[7+i*2]*256+modbus.rcbuf[8+i*2];//对寄存器一次写入数据
}
//写入数据完毕,接下来需要进行打包回复数据了
//以下为回应主机内容
//内容=接收数组的前6位+两位的校验位
modbus.sendbuf[0]=modbus.rcbuf[0]; //本设备地址
modbus.sendbuf[1]=modbus.rcbuf[1]; //功能码
modbus.sendbuf[2]=modbus.rcbuf[2]; //写入的地址
modbus.sendbuf[3]=modbus.rcbuf[3];
modbus.sendbuf[4]=modbus.rcbuf[4];
modbus.sendbuf[5]=modbus.rcbuf[5];
crc=CRC16_MudBus(modbus.sendbuf,6);//获取crc校验位
modbus.sendbuf[6]=crc/256; //crc校验位加入包中
modbus.sendbuf[7]=crc%256;
//数据发送包打包完毕
RS485_TX_ENABLE;;//使能485控制端(启动发送)
for(j=0;j<8;j++)
{
Modbus_Send_Byte(modbus.sendbuf[j]);
}
RS485_RX_ENABLE;//失能485控制端(改为接收)
}