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

Viewed 721

I need to store the state of 8 relays in EEPROM. I didn't want to bother with shifting and I like using bitfields. So I wanted to define them like this:

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;

In my main code flow, I set the state of each relay using relay_nvm variable. Example

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

Then finally when I need to read/write to EEPROM, I just cast relay_nvm to uint8_t to read/write one byte to EEPROM. But I get error: conversion to non-scalar type requested error. Below are my functions.

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

Is there any way we can get over this error? I thought I could just do it by typecasting. The use of bitfields makes my job very easy and makes the code easy to understand.

I know that bitfields may not be safe in this case due to the padding but I think I can get over it using POP-PUSH (is it worth?)

2 Answers

I see more ways to handle this:

  1. Use union.

  2. Use pointer type-cast: *((uint8_t*)&relay_nvm)

  3. Use 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.

If using the bitwise operators is a "bother", then you probably shouldn't be writing embedded systems code until you've gotten a hang of them... this is a very poor rationale for writing non-standard, non-portable code.

Unlike the bitwise version, bit-fields come with tons of problems: undefined bit order, endianess-dependence, poorly specified signedness, alignment & padding hiccups and so on. You are already writing platform-specific code with a uint8_t bit-fields, because those are not covered by the C standard.

If you insist on using bit-fields, you have to read the specific compiler docs about how they are implemented. Don't assume there are any guarantees regarding how things are allocated, because there is no standardization in this case.


Your specific problem is that you can't convert directly from a struct type (an aggregate - in plain english a "container type") to a uint8_t and back, for the same reason as why you can't do that with an array. You will have to use a pointer to the first element instead, then cast that one to uint8_t* and de-reference. But that comes with a bunch of other problems related to alignment, compatible types and "strict aliasing".

Structs and unions in general aren't very suitable for memory-mapping purposes, especially not when it comes to portability. At a minimum, you have to enable packing with #pragma pack(1) or similar compiler-specific commands.

So you should really consider dropping the bit-field entirely and use bitwise operators on a raw uint8_t, since you get rid of so many problems that way.


As a side-note, all variables stored in EEPROM must be volatile qualified, as must all pointers to them. Otherwise there is a big chance that the program goes haywire when you enable optimizations.

Related