一、SDIO概述
原本SD协议是用在存储上的,后来扩展了一个叫SDIO的协议(全名:Secure Digital Input and Output),这个协议属于外设接口,使得它能连接一些外设,例如:GPS、相机、Wi-Fi、调频广播、以太网、条形码读卡器、蓝牙等,从此跳出存储这个局限; MMC 卡可以说是SD 卡的前身,现阶段已经用得很少; SD I/O 卡本身不是用于存储的卡,它是指利用SDIO 传输协议的一种外设; SD卡(Secure Digital Card)是一种广泛使用的非易失性存储器; SD卡按物理尺寸分为标准SD卡、miniSD卡和microSD卡,按容量分为SDSC、SDHC、SDXC和SDUC,按速度分为不同速度等级(如Class 10、UHS-I、V30等)。 |
二、SD卡驱动
引脚分配:
注:最好选用带插卡检测的SD卡槽,方便程序检测。 |
1、CubeMX驱动生成
SDIO配置,4线模式,时钟2分频,打开硬件流控制: 修改时钟树: |
2、添加软件包
|
确保HAL库hal_conf.h功能模块使能(CubeMX会自动解除注释): #define HAL_SD_MODULE_ENABLED 添加sdio.c硬件驱动到board.c: void HAL_SD_MspInit(SD_HandleTypeDef* sdHandle) { GPIO_InitTypeDef GPIO_InitStruct = {0}; if(sdHandle->Instance==SDIO) { /* USER CODE BEGIN SDIO_MspInit 0 */
/* USER CODE END SDIO_MspInit 0 */ /* SDIO clock enable */ __HAL_RCC_SDIO_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE(); __HAL_RCC_GPIOD_CLK_ENABLE(); /**SDIO GPIO Configuration PC8 ------> SDIO_D0 PC9 ------> SDIO_D1 PC10 ------> SDIO_D2 PC11 ------> SDIO_D3 PC12 ------> SDIO_CK PD2 ------> SDIO_CMD */ GPIO_InitStruct.Pin = GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_11 |GPIO_PIN_12; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; GPIO_InitStruct.Alternate = GPIO_AF12_SDIO; HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_2; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; GPIO_InitStruct.Alternate = GPIO_AF12_SDIO; HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
/* USER CODE BEGIN SDIO_MspInit 1 */
/* USER CODE END SDIO_MspInit 1 */ } } 修改board.h: #define BSP_USING_SDIO |
![]() |
3、SD卡读写示例
#include <rtthread.h>
#include <rtdevice.h>
#include <stdlib.h>
#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
#define SD_DEVICE_NAME "sd0"
void fill_buffer(rt_uint8_t *buff, rt_uint32_t buff_length)
{
rt_uint32_t index;
/* 往缓冲区填充随机数 */
for (index = 0; index < buff_length; index++) {
buff[index] = ((rt_uint8_t)rand()) & 0xff;
}
}
int sd_sample()
{
rt_err_t ret;
rt_device_t sd_device;
char sd_name[RT_NAME_MAX];
rt_uint8_t *write_buff, *read_buff;
struct rt_device_blk_geometry geo;
rt_uint8_t block_num;
rt_strncpy(sd_name, SD_DEVICE_NAME, RT_NAME_MAX);
/* 查找设备获取设备句柄 */
sd_device = rt_device_find(sd_name);
if (sd_device == RT_NULL) {
rt_kprintf("find device %s failed!\n", sd_name);
return RT_ERROR;
}
/* 打开设备 */
ret = rt_device_open(sd_device, RT_DEVICE_OFLAG_RDWR);
if (ret != RT_EOK) {
rt_kprintf("open device %s failed!\n", sd_name);
return ret;
}
rt_memset(&geo, 0, sizeof(geo));
/* 获取块设备信息 */
ret = rt_device_control(sd_device, RT_DEVICE_CTRL_BLK_GETGEOME, &geo);
if (ret != RT_EOK) {
rt_kprintf("control device %s failed!\n", sd_name);
return ret;
}
rt_kprintf("device information:\n");
rt_kprintf("sector size : %d byte\n", geo.bytes_per_sector);
rt_kprintf("sector count : %d \n", geo.sector_count);
rt_kprintf("block size : %d byte\n", geo.block_size);
/* 准备读写缓冲区空间,大小为一个块 */
read_buff = rt_malloc(geo.block_size);
if (read_buff == RT_NULL) {
rt_kprintf("no memory for read buffer!\n");
return RT_ERROR;
}
write_buff = rt_malloc(geo.block_size);
if (write_buff == RT_NULL) {
rt_kprintf("no memory for write buffer!\n");
rt_free(read_buff);
return RT_ERROR;
}
/* 填充写数据缓冲区,为写操作做准备 */
fill_buffer(write_buff, geo.block_size);
/* 把写数据缓冲的数据写入SD卡中,大小为一个块,size参数以块为单位 */
block_num = rt_device_write(sd_device, 0, write_buff, 1);
if (1 != block_num) {
rt_kprintf("write device %s failed!\n", sd_name);
}
/* 从SD卡中读出数据,并保存在读数据缓冲区中 */
block_num = rt_device_read(sd_device, 0, read_buff, 1);
if (1 != block_num) {
rt_kprintf("read %s device failed!\n", sd_name);
}
/* 比较写数据缓冲区和读数据缓冲区的内容是否完全一致 */
if (rt_memcmp(write_buff, read_buff, geo.block_size) == 0) {
rt_kprintf("Block test OK!\n");
} else {
rt_kprintf("Block test Fail!\n");
}
/* 释放缓冲区空间 */
rt_free(read_buff);
rt_free(write_buff);
return RT_EOK;
}
MSH_CMD_EXPORT(sd_sample, sd_sample);
int main(void)
{
while (1) {
rt_kprintf("666!\n");
rt_thread_mdelay(1000);
}
return RT_EOK;
}
4、文件系统示例
详见RT-Thread API参考手册:模块->文件系统; 注:实际情况需要考虑SD卡热插拔进行文件系统挂载/卸载; 注:挂载虚拟文件系统成功后,可在终端中使用ls等unix命令操作。 |
#include <rtthread.h>
#include <rtdevice.h>
#include <stdlib.h>
#include <dfs_fs.h>
#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
#define SD_DEVICE_NAME "sd0"
rt_err_t sdcard_init(void)
{
rt_err_t ret;
rt_device_t sd_device;
char sd_name[RT_NAME_MAX];
struct rt_device_blk_geometry geo;
rt_strncpy(sd_name, SD_DEVICE_NAME, RT_NAME_MAX);
// 查找设备获取设备句柄
sd_device = rt_device_find(sd_name);
if (sd_device == RT_NULL) {
rt_kprintf("find device %s failed!\n", sd_name);
return RT_ERROR;
}
// 打开设备
ret = rt_device_open(sd_device, RT_DEVICE_OFLAG_RDWR);
if (ret != RT_EOK) {
rt_kprintf("open device %s failed!\n", sd_name);
return ret;
}
rt_memset(&geo, 0, sizeof(geo));
// 获取块设备信息
ret = rt_device_control(sd_device, RT_DEVICE_CTRL_BLK_GETGEOME, &geo);
if (ret != RT_EOK) {
rt_kprintf("control device %s failed!\n", sd_name);
return ret;
}
rt_kprintf("device information:\n");
rt_kprintf("sector size : %d byte\n", geo.bytes_per_sector);
rt_kprintf("sector count : %d \n", geo.sector_count);
rt_kprintf("block size : %d byte\n", geo.block_size);
return RT_EOK;
}
rt_err_t dfs_elm_init(void)
{
// SD卡初始化
if(sdcard_init() != RT_EOK) {
rt_kprintf("sdcard init failed\n");
return RT_ERROR;
}
// 文件系统挂载
if(dfs_mount("sd0", "/", "elm", 0, 0) == 0) {
rt_kprintf("Filesystem initialized!\n");
return RT_EOK;
} else {
// 创建文件系统
if(dfs_mkfs("elm", SD_DEVICE_NAME) == 0) {
rt_kprintf("mkfs elm filesystem successfully!\n");
if(dfs_mount(SD_DEVICE_NAME, "/", "elm", 0, 0) == 0) {
rt_kprintf("mount to / ok\n");
return RT_EOK;
} else {
rt_kprintf("mount to / failed\n");
return RT_ERROR;
}
}
rt_kprintf("Fail to initialize filesystem\n");
return RT_ERROR;
}
}
void dfs_test(void)
{
// 创建/dir_test目录
int ret = mkdir("/dir_test", 0x777);
if (ret < 0) {
rt_kprintf("dir error!\n");
} else {
rt_kprintf("mkdir ok!\n");
DIR *dirp;
// 打开/dir_test 目录
dirp = opendir("/dir_test");
if (dirp == RT_NULL) {
rt_kprintf("open directory error!\n");
} else {
rt_kprintf("open directory OK!\n");
char s[] = "RT-Thread Programmer!", buffer[80];
rt_kprintf("Write string %s to test.txt.\n", s);
// 以创建和读写模式打开 /text.txt 文件,如果该文件不存在则创建该文件
int fd = open("/dir_test/text.txt", O_WRONLY | O_CREAT);
if (fd >= 0) {
write(fd, s, sizeof(s));
close(fd);
rt_kprintf("Write done.\n");
}
// 关闭目录
closedir(dirp);
}
}
}
MSH_CMD_EXPORT(dfs_elm_init, dfs_elm_init);
MSH_CMD_EXPORT(dfs_test, dfs_elm_init);
int main(void)
{
while (1) {
rt_kprintf("666!\n");
rt_thread_mdelay(1000);
}
return RT_EOK;
}
5、ulog日志示例
ulog 是一个非常简洁、易用的 C/C++ 日志组件,第一个字母 u 代表 μ,即微型,它能做到最低 ROM<1K, RAM<0.2K 的资源占用,ulog 不仅有小巧体积,同样也有非常全面的功能,其设计理念参考的是另外一款 C/C++ 开源日志库:EasyLogger(简称 elog),并在功能和性能等方面做了非常多的改进,主要特性如下:
|
添加软件包(使能异步输出模式,修改线程栈大小为2048): 修改后端代码(修改*backend_output函数,此函数为log运行时的处理函数): #include <rthw.h> #include <ulog.h> #define SDLOGFILE "/LOG.txt" #if defined(ULOG_ASYNC_OUTPUT_BY_THREAD) && ULOG_ASYNC_OUTPUT_THREAD_STACK < 384 #error "The thread stack size must more than 384 when using async output by thread (ULOG_ASYNC_OUTPUT_BY_THREAD)" #endif static struct ulog_backend SDLog = { 0 }; void ulog_SDLog_backend_output(struct ulog_backend *backend, rt_uint32_t level, const char *tag, rt_bool_t is_raw, const char *log, rt_size_t len) { #ifdef RT_USING_DEVICE rt_device_t dev = rt_device_find("sd0"); if (dev == RT_NULL) { rt_hw_console_output(log); } else { // 将log保存到SD卡中的日志文件 int fd = open(SDLOGFILE,O_WRONLY | O_CREAT | O_APPEND); if(fd < 0) { return; } write(fd,log,len); close(fd); } #else rt_hw_console_output(log); #endif } int ulog_SDLog_backend_init(void) { ulog_init(); SDLog.output = ulog_SDLog_backend_output; ulog_backend_register(&SDLog, "SDLog", RT_TRUE); return 0; } 如果日志格式中开启时间需要使能RTC设备(W/time: Cannot find a RTC device!,日志中非常频繁) 参考STM32片上驱动 - RTC |
示例代码: 注:实际情况需要考虑SD卡热插拔进行文件系统挂载/卸载。 dfs_unmount("/"); rt_device_close(sd_device); sd_device = RT_NULL; |
#include <rtthread.h> #include <rtdevice.h> #include <stdlib.h> #include <dfs_fs.h> #define DBG_TAG "main" #define DBG_LVL DBG_LOG #include <rtdbg.h> #define SD_DEVICE_NAME "sd0" rt_err_t sdcard_init(void) { rt_err_t ret; rt_device_t sd_device; char sd_name[RT_NAME_MAX]; struct rt_device_blk_geometry geo; rt_strncpy(sd_name, SD_DEVICE_NAME, RT_NAME_MAX); // 查找设备获取设备句柄 sd_device = rt_device_find(sd_name); if (sd_device == RT_NULL) { rt_kprintf("find device %s failed!\n", sd_name); return RT_ERROR; } // 打开设备 ret = rt_device_open(sd_device, RT_DEVICE_OFLAG_RDWR); if (ret != RT_EOK) { rt_kprintf("open device %s failed!\n", sd_name); return ret; } rt_memset(&geo, 0, sizeof(geo)); // 获取块设备信息 ret = rt_device_control(sd_device, RT_DEVICE_CTRL_BLK_GETGEOME, &geo); if (ret != RT_EOK) { rt_kprintf("control device %s failed!\n", sd_name); return ret; } rt_kprintf("device information:\n"); rt_kprintf("sector size : %d byte\n", geo.bytes_per_sector); rt_kprintf("sector count : %d \n", geo.sector_count); rt_kprintf("block size : %d byte\n", geo.block_size); return RT_EOK; } rt_err_t dfs_elm_init(void) { // 文件系统挂载 if(dfs_mount("sd0", "/", "elm", 0, 0) == 0) { rt_kprintf("Filesystem initialized!\n"); return RT_EOK; } else { // 创建文件系统 if(dfs_mkfs("elm", SD_DEVICE_NAME) == 0) { rt_kprintf("mkfs elm filesystem successfully!\n"); if(dfs_mount(SD_DEVICE_NAME, "/", "elm", 0, 0) == 0) { rt_kprintf("mount to / ok\n"); return RT_EOK; } else { rt_kprintf("mount to / failed\n"); return RT_ERROR; } } rt_kprintf("Fail to initialize filesystem\n"); return RT_ERROR; } } int main(void) { // 等待SD卡初始化完成 while (sdcard_init() != RT_EOK) { rt_thread_mdelay(100); } rt_err_t ret = dfs_elm_init(); if (ret != RT_EOK) { rt_kprintf("dfs_elm_init failed with error code %d\n", ret); return ret; } ulog_SDLog_backend_init(); while (1) { rt_kprintf("666!\n"); LOG_I("log file ok\n"); rt_thread_mdelay(1000); } return RT_EOK; } |