发一个关于modbus-rtu通讯的大致思路及主要模块

2019-08-11 18:24发布

最近一直工作需要在学习modbus协议,小白一名吧,找了很多资料,但是依旧无从下手,最终借鉴着前辈的足迹,写下如下个人学习经验,仅供参考,欢迎指正!
首先,说一点modbus,这个看起来就头大,洋洋洒洒好多页,其实我想说一点,把它当作一个规定就好,按照它的要求去发送自己的功能代码。
一般模式:{0x00,0x03,0x00,0x05,0x00,0x03,0x00,0x00}; //从机地址,功能码,寄存器高,寄存器低,读寄存器个数高,读寄存器个数低,CRC低,CRC高--------具体这句代码是要做什么,这个就是modbus的内容,必须要知道(不知道就要去看噢!)。
直奔主题:思路和代码对应
第一步:拟定指令协议--其实就是我们要利用modbus来做什么。就是上面的一般模式那行(建议自己做一个文档来记录说明)。
第二步:找合适的数据结构来装我们要操作的数据(接收缓冲区)。
一般是结构体,根据自己需要来安排,如我的就是以下:
typedef struct
{
      bool     MesgRecvState;
      bool     MesgExecState;

     uint16_t MesgLength;
      uint8_t  MesgData[MB_ADU_MAX_LEN];  
}MBMESG_Typedef;
第三步:轮训发送指令。
    while(1)
    {
       if(TIM2StateType.T2State==true)//0.5S   
     {
          static uint16_t send_count=0;
      
         if(send_count%3==0)
        {
                    Modbus_Require_Communication(); //串口4与传感器--485(与本帖相关的是这个,另外两个是其他的)   
        }
        else if(send_count%3==1)
        {
                 IOBoard1_SendDataToIOBoard2(); //串口2与IO板2
               }
        else if(send_count%3==2)
        {
                     IOBoard1_SendDataToIOBoard3(); //串口3与IO板3
               }      
        send_count++;
        
        TIM2StateType.T2State=false;
     }
     
     Modbus_Response_Communication();
     
     ADU2_Response_Communication();
     
       ADU3_Response_Communication();
     
          if(TIM2StateType.T3State==true) //1.0S
     {
               IOBoard1_SendDatatoCKBoard(); //串口1与程控板
         TIM2StateType.T3State=false;
          }
          //IWDG_Feed();     
    }
