Memory-Saving Software Design Techniques in Embedded Programming

Memory-Saving Software Design Techniques in Embedded Programming

ID: The Last Bug

Author: Unknown Bug

First, let’s talk about

Everyone knows that there is a significant difference between microcontroller programming and computer programming: the resources of microcontrollers are very limited, and most low-end microcontrollers do not have an operating system. Except for some embedded-level chips that use Linux, most operations are done with relatively simple RTOS, and some applications or chips do not use any system at all, running directly as bare-metal programs.

However, most microcontroller programming is closely integrated with hardware, allowing engineers to have more control and understanding of the current project. Due to its simplicity, we often need to control the project costs, and we are very cautious about the selection of microcontrollers and resource evaluation; similarly, as we continuously expand project functions, the system program gradually becomes larger, making resource usage even more critical.

Therefore, when resources are limited (generally, microcontroller RAM is only in the Kb range), for example, if the microcontroller RAM is insufficient, even if you have a great algorithm, it may not be able to be included in the project. Some may ask, can’t we just change the chip? I can only say that you are overthinking it. For products that are not very popular or from non-standard companies, it might be allowed, but for most companies, projects are tightly controlled. Changing the main control chip not only involves software porting work but also inevitably increases costs, and product testing must be replanned, which management is unlikely to approve.

So, if we cannot change the main control chip, what can we do? We should extract resources from the original program. Below, I summarize several common methods for your reference. (Specific content can be found online)

Union

Union is a commonly used keyword in C language. Literally, it means to be jointly combined together. All members of a union share a segment of memory space, and the size of that memory depends on the largest member.

Since union structures share the same memory, they can greatly save memory space. So when should we use union? What are the characteristics of union? Below, I will answer these questions.

1) All members of a union and the union itself have the same address.

2) The storage model of union is influenced by endianness, which we can test with the following code. (If the output is 1, it indicates little-endian mode; otherwise, it is big-endian mode)

Endianness Knowledge

Big-endian: The high byte of data is stored at a low address, and the low byte is stored at a high address. The pointer points to the starting address at the low address.

Little-endian: The high byte of data is stored at a high address, and the low byte is stored at a low address. The pointer points to the starting address at the high address.

3) Unlike structures, changes to members of a union may affect other member variables, so we need to form a mutual exclusion usage. For example, our sequential execution means that each piece of code is mutually exclusive, so we can use union for function processing, caching, etc. (I personally think it can also be considered as time-sharing reuse, and it is a processing method that is not affected by memory initialization values)

#include<stdio.h>

typedef union _tag_test
{
  char a;
  int  b;
}uTest;


uTest test;
unsigned char Checktype(void);


int main(void)
{
    printf("%x\n",(unsigned int)&test.a);
    printf("%x\n",(unsigned int)&test.b);
    printf("%x\n",(unsigned int)&test);
    printf("%d\n",Checktype());
}
 unsigned char Checktype(void)
{
     uTest chk;
     chk.b = 0x01;
     if(chk.a == 0x01)return 1;
     return 0;
}

Bit Fields

Bit fields may be used less frequently by beginners, but for most engineers in the workforce, they are quite common and indeed a memory-saving tool.

In our programming process, the variables we use are closely related to actual situations. For example, the state of a switch is generally represented by 0 or 1 for off and on, respectively. If we use a bit to represent this, using a char would waste 7 bits. If similar situations arise in the future, most memory would not be effectively utilized. Thus, C language bit fields are designed to solve this problem.

However, we need to pay attention to the following points:

1) Bit fields are implemented within structures, and the length specified for a bit field cannot exceed the defined type, and a bit field can only be defined within the same storage unit.

2) The use of unnamed bit fields can be seen in the code below.

3) Since bit fields are related to data types, their memory usage is also platform-dependent. (Related content can be found online)

#include<stdio.h>
//Result: Compilation successful
//Reason: Conventional form (structure occupies two bytes)
typedef struct _tag_test1
{
  char a:1;
  char b:1;
  char c:1;
  char d:6;
}sTest1;
//Result: Compilation fails
//Reason: d's bit field length of 10 exceeds char type length
/*
typedef struct _tag_test2
{
  char a:1;
  char b:1;
  char c:1;
  char d:10;
}sTest2;
*/
//Result: Compilation successful
//Reason: Below uses unnamed bit field, occupying 8 bytes
typedef struct _tag_test3
{
  int a:1;
  int b:1;
  int :0;//Unnamed bit field
  int c:1;
}sTest3;
int main(void)
{
    printf("%d\n",sizeof(sTest1));
    printf("%d\n",sizeof(sTest3));
    printf("Welcome to follow the public account: The Last Bug\n");
}

Structure Alignment

The issue of structure alignment may not be a major concern for most people, but it is often encountered in memory copying in communication fields. The structure alignment issue is also platform-dependent, as CPUs may read memory in chunks of 2, 4, or 8 bytes to improve access efficiency, so compilers will automatically align the memory of structures.

Without further ado, the code explains everything:

#include<stdio.h>
#pragma pack(1)
//With byte alignment, precompiled results: 12, 8
//Without byte alignment, precompiled results: 6, 6
typedef struct _tag_test1{
    char a;
    int  b;
    char c;
   
}STest1;

