本帖最后由 yang377156216 于 2023-8-29 16:19 编辑
#申请原创# @21小跑堂 由于MCU本身的FLASH容量有限,而在本项目中需要存储大量的电能数据,此时则要使用外部的存储器,同时考虑到成本以及方便在其他设备上使用,microSD卡成为常用外部存储器,它具有体积小、易使用、容量大的优点,同时现有的配套软件和硬件非常齐全,能够达到开箱即用的效果。为了管理大容量的SD卡设备,还需要一个文件系统,好在RT-Thread已经为我们提供了一套完善的文件系统机制,称为Device Virtual File System(即设备虚拟文件系统),提供POSIX标准的文件操作功能(open,write,read等),完全可以使用与PC端基本一致的API调用进行文件的读写等操作。microSD卡和文件系统之间需要的驱动程序(elm-FatFs和MSD)也由RT-Thread提供了,我们几乎不需要编写代码就能够在MCU的嵌入式系统上,读写SD卡上的文件了,省去学习和调试代码的大量时间。本文将介绍在 F4A0 平台上,基于 RT-Thread bsp 框架实现使用 spi 接口对外部 SD/TF 卡的操作。 - IDE :Keil MDK 5.2X
- 硬件平台 :EV_HC32F4A0_LQFP176_Rev1.0 开发板和 16G 的 TF 卡
- Env tool :env_released_x_x_x
- 调试工具 :USB 串口线和逻辑分析仪
基础工程
本项目工程之前是基于 RT-Thread v4.0.4 版本上开发的,所以得克隆下对应版本,链接为: https://gitee.com/rtthread/rt-thread/tree/v4.0.4 。至于如何搭建 env 环境和构建基础的 F4A0 rt-thread bsp 的 MDK 工程,在此忽略,可参考上篇介绍的流程进行处理: https://bbs.21ic.com/icview-3320018-1-1.html 。
配置工程
得到基础工程后,需要在 menuconfig 环境下完成所需功能的配置。
- RT-Thread Components -> Device virtual file system 开启文件系统,并启用 Enable elm-chan fatfs 开启 elm-fatfs 文件系统
- Hardware Drivers Config -> On-chip Peripheral Drivers -> SPI Drivers 开启spi1
- RT-Thread Components -> Device Drivers -> Using SPI Bus/Device device drivers开启SPI设备驱动,并启用Using SD/TF card driver with spi选项开启spi协议的sd卡驱动支持,由于文件系统中还会需要 RTC 的支持,暂时使用软件模拟 RTC 来适配该功能,启用 Using RTC device drivers 选项和启用 Using software simulation RTC device 选项来开启 RTC 功能
到此,已经完成了所需驱动的所有配置。接下来就可以重新生成对应的 Keil 工程了,使用命令 : scons --target=mdk5 -s ,生成的工程文件为根目录下的 project.uvprojx 。
修改配置代码
由于 bsp 中未使能 spi 对应引脚的配置功能,所以需要根据硬件原理图完成更改,开发板上的连线方式是 SDIO 接口的,实际上也可以直接换成 SPI 接口的,而且加上 F4A0 具备通信端口可自由映射的超级方便的功能,这样可以直接在原 demo 板上实现一个 TF 卡既可以用 SDIO 驱动又可以用 SPI 模式驱动。根据原理图可以得到对应的通信引脚功能配置如下:
PB05 - SPI1_NSS 3线制软cs PD02 - SPI1_MOSI FUNC41 PC12 - SPI1_SCK FUNC40 PB07 - SPI1_MISO FUNC42
相应需要更改的 board_config.h 代码如下:
#if defined(BSP_USING_SPI1)
#define SPI1_NSS_PORT (GPIO_PORT_B)
#define SPI1_NSS_PIN (GPIO_PIN_05)
#define SPI1_NSS_GPIO_FUNC (GPIO_FUNC_0_GPO)
#define SPI1_SCK_PORT (GPIO_PORT_C)
#define SPI1_SCK_PIN (GPIO_PIN_12)
#define SPI1_SCK_GPIO_FUNC (GPIO_FUNC_40_SPI1_SCK)
#define SPI1_MOSI_PORT (GPIO_PORT_D)
#define SPI1_MOSI_PIN (GPIO_PIN_02)
#define SPI1_MOSI_GPIO_FUNC (GPIO_FUNC_41_SPI1_MOSI)
#define SPI1_MISO_PORT (GPIO_PORT_B)
#define SPI1_MISO_PIN (GPIO_PIN_07)
#define SPI1_MISO_GPIO_FUNC (GPIO_FUNC_42_SPI1_MISO)
#endif
接着创建 spi_tfcard.c 应用代码层源文件,主要是在整个 component 初始化时触发调用 spi msd 驱动设备的初始化,详细见下面代码:
void sd_mount(void *parameter)
{
while (1)
{
rt_thread_mdelay(500);
if(rt_device_find("sd0") != RT_NULL)
{
if (dfs_mount("sd0", "/", "elm", 0, 0) == RT_EOK)
{
LOG_I("sd card mount to '/'");
break;
}
else
{
LOG_W("sd card mount to '/' failed!");
}
}
}
}
int hc32_sdcard_mount(void)
{
rt_thread_t tid;
tid = rt_thread_create("sd_mount", sd_mount, RT_NULL,
1024, RT_THREAD_PRIORITY_MAX - 2, 20);
if (tid != RT_NULL)
{
rt_thread_startup(tid);
}
else
{
LOG_E("create sd_mount thread err!");
}
return RT_EOK;
}
INIT_APP_EXPORT(hc32_sdcard_mount);
static int rt_hw_spi1_tfcard(void)
{
hc32_hw_spi_device_attach("spi1", "spi10", SPI1_NSS_PORT, SPI1_NSS_PIN);
LOG_I("hw_spi_device_attach ok!\r\n");
return msd_init("sd0", "spi10");
}
INIT_DEVICE_EXPORT(rt_hw_spi1_tfcard);
似乎到这里基本就完成了所有代码的编辑了。好,现在是踩坑时刻,编译运行起来后串口输出没有正确 mount 的信息,得一步步 debug 了。
首先一个坑,msd_init 函数调用后一直无法正确触发回调到底层配置 hc32_spi_configure 函数里面来,导致 spi1 初始化未能正确完成。经过一番挣扎,终于发现到问题点:之前用的是 INIT_COMPONENT_EXPORT(rt_hw_spi1_tfcard); 需要改成 INIT_DEVICE_EXPORT(rt_hw_spi1_tfcard); 至于为什么,那要好好研究 rt-thread 了,在此不作过多讨论。
第二个坑,使用逻辑分析仪抓波形,发现 cs 引脚电平一直不变而其它引脚上有波形了。很明显这是 IO 配置的问题,一下子就定位到了问题点:需要在 board_config.c 的 hc32_board_spi_init 初始化函数中添加以下代码使能输出作用并且初始化拉高 cs:
GPIO_OE(SPI1_NSS_PORT, SPI1_NSS_PIN, Enable);
GPIO_SetPins(SPI1_NSS_PORT, SPI1_NSS_PIN);
到这里填完坑后,才真正可以实现所需要的功能了。
测试验证
编译代码并下载成功后,飞线将 PH15/PH13 连接到板载 CMSIS-Dap 端的 RX/TX 即可使用调试器枚举出的虚拟串口,开启串口工具以及抓取对应的 SPI1 波形,正常会显示如下:
另外,不再多写一句代码都可以进行测试读写 TF 卡,那就是在 msh 中使用命令实现,因为文件系统中已经自带了该功能。大致命令流程梳理如下:
- 输入list device命令并回车,可以查看设备,[size=1em]这里sd0为分区,后续会对他进行操作
- [size=1em]使用 date 命令进行时间的设置和查询,让文件系统具备时钟功能
- 初次使用的SD卡,可能没有文件系统,或者文件系统不是FAT格式的,需要使用 mkfs 命令将SD卡格式化为FAT文件系统,后续才能挂载成功
- 使用 ls、echo、cat 命令,进行文件读写操作,使用 cd、mkdir、pwd 等命令,进行文件目录的操作
当然也可以使用文件系统的 api 接口来实现文件的读写操作,这里也不做过多探讨了,因为底层都已经通了,后面可以随便造了。
|
基于 RT-Thread bsp 框架实现使用 spi 接口对外部 SD/TF 卡进行操作,解决大量数据存储问题。