报价
HOME
报价
正文内容
sd nand驱动 rt-thread SDIO驱动框架分析(SD卡flash驱动SD Nand flash驱动)
发布时间 : 2024-10-12
作者 : 小编
访问数量 : 23
扫码分享至微信

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 github开源地址:https://github.com/RT-Thread/rt-thread

本文基于代码仓库 rt-thread/bsp/stm32/stm32f103-fire-arbitrary 分析代码

分支:maincommit: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 hsdstm32 sdio 设备结构体 struct stm32_sdio_des sdio_dessdio硬件外设结构体 struct rthw_sdio *sdiommc 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 << 30;

err = mmcsd_send_app_op_cond(host, ocr, RT_NULL); /* 发送ACMD41(ACMD41 = CMD55+CMD41)指令,发送主机容量支持信息,并询问卡的操作条件 */

if (err)

goto err;

if (controller_is_spi(host)) /* 判断是否使用SPI方式访问SD卡 */

err = mmcsd_get_cid(host, resp); /* 采用SPI方式获取CID寄存器值 */

else

err = mmcsd_all_get_cid(host, resp);/* 发送CMD2命令,获取CID寄存器值 */

if (err)

goto err;

card = rt_malloc(sizeof(struct rt_mmcsd_card)); /* 创建rt_mmcsd_card结构体,用于存储对应SD卡的CID寄存器内容 */

if (!card)

{

LOG_E("malloc card failed!");

err = -RT_ENOMEM;

goto err;

}

rt_memset(card, 0, sizeof(struct rt_mmcsd_card));

card->card_type = CARD_TYPE_SD;

card->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设备操作)方法二:将SD nand通过读卡器,插入电脑,在电脑上进行格式化U盘操作,不过此操作需要SD nand的转接板

7. 总结

以上便是SD卡的识别与初始化流程,整体流程简单的梳理一下,大致如下:由 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的识别与初始化流程顺利完成

相关文章推荐:

(点击跳转)SD Nand 与 SD卡 SDIO模式应用流程(点击跳转)SD nand与SD卡 SPI模式驱动

SD nand 与 SD卡的SPI模式驱动

文章目录

SD nand 与 SD卡的SPI模式驱动

1. 概述

2. SPI接口模式与SD接口模式区别

2.1 接口模式区别

2.2 硬件引脚

2.3 注意事项

3. SD接口协议

3.1 命令

3.1.1 命令格式

3.1.2 命令类型

3.2 响应

3.2.1 响应格式

4. SD nand(SD卡)结构描述

5. SD nand SPI通讯

5.1 SD nand SPI 通讯概述

5.2 SPI 时序

5.3 上电初始化及模式切换

5.3.1 初始化及模式切换流程说明

5.3.2 代码实现

5.4 识别过程

5.4.1 识别流程说明

5.4.2 代码实现

5.3 数据传输

5.3.1 数据写入

5.3.2 数据读取

5.3.3 代码实现

6. 总结

1. 概述

首先简单介绍下SD卡和SD nand:

