I'm currently developing a radio (nRF24) module driver on top of a homemade SPI driver. The driver is pretty simple: to send a message via the radio module, you need to send the message via SPI, prefixed with a command:
I got it working perfectly, but I'm struggling with the encapsulation of the user payload (i.e adding the NRF24_CMD before the user payload).
As of today, I'm allocating a new char array for the SPI payload each time I'm sending a message. I'm writing the radio module command in the first byte of the array, and then copying the user payload as demonstrated here:
void nrf24_send(char * payload, size_t length){
char spi_payload[MAX_DATA];
spi_payload[0] = RF_SEND_CMD;
for(int i=0; i<length; i++){
spi_payload[i+1] = payload[i];
}
spi_write(spi_payload, length + 1);
}
This generates a lot of pointless memory accesses just to copy the user payload and I feel that there is room for improvement.
What would be an efficient way to encapsulate the user message ?
Is there a good practice in the driver development community for these kinds of problems ?
FYI:
Here are a few ideas I had:
Hiding the complexity with a struct:
struct nrf24_message_t {
char cmd;
char payload;
}
Thanks for your help!
Hugo
As the command byte and the payload array's underlying data type are all of type char
you don't need to care for alignment issues – most important, there won't be any gaps in between if you define one after the other.
I'd now allow the user to allocate the memory appropriately and provide some kind of conversion function, e.g.:
// header:
// TODO: include guards, etc
struct nrf24_message;
struct nrf24_message* nrf24_createFromRaw(size_t size, char* raw);
// to allow the user to write to
size_t nrf24_bufferSize(struct nrf24_message* message);
char* nrf24_buffer(struct nrf24_message* message);
void nrf24_send(struct nrf24_message* message, size_t length);
// still a length parameter:
// maybe a message gets shorter than maximum the buffer size!
This is the visible user interface while the implementation sees the details of this struct:
//source:
struct nrf24_message
{
size_t payloadSize;
char cmd;
char payload[]; // flexible array member...
};
struct nrf24_message* nrf24_createFromRaw(size_t size, char* raw)
{
if(size <= sizeof(size_t) + 1) // < if you want to allow empty message
{
// failure! buffer is too short
return NULL;
}
struct message* m = (struct message*)raw;
m->payloadSize = size - sizeof(size_t) - 1;
return m;
}
size_t nrf24_bufferSize(struct message* nrf24_message)
{
return message->payloadSize;
}
char* nrf24_buffer(struct nrf24_message* message)
{
return message->payload;
}
void nrf24_send(struct nrf24_message* message, size_t length)
{
spi_write(&message->cmd, length + 1);
}
Et voilà: Now the user can provide memory while you still have space in front of the data.
Usage example:
char raw[256];
struct* nrf24_message = nrf24_createFromRaw(sizeof(raw), raw);
size_t length = snprintf(nrf24_buffer(message), nrf24_bufferSize(message), "...");
nrf24_send(message, length);