rt-thread SDIO驱动框架分析(贴片SD卡flash驱动\SD Nand flash驱动)

文章目录
rt-thread sdio驱动框架分析之sd卡驱动
1. 前言
2. sdio通用驱动框架介绍
3. 文件架构分析
4. sdio设备驱动分析
5. sdio设备驱动架构分析
6. 调试记录
7. 总结
1. 前言
rt-thread是一款国产化的嵌入式操作系统,目前在嵌入式领域得到广泛应用,其强大的扩展功能以及通用的外设驱动框架备受大家追捧。
关于基本的外设驱动,其官网上基本也都有部分描述,但是关于sdio设备驱动目前为止还没有相关文档说明,因此本文笔者将根据自己的调试使用经验,与大家分享下rtthread的通用sdio设备驱动的实现。
本文基于代码仓库 rt-thread/bsp/stm32/stm32f103-fire-arbitrary 分析代码
分支:main
commit:6808f48bdcf914f03ac757cc19b264a5d0db56de
说明:main分支会有不断更新,但是sdio驱动框架目前应该不会有大变更
硬件介绍:
控制器:stm32 基于手上为数不多的野火开发板吧
sd卡:本次采用的并非sd卡,而是创世cs家的一颗sd nand, csnp4gcr01-amw,有幸申请到了一颗样片
这里多说几句,sd nand使用起来和sd卡完全一样,而且sd nand相比sd卡感觉好用太多,贴片lga-8封装,和spi flash 差不多,完美的解决了sd卡松动导致系统不稳定的问题,而且容量又大,个人感觉以后必定是嵌入式存储应用上的主流 (除了价格贵点啥都好,哈哈)想要样片试试水的可以去找深圳雷龙公司官网申请下
2. sdio通用驱动框架介绍
首先来介绍下 sdio 通用驱动框架。
rt-thread 区别于其他操作系统,如freertos,的一大重要特征是,rt-thread 中引入了设备驱动框架,并且针对绝大多数外设基本上都已完成对应的设备驱动框架编写,所谓的设备驱动框架,也就是我们所说的建立在应用层与底层驱动层之间的中间件
如下图所示:
应用层:完成业务应用,调用通用接口操作设备驱动层
设备驱动框架层:完成外设通用驱动框架设计,脱离具体的芯片,将驱动中相同部分,如针对spi,关于spi的完整读写逻辑等抽离出来
设备驱动层:完成对应芯片的外设驱动程序编写,实现设备驱动框架层的具体接口
对于sdio外设亦是如此:
在设备驱动框架层中,实现sd卡、sdio卡、mmc卡的通用外设驱动逻辑,如卡的识别、卡的模块切换、卡的读写操作等,这些都是通用的,遵循sd标准协议;
在设备驱动层中,根据对应的硬件,完成具体芯片的sdio外设配置,并实现设备驱动框架层所需要实现的具体接口,如发送cmd命令等。
在应用层实现具体的应用,应用层与驱动层解耦
通过这种方式,这样便可以轻松的做到:
需要驱动具体的sd、sdio、mmc时,根据具体的芯片实现对应的sdio驱动接口即可
应用层可直接移植,如出现方案芯片替代时,只需完成设备驱动层适配即可
这也就是rt-thread让众多开发者疯狂追捧的重大原因了,接下来,我们将具体分析关于sd卡的具体框架层实现,关于sdio卡、mmc卡,由于使用不多,本文不做深入分析。
3. 文件架构分析首先我们先来看下sdio驱动框架有关文件及架构
sdio驱动框架文件:
sdio驱动框架文件架构:
4. sdio设备驱动分析
设备驱动与驱动框架文件在不同的目录,设备驱动一般在 bsp 目录中
通常设备驱动完成以下几个事情:
初始化具体外设有关数据结构;
完成具体外设初始化程序编写;
实现设备框架层的具体接口,如:open,read,write,close,control 等;
将具体设备注册到内核中;
需要注意的是,sdio设备驱动会有些许区别,在sdio设备驱动程序中,主要完成以下几件事:
初始化具体外设有关数据结构;
sdio外设的初始化配置;
实现设备框架层的以下几个接口:
struct rt_mmcsd_host_ops {
void (*request)(struct rt_mmcsd_host *host, struct rt_mmcsd_req *req);                
    void (*set_iocfg)(struct rt_mmcsd_host *host, struct rt_mmcsd_io_cfg *io_cfg);
    rt_int32_t (*get_card_status)(struct rt_mmcsd_host *host);
    void (*enable_sdio_irq)(struct rt_mmcsd_host *host, rt_int32_t en);
};
4.通知驱动框架层(此处demo程序默认上电前sd卡已接入);
以 rt-thread/bsp/stm32/libraries/hal_drivers/drv_sdio.c 程序为例,sdio驱动层程序从 rt_hw_sdio_init 函数开始,由于使能了自动初始化,此函数由 init_device_export(rt_hw_sdio_init); 宏实现初始化调用
(关于自动初始化如何实现的细节,可参考笔者另外一篇博文对自动初始化的详细分析:代码自动初始化(点击跳转))
在 rt_hw_sdio_init 函数中,驱动程序主要初始化以下几个结构体:
stm32外设hal库配置结构体 sd_handletypedef hsd
stm32 sdio 设备结构体 struct stm32_sdio_des sdio_des
sdio硬件外设结构体 struct rthw_sdio *sdio
mmc sd host结构体struct rt_mmcsd_host
其关系如下图所示:
结构体数据初始化完成以后,调用 mmcsd_change() 函数,触发框架层逻辑
此外,在设备驱动层提供的操作函数主要有:
static const struct rt_mmcsd_host_ops ops =
{
    rthw_sdio_request,
    rthw_sdio_iocfg,
    rthw_sd_detect,
    rthw_sdio_irq_update,
};
rthw_sdio_request 实现一次sdio数据发送
rthw_sdio_iocfg 实现sdio外设配置,注意在sd识别过程中会反复调用,不断更新sdio外设配置
rthw_sd_detect 实现获取卡的状态获取,demo里这里实际没有实现
rthw_sdio_irq_update 实现sdio外设中断的开关配置
函数调用顺序如下:
/* 函数调用顺序 */
rt_hw_sdio_init()
-> sdio_host_create(&sdio_des)
-> mmcsd_change(host)
 5. sdio设备驱动架构分析设备驱动架构层,也就是中间层,文件框架如下图所示:
