高重用ModBus通讯框架

本文章基于STM32 UART创建高重用性的ModBus通讯框架,主要分为CPU_UART底层驱动,通信帧断帧校验逻辑,以及RTU协议帧解析,试图构建一个高重用,跨平台易于移植,易于使用和扩展的ModBus通讯框架。

1.CPU外设框架结构

typedef struct {
    Uint16 *p_utbuf;
    Uint16 *p_urbuf;
    Uint16 *p_utbuf_used;
    Uint16 *p_urbuf_used;
	Uint16 *p_T_break_fram;
	Uint16 *is_sending;
	void (*SciSendFrame)();
	void (*SetSendIO)();
	void (*SetRecIO)();
	void (*SetBaudrate)(Uint16 a);
	void (*SetDateFram)(Uint16 a);
	Uint16 (*Send_Over)();
	void (*BaseInit)();
}SCI_DateStru;


SCI_DateStru  SCI1DateStru;
Uint16  SCI1_RBuff[60];
Uint16	SCI1_RBuffUse = 0;
Uint16  SCI1_TBuff[100];
Uint16	SCI1_TBuffUse = 0;
Uint16  SCI1_Sending = 0;
Uint16	SCI1_BreakFram = 0;
Uint16 SCI1_TLEN = 0;
Uint16 SCI1BufHead = 0;


void SCI1_DateStruDefault()
{
SCI1DateStru.p_utbuf        =  SCI1_TBuff;  //发送缓存
SCI1DateStru.p_urbuf        =  SCI1_RBuff; //接收缓存
SCI1DateStru.p_utbuf_used   = &SCI1_TBuffUse;//发送缓存使用长度
SCI1DateStru.p_urbuf_used   = &SCI1_RBuffUse;//接收缓存使用长度
SCI1DateStru.p_T_break_fram = &SCI1_BreakFram;//断帧用于标记系统时间
SCI1DateStru.is_sending     = &SCI1_Sending;//判断是否发送中

SCI1DateStru.SciSendFrame = UART4_Trans;//底层发送函数
SCI1DateStru.SetSendIO    = UART4_OccuBus;//使用该函数用于占据总线
SCI1DateStru.SetRecIO     = UART4_ReleBus;//使用该函数用于释放总线
SCI1DateStru.SetBaudrate  = UART4_SetBaud;//设置波特率参数
SCI1DateStru.SetDateFram  = UART4_SetDateFram;//设置数据帧形式RTUorTCp
SCI1DateStru.Send_Over    = UART4_TOver;//判断底层发送完成
SCI1DateStru.BaseInit = UART4_Init;
	}

除此之外还有一个接收函数用于中断接收每一个字节

void UART4_IRQHandler()
{
	u8 a = 0;
  if(UART4->SR&(0x20))	
	{	
		if(SCI1_RBuffUse > 25)SCI1_RBuffUse = 0;		  
      	SCI1_RBuff[SCI1_RBuffUse] = UART4->DR;
     	SCI1_RBuffUse++;		
     	SCI1_BreakFram = P_SysTime->Cnt_ms;	//记录接收数据的时间用于断帧处理
	}
	if(UART4->SR&0x08)
	{
		a= UART4->DR;
	    a=	UART4->SR;
		a= UART4->DR;
		a=	UART4->CR1;
		a=	UART4->CR2;
		a=	UART4->CR3;	
	}	
  UART4->SR	= 0;
}

接下来就在于实现以上的几个函数注册到结构体上的函数了。

初始化CPU外设函数:

void UART4_Init()
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_UART4, ENABLE);
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA2, ENABLE);
	UartXInit(UART4,(u32 *       )SCI1_TBuff,DMA2_Channel5,USART_StopBits_2);
}

