• 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,欢迎大家批评指正。

  • 相关阅读:
    leetcode33. Search in Rotated Sorted Array
    pycharm 设置sublime text3 monokai主题
    django class Meta
    leetcode30, Substring With Concatenation Of All Words
    Sublime text3修改tab键为缩进为四个空格,
    sublime text3 python打开图像的问题
    安装上imesupport输入法依然不跟随的解决办法,
    sublime text3 的插件冲突弃用问题,
    sublime text3 BracketHighlighter括号匹配的设置
    windows 下wget的使用
  • 原文地址:https://www.cnblogs.com/hekuzhengfeng/p/14461439.html
Copyright © 2020-2023  润新知