2017-02-17 33 views
0

我必須處理簡單的串行協議。協議格式爲起始字節,長度,數據字節和校驗和。 我正在使用UART進行數據通信。我想知道處理協議的最佳方式是什麼。 在第一種方法中,我計劃在ISR內部處理接待。 ISR內部不會處理校驗和驗證。我的ISR可能看起來像下面的代碼(考慮通用微控制器)。但是ISR很少冗長(雖然代碼冗長,但有很多if語句,最終可能不會太冗長)處理ISR內部的協議,以避免原子性

在第二種方法中,ISR只會將數據轉儲到接收緩衝區,主程序將處理處理協議。雖然ISR很短,但我可能必須頻繁地禁用/啓用ISR,同時從主接入接收緩衝區以避免原子性問題。這可能會導致性能問題,頻繁的ISR禁用可能會導致數據丟失!

哪種方法最好?任何這樣的協議實現示例? 注意:下面的代碼只是一個大綱,尚未經過測試。可能很少有邏輯錯誤。

#ifndef TRUE 
#define TRUE 1 
#endif // TRUE 

#ifndef FALSE 
#define FALSE 0 
#endif // TRUE 

#define NUM_START_BYTES 1 
#define LENGTH_BYTE_POSITION (NUM_START_BYTES) 
#define START_BYTE1 0xAA 
#define START_BYTE2 0x55 
#define END_BYTE 0x0A 



#ifdef CHECKSUM_DISABLED 
#define CHECKSUM_BYTES 0 
#elif defined SIMPLE_CHECKSUM 
#define CHECKSUM_BYTES 1 
#else 
#define CHECKSUM_BYTES 2 
#endif // CHECKSUM_DISABLED 


#define NUM_START_BYTES 1 
#define LENGTH_BYTE_POSITION (NUM_START_BYTES) 
#define START_BYTE1 0xAA 
#define START_BYTE2 0x55 
#define END_BYTE 0x0A 

#define UDR0 0 // Only temporarily declared it as 0. It is actually a register in processor. 

enum FRAME_RECEIVE_STATUS 
{ 
    FRAME_SUCCESS=0, 
    START_BYTE_RECVD=1, 
    RECV_PROGRESS=2, 
    FRAME_RECEIVED=3, 
    CHECKSUM_ERROR=4, 
    FRAME_NOT_RECEIVED=5, 
}; 

volatile enum FRAME_RECEIVE_STATUS frameStatus=FRAME_NOT_RECEIVED; 

#define RX_MAX_SIZE 32 // size for received data buffer. 

volatile uint8_t RxData[RX_MAX_SIZE]; 
volatile uint8_t RxHead=0;    // Initialize the RxHead to 0 
volatile uint8_t frameLength=0;   // Overall length of the received data 


/**RX Complete interrupt service routine 
// Receive the data and write to buffer if buffer is not full 
Initially frameStatus=FRAME_NOT_RECEIVED; 
This protocol supports, 0, 1 or 2 start bytes and indicated by NUM_START_BYTES 

As soon as first START_BYTE is verified, frameStatus changes to START_BYTE_RECVD. 
As soon as second start byte is received, frameStatus changes to RECV_PROGRESS. 
That means as soon as START Bytes are verified (It could be 0 1 or 2), frameStatus changes to RECV_PROGRESS 

Packet Format: 
Start Byte1 (optional), Start byte2 (optional), Length=n, data bytes = n-check sum bytes, one or two check sum bytes 

Length includes data bytes and check sum 
Checksum may be 0, 1 or 2 bytes and indicated by CHECKSUM_BYTES field 
*/ 