SD卡,也称之为内存卡,在早些年间的手机上出现过,主要用来存储数据; SD nand,贴片式SD卡,使用起来和SD卡一致,不同的是采用,通常采用LGA-8封装,尺寸为8mm x 6mm x 0.75mm,重点是采用贴片封装,可以直接贴在板卡上,直接解决了SD卡固定问题,再也不用为SD卡的接触稳定性操心! SD nand 与 SD卡除了封装上的区别,使用起来基本没什么不一样,因此下文中不再做区分,统一以SD nand作为描述。SD nand 和 SD 卡、SPI Nor flash、 nand flash、eeprom一样,都是嵌入式系统中常见的用来存储数据所使用的存储芯片,这几种存储芯片主要的区别在于存储数据容量不一样、操作的大小不一样,价格不一样,因此在实际产品设计中,往往需要根据自身产品的需求来选择对应的存储芯片。SD nand存储空间大小在上述存储系列芯片中属于偏大的,其存储空间小到 1Gb(256MB) 起步,大到可以到32G,最小读写单元通常是 512 Byte,与SD卡一样,均支持SD接口模式以及SPI接口模式(后文会详细描述其区别)。关于采用SPI接口模式完成于SD nand和SD卡的通讯,网上也有相关资料,但描述均不是很清楚或完整,因此特整理此博客,以作记录及分享。本博文以 CS 创世 CSNPGCR01-AOW 这颗IC为例,着重描述如何通过SPI接口完成SD nand(SD卡)的读写驱动。2. SPI接口模式与SD接口模式区别2.1 接口模式区别SD nand同时支持SPI接口和SD接口,接下来主要从以下几个维度分析二者的区别:硬件资源角度:SD接口需要控制器具有SDIO外设硬件支持SPI接口如果控制器具有SPI硬件外设那就最好了,没有也可以使用软件模式SPI传输效率:SD接口支持四线同时传输SPI只有MOSI一根总线且接口速度上SD接口速度通常要大于SPI接口,因此SD效率远高于SPI接口控制难度:SPI协议比较简单,也是嵌入式开发中最常使用的协议之一,只有MISO和MOSI两根数据总线,因此控制难度简单;SD协议相对SPI要复杂,且需要控制的引脚多,内部还存在状态机,相比SPI较为复杂综上分析,SD接口效率更高,但是需要芯片有对应外设支持,而SPI接口虽然效率比不上SD接口,但是控制起来简单,且对芯片外设硬件依赖不高,对于低端的控制器,亦可使用软件模式SPI来驱动SD nand。2.2 硬件引脚SD nand以及SD 卡在SPI接口以及SD接口模式下,硬件引脚如下图所示:SD nand SPI接口及SD接口模式IO定义 SD卡 SPI接口及SD接口模式IO定义 2.3 注意事项此外对于使用SPI接口需要注意的是,SPI接口只是定义了物理传输层,并没有定义完整的数据传输协议,因此上层软件还是需要遵循SD接口协议!3. SD接口协议在2.3中我们重点强调了,SPI接口只是定义了物理层,也即硬件链路层,关于协议层并没有定义,写一次依旧遵循SD接口协议,因此我们需要首先了解下SD总线协议的内容。SD 总线协议由SD卡协议定义,是一个通用的标准协议。首先说明的是,SD总线协议不仅仅只适用于SD卡,还支持IO卡,MMC卡等等,而且对这些不同类型的设备均能做出区分的!有点像USB一样牛逼! 我们首先来了解下SD总线协议中的命令及响应。

3.1 命令

命令由主机发出,分为广播命令和寻址命令广播命令是针对与SD主机连接的所有设备发出的寻址命令是指定某个地址的设备进行命令传输3.1.1 命令格式命令由48bit位(6字节)组成,格式如下: 起始位:1bit 固定为0传输位:1bit 主要用于区分传输方向,1代表主机发送给从机的命令,0代表从机响应的主机命令命令号:6bit 命令号索引,总共能表示2^6=64个命令命令参数:32bit 命令所包含的参数信息CRC7:7bit CRC校验位,用于保证数据传输的正确性,生成器多项式为:G(x) = x^7 + x^3 + 13.1.2 命令类型命令主要有4种类型:bc:无响应广播命令bcr:有响应广播命令ac:寻址命令,发送到选定卡,DAT线没有数据传输adtc:寻址数据传输命令,发送到选定的卡,且DAT线有数据传输在SD总线协议中,经常见到的CMDx,代表的就是命令号,后面的x代表命令索引,在3.1.1中命令格式组成中描述了命令号总共占6bit,所以CMDx的范围是CMD0 - CMD63,CMD后面的数字代表的就是命令号command index的值。对于SD这么复杂的协议,64种命令类型通常还不能涵盖所有类型的数据,因此SD协会在制定此协议的时候将命令继续细化,分了两种类型的命令:CMD和ACMD,CMD代表常规命令,ACMD代表特定应用的命令,ACMD通常为制造商特定使用的。那么SD协议又是如何区分CMD和ACMD命令的呢?在发送ACMD命令之前必须发送特定的CMD命令(APP_CMD)表明接下来的一帧命令是ACMD命令,在SD协议种规定此特定命令名称叫APP_CMD,也就是CMD55。需要注意的是,CMD命令类型这么多,但实际上并没有都使用,针对SD nand(SD卡)的命令也就那么几条(注意SD模式命令的响应和SPI模式命令的响应有些许不同,SD模式请自行查阅手册) 上图中,命令序号对应3.1.1节命令格式中的命令号 command index,参数对应3.1.1节命令格式中的命令参数argument。3.2 响应针对需要响应的命令(bcr),SD nand(SD卡)在接收到命令之后会做出响应,根据命令的不同,响应的类型也不相同,其中命令中已规定哪个命令需要响应,并且返回什么类型的响应。响应总共分为7中类型,分别是R1~R7,需要注意的是,SD nand(SD卡)没有R4、R5类型的响应。响应的数据长度也并非完全一样,响应根据内容长度分为短响应和长响应,短响应长度为48bit(6Byte),长响应长度为136bit(17Byte),其中只有R2属于长响应,其他均属于短响应。3.2.1 响应格式 其中重点讲下R1响应,在上图中我们可以看到R1返回的内容为卡的状态,关于卡状态的描述如下,每个bit均代表着对应的含义,如下图中所示: 上图是SD nand的内部结构,与SD卡完全类似,主要有五个部分组成,这里就不细述了,不然此篇文章会过于臃长,关于这块大家可以上网查找,需要重点注意的是内部有7个寄存器,主要用来对卡片进行配置和读取卡片有关的信息,描述如下,其中SD接口有些命令就指定了读取哪个寄存器的内容!