这个是一个主要的框架吧,“ Modbus_Require_Communication(); //串口4与传感器--485”,与本帖相关的是这个,另外两个是其他的通讯。  
第四步:数据接收。
在上一步,我们把指令协议发出去了,就是说我们通过modbus协议向其他外设要东西了,那么其他设备遵循modbus协议就要返回相应的数据,于是,我们就要接收数据。(这是我们的重点)
我这里是通过串口中断来接收的:
if(USART_GetITStatus(UART4, USART_IT_RXNE) != RESET)
{
        recv_data = USART_ReceiveData(UART4);   
       Modbus_Message_Input_Data(&ModbusMessage, TIM7, recv_data);   //具体在下面
       USART_ClearITPendingBit(UART4, USART_IT_RXNE);  
}  
void Modbus_Message_Input_Data(MBMESG_Typedef *MBMESGx,TIM_TypeDef* TIMx, uint16_t Data)
{
     if(MBMESGx->MesgRecvState==false)//接收缓冲区能接收吗?
   {
          if(MBMESGx->MesgLength<MB_ADU_MAX_LEN)//接收的数据长度,超过最大接收数据长度了吗?
     {
               if(MBMESGx->MesgLength==0)//第一个数据,要开启定时器
        {
                    TIM_Cmd(TIMx, ENABLE);
               }
        
           MBMESGx->MesgData[MBMESGx->MesgLength]=Data;
            MBMESGx->MesgLength++;
         TIM_SetCounter(TIMx,0);//清定时器
          }
     else
     {
               MBMESGx->MesgRecvState=true;
     }
}
通过定时器中断来判断(3.5t),接收一帧数据是否完成,如下:
void TIM7_IRQHandler(void)
{
  if(TIM_GetITStatus(TIM7,TIM_IT_Update)!=RESET)
  {
     TIM_Cmd(TIM7,DISABLE); //关闭定时器7
     TIM_SetCounter(TIM7,0);//定时器7清零
   
        Modebus_Receive_Set(&ModbusMessage);
     Modbus_ADU_Copy_Message(&ModbusMessage);
     Modbus_Message_Rest(&ModbusMessage);
   
    TIM_ClearITPendingBit(TIM7, TIM_IT_Update);  //清除TIM7更新中断标志  
  }
}
中断中的函数:
void Modbus_ADU_Receive_Set(MBADU_Typedef *MBADUx)//置接收完成标志
{
     MBADUx->ADURecvState=true;
}
void Modbus_ADU_Rest(MBADU_Typedef *MBADUx)//清接收缓冲
{
     MBADUx->ADURecvState=false;
    MBADUx->ADUExecState=false;

     MBADUx->ADULength=0;

    memset(MBADUx->ADUData,0,MB_ADU_MAX_LEN);
}
MBADU_Typedef *Modbus_Receive_Index(void)//找寻可接收结构体
{  
    uint8_t index=0;
    uint8_t flag_sum=0;

    for(index=0; index<MB_MAX_ADU_QUEUE; index++)
    {
          flag_sum = MBADUQueue[index].ADURecvState;//????????---flag_sum += MBADUQueue[index].ADURecvState;
     }
   
   if(flag_sum==MB_MAX_ADU_QUEUE)
   {
          return NULL;
     }
   
    for(index=0; index<MB_MAX_ADU_QUEUE; index++)
     {
          if(MBADUQueue[index].ADURecvState==false)
          {      
               return &MBADUQueue[index];
          }
     }
     return NULL;
}
void Modbus_ADU_Copy_Message(MBMESG_Typedef *MBMESGx)//数据赋值
{
     MBADU_Typedef *ADUx=NULL;
    ADUx=Modbus_Receive_Index();
    if(ADUx==NULL) return;

    ADUx->ADURecvState=MBMESGx->MesgRecvState;

    ADUx->ADULength=MBMESGx->MesgLength;

    ADUx->ADURecvState=true;

     memcpy(ADUx->ADUData,MBMESGx->MesgData,MBMESGx->MesgLength);
}
其实以这步做的就是:通过数据帧之间的间隔来判断数据是否接收完成,接收完成之后,从接收缓冲区中把数据存到另外一个待处理缓冲区。
第五步:数据发送。
void Modbus_Response_Communication(void)
{
     MBADU_Typedef *ExeADUx=NULL;
    ExeADUx=ModBus_Execte_Index();
    if(ExeADUx==NULL) return;

     Modbus_ADU_Analysis(ExeADUx);

    Modbus_ADU_Rest(ExeADUx);
}

MBADU_Typedef *ModBus_Execte_Index(void)//查询待处理缓冲区
{
    uint8_t index=0;

     for(index=0; index<MB_MAX_ADU_QUEUE; index++)
     {
          if(MBADUQueue[index].ADURecvState==true)
          {      
               return &MBADUQueue[index];
          }
     }
     return NULL;
}

void Modbus_ADU_Analysis(MBADU_Typedef *MBADUx)//对所接收的数据判断
{
    if(Modbus_ReceiveIsOk(MBADUx)==true)
   {
          if(Modbus_LengthIsOk(MBADUx)==true)
     {
               if(Modbus_SlaveIsOk(MBADUx)==true)
        {
                    if(Modbus_CRCIsOk(MBADUx)==true)
          {
                         uint8_t control_code=0;
           
              control_code=Modbus_ControlCode(MBADUx);
              switch(control_code)
              {
                              case MB_READ_MULTI_HOLDRESG:
                   Modbbus_Read_Multi_HoldRegs(&ModbusRegs, MBADUx);
                                   break;
               
                              case MB_READ_MULTI_INPUTRESG:
                   Modbus_Read_Multi_InputRegs(&ModbusRegs, MBADUx);
                                   break;
               
               default:
                   break;
                         }         
                    }
               }
          }
     }
}

void Modbus_ADU_Rest(MBADU_Typedef *MBADUx)//清待处理缓冲区
{
     MBADUx->ADURecvState=false;
    MBADUx->ADUExecState=false;

     MBADUx->ADULength=0;

    memset(MBADUx->ADUData,0,MB_ADU_MAX_LEN);
}

至此,就结束一轮处理。
友情提示: 此问题已得到解决,问题已经关闭,关闭后问题禁止继续编辑,回答。