How to cast struct to uint8_t (error: conversion to non-scalar type requested)

How to cast struct to uint8_t (error: conversion to non-scalar type requested)

我需要在EEPROM中存储8个继电器的状态。我不想为移位而烦恼,我喜欢使用位域。所以我想这样定义它们:

typedef struct{
    uint8_t RELAY0_STATE:1;
    uint8_t RELAY1_STATE:1;
    uint8_t RELAY2_STATE:1;
    uint8_t RELAY3_STATE:1;
    uint8_t RELAY4_STATE:1;
    uint8_t RELAY5_STATE:1;
    uint8_t RELAY6_STATE:1;
    uint8_t RELAY7_STATE:1;
}relay_nvm_t;

relay_nvm_t   relay_nvm;

在我的主要代码流程中,我使用 relay_nvm 变量设置每个继电器的状态。范例

...
if(something)
{
    relay_nvm.RELAY0_STATE = 1;
    relay_nvm.RELAY1_STATE = 0;
    relay_nvm.RELAY2_STATE = 1;
    relay_nvm.RELAY3_STATE = 0;
    relay_nvm.RELAY4_STATE = 1;
    relay_nvm.RELAY5_STATE = 1;
    relay_nvm.RELAY6_STATE = 0;
    relay_nvm.RELAY7_STATE = 1;
}

然后最后当我需要 read/write 到 EEPROM 时,我只是将 relay_nvm 转换为 uint8_t 到 read/write 一个字节到 EEPROM。但是我收到 error: conversion to non-scalar type requested 错误。以下是我的功能。

static void NVM_Relay_Read(void)
{
    relay_nvm = (relay_nvm_t)NVM_ReadEepromByte(NVM_RELAY_INDEX);
}

static void NVM_Relay_Write(relay_nvm_t rs)
{
    NVM_WriteEepromByte(NVM_RELAY_INDEX, (uint8_t)rs);
}

有什么方法可以克服这个错误吗?我以为我可以通过类型转换来做到这一点。位域的使用使我的工作变得非常轻松,并使代码易于理解。

我知道在这种情况下,由于填充,位域可能不安全,但我认为我可以使用 POP-PUSH 来克服它(值得吗?)

我看到了更多处理这个问题的方法:

  1. 使用并集。

  2. 使用指针类型转换:*((uint8_t*)&relay_nvm)

  3. 使用uint8_t:

    uint8_t relay_nvm;
    #define RELAY0_MASK 1
    #define RELAY1_MASK 2
    #define RELAY2_MASK 4
    ...
    #define RELAY7_MASK 128

    // set exact relays state:
    relay_nvm = RELAY0_MASK | RELAY2_MASK | RELAY4_MASK | ... ;

    // set single relay (others left unchanged):
    relay_nvm |= RELAY2_MASK;

    // clear single relay (others left unchanged):
    relay_nvm &= ~RELAY2_MASK;

    // check current state of a relay:
    if (relay_nvm & RELAY2_MASK) { ... }

I didn't want to bother with shifting and I like using bitfields.

如果使用按位运算符是 "bother",那么在您掌握它们之前,您可能不应该编写嵌入式系统代码...这是编写非运算符的一个非常糟糕的理由-标准的、不可移植的代码。

与按位版本不同,位域有很多问题:未定义的位顺序、字节顺序依赖性、符号性指定不当、对齐和填充问题等等。您已经在编写带有 uint8_t 位字段的特定于平台的代码,因为 C 标准未涵盖这些代码。

如果你坚持使用位域,你必须阅读具体的编译器文档来了解它们是如何实现的。不要假设事物的分配方式有任何保证,因为在这种情况下没有标准化。


您的具体问题是您不能直接从结构类型(聚合 - 简单的英语 "container type")转换为 uint8_t 来回,原因与不能使用数组执行此操作的原因相同。您将不得不改用指向第一个元素的指针,然后将该元素转换为 uint8_t* 并取消引用。但这会带来一系列与对齐、兼容类型和 "strict aliasing".

有关的其他问题

一般而言,结构和联合不太适合内存映射目的,尤其是涉及到可移植性时。至少,您必须使用 #pragma pack(1) 或类似的特定于编译器的命令来启用打包。

因此,您真的应该考虑完全放弃位域并在原始 uint8_t 上使用按位运算符,因为这样可以解决很多问题。


作为旁注,存储在 EEPROM 中的所有变量都必须 volatile 合格,所有指向它们的指针也必须如此。否则,当您启用优化时,程序很可能会失控。