5. SD nand SPI通讯

主要参考资料:官方文档《Part_1_Pjysical_Layer_Specification_Ver2.0.0pdf》建议大家有时间的话也可以读一读,还是有收获的,如果没时间的话也可以先参考本博文5.1 SD nand SPI 通讯概述SD nand SPI通讯接口完成驱动主要可以分为三大部分:上电初始化以及模式切换SD nand(SD卡)识别数据传输两大步在以上三大部分中,每个部分均有命令传输,从3.1.1中我们可以知道发送给SD nand的命令为48bit,也就是8字节,那么SPI模式下与SD nand通讯,发送命令其实就是采用SPI总线往SD nand传输8个字节的数据,大家把握这这个思路去理解下文的通讯过程也就简单多了。需要注意的是:SD nand或SD卡上电默认均为SD模式,需要对齐完成初始化以及模式切换后才能切换到SPI模式。SD 模式,所有命令默认开启CRC校验,因此没有切换到SPI模式之前,所有命令都必须携带正确的CRC校验值进入SPI模式后,默认关闭CRC校验,此时CRC校验字段默认填充1即可,当然也可以通过命令配置打开SPI模式的CRC校验5.2 SPI 时序在开始进行通讯读写前,我们先来看下SPI时序,使用SPI完成于SD nand(SD卡)的通讯与我们平常使用SPI与其他设备通讯会有一点点小小的区别,主要在于往SD nand写了数据之后,回复不是马上的,以及在必要的数据之间需要增加间隔,我们挑几个重点看下,在实际开发中有需要注意的在后文对应处有描述,不用过于担心。1.主机发送命令给卡,卡响应,注意图中的NCR,NCR最小不是0,因此主机发送了命令之后,SD nand不是马上就响应的 2.卡连续响应两个指令之间需要有间隔,如图中的NRC 5.3 上电初始化及模式切换5.3.1 初始化及模式切换流程说明首先配置控制器SPI外设SD nand(SD卡)电源应该在250ms内到大VCC,这是硬件电路要求同时保持CS引脚为高电平状态,CLK时钟引脚至少发送74个时钟给SD nand已启动SD nand之后SD nand进入空闲状态,发送CMD0命令至SD卡切换进入SPI模式注意务必保证CMD0是第一包命令SD卡选择了对应的模式之后不可切换,如果需要重新切换,需要重新上电 5.3.2 代码实现1.SPI外设配置代码如下:#ifndef __BSP_SPI_H__#define __BSP_SPI_H__#include "stm32f10x.h"#define PIN_HIGH 1#define PIN_LOW 0int sd_spi_config(void);void set_sd_spi_cs_pin(uint8_t state);#endif /* __BSP_SPI_H__ */

#include "./spi/bsp_spi.h"

/**

* @brief spi gpio configuration

*

* @note CLK:PA5 MISO:PA6 MOSI:PA7 CS:PA8

*

*/

static void _spi_gpio_init(void)

{

GPIO_InitTypeDef GPIO_InitStructure = {0};

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

/* Configure SD_SPI pins: SCK */

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;

GPIO_Init(GPIOA, &GPIO_InitStructure);

/* Configure SD_SPI pins: MOSI */

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;

GPIO_Init(GPIOA, &GPIO_InitStructure);

/* Configure SD_SPI pins: MISO */

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;

GPIO_Init(GPIOA, &GPIO_InitStructure);

/*!< Configure SD_SPI_CS_PIN pin: SD Card CS pin */

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;

GPIO_Init(GPIOA, &GPIO_InitStructure);

}

/**

* @brief configer spi1 peripher.

*

* @note Data rising edge acquisition.

*/

static void _spi_config(void)

{

SPI_InitTypeDef SPI_InitStructure = {0};

RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);

/*!< SD_SPI Config */

SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;

SPI_InitStructure.SPI_Mode = SPI_Mode_Master;

SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;

SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;

SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;

SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;

SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;

SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;

SPI_InitStructure.SPI_CRCPolynomial = 0;

SPI_Init(SPI1, &SPI_InitStructure);

SPI_Cmd(SPI1, ENABLE);

}

int sd_spi_config(void)

{

_spi_gpio_init();

_spi_config();

return 0;

}

void set_sd_spi_cs_pin(uint8_t state)

{

if (state)

GPIO_SetBits(GPIOA, GPIO_Pin_8);

else

GPIO_ResetBits(GPIOA, GPIO_Pin_8);

}

