应用代码(3)——modbus模板(RTU消息帧)

[1]Modbus简介

首先要知道传输的流程:
一般情景是:主机(可以是pc)接底层的主控(以下叫从机)
主机主动发送命令——>从机接受到命令——>应答给主机
总的方式就是通过一问一答的方式进行通讯

帧的构成:
应用代码(3)——modbus模板(RTU消息帧)_第1张图片
T1-T2-T3-T4指的是帧结束的时间,是4个字符时间(要求的是3.5个字符)

传输示例:
应用代码(3)——modbus模板(RTU消息帧)_第2张图片

应用代码(3)——modbus模板(RTU消息帧)_第3张图片

[2]代码分享

例程功能示例:
应用代码(3)——modbus模板(RTU消息帧)_第4张图片

[2-1]处理一帧数据ReceOneChar函数(可以是串口发来的数据)

定义变量:

//modbus一帧数据错误类型(主要用来检查数据哪里出错)
#define FrameData_OK        0       //数据正常
#define FrameData_LENGTH    1       //数据长度有误
#define FrameData_CRC       2       //数据CRC有误
#define FrameData_STATION   3       //数据站号有误
#define FrameData_ADDR      4       //数据地址超限
#define FrameData_NUM       5       //数据无回应

//-------------------这一段个人猜想是根据主机来配置的大小-----------------
//用户数据区
//M和D共用一个数组,可按位/字访问整个用户数据区,每16个M位组成一个D
//对应MODBUS协议的%M和%MW
#define		D_OFFSET	0		//D起始地址
#define		D_MAX		1024		//D总数
#define		D_BK		768		//D掉电保持
#define		M_OFFSET	0		//M起始地址
#define		M_MAX		D_MAX*16	//M总数
//----------------------------------------------------
uint8_t com0recv_state;    //0:接收空闲 1:正在接收 2:接收完成
uint8_t com0recv_index;    //接收索引 用于确定接收数据长度
uint8_t com0recv_over;
uint8_t com0recv_tim;      //用于确定数据有没有接收成功
uint8_t com0recv[300];	   //COM0接收缓冲区

uint8_t com0send_over;     //1:发送完成
uint8_t com0send_index;    //发送索引 用于确定发送数据长度
uint8_t com0send_len;      //
uint8_t com0send[300];	   //COM0发送缓冲区

uint16_t modbus_number = 1;                //I通道地址(485通信站号) 1~126  默认:1

处理函数:

void ReceOneChar(void)
{
  uint8_t temp_cmd;
  
  //1# 接收不足一帧
  if(com0recv_index < 3)
  {
  	/*报错变量*/
    err_status = FrameData_LENGTH;  //NO.1    
    goto RECV0_END;
  }
 
  //2# CRC校验
  {
    union
    {
      uint8_t  B[2];
      uint16_t W;
    }tmp_crc;
    uint16_t tmp_crc1;  
    /*CRC校验*/
    tmp_crc1 = CRC16(com0recv,com0recv_index-2); 
    /*把该帧里的最后两个元素赋值给B[0],B[1]*/
    tmp_crc.B[0] = com0recv[com0recv_index-2];
    tmp_crc.B[1] = com0recv[com0recv_index-1];   
    if(tmp_crc1!=tmp_crc.W)//校验错误
    {
      err_status = FrameData_CRC;  //NO.2    
      goto RECV0_END;
    }
  }
  
  //3# 站号错误(因为可以接多个485,所以我们先确认,是否是我们需要地址线)
  if((com0recv[0]!=modbus_number) && (com0recv[0]!=0))
  {
    err_status = FrameData_STATION;  //NO.3
    goto RECV0_END;
  }
  
  //4# 取命令(回复的时候用)
  {
    temp_cmd = com0recv[1];  
    com0send[0] = com0recv[0]; //地址
    com0send[1] = temp_cmd; //功能码
  }
  
  //5# 命令处理
  {
    uint16_t temp_addr; 
    uint16_t temp_num;  
    
    //取地址(数据地址)
    temp_addr = com0recv[2];
    temp_addr<<= 8;
    temp_addr = temp_addr + com0recv[3];    //寄存器的地址16位
    
    //取点数(数据大小)
    temp_num = com0recv[4];
    temp_num<<= 8;
    temp_num = temp_num + com0recv[5];      //寄存器的数量16位
//--------------------------判断数据地址超限----------------------------    
    if((temp_cmd == 0x05) || (temp_cmd == 0x06))  //单点操作
    {
      if((temp_cmd == 0x05) && (temp_addr >= M_MAX))  //地址上限
      {
        err_status = FrameData_ADDR;  //NO.4      
        goto RECV0_END;
      }
      
      if((temp_cmd == 0x06) && (temp_addr >= D_MAX))  //地址上限
      {
        err_status = FrameData_ADDR;  //NO.4   
        goto RECV0_END;
      }
    }
    else  //多点操作
    {
      uint16_t max_addr;  
      if(temp_num == 0)  //点数为0,无回应
      {
        err_status = FrameData_NUM;  //NO.5   
        goto RECV0_END;
      } 
      max_addr = temp_addr + temp_num;//最大数据地址
      if((temp_cmd == 0x01) || (temp_cmd == 0x02) || (temp_cmd == 0x0F))  //读/写N位
      {
        if((max_addr>M_MAX) || (temp_num>(100*16)))  //地址上限
        {
          err_status = FrameData_ADDR;  //NO.4        
          goto RECV0_END;
        }
      }
      else if((temp_cmd == 0x03) || (temp_cmd == 0x04) || (temp_cmd == 0x10))  //读/写N字
      {
        if((max_addr>D_MAX) || (temp_num>100))  //地址上限
        {
          err_status = FrameData_ADDR;  //NO.4        
          goto RECV0_END;
        }
      }
    }
//------------------------------------------------------
    err_status = FrameData_OK;  //NO.0
    
    //处理程序
    switch(temp_cmd) //根据不同的功能码处理不同的事
    {   
      case 0x01:       ReadCoil();      break;
      case 0x02:       Read2Coil();     break;
      case 0x03:       ReadReg();       break;
      case 0x04:       ReadInputReg();  break;    
      case 0x05:       SetCoil();       break;
      case 0x06:       SetReg();        break;
      case 0x0f:       SetNCoil();      break;
      case 0x10:       SetNReg();       break;
      
      default:  goto RECV0_END; break;
    }
    
    //发送回应
    {
      /*tomi 发送*/
      uint16_t tmp_crc;
      
      modbus_dir_tx();  //发送模式
      
      tmp_crc = CRC16(com0send,com0send_len-2);
      
      com0send[com0send_len-1] = tmp_crc>>8;
      com0send[com0send_len-2] = tmp_crc;
      
      com0send_index=1;
      com0send_over=0;
      
      USART1->DR = com0send[0];
    }
  }  
  
  //接收处理结束
  RECV0_END:
    com0recv_index = 0;  //置0
    com0recv_state = 0;  //设置接收为空闲
    OPEN_COM0RECV();     //使能接收
}

