Understanding Data Types: Memory Representation and Type Conversion

Little-Endian and Big-Endian Memory Storage

Each memory address stores 8 bits, so data must be accessed in 8-bit increments.

Data Types

Type Casting Does Not Change Variable Types or Values

Consider this example from an official ST function:

FLASH_Status FLASH_ProgramWord(uint32_t Address, uint32_t Data)

Although Flash memory can only write 16 bits at a time, it can handle 32-bit data through the following approach:

*(__IO uint16_t*)Address = (uint16_t)Data;  // Store lower 16 bits at Address
uint32_t tmp = Address + 2;  // Move to next address for upper 16 bits
*(__IO uint16_t*)tmp = Data >> 16;  // Store upper 16 bits

The expression (uint16_t)Data places the value in a 16-bit temporary variable, effectively containing only the lower 16 bits. This temporary variable then stores these lower 16 bits at the specified address. Note that STM32 follows little-endian mode for memory writes, so lower bytes are stored first. When casting from a larger to a smaller data type, the excess higher bits are discarded. However, the original Data variable remains unchanged as a 32-bit value.

Similarly, Data >> 16 shifts the value right by 16 bits, effectively keeping only the upper 16 bits. When stored in memory at the temporary address, this completes the 32-bit storage process.

Let's demonstrate with a practical example:

int main()
{
    OLED_Init();
    uint32_t number = 0x00010001;
    uint16_t lower_half = (uint16_t)number;
    uint32_t temp = number;
    uint8_t byte1, byte2, byte3, byte4;
    
    byte1 = (0xff000000 & temp) >> 24;
    byte2 = (0x00ff0000 & temp) >> 16;
    byte3 = (0x0000ff00 & temp) >> 8;
    byte4 = (0x000000ff & temp);

    OLED_Printf(0, 0, OLED_6X8, "temp %d", temp);
    OLED_Printf(0, 8, OLED_6X8, "byte1 %d", byte1);
    OLED_Printf(0, 16, OLED_6X8, "byte2 %d", byte2);
    OLED_Printf(0, 24, OLED_6X8, "byte3 %d", byte3);
    OLED_Printf(0, 32, OLED_6X8, "byte4 %d", byte4);
    OLED_Update();
    return 0;
}

Type Casting Does Not Change Pointer Types or Pointed Value Types

int main()
{
    OLED_Init();
    uint32_t number = 0x00010001;
    uint32_t* pNumber = &number;
    uint16_t* pValue = (uint16_t*)pNumber;
    uint8_t byte1, byte2, byte3, byte4;
    
    byte1 = (0xff000000 & (*pNumber)) >> 24;
    byte2 = (0x00ff0000 & (*pNumber)) >> 16;
    byte3 = (0x0000ff00 & (*pNumber)) >> 8;
    byte4 = (0x000000ff & (*pNumber));

    OLED_Printf(0, 0, OLED_6X8, "temp %d", (*pNumber));
    OLED_Printf(0, 8, OLED_6X8, "byte1 %d", byte1);
    OLED_Printf(0, 16, OLED_6X8, "byte2 %d", byte2);
    OLED_Printf(0, 24, OLED_6X8, "byte3 %d", byte3);
    OLED_Printf(0, 32, OLED_6X8, "byte4 %d", byte4);
    OLED_Update();
    return 0;
}

Let's verify this with C++:

int main()
{
    int a = 10;
    int* pA = &a;
    float* temp = (float*)pA;
    cout << typeid(a).name() << endl;
    cout << typeid(pA).name() << endl;
    return 0;
}

Extracting Individual Bytes from Integers

We can define four uint8_t variables and extract each 8-bit segment from an integer:

int main()
{
    OLED_Init();
    int number = 261;
    uint8_t byte1, byte2, byte3, byte4;
    
    byte1 = (0xff000000 & number) >> 24;
    byte2 = (0x00ff0000 & number) >> 16;
    byte3 = (0x0000ff00 & number) >> 8;
    byte4 = (0x000000ff & number);

    OLED_Printf(0, 0, OLED_6X8, "number %d", number);
    OLED_Printf(0, 8, OLED_6X8, "byte1 %d", byte1);
    OLED_Printf(0, 16, OLED_6X8, "byte2 %d", byte2);
    OLED_Printf(0, 24, OLED_6X8, "byte3 %d", byte3);
    OLED_Printf(0, 32, OLED_6X8, "byte4 %d", byte4);
    OLED_Update();
    return 0;
}

Storing Data in Memory

*((__IO uint16_t*)Address);  // For STM32, addresses are 32-bit, but this creates a 16-bit pointer to the address

Extracting Bytes from Floating-Point Numbers

Single-Precision Floating-Point Numbers

Single-precision floats are stored in memory according to IEEE754 standards. For example, 19.625 is represented as 01000001100111010000000000000000.

Floating-point numbers cannot participate in bitwise operations, so we need special handling using unions. A union allows a uint32_t variable and a float to share the same memory space. The uint32_t will then have the same binary representation as the float.

union float_test
{
    float value;
    uint32_t num;
} test;

