当前位置: 首页 > news >正文

RT-Thread GD32F4xx实现SD卡热插拔检测功能

GD32F470移植RT-Thread操作系统添加SD卡功能,增加SD卡热插拔检测

    • 一、RT-Thread移植sd卡功能
    • 二、实现SD卡热插拔检测原理
    • 三、软件实现过程
    • 四、延展之ASSERT ERROR,即RT-Thread断言错误
    • 五、延展之STM32 SD卡热插拔检测
    • 六、结束语

一、RT-Thread移植sd卡功能

RT-Thread官方下载gd32f470的源代码,根据自己的情况裁剪一下无关的文件,配置好工程,打开env配置工具,使能SDIO,使能DFS虚拟文件系统,使能elm-chan fatfs文件系统,将注册的sd0块设备以elm fatfs形式注册到DFS虚拟文件系统中。文件系统需要用到RTC,在env中使能RTC功能,配置一下系统时间。
env配置如下图:
在这里插入图片描述
env配置完之后,打开keil编译工程,下载到板子里,用console查看sd设备是否挂载成功(默认sd0,块设备),如下图挂载成功:
在这里插入图片描述
当设备列表中有sd0块设备时表明sd卡注册成功了,此时在应用层调用dfs_mount挂载sd0,然后就可以使用文件系统了。
SD卡的流程大概就这样,这里不做详细描述了。RT-Thread官方下载的源代码工程裁剪以及env工具配置使用等可以参考我另一篇博文。工程裁剪及env配置

二、实现SD卡热插拔检测原理

添加好sd卡驱动后,挂载sd0块设备后就能用文件系统操作了。刚开始调试好时,测试发现如果上电前未插入sd卡,系统会直接死机;另外在sd卡挂载成功后,中途拔卡也会出现系统故障;如果支持热插拔会好很多,提升用卡体验感。但是gd32f470的sd驱动中不包含卡的热插拔功能,需要自己实现。
实现方式为:
通过检测SD卡的check pin脚的电平边沿变化来检测SD卡时候插入和拔出,当插入SD卡的时候,该脚变为低电平,当拔掉SD卡的时候,该脚变为高电平。
不过该方式有一定的局限性,因为有些板子的check pin是悬空的,并没有接在mcu的任何脚上,这种情况下,使用上面所述的方法进行热插拔检测就不可行了,比方说,野火的F429挑战者开发板,它的SD卡的check pin就是悬空的,如图:
在这里插入图片描述
不过我所用的板子,checkpin脚是有接到mcu的管脚上的,如下图:
在这里插入图片描述
所以只需在sd卡应用代码里增加这个管脚的信号检测就可以实现sd卡的热插拔功能了!

三、软件实现过程

1、定义热插拔检测管脚

/* SD Card hot plug detection pin */
#define SD_CHECK_PIN        GET_PIN(A, 15)

2、创建sd卡挂载线程,sd卡挂载线程代码如下:

static void sd_mount(void *parameter)
{rt_uint8_t re_sd_check_pin = 1;while (1){rt_thread_mdelay(200);if(re_sd_check_pin && (re_sd_check_pin = rt_pin_read(SD_CHECK_PIN)) == 0){if (_sdcard_mount() == RT_EOK){LOG_I("sd mount ok!");rt_event_send(&sd_mount_event, eEVENT_SD_MOUNT);}}if (!re_sd_check_pin && (re_sd_check_pin = rt_pin_read(SD_CHECK_PIN)) != 0){if (_sdcard_unmount() == RT_EOK){LOG_I("sd unmount ok!");rt_event_send(&sd_mount_event, eEVENT_SD_UNMOUNT);}}}
}

该线程只是检测SD卡插拔的状态。
“re_sd_check_pin = 1"的作用是为了让SD卡在未上电的时候已经插进去了这种情况下制造电平边沿用的。因为如果SD卡一直插着,是没有电平变化的。“re_sd_check_pin = 1"就是为了手动制造一个电平变化出来。否则无法通过if语言的电平边沿检测,也就是说无法执行”_sdcard_mount()”。需要注意的是,这段代码中的两个if语句之后都会跟着一个"re_sd_check = rt_pin_read(SD_CHECK_PIN)"去记录上次的的电平值。

在上电后拔卡时,检测SD_CHECK_PIN管脚为高电平,此时第二个if条件成立,执行"_sdcard_unmount()",卸载完sd卡后我加了一个卸载事件发送,告知sd卡应用线程,此时sd卡已拔出,关闭所有对sd卡的操作。

在上电后插卡时,检测SD_CHECK_PIN管脚为高电平,此时第一个if条件成立,执行"_sdcard_mount()",挂载完sd卡后我加了一个挂载事件发送,告知sd卡应用线程,此时sd卡已插入,可以对sd卡进行操作。