2.SD初始化代码如下,set_sd_to_idle_state 函数向SD nand发送CMD0指令,同时由于发送CMD0时,SD nand还处于SD模式,因此手动计算CRC结果为0x95并发送,发送完CMD0之后等待SD nand的R1响应,并根据响应内容,知道SD nand操作完成。

#ifndef __SD_SPI_DRV_H__

#define __SD_SPI_DRV_H__

#include "stm32f10x.h"

/**

* @brief Commands: CMDxx = CMD-number | 0x40

*/

#define SD_CMD_GO_IDLE_STATE 0 /*!< CMD0 = 0x40 */

#define SD_CMD_SEND_OP_COND 1 /*!< CMD1 = 0x41 */

#define SD_CMD_SEND_IF_COND 8 /*!< CMD8 = 0x48 */

#define SD_CMD_SEND_CSD 9 /*!< CMD9 = 0x49 */

#define SD_CMD_SEND_CID 10 /*!< CMD10 = 0x4A */

#define SD_CMD_STOP_TRANSMISSION 12 /*!< CMD12 = 0x4C */

#define SD_CMD_SEND_STATUS 13 /*!< CMD13 = 0x4D */

#define SD_CMD_SET_BLOCKLEN 16 /*!< CMD16 = 0x50 */

#define SD_CMD_READ_SINGLE_BLOCK 17 /*!< CMD17 = 0x51 */

#define SD_CMD_READ_MULT_BLOCK 18 /*!< CMD18 = 0x52 */

#define SD_CMD_SET_BLOCK_COUNT 23 /*!< CMD23 = 0x57 */

#define SD_CMD_WRITE_SINGLE_BLOCK 24 /*!< CMD24 = 0x58 */

#define SD_CMD_WRITE_MULT_BLOCK 25 /*!< CMD25 = 0x59 */

#define SD_CMD_PROG_CSD 27 /*!< CMD27 = 0x5B */

#define SD_CMD_SET_WRITE_PROT 28 /*!< CMD28 = 0x5C */

#define SD_CMD_CLR_WRITE_PROT 29 /*!< CMD29 = 0x5D */

#define SD_CMD_SEND_WRITE_PROT 30 /*!< CMD30 = 0x5E */

#define SD_CMD_SD_ERASE_GRP_START 32 /*!< CMD32 = 0x60 */

#define SD_CMD_SD_ERASE_GRP_END 33 /*!< CMD33 = 0x61 */

#define SD_CMD_UNTAG_SECTOR 34 /*!< CMD34 = 0x62 */

#define SD_CMD_ERASE_GRP_START 35 /*!< CMD35 = 0x63 */

#define SD_CMD_ERASE_GRP_END 36 /*!< CMD36 = 0x64 */

#define SD_CMD_UNTAG_ERASE_GROUP 37 /*!< CMD37 = 0x65 */

#define SD_CMD_ERASE 38 /*!< CMD38 = 0x66 */

#define SD_CMD_READ_OCR 58 /*!< CMD58 */

#define SD_CMD_APP_CMD 55 /*!< CMD55 返回0x01*/

#define SD_ACMD_SD_SEND_OP_COND 41 /*!< ACMD41 返回0x00*/

typedef enum {

/**

* @brief SD reponses and error flags

*/

SD_RESPONSE_NO_ERROR = (0x00),

SD_IN_IDLE_STATE = (0x01),

SD_ERASE_RESET = (0x02),

SD_ILLEGAL_COMMAND = (0x04),

SD_COM_CRC_ERROR = (0x08),

SD_ERASE_SEQUENCE_ERROR = (0x10),

SD_ADDRESS_ERROR = (0x20),

SD_PARAMETER_ERROR = (0x40),

SD_RESPONSE_FAILURE = (0xFF),

/**

* @brief Data response error

*/

SD_DATA_OK = (0x05),

SD_DATA_CRC_ERROR = (0x0B),

SD_DATA_WRITE_ERROR = (0x0D),

SD_DATA_OTHER_ERROR = (0xFF)

} SD_ERROR;

//SD卡的类型

#define SD_TYPE_NOT_SD 0 //非SD卡

#define SD_TYPE_V1 1 //V1.0的卡

#define SD_TYPE_V2 2 //SDSC

#define SD_TYPE_V2HC 4 //SDHC

extern uint8_t SD_Type;

void sd_power_on(void);

SD_ERROR set_sd_to_idle_state(void);

SD_ERROR get_sd_card_type(void);

#endif /* __SD_SPI_DRV_H__ */

#include "./sd_nand/sd_spi_drv.h"

#include "./spi/bsp_spi.h"

#define SD_SPI SPI1

#define SD_DUMMY_BYTE 0xFF

uint8_t SD_Type = 0;

static uint8_t _spi_read_write_byte(uint8_t data)

