Electrical – What happen when you store something greater than 32 bits on STM32

embeddedstm32

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.

Best Answer

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...
uint32_t b0 = addr[0]; //address 0x20017000
uint32_t b1 = addr[1]; //address 0x20017001
uint32_t b2 = addr[2]; //address 0x20017002
uint32_t b3 = addr[3]; //address 0x20017003
uint32_t b0 = addr[4]; //address 0x20017004
uint32_t b1 = addr[5]; //address 0x20017005
uint32_t b2 = addr[6]; //address 0x20017006
uint32_t b3 = addr[7]; //address 0x20017007

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.)