3、SD卡挂载函数_sdcard_mount()代码如下:

static rt_int32_t _sdcard_mount(void)
{rt_device_t device;device = rt_device_find("sd0");if (device == RT_NULL){if (sd_device_register() != RT_EOK) return -RT_ERROR;device = rt_device_find("sd0");}if (device != 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;}}return -RT_ERROR;
}

这里主要就是sd卡的挂载,当发现sd0设备找不到的时候,执行sd_device_register()注册设备,注册完之后find sd0设备,然后mount该sd0设备到dfs虚拟文件系统中。

4、SD卡卸载函数_sdcard_unmount()代码如下:

static rt_int32_t _sdcard_unmount(void)
{rt_thread_mdelay(200);if (dfs_unmount("/") != RT_EOK) return -RT_ERROR;LOG_I("Unmount \"/\"");if (sd_device_remove() != RT_EOK) return -RT_ERROR;return RT_EOK;
}

这里主要就是sd卡的卸载,先卸载文件系统,再移除sd0设备。

5、sd_device_register()

rt_err_t sd_device_register(void)
{rt_hw_sdcard_init();return RT_EOK;
}

sd设备驱动就是调用rt_hw_sdcard_init()进行初始化的,检测到重新插卡后就再次初始化一下sd卡。

6、sd_device_remove()

rt_err_t sd_device_remove(void)
{rt_sem_detach(&sd.sem);rt_mutex_detach(&sd.sd_lock);rt_device_unregister(&sd.sdcard_device);return RT_EOK;
}

当时调试的时候,这里导致了一个ASSERT错误,查了一天才找到原因。。。
这里需要分离semaphore和mutex,然后unregister设备。一开始我没有直接分离semaphore和mutex,导致拔卡之后再插卡就提示ASSERT错误,然后系统奔溃。
为啥是分离semaphore和mutex而不是删除,因为在rt_hw_sdcard_init()里面初始化了静态的semaphore和mutex。如果是创建的动态semaphore和mutex,则是执行删除,然后rt_free内存,否则也是会运行出错。

7、运行效果如下
在这里插入图片描述

四、延展之ASSERT ERROR,即RT-Thread断言错误

在这次调试中,有遇到ASSERT错误,导致系统直接死机。即我前面说到的未删除原来初始化的信号量及互斥量,再次初始化时导致ASSERT错误。
当时错误的提示:
在这里插入图片描述
RT-Thread系统还挺好用的,直接把错误给打印出来了,而且还很详细。首先obj != object是断言的内容,在rt_object_init函数里面断言出错的,而且告知了是在383行!我理解的就是:初始化object的时候,系统检测初始化的object和obj列表中的一致,意思是已经初始化了、已经存在该对象信息。
查看代码找到ASSERT错误的地方:
在这里插入图片描述

常见RT-Thread ASSERT错误原因:
1、使用的对象未创建或初始化,如:事件、信号量、消息队列等等。
2、对象还未初始化,在别的线程调用了,如:rt_sem_take等等。
3、初始化函数使用错误,init是静态初始化,先定义好控制块存储地址;create是动态初始化,由动态内存分配,create返回的是控制块的句柄,即指针。
4、RT-Thread默认只需要init一次内核对象,第二次默认不初始化。对未删除或分离的对象,再次初始化也会导致assert错误。

五、延展之STM32 SD卡热插拔检测

最初是使用ST的bsp库来开发sd卡功能的,但是发现在sd卡初始化的时候,读取scr寄存器一直没响应,导致sd卡初始化失败。随后在网上找到一种解决办法:

在这里插入图片描述
在rthw_sdio_send_command()里,去掉接收等待。
在cmd51发送完之后,hw_sdio->sta的值为0x20a400,与上HW_SDIO_IT_RXACT为真,导致超时,此时判断始终返回错误。注释掉cm->err = -RT_ERROR后,挂载成功,可以读写sd卡文件。目前尝试二十几次,均正常。现在始终不明白是读寄存器错误还是其他原因。 同样的程序在st下运行,sta返回值是0x400。

但是我调试发现偶尔有那么一两次能成功,但是概率很低,几乎99.9%初始化失败。对比找了两个芯片的驱动差异,没找到具体原因。。。最后无奈用gd32f470的bsp工程来调试sd卡,发现env配置好下载到板子里就能挂载sd设备!

用STM32的芯片驱动实现SD卡热插拔功能,原理一样,只是驱动库不一样,其实就是_sdcard_mount()和_sdcard_unmount()不一样,具体看代码:

1、_sdcard_mount()

