FreeModbus RTU传输

首先,在使能modbus协议栈的时候,会调用pvMBFrameStartCur函数

/* 使能modbus */ eMBErrorCode eMBEnable(void) { eMBErrorCode eStatus = MB_ENOERR; /* modbus还未使能 */ if(eMBState == STATE_DISABLED) { /* 启动modbus */ pvMBFrameStartCur(); /* 设置modbus状态为使能 */ eMBState = STATE_ENABLED; } else { /* 状态不合法 */ eStatus = MB_EILLSTATE; } return eStatus; }

在rtu模式下pvMBFrameStartCur指针指向eMBRTUStart函数
/* 启动modbus rtu */ void eMBRTUStart(void) { ENTER_CRITICAL_SECTION(); /* 接收状态设置为接收初始化 */ eRcvState = STATE_RX_INIT; /* 串口打开接收、关闭发送 */ vMBPortSerialEnable(TRUE, FALSE); /* 打开超时定时器 */ vMBPortTimersEnable(); EXIT_CRITICAL_SECTION(); }

启动RTU时,接收状态eRcvState 设置为接收初始化态STATE_RX_INIT。同时打开接收中断,并开启超时定时器。
情况分为两种,在超时之前接收到数据,在超时之间没有接收到错误。

如果在超时之前接收到数据。直接将数据丢弃,并重新开始定时。直到超时,超时标识一帧数据传输完毕,然后进行数据处理。
FreeModbus RTU传输
文章图片

FreeModbus RTU传输
文章图片


在超时之间没有接收到错误,向主程序发送就绪事件,关闭超时定时器,将接收状态eRcvState设置为接收空闲态STATE_RX_IDLE
/* modbus rtu超时函数 */ BOOL xMBRTUTimerT35Expired(void) { BOOL xNeedPoll = FALSE; /* 判断接收状态 */ switch(eRcvState) { /* 接收初始化 */ case STATE_RX_INIT: /* 发送就绪事件 */ xNeedPoll = xMBPortEventPost(EV_READY); break; ...... } /* 关闭超时定时器 */ vMBPortTimersDisable(); /* 将接收状态设置为接收空闲 */ eRcvState = STATE_RX_IDLE; return xNeedPoll; }

主程序接收到就绪事件后什么也没做
/* modbus轮询 */ eMBErrorCode eMBPoll(void) { ...... /* 获取事件 */ if(xMBPortEventGet(&eEvent) == TRUE) { /* 判断事件类型 */ switch(eEvent) { /* 就绪事件 */ case EV_READY: break; ...... } } return MB_ENOERR; }


在接收空闲态下,如果接收到数据,将接收状态eRcvState切换为接收态STATE_RX_RCV,并将数据存入RTU数据缓冲区。打开超时定时器。
/* modbus rtu接收一个字节函数 */ BOOL xMBRTUReceiveFSM(void) { BOOL xTaskNeedSwitch = FALSE; UCHAR ucByte; assert_param(eSndState == STATE_TX_IDLE); /* 串口接收一个字节 */ (void)xMBPortSerialGetByte((CHAR *)&ucByte); /* 判断接收状态 */ switch(eRcvState) { ....../* 接收空闲状态 */ case STATE_RX_IDLE: /* 接收缓冲区偏移量初始化为0 */ usRcvBufferPos = 0; /* 将接收到的数据放到接收缓冲区中,偏移量加一 */ ucRTUBuf[usRcvBufferPos++] = ucByte; /* 将接收状态切换为接收态 */ eRcvState = STATE_RX_RCV; /* 超时定时器使能 */ vMBPortTimersEnable(); break; ...... } return xTaskNeedSwitch; }


在接收态下继续接收数据
/* modbus rtu接收一个字节函数 */ BOOL xMBRTUReceiveFSM(void) { BOOL xTaskNeedSwitch = FALSE; UCHAR ucByte; assert_param(eSndState == STATE_TX_IDLE); /* 串口接收一个字节 */ (void)xMBPortSerialGetByte((CHAR *)&ucByte); /* 判断接收状态 */ switch(eRcvState) { ....../* 接收态 */ case STATE_RX_RCV: /* RTU数据帧最大256字节 */ if(usRcvBufferPos < MB_SER_PDU_SIZE_MAX) { /* 将接收到的数据放到接收缓冲区中,偏移量加一 */ ucRTUBuf[usRcvBufferPos++] = ucByte; } /* 接收字节数超过256字节 */ else { /* 接收错误状态 */ eRcvState = STATE_RX_ERROR; } /* 超时定时器使能 */ vMBPortTimersEnable(); break; } return xTaskNeedSwitch; }


