为什么要添加外部 SRAM
最近准备在 RT-Thread 上加载一些机器学习模型,通常做目标检测 (object detection) 标准模型 (Yolov3, SSD) 一般在 200MB-300MB,精简版 (tiny-yolov3, tiny ssd) 也在 20MB-30MB,一些特别精简的模型 (SqueezeDet) 可以只有 4MB,但是 STM32 的 SRAM 都是按照 KB 来计算的,所以要加载模型无法避免地要用到外部 SRAM,于是决定在 STM32F407ZGT6 上面外扩一个 SRAM。
SRAM 种类
板子上的 SRAM 是 IS62WV51216 (1MB),虽然很明显就算外扩了这个 1MB 的内存,也是没办法完整加载模型的,但是最后还是试了一下,因为以前没有尝试过外扩 SRAM,刚好也试一试做个总结。
其实如果要加载大模型,应当用 SDRAM (Synchronous Dynamic Random Access Memory) 而不是 SRAM (Static Random Access Memory),这两者的区别在于静态随机存储器 (SRAM) 是使用具有记忆功能的触发器 (Flip-Flop),它物理材料是比较占用空间的晶体管 (Transistor),而动态随机存储器 (SDRAM) 则是用的电容 (Capacitor),于是相同大小的物理空间,如果 SDRAM 可以存储几个 GB 的数据,那么 SRAM 只能保存几十 MB 的数据。因此相同存储容量 SRAM 芯片尺寸就会比 SDRAM 大很多,电脑的大内存也是 DDR SDRAM。
但是 SRAM 也有好处,因为它用的是晶体管 (Transistor),所以只要上电了它的数据就可以自动保存;如果是 SDRAM 的话,因为它用的是电容 (Capacitor),电量就会逐渐衰减导致数据丢失,因此需要反复刷新充电。这样的结果就是 SRAM 操作起来非常容易,不需要像 SDRAM 那样需要有同步时钟反复刷新。
另一方便,晶体管 (Transistor) 的开断速度通常要远快于电容 (Capacitor) 的充电速度,所以 SRAM 的速度也会比 SDRAM 快很多。
总结一下,就是 SRAM 速度快,容量小,操作简单;SDRAM 速度相对慢,容量大,需要反复刷新。
RT-Thread 外置 SRAM
要在 RT-Thread 上使用外置的 SRAM,可以利用 STM32 的 FSMC,首先利用 CubeMX 根据自己的 SRAM 时序配置,并点击 Generate Code 生成代码
然后在自己 bsp 下添加 sram 的驱动,例如在 F:\rt-thread\bsp\stm32\stm32f407-atk-explorer\board\ports 里添加 drv_sram.c[/size][backcolor=rgb(247, 247, 247)]
[/backcolor]
#include <rtthread.h>
#include <rtdevice.h>
#include <board.h>
#ifdef BSP_USING_SRAM
#include <sram_port.h>
#define DRV_DEBUG
#define LOG_TAG "drv.sram"
#include <drv_log.h>
static SRAM_HandleTypeDef hsram;
static FMC_NORSRAM_TimingTypeDef SRAM_Timing;
#ifdef RT_USING_MEMHEAP_AS_HEAP
static struct rt_memheap system_heap;
#endif
static int SRAM_Init(void)
{
int result = RT_EOK;
/* SRAM device configuration /
hsram.Instance = FMC_NORSRAM_DEVICE;
hsram.Extended = FMC_NORSRAM_EXTENDED_DEVICE;
/ SRAM device configuration /
SRAM_Timing.AddressSetupTime = ADDRESSSETUPTIME;
SRAM_Timing.AddressHoldTime = ADDRESSHOLDTIME; / Min value, Don't care on SRAM Access mode A /
SRAM_Timing.DataSetupTime = DATASETUPTIME;
// SRAM_Timing.DataHoldTime = DATAHOLDTIME;
SRAM_Timing.BusTurnAroundDuration = BUSTURNAROUNDDURATION;
SRAM_Timing.CLKDivision = CLKDIVISION; / Min value, Don't care on SRAM Access mode A /
SRAM_Timing.DataLatency = DATALATENCY; / Min value, Don't care on SRAM Access mode A /
SRAM_Timing.AccessMode = ACCESSMODE;
hsram.Init.NSBank = FSMC_NORSRAM_BANK3;
hsram.Init.DataAddressMux = FSMC_DATA_ADDRESS_MUX_DISABLE;
hsram.Init.MemoryType = FSMC_MEMORY_TYPE_SRAM;
#if SRAM_DATA_WIDTH == 8
hsram.Init.MemoryDataWidth = FMC_NORSRAM_MEM_BUS_WIDTH_8;
#elif SRAM_DATA_WIDTH == 16
hsram.Init.MemoryDataWidth = FMC_NORSRAM_MEM_BUS_WIDTH_16;
#else
hsram.Init.MemoryDataWidth = FMC_NORSRAM_MEM_BUS_WIDTH_32;
#endif
hsram.Init.BurstAccessMode = FSMC_BURST_ACCESS_MODE_DISABLE;
hsram.Init.WriteOperation = FSMC_WRITE_OPERATION_ENABLE;
hsram.Init.WaitSignal = FSMC_WAIT_SIGNAL_DISABLE;
hsram.Init.WaitSignalActive = FSMC_WAIT_TIMING_BEFORE_WS;
hsram.Init.WaitSignalPolarity = FSMC_WAIT_SIGNAL_POLARITY_LOW;
hsram.Init.ExtendedMode = FSMC_EXTENDED_MODE_DISABLE;
hsram.Init.AsynchronousWait = FSMC_ASYNCHRONOUS_WAIT_DISABLE;
hsram.Init.WriteBurst = FSMC_WRITE_BURST_DISABLE;
hsram.Init.ContinuousClock = FSMC_CONTINUOUS_CLOCK_SYNC_ONLY;
// hsram.Init.WriteFifo = FSMC_WRITE_FIFO_DISABLE;
// hsram.Init.NBLSetupTime = 0;
hsram.Init.PageSize = FSMC_PAGE_SIZE_NONE;
/ Initialize the SRAM controller /
if (HAL_SRAM_Init(&hsram, &SRAM_Timing, &SRAM_Timing) != HAL_OK)
{
LOG_E("SRAM init failed!");
result = -RT_ERROR;
}
else
{
LOG_D("sram init success, mapped at 0x%X, size is %d bytes, data width is %d", SRAM_BANK_ADDR, SRAM_SIZE, SRAM_DATA_WIDTH);
#ifdef RT_USING_MEMHEAP_AS_HEAP
/ If RT_USING_MEMHEAP_AS_HEAP is enabled, SRAM is initialized to the heap */
rt_memheap_init(&system_heap, "sram", (void *)SRAM_BANK_ADDR, SRAM_SIZE);
#endif
}
return result;
}
INIT_DEVICE_EXPORT(SRAM_Init);
#ifdef DRV_DEBUG
#ifdef FINSH_USING_MSH
int sram_test(void)
{
int i = 0;
uint32_t start_time = 0, time_cast = 0;
#if SRAM_DATA_WIDTH == 8
char data_width = 1;
uint8_t data = 0;
uint8_t *ptr = (uint8_t *)SRAM_BANK_ADDR;
#elif SRAM_DATA_WIDTH == 16
char data_width = 2;
uint16_t data = 0;
uint16_t *ptr = (uint16_t *)SRAM_BANK_ADDR;
#else
char data_width = 4;
uint32_t data = 0;
uint32_t *ptr = (uint32_t )SRAM_BANK_ADDR;
#endif
/ write data */
LOG_D("Writing the %ld bytes data, waiting....", SRAM_SIZE);
start_time = rt_tick_get();
for (i = 0; i < SRAM_SIZE / data_width; i++)
{
#if SRAM_DATA_WIDTH == 8
((__IO uint8_t *)ptr) = (uint8_t)0x55;
#elif SRAM_DATA_WIDTH == 16
((__IO uint16_t *)ptr) = (uint16_t)0x5555;
#else
((__IO uint32_t )ptr) = (uint32_t)0x55555555;
#endif
}
time_cast = rt_tick_get() - start_time;
LOG_D("Write data success, total time: %d.%03dS.", time_cast / RT_TICK_PER_SECOND,
time_cast % RT_TICK_PER_SECOND / ((RT_TICK_PER_SECOND * 1 + 999) / 1000));
/ read data */
LOG_D("start Reading and verifying data, waiting....");
for (i = 0; i < SRAM_SIZE / data_width; i++)
{
#if SRAM_DATA_WIDTH == 8
data = ((__IO uint8_t *)ptr);
if (data != 0x55)
{
LOG_E("SRAM test failed at %d!", i);
continue;
}
#elif SRAM_DATA_WIDTH == 16
data = ((__IO uint16_t *)ptr);
if (data != 0x5555)
{
LOG_E("SRAM test failed at %d!", i);
continue;
}
#else
data = ((__IO uint32_t )ptr);
if (data != 0x55555555)
{
LOG_E("SRAM test failed at %d!", i);
continue;
}
#endif
}
if (i >= SRAM_SIZE / data_width)
{
LOG_D("SRAM test end!");
}
return RT_EOK;
}
MSH_CMD_EXPORT(sram_test, sram test);
#endif / FINSH_USING_MSH /
#endif / DRV_DEBUG /
#endif / BSP_USING_SRAM /
以及对应的配置文件 sram_port.h
#ifndef SDRAM_PORT_H
#define SDRAM_PORT_H
/ parameters for sdram peripheral /
/ Bank1 /
#define SRAM_TARGET_BANK 3
/ stm32f4 Bank1:0x60000000 /
#define SRAM_BANK_ADDR ((uint32_t)0x68000000)
/ data width: 8, 16, 32 /
#define SRAM_DATA_WIDTH 16
/ sram size /
#define SRAM_SIZE ((uint32_t)0x100000)
/ Timing configuration for IS61WV102416BLL-10MLI /
#define ADDRESSSETUPTIME 0
#define ADDRESSHOLDTIME 0
#define DATASETUPTIME 9
#define DATAHOLDTIME 1
#define BUSTURNAROUNDDURATION 0
#define CLKDIVISION 0
#define DATALATENCY 0
#define ACCESSMODE FSMC_ACCESS_MODE_A
/ Timing configuration for IS61WV102416BLL-10MLI */
#endif
还需要需要修改对应的 Sconscript,这样就生成项目就可以自动添加文件了,例如 F:\rt-thread\bsp\stm32\stm32f407-atk-explorer\board\Sconscript 里添加:[/size]
[code]if GetDepend([‘BSP_USING_SRAM’]):
src += Glob(‘ports/drv_sram.c’)
如果希望用 rt_malloc 和 rt_free 统一管理这块内存,需要打开 memheap
最后由于用到了 FSMC 和 SRAM 需要在 rtconfig.h 里添加:
[code]#define BSP_USING_SRAM
define BSP_USING_FMC
define BSP_USING_EXT_FMC_IO
[/code][size=2]这样就可以生成项目文件并编译了:
[code]scons —target=mdk5 -s[/code]
[size=5]RT-Thread 外置 SRAM 测试
如果一切正常的话开机在 msh 里输入 free 应当可以看到对应的内存块:
\ | /
RT - Thread Operating System
/ | \ 4.0.2 build Aug 12 2019
2006 - 2019 Copyright by rt-thread team
[D/drv.sram] sram init success, mapped at 0x68000000, size is 1048576 bytes, data width is 16
msh >free
memheap pool size max used size available size
sram 1048576 48 1048528
heap 126480 7192 119288
msh>[/code]
[size=2]可以看到这里我外置的 SRAM 大小就是 1M,如果输入 sram_test 可以对挂载的内存进行测试,确保数据读写是正常的:[/size]
[code]msh >sram_test
[D/drv.sram] Writing the 1048576 bytes data, waiting....
[D/drv.sram] Write data success, total time: 0.053S.
[D/drv.sram] start Reading and verifying data, waiting....
[D/drv.sram] SRAM test end!
msh >
总结
最后 STM32F407 的 SRAM1 有 128KB 的内存,加上外置的 1MB,一共就有了 1152KB 的内存了,但是这并不意味着可以一次性 malloc 这么多内存,因为它们在2个不同的物理内存单元上,RTT的内存管理可以将两个不同的物理内存单元合并为一个单元统一由 rt_malloc 和 rt_free 管理,但是只能在相同的物理内存单元上合并相邻的内存碎片,如果要申请的内存太大,即使跨物理单元有一块那么大的内存加在一起有那么大,也是没办法成功申请的。
原作者:wuhanstudio