【stm32+Freertos】RAM爆满了如何优化内存,发现到解决一文搞定
define configTOTAL_HEAP_SIZE ((size_t)10240)就会报错。所以 Section 就是“这一堆变量在内存里一块占了多少字节”,而 Data 是具体到单个变量。结合之前的代码和 FreeRTOS 运行机制,分析核心原因是。在 map 文件里看到的这些数字,最后一列就是。指的是编译器/链接器为一个目标文件(比如。就是一个具体的全局变量,它有明确的名字(所以不是 F
一. 如何查看这个程序占了多少内存了?
D:\stm32project\modbus\6_12pcAndThreeSensor\MDK-ARM\HAL_LCD
找到xxx.map文件
划到最后面,就有了。

二.内存改大了,问题分析
这个芯片的总大小是128kb flash,32kbD RAM,但是我把rtos的堆从原来的6KB扩大到10kb
#define configTOTAL_HEAP_SIZE ((size_t)10240)就会报错
Total RO Size (Code + RO Data) 63912 ( 62.41kB)
Total RW Size (RW Data + ZI Data) 32336 ( 31.58kB)
Total ROM Size (Code + RO Data + RW Data) 64248 ( 62.74kB),
.\HAL_LCD\HAL_LCD.axf: Error: L6406E: No space in execution regions with .ANY selector matching startup_stm32g431xx.o(STACK).
.\HAL_LCD\HAL_LCD.axf: Error: L6406E: No space in execution regions with .ANY selector matching usart.o(.bss).
,........
2.1. 先看芯片资源
STM32G431CBT6(常见封装)
- Flash: 128 KB
- SRAM: 32 KB
编译器报错的时候说的不是 Flash 不够,而是 SRAM 不够放 RW/ZI 数据(静态变量 + BSS + FreeRTOS 堆 + 系统栈)。
2.2. map 里的大小信息
Total RW Size (RW Data + ZI Data) = 32336 (≈ 31.6 KB)
这已经基本占满了芯片的 32 KB SRAM。
- 你设定 FreeRTOS 堆
6144(6 KB),这包含在 ZI Data 里。 - 当你把堆改成
10240(10 KB),RW/ZI 总量就变成了 超过 32 KB,所以编译器直接报错No space in execution regions。
所以问题不是 Flash,而是 RAM 空间紧张。
2.3. 你还能用多少 SRAM?
- 现在 RW+ZI = 32336 ≈ 31.6 KB
- 芯片只有 32 KB
- 也就是说你只剩下大概 几百字节的余量(不到 1 KB)。
换句话说:
- FreeRTOS heap 再往上加就爆 RAM
- 现在堆 6 KB 已经是极限,能跑起来算是刚好卡在边缘了。
2.4. 建议的优化方向
- 调低 FreeRTOS heap,不要盲目加大
- 你现在已经 6 KB,其实问题不是“heap 太小”,而是 任务栈分配太大/全局数组占用过多。
- 建议保留 6 KB heap,先用
xPortGetFreeHeapSize()实测,看看实际还剩多少。
- 检查任务栈大小
- 你现在每个任务栈给了 300(words)≈ 1200B
- FreeRTOS 默认的
configMINIMAL_STACK_SIZE = 128就是 512B - 很多任务不需要那么大,可以缩小到 256 或 384(words),节省几 KB。
- 移出大数组
- 看报错里的
.bss段:usart.o(.bss)、usb.o(.bss)、lcd.o(.data)……这些里可能有大数组(几 KB buffer)。 - 比如 USB CDC buffer、LCD 显存 buffer、USART DMA buffer 等。
- 可以:
- 用
static+__attribute__((section(".ccmram")))把这些 buffer 挪到 CCM RAM(如果芯片有)。 - 或者直接减小 buffer 大小。
- 用
- 看报错里的
- 调试内存占用
- 打开 map 文件,搜索
.bss.data,找到哪个变量占了大头。(四.具体分析) - 优化掉不必要的大数组,效果立竿见影。
- 打开 map 文件,搜索
2.5. 结论
- 剩余 RAM:不到 1 KB(几百字节),几乎没有余量。
- Heap 最大能用 6 KB,再大就爆。
- 要解决问题:必须减少全局变量占用(特别是 USB/LCD buffer),或减少任务栈需求,否则再增加 FreeRTOS heap 也没用。
三.任务2,3,可以同时运行,但是任务2,3,4同时就只有任务2,3,能运行?
现象就是:
- Task2 + Task3 → 正常
- Task2 + Task3 + Task4 → 只有 Task2、Task3 运行,Task4 不显示
结合之前的代码和 FreeRTOS 运行机制,分析核心原因是 任务间互斥锁和资源占用:
1️⃣ 堆空间限制
你之前的 map 文件显示:
RW + ZI 数据 ≈ 31.6 KB
SRAM 总量 = 32 KB
FreeRTOS heap = 6144 bytes (~6 KB)
- Task1、Task2、Task3、Task4 每个栈 300 words ≈ 1.2 KB
- 四个任务的栈 + heap 已经接近 6 KB + 4.8 KB = 10.8 KB
- RAM 总量 32 KB,静态变量 + BSS 已经占 ~31.6 KB
- 剩余 RAM 太少 → 新建的 Task4 堆栈无法分配 → 任务创建失败,但你没检查
xTaskCreate的返回值,所以没发现。
✅ 这是最直接原因:RAM 不够 Task4 分配栈。
2️⃣ 互斥锁嵌套可能加剧问题
- 你的 LCD 互斥锁和 Modbus mutex (
g_ch1_lock) 有交叉 - Task2/3 调用 LCD + Modbus mutex → 栈占用多
- Task4 需要 LCD mutex,但堆栈分配失败 → 永远不运行
- 所以单独 Task4 或 Task2+Task3 运行没问题,但三者同时就失败。
3️⃣ 解决办法
方法 A:增加可用 RAM
- 你的 STM32G431 SRAM = 32 KB
- 你的 RW+ZI 已经 31.6 KB → 基本满
- 只能优化 RAM 使用:
- 减少全局数组(LCD buffer / USB buffer / USART buffer)
- 减少任务栈大小:
- 当前 300 words ≈ 1.2 KB
- Task2/3/4 其实不需要太大,可以改 200-256 words
- FreeRTOS heap 保持 6 KB 或略减
方法 B:检查任务创建返回值
if (xTaskCreate(LibmodbusCH2TempHumiClientTask, "Task4", 300, NULL, osPriorityNormal, NULL) != pdPASS) {
// 分配失败,打印错误
}
- 这样能确认 Task4 栈分配失败
方法 C:优化 LCD 调用
- 避免在持 Modbus mutex 时调用 LCD mutex,减少栈占用和嵌套锁
四.具体分析 第2.4.4条
4.1为什么会爆
- STM32G431 只有 32 KB SRAM。
- 你编译出来 RW+ZI = 32336 (≈31.6 KB),已经几乎占满。
- 当你把 FreeRTOS
heap从 6144 (6 KB) 提高到 10240 (10 KB),RW/ZI 总量直接 >32 KB,所以报错No space in execution regions。
所以不是 Flash 不够,而是 RAM 爆了。
4.2map 里能看出什么
.bss/.data主要消耗来自:- UART/USB DMA 缓冲区(
g_uart2_rx_buf,g_uart3_rx_buf, USB CDC buffer) - LCD 全局变量(
TextColor,BackColor,g_spi_lcd_lock等) - FreeRTOS 内核对象(队列、信号量、堆、任务栈)
- libmodbus 上下文(
g_ch1_ctx,g_mb_mapping)
- UART/USB DMA 缓冲区(
这些东西全是全局变量,占的是 SRAM 的 RW/ZI 段。
这是map的内容:部分.
bss: usart.o(i.HAL_UARTEx_RxEventCallback) refers to usart.o(.bss) for huart2
Line 192: usart.o(i.HAL_UART_ErrorCallback) refers to usart.o(.bss) for huart2
Line 203: usart.o(i.HAL_UART_MspInit) refers to usart.o(.bss) for hdma_usart2_rx
Line 206: usart.o(i.HAL_UART_RxCpltCallback) refers to usart.o(.bss) for huart2
Line 209: usart.o(i.HAL_UART_TxCpltCallback) refers to usart.o(.bss) for huart2
....
...
Line 3835: .bss 0x20000388 Section 880 usart.o(.bss)
Line 3835: .bss 0x20000388 Section 880 usart.o(.bss)
Line 3836: g_uart2_rx_buf 0x20000630 Data 100 usart.o(.bss)
Line 3837: g_uart3_rx_buf 0x20000694 Data 100 usart.o(.bss)
Line 3843: .bss 0x2000322c Section 1248 ux_device_descriptors.o(.bss)
.....
.....
.data 0x20000000 Section 12 lcd.o(.data)
Line 3760: .data 0x20000000 Section 12 lcd.o(.data)
Line 3761: TextColor 0x20000000 Data 2 lcd.o(.data)
Line 3762: BackColor 0x20000002 Data 2 lcd.o(.data)
Line 3763: g_spi_lcd_lock 0x20000008 Data 4 lcd.o(.data)
Line 3764: .data 0x2000000c Section 28 app_freertos.o(.data)
Line 3764: .data 0x2000000c Section\
.....
...
这些是什么意思?
在 map 文件里看到的这些数字,最后一列就是 大小(字节数,单位是 Byte)。
比如你贴的这一段:
TextColor 0x20000000 Data 2 lcd.o(.data) // 占 2B
BackColor 0x20000002 Data 2 lcd.o(.data) // 占 2B
这些都很小(几个字节)。
真正“吃大头”的内存不是 .data 段,而是 `.bss 段的大数组。比如:
g_uart2_rx_bufg_uart3_rx_buf- USBX 里的
ux_device_stack - LCD 显示缓冲区(如果你有 frame buffer)
- FreeRTOS
ucHeap(堆,几 KB) - libmodbus 的
g_mb_mapping(寄存器映射表,可能几 KB)
这些在 map 里会显示成:
g_uart2_rx_buf 0x20001000 Data 1024 usart.o(.bss) // 1 KB
g_mb_mapping 0x20002000 Data 4096 app_freertos.o // 4 KB
4.3 有些是Section什么意思?
在 map 文件里你会看到两类东西:
4.3.1. Data
TextColor 0x20000000 Data 2 lcd.o(.data)
这里的 Data 就是一个具体的全局变量,它有明确的名字(TextColor),大小(2 字节),地址(0x20000000)。
4.3.2. Section
.data 0x2000000c Section 28 app_freertos.o(.data)
这里的 Section 指的是编译器/链接器为一个目标文件(比如 app_freertos.o)生成的 整个段(section),它里面可能包含多个 Data 变量。
.data段:存放有初始值的全局变量.bss段:存放未初始化的全局变量.rodata段:存放只读常量
所以 Section 就是“这一堆变量在内存里一块占了多少字节”,而 Data 是具体到单个变量。
🔎 举个例子:
.data 0x2000000c Section 28 app_freertos.o(.data)
g_xBinarySemaphoreSwitch 0x2000000c Data 4 app_freertos.o(.data)
g_xBinarySemaphoreENV 0x20000010 Data 4 app_freertos.o(.data)
g_xBinarySemaphoreTempHumi 0x20000014 Data 4 app_freertos.o(.data)
g_ch1_lock 0x20000018 Data 4 app_freertos.o(.data)
g_ch1_ctx 0x20000020 Data 4 app_freertos.o(.data)
- 这一段
.data总共是 28 字节(Section 28) - 它里面有 5 个变量(Data),每个大小 4B + 前后对齐填充
✅ 总结:
- Data = 单个变量
- Section = 变量的集合(某个 .o 文件里的 .data/.bss 段整体)
4.4那.bss为什么几百的数字都来了?
.bss存放的是 未初始化的全局变量 / 静态变量。- 因为没有初始值,所以在 Flash 里不占空间(只在启动时清零),但在 RAM 里要占空间。
- 你看到的数字(比如 880, 732, 17420 等),就是 这个 Section 在 RAM 里占用的字节数。
举几个例
1. USART
.bss 0x20000388 Section 880 usart.o(.bss)
usart.o的.bss占了 880 字节 RAM。- 里面的变量有:
huart2 (148B),huart3 (148B),hdma_usart2_rx (96B),g_uart2_rx_buf (100B)… 加起来差不多就 880B。
2. USB
.bss 0x200006f8 Section 732 usb.o(.bss)
usb.o占 732B RAM。- 比如
hpcd_USB_FS (732B)。
3. USBX 设备栈
.bss 0x20000a20 Section 17420 app_usbx_device.o(.bss)
ux_device_byte_pool_buffer 17408
cdc_acm_parameter 12
- 光
ux_device_byte_pool_buffer一个数组就占了 17408B (~17KB)。 - 这是 USBX 内存池,非常大,是内存吃紧的主要原因之一。
4. FreeRTOS 内核
.bss 0x2000534c Section 1220 tasks.o(.bss)
pxReadyTasksLists 1120
…
.bss 0x20005928 Section 1720 cmsis_os2.o(.bss)
Idle_Stack 512
Timer_Stack 1024
- FreeRTOS 内核自己要维护就绪任务队列、空闲任务堆栈、定时器堆栈,也占了几 KB。
5. Heap
.bss 0x20005fe0 Section 6144 heap_4.o(.bss)
ucHeap 6144
- 这是你配置的
configTOTAL_HEAP_SIZE(6KB)。 - 你要是改成
10240,这里就会变成 10KB。
为什么你看到“几百、上千的数字”
- 因为这些是 Section 的大小(一整个
.bss段的总和)。 - 它往往是数组、大结构体、内存池在占空间。
总结:
.bss里的数字就是 RAM 占用(字节数)。- 大户主要是:
ux_device_byte_pool_buffer(17KB)heap_4(6KB ~ 10KB,取决于配置)- FreeRTOS 任务和队列(几 KB)
五.怎么优化
优化 .bss(也就是 RAM 占用)一般有几条路线
优化思路
- 找“大户”下手
- 先从 最大数组 / 内存池 下手,比如
ux_device_byte_pool_buffer (17KB)和ucHeap (6KB)。 - MAP 文件里已经能看出谁占得多,优先盯它们。
- 先从 最大数组 / 内存池 下手,比如
- 调整 USBX 内存池大小
ux_device_byte_pool_buffer是 USBX 的内存池。- 默认配置可能很大(比如 32KB),你现在是 17KB。
- 如果你只用 CDC/MSC 这种简单类,可以在
ux_user.h或app_usbx_device.c里减小UX_DEVICE_MEMORY_POOL_SIZE。 - ✨ 一般几 KB 就够,不用 17KB!!!
- 精简 FreeRTOS Heap
- 你现在
ucHeap = 6144。 - 如果任务和队列不多,可以把
configTOTAL_HEAP_SIZE调小,比如 4KB。 - 方法:在
FreeRTOSConfig.h改#define configTOTAL_HEAP_SIZE ( ( size_t ) ( 4 * 1024 ) )。
- 你现在
- 检查任务栈大小
- FreeRTOS 里任务的栈空间是静态分配的,容易过大。
- 比如 Idle 任务 512B,Timer 任务 1024B,用户任务可能配了 512B、1024B,其实很多只用到几十字节。
- 建议:先用
uxTaskGetStackHighWaterMark()测每个任务的剩余栈,再调小。
- 检查全局数组 / 缓冲区
g_uart2_rx_buf (100B)、g_uart3_rx_buf (100B)还好。- 重点是看有没有大数组,比如
uint8_t big_buffer[4096];这种。 - 如果只在某个函数里用,可以改成 局部变量(malloc 或栈上分配)。
- 优化 USB 描述符 / 框架数组
DevFrameWorkDesc_FS (200B),USBD_Device_FS (296B)这些小不了多少,但如果不需要 HS 模式,可以关掉 HS 描述符,能省一点。
实战优化路线(推荐你按顺序做)
- 确认 RAM 大头(USBX 17KB、heap 6KB、FreeRTOS 栈几 KB)。
- 减 USBX 内存池 → 从 17KB 改到 8KB(试试看还能不能跑 CDC)。
- 减 FreeRTOS heap → 从 6KB 改到 4KB,够用就行。
- 检查任务栈 → 用
uxTaskGetStackHighWaterMark(),把 1024 改到 256/512。 - 找全局大数组 → 能不用全局的就用局部。
这样搞下来, RAM 占用能至少省个 8KB~12KB。
更多推荐



所有评论(0)