1

I am new to programming and I am not strong in pointers and typecasting, so I need some help.

I am using IAR Workbench and STM32L475. I am trying to convert 4 bytes in a struct to a float, after loading them from an Eeprom.

I know there can be challenges with Big/Little Endian and portability of code to other micro's but please do not clutter this thread with that as this is not important for me right now.

What am I doing wrong, thanks for helping?

Please keep it simple and explain "for dummies".

I am getting pe513 error.

My code:

struct Test {
    uint8_t Byte1;
    uint8_t Byte2;
    uint8_t Byte3;
    uint8_t Byte4;
} TestStruct;

float x = 0.0;

uint8_t *TestStruct_ptr;

int main(void)
{
    /* USER CODE BEGIN 1 */
    TestStruct.Byte1 = 0x41; //float value = 23.10
    TestStruct.Byte2 = 0xB8;
    TestStruct.Byte3 = 0xCC;
    TestStruct.Byte4 = 0xCD;  

    TestStruct_ptr = (float*)&TestStruct;

    x = (float*) TestStruct_ptr;

    // some code



    return 0;
}

Edit: I am loading an array from an Eeprom and must "collect" an array of four uint8 bytes to a float, they were a part of a struct before they were saved to the Eeprom. I will update with the exact error message when I get to work tomorrow.

I ended up using "union" as this seems to be the best solution.

My sample code looks like this now:

union Eeprom {
  struct {
    uint8_t Byte1;
    uint8_t Byte2;
    uint8_t Byte3;
    uint8_t Byte4;
  };
  float x;
  uint8_t Array[4];
};


int main(void)
{

  union Eeprom Test;

  //assign values to individual bytes
  Test.Byte1=0xCD; 
  Test.Byte2=0xCC;
  Test.Byte3=0xB8;
  Test.Byte4=0x41;

  //Assign values as an array (here individual bytes, overwrites above assigned values).
  //Data will be formatted as an array when loaded from E2prom.
  Test.Array[0]=0xCD;  
  Test.Array[1]=0xCC;
  Test.Array[2]=0xB8;
  Test.Array[3]=0x41;

  //Assign value as floating point value (overwrites the above assigned values)
  Test.x = 23.1;  

  printf("FPvalue %3.2f \n Byte1 %x\n Byte2 %x\n Byte3 %x\n Byte4 %x\n 
    Array[0] %x\n Array[1] %x\n Array[2] %x\n Array[3] %x\n",
    Test.x, Test.Byte1, Test.Byte2, Test.Byte3, Test.Byte4, 
    Test.Array[0], Test.Array[1], Test.Array[2], Test.Array[3]);
}

And the output looks like this:

floatvalue 23.10 
Byte1 cd
Byte2 cc
Byte3 b8
Byte4 41
Array[0] cd
Array[1] cc
Array[2] b8
Array[3] 41
Mike
  • 4,041
  • 6
  • 20
  • 37