{

while(SPI_I2S_GetFlagStatus(SD_SPI, SPI_I2S_FLAG_TXE) == RESET);

SPI_I2S_SendData(SD_SPI, data);

while(SPI_I2S_GetFlagStatus(SD_SPI, SPI_I2S_FLAG_RXNE) == RESET);

return SPI_I2S_ReceiveData(SD_SPI);

}

static void sd_send_cmd(uint8_t cmd, uint32_t arg, uint8_t crc)

{

uint8_t data[6] = {0};

/* command bit7 is always 1, bit6 is always 0, see SD manual. */

data[0] &= ~(0x80);

data[0] = cmd | 0x40;

data[1] = (uint8_t)(arg >> 24);

data[2] = (uint8_t)(arg >> 16);

data[3] = (uint8_t)(arg >> 8);

data[4] = (uint8_t)(arg);

data[5] = crc;

for (int i = 0; i < 6; i ++)

_spi_read_write_byte(data[i]);

}

static uint8_t sd_read_response(uint8_t response)

{

uint32_t repeat = 0xfff;

while (repeat --) {

if (_spi_read_write_byte(SD_DUMMY_BYTE) == response)

break;

}

if (repeat)

return SD_RESPONSE_NO_ERROR;

else

return SD_RESPONSE_FAILURE;

}

void sd_power_on(void)

{

set_sd_spi_cs_pin(PIN_HIGH);

uint32_t i = 0;

for (i = 0; i <= 9; i++) {

_spi_read_write_byte(SD_DUMMY_BYTE);

}

}

SD_ERROR set_sd_to_idle_state(void)

{

uint32_t repeat = 0xfff;

set_sd_spi_cs_pin(PIN_LOW);

sd_send_cmd(SD_CMD_GO_IDLE_STATE, 0, 0x95);

if (sd_read_response(SD_IN_IDLE_STATE)) //查询卡是否处于空闲状态

return SD_RESPONSE_FAILURE;

set_sd_spi_cs_pin(PIN_HIGH);

_spi_read_write_byte(SD_DUMMY_BYTE); //释放卡

if (repeat == 0)

return SD_RESPONSE_FAILURE;

else

return SD_RESPONSE_NO_ERROR;

}

5.4 识别过程

SD nand的识别过程颇为复杂,需要参考下图所示状态机。

其复杂的原因是,随着科技的发展,SD卡也迭代了好几轮,但是协议需要兼容所有版本的卡,因此看上去会复杂很多。

我们采用的SD nand 型号为 CSNPGCR01-AOW,为V2.0.0的卡,且容量为1Gb,因此整体识别路线为中间那条线路。

5.4.1 识别流程说明

V2.0卡识别流程:

1.SD nand上电首先完成初始化,并发送CMD0配置为SPI模式

2.之后发送CMD8命令,读取R7响应,判断SD nand的版本

如果响应值为0x01则判断为V2.0的卡(此时是这个)

如果响应值非0x01则需要进一步判断时V1.0的卡还是MMC卡

3.发送循环指令CMD55+ACMD41,(CMD55用来表示后面的CMD41为ACMD命令),读取R1响应,直到响应0x00表示SD 2.0卡初始化完成

4.发送CMD58命令,读取R3响应,R3中包含OCR寄存器的值,OCR寄存器的第31位(bit30)描述了此卡类型是否为SDHC类型,根据此位判断此卡属于标准容量卡还是高容量卡

V1.0卡识别流程:

1.SD nand上电首先完成初始化,并发送CMD0配置为SPI模式

2.之后发送CMD8命令判断SD nand的版本

如果响应值为0x01则判断为V2.0的卡

如果响应值非0x01则需要进一步判断时V1.0的卡还是MMC卡(此时是这个)

3.发送CMD58命令,并判断响应值R3,如果没有返回则不是SD V1.0的卡

4.发送ACMD41(argument为置0),并判断R1响应值,直到卡空闲

关于CMD8指令,此处重点说明:

CMD8命令的参数中主要包含两个部分,Voltage Supplied(VHS)和check pattern,发送CMD8时,VHS参数应设置为主机支持的电压范围,我们的控制器通常是3.3V,因此此处设置为0001b; check pattern可以设置为任意值,当SD nand(SD卡)接收到此CMD8指令之后会返回R7响应,如果SD nand支持此电压等级,SD nand会回显 VHS 和check pattern的内容在R7中,如果SD nand不支持此电压等级,SD nand将不会返回,并始终保持在空闲状态。

5.4.2 代码实现

SD nand识别代码如下:

SD_ERROR get_sd_card_type(void)

