zoukankan      html  css  js  c++  java
  • RT-Thread文件系统组件在STM32H743上的应用

    原文链接
    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,欢迎大家批评指正。

  • 相关阅读:
    Idea启动多服务时的Dashboard展示
    通过maven动态配置spring boot配置文件
    辅域抢夺五大角色命令
    H3C交换机堆叠技术
    KMS激活专用:所有Windows版本的GVLK密钥对照表
    Windows Server 2012从Evaluation版转成正式版
    u盘装系统无法引导
    [调优]彻底解决RDP连接过程缓慢的问题
    [排错]无法初始化 vGPU“grid_p40-1q”的插件“/usr/lib64/vmware/plugin/libnvidia-vgx.so”
    Virtual Apps and Desktops 7 1912 LTSR集成DB迁移到SQL Server 2016 AlwaysOn生产环境
  • 原文地址:https://www.cnblogs.com/hekuzhengfeng/p/14461439.html
Copyright © 2011-2022 走看看