一. 如何查看这个程序占了多少内存了?

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. 建议的优化方向

  1. 调低 FreeRTOS heap,不要盲目加大
    • 你现在已经 6 KB,其实问题不是“heap 太小”,而是 任务栈分配太大/全局数组占用过多
    • 建议保留 6 KB heap,先用 xPortGetFreeHeapSize() 实测,看看实际还剩多少。
  2. 检查任务栈大小
    • 你现在每个任务栈给了 300(words)≈ 1200B
    • FreeRTOS 默认的 configMINIMAL_STACK_SIZE = 128 就是 512B
    • 很多任务不需要那么大,可以缩小到 256 或 384(words),节省几 KB。
  3. 移出大数组
    • 看报错里的 .bssusart.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 大小。
  4. 调试内存占用
    • 打开 map 文件,搜索 .bss .data,找到哪个变量占了大头。(四.具体分析)
    • 优化掉不必要的大数组,效果立竿见影。

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 使用
    1. 减少全局数组(LCD buffer / USB buffer / USART buffer)
    2. 减少任务栈大小:
      • 当前 300 words ≈ 1.2 KB
      • Task2/3/4 其实不需要太大,可以改 200-256 words
    3. 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 heap6144 (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

这些东西全是全局变量,占的是 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_buf
  • g_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.o732B 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 占用)一般有几条路线


优化思路
  1. 找“大户”下手
    • 先从 最大数组 / 内存池 下手,比如 ux_device_byte_pool_buffer (17KB)ucHeap (6KB)
    • MAP 文件里已经能看出谁占得多,优先盯它们。
  2. 调整 USBX 内存池大小
    • ux_device_byte_pool_buffer 是 USBX 的内存池。
    • 默认配置可能很大(比如 32KB),你现在是 17KB。
    • 如果你只用 CDC/MSC 这种简单类,可以在 ux_user.happ_usbx_device.c 里减小 UX_DEVICE_MEMORY_POOL_SIZE
    • ✨ 一般几 KB 就够,不用 17KB!!!
  3. 精简 FreeRTOS Heap
    • 你现在 ucHeap = 6144
    • 如果任务和队列不多,可以把 configTOTAL_HEAP_SIZE 调小,比如 4KB。
    • 方法:在 FreeRTOSConfig.h#define configTOTAL_HEAP_SIZE ( ( size_t ) ( 4 * 1024 ) )
  4. 检查任务栈大小
    • FreeRTOS 里任务的栈空间是静态分配的,容易过大。
    • 比如 Idle 任务 512B,Timer 任务 1024B,用户任务可能配了 512B、1024B,其实很多只用到几十字节。
    • 建议:先用 uxTaskGetStackHighWaterMark() 测每个任务的剩余栈,再调小。
  5. 检查全局数组 / 缓冲区
    • g_uart2_rx_buf (100B)g_uart3_rx_buf (100B) 还好。
    • 重点是看有没有大数组,比如 uint8_t big_buffer[4096]; 这种。
    • 如果只在某个函数里用,可以改成 局部变量(malloc 或栈上分配)
  6. 优化 USB 描述符 / 框架数组
    • DevFrameWorkDesc_FS (200B)USBD_Device_FS (296B) 这些小不了多少,但如果不需要 HS 模式,可以关掉 HS 描述符,能省一点。

实战优化路线(推荐你按顺序做)
  1. 确认 RAM 大头(USBX 17KB、heap 6KB、FreeRTOS 栈几 KB)。
  2. 减 USBX 内存池 → 从 17KB 改到 8KB(试试看还能不能跑 CDC)。
  3. 减 FreeRTOS heap → 从 6KB 改到 4KB,够用就行。
  4. 检查任务栈 → 用 uxTaskGetStackHighWaterMark(),把 1024 改到 256/512。
  5. 找全局大数组 → 能不用全局的就用局部。

这样搞下来, RAM 占用能至少省个 8KB~12KB。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