{

uint32_t i = 0;

uint32_t count = 0xFFF;

uint8_t R7R3_Resp[4];

uint8_t R1_Resp;

set_sd_spi_cs_pin(PIN_HIGH);

_spi_read_write_byte(SD_DUMMY_BYTE);

set_sd_spi_cs_pin(PIN_LOW);

sd_send_cmd(SD_CMD_SEND_IF_COND, 0x1AA, 0x87);

/*!< Check if response is got or a timeout is happen */

while (( (R1_Resp = _spi_read_write_byte(SD_DUMMY_BYTE)) == 0xFF) && count) {

count--;

}

if (count == 0) {

/*!< After time out */

return 1;

}

//响应 = 0x05 非V2.0的卡

if(R1_Resp == (SD_IN_IDLE_STATE|SD_ILLEGAL_COMMAND)) {

/*----------Activates the card initialization process-----------*/

count = 0xfff;

do {

set_sd_spi_cs_pin(PIN_HIGH);

_spi_read_write_byte(SD_DUMMY_BYTE);

set_sd_spi_cs_pin(PIN_LOW);

/*!< 发送CMD1完成V1 版本卡的初始化 */

sd_send_cmd(SD_CMD_SEND_OP_COND, 0, 0xFF);

/*!< Wait for no error Response (R1 Format) equal to 0x00 */

if (sd_read_response(SD_RESPONSE_NO_ERROR))

break;

} while (count --);

if (count == 0) {

return 2;

}

SD_Type = SD_TYPE_V1;

//不处理MMC卡

//初始化正常

} else if (R1_Resp == 0x01) { //响应 0x01 V2.0的卡

/*!< 读取CMD8 的R7响应 */

for (i = 0; i < 4; i++) {

R7R3_Resp[i] = _spi_read_write_byte(SD_DUMMY_BYTE);

}

set_sd_spi_cs_pin(PIN_HIGH);

_spi_read_write_byte(SD_DUMMY_BYTE);

set_sd_spi_cs_pin(PIN_LOW);

if(R7R3_Resp[2]==0x01 && R7R3_Resp[3]==0xAA) { //判断该卡是否支持2.7-3.6V电压

count = 200; //支持电压范围,可以操作

do { //发卡初始化指令CMD55+ACMD41

sd_send_cmd(SD_CMD_APP_CMD, 0, 0xFF); //CMD55,以强调下面的是ACMD命令

if (sd_read_response(SD_RESPONSE_NO_ERROR)) // SD_IN_IDLE_STATE

return 3; //超时返回

sd_send_cmd(SD_ACMD_SD_SEND_OP_COND, 0x40000000, 0xFF); //ACMD41命令带HCS检查位

if (sd_read_response(SD_RESPONSE_NO_ERROR))

break;

}while(count--);

if(count == 0)

return 4; //重试次数超时

//初始化指令完成,读取OCR信息,CMD58

//-----------鉴别SDSC SDHC卡类型开始-----------

count = 200;

do {

set_sd_spi_cs_pin(PIN_HIGH);

_spi_read_write_byte(SD_DUMMY_BYTE);

set_sd_spi_cs_pin(PIN_LOW);

sd_send_cmd(SD_CMD_READ_OCR, 0, 0xFF);

if (!sd_read_response(SD_RESPONSE_NO_ERROR))

break;

} while (count--);

if(count == 0)

return 5; //重试次数超时

//响应正常,读取R3响应

/*!< 读取CMD58的R3响应 */

for (i = 0; i < 4; i++) {

R7R3_Resp[i] = _spi_read_write_byte(SD_DUMMY_BYTE);

}

//检查接收到OCR中的bit30(CCS)

//CCS = 0:SDSC CCS = 1:SDHC

if(R7R3_Resp[0]&0x40) { //检查CCS标志 {

SD_Type = SD_TYPE_V2HC;

} else {

SD_Type = SD_TYPE_V2;

}

//-----------鉴别SDSC SDHC版本卡的流程结束-----------

}

}

set_sd_spi_cs_pin(PIN_HIGH);

_spi_read_write_byte(SD_DUMMY_BYTE);

//初始化正常返回

return SD_RESPONSE_NO_ERROR;

}

5.3 数据传输

在完成卡识别之后,便进入了数据传输过程,在输出传输过程内即可完成数据的读写操作。

SD NAND单个块为512字节,擦除、读写都是以块为单位进行的,而且SD NAND可以直接写入,不需要先擦除才能写入!!!牛逼Plus吧!哈哈!

5.3.1 数据写入

数据分为单块写入和多块写入,多块写入可循环执行多块写入实现。单个块写入使用CMD24,多个块写入使用CMD25,注意此处,SD nand的操作与SD卡可能会有所不一样,在对应位置有详细描述。

单块写入步骤如下:

1.发送CMD24,读取响应值R1,判断卡无错误

