嵌入式 五月 31, 2021

RT-Thread文件系统组件在STM32H743上的应用

文章字数 9.2k 阅读约需 8 mins. 阅读次数 1000000

RT-Thread(后文简称RT)提供的DFS组件、Fatfs组件和SDIO驱动组合起来可用于操作SD卡,但RT的底层驱动目前对STM32H743(后文简称H743)适配不是很好,在stm32h743上移植RT时,包括SDIO在内的多个设备驱动都无法直接编译通过。且当前官方论坛中关于在H743上应用RT的相关的帖子也比较少,因此在本次使用SD卡挂载文件系统时,因为底层驱动不适配,遇到了很多问题,也尝试了很多办法,最后通过重写块设备部分的代码实现了文件系统的挂载。

开发环境

主控芯片:stm32h743iit6

硬件平台:自行制作的单板

RT-Thread版本:4.0.2

开发工具:RT-Thread Studio (Version: 2.0.0)(后文简称RT-Studio)

​ STM32CubeMX(Version: 6.0.1)

​ STM32CubeIDE(Version: 1.5.1)

存储设备:SD卡

RT虚拟文件系统简介

详细内容参看RT官方文档

RT虚拟文件系统层次架构如下图所示。

DFS是RT提供的虚拟文件系统组件,该组件为应用程序提供方便的文件操作接口,并支持多种文件系统和存储设备。本文采用的文件系统是FatFS,主要是因为FatFS可以兼容微软的FAT格式,基于该文件系统将文件写入到SD卡后,SD内容可以直接被windows系统识别。

由于底层驱动不适配的问题,本文基于ST的HAL库重写了块设备相关代码,最终实现了文件系统的功能。

基于RT-Studio的配置

在RT-Studio中新建工程及其他功能的实现此处不再详述,首先介配置DFS组件和FatFS组件。如下图所示:

点亮DFS组件和Fatfs组件。可能需要libc组件的支持,这点没有做过验证。其他组件与文件系统本身无关,此处不详细介绍。

按上图所示进行配置,DFS可以加载多种文件系统,但此处只使用了FatFs文件系统。

重写块设备相关代码

重写块设备代码的原因

之所以要重新块设备相关代码,主要原因有二:一个是本文已经反复提到的底层驱动不适配的问题,另一个是RT原有的SD Card设备类mcsd_blk_device较为复杂,包含了多个子类,需要研究多个类型及相关函数才能完成实例化,比较困难。

mcsd_blk_device的定义如下:

struct mmcsd_blk_device
{
    struct rt_mmcsd_card *card;
    rt_list_t list;
    struct rt_device dev;
    struct dfs_partition part;
    struct rt_device_blk_geometry geometry;
    rt_size_t max_req_size;
};

生成SDMMC初始化代码

使用STM32CubeMX创建项目,配置SDMMC1为SD 4 bit Wide bus模式。配置如下图:

时钟配置此处不做展示,项目中时钟源选择PLL1Q,频率为400MHz。配置完成后生成代码备用。

在RT中初始化并注册块设备

struct rt_device
{
    struct rt_object          parent;                   /**< inherit from rt_object */

    enum rt_device_class_type type;                     /**< device type */
    rt_uint16_t               flag;                     /**< device flag */
    rt_uint16_t               open_flag;                /**< device open flag */

    rt_uint8_t                ref_count;                /**< reference count */
    rt_uint8_t                device_id;                /**< 0 - 255 */

    /* device call back */
    rt_err_t (*rx_indicate)(rt_device_t dev, rt_size_t size);
    rt_err_t (*tx_complete)(rt_device_t dev, void *buffer);

#ifdef RT_USING_DEVICE_OPS
    const struct rt_device_ops *ops;
#else
    /* common device interface */
    rt_err_t  (*init)   (rt_device_t dev);
    rt_err_t  (*open)   (rt_device_t dev, rt_uint16_t oflag);
    rt_err_t  (*close)  (rt_device_t dev);
    rt_size_t (*read)   (rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size);
    rt_size_t (*write)  (rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size);
    rt_err_t  (*control)(rt_device_t dev, int cmd, void *args);
#endif

#if defined(RT_USING_POSIX)
    const struct dfs_file_ops *fops;
    struct rt_wqueue wait_queue;
#endif

    void                     *user_data;                /**< device private data */
};

块设备的类型定义如上,其中RT_USING_DEVICE_OPSRT_USING_POSIX未被定义。

下文是初始化和注册块设备的代码。首先创建块设备device_sd0,然后初始device_sd0的几个成员函数,初始化MCU的SDMMC1外设并将块设备注册到系统中。

int sdInit(void)
{
    rt_device_t device_sd0;
    SD_HandleTypeDef *hsd1;

    //创建一个系统块设备对象
    device_sd0 = rt_device_create(RT_Device_Class_Block, sizeof(SD_HandleTypeDef));

    //手动设置块设备对象的函数指针
    device_sd0->init = sdmmcInit;//初始化函数指针
    device_sd0->open = sdmmcOpen;
    device_sd0->close = sdmmcClose;
    device_sd0->read = sdmmcRead;
    device_sd0->write = sdmmcWrite;
    device_sd0->control = sdmmcControl;

    //将SDMMC1的控制块存定义到块设备的user data中
    device_sd0->user_data = &(device_sd0->user_data) + 1;
    hsd1 = (SD_HandleTypeDef *)device_sd0->user_data;
    rt_memset(hsd1, 0, sizeof(SD_HandleTypeDef));

    //将device_sd0注册到系统中,设备名为sd0
    rt_device_register(device_sd0, "sd0", RT_DEVICE_FLAG_RDWR);

    //初始化SDMMC1
    hsd1->Instance = SDMMC1;
    hsd1->Init.ClockEdge = SDMMC_CLOCK_EDGE_RISING;
    hsd1->Init.ClockPowerSave = SDMMC_CLOCK_POWER_SAVE_DISABLE;
    hsd1->Init.BusWide = SDMMC_BUS_WIDE_4B;
    hsd1->Init.HardwareFlowControl = SDMMC_HARDWARE_FLOW_CONTROL_ENABLE;
    hsd1->Init.ClockDiv = 8;
    if (HAL_SD_Init(hsd1) != HAL_OK)
    {
        return RT_ERROR;
    }
    return RT_EOK;
}
//注册初始化函数,参考官方文档自动初始化相关内容
INIT_DEVICE_EXPORT(sdInit);