直到数据接收完毕,产生超时。向主程序发送接收完成事件,关闭超时定时器,将接收状态eRcvState切换为接收空闲状态STATE_RX_IDLE。
/* modbus rtu超时函数 */ BOOL xMBRTUTimerT35Expired(void) { BOOL xNeedPoll = FALSE; /* 判断接收状态 */ switch(eRcvState) { ....../* 接收态 */ case STATE_RX_RCV: /* 发送接收完成事件 */ xNeedPoll = xMBPortEventPost(EV_FRAME_RECEIVED); break; ...... } /* 关闭超时定时器 */ vMBPortTimersDisable(); /* 将接收状态设置为接收空闲 */ eRcvState = STATE_RX_IDLE; return xNeedPoll; }


主程序接收到接收完成事件之后,对数据帧进行校验和拆解,最后会得到PDU数据的指针和长度。并向主程序发送执行事件。
/* modbus轮询 */ eMBErrorCode eMBPoll(void) { ...... /* 获取事件 */ if(xMBPortEventGet(&eEvent) == TRUE) { /* 判断事件类型 */ switch(eEvent) { ....../* 接收完成事件 */ case EV_FRAME_RECEIVED: /* modbus接收函数,获取地址、PDU指针、PDU长度 */ eStatus = peMBFrameReceiveCur(&ucRcvAddress, &ucMBFrame, &usLength); if(eStatus == MB_ENOERR) { /* 判断地址是否吻合 */ if((ucRcvAddress == ucMBAddress) || (ucRcvAddress == MB_ADDRESS_BROADCAST)) { /* 发送执行事件 */ (void)xMBPortEventPost(EV_EXECUTE); } } break; ...... } } return MB_ENOERR; }

下面看一下peMBFrameReceiveCur调用的eMBRTUReceive函数。主要工作是,对数据帧进行CRC校验,然后对数据帧进行拆分。
FreeModbus RTU传输
文章图片

/* modbus rtu接收函数 */ eMBErrorCode eMBRTUReceive(UCHAR *pucRcvAddress, UCHAR **pucFrame, USHORT *pusLength) { BOOL xFrameReceived = FALSE; eMBErrorCode eStatus = MB_ENOERR; ENTER_CRITICAL_SECTION(); assert_param(usRcvBufferPos < MB_SER_PDU_SIZE_MAX); /* RTU数据帧最小4字节,进行CRC校验 */ if((usRcvBufferPos >= MB_SER_PDU_SIZE_MIN) && (usMBCRC16((UCHAR *)ucRTUBuf, usRcvBufferPos) == 0)) { /* 从机地址 */ *pucRcvAddress = ucRTUBuf[MB_SER_PDU_ADDR_OFF]; /* PDU长度=ADU长度-1字节(地址)-2字节(CRC) */ *pusLength = (USHORT)(usRcvBufferPos - MB_SER_PDU_PDU_OFF - MB_SER_PDU_SIZE_CRC); /* PDU数据指针 */ *pucFrame = (UCHAR *)&ucRTUBuf[MB_SER_PDU_PDU_OFF]; /* 已经接收到数据 */ xFrameReceived = TRUE; } /* 检验失败 */ else { /* IO错误 */ eStatus = MB_EIO; } EXIT_CRITICAL_SECTION(); return eStatus; }


主程序接收到执行事件之后,判断功能码,调用相应功能函数。然后对主机进行响应。
/* modbus轮询 */ eMBErrorCode eMBPoll(void) { ...... /* 获取事件 */ if(xMBPortEventGet(&eEvent) == TRUE) { /* 判断事件类型 */ switch(eEvent) { ....../* 执行事件 */ case EV_EXECUTE: /* 功能码 */ ucFunctionCode = ucMBFrame[MB_PDU_FUNC_OFF]; eException = MB_EX_ILLEGAL_FUNCTION; /* 遍历所有支持的功能码 */ for(i = 0; i < MB_FUNC_HANDLERS_MAX; i++) { /* 遍历完了 */ if(xFuncHandlers[i].ucFunctionCode == 0) { break; } /* 匹配到合适的功能码 */ else if(xFuncHandlers[i].ucFunctionCode == ucFunctionCode) { /* 调用相关功能 */ eException = xFuncHandlers[i].pxHandler(ucMBFrame, &usLength); break; } }/* 不是广播 */ if(ucRcvAddress != MB_ADDRESS_BROADCAST) { /* 出现异常 */ if(eException != MB_EX_NONE) { /* PDU长度初始化为0 */ usLength = 0; /* 功能码+0x80则表示异常 */ ucMBFrame[usLength++] = (UCHAR)(ucFunctionCode | MB_FUNC_ERROR); /* 异常码 */ ucMBFrame[usLength++] = eException; }if((eMBCurrentMode == MB_ASCII) && MB_ASCII_TIMEOUT_WAIT_BEFORE_SEND_MS) { vMBPortTimersDelay(MB_ASCII_TIMEOUT_WAIT_BEFORE_SEND_MS); }/* 发送响应帧 */ eStatus = peMBFrameSendCur(ucMBAddress, ucMBFrame, usLength); } break; ...... } } return MB_ENOERR; }