2.发送写开始指令 0xFE(SD协议中未找到此描述,此应该是SD nand所特有)

3.依次传输写入数据

4.发送两个字节的CRC校验,由于SPI默认没有开启CRC,因此填充为0xFFFF

5.读取卡的状态判断是否有误,结束

5.3.2 数据读取

数据读取也分为单块读取和多块读取,多块读取可采用循环执行单块读取逻辑实现。

单块数据读取步骤如下:

1.发送CMD17,读取响应值R1,判断有无错误2.等待SD nand发送数据输出开始标志 0xFE3.依次读取数据4.多读取两位CRC值,结束 5.3.3 代码实现#define SD_START_DATA_SINGLE_BLOCK_READ 0xFE /*!< Data token start byte, Start Single Block Read */#define SD_START_DATA_MULTIPLE_BLOCK_READ 0xFE /*!< Data token start byte, Start Multiple Block Read */#define SD_START_DATA_SINGLE_BLOCK_WRITE 0xFE /*!< Data token start byte, Start Single Block Write */#define SD_START_DATA_MULTIPLE_BLOCK_WRITE 0xFD /*!< Data token start byte, Start Multiple Block Write */#define SD_STOP_DATA_MULTIPLE_BLOCK_WRITE 0xFD /*!< Data toke stop byte, Stop Multiple Block Write */SD_ERROR sd_write_block(uint8_t* pbuf, uint64_t addr, uint16_t size){uint32_t i = 0;SD_ERROR ret = SD_RESPONSE_FAILURE;//SDHC卡块大小固定为512,且写命令中的地址的单位是sectorif (SD_Type == SD_TYPE_V2HC) {size = 512;addr /= 512;}/*!< SD chip select low */set_sd_spi_cs_pin(PIN_LOW);/*!< Send CMD24 (SD_CMD_WRITE_SINGLE_BLOCK) to write multiple block */sd_send_cmd(SD_CMD_WRITE_SINGLE_BLOCK, addr, 0xFF);/*!< Check if the SD acknowledged the write block command: R1 response (0x00: no errors) */if (!sd_read_response(SD_RESPONSE_NO_ERROR)) {/*!< Send a dummy byte */_spi_read_write_byte(SD_DUMMY_BYTE);/*!< Send the data token to signify the start of the data */_spi_read_write_byte(SD_START_DATA_SINGLE_BLOCK_WRITE);/*!< Write the block data to SD : write count data by block */for (i = 0; i < size; i++) {/*!< Send the pointed byte */_spi_read_write_byte(*pbuf);/*!< Point to the next location where the byte read will be saved */pbuf++;}/*!< Put CRC bytes (not really needed by us, but required by SD) */_spi_read_write_byte(SD_DUMMY_BYTE);_spi_read_write_byte(SD_DUMMY_BYTE);/*!< Read data response */if (sd_get_data_response() == SD_DATA_OK) {ret = SD_RESPONSE_NO_ERROR;}}/*!< SD chip select high */set_sd_spi_cs_pin(PIN_HIGH);/*!< Send dummy byte: 8 Clock pulses of delay */_spi_read_write_byte(SD_DUMMY_BYTE);/*!< Returns the reponse */return ret;}SD_ERROR sd_read_block(uint8_t* pbuf, uint64_t addr, uint16_t size){uint32_t i = 0;SD_ERROR ret = SD_RESPONSE_FAILURE;//SDHC卡块大小固定为512,且读命令中的地址的单位是sectorif (SD_Type == SD_TYPE_V2HC) {size = 512;addr /= 512;}/*!< SD chip select low */set_sd_spi_cs_pin(PIN_LOW);/*!< Send CMD17 (SD_CMD_READ_SINGLE_BLOCK) to read one block */sd_send_cmd(SD_CMD_READ_SINGLE_BLOCK, addr, 0xFF);/*!< Check if the SD acknowledged the read block command: R1 response (0x00: no errors) */if (!sd_read_response(SD_RESPONSE_NO_ERROR)) {/*!< Now look for the data token to signify the start of the data */if (!sd_read_response(SD_START_DATA_SINGLE_BLOCK_READ)) {/*!< Read the SD block data : read NumByteToRead data */for (i = 0; i < size; i++) {/*!< Save the received data */*pbuf = _spi_read_write_byte(SD_DUMMY_BYTE);/*!< Point to the next location where the byte read will be saved */pbuf++;}/*!< Get CRC bytes (not really needed by us, but required by SD) */_spi_read_write_byte(SD_DUMMY_BYTE);_spi_read_write_byte(SD_DUMMY_BYTE);/*!< Set response value to success */ret = SD_RESPONSE_NO_ERROR;}}/*!< SD chip select high */set_sd_spi_cs_pin(PIN_HIGH);/*!< Send dummy byte: 8 Clock pulses of delay */_spi_read_write_byte(SD_DUMMY_BYTE);/*!< Returns the reponse */return ret;}此外,为了验证以上代码正常运行,编写简单测试程序进行测试,代码如下:int main(void){USART1_Config();LED_GPIO_Config();sd_spi_config();printf("sd card test!\n");sd_init();uint8_t tx_data[512] = {0};uint8_t rx_data[512] = {0};for (i = 0; i < 512; i ++)tx_data[i] = 512-i;sd_write_block(tx_data, 0, sizeof(tx_data));sd_read_block(rx_data, 0, sizeof(rx_data));for (i = 0; i < 512; i ++) {if (tx_data[i] != rx_data[i])break;printf("%d ", rx_data[i]);}if (i == 512) {printf("sd card 读写测试成功\n");} else {printf("sd card 读写测试失败, i:%d\n", i);}}代码运行如下,测试通过:

