# What happen when you store something greater than 32 bits on STM32?

A couple of questions regarding the word size: STM32 series have 32-bit wide words.

• Considering that, what happens when we try to store something more than 32 bits or even uint64_t for instance? I gave it a try and defined uint64_t a = 4294967299; and what I see in the memory is 00000003. What does that mean? Is it truncating?

• an address stores 32 bits, isn't it? then why do I see in the resultant bytes (0x00000003) stored at &a different addresses every byte? so from MSB, 0x00 is at 0x20017FE8, the second byte 0x00 is at 0x20017FE9, ... and the last 0x3 is at 0x20017FEA. Shouldn't each set of 32 bits have a distinct address?

Edit:

a side question: SRAM is a part of the MCU (which contains the cortex Mx processor). Is little/big endian related to the processor or the memory? The value that I see in memory seems to be big endian but according to the RM, it's little endian

Tried this code out to test the endianness:

uint64_t a = 0x00008a5d78456301;
uint32_t b = (uint32_t) a;    // 0x78456301


shouldn't b store the first 32 bits (from smallest address) instead of the latter 32?

Any visuals would help a lot.

• 4294967299 = 0x0000_0001_0000_0003. STM32 CPUs are little-endian. A 64bits integer will occupy 8 bytes in memory. For example, from 0x20017FE8 to 0x20017FEF. Bytes in memory will be 03 00 00 00 01 00 00 00 (from ...FE8 to ...FEF) Jun 3, 2020 at 22:28
• Something is not right. uint64_t should be able to store that number. I think you have made a mistake in interpreting or displaying the memory contents. What happens behind the scenes is that two 32 bit registers are ganged together to make a 64 bit register. So in order to see the actual value stored in memory you need to look at both registers. Jun 3, 2020 at 22:29
• The C language has various rules about type promotions, and sometimes it may happen that you accidentally truncate a number before it finds its way into a 64 bit variable type. But that simple direct assignment should work. Jun 3, 2020 at 22:30
• @mkeith so I do see: 0x20017FE8 00000003 00000001 in the memory so I guess that's what you mean by ganging together to make a 64 bit register?
– MKD
Jun 3, 2020 at 22:33
• Shouldn't each set of 32 bits have a distinct address? The addresses are for bytes, so each byte will have a distinct address. Jun 3, 2020 at 22:45

Memory addresses are byte addresses. A word is made up of multiple bytes. An STM32 can (with some caveats) read 4 bytes (32 bits) at once, but each of those bytes has a separate address.

When you declare a uint64_t, you get an 8-byte (64-bit) variable. You're only looking at the lower four bytes, so what you see is correct. 4,294,967,299 in hexadecimal is:

0x00000001_00000003


If the variable is stored at address 0x20017000, the bytes will be:

+------------+------------+
|  Address   | Byte value |
+------------+------------+
| 0x20017000 | 0x03       |
| 0x20017001 | 0x00       |
| 0x20017002 | 0x00       |
| 0x20017003 | 0x00       |
| 0x20017004 | 0x01       |
| 0x20017005 | 0x00       |
| 0x20017006 | 0x00       |
| 0x20017007 | 0x00       |
+------------+------------+


This is for a little-endian CPU. If you add 1 to your uint64_t, it should increment the first byte (the one at 0x20017000).

If you try a larger number like 10^17, you should get:

0x01634578_5D8A0000

+------------+------------+
|  Address   | Byte value |
+------------+------------+
| 0x20017000 | 0x00       |
| 0x20017001 | 0x00       |
| 0x20017002 | 0x8a       |
| 0x20017003 | 0x5d       |
| 0x20017004 | 0x78       |
| 0x20017005 | 0x45       |
| 0x20017006 | 0x63       |
| 0x20017007 | 0x01       |
+------------+------------+


If you do a 32-bit read from address 0x20017000, you'll see 0x5D8A0000. This is expected and intentional -- the CPU doesn't know what you're storing in that memory!