int main()
{
    test.value = 19.625;
    OLED_Init();
    uint8_t byte1, byte2, byte3, byte4;
    
    byte1 = (0xff000000 & (test.num)) >> 24;  // 65 decimal, 41 hex
    byte2 = (0x00ff0000 & (test.num)) >> 16;  // 157 decimal, 9D hex
    byte3 = (0x0000ff00 & (test.num)) >> 8;   // 0
    byte4 = (0x000000ff & (test.num));
    
    OLED_Printf(0, 0, OLED_6X8, "number %f", test.value);
    OLED_Printf(0, 8, OLED_6X8, "byte1 %d", byte1);
    OLED_Printf(0, 16, OLED_6X8, "byte2 %d", byte2);
    OLED_Printf(0, 24, OLED_6X8, "byte3 %d", byte3);
    OLED_Printf(0, 32, OLED_6X8, "byte4 %d", byte4);
    OLED_Update();
    return 0;
}

Project Problem 1: Writing to Flash Memory

How do we write floating-point and integer values to Flash memory? We can use FLASH_Status FLASH_ProgramWord(uint32_t Address, uint32_t Data):

float value = 19.625;
MyFLASH_ErasePage(0x0800FC00);
MyFLASH_ProgramWord(0x0800FC00, value);  // Core function is FLASH_Status FLASH_ProgramWord(uint32_t Address, uint32_t Data)
OLED_Printf(35, 17, OLED_8X16, "%05.3f", MyFLASH_ReadFloat(0x0800FC00));
OLED_Update();

The issue is that the data type of the variable determines how it's stored in Flash. When passing a float to a uint32_t parameter, it gets stored as a uint32_t rather than a float.

We can solve this using a union:

union float_test
{
    float value;
    uint32_t binary;
} test;

int main(void)
{
    MyI2C_Init();
    OLED_Init();

    test.value = 19.625;
    MyFLASH_ErasePage(0x0800FC00);
    MyFLASH_ProgramWord(0x0800FC00, test.binary);
    OLED_Printf(35, 17, OLED_8X16, "%05.3f", MyFLASH_ReadFloat(0x0800FC00));
    OLED_Update();
}

The principle is that value and binary share the same memory. When value stores 19.625 as a float, the binary representation remains unchanged when accessed as a uint32_t.

Alternatively, using pointers (a better approach):

int main()
{
    float data = 19.625;
    uint8_t* pData = (void*)&data;
    OLED_Init();
    OLED_Clear();
    OLED_Printf(0, 0, OLED_8X16, "%d", *pData);
    OLED_Printf(0, 16, OLED_8X16, "%d", *(pData + 1));  // Also pData[1]
    OLED_Printf(0, 32, OLED_8X16, "%d", *(pData + 2));
    OLED_Printf(0, 48, OLED_8X16, "%d", *(pData + 3));    
    OLED_Update();
    return 0;
}

Reading from Flash Memory

Reading data from Flash depends on how it's interpreted and printed:

uint32_t MyFLASH_ReadWord(uint32_t Address)
{
    return *((__IO uint32_t*)Address);  // Retrieve data as uint32_t
}

float MyFLASH_ReadFloat(uint32_t Address)
{
    return *((__IO float*)Address);  // Retrieve data as float
}

OLED_Printf(35, 17, OLED_8X16, "%u", MyFLASH_ReadWord(0x0800FC00));  // Print as uint32_t

OLED_Printf(35, 17, OLED_8X16, "%06.3f", MyFLASH_ReadFloat(0x0800FC00));  // Print as float

In summary: when reading, printing, or writing to memory (including arrays), always be mindful of the data storage format. These methods also apply to EEPROM.

Project Problem 2: Working with EEPROM Arrays

When working with EEPROM, we can define a buffer array and write multiple values at once. How do we write float or int arrays to EEPROM, which stores data one byte at a time?

Here's a simplified approach:

void process_array(uint8_t* pArray) {
    OLED_Printf(0, 0, OLED_6X8, "pArray[0] %d", pArray[0]);
    OLED_Printf(0, 8, OLED_6X8, "pArray[1] %d", pArray[1]);
    OLED_Printf(0, 16, OLED_6X8, "pArray[2] %d", pArray[2]);
    OLED_Printf(0, 24, OLED_6X8, "pArray[3] %d", pArray[3]);
    OLED_Printf(0, 32, OLED_6X8, "pArray[4] %d", pArray[4]);
    OLED_Printf(0, 40, OLED_6X8, "pArray[5] %d", pArray[5]);
    OLED_Printf(0, 48, OLED_6X8, "pArray[6] %d", pArray[6]);
    OLED_Printf(0, 56, OLED_6X8, "pArray[7] %d", pArray[7]);
}

int main()
{
    OLED_Init();
    float arr[2] = {19.625, 65.875};
    process_array((void*)arr);
    OLED_Update();
    return 0;
}

Note that writing arrays to memory follows little-endian storage. To read back the data:

int main()
{
    OLED_Init();
    uint8_t data_array[8] = {0, 0, 157, 65, 0, 192, 131, 66};
    float* pArray = (void*)data_array;
    OLED_Clear();
    OLED_Printf(0, 0, OLED_6X8, "pArray[0] %05.3f", pArray[0]);
    OLED_Printf(0, 8, OLED_6X8, "pArray[1] %05.3f", pArray[1]);
    OLED_Update();
}

The key principle is that pointer interpretation depends only on the pointer type, not the original data type. This applies equally to integers and floating-point numbers.

Tags: Data Types memory representation Type Conversion endian Flash Memory

Posted on Wed, 01 Jul 2026 16:27:01 +0000 by toasty2