首先要复习STM32单片机中的内存五区。

1 内存五区

在嵌入式系统中,五种内存区域分别是:堆(Heap)、栈(Stack)、全局区/静态区、常量区和代码区。

alt text

  • RO (Read-Only):这部分内存用于存储只读数据,即程序代码(比如应用程序的指令)和常量数据(比如常量数组、字符串等)。这些数据在程序运行时不能被修改。

  • RW (Read-Write):这部分内存用于存储可读可写的数据。在程序运行过程中,数据可能会被修改。

  • ZI (Zero Initialized):这部分内存用于存储未初始化的数据,在程序启动时,这些数据会被自动初始化为零。

1.1 栈(Stack)

alt text

1.2 堆(Heap)

alt text

#include <stdio.h>
#include <stdlib.h>
 
//堆内存通常用于自主动态分配内存
void HeapExample() 
{
    int *array;// 定义一个指向整数的指针,用于存储动态分配的数组
    int size = 10; // 需要分配的数组大小
 
    // 从堆中分配内存,分配的总字节数为40字节,array得到的是一个指向分配内存的指针
    array = (int *)malloc(size * sizeof(int));
    //检查malloc是否成功分配内存。如果分配失败,malloc会返回NULL。
    if (array == NULL) 
    {
        printf("Memory allocation failed!\n");
        return;
    }
 
    // 使用分配的内存
    for (int i = 0; i < size; i++) 
    {
        array[i] = i * i; // 示例:存储平方值
    }
 
    // 打印分配的内存内容
    for (int i = 0; i < size; i++) 
    {
        printf("array[%d] = %d\n", i, array[i]);
    }
 
    // 释放分配的内存
    free(array);
    printf("Memory freed.\n");
}

1.3 全局区/静态区

全局区/静态区用于存储全局变量和静态变量。它分为两个部分:

  • .bss段:用于存储未初始化或初始化为0的全局变量和静态变量。它不占用可执行文件的空间。

  • .data段:用于存储经过初始化且初始化值非0的全局变量和静态变量。

#include <stdio.h>
 
// 全局变量
int globalVar = 42;
 
void StaticExample() 
{
    // 静态变量
    static int staticVar = 0;
 
    // 每次调用函数时,静态变量的值会保留,不会像局部变量一样消失
    staticVar++;
    printf("Global Variable: %d\n", globalVar);
    printf("Static Variable: %d\n", staticVar);
}

1.4 常量区

常量区用于存储字符串常量和其他不可修改的数据。这些数据在程序运行期间是只读的。

const char *message = "Hello, World!";

1.5 代码区

alt text

2 关于堆栈内存的设置

  1. 启动文件设置的堆栈是上面讲到内存五区的堆栈内存大小设置,跟FreeRTOS堆栈内存没有关系。

  2. FreeRTOS的申请堆内存和任务栈内存,都是占用以下宏定义内存区域。

     #define configTOTAL_HEAP_SIZE ((size_t)3072)
    

3 STM32 Stack_Size和Heap_Size大小设置的意义

在使用STM32编程时,一般情况下我们不会关注堆栈空间的大小,因为在STM32的启动文件中,已经帮我们预先设置好了堆栈空间的大小。如下图所示的启动代码中,Stack栈的大小为:0x400(1024Byte),Heap堆的大小为:0x200(512Byte)。

1、若工程中使用的局部变量较多,定义的数据长度较大时,若不调整栈的空间大小,则会导致程序出现栈溢出,程序运行结果与预期的不符或程序跑飞。这时我们就需要手动的调整栈的大小。

2、当工程中使用了malloc动态分配内存空间时,这时分配的空间就为堆的空间。所以若默认的堆空间大小不满足工程需求时,就需要手动调整堆空间的大小。

裸机程序里面这两个值 在程序中我要怎么计算才能知道分配多少合适?

  • Stack Size:一般小工程0X400足够,我们综合实验才设置0X1000就够用,所以默认无需设置太大。

  • Heap Size:如果没有用到标准库的malloc,就是废物,纯属浪费内存,所以直接设置为0即可。

在嵌入式使用UCOSII和LUA的时候堆栈也很重

  • Stack Size,0X2000足够,我们综合实验才设置0X1000就够用,所以默认无需设置太大。

  • Heap Size,如果没有用到标准库的malloc,就是废物,纯属浪费内存,所以直接设置为0即可。

4 使用freertos如何确定分配堆栈空间大小

运行freertos系统的大部分都是资源有限的MCU,所以对于ram我们都要考虑尽量的节省,避免资源浪费,从而也可以针对项目选择性价比更好的mcu。

首先要配置freertos的堆(heap)空间,创建任务我们还需要为每个任务分配栈(stack)空间,那么针对freertos的堆栈空间到底该如何确定?

freertos从V9版本以后同时支持静态内存和动态内存分配方式。静态内存分配在编译时候就会对freertos的内核对象分配ram空间。动态分配都是在程序运行起来以后从堆空间上分配的。这里我们也只讨论动态内存分配,动态内存分配的好处是可以在删除对象的时候释放掉内存的空间。从而保证ram的可持续利用!

先看下图弄清楚freertos的heap空间和任务栈空间的不同与联系。

alt text

FreeRTOS创建任务时默认的任务栈大小为128字,在32位系统中即为128*4=512Byte,再加上TCB块占用84Byte,一共596Byte。而大小为3072Byte的堆允许创建3个这样的任务,占用约1800Byte。堆中剩余的部分则存放了系统内核、信号量、队列、任务通知等数据。

参考

参考1:STM32内存五区及堆栈空间大小设置(启动文件浅析)

参考2:STM32内存结构介绍,FreeRTOS内存分配技巧,Stack_Size和Heap_Size大小设置