Endianness is a property of the CPU. (The SRAM probably doesn't do byte addressing at all -- it reads and writes whole memory words at a time.) Cortex-Ms don't load or store more than 32 bits in once access, so it would be up to the compiler how to order the two halves of the 64-bit value in SRAM. You might have a big-endian CPU (although those are pretty rare), but the compiler could still put the lower 32 bits first in memory. In that case, you might see something like:

0x01634578_5D8A0000

+------------+------------+
|  Address   | Byte value |
+------------+------------+
| 0x20017000 | 0x5d       |
| 0x20017001 | 0x8a       |
| 0x20017002 | 0x00       |
| 0x20017003 | 0x00       |
| 0x20017004 | 0x01       |
| 0x20017005 | 0x63       |
| 0x20017006 | 0x45       |
| 0x20017007 | 0x78       |
+------------+------------+


A fully big-endian 64-bit value (big-endian byte order and word order) would go like this:

0x01634578_5D8A0000

+------------+------------+
|  Address   | Byte value |
+------------+------------+
| 0x20017000 | 0x01       |
| 0x20017001 | 0x63       |
| 0x20017002 | 0x45       |
| 0x20017003 | 0x78       |
| 0x20017004 | 0x5d       |
| 0x20017005 | 0x8a       |
| 0x20017006 | 0x00       |
+------------+------------+


Note that, while the CPU can do byte addressing just fine, JTAG debuggers may handle it poorly. If you're looking at a memory window in an IDE, the IDE is probably doing 32-bit reads and manually splitting up the bytes afterward. Try having the CPU store each byte in a separate 32-bit variable, then look at those variables in the memory window to see what's really going on.

EDIT: Your code is wrong. This:

uint32_t b = (uint32_t)a;


casts a to a uint32_t, which just truncates it to 32 bits. If you want to see what's in memory, you can do:

uint32_t *addr = (uint32_t *)&a;
uint32_t w0 = a[0];  // == *a
uint32_t w1 = a[1];  // == *(a + 1)


This will read 32 bits from the address of a (let's say it's 0x20017000), and 32 bits from the word after that in memory (0x20017004). If you want to read the bytes, the simplest way is:

uint8_t *addr = (uint8_t *)&a; //If this is 0x20017000...


Remember, when you do pointer arithmetic in C, the size of the data type is taken into account. So if a is a uint32_t, then &a + 1 is 32 bits (4 bytes) later in memory. If it's a uint8_t, then &a + 1 is 8 bits (1 byte) later in memory.

As for why your CPU uses byte addresses instead of word addresses... By definition, a byte is the smallest addressable unit of memory. If each memory address held 32 bits, then a byte would be 32 bits on that system. Bytes are 8 bits for historical reasons (meaning software compatibility). To simplify: because that way each byte could hold one character of (English) text.

(There are still some DSPs with 16-bit or 32-bit bytes, but those are special-purpose processors.)

• thanks for the detailed response. so I tried creating a 64 bit variable with a value 0x00008a5d78456301 and read 32 bits into a different variable, and I see it stores 78456301. and in the memory, I see -> imgur.com/a/8QbMMd0. I am sort of confused cause 78456301 should have a higher address than former 32 bits, as you stated.
– MKD
Jun 3, 2020 at 23:08
• That is odd. Can you edit your question to include the code you used and your method for reading the memory contents? Jun 3, 2020 at 23:15
• edited. also, why does each byte have a distinct address as opposed to every 32 bits having one distinct address? to visualize, i'm referring to imgur.com/a/38IP8i7. and endianness is CPU's property so when you want to write to SRAM, the processor takes care of the endianness first and then writes to it?
– MKD
Jun 3, 2020 at 23:29
• @Pokloha considering your test results and the information on this answer (see the illustration after "If you try a larger number like 10^17, you should get:"), I think they are consistent. Could not understand why @ AdamHaun called it "odd" Jun 4, 2020 at 0:17
• @muyustan Ah, I misread Pokloha's original comment and image since he reversed the byte order from my answer. You're right; that is consistent with a normal little-endian CPU. His code is still wrong, though. :-) Jun 4, 2020 at 1:31
uint64_t a = 0x1234567811223344;
uint64_t b;
uint64_t fun0 ( void )
{
b = 0xAABBCCDDEEFF1234;
return(0x1001200230034004);
}

