系列文章目录
RT-Thread (2) RTT SPI设备驱动流程 || LWIP + ENC28J60
RT-Thread (3) 为RTT增加SP485驱动||RTT UART设备
0 前言
以前接触过一段时间的RTT,但是当时是直接使用了RTT的一个BSP,所以并未深入研究过。这次我有一个物联网项目需要从头适配RTT,所以记录一下适配过程中遇到的问题。
0.1 硬件资源
MCU: STM32F103ZET6
SRAM: XM8A51216 16 位宽 512K(512*16,即 1M 字节)FSMC总线挂载
0.2 软件资源
开发环境:RT-Thread Studio
RT-Thread:4.0.3
1 初始化硬件资源
XM8A51216挂载在STM32的FSMC总线上。这部分我在设计电路时参考的是正点原子的战舰开发板。之前使用的是库函数开发环境。这次使用RTT也是我正式转到HAL库。
① 在【driver】目录下新建drv_sram_ex.c文件。
② 引入头文件
#include "board.h"
#include <drv_common.h>
#include <rtthread.h>
#include <rtdevice.h>
③ 视情况引入debug头文件
#define DBG_TAG "drv_exsram"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
④ 编写FSMC和外部SRAM的初始化程序
这部分可以使用STM32CubeMX配置,也可以直接复制正点原子的初始化程序。这里我直接使用正点原子的初始化程序。如下:
SRAM_HandleTypeDef SRAM_Handler; //SRAM句柄
//SRAM初始化
int DRV_SRAM_Init(void)
{
GPIO_InitTypeDef GPIO_Initure;
FSMC_NORSRAM_TimingTypeDef FSMC_ReadWriteTim;
__HAL_RCC_FSMC_CLK_ENABLE(); //使能FSMC时钟
__HAL_RCC_GPIOD_CLK_ENABLE(); //使能GPIOD时钟
__HAL_RCC_GPIOE_CLK_ENABLE(); //使能GPIOE时钟
__HAL_RCC_GPIOF_CLK_ENABLE(); //使能GPIOF时钟
__HAL_RCC_GPIOG_CLK_ENABLE(); //使能GPIOG时钟
//PD0,1,4,5,8~15
GPIO_Initure.Pin=GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_8|\
GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_11|GPIO_PIN_12|GPIO_PIN_13|\
GPIO_PIN_14|GPIO_PIN_15;
GPIO_Initure.Mode=GPIO_MODE_AF_PP; //推挽复用
GPIO_Initure.Pull=GPIO_PULLUP; //上拉
GPIO_Initure.Speed=GPIO_SPEED_FREQ_HIGH; //高速
HAL_GPIO_Init(GPIOD,&GPIO_Initure);
//PE0,1,7~15
GPIO_Initure.Pin=GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_7|GPIO_PIN_8|GPIO_PIN_9|\
GPIO_PIN_10| GPIO_PIN_11|GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|\
GPIO_PIN_15;
HAL_GPIO_Init(GPIOE,&GPIO_Initure);
//PF0~5,12~15
GPIO_Initure.Pin=GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3|GPIO_PIN_4|\
GPIO_PIN_5|GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15;
HAL_GPIO_Init(GPIOF,&GPIO_Initure);
//PG0~5,10
GPIO_Initure.Pin=GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_10;
HAL_GPIO_Init(GPIOG,&GPIO_Initure);
SRAM_Handler.Instance=FSMC_NORSRAM_DEVICE;
SRAM_Handler.Extended=FSMC_NORSRAM_EXTENDED_DEVICE;
SRAM_Handler.Init.NSBank=FSMC_NORSRAM_BANK3; //使用NE3
SRAM_Handler.Init.DataAddressMux=FSMC_DATA_ADDRESS_MUX_DISABLE; //地址/数据线不复用
SRAM_Handler.Init.MemoryType=FSMC_MEMORY_TYPE_SRAM; //SRAM
SRAM_Handler.Init.MemoryDataWidth=FSMC_NORSRAM_MEM_BUS_WIDTH_16; //16位数据宽度
SRAM_Handler.Init.BurstAccessMode=FSMC_BURST_ACCESS_MODE_DISABLE; //是否使能突发访问,仅对同步突发存储器有效,此处未用到
SRAM_Handler.Init.WaitSignalPolarity=FSMC_WAIT_SIGNAL_POLARITY_LOW; //等待信号的极性,仅在突发模式访问下有用
SRAM_Handler.Init.WaitSignalActive=FSMC_WAIT_TIMING_BEFORE_WS; //存储器是在等待周期之前的一个时钟周期还是等待周期期间使能NWAIT
SRAM_Handler.Init.WriteOperation=FSMC_WRITE_OPERATION_ENABLE; //存储器写使能
SRAM_Handler.Init.WaitSignal=FSMC_WAIT_SIGNAL_DISABLE; //等待使能位,此处未用到
SRAM_Handler.Init.ExtendedMode=FSMC_EXTENDED_MODE_DISABLE; //读写使用相同的时序
SRAM_Handler.Init.AsynchronousWait=FSMC_ASYNCHRONOUS_WAIT_DISABLE; //是否使能同步传输模式下的等待信号,此处未用到
SRAM_Handler.Init.WriteBurst=FSMC_WRITE_BURST_DISABLE; //禁止突发写
//FMC读时序控制寄存器
FSMC_ReadWriteTim.AddressSetupTime=0x00; //地址建立时间(ADDSET)为1个HCLK 1/72M=13.8ns
FSMC_ReadWriteTim.AddressHoldTime=0x00; //地址保持时间(ADDHLD)模式A未用到
FSMC_ReadWriteTim.DataSetupTime=0x01; //数据保存时间为3个HCLK =4*13.8=55ns
FSMC_ReadWriteTim.BusTurnAroundDuration=0X00;
FSMC_ReadWriteTim.AccessMode=FSMC_ACCESS_MODE_A;//模式A
HAL_SRAM_Init(&SRAM_Handler,&FSMC_ReadWriteTim,&FSMC_ReadWriteTim);
return 0;
}
⑤ 通过INIT_BOARD_EXPORT(fn) 导出初始化,使其在RTT的板级初始化阶段运行
INIT_BOARD_EXPORT(DRV_SRAM_Init);
至此外部SRAM的硬件初始化即完成,下面开始将外部SRAM加入内存管理。
2 注册内存管理
使用RT-Thread Studio建立STM32 的 RT-Thread 项目时,内存管理默认使用的是内存池。如下图所示。
2.1 RTT内存堆初探
① 修改RTT 内核中内存管理配置
将内存管理配置修改为:
【取消】【使用内存池】;
【勾选】【使用内存堆对象】
【修改】【使能动态内存】为【Use all of memheap objects as heap】
【保存修改】
此时,编译可以编译下载工程。在终端中使用list_memheap命令查看所有内存堆,可以看到已经有了一个内存堆。如下图所示。
该内存堆为程序编译后,内部RAM剩余空间。这点引用RTT文档中的说明:
程序运行之前,需要有文件实体被烧录到 STM32 的 Flash 中,一般是 bin 或者 hex 文件,该被烧录文件称为可执行映像文件。如下图左边部分所示,是可执行映像文件烧录到 STM32 后的内存分布,它包含 RO 段和 RW 段两个部分:其中 RO 段中保存了 Code、RO-data 的数据,RW 段保存了 RW-data 的数据,由于 ZI-data 都是 0,所以未包含在映像文件中。
STM32 在上电启动之后默认从 Flash 启动,启动之后会将 RW 段中的 RW-data(初始化的全局变量)搬运到 RAM 中,但不会搬运 RO 段,即 CPU 的执行代码从 Flash 中读取,另外根据编译器给出的 ZI 地址和大小分配出 ZI 段,并将这块 RAM 区域清零。其中动态内存堆为未使用的 RAM 空间,RT-Thread 将 “ZI 段结尾处” 到内存尾部的空间用作内存堆。
这一点我们可以打开【工程】→【driver】→board.c文件。
可以看到,在RT_WEAK void rt_hw_board_init()中调用了void rt_system_heap_init(void begin_addr, void end_addr);进行内存堆初始化。
RTT手册上说:
在使用内存堆时,必须要在系统初始化的时候进行堆的初始化,可以通过下面的函数接口完成:
void rt_system_heap_init(void begin_addr, void end_addr);
这个函数会把参数 begin_addr,end_addr 区域的内存空间作为内存堆来使用。下表描述了该函数的输入参数:
rt_system_heap_init() 的输入参数
begin_addr 堆内存区域起始地址
end_addr 堆内存区域结束地址
2.2 memheap管理算法
在我们使用RTT时,嵌入式的内存资源时比较紧张的,因此我们不希望只使用外部SRAM而放弃内部RAM。因此我们的RTT需要管理多个地址不连续的内存堆。好在RTT支持memheap管理算法,可以很方便地把多个 memheap(地址可不连续)粘合起来用于系统的 heap 分配。
在RTT的手册中有一个提示,刚开始我被这个提示迷惑住了。提示如下:
这个提示的意思是系统原来的heap和memheap是两套不同的机制。而我理解成如果使用heap管理则使用void rt_system_heap_init(void begin_addr, void end_addr);接口初始化内存堆;而使用memheap则全部使用rt_err_t rt_memheap_init(...)初始化所有内存堆(包括首堆)。
然而并非如此。即使使用memheap管理算法,第一个内存堆仍然要使用void rt_system_heap_init(void begin_addr, void end_addr);所以,board.c中的void rt_hw_board_init()内初始化内存堆的代码不需要修改。
② 在drv_sram_ex.c中增加内存注册函数。
#define RAM_HEAP_EX_START (0x68000000)
#define RAM_HEAP_EX_SIZE (1024 * 1024)
#define RAM_HEAP_EX_END (RAM_EX_START + RAM_EX_SIZE)
#include
struct rt_memheap ex_ram_heap;
int DRV_EX_SRAM_Register(void)
{
//注册内存堆到内存堆管理
rt_memheap_init(&ex_ram_heap,"SRAM EX",(void*)RAM_HEAP_EX_START,RAM_HEAP_EX_SIZE);
return 0;
}
首先引入#include 头文件。
而后定义了一个静态结构体变量,struct rt_memheap ex_ram_heap 作为内存堆控制块。rt_memheap变量类型需要引入上述的头文件。
然后调用rt_memheap_init接口将外部内存添加到内存堆中。
最后,调用INIT_COMPONENT_EXPORT将其导出到RTT组件初始化,使其在RTT组件级自动运行。
编译下载后使用list_memheap命令在终端中可以查看内存堆,效果如下:
可以看到外部内存堆已经初始化完成了,后续我们就可以使用RTT的内存管理API申请、释放内存。
更多内容请查看Skyate的个人博客。