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。同時打開接收中斷,并開啟超時定時器。
情況分為兩種,在超時之前接收到數據,在超時之間沒有接收到錯誤。
?
如果在超時之前接收到數據。直接將數據丟棄,并重新開始定時。直到超時,超時標識一幀數據傳輸完畢,然后進行數據處理。
?
在超時之間沒有接收到錯誤,向主程序發送就緒事件,關閉超時定時器,將接收狀態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校驗,然后對數據幀進行拆分。
/* 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,并啟動發送,發送一個字節。
?
發送完一個字節之后,產生發送中斷,調用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传输的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 芯片内部长啥样?牛人用1500张照片,一
- 下一篇: 程序员的噩梦:世界上最难的5种编程语言