Disassembly of section .text:

00000000 <fun0>:
0:   e92d0030    push    {r4, r5}
4:   e59f302c    ldr r3, [pc, #44]   ; 38 <fun0+0x38>
8:   e28f5018    add r5, pc, #24
c:   e8950030    ldm r5, {r4, r5}
10:   e28f1018    add r1, pc, #24
14:   e8910003    ldm r1, {r0, r1}
18:   e8830030    stm r3, {r4, r5}
1c:   e8bd0030    pop {r4, r5}
20:   e12fff1e    bx  lr
24:   e1a00000    nop         ; (mov r0, r0)
28:   eeff1234    mrc 2, 7, r1, cr15, cr4, {1}
2c:   aabbccdd    bge feef33a8 <fun0+0xfeef33a8>
30:   30034004    andcc   r4, r3, r4
34:   10012002    andne   r2, r1, r2
38:   00000000    andeq   r0, r0, r0

Disassembly of section .data:

00000000 <a>:
0:   11223344            ; <UNDEFINED> instruction: 0x11223344
4:   12345678    eorsne  r5, r4, #120, 12    ; 0x7800000


Technically a number of the cortex-m cores can be compiled little endian and/or big or selected runtime. But I have not see a big endian one yet, and it would be unwise anyway. ARMs native mode is little endian. Tools default to little endian. Not sure why it is even a question.

The sram itself doesnt matter, the sram can be configured however the chip vendor wants so long as they get and put the data on the bus correctly.

uint64_t is part of C so whether it is a PIC or AVR or something cortex-m based you still get 64 bits whatever size the memory is, whatever its endianness. Variables are high level language notions and have nothing to do with the underlying architecture that implements them so long as it functionally implements the high level code correctly.

Smells like you are going to abuse the language if asking this question.

little endian.

The language supports 64 bit variables so the compiled code will.

Your test is endian-less it doesnt demonstrate endian-ness, but it does show the correct answer.

• possible typing error in your first line of text after the code snippet? Jun 4, 2020 at 2:31

It is a C feature reagrdless the endianess. Then you store the larger data type info the smaller one it gets truncated.

in out case dest32 = src64 & 0xffffffff

A side-note about integer conversions in C, is that they work on a higher abstraction level than the raw binary representation. The rule is (C11 6.3.1.3):

Otherwise, if the new type is unsigned, the value is converted by repeatedly adding or subtracting one more than the maximum value that can be represented in the new type until the value is in the range of the new type.

In this case the conversion should be done "as if subtracting UINT32_MAX+1 from the value until it is in range" - in practice this is truncating the value to 32 bits.

It doesn't matter whether you explicitly force a conversion through a cast: uint32_t b = (uint32_t) a; or just do uint32_t b = a; and get implicit conversion through the rule of simple assignment. Either is endianess-independent and the above conversion rule applies in both cases.

Had you done something wild and crazy like uint32_t b = *(uint32_t*)&a; however, this would be endianess-dependent (and highly questionable code for several other reasons).

4294967299(dec) = 1 0000 0003‬ (hex)

Now 64 bits are 8 bytes (8 x 8 bits = 64). Therefore the number 4294967299(dec) is divided into 8 bytes 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00.

So the last byte can't be the 4-th byte.

• so you're basically saying to make 64 bit register, two 32-bit registers are sort of used to store 32 bits in each. Not sure what you meant by the last line...
– MKD
Jun 3, 2020 at 22:35
• @Pokloha Yes ,8 bytes are used, You said: "and the last 0x3 is at 0x20017FEA" so, the 4-th (0x03) is not the last. Jun 3, 2020 at 22:40
• that's what I referred to: imgur.com/a/TvYjhPV. the last byte of the first 32-bit register
– MKD
Jun 3, 2020 at 22:43
• Then it should be 0x07 byte,not the 0x03 byte, regardless of the register length. Jun 3, 2020 at 22:53
• explanation is wrong Jun 7, 2020 at 22:54