0
\$\begingroup\$

I have uint32_t data, but my SPI transfer function command can send 8 bits at one time.
I want to create an uint8_t array. I tried the following, but I am not sure about the & * address game. Is this the correct way to split uint32?

uint8_t arr[4];
uint32_t Tx_PP= {0x02F003E7}; 
arr[0]=*Tx_PP
arr[1]=*(Tx_PP+8)
arr[2]=*(Tx_PP+16)
arr[3]=*(Tx_PP+24)
SPI_tranfer(USART1,arr[0]);
\$\endgroup\$
4
  • 1
    \$\begingroup\$ You can use a union. \$\endgroup\$ Commented Sep 26, 2020 at 9:04
  • \$\begingroup\$ See stackoverflow.com/questions/6499183/… \$\endgroup\$
    – ocrdu
    Commented Sep 26, 2020 at 9:09
  • \$\begingroup\$ I’m voting to close this question because it is a pure software question and belongs on stackoverflow.com (where lots of duplicates of this question may be found). \$\endgroup\$
    – Lundin
    Commented Sep 28, 2020 at 7:03
  • \$\begingroup\$ Also, this won't compile, so trying to compile it should answer the question "is this the correct way". \$\endgroup\$
    – Lundin
    Commented Sep 28, 2020 at 7:06

2 Answers 2

1
\$\begingroup\$

Your example as shown won't work, because you're using pointer arithmetic wrong, and are missing a key referencing (&) step. You're also trying to initialise a value of type uint32_t as it it was an array (uint32_t*), which it isn't.

The code *(Tx_PP+8) in C will take the uint32_t value (not pointer), add on to it 8, then read the value at that address. This will give you a memory address of 0x02F003E7 + 8, which is not in any way a byte within Tx_PP (well, it could be by sheer luck but it most likely wont be).

I think what you intended to write was *(&Tx_PP + 8) which would take the address in memory where the value of Tx_PP is stored (a pointer to it), offset the pointer by 8 * sizeof(uint32_t), and then dereference it to get the value. However this would again be incorrect as it would point to a memory address 32 bytes further into the memory, not one.

Instead you would need to cast the referenced pointer to a uint8_t* and then add 1 to move to the next byte.

arr[1]=*(((uint8_t*)&Tx_PP)+1);

Of course that's kind of ugly. There are cleaner alternatives.

One option is to use memcpy or similar:

memcpy(arr, &Tx_PP, sizeof(uint32_t)); //Copy bytes from Tx_PP into array

You could also use an intermediate pointer:

uint32_t Tx_PP = ...
uint8_t Tx_PP_int = (uint8_t)&Tx_PP; // Note: It is ok to cast down to byte, not to cast up to larger values due to potential address alignment issues.
arr[0] = Tx_PP_int[0];
arr[1] = Tx_PP_int[1]; // or *(Tx_PP_int+1)
arr[2] = Tx_PP_int[2];
arr[3] = Tx_PP_int[3];

In that second example, a for loop could be used rather than writing it out four times - which is essentially what memcpy does.


A StackOverflow duplicate was linked in the comments which upon reading the answers their raises a third example:

*(uint32_t*)arr = Tx_PP;

This basically treats arr as if its a single 32-bit number rather than four 8-bit numbers. I would strongly advise against using this. Unless arr happens to start in memory on a 32-bit boundary, this kind of cast can easily result in unaligned access errors.

\$\endgroup\$
1
  • \$\begingroup\$ Using pointer arithmetic is always incorrect, because that creates a needless dependency on CPU endianess. Use shifts instead. Also, casting to uint32_t* is always wrong and not just because of alignment, but also because of strict pointer aliasing. \$\endgroup\$
    – Lundin
    Commented Sep 28, 2020 at 7:07
3
\$\begingroup\$

I wouldn't use pointer arithmetic for this, it is less likely to be optimized well (even though modern compilers are trying hard) and has issues with endianness.

If you want to transfer as big endian:

uint8_t arr[4];
uint32_t Tx_PP= {0x02F003E7}; 
arr[0]=(Tx_PP >> 24) & 0xFF;
arr[1]=(Tx_PP >> 16) & 0xFF;
arr[2]=(Tx_PP >> 8) & 0xFF;
arr[3]=(Tx_PP) & 0xFF;
SPI_transfer(USART1,arr);

For little endian, adjust the shifts. The & 0xFF is not strictly necessary, but avoids warnings on MSVC, which otherwise complains that the conversion loses bits.

Compilers are generally smart enough to optimize this, e.g. on ARM you get

    push    {lr}                ; save return address
    movs    r0, #1              ; constant USART1
    movw    r3, #61442          ; 0xF002 to bottom 16 bits
    movt    r3, 59139           ; 0xE703 to top 16 bits
    sub     sp, sp, #12         ; create stack frame (4 bytes for arr, 8 bytes padding)
    add     r1, sp, #4          ; create pointer to arr
    str     r3, [sp, #4]        ; store all four bytes
    bl      SPI_transfer(PLT)   ; call
    add     sp, sp, #12         ; demolish stack frame
    ldr     pc, [sp], #4        ; return

Since this is the code for a big endian transfer, and the machine is little endian, the bytes are reversed here.

\$\endgroup\$
3
  • 1
    \$\begingroup\$ "For little endian, adjust the shifts." No, adjust the array indices. The shifts need to be there as-is, as a platform independent to grab the right byte regardless of CPU endianess. \$\endgroup\$
    – Lundin
    Commented Sep 28, 2020 at 7:04
  • \$\begingroup\$ Sorry i only Know basic C, Little endian and large endian are for designated the order of sending bits . What sending bits order has to do with dividing array by 4? Thanks. \$\endgroup\$
    – rocko445
    Commented Sep 28, 2020 at 14:01
  • \$\begingroup\$ @rocko445, you are transferring a 32 bit value over a serial link, splitting it into bytes for the transfer. If the serial link transports each byte with the highest bit first, you need to queue the most significant byte first, and vice versa, so the 32 bits are in the expected order. It is a good idea to explicitly write the expected order in the source code, that leads to fewer surprises. \$\endgroup\$ Commented Sep 28, 2020 at 14:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.