单片机走ModBus协议示例

单片机作为从机设备

(以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控制端(改为接收)
 }

  • 13
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
//单片机Modbus RTU Slave程序 //==================================================================================================== //波 特 率:9600bps //起 始 位:1位 //数 据 位:8位 //校 验 位:偶校验 //停 止 位:1位 //系统时钟:11.0592MHz //物 理 层:RS485 //从站地址:0x01 //使用串口:STC12C5A60S2单片机串口1 //功 能 码:支持01、02、03、04、05、06、15、16等功能码 //01功能码:此功能可对单片机16路(甚至更多,根据自己实际需要扩展)数字量输出多路进行读操作 //02功能码:此功能可对单片机16路(甚至更多,根据自己实际需要扩展)数字量输入多路进行读操作 //03功能码:此功能可对单片机16路(甚至更多,根据自己实际需要扩展)模拟量输出多路进行读操作 //04功能码:此功能可对单片机16路(甚至更多,根据自己实际需要扩展)模拟量输入多路进行读操作 //05功能码:此功能可对单片机16路(甚至更多,根据自己实际需要扩展)数字量输出一路进行写操作 //06功能码:此功能可对单片机16路(甚至更多,根据自己实际需要扩展)模拟量输出一路进行写操作 //15功能码:此功能可对单片机16路(甚至更多,根据自己实际需要扩展)数字量输出多路进行写操作 //16功能码:此功能可对单片机16路(甚至更多,根据自己实际需要扩展)模拟量输出多路进行写操作

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值