使用HAL库初始化外设需要一个控制块(即SD_HandleTypeDef类型的一个实例),在创建块设备对象时,已预留了空间用于存储控制块。其原理如下:

  1. 函数rt_device_create中有为块设备申请内存的操作,计算所需内存大小时在块设备类型的基础上增加了作为参数传入的SDMMC1控制块所需的内存大小;
  2. 将块设备最后一个成员void * user_data指向预留的空间,及user_data的下一个地址;
  3. 将指针hsd1也指向预留的空间(方便操作);
  4. 此时已完成将SDMMC1控制块定义到块设备user data中的操作。

初始化外设SDMMC1的代码可直接从上文STM32CubeMX生成的代码中拷贝。此处有几个细节需要注意:

  1. 原来代码中是直接定义了一个SDMMC控制块对象,而在本项目中使用的是指向SDMMC控制块的指针,在调用控制块成员和函数传参时语法不同;
  2. 原来代码中初始化函数HAL_SD_init返回失败时进入一个错误处理函数(默认为死循环),而本项目中改为返回RT_ERROR
  3. 生成的代码中还有一个函数HAL_SD_MspInit也需要拷贝过来,改函数不需要任何更改,此处不再贴出代码。

块设备的几个成员函数定义如下,这些函数是文件系统与SD卡之间的桥梁。定义这几个函数的过程比较曲折,画了很多功夫才搞明白每一个函数应该实现什么样的功能,以及函数入参、返回值的意义。但函数的内容并不复杂,此处直接列出源码,不再详细解释。

rt_err_t sdmmcInit(rt_device_t dev)
{
    return RT_EOK;
}

rt_err_t sdmmcOpen(rt_device_t dev, rt_uint16_t oflag)
{
    return RT_EOK;
}

rt_err_t sdmmcClose(rt_device_t dev)
{
    return RT_EOK;
}

rt_size_t sdmmcRead(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size)
{
    if (HAL_SD_ReadBlocks((SD_HandleTypeDef *)dev->user_data, (uint8_t *)buffer, pos, size, 5000) != HAL_OK)
    {
        return 0;
    }
    return size;
}

rt_size_t sdmmcWrite(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size)
{
    if (HAL_SD_WriteBlocks((SD_HandleTypeDef *)dev->user_data, (uint8_t *)buffer, pos, size, 5000) != HAL_OK)
    {
        return 0;
    }
    return size;
}

rt_err_t sdmmcControl(rt_device_t dev, int cmd, void *args)
{
    struct rt_device_blk_geometry info;
    switch (cmd)
    {
    case RT_DEVICE_CTRL_BLK_GETGEOME:
        info.sector_count = ((SD_HandleTypeDef *)dev->user_data)->SdCard.LogBlockNbr;
        info.bytes_per_sector = ((SD_HandleTypeDef *)dev->user_data)->SdCard.LogBlockSize;
        info.block_size = ((SD_HandleTypeDef *)dev->user_data)->SdCard.LogBlockSize;
        rt_memcpy(args, &info, sizeof(struct rt_device_blk_geometry));
        break;
    default:
        break;
    }
    return RT_EOK;
}

将块设备挂载到文件系统

经过一番努力,现在已经在系统中注册了块设备”sd0”,DFS组件和FatFS组件会自动初始化,接下来需要将块设备挂载到文件系统中。函数如下:

int sd_mount(void)
{
    if(rt_device_find("sd0") != RT_NULL)
    {
        if (dfs_mount("sd0", "/", "elm", 0, 0) == RT_EOK)
        {
            LOG_I("sd card mount to '/'");
            return RT_EOK;
        }
        else
        {
            LOG_W("sd card mount to '/' failed!");
        }
    }
    return RT_ERROR;
}
INIT_APP_EXPORT(sd_mount);

更好的挂载文件系统的操作可以参考帖子RT-Thread进阶笔记之虚拟文件系统中6.6.4小节的方法创建一个挂载文件系统的线程。本项目中实际上是偷了个懒,挂载文件系统的操作只会尝试一次,因此必须等到DFS、FatFS、块设备都初始化完成后调用该函数才能成功挂载文件系统,所以把该函数注册到自动初始化最晚的“application init functions”中,具体参考官方文档相关章节。

其他注意事项

需要注意的是,在初始化系统时钟时,一定要配置外设SDMMC1的时钟,具体方法这里也不再详细说明。

小结

本项目不仅完成了RT-Thread文件系统组件在stm32h743上的应用,实际上也为RT-Thread其他设备驱动不适配stm32h743的问题提供了一个解决问题的思路。

由于本人能力有限,代码中可能存在诸多问题或bug,欢迎大家批评指正。

0%