void UART4_Trans()
{
	uint16_t i=0;
	UART4->CR3=1<<7; 
	if(SCI1_TBuffUse > 80) SCI1_TBuffUse = 0;
	for(i = 0;i<SCI1_TBuffUse;i++)
	{
		buf[i] = SCI1_TBuff[i]&0xff;
	}
	MYDMA_Enable(DMA2_Channel5,(u32)buf, SCI1_TBuffUse);
	SCI1_TBuffUse = 0;
}


void UART4_OccuBus()
{
//在485总线时 置位RTS占据总线
}


void UART4_ReleBus()
{
//在485总线时复位RTS释放总线
}

void UART4_SetBaud(u16 a)
{
//UART4->BRR 修改波特率
}

void UART4_SetDateFram(u16 a)
{
//设置格式 也不用现在
}
u16 UART4_TOver()
{
	//判断DMA发完了没有
	Uint16 sover = 0;
	if(DMA2->ISR & 0x00020000) //finish
	{
		DMA2->IFCR |= 0x000f0000;
		sover = 1;
	}
	return sover;
}

最终调用以下函数初始化完成底层配置:

SCI1_DateStruDefault();
*(SCI1DateStru.BaseInit)();

2.ModBus帧处理

首先是modbus帧处理的结构体

typedef struct
{
    u16 masterorslaver;
    u16 modebus_type;
    u16 *TBuf_p;  //指向底层发送缓存
    u16 *RBuf_p;  //指向底层接收缓存
    u16 RbufSize;//
    u16 *R_len;    //一级接收缓存使用量
    u16 *T_len;    //一级发送缓存使用量
    u16 *BreakFrmCnt;//指向底层断帧时间
    u16 BreakFrmLen;//设置断帧时间
    u16 SuccessCnt; //通讯成功计数
    u16 FrameCnt; //通讯次数
    u16 goodpercent;//通讯成功百分比,便于观测通讯质量
	u16 MasterWaitACKCnt; //主机等待应答计数
	 u16 MasterGetAckFlag;//主机接收到应答标记
    void (*BaseTrans)();//底层发送函数
    u16 (*isBaseTransFinish)();//判断是否底层发送完成
} FrameStruct;

结构初始化将帧处理结构与底层处理数据和方法连接

void ModbusInit(FrameStruct *modbus_frame, SCI_DateStru *scibase)
{
    modbus_frame->BreakFrmCnt = scibase->p_T_break_fram; //
    modbus_frame->BreakFrmLen = 3; //3ms断帧时间
    modbus_frame->TBuf_p = scibase->p_utbuf;  //
    modbus_frame->RBuf_p = scibase->p_urbuf;  //
    modbus_frame->R_len = scibase->p_urbuf_used;//
    modbus_frame->T_len = scibase->p_utbuf_used;//
    modbus_frame->RbufSize = 35;
    modbus_frame->masterorslaver = 1; //slaver
    modbus_frame->modebus_type = 1;//RTU
    modbus_frame->SuccessCnt = 0;
    modbus_frame->FrameCnt = 0;
    modbus_frame->goodpercent = 0;
  	modbus_frame->MasterWaitACKCnt = 0;
	modbus_frame->MasterGetAckFlag = 0;
    modbus_frame->BaseTrans = scibase->SciSendFrame;
    modbus_frame->isBaseTransFinish = scibase->Send_Over;
}

在主流程中调用
ModbusInit(&ma_Frame,&SCI1DateStru);
完成帧处理模块与底层模块的连接

Modbus丛机协议执行主函数
ModbusSlaverHandel(&ma_Frame,&ma_RTU);

void   ModbusSlaverHandel(FrameStruct *p_frame,MODBUSRTU_DEV *p_rtu)
{
    if( Modbus_Recive(p_frame) ){//帧检测函数
        if(ModbusCheck(p_frame)){//帧校验函数
           Frame2ModbusRTU(p_frame,p_rtu);//RTU协议处理函数			
        }
		*(p_frame->R_len) = 0;//接收缓存清零
    }
}

帧检测函数