6. 总结

综上,便是关于使用SPI接口驱动SD nand的全部说明了,确实花费了不少时间整理说明,关于SD nand的驱动玩法还有很多,比如采用SD接口驱动,移植文件系统,导入日志系统等等,后续有机会有时间我也会继续做整理分享。希望本篇博文能帮助到你对于如何使用SPI实现SD nand的驱动也有大致清晰的了解。

相关问答

可不可以先装 驱动 再装显卡-ZOL问答

不会装显卡驱动那就自己下一个驱动精灵2010的那是傻瓜安装有用(0)回复33311115最好是装显卡自带的光碟的驱动驱动盘的驱动虽然可以用但有时用起来有些...

amd 驱动 和nvidia驱动能同时装吗?

能同时装,可以同时存在的,但是根据你安装的显卡类型,后台只能驻留其中一个比如你安装过AMD显卡,现在换NVIDIA的显卡,再安装N卡驱动,A卡驱动只要不删除就还在...

换显卡需要重新装 驱动 吗?-ZOL问答

baidu_huangbanxi呢要看你换的是什么显卡了如果你以前装的是A卡现在换了一个A卡呢么只需要升级驱动就可以了如果换的是N卡呢么驱动只有从装要不然电脑无法扫...

A卡和N卡 驱动 能同时安装在电脑里吗?

是的,A卡和N卡驱动可以同时安装在电脑中。因为A卡和N卡属于不同的品牌和型号,它们的驱动程序并不会互相冲突。而且,许多电脑用户需要同时使用A卡和N卡,以满足...

更换显卡是不是都要重新安装 驱动 ?

需要如果新显卡和老显卡都是A卡或N卡,并且原有驱动程序可以兼容新显卡,不需要重新安装显卡驱动程序。如果新显卡和老显卡不是同一种显卡,那么必须要卸载原有...

显卡 驱动 安装了 重新启动后又需要安装 卸载没用-ZOL问答

4条回答:【推荐答案】你是不是更换了显卡?就是使用了新显卡然后安装驱动。如果是,一,如果你是A卡换N卡,或者N换A,你必须卸载干净才可以,不然两个驱动有冲突,最好...

a卡和n卡的显卡 驱动 一起会不会有问题?

在大多数情况下,A卡和N卡的显卡驱动可以同时存在而不会有问题。这是因为A卡和N卡使用不同的显卡架构和驱动程序。然而,某些情况下可能会出现冲突或兼容性问题,...

win10自带的显卡 驱动 与N卡官网 驱动 有差异吗?这对显卡性能有影响吗?

如果你安装的是从系统之家或者其他网站上下载的WINDOWS10系统,安装完系统之后,系统自动匹配到你所安装显卡驱动,在实际测试中,就拿鲁大师娱乐或者是三dmax,...如...

英伟达声卡 驱动 需要安装么?

需要安装。因为声卡驱动是让计算机的音频设备正常工作的重要程序。英伟达是一个声卡品牌,其声卡驱动也需要正确安装才能发挥其正常的音频效果,否则可能会出现...

n卡 驱动 每次开机都要登录?

你好,通常情况下,NVIDIA显卡驱动在每次开机后不需要登录。但是,如果您使用的是需要登录的操作系统或者您的系统设置了需要登录才能加载驱动程序的安全策略,那...

 廖智的故事  三星note2双卡 
王经理: 180-0000-0000(微信同号)
10086@qq.com
北京海淀区西三旗街道国际大厦08A座
©2024  上海羊羽卓进出口贸易有限公司  版权所有.All Rights Reserved.  |  程序由Z-BlogPHP强力驱动
网站首页
电话咨询
微信号

QQ

在线咨询真诚为您提供专业解答服务

热线

188-0000-0000
专属服务热线

微信

二维码扫一扫微信交流
顶部