[Code Quality] Software design techniques to save memory in embedded programming

Let's talk first

  Everyone knows that the biggest difference between microcontroller programming and computer programming is that the resources of the microcontroller are very limited, and there is no operating system for most low-end microcontrollers. Except for some embedded-level chips that use the Linux system, most of the other operations are relatively simple RTOS, and there may be some simple applications or the chips do not use the system at all, and are directly bare-metal programs.

  However, most of the microcontroller programming is closely integrated with the hardware, so that engineers can have more control and understanding of the current project objects. However, due to its simplicity, we often need to control the cost of a project in our work, and we are very cautious in the selection of single-chip microcomputers and the evaluation of resources; also as our project functions continue to expand, the system program will gradually change. To be huge, the use of resources at this time needs to be more economical.

  So when the resources are limited (the RAM of the general single-chip microcomputer is also at the Kb level), for example, the RAM of the single-chip microcomputer is not enough, even if you have the most powerful algorithm, you may not be able to join the project, then some comrades will ask, why not change the chip Is it all right? I just want to say that this comrade, you think too much. For companies that do not sell very well or are not standardized, you may be allowed to try it. However, the general company projects are stuck, and the main control chip is changed, leaving aside the software. The cost of the transplantation work on the Internet will definitely increase when the chip is replaced, and the test of the product must be re-planned. The boss and the leader are not willing.

  So what else can we do if the main control chip cannot be replaced? Then we should squeeze out resources from the original program to use. Below I have summarized several common methods for your reference. (The specific content can be found online)

Commonwealth-union

 Union-Combination is a commonly used keyword in C language. Literally, it means to join together. All members of the union jointly maintain a memory space. The size of the memory depends on the member that takes up the largest space among all members.

 The union structure can greatly save memory space because it shares the same piece of memory. Under what circumstances do you use union? Or what are the characteristics of union? Below I will use a few points for your answers.

 1) All union members and their addresses are the same.

 2) Union's storage model is affected by the big and small end, we can test it with the following code. (If the output result is 1, it means little endian mode, otherwise it is big endian mode)

Little knowledge

Big endian mode (Big_endian): The high byte of a data is stored at the low address, and the low byte is stored at the high address. The first address pointed to by the pointer is at the lower address.

Little_endian: The high byte of a data is stored at the high address, and the low byte is stored at the low address. The first address pointed to by the pointer is at the high address.

 3) Union is different from the structure struct, the changes of union to the members may affect other member variables, so we have to form a mutually exclusive use, for example, our sequential execution is that each code is mutually exclusive, so We can use union for function processing and caching. (Personally, I think it can be considered as time-sharing multiplexing, and it will not be affected by the initial value of the memory.)

#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 field

 Bit field may be less used for beginners, but it should be commonplace for most of the engineers participating in the work. Indeed, it is also an artifact of our memory saving.

 Because in our usual programming process, the variables we use are closely related to the actual situation. For example, the state of the switch. We generally use 0 or 1 to indicate open and close respectively, then we can use a bit to indicate that, if we say We use a char to store almost 7 bits. If there are similar situations in the future, most of the memory will not be effectively used. So the bit field of C language is used to solve this problem.

 But we need to pay attention to the following points:

 1) The bit field is implemented in the structure, where the length specified by the bit field cannot exceed the defined type, and a bit field can only be defined in the same storage unit.

 2) For the use of unnamed bit fields, you can see the code below.

 3) Since the bit field is related to the data type, its memory usage is also related to the number of bits of the platform. (Related content can be found online)

#include<stdio.h>
//结果:编译通过
//原因:常规形式(结构体占用两个字节)
typedef struct _tag_test1
{
  char a:1;
  char b:1;
  char c:1;
  char d:6;
}sTest1;
//结果:编译无法通过
//原因:d的位域长度10超过了char类型长度
/*
typedef struct _tag_test2
{
  char a:1;
  char b:1;
  char c:1;
  char d:10;
}sTest2;
*/
//结果:编译可通过
//原因:下面使用无名位域,且占8个字节
typedef struct _tag_test3
{
  int a:1;
  int b:1;
  int :0;//无名位域
  int c:1;
}sTest3;
int main(void)
{
    printf("%d\n",sizeof(sTest1));
    printf("%d\n",sizeof(sTest3));
    printf("欢迎关注公众号:最后一个bug\n");
 }