static void _sdcard_mount(void)
{rt_device_t device;device = rt_device_find("sd0");if (device == NULL){mmcsd_wait_cd_changed(0);stm32_mmcsd_change();mmcsd_wait_cd_changed(RT_WAITING_FOREVER);device = rt_device_find("sd0");}if (device != RT_NULL){if (dfs_mount("sd0", "/", "elm", 0, 0) == RT_EOK){LOG_I("sd card mount to '/'");}else{LOG_W("sd card mount to '/' failed!");}}
}

2、_sdcard_unmount()

static void _sdcard_unmount(void)
{rt_thread_mdelay(200);dfs_unmount("/");LOG_I("Unmount \"/\"");mmcsd_wait_cd_changed(0);stm32_mmcsd_change();mmcsd_wait_cd_changed(RT_WAITING_FOREVER);
}

stm32_mmcsd_change()是drv_sdio里面提供的一个接口,该函数实际是调用mmcsd_change()函数,触发mmcsd检测线程,检测线程按流程检测各类卡(SD卡、MMC卡),继续查看源代码可以发现,在init_sd里面有块设备探测失败的情况,该情况下会进入remove card的流程:
在这里插入图片描述

继续查看rt_mmcsd_blk_remove做了什么:
在这里插入图片描述
可以看到这里面起始也是删除当时初始化SD卡时创建的sem,然后unregister设备,再从对象信息列表中移除之前创建的object,最后rt_free该设备块。

六、结束语

最近调试GD32F470的sd卡功能,对RT-Thread操作系统还不是很熟悉,都是不断摸索的过程,特写此博文记录一下。也给正在调试或使用RT-Thread的朋友一点帮助。文中如有描述不对的地方还望提醒纠正。

相关文章:

  • 达摩研究院Paraformer-large模型已支持windows
  • mysql服务多实例运行
  • HCIP-2
  • 访问学者申请需要注意什么?
  • 拼多多根据ID取商品详情原数据 API 实现实时数据获取的完整指南
  • 奥伦德光电耦合器5G通信领域及其相关领域推荐
  • 【动态规划】20子数组系列_环形子数组的最大和_C++(medium)
  • Linux部署WBO在线白板
  • create_metrology_model
  • MYSQL篇--sql优化高频面试题
  • 数据库系统原理总结之——数据库编程
  • 贝叶斯优化的基本流程
  • 做一个个人博客第一步该怎么做?
  • GPT-4:人工智能的新纪元与未来的无限可能
  • 002 Golang-channel-practice
  • 07.Android之多媒体问题
  • CODING 缺陷管理功能正式开始公测
  • CSS相对定位
  • js继承的实现方法
  • Perseus-BERT——业内性能极致优化的BERT训练方案
  • python学习笔记-类对象的信息
  • webpack+react项目初体验——记录我的webpack环境配置
  • 从0搭建SpringBoot的HelloWorld -- Java版本
  • 缓存与缓冲
  • 聊一聊前端的监控
  • 爬虫进阶 -- 神级程序员:让你的爬虫就像人类的用户行为!
  • 如何使用Mybatis第三方插件--PageHelper实现分页操作
  • 听说你叫Java(二)–Servlet请求
  • MiKTeX could not find the script engine ‘perl.exe‘ which is required to execute ‘latexmk‘.
  • raise 与 raise ... from 的区别
  • #FPGA(基础知识)
  • #数学建模# 线性规划问题的Matlab求解
  • #我与Java虚拟机的故事#连载03:面试过的百度,滴滴,快手都问了这些问题
  • (32位汇编 五)mov/add/sub/and/or/xor/not
  • (cos^2 X)的定积分,求积分 ∫sin^2(x) dx
  • (搬运以学习)flask 上下文的实现
  • (附源码)springboot 智能停车场系统 毕业设计065415
  • (十五)使用Nexus创建Maven私服
  • (转)程序员技术练级攻略
  • .NET Core6.0 MVC+layui+SqlSugar 简单增删改查
  • .NET Remoting Basic(10)-创建不同宿主的客户端与服务器端
  • .NET 表达式计算:Expression Evaluator
  • .NET 除了用 Task 之外,如何自己写一个可以 await 的对象?
  • .NET基础篇——反射的奥妙
  • :“Failed to access IIS metabase”解决方法
  • @property python知乎_Python3基础之:property
  • [ 常用工具篇 ] AntSword 蚁剑安装及使用详解
  • [2010-8-30]
  • [BJDCTF 2020]easy_md5
  • [BUUCTF NewStarCTF 2023 公开赛道] week4 crypto/pwn
  • [bzoj1006]: [HNOI2008]神奇的国度(最大势算法)
  • [C++ 从入门到精通] 12.重载运算符、赋值运算符重载、析构函数
  • [C语言]——分支和循环(4)
  • [DM复习]Apriori算法-国会投票记录关联规则挖掘(上)
  • [EFI]Atermiter X99 Turbo D4 E5-2630v3电脑 Hackintosh 黑苹果efi引导文件