void myRxISR() 
{ 

    // If buffer is full, we cannot transfer data. We probably want to discard the data if buffer is full. 
    // Yet to decide on this 
    if(RxHead<RX_MAX_SIZE) 
    { 
     RxData[RxHead++]=UDR0; 
     frameLength++; 
     if(frameStatus==RECV_PROGRESS) //Packet reception is already started 
     { 
      // We need to check if all bytes including checksum is received 
      //First verify the length field. Length field is immediately after START_BYTE fields 
      if(frameLength==CHECKSUM_BYTES+1) 
      { 
       //Minimum 1 byte must be there in any command excluding check sum 
       // In case the data length is less than 1+checksum bytes, 
       //we need to completely discard the transaction. 
       // Length is available in RxData[NUM_START_BYTES] 
       if(RxData[NUM_START_BYTES]<CHECKSUM_BYTES+1) 
       { 
        frameStatus=FRAME_NOT_RECEIVED; // Discard the data 
        RxHead=0; // Clear the received data buffer 
       } 
      } 
      else // Length is already received and verified. Receive other data bytes and CS 
      { 

       // Once the length is received, we need to count as many bytes as length 
       //and receive the complete the frame. 

       if(frameLength >= RxData[NUM_START_BYTES]+NUM_START_BYTES+1) //1 for length field itself 
       { 
        // Finished receiving the complete frame 
        //At the end, frameRecived flag must be set. 
        frameStatus=FRAME_RECEIVED; 
       } 
       else 
       { 
        //Nothing needs to be done. Just data bytes are being received 
       } 
      } 
     } 
     else 
     { 
      // Check if START_BYTE is present in this protocol or not. 
      // This code supports 0, 1 or 2 start bytes. 
      if(NUM_START_BYTES) 
      { 
       //First wait for the first START_BYTE. If first START_BYTE is received, 
       // check if second start byte is present and verify the second start byte. 
       // As soon as first start byte is received, status changes to START_BYTE_RECVD 

       if((frameStatus==START_BYTE_RECVD)) 
       { 
        // first byte is received already. This is the second byte 
        // Need to verify the second Byte in case there are two bytes 
        // In case there is only one start byte, control will not come here anyway 

        if(RxData[RxHead-1]==START_BYTE2) 
        { 
         frameStatus=RECV_PROGRESS; 
        } 
        else 
        { 
         //Discard data 
         RxHead=0; 
         frameStatus=FRAME_NOT_RECEIVED; 
        } 
       } 
       else // Just First start Byte is received 
       { 
        if(RxData[RxHead-1]==START_BYTE1) 
        { 
         if(NUM_START_BYTES>1) // 2 start bytes only possible in this protocol 
         { 
          // We need to wait for start byte2 next 
          frameStatus=START_BYTE_RECVD; 
         } 
         else 
         { 
          // Only one start byte. So start byte reception is successful 
          frameStatus=RECV_PROGRESS; 
         } 
        } 
        else 
        { 
         //Discard data 
         RxHead=0; 
         //frameStatus already FRAME_NOT_RECEIVED 
         //frameStatus=FRAME_NOT_RECEIVED; 
        } 
       } 
      } 
      else // NUM_START_BYTES=0. Means we directly start reception without any start bytes 
      { 
       frameStatus=RECV_PROGRESS; 
      } 
     } 

    } 
    else 
    { 
     //In case buffer is full, we need to see what to do. 
     // May be discard the data received. 
     frameStatus=FRAME_NOT_RECEIVED; // Discard the data 
     RxHead=0; // Clear the received data buffer 
    } 
} 
+3

你可能想研究*環緩衝區*或*隊列*。 –

+0

不需要禁用isr,只需使用帶緩衝區的寫緩衝區即可。和一個讀指針。你只需要正確處理一個可能的溢出 – Ctx

+1

讓isr代碼儘可能短(及時)總是最好的主意。 – LPs

回答

3

總體而言,這取決於您的要求。如果您有嚴格的實時要求,說明您的程序必須對收到的數據做出非常快速的響應,或者必須立即標記數據中的錯誤,那麼將解碼放入ISR中可能會有所幫助。雖然這不會很漂亮。

有這樣的要求的稀有案例確實存在,我做過一次這樣的需求的項目。但更有可能的是,你沒有這樣的要求。

然後首先要看的是如果你的UART外設得到DMA的支持。如果是這樣,那麼你應該利用它,並將DMA中的數據存入RAM中。這是最好的方法。

如果你沒有DMA支持,那麼你需要檢查你有多大的接收緩衝區。根據其大小,您可能可能重複輪詢接收緩衝區。如果可能的話,在中斷之前總是要選擇 - 輪詢是第二好的方法。

如果你沒有一個大的接收緩衝區,或者如果主程序忙於很多其他的事情,那麼你將不得不求助於中斷。請注意,如果沒有其他選項,中斷應始終是最後的選擇。他們對實際表現存在問題,他們很容易出錯,編程很棘手......他們總體上是邪惡的。

如果您有一個很大的接收緩衝區,您可以減少只響應緩衝區滿標誌觸發的中斷數量。

您應該儘可能減少中斷長度。正如評論中提到的那樣,您將需要一個環形緩衝區ADT,它可以快速存儲數據而無需太多開銷。

您還必須整理ISR和調用者代碼之間的重新連接,因爲他們都會訪問環形緩衝區。這可以通過信號量或通過啓用/禁用中斷來處理,或者利用ISR不會被主程序中斷等事實來處理。這非常系統化。

本質上,ISR應該是這樣的僞:

interrupt void UART_ISR (void) 
{ 
    check interrupt source if needed 

    if interrupt was rx data 
    { 
    check/grab ring buffer semaphore 
    store rx buffer inside ringbuffer ADT 
    release ring buffer semaphore 
    } 
    clear relevant flags 
} 

然後在調用者,你檢查/搶環緩衝信號,將內容複製到本地緩存,釋放信號量,然後踏踏實實地協議解碼。