typedef struct _tag_test2{
    int  b;
    char a;
    char c;
   
}STest2;

int main(void)
{
    printf("%d\n",sizeof(STest1));
    printf("%d\n",sizeof(STest2));
    printf("Welcome to follow the public account: The Last Bug\n");
}

Algorithm Optimization

Algorithm optimization mainly involves modifying some algorithms to achieve a balance between efficiency and memory usage. We all know that our algorithms have complexity issues, and most high-efficiency algorithms trade memory for efficiency, which is a concept of space for time. When our memory usage is limited, we can appropriately use time to exchange for space, freeing up more space to implement more functions.

Similarly, when designing related systems, we can try to use local variables to reduce the use of global variables!

Additional Memory-Saving Methods

1

Use of const Let’s take the STM32 microcontroller as an example to see how const variables are stored.

Reference demo:

 1#include "led.h"
 2#include "delay.h"
 3#include "usart.h"
 4
 5#define DEV_NUM_MAX   (3)
 6#define DEV_PARAM_MAX (2)
 7
 8typedef struct _tag_DevParam
 9{
10    char* Name;                     //Device name
11    uint32_t Param[DEV_PARAM_MAX];  //Device parameters
12}sDevParam;
13
14
15 const sDevParam stDevParam[DEV_NUM_MAX] = {
16                                {"Uart1",57600,0},
17                                {"Uart2",57600,1},
18                                {"CAN",1000000,0},
19                                };
20/***************************************
21 * Function:const memory allocation test
22 * Author :bug菌                                
23 **************************************/
24 int main(void)
25 {
26    uint8_t t = 0;
27    uint8_t devCnt = 0;
28
29    delay_init();            //Delay function initialization    
30    uart_init(115200);   //UART initialization
31
32    printf("\n*******************const Test*******************\r\n");
33
34    for(devCnt = 0 ;devCnt < DEV_NUM_MAX;devCnt++)
35    {
36        printf("DevName = %s,Param1 = %d,Param2 = %d\r\n",stDevParam[devCnt].Name,\
37                                                      stDevParam[devCnt].Param[0],\
38                                                      stDevParam[devCnt].Param[1]);
39    }
40    printf("stDevParam Size : %d \r\n",sizeof(stDevParam));
41    printf("stDevParam Addr : 0x%X \r\n",stDevParam);
42    printf("\n***********Welcome to follow the public account: The Last Bug************\n");
43    while(1)
44    {
45        delay_ms(10); 
46        if(++t > 150){LED0=0;}else{LED0=1;}
47    }    
48 }

Running Result:

Memory-Saving Software Design Techniques in Embedded Programming

Analysis:

  • For all storage images of STM32, they are in the corresponding project’s compiled .map file. Familiarity with the .map file (which is in the project directory) reflects your proficiency with STM32 microcontrollers.

  • After successful compilation, you can directly find the array name modified by const in the map file, resulting in the following:

Memory-Saving Software Design Techniques in Embedded Programming

  • From the above image, we can see that the stDevParam variable is located at 0x080016b8 in the data area and is in the (.contdata section–read-only data section) and occupies 36 bytes, which matches our UART output result.

2

Storage of const data The above test program shows the storage location of const data. Now let’s see which storage area this location belongs to in STM32, whether it is RAM or FLASH? Since we mainly save memory by occupying less RAM to meet the same project requirements, it is best for MCUs to leverage Flash, exchanging time for space. Let’s check the data manual to see how these storage ranges are allocated.

Memory-Saving Software Design Techniques in Embedded Programming

The above image is sourced from the ST manual Memory Mapping It is clear that the previously tested const stDevParam variable at 0x080016b8 is located in FLASH storage, so it does not occupy RAM resources.

3

Usage of const data Many developers writing microcontroller programs like to save some read-only variables as global variables, but these variables usually only store some parameters, which is a significant waste of RAM resources for microcontrollers. Bug Jun once took over a project from a former colleague. How should I put it? Perhaps this project was also taken over from someone else, and the MCU had an external 16M SDRAM expansion. Everyone thought that since RAM was large, variables could be defined freely without considering data ranges, often using float and double, which was really impressive. Until Bug Jun took over, the memory usage rate had reached 95%, and adding even slight requirements felt like RAM was about to explode. There was no way to continue like this, so I applied for code refactoring, and through optimizing code structure and design, the RAM usage rate was directly reduced to about 50%. You can imagine how reckless the previous developers were. So, as the saying goes, “Those who plant trees benefit later; those who dig pits cause others to fall in.” We previously analyzed that the STM32 const data is located in Flash, and generally, Flash is several times larger than RAM: (as shown in the following image:)

Memory-Saving Software Design Techniques in Embedded Programming

The above image is sourced from the ST official website

Thus, for some pre-set parameters, they can be organized and uniformly placed in a structure array like the previous demo, significantly reducing RAM usage. One point to note is: Accessing RAM is generally faster than accessing Flash; however, for most projects, this difference has minimal impact.Memory-Saving Software Design Techniques in Embedded ProgrammingRecommended Reading:

Embedded Programming Series

Linux Learning Series

C/C++ Programming Series
Follow the WeChat public account "Technology Makes Dreams Greater", reply "m" for more content, reply "join group" to join the technical exchange group.

Long press to go to the public account in the image to follow

Leave a Comment