Structure alignment

 Most people may not pay much attention to the structure alignment problem, and they may be exposed to more when copying memory in the communication field. The structure alignment problem is also related to the platform. In order to improve the efficiency of accessing the memory, the CPU may read 2 bytes, 4 bytes, 8 bytes, etc., so the compiler will automatically align the structure memory.

 Not much nonsense, the code explains everything:

#include<stdio.h>
#pragma pack(1)
//有字节对齐预编译结果为:12,8
//无字节对齐预编译结果为: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("欢迎关注公众号:最后一个bug\n");
 }

Algorithm optimization

 Algorithm optimization is actually a balance between efficiency and memory usage by modifying some algorithms. We all know that our algorithms have complexity problems. Most of our high-efficiency algorithms are exchanged by using memory. Efficiency is a concept of using space for time. So when our memory usage is limited, we can appropriately use time to exchange space, freeing up more space to achieve more functions.

 Similarly, we can use local variables as much as possible to reduce the use of global variables when carrying out related designs!

Add a few ways to save memory

1

Use of const

    Take the stm32 microcontroller as an example to see how the 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;                     //设备名称
11    uint32_t Param[DEV_PARAM_MAX];  //设备参数
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 * Fuction:const内存分配测试
22 * Author :bug菌                                
23 **************************************/
24 int main(void)
25 {
26    uint8_t t = 0;
27    uint8_t devCnt = 0;
28
29    delay_init();            //延时函数初始化    
30    uart_init(115200);   //串口初始化
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***********欢迎关注公众号:最后一个bug************\n");
43    while(1)
44    {
45        delay_ms(10); 
46        if(++t > 150){LED0=0;}else{LED0=1;}
47    }    
48 }

operation result:

Analyze:

  • All storage images of stm32 are in the .map file compiled by the corresponding project. The familiarity with the .map file (its file is in the project directory) shows to a certain extent your proficiency with the stm32 microcontroller.

  • After the program is successfully compiled, you can directly search for the const-modified array name in the map file to get the following result:

  • From the above figure, we know that its stDevParam variable is located in the 0x080016b8 data area and is located in (.contdata section-read-only data section) and occupies 36 bytes, which is consistent with our serial port output result.

2

const data storage

    The above test program shows the storage location of const data, then let's take a look at which storage area of ​​stm32 the location is located, is it RAM or FLASH?

    Because we save memory mainly by occupying smaller RAM to achieve the same project requirements, then the best for MCU is to use Flash to replace space through time, and take out the corresponding data manual to see how these storage ranges are distributed.

The above picture is from the ST manual Memory Mapping

    Obviously, the location of the const stDevParam variable tested in the previous section is 0x080016b8, which is exactly the FLASH storage location, so it does not occupy RAM resources.

3

const data usage

    Many small partners who write MCU programs like to save some read-only variables as global variables, but these variables basically only save some parameters, which is very wasteful to the RAM resources of the MCU.

    Bug bacteria once took over a former colleague’s project, how do you say? Maybe he also took over this project. The MCU of this project also extended a 16M SDRAM externally. Everyone thinks that RAM is large and the variables are arbitrary and don’t care. The data range, float or double at every turn , is really good.

    Until the bug bacteria took over the memory occupancy rate was as high as 95%, and some requirements were added later, and the RAM was about to burst. If there is no way, it will eventually cause problems, so I applied for code refactoring, and optimized the code structure, design and other RAM occupancy. The rate dropped directly to about 50%. You can imagine how capricious the previous developers were.

    Therefore, it is well said that "the predecessors planted trees, and the descendants enjoy the shade; the predecessors dig pits, and the descendants enter the fen". Earlier we analyzed that the const data of stm32 is located on Flash. Generally, Flash will be several times larger than RAM: (As shown in the figure below:)

The above picture is from the ST official website

    In this way, some pre-set parameters and so on can be sorted and placed in a structure array similar to the previous demo, which can greatly reduce the RAM usage.

    One thing to note is that access to RAM is generally faster than access to Flash. However, most projects have very little impact on such differences.

Guess you like

Origin blog.csdn.net/u012846795/article/details/108633844