我们首先来看下 mmcsd_core.c 这个文件:
rt_mmcsd_core_init() 初始化函数通过 init_prev_export(rt_mmcsd_core_init); 被初始化调用,同时初始化用于 mmc、sd、sdio检测的邮箱mmcsd_detect_mb,用于热插拔处理的 mmcsd_hotpluge_mb 以及 mmc、sd、sdio检测线程 mmcsd_detect_thread;
在线程mmcsd_detect_thread 中,等待mmcsd_detect_mb邮箱唤醒;
当sdio驱动层完成初始化话之后,通过调用 mmcsd_change(host) 函数,将mmcsd_detect_thread线程唤醒,开始进行mmc、sd卡、sdio卡的识别过程
mmcsd_core_init() 函数内容如下:
int rt_mmcsd_core_init(void)
{
    rt_err_t ret;
/* initialize detect sd cart thread */
    /* initialize mailbox and create detect sd card thread */
    ret = rt_mb_init(&mmcsd_detect_mb, mmcsdmb,
        &mmcsd_detect_mb_pool[0], sizeof(mmcsd_detect_mb_pool) / sizeof(mmcsd_detect_mb_pool[0]),
        rt_ipc_flag_fifo);
    rt_assert(ret == rt_eok);
ret = rt_mb_init(&mmcsd_hotpluge_mb, mmcsdhotplugmb,
        &mmcsd_hotpluge_mb_pool[0], sizeof(mmcsd_hotpluge_mb_pool) / sizeof(mmcsd_hotpluge_mb_pool[0]),
        rt_ipc_flag_fifo);
    rt_assert(ret == rt_eok);
     ret = rt_thread_init(&mmcsd_detect_thread, mmcsd_detect, mmcsd_detect, rt_null,
                 &mmcsd_stack[0], rt_mmcsd_stack_size, rt_mmcsd_thread_preority, 20);
    if (ret == rt_eok)
    {
        rt_thread_startup(&mmcsd_detect_thread);
    }
rt_sdio_init();
return 0;
}
init_prev_export(rt_mmcsd_core_init);
mmcsd_detect()线程以及 mmcsd_change() 函数如下:
mmcsd_detect() 函数主要负责完成 sdio卡、sd卡、mmc卡的初步识别,初步识别确认是哪种类型的卡接入之后,将会调用对应卡驱动文件(sd卡对应sd.c,sdio卡对应sdio.c,mmc卡对应mmc.c)内的初始化函数,重新完成卡的完整识别流程
如果对于sd卡识别流程不了解,建议先熟悉sd卡识别流程,参考 sd nand 与 sd卡 sdio模式应用流程(点击跳转)
具体流程见下述函数描述,对应步骤已补充注释描述
void mmcsd_change(struct rt_mmcsd_host *host)
{
    rt_mb_send(&mmcsd_detect_mb, (rt_uint32_t)host);
}
void mmcsd_detect(void *param)
{
    struct rt_mmcsd_host *host;
    rt_uint32_t  ocr;
    rt_int32_t  err;
while (1)
    {
    /* 首先等待 mmcsd_detect_mb 信号量,此信号量由 mmcsd_change() 函数发送过来 */
        if (rt_mb_recv(&mmcsd_detect_mb, (rt_ubase_t *)&host, rt_waiting_forever) == rt_eok)
        {
        /* 通过判断 host->card 确认此次操作是识别卡还是移除卡 */
        if (host->card == rt_null) /* 识别卡 */
            {
                mmcsd_host_lock(host); /* 获取锁 */
                mmcsd_power_up(host); /* 配置sdio外设电源控制器,power up, 即卡的时钟开启,同时配置sdio外设时钟为低速模式 */
                mmcsd_go_idle(host); /* 发送cmd0指令,使卡进入空闲状态 */
mmcsd_send_if_cond(host, host->valid_ocr); /* 发送cmd8命令,查询sd卡接口条件 (获取ocr寄存器) */
/*
                 * 检测sdio卡使用,sd卡不用管
                 */
                err = sdio_io_send_op_cond(host, 0, &ocr); /* 发送cmd5命令,此处是针对sdio卡使用,sd卡不会响应 */
                if (!err) /* sd卡不会响应此指令,因此此条件不会成立 */
                {
                    if (init_sdio(host, ocr))
                        mmcsd_power_off(host);
                    mmcsd_host_unlock(host);
                    continue;
                }
/*
                 * 检测sd卡使用,使用sd卡重点关注此项!!!
                 */
                err = mmcsd_send_app_op_cond(host, 0, &ocr); /* 发送acmd41指令(acmd41:cmd55+cmd41) sd卡将应答此指令 */
                if (!err)
                {
                    if (init_sd(host, ocr)) /* 此函数内完成sd卡完整的识别流程 */
                        mmcsd_power_off(host); /* 设置sdio外设,电源关闭,卡的时钟停止 */
                    mmcsd_host_unlock(host); /* 释放锁 */
                    rt_mb_send(&mmcsd_hotpluge_mb, (rt_uint32_t)host); /* 发送邮箱,通知热插拔事件 */
                    continue;
                }
/*
                 * 检测mmc卡检测使用,sd卡不用管
                 */
                err = mmc_send_op_cond(host, 0, &ocr);
                if (!err)
                {
                    if (init_mmc(host, ocr))
                        mmcsd_power_off(host);
                    mmcsd_host_unlock(host);
                    rt_mb_send(&mmcsd_hotpluge_mb, (rt_uint32_t)host);
                    continue;
                }
                mmcsd_host_unlock(host); /* 识别失败,释放锁 */
            }
            else /* 移除卡 */
            {
                /* card removed */
                mmcsd_host_lock(host); /* 获取锁 */
                if (host->card->sdio_function_num != 0)
                {
                    log_w(unsupport sdio card plug out!);
                }
                else
                {
                    rt_mmcsd_blk_remove(host->card);
                    rt_free(host->card);
host->card = rt_null;
                }
                mmcsd_host_unlock(host); /* 释放锁 */
                rt_mb_send(&mmcsd_hotpluge_mb, (rt_uint32_t)host);
            }
        }
    }
}
在 mmcsd_detect() 函数内完成sd卡的初步识别之后,之后将调用sd.c文件内的init_sd() 函数完成 sd 卡的完整识别过程
/*
 * starting point for sd card init.
 */
