我必须处理简单的串行协议。协议格式为起始字节、长度、数据字节和校验和。
我正在使用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/