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的朋友一点帮助。文中如有描述不对的地方还望提醒纠正。