u16 Modbus_Recive(FrameStruct *p)
{
    u16 t= 0;
    //判断是否接收到数据
    if(*(p->R_len) == 0) {
        *(p->BreakFrmCnt) = P_SysTime->Cnt_ms;
        return 0;
    }
    //判断是否产生断帧
    if(P_SysTime->Cnt_ms >= *(p->BreakFrmCnt)){
        if( (P_SysTime->Cnt_ms - *(p->BreakFrmCnt)) > p->BreakFrmLen){
            *(p->BreakFrmCnt) = P_SysTime->Cnt_ms;
            t= 1;
        }
    }else
    {
        if( (P_SysTime->Cnt_ms+1000 - *(p->BreakFrmCnt)) > p->BreakFrmLen){
            *(p->BreakFrmCnt) = P_SysTime->Cnt_ms;
            t= 1;
        }
    }
    return t;
}

校验与通讯质量检测

u16 ModbusCheck(FrameStruct *p)
{
    u16 a = 0;
    u16 b = 0;
    u16 c= 0;
    u16 d = 0;
	 if((*(p->R_len)) < 2) return d;
     a = *(p->R_len)-2;
	 c= ((p->RBuf_p)[a]<<8) + (p->RBuf_p)[a+1];
	 b = CrcCheck((p->RBuf_p),a); //CRC校验
     if(c == b){
       p->SuccessCnt ++;
       d = 1;
   }
   else{
   }

    p->FrameCnt ++;
   if(p->FrameCnt > 99){
       p->goodpercent = p->SuccessCnt;
       p->FrameCnt = 0;
       p->SuccessCnt  = 0;
   }
   return d;

}

3.RTU协议

RTU协议处理结构体规划

//命令字定义
typedef enum {
    R_DORGE = 1,//读输出线圈
    R_DIRGE = 2,//读输入线圈
    R_AORGE  = 3,//读输出寄存器
    R_AIRGE  = 4,//读输入寄存器
    W_DORGE = 5,//写输出线圈
    W_AORGE = 6,//写输出寄存器
    W_MDORGE  = 15,//写多个线圈
    W_MAORGE  = 16//写多个输出寄存器
}MODBUS_RTU_CMD;
//帧头两个字节 设备地址 + 命令字
typedef struct {
   u16 dev_add;
   MODBUS_RTU_CMD cmd;
} Modbush_RTU_Head;

//RTU 主机帧
typedef struct {
    Modbush_RTU_Head head;
    u8 Startadd_H;
    u8 Startadd_L;
    u8 Lenth_H;
    u8 Lenth_L;
	u8 WBytelen;
	u16 WWord[10];
    u8 WaitAckCnt;
	u16 Rbuf[30];
	u16 *Tbuf;
} MODBUS_MASTER;
//RYU 丛机帧
typedef struct {
    Modbush_RTU_Head head;
    u16 Lenth_L;
    u16 SlaveBuf[35];
    u16 *Tbuf;
} MODBUS_SLAVE;
//RTU协议结构体
typedef struct{
    MODBUS_MASTER master;
    MODBUS_SLAVE  slaver;
	u16  Online;
	//两个钩子函数 当参数改变时调用,判断参数有效性
	u16 (*CheckBitWork)(u16 add,u16 value);//位值参数改变
	u16 (*CheckWordWork)(u16 add,u16 value);//字参数改变
} MODBUSRTU_DEV;

结构体初始化

void ModBusRTUInit(MODBUSRTU_DEV *m, u16 DevAdd,void *checkbit,void *checkword)
{
  m->slaver.head.dev_add = DevAdd;
	m->Online = 0;
	m->CheckBitWork = checkbit;
	m->CheckWordWork = checkword; 
}

这个函数处理RTU协议的丛机应答
Frame2ModbusRTU(p_frame,p_rtu);


