2017-02-17 5 views
0

私は単純なシリアルプロトコルを扱わなければなりません。プロトコルのフォーマットはStart byte、Length、Data bytes、Check sumです。 データ通信にUARTを使用しています。私はプロトコルを処理する最良の方法が何であるかを知りたい。 最初の方法では、ISR自体で受信を処理する予定でした。チェックサムの検証はISR内では処理されません。私のISRは以下のコードのように見えるかもしれません。しかし、ISRは少し時間がかかります(コードは長すぎますが、多くのif文があり、長すぎるとは限りません)。アトミック性を避けるためにISR内のプロトコルを処理する

2番目の方法では、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をサポートしていない場合は、受信バッファの容量を確認する必要があります。そのサイズに応じて、受信バッファを繰り返しポーリングすることができます。可能であれば割り込みの前に常に優先することです。ポーリングは2番目に良い方法です。

大きな受信バッファがない場合、またはメインプログラムが他の多くのことで忙しい場合は、割り込みに頼らざるを得ません。他のオプションがない場合は、割り込みを常に考慮する必要があります。彼らはリアルタイムのパフォーマンスには問題があり、バグが発生しやすく、プログラムするのが難しいです...一般的には悪です。

受信バッファが大きい場合は、バッファフルフラグに応答することによってトリガされる割り込みの数を減らすことができます。

できるだけ割り込みの長さを最小にする必要があります。コメントの中で述べたように、オーバーヘッドをかけずにデータをすばやく保存できるリングバッファ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を書き直す必要はありません。あなたは、ドライバとprotcolの間の疎結合を取得します。また

すでに実現しているとして、あまりにも離れてISRのからそれらを保つために良いですので、CRCの計算は、いくつかの実行時間をかむことができます。サイドノートとして

、あなたがエラーをフレーミング、バッファオーバーランのエラー処理のいくつかのフォームを必要とするなど、これらは別々のフラグ/割り込みとして利用できます。このようなエラー処理を実装してください!

+0

ドキュメントへのこの答えを保存することをお勧めします。この種の質問は頻繁に尋ねられます。 – LPs

+0

ええ、これ。私は非常に小さな組み込みマイクロプロセッサでISRを可能な限りシンプルにするこのアプローチを使用しました。 "&0x0f"を使用するだけで16バイトのアドレス境界に16バイトのリングバッファがあり、ISRが '書き込み'ポインタを変更し、メインラインが '読み取り'ポインタを変更する場合にはセマフォの必要はありません。ほとんどの場合、メインラインがデータをすばやく消費していることを確認してください。ポインタが重複している場合は、メインラインがrdptr = wrptrに設定され、部分的にデコードされたものはすべて破棄されます。 – barny

+0

@barny ISRと変数を共有する場合は、常にセマフォが必要です。また、すべてのアクセスがアトミックであることを保証する方法を見つけることもできます。たとえば、バッファがいっぱいで、データの読み込みを開始し、読み込みの途中で中断した場合、読み込んだ新しい場所に新しいデータが書き込まれます。ガベージデータと微妙な断続的なバグがあります。 – Lundin

0

「ベスト」はユーザ/読者によって定義されているため、普遍的ではありません。だから、あなたのために「最良の方法」が何であるかを答えることができます。

あなたは比較的早く状態に基づいて可能性がでてくる各バイトのために非常に短い操作を行うことISRにステートマシンを作成することができます。バッファにバイトを保存します。長さがバンプ状態の場合はチェックサムに追加します。そうでない場合は戻りません。

より速いステートマシンで、バイトをバッファに保存して戻すだけです。 (あなたはおそらく、ISRに、より多くを行うことができ、あなたのシステム設計に基づいている)の両方の場合で

おそらく新しいパケットのためのアプリケーション・ポーリングを持っています。あなたのシステム設計に応じて、isrはパケットを分離しチェックサム(またはcrc)してフォアグラウンドだけを操作する必要があります。それとももう一方の極端なフォアグラウンドでも、この作業をすべてやっていなければならず、isrは循環バッファの先頭を満たすだけです。あなたは最高のが何であるかを決定するために決定する必要がありますどのような

は、あなたがISRではオフに開催されて耐えることができるどのくらい最悪の場合、あなたのリアルタイムの要件が何であるか、お使いのシステム設計です。および/または他の割り込みが入って来る頻度は、定期的なタイマーが長すぎることができない、またはシステムが割り込みの優先順位スキームを持っているため、シリアル受信isrが高優先度タイマーなど

基本的にはシステムエンジニアリングだけであり、これらの回答はすべて抜けています。

+0

フォアグラウンド側では重いデザイン、完全に割り込みではないにしても重いデザイン、すべてのコード/作業が割り込みハンドラにあるのはまったく問題ありません。いずれの場合も、あなたはすべてを知っていなければならず、最悪の場合のパスをisrを通してシステム設計に収める必要があります。 –

1

私は単純なシリアルプロトコル、Start、Length、Data、Checksumを処理する必要があります。

ISRのすべてを管理するとします。

特に、プロトコルがpoll-replyタイプの場合、単一のバッファを使用して、パケットが受信されたことをフォアグラウンドに通知して、処理を待つことができます。データはそのまま残ります。

これを考慮してください:キャラクターを受け取るために行うすべての作業は、とにかく(DMAを考慮しないで)実行する必要があります。 CPUの合計「電力」は同じように強調されます。したがって、その観点からは、ISR内のプロトコルを管理するかどうかは同じです。

ISRで行うことが多いほど、CPUパワーの「拡散」が増えます。つまり、遅れは広がりますが、遅延は少なくなりますが、遅れは少なくなります。パケットを受信するのがCPUにとって重い場合:ISRを使用すると、プログラムは全体的に遅くなりますが、長い休止はありません.ISRを使用しないと、プログラムは速くなりますが、遅くなります。 CPUには重いことに注意してください。私はあなたの議定書が重いとは思わないが、わからない。

3番目と最後の問題:ISRは他のISRと反応しますか?はいの場合、このISRを可能な限り速くしたい(高速、ではなく、短く)。しかし、しばしばISRの優先順位をつける可能性があるため、問題はほとんどなくなっています。

私が上で言ったのは純粋に技術的なことです。ここでのいくつかの他の答えは、明快さ、コーディングスタイル、「受信」と「受信データの処理」の分離について述べています。重要なことだが、私はそれを考慮していない。

結論としては、の内部にのISRが実装されていても悪くないと言います。さらに、CRC計算はISR内部で段階的に行うことができます(おそらく可能です)。このようにして、すべてのパケットを受信する代わりに、パケットを再読してチェックサムを計算すると、パケットが完了するとすぐにチェックサムをチェックできるようになります。 "もっと広がる"。

セマフォ、ローカルバッファ内にデータをコピーすることは、必要でない場合、リソースと時間を無駄にするすべての方法です。リソースが少ない場合は、それらを保存する必要があります。とにかく問題がありません。これは、それらの方法が間違っていると言うことではなく、単にこれが本当に必要なのかどうかということです。特にマイクロコントローラ(正確にインターネットサーバーではない)で?

関連する問題