FreeModbus ASCII传输
首先,在使能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指針指向eMBASCIIStart函數
/* modbus ascii啟動函數 */ void eMBASCIIStart(void) {ENTER_CRITICAL_SECTION();/* 串口打開接收、關閉發送 */vMBPortSerialEnable(TRUE, FALSE);/* 接收狀態設置為接收空閑 */eRcvState = STATE_RX_IDLE;EXIT_CRITICAL_SECTION();/* 發送就緒事件 */(void)xMBPortEventPost(EV_READY); }啟動RTU時,接收狀態eRcvState 設置為接收空閑態STATE_RX_IDLE,打開接收中斷,向主程序發送就緒事件。
?
主程序接收到就緒事件后什么也沒做?
/* modbus輪詢 */ eMBErrorCode eMBPoll(void) {....../* 獲取事件 */if(xMBPortEventGet(&eEvent) == TRUE){/* 判斷事件類型 */switch(eEvent){/* 就緒事件 */case EV_READY:break;......}}return MB_ENOERR; }?
ASCII傳輸模式下,modbus報文包含明確的起始字符和結束字符
因此在接收空閑態下,如果接收到數據,先判斷是否為起始字符。如果是起始字符,使能超時定時器,將接收狀態eRcvState切換為接收態STATE_RX_RCV。由于ASCII模式下每個字節需要兩個字符編碼,所以需要來回切換高半字和低半字,一開始將字節位置初始化為高半字。
/* modbus ascii接收一個字節函數 */ BOOL xMBASCIIReceiveFSM(void) {BOOL xNeedPoll = FALSE;UCHAR ucByte;UCHAR ucResult;assert_param(eSndState == STATE_TX_IDLE);/* 串口接收一個字節 */(void)xMBPortSerialGetByte((CHAR *) & ucByte);/* 判斷接收狀態 */switch(eRcvState){....../* 接收空閑狀態 */case STATE_RX_IDLE:/* 起始字符 */if(ucByte == ':'){/* 超時定時器使能 */vMBPortTimersEnable();/* 接收緩沖區偏移量初始化為0 */usRcvBufferPos = 0;/* 字節位置初始化高半字節 */eBytePos = BYTE_HIGH_NIBBLE;/* 將接收狀態切換為接收態 */eRcvState = STATE_RX_RCV;}break;}return xNeedPoll; }在接收態下,將一個一個字節接收數據。直到接收到結束字符CR,將接收狀態eRcvState切換為等待結束態STATE_RX_WAIT_EOF。
/* modbus ascii接收一個字節函數 */ BOOL xMBASCIIReceiveFSM(void) {BOOL xNeedPoll = FALSE;UCHAR ucByte;UCHAR ucResult;assert_param(eSndState == STATE_TX_IDLE);/* 串口接收一個字節 */(void)xMBPortSerialGetByte((CHAR *) & ucByte);/* 判斷接收狀態 */switch(eRcvState){/* 接收態 */case STATE_RX_RCV:/* 超時定時器使能 */vMBPortTimersEnable();/* 起始字符 */if(ucByte == ':'){/* 字節位置重新初始化高半字節 */eBytePos = BYTE_HIGH_NIBBLE;/* 接收緩沖區偏移量重新初始化為0 */usRcvBufferPos = 0;}/* 結束字符,CR */else if(ucByte == MB_ASCII_DEFAULT_CR){/* 將接收狀態設置為等待LF狀態 */eRcvState = STATE_RX_WAIT_EOF;}/* 普通數據 */else{/* 將ascii碼轉換為數字 */ucResult = prvucMBCHAR2BIN(ucByte);/* 判斷高半字節還是低半字節 */switch(eBytePos){/* 高半字節 */case BYTE_HIGH_NIBBLE:/* 數據幀最大256字節 */if(usRcvBufferPos < MB_SER_PDU_SIZE_MAX){/* 將數據填入高半字節 */ucASCIIBuf[usRcvBufferPos] = (UCHAR)(ucResult << 4);/* 字節位置設置為低半字節 */eBytePos = BYTE_LOW_NIBBLE;break;}/* 接收字節數超過256字節 */else{/* 接收狀態設置為空閑態 */eRcvState = STATE_RX_IDLE;/* 超時定時器關閉 */vMBPortTimersDisable();}break;/* 低半字節 */case BYTE_LOW_NIBBLE:/* 將數據填入低半字節 */ucASCIIBuf[usRcvBufferPos] |= ucResult;/* 字節數加一 */usRcvBufferPos++;/* 字節位置設置為高半字節 */eBytePos = BYTE_HIGH_NIBBLE;break;}}break;......}return xNeedPoll; }在等待結束態下,如果接收到LF字符,則表示一幀數據結束。關閉超時定時器,將接收狀態eRcvState切換為接收空閑態STATE_RX_IDLE,向主程序發送接收完成事件。
/* modbus ascii接收一個字節函數 */ BOOL xMBASCIIReceiveFSM(void) {BOOL xNeedPoll = FALSE;UCHAR ucByte;UCHAR ucResult;assert_param(eSndState == STATE_TX_IDLE);/* 串口接收一個字節 */(void)xMBPortSerialGetByte((CHAR *) & ucByte);/* 判斷接收狀態 */switch(eRcvState){......./* 等待LF狀態 */case STATE_RX_WAIT_EOF:/* 檢查該字節是否為LF */if(ucByte == ucMBLFCharacter){/* 超時定時器關閉 */vMBPortTimersDisable();/* 將接收狀態設置為接收空閑狀態 */eRcvState = STATE_RX_IDLE;/* 發送接收完成事件 */xNeedPoll = xMBPortEventPost(EV_FRAME_RECEIVED);}/* 該字節為起始字符 */else if(ucByte == ':'){/* 字節位置重新初始化高半字節 */eBytePos = BYTE_HIGH_NIBBLE;/* 接收緩沖區偏移量重新初始化為0 */usRcvBufferPos = 0;/* 將接收狀態重新切換為接收態 */eRcvState = STATE_RX_RCV;/* 超時定時器使能 */vMBPortTimersEnable();}/* 該字節不是LF字符也不是LF字符 */else{/* 將接收狀態設置為接收空閑狀態 */eRcvState = STATE_RX_IDLE;}break;......}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調用的eMBASCIIReceive函數。主要工作是,對數據幀進行LRC校驗,然后對數據幀進行拆分。
/* modbus ascii接收函數 */ eMBErrorCode eMBASCIIReceive(UCHAR *pucRcvAddress, UCHAR **pucFrame, USHORT *pusLength) {eMBErrorCode eStatus = MB_ENOERR;ENTER_CRITICAL_SECTION();assert_param(usRcvBufferPos < MB_SER_PDU_SIZE_MAX);/* ASCII數據幀最小3字節,進行LRC校驗 */if((usRcvBufferPos >= MB_SER_PDU_SIZE_MIN) && (prvucMBLRC((UCHAR *)ucASCIIBuf, usRcvBufferPos) == 0)){/* 從機地址 */*pucRcvAddress = ucASCIIBuf[MB_SER_PDU_ADDR_OFF];/* PDU長度=ADU長度-1字節(地址)-1字節(LRC) */*pusLength = (USHORT)(usRcvBufferPos - MB_SER_PDU_PDU_OFF - MB_SER_PDU_SIZE_LRC);/* PDU數據指針 */*pucFrame = (UCHAR *)&ucASCIIBuf[MB_SER_PDU_PDU_OFF];}/* 檢驗失敗 */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指針調用eMBASCIISend對主機進行響應。主要工作包括:將PDU封裝為ADU數據,將發送狀態eSndState切換為發送開始態STATE_TX_START,并啟動發送,發送一個字節。
?
在發送開始態下,發送起始字符。然后將發送狀態eSndState切換為發送數據態STATE_TX_DATA,將字節位置初始化為高半字節。
/* modbus ascii發送一個字節函數 */ BOOL xMBASCIITransmitFSM(void) {BOOL xNeedPoll = FALSE;UCHAR ucByte;assert_param(eRcvState == STATE_RX_IDLE);/* 判斷發送狀態 */switch(eSndState){/* 發送開始狀態 */case STATE_TX_START:/* 發送起始字符 */ucByte = ':';xMBPortSerialPutByte((CHAR)ucByte);/* 發送狀態設置為發送態 */eSndState = STATE_TX_DATA;/* 字節位置初始化高半字節 */eBytePos = BYTE_HIGH_NIBBLE;break;......}return xNeedPoll; }在發送數據態下,將數據一個字節一個字節發送出去。直到數據都發送完畢,發送結束字符CR,將發送狀態eSndState設置為發送結束態STATE_TX_END。
/* modbus ascii發送一個字節函數 */ BOOL xMBASCIITransmitFSM(void) {BOOL xNeedPoll = FALSE;UCHAR ucByte;assert_param(eRcvState == STATE_RX_IDLE);/* 判斷發送狀態 */switch(eSndState){....../* 發送態 */case STATE_TX_DATA:/* 還有數據未發送 */if(usSndBufferCount > 0){/* 判斷字節位置 */switch(eBytePos){/* 高半字節 */case BYTE_HIGH_NIBBLE:/* 將數字轉換為ascii碼 */ucByte = prvucMBBIN2CHAR((UCHAR)(*pucSndBufferCur >> 4));/* 將數據發送出去 */xMBPortSerialPutByte((CHAR)ucByte);/* 字節位置設置為低半字節 */eBytePos = BYTE_LOW_NIBBLE;break;/* 低半字節 */case BYTE_LOW_NIBBLE:/* 將數字轉換為ascii碼 */ucByte = prvucMBBIN2CHAR((UCHAR)(*pucSndBufferCur & 0x0F));/* 將數據發送出去 */xMBPortSerialPutByte((CHAR)ucByte);/* 將指針向后偏移一個 */pucSndBufferCur++;/* 字節位置設置為高半字節 */eBytePos = BYTE_HIGH_NIBBLE;/* 剩余字節數減一 */usSndBufferCount--;break;}}/* 數據發完 */else{/* 發送結束字符,CR */xMBPortSerialPutByte(MB_ASCII_DEFAULT_CR);/* 將發送狀態設置為發送結束態 */eSndState = STATE_TX_END;}break;......}return xNeedPoll; }在發送結束態下,發送結束字符LR,將發送狀態切換為通知態。
/* modbus ascii發送一個字節函數 */ BOOL xMBASCIITransmitFSM(void) {BOOL xNeedPoll = FALSE;UCHAR ucByte;assert_param(eRcvState == STATE_RX_IDLE);/* 判斷發送狀態 */switch(eSndState){....../* 發送結束態 */case STATE_TX_END:/* 發送結束字符,LF */xMBPortSerialPutByte((CHAR)ucMBLFCharacter);/* 發送狀態設置為通知態 */eSndState = STATE_TX_NOTIFY;break;......}return xNeedPoll; }在通知態下,將發送狀態eSndState切換為發送空閑態STATE_TX_IDLE,打開接收中斷,通知主程序發送完畢事件
/* modbus ascii發送一個字節函數 */ BOOL xMBASCIITransmitFSM(void) {BOOL xNeedPoll = FALSE;UCHAR ucByte;assert_param(eRcvState == STATE_RX_IDLE);/* 判斷發送狀態 */switch(eSndState){....../* 通知態 */case STATE_TX_NOTIFY:/* 將發送狀態設置為空閑態 */eSndState = STATE_TX_IDLE;/* 發送發送完成事件 */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 ASCII传输的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: framebuffer驱动
- 下一篇: 2018生活消费趋势:越来越多95后开始