[2-1-2] 接收完成标志位置位

/**
  * @brief  TIME8定时器中断服务程序
  * @brief  1ms时基 专门用来处理modbus485
  * @param  None
  * @return None  
  */
void TIM8_UP_IRQHandler(void)     
{
  if(TIM_GetITStatus(TIM8,TIM_IT_Update)!=RESET)
  {
    TIM_ClearITPendingBit(TIM8, TIM_IT_Update);
    
    //COM0接收空闲处理
    {
      if(com0recv_state == 1)  //接收中
      {      
        com0recv_tim++;
          
        if(com0recv_tim>5)
        {
          com0recv_state =2; //置2:接收完成
          com0recv_tim = 0;  //清0
          
          CLOSE_COM0RECV();  //停止接收
        }   
      }
    }
  }
}

[2-1-3] 以04功能码为例的应答

/**
 *@brief  读输入寄存器-读N字(功能码:0x04) [3F暂时未用到]
 *@param  none
 *@return none
 */
void ReadInputReg(void)   
{  
  uint16_t i;
  uint16_t j;
  
  uint16_t addr; 
  uint16_t num;  
  
  //取地址
  addr = com0recv[2];
  addr<<= 8;
  addr = addr + com0recv[3];
  
  //取点数
  num = com0recv[4];
  num<<= 8;
  num = num + com0recv[5];  
  
  //开始读字数据
  j=3;
  for(i=0; i<num; i++)
  {
    com0send[j++] = (uint8_t)((DATAW[addr]>>8)&0xff); 
    com0send[j++] = (uint8_t)(DATAW[addr]&0xff);    
    
    addr++;
  }   
    
  com0send[2] = num*2;           //byte个数
  com0send_len = com0send[2]+5;  //5=1+1+1+2 => 站号+功能码+字节数+CRC长度
}

[2-1-4]CRC校验代码

/**
 *@brief   CRC效验  查表法
 *@param1  帧其起始指针
 *@param2  长度
 *@return  none
 */
unsigned short CRC16(uint8_t *buff_addr, unsigned short len)  //产生CRC值
{  
  const uint8_t CRC_HIGH[]=    //高字节CRC值表
  {
    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
  };
  const uint8_t CRC_LOW[] =   //低字节CRC值
  {
    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
  };
  
  uint8_t tmp_HIGH = 0xff;    //CRC高字节初始化
  uint8_t tmp_LOW = 0xff;     //CRC低字节初始化
  unsigned short uIndex;    //CRC查表指针
  
  //计算CRC值
  while(len--)  
  {   
    uIndex = tmp_HIGH^(*buff_addr++);
    
    tmp_HIGH = tmp_LOW^CRC_HIGH[uIndex];
    
    tmp_LOW = CRC_LOW[uIndex];
  }	
  
 //return(tmp_HIGH<<8|tmp_LOW);//大端模式(51)
 return(tmp_LOW<<8|tmp_HIGH);//小端模式
}

你可能感兴趣的:(应用代码)