What is the intended design of higher-level code using the driver? Will it operate on individual characters or bytes as they come in? Or does it make more sense for the higher-level code to batch transfers into blocks/frames of bytes?
Queue-based drivers are very useful when dealing with unknown amounts (or streams) of data that can come in at any point in time. They are also a very natural fit for code that processes individual bytes—uartPrintOutTask was a good example of this:
while(1)
{
xQueueReceive(uart2_BytesReceived, &nextByte, portMAX_DELAY);
//do something with the byte received
SEGGER_SYSVIEW_PrintfHost("%c", nextByte);
}
While ring-buffer implementations (such as the one in the preceding code) are perfect for streamed data, other code naturally gravitates toward operating on blocks of data. Say, for example, our high-level code is meant to read in one of the structures defined in Chapter 9, Intertask Communication, over a serial port.
The following excerpt is from Chapter_9/MainQueueCompositePassByValue.c:
typedef struct
{
uint8_t redLEDState : 1;
uint8_t blueLEDState : 1;
uint8_t greenLEDState : 1;
uint32_t msDelayTime;
}LedStates_t;
Rather than operate on individual bytes, it is very convenient for the receiving side to pull in an instance of the entire struct at once. The following code is designed to receive an entire copy of LedStates_t from a queue. After the struct is received, it can be operated on by simply referencing members of the struct, such as checking redLEDState, in this example:
LedStates_t nextCmd;
while(1)
{
if(xQueueReceive(ledCmdQueue, &nextCmd, portMAX_DELAY) == pdTRUE)
{
if(nextCmd.redLEDState == 1)
RedLed.On();
else
. . .
This can be accomplished by serializing the data structure and passing it over the communication medium. Our LedStates_t struct can be serialized as a block of 5 bytes. All three red, green, and blue state values can be packed into 3 bits of a byte and the delay time will take 4 bytes:
In this case, it makes sense for the underlying peripheral driver to operate on a buffer of 5 bytes, so a buffer-based approach that groups a transfer into a block of 5 bytes is more natural than a stream of bytes. The following pseudo-code outlines an approach based on the buffer-based driver we wrote in the previous section:
uint8_t ledCmdBuff[5];
startReceiveInt(ledCmdBuff, 5);
//wait for reception to complete
xSemaphoreTake(cmdReceived, portMAX_DELAY);
//populate an led command with data received from the serial port
LedStates_t ledCmd = parseMsg(ledCmdBuff);
//send the command to the queue
xQueueSend(ledCmdQueue, &ledCmd, portMAX_DELAY);
In a situation like the previous one, we have covered two different approaches that can provide efficient implementations:
- A buffer-based driver (receiving 5 bytes at a time)
- A stream buffer (the receiving side can be configured to acquire 5 bytes at a time)