保持協議解碼與ISR完全分離,使得乾淨的設計具有最小的中斷延遲。這也是一個很好的面向對象設計,因爲如果你改變了硬件時序,它不會影響協議,或者如果你修改協議,你不必重寫ISR。司機和協議之間會有鬆散的耦合。另外,正如你已經意識到的那樣,CRC計算可以咀嚼一些執行時間,所以最好讓它們遠離ISR。作爲一個方面說明,你需要某種形式的緩衝區溢出,幀錯誤等錯誤處理。這些可以作爲單獨的標誌/中斷提供。請執行此類錯誤處理!

+0

好主意將這個答案保存到文檔中。這種問題經常被問到。 – LPs

+0

是的,這個。我已經使用這種方法在儘可能簡單的小型嵌入式微處理器上實現ISR。 16字節地址邊界上的16字節環形緩衝區,僅使用「&0x0f」環繞,並且當ISR只修改'寫入'指針並且主線路僅修改'讀取'指針時不需要信號量。在大多數情況下,確保主線足夠快地消耗數據 - 或者更好地使ISR檢查讀/寫指針的重疊並丟棄更多的rx數據,如果指針重疊,主線設置rdptr = wrptr並丟棄部分解碼的任何東西。 – barny

+0

@barny如果你與ISR共享變量,總是需要信號量。或者,找出一種方法來保證所有訪問都是原子性的。例如,考慮緩衝區已滿,您開始讀取數據,讀取中途中斷,新數據寫入您正在讀取的位置的情況 - 您最終得到垃圾數據和細微的間歇性錯誤。 – Lundin

0

「最佳」由用戶/讀者定義,它不是通用的。所以只有你可以回答什麼是「最好的方法」。

您可以在ISR中創建一個狀態機,對於每個進入的字節都可以基於狀態相對快速地執行一個非常短的操作。將緩衝區中的字節添加到校驗和中,如果長度不變,則返回。

或更快的狀態機,它只是將字節保存在緩衝區中並返回。

在這兩種情況下(儘管基於您的系統設計,您可能會在isr中做更多的工作),但您可能需要應用程序輪詢新數據包。根據您的系統設計,isr可能已經完成了所有工作,隔離和校驗(或crc)數據包,因此前臺只需對其進行操作。或者前景必須做的所有這些工作的其他極端,isr只填充循環緩衝區的頭部。

你必須決定什麼纔是最好的,在你的系統設計中,你的實時需求是什麼,你最多可以忍受的最壞情況是由isr阻止的。和/或其他中斷是否會進入,有沒有一個定時器不能保持太長時間和/或您的系統是否具有中斷優先級方案,以便串行接收isr可以被中斷一個高優先級的定時器isr等。

基本上只是做你的系統工程,所有這些答案都沒有。

+0

在前景側有一個沉重的設計是非常好的,同樣,如果不是完全基於中斷的,那麼所有代碼​​/工作都在中斷處理程序中,這樣的設計也很重。只要取決於您的系統設計,無論是哪種情況,您都需要通過isr瞭解所有最差路徑,並確保它們符合系統設計。 –

1

我必須處理簡單的串行協議...開始,長度,數據,校驗和。

根據你的信息,我會說在ISR中管理所有。

特別是如果協議類型爲poll-reply,則可以使用單個緩衝區並向前臺發送已接收數據包的信號,並等待處理。數據將留在那裏。

請考慮一下:無論如何,你要做的任何事情都是要做(不考慮DMA)。 CPU的總「功率」強調相同。所以,從這個角度來看,在ISR中管理協議也是一樣的。

您在ISR中做的越多,CPU功率結果越「散佈」。或者換句話說,延期被延長 - 許多小的延誤,而不是更少的延期。如果接收數據包對CPU來說很重要:通過使用ISR,您的程序總體上會比較慢,但不會長時間停頓 - 不使用ISR會使程序更快,但更慢。請注意,「CPU重」。我不認爲你的協議很重,但我不知道。

第三個也是最後一個問題:ISR是否會竊取對其他ISR的反應?如果是,我們希望這個ISR儘可能快(快,不是短)。但是通常有可能優先考慮ISR,所以問題幾乎沒有了。

我上面所說的純粹是技術性的。這裏的其他一些答案討論了清晰度,編碼風格,「接收」和「處理接收數據」之間的區別。重要的事情,但我不考慮這一點。

所以得出結論,我會說在內部實施整個協議,ISR並不差。此外,CRC計算可以在ISR內部逐步進行(可能有可能)。這樣,並不是接收所有的數據包,然後重新讀取它來計算校驗和,只要數據包完成,就可以立即檢查校驗和。 「更多傳播」。

使用環緩衝區,信號量,在本地緩衝區中複製數據,都是浪費資源和時間的方法,如果不需要的話。如果我們資源不足,我們必須拯救他們。如果我們有足夠多的話,那麼反正沒有問題。這並不是說這些方法是錯誤的,而只是這個:它們真的需要嗎?特別是在微控制器(不完全是互聯網服務器)?