前言
本文以stm32f407VGT6为例描述,查阅该芯片的手册,可以知道其内存一共192K,但是在MDK编译项目时,有时候发现内存总量只有128k,原因在于,192k中有64k是CCM内存,剩下的128K才是正常的内存,所谓的CCM内存是cpu直接访问的空间,读写速度比其它内存快,但是不支持DMA,所以DMA对应的内存空间不能放在CCM中。
一、使用CCM
打开mdk项目options,target选项卡,右下角可以配置使用的内存空间,注意新版的cube自动生成的mdk项目,默认是不使用ccm的,所以默认配置下IRAM1和IRAM2都是128k的地址空间,首地址是0x20000000,只不过将128k划分为两段。
我们得手动修改才能激活使用CCM,首先将IRAM1大小改为0x20000,即整个128k空间,然后将IRAM2改为CCM的首地址0x10000000,大小为0x10000。
这样配置后,如果程序没有用到DMA,已经可以正常使用了。系统会自动将整个项目程序的data、bss等内容分配到IRAM1和IRAM2中。
但是如果程序中用到了DMA,就存在风险了,一旦DMA对应的内存被自动分配到了IRAM2 ccm中,运行就会出错。
二、手动管理内存分配
为了解决这个问题,我们需要手动分配内存块,首先,去掉linker里面的 use memory layout from Target Dialog,那么就取消了自动内存分配,我们需要手动加载自定义的sct文件,手动配置的sct文件可以放在任意路径下,linker里面 scatter file可以指定sct文件的路径。这里我自己的sct文件叫my.sct还是放在原来的sct文件目录下,即MDK-ARM\项目名称\my.sct。
sct文件功能很强大,能够以多种方法进行内存的配置,比如,直接指定某个c文件使用的内存地址空间,另外,在代码中申请内存空间的时候,还可以用__attribute__((at(内存地址)))或__attribute__((section(”段别名“)))这个后缀来直接指定内存地址或内存段别名,二者配合使用可以很方便的分配使用的内存。
打开my.sct,修改内容如下:
; *************************************************************
; *** Scatter-Loading Description File generated by uVision ***
; *************************************************************
LR_IROM1 0x08000000 0x00100000 { ; load region size_region
ER_IROM1 0x08000000 0x00100000 { ; load address = execution address
*.o (RESET, +First)
*(InRoot$$Sections)
.ANY (+RO)
.ANY (+XO)
}
RW_IRAM1 0x20000000 0x00020000 { ; RW data
.ANY (+RW +ZI)
.ANY (ORIRAM)
}
RW_IRAM2 0x10000000 0x00010000 {
startup_stm32f407xx.o(Heap)
heap_4.o(+RW +ZI)
.ANY(CCMRAM)
}
}
这个配置的目标是分别将 startup_stm32f407xx.s中的Heap和 heap_4.c包含的所有数据放在了CCM IRAM2中,另外设置了一个段别名CCMRAM,凡是定义时指定了这个别名的数据,也会放入IRAM2中。同时在IRAM1中也设置了一个段别名ORIRAM,用来放置DMA使用的内存空间。其它data和bss数据都默认保存在IRAM1中( .ANY (+RW +ZI) )。
在程序main.c中,分别定义两个数组:
__align(32) uint8_t DmaMenBase[DMA_BUF_SIZE] __attribute__((section("ORIRAM")));
__align(32) uint8_t RapidMemBase[1024] __attribute__((section("CCMRAM")));
第一个DmaMenBase是串口接收DMA内存,指定放在ORIRAM section中,也就是IRAM1中避免读写错误,第二个RapidMemBase数组则指定放在CCMRAM section中,也就是IRAM2。设置完后编译程序,如果配置正确不会报错。
三、结果验证
编译完成后,打开map文件(和sct在同一个目录,即MDK-ARM\项目名称\XX.map),搜索 RW_IRAM2,可以看到如下内容:
Execution Region RW_IRAM2 (Exec base: 0x10000000, Load base: 0x0800396c, Size: 0x00004220, Max: 0x00010000, ABSOLUTE, COMPRESSED[0x0000000c])
Exec Addr Load Addr Size Type Attr Idx E Section Name Object
0x10000000 COMPRESSED 0x00000020 Data RW 4603 .data heap_4.o
0x10000020 COMPRESSED 0x00000400 Data RW 17 CCMRAM main.o
0x10000420 - 0x00003c00 Zero RW 4602 .bss heap_4.o
0x10004020 - 0x00000200 Zero RW 2 HEAP startup_stm32f407xx.o
Execution Region RW_IRAM1 (Exec base: 0x20000000, Load base: 0x0800395c, Size: 0x000013a0, Max: 0x00020000, ABSOLUTE, COMPRESSED[0x00000010])
Exec Addr Load Addr Size Type Attr Idx E Section Name Object
0x20000000 COMPRESSED 0x00000004 Data RW 227 .data freertos.o
0x20000004 COMPRESSED 0x0000000c Data RW 1372 .data stm32f4xx_hal.o
0x20000010 COMPRESSED 0x00000004 Data RW 2937 .data system_stm32f4xx.o
0x20000014 COMPRESSED 0x0000003c Data RW 3601 .data tasks.o
0x20000050 COMPRESSED 0x00000014 Data RW 3932 .data timers.o
0x20000064 COMPRESSED 0x00000004 Data RW 4157 .data cmsis_os2.o
0x20000068 COMPRESSED 0x0000000c Data RW 4659 .data port.o
0x20000074 COMPRESSED 0x0000000c PAD
0x20000080 COMPRESSED 0x00000100 Data RW 18 ORIRAM main.o
0x20000180 - 0x000000a4 Zero RW 293 .bss usart.o
0x20000224 - 0x00000048 Zero RW 435 .bss stm32f4xx_hal_timebase_tim.o
0x2000026c - 0x00000040 Zero RW 3176 .bss queue.o
0x200002ac - 0x000004c4 Zero RW 3600 .bss tasks.o
0x20000770 - 0x00000118 Zero RW 3931 .bss timers.o
0x20000888 - 0x000006b8 Zero RW 4156 .bss cmsis_os2.o
0x20000f40 - 0x00000060 Zero RW 4783 .bss c_w.l(libspace.o)
0x20000fa0 - 0x00000400 Zero RW 1 STACK startup_stm32f407xx.o
可以看到 heap_4.o 的data和bss 以及 startup_stm32f407xx.o 的Heap都放在了IRAM2里,IRAM2还包含main.o的CCMRAM段,大小为0x400,即1024字节,对应mian.c中定义的RapidMemBase。而IRAM1则包含了main.o的ORIRAM段,大小为0x100,即256字节,对应mian.c中定义的DmaMenBase。证明自定义配置已经生效。
注意,在程序代码中定义的数组,需要在代码段中确实使用到,否则可能会被编译器优化掉,那么在map中就看不到了。