Working with Data Queues¶
Data queues are used for transferring packets from the RF core to the main CPU and vice versa. They are implemented as linked lists of queue entries. This document describes the available queue entry types and provides code snippets to create queues and to interact with them.
Queue Entry Types¶
The RF core supports 3 different kind of queue entries:
Name |
Data type |
Description |
---|---|---|
Single packet entry |
|
One packet per entry; data after the header. |
Pointer entry |
|
One entry per packet; data at another memory location. |
Partial entry |
|
Stores packets with unknown or unlimited length; data after the header. |
All queue entries start with a common header part, containing a pointer to the next entry, a configuration field and the length of the entry in bytes.
It is very important that each queue entry is aligned to 4-byte boundaries. Otherwise the RF core will fail to access the queue entry.
Field |
Type |
Description |
|
---|---|---|---|
pNextEntry |
|
Points to the next queue entry. |
|
status |
|
Current status of the entry.
|
|
config |
type |
|
Specifies the entry type.
|
lenSz |
|
Specifies the size of the packet length field in the data section. This is used for variable packet length.
|
|
irqIntv |
|
|
|
length |
|
|
The current state of buffer entries during packet reception is kept in the
status
field. Figure Figure 25. shows a typical scenario
where the RF core writes to a queue entry and the application reads the packet
payload before it makes the entry writable again.
Single Packet Entries rfc_dataEntryGeneral_t
¶
Single packet entries contain one packet per entry and store the data directly
behind the header so that the whole queue can be allocated in a linear memory
section. They are the simplest entry type and sufficient for many
applications. The data section may contain the packet length, configured by
config.lenSz
, the payload and optionally appended meta data, like the RX
timestamp or the CRC.
The length indicator at the begin of the data section may be omitted when the packet size can be determined otherwise. For packets with variable length it is necessary.
Field |
Type |
Description |
---|---|---|
… |
Generic header described in Table 5. |
|
data |
|
Dummy field representing the first byte in the data section. |
Pointer Entries rfc_dataEntryPointer_t
¶
A pointer entry is similar to a single packet entry, but doesn’t contain the data after the header. Instead, it holds a pointer to another memory location which contains the data. This queue type is useful when you want to:
keep the data storage separate from the queue entries share the same buffers
between different queues without recreating the queue structure move buffers
to another queue (for instance a custom queue implementation) without
copying the content
Field |
Type |
Description |
---|---|---|
… |
Generic header described in Table 5. |
|
pData |
|
Pointer to the physical data storage location |
Partial Entries rfc_dataEntryPartial
¶
The proprietary PHY supports an entry type where the data can be accessed before the entire packet is received over the air. It can be used for the following purposes:
When data must be read before the entire packet is received. For instance, when the packet contains a length field that is incompatible to the supported packet formats.
When the length of the packet is not known at the beginning of the packet.
When the length of the packet is too long for a single packet entry or when using unlimited packet length.
Partial entries may contain several packets. In this case, each packet in the
data section starts with a length field of size lenSz which can
be used to calculate the start of the next packet within the same entry. The
field nextIndex
contains the total number of written bytes in the entry.
Field |
Type |
Description |
|
---|---|---|---|
… |
Generic header described in Table 5. |
||
pktStatus |
numElements |
|
Number of packets committed to this entry. |
bEntryOpen |
|
|
|
bFirstCont |
|
|
|
bLastCont |
|
|
|
nextIndex |
|
The total number of committed bytes in the data section. This field is constantly being updated by the RF core while receiving. |
|
data |
|
Dummy field representing the first byte in the data section. |
The entry is updated as follows:
The
nextIndex
field is updated whenever a new bytes is written to the data section.While a packet is being received,
pktStatus.bEntryOpen
is set to 1 by the RF core.
When an entry element is finished, either because the packet ended or because
the element reached the end of the entry, pktStatus.bEntryOpen
is set to 0
by the RF core and pktStatus.numElements
is incremented. If the packet
continues in the next entry, pktStatus.bLastCont
is set to 1 by the radio
CPU. In this case, the pktStatus.bFirstCont
bit of the next entry is also
set to 1 by the RF core. If no next entry is available, the status is set to
DATA_ENTRY_UNFINISHED
, otherwise it is set to DATA_ENTRY_FINISHED
.
Each entry element starts with a length info field (depending on
config.lenSz
). If an entry contains a single packet, then the length info
is equal to the packet length. If the packet doesn’t fit into the current
entry, it is continued in the next entry. In this case, the length info
contains only the number of bytes within the current entry.
For a partial read RX entry, the RF core generates an Rx_Data_Written
interrupt to the system CPU whenever one or more bytes are written to the
entry. In addition, it generates an IRQ_RX_N_DATA_WRITTEN
interrupt when
config.irqIntv
bytes have been written since the last interrupt or since
the start of the entry element. The RF driver propagates this interrupt as
RF_EventNDataWritten.
Creating a Queue¶
In order to create a data queue, the following steps are necessary:
Allocate sufficient memory for all queue entries.
Initialize each queue entry header.
Create a queue object
The object of the type dataQueue_t
is defined in the header file
<ti/devices/${DEVICE_FAMILY}/driverlib/rf_mailbox.h>
:
// The queue structure
typedef struct {
uint8_t *pCurrEntry; // Points to the first entry, NULL for an empty queue
uint8_t *pLastEntry; // Pointer to the last entry, NULL for a circular queue
} dataQueue_t;
It holds a pointer to the first and to the last entry and may either have a
fixed or unlimited size. In the former case pLastEntry
points to the last
entry. In a circular queue, pLastEntry
is a null pointer.
The following example explains how to create a circular queue of 4 single- packet entries that can store up to 32 bytes.
As a first step, we need to allocate sufficient memory for all queue entries:
#include <ti/devices/DeviceFamily.h> #include DeviceFamily_constructPath(driverlib/rfc_data_entry.h) #include DeviceFamily_constructPath(driverlib/rfc_mailbox.h) #define BUFFER_ENTRIES 4 // Must be word-aligned #define DATA_SECTION_SIZE 32 // -1: Do not count the dummy data byte in the entry #define ENTRY_HEADER_SIZE (sizeof(rfc_dataEntryGeneral_t) - 1) #define BUFFER_SIZE_BYTES (BUFFER_ENTRIES * (ENTRY_HEADER_SIZE + DATA_SECTION_SIZE)) // Align the buffer to word boundaries. Example for GCC. uint8_t buffer[BUFFER_SIZE_BYTES] __attribute__ ((aligned (4)));
All queue entries need to be word-aligned. When the expected payload size is not a multiple of 4, choose the next greater word-aligned value as
DATA_SECTION_SIZE
.
Prepare the entry headers and close the ring:
rfc_dataEntryGeneral_t item = (rfc_dataEntryGeneral_t*)&buffer[0]; for (uint8_t i = 0; i < BUFFER_ENTRIES; i++) { item->config.type = DATA_ENTRY_TYPE_GEN; item->length = DATA_SECTION_SIZE; // Note: When creating partial items, add 4 // bytes for the additional header fields. item->status = DATA_ENTRY_PENDING; item->pNextEntry = ((uint8_t*)item) + ENTRY_HEADER_SIZE + DATA_SECTION_SIZE; if (i == (elements - 1)) { // Close the circle for the last item item->pNextEntry = buffer; } item = (rfc_dataEntryGeneral_t*)item->pNextEntry; }
Create a circular queue object:
dataQueue_t queue = { .pCurrEntry = &buffer[0]; .pLastEntry = NULL; };
Reading Data from a Queue¶
Whenever the RF core completes a packet, it raises the interrupt
IRQ_RX_ENTRY_DONE
which maps to the event RF_EventRxEntryDone
in the RF driver. When handling the interrupt and executing the callback, the
packet data is read from entry and the status
field must be set back to
DATA_ENTRY_PENDING
before the RF core can re-used it.
Keep the current entry in a variable:
// Initialize to the first entry
rfc_dataEntryGeneral_t* rxEntry = (rfc_dataEntryGeneral_t*)queue.pCurrEntry;
Read a packet from a single packet entry in an RX queue. This can either be done within a RF driver callback or in the RX task:
// The current entry to be read.
rfc_dataEntryGeneral_t* rxEntry;
// Packet starts with 1 byte length information (lenSz = 1)
uint8_t packetLength = *(uint8_t*)(&rxEntry->data);
// Payload follows.
uint8_t* packetDataPointer = (uint8_t*)(&rxEntry->data + sizeof(packetLength));
// Correct: Use memcpy to read the payload from the buffer.
uint32_t myValue;
memcpy(&myValue, packetDataPointer, sizeof(myValue));
// Dangerous: De-reference potentially misaligned pointers in the packet payload:
// myValue = *((uint32_t*)packetDataPointer);
Set rxEntry
to the next queue item for the next iteration:
// Mark the entry as being read
((volatile rfc_dataEntryGeneral_t*)rxEntry)->status = DATA_ENTRY_PENDING;
// Get the next entry
rfc_dataEntryGeneral_t* rxEntry = ((rfc_dataEntryGeneral_t*)rxEntry->pNextEntry);
Warning
When interacting with queue entries while the RF core is accessing the entry as well, you must make sure that the value being read by the system CPU is up-to-date. For instance when writing loops, the compiler might cache values in a register, especially when turning on optimization:
// Wrong code: Might cause a deadlock
rfc_dataEntryGeneral_t* entry;
while (entry->status != DATA_ENTRY_FINISHED) {}
Force the compiler to reload fields with concurrent access from memory on every access:
// Correct: status is being reloaded from memory on every access
rfc_dataEntryGeneral_t* entry;
while (((volatile rfc_dataEntryGeneral_t*)entry)->status != DATA_ENTRY_FINISHED) {}