我必须处理简单的串行协议。协议格式为起始字节、长度、数据字节和校验和。
我正在使用UART进行数据通信。我想知道处理协议的最好方法是什么。
在第一种方法中,我计划在ISR内部处理接收。ISR内部不会处理校验和验证。我的ISR可能看起来像下面的代码(考虑通用微控制器)。但是ISR有点长(虽然代码很长,但是有很多if语句,最终可能不会太长)
在第二种方法中,ISR将数据转储到接收缓冲区,主程序负责处理协议。虽然ISR很短,但是在从main访问接收缓冲区时,我可能需要经常禁用/启用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
    }
}

最佳答案

总的来说,这取决于您的需求。如果您有硬实时要求,说明您的程序必须对接收到的数据作出非常快速的响应,或者如果必须立即标记数据中的错误,那么将解码放在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。你的驱动器和protcol之间的耦合松了。
此外,正如您已经意识到的,CRC计算会占用一些执行时间,因此也最好让这些计算远离isr。
另外,您需要对缓冲区溢出、帧错误等进行某种形式的错误处理。这些错误可以作为单独的标志/中断提供。请执行这样的错误处理!

关于c - 在ISR中处理协议(protocol)以避免原子性,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/42294750/

10-11 22:23