Peter1
  • 87
  • 1
  • 9
  • 1
    *"I am getting pe513 error."* That's too cryptic for anyone to remember what it means. Please [edit] and add **complete** error message to your question. – user694733 Aug 14 '18 at 12:16
  • 8
    `TestStruct_ptr = (float*)&TestStruct;` That [violates strict aliasing](https://stackoverflow.com/questions/98650/what-is-the-strict-aliasing-rule) and is undefined behavior. `TestStruct` is not a `float` and can't be addressed as one. – Andrew Henle Aug 14 '18 at 12:17
  • I'm not clear. Are you trying to convert 4 `int` to 4 `float`, 4 `int` to single `float`, or convert the entire `struct` contents to a single `float` value? – ryyker Aug 14 '18 at 12:28
  • why don't you use float type instead of changing the data type from int to float? – danglingpointer Aug 14 '18 at 12:29
  • If your STM32 is operating in little-endian mode, you have the four bytes the wrong way round for a value of 23.10. Also, `&TestStruct` might not be correctly aligned for a `float *`. Why do you declare `TestStruct_ptr` as a `uint8_t *` and then do `TestStruct_ptr = (float*)&TestStruct;`? – Ian Abbott Aug 14 '18 at 12:31
  • If the EEPROM has no declared types - that is, the compiler doesn't know that there are any variables there, you can simply do `*(float*)address`. Otherwise, if you read the data into a temporary `uint8_t` buffer, you get the mentioned aliasing problems and you'll need to use a union or similar to dodge that problem. – Lundin Aug 14 '18 at 12:37
  • 1
    @Lundin Assuming `address` is correctly aligned of course! – Ian Abbott Aug 14 '18 at 13:31
  • @IanAbbott Why? There's plenty of CPUs without alignment requirments. Though of course those tend to be smaller 8 and 16 bitters without FPU. – Lundin Aug 14 '18 at 13:33
  • @Lundin I didn't mention *how* it should be aligned, only that it be *correctly* aligned. Here, "correctly" depends on the CPU/system as you say. – Ian Abbott Aug 14 '18 at 15:43
  • Further to what I wrote about little-endian mode a few posts up, it seems that the STM32 CPU always operates in little-endian mode as far as memory access by the CPU is concerned, so you do have the bytes the wrong way round. (Some of the peripheral components may have different or programmable endianness.) – Ian Abbott Aug 14 '18 at 16:00
  • Yes, address should be correct, I try to use the pointer to the 4 bytes. As I wrote Endianess is not important right now, but as it will be saved to mem in the same order I am not sure it will not be correct, but not crucial now. – Peter1 Aug 14 '18 at 18:35

4 Answers4

7

Union punning will do.

typedef union
{
    uint32_t u32;
    uint16_t u16[2];
    uint8_t  u8[4];
    float    f;
}b32data;

when you read from the NV memory just assign the correct member without any pointers.

ryyker
  • 22,849
  • 3
  • 43
  • 87
0___________
  • 60,014
  • 4
  • 34
  • 74
  • I am not sure this would solve my issue, the "Input" format is 4 uint8 bytes stored in an array, like the struct I made. – Peter1 Aug 14 '18 at 18:24
  • 1
    @Peter1 do you know the C at all ? Or you copy the code from somewhere - which is IMO after that comment more probable – 0___________ Aug 14 '18 at 18:44
  • It should not be such a big surprise as my question starts with "I am new to programming and I am not strong in pointers". Yes I did a long search first and tried (with my missing skills as you kindly point out) the solutions I found as good as I could. – Peter1 Aug 15 '18 at 04:53
5

You could use a union:

typedef union
{
    struct
    {
        uint8_t Byte1;
        uint8_t Byte2;
        uint8_t Byte3;
        uint8_t Byte4;
     };
     float      floatvalue;
}TestT;

TestT   Test;

Test.floatvalue = ......    //complete float
Test.Byte1 = .....          //single Byte to save in EEPROM
Mike
  • 4,041
  • 6
  • 20
  • 37
5

Since you don't care about the portability or endianness issues, you could simply copy the bytes with memcpy:

memcpy(&x, &TestStruct, sizeof x);

You may have seen this option or similar somewhere:

x = *(float*)&TestStruct;  // BAD!!!!! dereferencing using wrong pointer type

Don't do this! It breaks strict aliasing rules which is undefined behaviour.

user694733
  • 15,208
  • 2
  • 42
  • 68
  • I suggest `memcpy(&x, &TestStruct, sizeof x);` – 0___________ Aug 14 '18 at 12:34
  • @P__J__ Yeah `sizeof x` is better. I don't think `sizeof float` would have even compiled (should have been `sizeof(float)`). – user694733 Aug 14 '18 at 12:36
  • @user694733 Both `sizeof float` and `sizeof(float)` are correct. See [**6.5.3 Unary operators**](https://port70.net/~nsz/c/c11/n1570.html#6.5.3.4) and [**6.5.3.4 The sizeof and _Alignof operators**](https://port70.net/~nsz/c/c11/n1570.html#6.5.3.4). – Andrew Henle Aug 14 '18 at 13:57
  • 1
    @AndrewHenle No, `sizeof type-name` does not satisfy the syntax for `unary-expression` in 6.5.3. – Ian Abbott Aug 14 '18 at 16:12
  • @IanAbbott I missed that context here. And **that** is why I always use `()` with `sizeof`. – Andrew Henle Aug 14 '18 at 16:19
  • I will try memcpy when I get to work in about 10 hours. – Peter1 Aug 14 '18 at 18:27
1

It is illegal to type pun in this manner. As others have mentioned, unions can be used for doing type punning. Alternatively, as the intention seems to be to have a float that is byte-accessible, it is legal to type-pun to the char types. So instead of declaring the type of your variable as a struct of 4 chars, you could instead declare it as a float and then type-pun to unsigned char and access it as an array:

float x = 0.0;

unsigned char *test_ptr = (unsigned char *)&x;

int main(void)
{
    /* USER CODE BEGIN 1 */
    test_ptr[0] = 0x41; //float value = 23.10
    test_ptr[1] = 0xB8;
    test_ptr[2] = 0xCC;
    test_ptr[3] = 0xCD;  

    // some code

    return 0;
}

Of course the order of the bytes will be endian-dependent. I kept your byte-ordering, which should be correct for a big-endian target.

Christian Gibbons
  • 4,272
  • 1
  • 16
  • 29
  • It violates the strict aliasing rules and should be avoided – 0___________ Aug 14 '18 at 15:57
  • 1
    @P__J__ the `char` types are an exception to the aliasing rules. See C11 standard section 6.5 paragraph 7. *An object shall have its stored value accessed only by an lvalue expression that has one of the following types: [...] --a character type* – Christian Gibbons Aug 14 '18 at 16:06
  • 1
    Sorry, I do not understand how this solves the problem. – Peter1 Aug 14 '18 at 18:31
  • `pe513` seems to be an error IAR spits out related to breaking strict aliasing from the brief search I did on it. Because character types *are* allowed to alias other types (but not the other way around), this should not cause a strict aliasing error if the IAR compiler conforms to the C standard. The other answers regarding using `union`s should also get around this issue. – Christian Gibbons Aug 14 '18 at 18:45