peMBFrameSendCur指针调用eMBRTUSend对主机进行响应。主要工作包括:将PDU封装为ADU数据,将发送状态eSndState切换为发送态STATE_TX_XMIT,并启动发送,发送一个字节。
/* modbus rtu发送函数 */ eMBErrorCode eMBRTUSend(UCHAR ucSlaveAddress, const UCHAR *pucFrame, USHORT usLength) { eMBErrorCode eStatus = MB_ENOERR; USHORT usCRC16; ENTER_CRITICAL_SECTION(); /* 接收空闲状态 */ if(eRcvState == STATE_RX_IDLE) { /* 将指针偏移到ADU */ pucSndBufferCur = (UCHAR *)pucFrame - 1; /* 1字节(从机地址) */ usSndBufferCount = 1; /* 从机地址 */ pucSndBufferCur[MB_SER_PDU_ADDR_OFF] = ucSlaveAddress; /* PDU长度 */ usSndBufferCount += usLength; /* CRC */ usCRC16 = usMBCRC16((UCHAR *)pucSndBufferCur, usSndBufferCount); ucRTUBuf[usSndBufferCount++] = (UCHAR)(usCRC16 & 0xFF); ucRTUBuf[usSndBufferCount++] = (UCHAR)(usCRC16 >> 8); /* 发送状态 */ eSndState = STATE_TX_XMIT; /* 串口启动,使能发送 */ vMBPortSerialEnable(FALSE, TRUE); } /* 接收不在空闲状态不能发送 */ else { /* IO错误 */ eStatus = MB_EIO; } EXIT_CRITICAL_SECTION(); return eStatus; }


发送完一个字节之后,产生发送中断,调用pxMBFrameCBTransmitterEmpty,pxMBFrameCBTransmitterEmpty指针指向xMBRTUTransmitFSM函数。将数据一个字节,一个字节发送出去。直到发送完毕,通知主程序发送完成事件,并将发送状态eSndState切换为发送空闲态STATE_TX_IDLE。
/* modbus rtu发送一个字节函数 */ BOOL xMBRTUTransmitFSM(void) { BOOL xNeedPoll = FALSE; assert_param(eRcvState == STATE_RX_IDLE); /* 判断发送状态 */ switch(eSndState) { /* 发送空闲状态 */ case STATE_TX_IDLE: /* 串口接收启动、发送关闭 */ vMBPortSerialEnable(TRUE, FALSE); break; /* 发送状态 */ case STATE_TX_XMIT: /* 还有数据未发送 */ if(usSndBufferCount != 0) { /* 串口发送一个字节 */ xMBPortSerialPutByte((CHAR)*pucSndBufferCur); /* 将指针向后偏移一个 */ pucSndBufferCur++; /* 剩余字节数减一 */ usSndBufferCount--; } /* 数据发完 */ else { /* 发送发送完成事件 */ xNeedPoll = xMBPortEventPost(EV_FRAME_SENT); /* 串口接收启动、发送关闭 */ vMBPortSerialEnable(TRUE, FALSE); /* 发送状态设置为空闲状态 */ eSndState = STATE_TX_IDLE; } break; } return xNeedPoll; }


主程序接收到发送完成事件后,什么也不做
/* modbus轮询 */ eMBErrorCode eMBPoll(void) { ...... /* 获取事件 */ if(xMBPortEventGet(&eEvent) == TRUE) { /* 判断事件类型 */ switch(eEvent) { ....../* 发送完成 */ case EV_FRAME_SENT: break; } } return MB_ENOERR; }

【FreeModbus RTU传输】

    推荐阅读