rt_int32_t init_sd(struct rt_mmcsd_host *host, rt_uint32_t ocr)
{
    rt_int32_t err;
    rt_uint32_t  current_ocr;
    /*
     * we need to get ocr a different way for spi.
     */
    if (controller_is_spi(host)) /* 判断是否采用spi模式访问sd卡 */
    {
        mmcsd_go_idle(host);
err = mmcsd_spi_read_ocr(host, 0, &ocr);
        if (err)
            goto err;
    }
if (ocr & vdd_165_195)
    {
        log_i( sd card claims to support the
               incompletely defined 'low voltage range'. this
               will be ignored.);
        ocr &= ~vdd_165_195;
    }
current_ocr = mmcsd_select_voltage(host, ocr); /* 配置sdio外设设置为合适的电压,对于stm32、gd32等相关控制器,实际是不支持不同等级电压配置的,所以这里可以忽略,不过你需要注意你所使用的sd卡的电源在硬件上是匹配的 */
/*
     * can we support the voltage(s) of the card(s)?
     */
    if (!current_ocr)
    {
        err = -rt_error;
        goto err;
    }
/*
     * detect and init the card.
     */
    err = mmcsd_sd_init_card(host, current_ocr); /* 完整的sd卡初始化流程在此函数内实现 */
    if (err)
        goto err;
mmcsd_host_unlock(host); /* 释放锁 */
err = rt_mmcsd_blk_probe(host->card); /* 注册块设备 */
    if (err) /* 如果注册块设备失败,将移除卡 */
        goto remove_card;
    mmcsd_host_lock(host); /* 获取锁 */
return 0;
remove_card:
    mmcsd_host_lock(host); /* 获取锁 */
    rt_mmcsd_blk_remove(host->card); /* 移除块设备 */
    rt_free(host->card); /* 释放对应的内存 */
    host->card = rt_null;
err:
log_d(init sd card failed!);
return err;
}
调用 mmcsd_sd_init_card() 函数完成sd卡检测以及初始化配置
static rt_int32_t mmcsd_sd_init_card(struct rt_mmcsd_host *host,
                                     rt_uint32_t           ocr)
{
    struct rt_mmcsd_card *card;
    rt_int32_t err;
    rt_uint32_t resp[4];
    rt_uint32_t max_data_rate;
mmcsd_go_idle(host); /* 发送cmd0,复位sd卡,使卡进入空闲模式 */
/*
     * if sd_send_if_cond indicates an sd 2.0
     * compliant card and we should set bit 30
     * of the ocr to indicate that we can handle
     * block-addressed sdhc cards.
     */
    err = mmcsd_send_if_cond(host, ocr); /* 发送cmd8指令,判断是否为v2.0或v2.0以上的卡,并获取ocr寄存器值 */
    if (!err) /* 如果是v2.0及以上版本的卡,将置为ocr的bit30位,表明主机支持高容量sdhc卡(ocr将在acmd41指令时作为参数发送给卡) */
        ocr |= 1 host = host;
    rt_memcpy(card->resp_cid, resp, sizeof(card->resp_cid));
/*
     * for native busses:  get card rca and quit open drain mode.
     */
    if (!controller_is_spi(host)) /* 如果不是采用spi方式访问sd卡 */
    {
        err = mmcsd_get_card_addr(host, &card->rca); /* 发送cmd3命令,获取rca地址 */
        if (err)
            goto err1;
mmcsd_set_bus_mode(host, mmcsd_busmode_pushpull);/* 设置cmd总线为推挽输出模式,需要注意的是,mmc卡v3.31版本以前的卡,初始化阶段,cmd总线需要为开路模式,对于sd/sd i/o卡和mmc v4.2在初始化时也使用推挽驱动 */
    }
err = mmcsd_get_csd(card, card->resp_csd); /* 发送cmd9命令,获取csd寄存器值 */
    if (err)
        goto err1;
err = mmcsd_parse_csd(card); /* 解析csd寄存器值,将解析完成的数据存放在刚刚申请的card结构体内 */
    if (err)
        goto err1;
if (!controller_is_spi(host)) /* 如果不是采用spi方式访问sd卡 */
    {
        err = mmcsd_select_card(card); /* 发送cmd7命令,选择卡 */
        if (err)
            goto err1;
    }
err = mmcsd_get_scr(card, card->resp_scr); /* 发送cmd9命令,获取scr寄存器值,并保存在刚刚申请的card结构体内 */
    if (err)
        goto err1;
mmcsd_parse_scr(card); /* 解析scr寄存器的值,并将解析结果存放在在card结构体内 */
if (controller_is_spi(host))
    {
        err = mmcsd_spi_use_crc(host, 1);
        if (err)
            goto err1;
    }
/*
     * change sd card to high-speed, only sd2.0 spec
     */
    err = mmcsd_switch(card); /* 发送cmd6指令,切换卡访问速率由默认的12.5mb/sec为25mb/sec高速接口 */
    if (err)
        goto err1;
/* set bus speed */
    max_data_rate = (unsigned int)-1;
if (card->flags & card_flag_highspeed)
    {
        if (max_data_rate > card->hs_max_data_rate)
            max_data_rate = card->hs_max_data_rate;
    }
    else if (max_data_rate > card->max_data_rate)
    {
        max_data_rate = card->max_data_rate;
    }
mmcsd_set_clock(host, max_data_rate); /* 修改sdio外设时钟速度 */
/*switch bus width*/
    if ((host->flags & mmcsd_buswidth_4) &&
        (card->scr.sd_bus_widths & sd_scr_bus_width_4)) /* 根据sd卡的scr寄存器反馈的值,判断sd卡是否支持4线宽度访问模式,如果支持则切换为4线宽度访问模式 */
    {
        err = mmcsd_app_set_bus_width(card, mmcsd_bus_width_4); /* 发送acmd6(acmd6=cmd55+cmd6)指令,通知sd卡切换为4线访问模式 */
        if (err)
            goto err1;
mmcsd_set_bus_width(host, mmcsd_bus_width_4); /* 修改sdio外设配置为4线访问模式 */
    }
host->card = card; /* 将card结构体数据与host结构体建立绑定关系 */
return 0;
err1:
    rt_free(card);
err:
return err;
}
6. 调试记录
rt-thread的sdio驱动,默认上层使用到了 elm-fatfs 文件系统,因此通常我们配置好对应的芯片的sdio驱动之后,直接就可以快速使用文件系统来操作访问sd nand了,关于文件系统的有关内容,不在此文中做过多描述,有兴趣的同学可以关注本人博客,后续将及时更新。
此外,在实际使用中有一点需要注意,当我们首次使用芯片的时候,sd nand内还未写入任何数据,此时通常是没有文件系统的,所以当一次执行之后你会见到如下错误:
这是由于sd nand内没有挂载文件系统导致,解决此问题有以下两个方法:
方法一:在命令终端使用mkfs挂载文件系统,具体命令步骤如下:
使用list_device查看sd nand对应的设备名
使用 mkfs 命令格式化sd nand:mkfs -t elm sd0 (-t 指定文件系统类型为elm-fat文件系统,对sd0设备操作)
由 drv_sdio.c 外设驱动或其他调用 mmcsd_change() 触发 mmcsd_detect() 检测
在 mmcsd_detect () 任务中,实现对sd卡、sd i/o卡、mmc卡的初步识别(发送对应卡特有命令,并判断是否正确响应),之后根据卡片类型调用不同类型卡片驱动文件内的初始化程序
如针对sd卡,则调用sd.c文件内的 init_sd() 函数完成
在init_sd()函数内调用 mmcsd_sd_init_card() 完成sd卡的完整识别流程以及初始化流程,同时同步修改sdio外设配置
sd卡初始化完成之后,调用 rt_mmcsd_blk_probe() 将sd卡注册为块设备
至此sd的识别与初始化流程顺利完成

