STM32片上驱动 - SDIO驱动

一、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卡驱动

引脚分配:

  1. PC8: SDIO_D0;
  2. PC9: SDIO_D1;
  3. PC10:SDIO_D2;
  4. PC11:SDIO_D3;
  5. PC12:SDIO_CK;
  6. PD2:SDIO_CMD。

注:最好选用带插卡检测的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),并在功能和性能等方面做了非常多的改进,主要特性如下:

  1. 日志输出的 后端多样化 ,可支持例如:串口、网络,文件、闪存等后端形式;
  2. 日志输出被设计为 线程安全 的方式,并支持 异步输出 模式;
  3. 日志系统 高可靠 ,在中断 ISR 、Hardfault 等复杂环境下依旧可用;
  4. 支持 动态/静态 开关控制全局的日志输出级别;
  5. 各模块的日志支持 动态/静态 设置输出级别;
  6. 日志内容支持按 关键词及标签 方式进行全局过滤;
  7. API 和日志格式可兼容 linux syslog ;
  8. 支持以 hex 格式 dump 调试数据到日志中;
  9. 兼容 rtdbg (RTT 早期的日志头文件)及 EasyLogger 的日志输出 API。

添加软件包(使能异步输出模式,修改线程栈大小为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;

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值