c - 在 ISR 内部处理协议(protocol)以避免原子性

标签 c embedded microcontroller

我必须处理简单的串行协议(protocol)。协议(protocol)格式为起始字节、长度、数据字节和校验和。 我正在使用 UART 进行数据通信。我想知道处理协议(protocol)的最佳方式是什么。 在第一种方法中,我计划在 ISR 本身内部处理接收。校验和验证不会在 ISR 内部处理。我的 ISR 可能类似于下面的代码(考虑通用微 Controller )。但是ISR有点冗长(代码虽然冗长,但是if语句很多,最终可能不会太冗长)

在第二种方法中,ISR 会将数据转储到接收缓冲区中,主程序将负责处理协议(protocol)。尽管 ISR 很短,但我可能不得不在从 main 访问接收缓冲区时频繁禁用/启用 ISR 以避免原子性问题。这可能会导致性能问题,频繁的 ISR 禁用可能会导致数据丢失!!

哪种方法最好?有这样的示例协议(protocol)实现吗? 注意:下面的代码只是一个大纲,还没有经过测试。几乎没有逻辑错误。

#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
}

然后在调用者中,检查/获取环形缓冲区信号量,将内容复制到本地缓冲区,释放信号量,然后开始进行协议(protocol)解码。

将协议(protocol)解码与 ISR 完全分开可以提供一个干净的设计,中断延迟最小。这也是很好的 OO 设计,因为如果更改硬件时序,不会影响协议(protocol),或者如果更改协议(protocol),则不必重新编写 ISR。驱动程序和协议(protocol)之间的耦合松散。

此外,正如您已经意识到的那样,CRC 计算会消耗一些执行时间,因此最好也让这些远离 ISR。

作为旁注,您需要某种形式的错误处理来处理缓冲区溢出、帧错误等。这些可能作为单独的标志/中断提供。实现此类错误处理!

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

相关文章:

c - 从文件中的特定位置读取特定数据(C)

c - 我如何在 c 中使用 read() 向后读取文件?

c++ - 从0还是1开始循环?哪一个,为什么?

embedded - 对于软件开发人员来说,学习如何对微 Controller 进行编程有多难?

c - 时间差(以微秒为单位)

lua - 语言是 Lua,单词是 "embedded"

Linux 2.6.23 。接收错误。读取函数返回-1

c - 使用 tiva 系列 c 将数据从微 Controller 发送到计算机

c - 如何使timer0使用1 :64 in 16 bit mode?的预分频器

c - 输出错误 [C]