Frame decoding

As data comes in from the USB, it is placed in a stream buffer by the USB stack. The StreamBuffer function for incoming data can be accessed from GetUsbRxStreamBuff() in Drivers/HandsOnRTOS/VirtualCommDriverMultiTask.c:

StreamBufferHandle_t const * GetUsbRxStreamBuff( void )
{
return &vcom_rxStream;
}

This function returns a constant pointer to StreamBufferHandle_t. This is done so the calling code can access the stream buffer directly, but isn't able to change the pointer's value.

The protocol itself is a strictly binary stream that starts with 0x02 and ends with a CRC-32 checksum, transmitted in little endian byte order:

There are many different ways of serializing data. A simple binary stream was chosen here for simplicity. A few points should be considered:

  • The 0x02 header is a convenient delimiter that can be used to find the (possible) start of a frame. It is not sufficiently unique since any of the other bytes in the message can also be 0x02 (it is a binary stream, not ASCII). The CRC-32 at the end provides assurance that the frame was correctly received.
  • Since the frame has exactly 1 byte per LED value, we can represent the 0-100% duty cycle with 0-255 and we are guaranteed to have valid, in-range parameters, without any additional checking.
  • This simple method of framing is extremely rigid and provides no flexibility whatsoever. The moment we need to send something else over the wire, we're back to square one. A more flexible (and complex) serialization method is required if flexibility is desired.

The frameDecoder function is defined in mainColorSelector.c:

void frameDecoder( void* NotUsed)
{
LedCmd incomingCmd;
#define FRAME_LEN 9
uint8_t frame[FRAME_LEN];
while(1)
{
memset(frame, 0, FRAME_LEN);
while(frame[0] != 0x02)
{
xStreamBufferReceive( *GetUsbRxStreamBuff(), frame, 1,
portMAX_DELAY);
}
xStreamBufferReceive( *GetUsbRxStreamBuff(),
&frame[1],
FRAME_LEN-1,
portMAX_DELAY);
if(CheckCRC(frame, FRAME_LEN))
{
incomingCmd.cmdNum = frame[1];
incomingCmd.red = frame[2]/255.0 * 100;
incomingCmd.green = frame[3]/255.0 * 100;
incomingCmd.blue = frame[4]/255.0 * 100;
xQueueSend(ledCmdQueue, &incomingCmd, 100);
}
}
}

Let's break it down line by line:

  •  Two local variables, incomingCmd and frame, are created. incomingCmd is used to store the fully parsed command. frame is a buffer of bytes that is used to store exactly one frame's worth of data while this function parses/verifies it:
LedCmd incomingCmd;
#define FRAME_LEN 9
uint8_t frame[FRAME_LEN];
  • At the beginning of the loop, the contents of frame are cleared. Only clearing the first byte is strictly necessary so we can accurately detect 0x02 and since the frame is binary and has a well-defined length (only variable-length strings need to be null-terminated). However, it is very convenient to see 0 for unpopulated bytes, if you happen to be watching the variable during debugging:
memset(frame, 0, FRAME_LEN);
  • A single byte is copied from the StreamBuffer function into the frame until 0x02 is detected. This should indicate the start of a frame (unless we were unlucky enough to start acquiring data in the middle of a frame with a binary value of 0x02 in the payload or CRC):
 while(frame[0] != 0x02)
{
xStreamBufferReceive( *GetUsbRxStreamBuff(), frame, 1,
portMAX_DELAY);
}
  • The remaining bytes of the frame are received from StreamBuffer. They are placed in the correct index of the frame array:
xStreamBufferReceive( *GetUsbRxStreamBuff(), &frame[1], 
FRAME_LEN-1, portMAX_DELAY);
  • The entire frame's CRC is evaluated. If the CRC is invalid, this data is discarded and we'll begin looking for the start of another frame:
if(CheckCRC(frame, FRAME_LEN))
  • If the frame was intact, incomingCmd is filled out with the values in the frame:
 incomingCmd.cmdNum = frame[1];
incomingCmd.red = frame[2]/255.0 * 100;
incomingCmd.green = frame[3]/255.0 * 100;
incomingCmd.blue = frame[4]/255.0 * 100;
  • The populated command is sent to the queue, which is being watched by LedCmdExecutor(). Up to 100 ticks may elapse, waiting for space in the queue to become available before the command is discarded:
 xQueueSend(ledCmdQueue, &incomingCmd, 100);
It is important to note that none of the framing protocol is being placed in LedCmd, which will be sent through the queue – only the payload is. This allows more flexibility in how data is acquired before being queued, as we will see in the Reusing a queue definition for a new targesection.

Choosing the number of slots available in the queue can have important effects on the response of the application to incoming commands. The more slots that are available, the higher the likelihood that a command will incur a significant delay before being executed. For a system that requires more determinism on when (and if) a command will be executed, it is a good idea to limit the queue length to only a single slot and perform a protocol-level acknowledgment, based on whether the command was successfully queued.

Now that we've seen how the frame is decoded, the only remaining piece of the puzzle is how data is placed into the USB's receiving stream buffer.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.211.66