STM32F4 运行 外部 RAM 中的 FreeRTOS
STM32F4 running FreeRTOS in external RAM
我们有一个论文项目正在努力让外部 RAM 为 STM32F417 MCU 工作。
该项目正在尝试一些非常耗费资源且内部 RAM 不够用的东西。
问题是如何最好地做到这一点。
目前的方法是将 link 脚本 (gnu ld) 中的 RAM 地址替换为外部 RAM 的地址。
这种方法的问题是在初始化期间,由于 FSMC 尚未初始化,芯片必须 运行 在内部 RAM 上。
它似乎可以工作,但是一旦 pvPortMalloc 为 运行,我们就会遇到一个硬错误,这可能是由于取消引用了虚假地址,我们可以看到变量在系统初始化时没有正确初始化(这使得我猜是因为内部 RAM 根本没有被使用,而它可能应该被使用。
我意识到这是一个模糊的问题,但是当 运行在 Cortex M4 MCU(更具体地说是 STM32F4)的外部 RAM 中写入代码时,一般方法是什么?
谢谢
当应用程序启动时,它会尝试通过将数据清零或将其初始化为非零值来初始化数据,具体取决于变量所在的部分。使用普通 运行 时间模型,这将在调用 main() 之前发生。所以你有类似的东西:
1) 重置向量调用初始化代码
2) C 运行 时间初始化代码初始化变量
3) C 运行 时间初始化代码调用 main()
如果您使用链接器将变量放置在外部 RAM 中,那么您需要确保在初始化发生之前可以访问 RAM,否则您将遇到硬故障。因此,您需要有一个引导加载程序来为您设置系统,然后启动您的应用程序....或者更简单地编辑启动代码以执行以下操作:
1) 重置向量调用初始化代码
2) >>>C 运行 时间初始化代码配置外部RAM<<<
3) C 运行 时间初始化代码初始化变量
4) C 运行 时间初始化代码调用 main().
这样,在您尝试访问 RAM 之前,RAM 就可用了。
但是,如果您只想在外部 RAM 中拥有 FreeRTOS 堆,那么您可以保持初始化代码不变,只需使用适当的堆实现 - 基本上不只是声明大型静态数组的实现.例如,如果您使用 heap_5 那么您需要做的就是确保在执行任何分配之前调用堆初始化函数,因为堆初始化只是描述将哪个 RAM 用作堆,而不是静态声明堆.
FreeRTOS 定义并使用单个大内存区域进行堆栈和堆管理;这只是一个字节数组,其大小由 FreeRTOSConfig.h
中的 configTOTAL_HEAP_SIZE
符号指定。 FreeRTOS使用其pvPortMalloc
函数在该内存区域分配任务堆栈,因此这里的主要目标是将FreeRTOS堆区域放入外部SRAM。
FreeRTOS堆内存区域在heap_*.c
中定义(除了heap_3.c
使用标准库malloc并且它没有定义任何自定义堆区域),变量被调用ucHeap
。您可以使用编译器扩展来设置它的部分。对于 GCC,这将类似于:
static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ] __attribute__ ((section (".sram_data")));
现在我们需要配置链接描述文件以将此自定义段放入外部 SRAM。有几种方法可以做到这一点,这又取决于您使用的工具链。使用 GCC 的一种方法是为 SRAM 定义一个内存区域,并为 ".sram_data"
定义一个部分以附加到 SRAM 区域,例如:
MEMORY
{
...
/* Define SRAM region */
sram : ORIGIN = <SRAM_START_ADDR>, LENGTH = <SRAM_SIZE>
}
SECTIONS
{
...
/* Define .sram_data section and place it in sram region */
.sram_data :
{
*(.sram_data)
} >sram
...
}
这会将 ucHeap
区域放置在外部 SRAM 中,而所有其他文本和数据部分将放置在默认内存区域(内部闪存和 ram)中。
一些注意事项:
- 确保在调用任何 FreeRTOS 函数(如
xTaskCreate
)之前初始化 SRAM controller/FSMC
- 一旦开始任务,所有堆栈分配的变量将被放置在
ucHeap
(即外部RAM)中,但全局变量仍然分配在内部RAM中。如果您仍然有内部 RAM 大小问题,您可以使用编译器扩展配置其他全局变量以放置在 ".sram_section"
中(如 ucHeap 所示)
- 如果您的代码使用动态内存分配,请确保使用
pvPortMalloc/vPortFree
,而不是标准库 malloc/free
。这是因为只有 pvPortMalloc/vPortFree
会使用 ext RAM 中的 ucHeap 区域(而且它们是线程安全的,这是一个加号)
- 如果您正在执行大量动态任务 creation/deletion 并使用具有不同内存块大小的
pvPortMalloc/vPortFree
进行内存分配,请考虑使用 heap_4.c
而不是 heap_2.c
。 heap_2.c
在使用多个不同的块大小时存在内存碎片问题,而 heap_4.c
能够将相邻的空闲内存块组合成一个大块
另一种(可能更简单的)解决方案是将 ucHeap 变量定义为指针而不是数组,如下所示:
static uint8_t * const ucHeap = <SRAM_START_ADDR>;
这不需要任何特殊的链接描述文件编辑,所有内容都可以放在默认部分中。请注意,使用此解决方案,链接器不会显式为堆保留任何内存,并且您将失去一些可能有用的 information/errors (例如堆区域不适合 ext RAM)。但只要你的外部 RAM 中只有 ucHeap
,并且你的 configTOTAL_HEAP_SIZE
小于外部 RAM 大小,那可能就可以正常工作。
我们有一个论文项目正在努力让外部 RAM 为 STM32F417 MCU 工作。 该项目正在尝试一些非常耗费资源且内部 RAM 不够用的东西。
问题是如何最好地做到这一点。
目前的方法是将 link 脚本 (gnu ld) 中的 RAM 地址替换为外部 RAM 的地址。
这种方法的问题是在初始化期间,由于 FSMC 尚未初始化,芯片必须 运行 在内部 RAM 上。
它似乎可以工作,但是一旦 pvPortMalloc 为 运行,我们就会遇到一个硬错误,这可能是由于取消引用了虚假地址,我们可以看到变量在系统初始化时没有正确初始化(这使得我猜是因为内部 RAM 根本没有被使用,而它可能应该被使用。
我意识到这是一个模糊的问题,但是当 运行在 Cortex M4 MCU(更具体地说是 STM32F4)的外部 RAM 中写入代码时,一般方法是什么?
谢谢
当应用程序启动时,它会尝试通过将数据清零或将其初始化为非零值来初始化数据,具体取决于变量所在的部分。使用普通 运行 时间模型,这将在调用 main() 之前发生。所以你有类似的东西:
1) 重置向量调用初始化代码 2) C 运行 时间初始化代码初始化变量 3) C 运行 时间初始化代码调用 main()
如果您使用链接器将变量放置在外部 RAM 中,那么您需要确保在初始化发生之前可以访问 RAM,否则您将遇到硬故障。因此,您需要有一个引导加载程序来为您设置系统,然后启动您的应用程序....或者更简单地编辑启动代码以执行以下操作:
1) 重置向量调用初始化代码 2) >>>C 运行 时间初始化代码配置外部RAM<<< 3) C 运行 时间初始化代码初始化变量 4) C 运行 时间初始化代码调用 main().
这样,在您尝试访问 RAM 之前,RAM 就可用了。
但是,如果您只想在外部 RAM 中拥有 FreeRTOS 堆,那么您可以保持初始化代码不变,只需使用适当的堆实现 - 基本上不只是声明大型静态数组的实现.例如,如果您使用 heap_5 那么您需要做的就是确保在执行任何分配之前调用堆初始化函数,因为堆初始化只是描述将哪个 RAM 用作堆,而不是静态声明堆.
FreeRTOS 定义并使用单个大内存区域进行堆栈和堆管理;这只是一个字节数组,其大小由 FreeRTOSConfig.h
中的 configTOTAL_HEAP_SIZE
符号指定。 FreeRTOS使用其pvPortMalloc
函数在该内存区域分配任务堆栈,因此这里的主要目标是将FreeRTOS堆区域放入外部SRAM。
FreeRTOS堆内存区域在heap_*.c
中定义(除了heap_3.c
使用标准库malloc并且它没有定义任何自定义堆区域),变量被调用ucHeap
。您可以使用编译器扩展来设置它的部分。对于 GCC,这将类似于:
static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ] __attribute__ ((section (".sram_data")));
现在我们需要配置链接描述文件以将此自定义段放入外部 SRAM。有几种方法可以做到这一点,这又取决于您使用的工具链。使用 GCC 的一种方法是为 SRAM 定义一个内存区域,并为 ".sram_data"
定义一个部分以附加到 SRAM 区域,例如:
MEMORY
{
...
/* Define SRAM region */
sram : ORIGIN = <SRAM_START_ADDR>, LENGTH = <SRAM_SIZE>
}
SECTIONS
{
...
/* Define .sram_data section and place it in sram region */
.sram_data :
{
*(.sram_data)
} >sram
...
}
这会将 ucHeap
区域放置在外部 SRAM 中,而所有其他文本和数据部分将放置在默认内存区域(内部闪存和 ram)中。
一些注意事项:
- 确保在调用任何 FreeRTOS 函数(如
xTaskCreate
)之前初始化 SRAM controller/FSMC - 一旦开始任务,所有堆栈分配的变量将被放置在
ucHeap
(即外部RAM)中,但全局变量仍然分配在内部RAM中。如果您仍然有内部 RAM 大小问题,您可以使用编译器扩展配置其他全局变量以放置在".sram_section"
中(如 ucHeap 所示) - 如果您的代码使用动态内存分配,请确保使用
pvPortMalloc/vPortFree
,而不是标准库malloc/free
。这是因为只有pvPortMalloc/vPortFree
会使用 ext RAM 中的 ucHeap 区域(而且它们是线程安全的,这是一个加号) - 如果您正在执行大量动态任务 creation/deletion 并使用具有不同内存块大小的
pvPortMalloc/vPortFree
进行内存分配,请考虑使用heap_4.c
而不是heap_2.c
。heap_2.c
在使用多个不同的块大小时存在内存碎片问题,而heap_4.c
能够将相邻的空闲内存块组合成一个大块
另一种(可能更简单的)解决方案是将 ucHeap 变量定义为指针而不是数组,如下所示:
static uint8_t * const ucHeap = <SRAM_START_ADDR>;
这不需要任何特殊的链接描述文件编辑,所有内容都可以放在默认部分中。请注意,使用此解决方案,链接器不会显式为堆保留任何内存,并且您将失去一些可能有用的 information/errors (例如堆区域不适合 ext RAM)。但只要你的外部 RAM 中只有 ucHeap
,并且你的 configTOTAL_HEAP_SIZE
小于外部 RAM 大小,那可能就可以正常工作。