结构体对齐在STM32中的具体体现和如何进行不同对齐方式的设置
需求迫切的医疗器械产品上市 加快了高端医疗器械国产化替代的步伐
国内新能源汽车增长迅速 但短板也非常明显
云原生计算对这三个热门市场的影响
Agilent安捷伦E4438C射频发生器6GHz
rt-thread SDIO驱动框架分析(贴片SD卡flash驱动\SD Nand flash驱动)
使用RX单片机实现数字电源控制的示例
国风设计 北通新品SWITCH游戏机收纳包发布
亚马逊旗下的Ring推出了其第一款视频门铃的更新版本
rfid在智慧医疗上可以怎样融入进去
小米发布会:相机升级的Mix2s,以及能带去上班的游戏本
未来华为智能手机将无法使用谷歌移动服务GMS
基于区块链的转账支付系统项目介绍
表面电位传感器用途与结构
没抢到小米6?没关系!5月还有一加5和魅族MX7即将上市,颜值和性能绝不输小米6
E分析:华为手机中麒麟9系列处理器套片的进化史
智能分拣机器人是如何对混合生活垃圾进行精准分选的
保留Linux内存的初始化原理及应用实战
Your hair Thinning Tips And Tricks That Work
美股IPO开启超级周期 从机构垄断到平民级服务