```c
void Frame2ModbusRTU(FrameStruct *p,MODBUSRTU_DEV *p_RTU)
{
    u16 a = *(p->R_len)-2;
    u16 *b = (p->RBuf_p);
    u16 i =0;
    p_RTU->slaver.Tbuf = p->TBuf_p;
     if(p_RTU->slaver.head.dev_add == b[0] ) //判断站号
     {
         p_RTU->slaver.Lenth_L = 0;
         p_RTU->slaver.head.cmd = (u8)b[1]; //读取命令字   
         for(i=2;i<a;i++)//将帧缓存 载入到RTU丛机的接收缓存中
         {
             p_RTU->slaver.Lenth_L++;
             p_RTU->slaver.SlaveBuf[i-2] = b[i];  
         }
         DecodeMaster_RTU(p_RTU);//RTU协议解析
         if( (*(p->isBaseTransFinish))() )//判断是否发送完成
         ModBusTrans(p,p_RTU);//发送函数
		 p_RTU->Online = 1; //标记在线
     }
     *(p->R_len) = 0;//
}

//RTU协议解码

void DecodeMaster_RTU(MODBUSRTU_DEV *m)
{
     u16 add =0;
     u16 *p = m->slaver.SlaveBuf;
     u16 a,len,i;
     switch(m->slaver.head.cmd)
     {
         case R_DORGE:{//???bits
             if(m->slaver.Lenth_L == 4)
             {
                 m->slaver.Lenth_L = 0;
                 add = p[0]; //????
                 add = (add<<8) + p[1];


                     len = p[2]; //????
                     len = (len<<8)+ p[3];

                     a = len / 8; //???
                     if(a*8 < len) a++;

                     m->slaver.Tbuf[0] = m->slaver.head.dev_add;
                     m->slaver.Tbuf[1] = m->slaver.head.cmd;
                     m->slaver.Tbuf[2] = a;//???
                     m->slaver.Lenth_L = 3;
                     for(i=0;i<a;i++)
                     {
                         m->slaver.Tbuf[i+3]  = BIT_Write[0];
                         m->slaver.Lenth_L ++;
                     }                
             }
         }break;
         case R_DIRGE:{//???bit
             if(m->slaver.Lenth_L == 4)
             {
                 m->slaver.Lenth_L = 0;
                 add = p[0]; //????
                 add = (add<<8) + p[1];

								 len = p[2]; //????
								 len = (len<<8)+ p[3];

								 a = len / 8; //???
								 if(a*8 < len) a++;

								 m->slaver.Tbuf[0] = m->slaver.head.dev_add;
								 m->slaver.Tbuf[1] = m->slaver.head.cmd;
								 m->slaver.Tbuf[2] = a;//???
								 m->slaver.Lenth_L = 3;
								 for(i=0;i<a;i++)
								 {
										 m->slaver.Tbuf[i+3]  = BIT_Read[0];
										 m->slaver.Lenth_L ++;
								 }
                 
             }
         }break;
         case R_AORGE:{
             if(m->slaver.Lenth_L == 4){
                  m->slaver.Lenth_L = 0;
                  add = p[0]; //????
                  add = (add<<8) + p[1];                 
									len = p[2]; //????
									len = (len<<8)+ p[3];
									a = len;
									m->slaver.Tbuf[0] = m->slaver.head.dev_add;
									m->slaver.Tbuf[1] = m->slaver.head.cmd;
									m->slaver.Tbuf[2] = a*2;
									m->slaver.Lenth_L = 3;
									for(i=0;i<a;i++)
									{																																					
											m->slaver.Tbuf[2*i+3]  = ( Word_Write[i+add]>>8)&0xff;
											m->slaver.Lenth_L ++;
											m->slaver.Tbuf[2*i+4]  =  Word_Write[i+add]&0xff;
											m->slaver.Lenth_L ++;
									}
                  
              }
         }break;
         case R_AIRGE:{
             if(m->slaver.Lenth_L == 4){
                  m->slaver.Lenth_L = 0;
                  add = p[0]; //????
                  add = (add<<8) + p[1];
                
									len = p[2]; //????
									len = (len<<8)+ p[3];
									a = len;
									m->slaver.Tbuf[0] = m->slaver.head.dev_add;
									m->slaver.Tbuf[1] = m->slaver.head.cmd;
									m->slaver.Tbuf[2] = a*2;
									m->slaver.Lenth_L = 3;
									for(i=0;i<a;i++)
									{												
											m->slaver.Tbuf[2*i+3]  = ( Word_Write[i+add]>>8)&0xff;
											m->slaver.Lenth_L ++;
											m->slaver.Tbuf[2*i+4]  =  Word_Write[i+add]&0xff;
											m->slaver.Lenth_L ++;
									}               
              }
         }break;
         case W_DORGE:{
             if(m->slaver.Lenth_L == 4)
               {
                  m->slaver.Lenth_L = 0;
                  add = p[0]; //????
                  add = (add<<8) + p[1];
                  //HMI??????

                      m->slaver.Tbuf[0] = m->slaver.head.dev_add;
                      m->slaver.Tbuf[1] = m->slaver.head.cmd;
                      m->slaver.Tbuf[2] = p[0];
                      m->slaver.Tbuf[3] = p[1];
                      m->slaver.Tbuf[4] = p[2];
                      m->slaver.Tbuf[5] = p[3];
                      m->slaver.Lenth_L = 6;
                     
								      if( (*(m->CheckBitWork))(add,p[2]))
											{ 
												if(p[2] == 0xFF)
                          BIT_Write[0] |= 0x01<<add;
												else
                          BIT_Write[0] &= ~((0x01)<<add);
											}

                  
               }
         }break;
         case W_AORGE:{
             if(m->slaver.Lenth_L == 4)
               {
                     m->slaver.Lenth_L = 0;
                     add = p[0]; //????
                     add = (add<<8) + p[1];
                     
                     m->slaver.Tbuf[0] = m->slaver.head.dev_add;
                     m->slaver.Tbuf[1] = m->slaver.head.cmd;
                     m->slaver.Tbuf[2] = p[0];
                     m->slaver.Tbuf[3] = p[1];
                     m->slaver.Tbuf[4] = 0;
                     m->slaver.Tbuf[5] = 1;
                     m->slaver.Lenth_L = 6;
								    if((*(m->CheckWordWork))(add,(p[2]<<8) + p[3]))
                     Word_Write[add] = (p[2]<<8) + p[3];
                     
               }
         }break;
         case W_MDORGE:{//???????
             m->slaver.Lenth_L = 0;
             add = p[0]; //????
             

                 len = p[4]/2;

                 for(i=0;i<len;i++)
									{
										if((*(m->CheckWordWork))(add+i,(p[2*i+5]<<8) + p[2*i+6]))
                     Word_Write[add+i] = (p[2*i+5]<<8) + p[2*i+6];
									}

                 m->slaver.Tbuf[0] = m->slaver.head.dev_add;
                 m->slaver.Tbuf[1] = m->slaver.head.cmd;
                 m->slaver.Tbuf[2] = p[0];
                 m->slaver.Tbuf[3] = p[1];
                 m->slaver.Tbuf[4] = p[2];
                 m->slaver.Tbuf[5] = p[3];
                 m->slaver.Lenth_L = 6;
             

         }break;
         case W_MAORGE:{//???????
             add = p[0]; //????
             add = (add<<8) + p[1];             
						 m->slaver.Tbuf[0] = m->slaver.head.dev_add;
						 m->slaver.Tbuf[1] = m->slaver.head.cmd;
						 m->slaver.Tbuf[2] = p[0];
						 m->slaver.Tbuf[3] = p[1];
						 m->slaver.Tbuf[4] = p[2];
						 m->slaver.Tbuf[5] = p[3];
						 
						 len = p[4]/2;

						 for(i=0;i<len;i++)
						 {
									if( (*(m->CheckWordWork))(add+i,(p[2*i+5]<<8) + p[2*i+6]) )
									 Word_Write[i+add] = (p[2*i+5]<<8) + p[2*i+6];
						 }
						 m->slaver.Lenth_L = 6;
             
         }break;
         default:break;
     }

}



  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 如果你想在 Java 中清除所有资源并重用现有框架,可以采取以下步骤: 1. 关闭所有打开的数据库连接。 2. 关闭所有打开的文件或网络连接。 3. 释放所有使用的内存或其他资源。 4. 重用现有框架,确保不需要重新加载或初始化。 具体实现方式将根据你使用的框架和资源类型而有所不同。一般来说,你可以使用 try-with-resources 语句来自动关闭资源,例如: ``` try (Connection conn = DriverManager.getConnection(url, user, password); Statement stmt = conn.createStatement()) { // 执行数据库操作 } catch (SQLException e) { // 处理异常 } ``` 你可以在 try-with-resources 语句中使用多个资源,并且它们将在代码块结束时自动关闭。 对于其他类型的资源,你可以使用类似的技术,例如 try-finally 语句手动释放资源。同时,你可以使用缓存技术来重用现有框架,以避免重复加载和初始化。 ### 回答2: 在Java中,清除所有资源并重用现有框架的方法可以分为以下几个步骤: 1. 执行资源释放:首先,通过关闭和释放所有占用的资源来确保它们不再被使用。这包括数据库连接、文件流、网络连接等等。使用try-finally或try-with-resources语句块,可以确保在使用完资源后能够正确释放它们,以避免资源泄漏。 例如,如果有一个数据库连接,可以在不再需要它时使用close()方法来关闭连接,以确保释放相应的资源。 2. 清除对象引用:在Java中,垃圾收集器会自动回收不再被引用的对象。因此,可以通过将对象引用设置为null来显式地释放对象。这样一来,垃圾收集器在下一次运行时会将这些对象回收,从而清除占用的内存空间。 3. 重用现有框架:在清除所有资源后,可以将现有的框架对象进行重用,而不是重新创建新的实例。这样可以节省资源,并提程序的性能。通过设计合适的对象池,可以在需要时从池中获取对象,并在使用完毕后将其返回池中,以便下次再次使用。 例如,如果有一个线程池对象,在需要线程时可以从线程池中获取可用的线程,并在完成任务后将线程归还给线程池,以便重复利用。 总结起来,要清除所有资源并重用现有框架,需要关闭和释放所有占用的资源,清除对象引用以便垃圾收集器可以回收它们,并通过设计合适的池来重用现有的框架对象。这样可以有效地管理资源,并提程序的性能和效率。 ### 回答3: 在Java中清除所有资源并重用现有框架,可以采取以下步骤: 1. 关闭打开的数据库连接:可以使用try-with-resources语句来打开数据库连接,并在代码块结束时自动关闭连接,确保资源得到释放。另外,可以使用连接池来管理数据库连接,以便正确地释放和重用连接。 2. 关闭打开的文件资源:可以使用try-with-resources语句来打开文件资源,并在代码块结束时自动关闭资源。确保使用完文件后及时关闭,以释放资源。 3. 清理线程池:如果应用程序使用了线程池来执行并发任务,应该正确地关闭线程池,以便释放线程资源。可以通过调用线程池的`shutdown()`方法来关闭线程池。 4. 释放网络连接:如果应用程序使用了网络连接(如HTTP),应该在使用完毕后正确地关闭连接,以释放资源。可以通过调用连接对象的`close()`方法来关闭网络连接。 5. 重用现有框架:在Java中,可以利用现有的框架来简化开发工作,并重用已有的组件。例如,利用Spring框架可以将应用程序拆分为模块并管理依赖关系,从而提代码的可维护性和重用性。 通过以上步骤,可以清除和释放Java应用程序中的资源,并确保能够重用现有的框架,提代码的效率和可重复使用性。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值