繁体   English   中英

C 消息封装(SPI/nRF24 驱动程序):有什么好的做法吗?

[英]C Message Encapsulation (SPI/nRF24 Driver): Any good practices?

我目前正在自制 SPI 驱动程序之上开发无线电 (nRF24) 模块驱动程序。 驱动非常简单:要通过无线电模块发送消息,您需要通过 SPI 发送消息,并以命令为前缀:

发送射频消息

我让它完美地工作,但我正在努力封装用户有效载荷(即在用户有效载荷之前添加 NRF24_CMD)。

截至今天,每次发送消息时,我都会为 SPI 有效负载分配一个新的 char 数组。 我在数组的第一个字节中编写无线电模块命令,然后复制用户有效负载,如下所示:

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);
}

这会产生很多无意义的 memory 访问,只是为了复制用户有效负载,我觉得还有改进的余地。

封装用户消息的有效方法是什么?

驱动程序开发社区中是否有针对此类问题的良好实践?

供参考:

  • 我正在编写一个微控制器(MSP430FR5969)裸机,所以我还没有实现任何动态分配机制
  • 我正在使用的 MCU 配备了 DMA

以下是我的一些想法:

  1. 使用 memcpy 而不是 for 循环 -> 更好,数组的副本会更快,但仍然需要复制有效负载
  2. 告诉用户分配缓冲区并将第一个字节留空->不需要复制,但用户需要了解硬件,我不希望这样
  3. 创建一个 c 结构来隐藏复杂性(参见下文)-> 我担心可能会出现对齐问题,并且由于我的 NRF24_CMD 字节需要直接跟在有效负载后面,我不知道我是否可以信任我的编译器。 ..

用结构隐藏复杂性:

struct nrf24_message_t {
   char cmd;
   char payload;
}

谢谢你的帮助!

雨果

由于命令字节和有效负载数组的基础数据类型都是char类型,因此您无需关心 alignment 问题——最重要的是,如果您一个接一个地定义,则两者之间不会有任何间隙。

我现在允许用户适当地分配 memory 并提供某种转换 function,例如:

// 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!

这是可见的用户界面,而实现可以看到此结构的详细信息:

//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);
}

等等:现在用户可以提供 memory,而数据前面仍有空间。

使用示例:

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);

在非常低的级别上工作肯定是具有挑战性的,这是我过去使用的主要是 C 的一部分,它通过向调用者直接提供指向缓冲区的指针来避免这种复制

struct nrf24_message_t {
   char buffer[MAX_SIZE];
};

static struct nrf24_message_t transmitBuffer; // assuming you have just one

// give the caller a pointer where it can stuff bytes, letting it
// know how much space is available. 
char *prepareForTransmit(int *nbytes)
{
    *nbytes = sizeof(transmitbuffer.buffer) - 1; // room for CMD

    transmitbuffer.buffer[0] = RF_SEND_CMD; // or whatever
    return transmitbuffer.buffer + 1; // room for CMD:
}

void transmit(int nbytes)
{
    // do the SPI thing. be sure to +1 for the cmd byte.
}

然后在调用者中它可以请求缓冲区-注意最大大小-然后传输。 我不知道您的数据包的格式,但您应该能够理解。

{
    ....
    int bufsize;
    char *tbuf = prepareForTransmit(&bufsize);

    // put stuff in tbuf up to the # of bytes available. Keep 
    // track of how many bytes you actually used!

    transmit(nbytes);
}

编辑:如果您有多个可能的访问权限,那就更有趣了。 最好的是,如果您可以使用小型 memory 分配器,以便调用者拥有缓冲区,但它确实必须保护实际的传输 function,这样它们就不会相互重叠。

以上只是一个框架; 它甚至不是一个可以编译的完整示例。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM