主流Wifi芯片简要介绍
简介 其实,802.11n是可以达到最高600Mbps标准的,这得益于MIMO(多进多出)以及OFDM(正交频分复用)技术两项技术的应用,但是由于还存在路由器设计限制、无线网卡规格兼容性等问题,600Mbps基本还无法实现。通常你能见到的802.11n连接速度分别有108Mbps、130Mbps、240Mbps及300Mbps。 选路由用“芯”最关键 正如我们所说的,路由器也同样存在兼容性问题。目前世界上主要有这么几家供应无线路由芯片提供商,分别是Broadcom、Atheros、Ralink、Realtek和Marvell。 Ralink (雷凌,台湾) Ralink Technology公司成立于2001年,总部位于台湾新竹,并在美国加州Cupertino设有研发中心。Ralink方案是很多廉价无线路由最常见的。Ralink 产品因Wi-Fi、移动和嵌入式应用所需的出色吞吐量、扩展范围、低功耗及一致的可靠性而获得认可。目前已知的Edimax、Tenda、ASUS及D-link都有采用Ralink的产品,Ralink芯片产品主要是打低端市场,突出优点就是价格十分便宜,不少100余元的802.11n无线路由都是采用Ralink的。 Broadcom(博通,美国) Broadcom芯片是最成熟、最稳定的一种,而且还可以使用DD-WRT这种第三方开源固件改善性能,增加功能。
Atheros(创锐讯,美国) 1999年由斯坦福大学的Teresa Meng博士和斯坦福大学校长,MIPS创始人John Hennessy博士共同在硅谷创办,公司已在全球拥有超过一千名员工。该公司是基于OFDM的无线网络技术厂商,提供基于IEEE802.11a 5-GHz的芯片组,还拓展了蓝牙、GPS、以太网等领域的开发。Atheros的芯片被各大厂商所广泛采用,Netgear、D-Link、Intel等厂商均为Atheros客户。 Atheros芯片则主要是突出性能,尤其在匹配Atheros芯片无线网卡时,性能明显要好一些,只是兼容性略差,稳定性也不如Broadcom芯片产品稳定。 Realtek(瑞昱,台湾) 台湾「硅谷」的新竹科学园区
瑞昱半导体成立于1987年,位于,凭借当年几位年轻工程师的热情与毅力,走过艰辛的草创时期到今日具世界领导地位的专业IC设计公司,瑞昱半导体劈荆斩棘,展现旺盛的企图心与卓越的竞争力,开发出广受全球市场肯定与欢迎的高性能、高品质与高经济效益的IC解决方案。瑞昱半导体自成立以来一直保持稳定的成长,归功于瑞昱对产品/技术研发与创新的执着与努力,同时也归因于瑞昱的优良传统。 大事件:被收购 2011年3月16日,联发科(MTK)通过换股并购Ralink雷凌公司,将Ralink作为联发科旗下的无线技术事业群,2011年10月1日并购正式生效。 2011年2月28日,Atheros(创锐讯)已经被美国高通Qualcomm以32亿美金收购。 -------------------------------------------- (1) Wifi卡的常用接口有: –CF 接口 –USB接口 –SDIO接口 –SPI接口 –PCMCIA接口 很多时候,同一个wifi卡同时支持多种接口,譬如marvell的8686的wifi卡,既支持spi接口,也支持sdio接口. (2) SD卡与SDIO卡的异同 1.SD卡使用的是SD卡协议,而SDIO卡使用的是SDIO协议;协议不一样,初始化/读写方式都不一样 2.ARK1600控制器既支持SD卡也支持SDIO卡,在驱动上完全可以做到同一个卡槽既支持SD卡也支持SDIO卡,甚至combo卡,因此在驱动上有个判断过程,判断插进来的是SD卡还是SDIO卡 3.两者的引脚定义不一样,尽管引脚个数都一样 (3) ?SDIO协议 –相对于SD协议,SDIO协议特有的命令有:cmd5,cmd52,cmd53 –cmd5命令相当于SD卡协议中的acmd41,用于匹配SDIO卡的电压 –对于SDIO卡中的单个寄存器的读写一般都使用cmd52命令,而对于多字节数据的读写则用cmd53命令。 –cmd53分字节和块传输模式 对于字节传输模式相当于SD协议的单块读写(cmd17,cmd24) 对于块传输模式相当于SD协议的块读写(cmd18,cmd25) (4) ?Cmd53 –对于字节传输模式,它可以是1~512之间的任意长度的传输,而不是固定长度 –对于块传输模式,首先它的块长度可以人为设定,但不能超过规定的最大块长度 –相对于SD卡中的块传输模式,在最后一个块传输末尾,是不需要发送块停止命令的(cmd12) --每个SDIO卡都由1~7个function (optional)和一个memory function(mandatory)组成 ?什么是function ? –所谓function,就是一个I/O设备,它实质就是一些寄存器的集合 (5) SDIO卡里function的组织分布 ?CIA就是function0,也就是memory function,它每个SDIO卡所必须具有的,它里面包含了如下一些重要信息: –SDIO协议的版本号,BUS MOD,块大小等。这些信息有些是只读的,有些是可读可写的 ?Function1~7是可选的,根据具体的应用的不同,所包含的function数目也不一样,譬如我门使用的marvell的8686芯片,它就只有function0和function1 (6)sdio 1~4bit mode
PIN | SDIO 4-BIT MODE | SDIO 1-BIT MODE | ||
1 | CD/DAT3 | Data line 3 | N/C | Not used |
2 | CMD | Command line | CMD | Command line |
3 | VSS1 | Ground | VSS1 | Ground |
4 | VDD | Supply voltage | VDD | Supply voltage |
5 | CLK | Clock | CLK | Clock |
6 | VSS2 | Ground | VSS2 | Ground |
7 | DAT[0] | Data line 0 | DATA | Data line |
8 | DAT[1] | Data line1 or INT line | IRQ | Interruption |
9 | DAT[2] | Data line2 or read wait | RW | Read wait |
PIN | SD MODE | ||
1 | Name | Type | Description |
2 | CD/DATA3 | I/O/PP | Card detect/data line3 |
3 | CMD | Pp | Command/response |
4 | Vss1 | S | Ground |
5 | VDD | S | Supply voltage |
6 | Vss2 | S | Ground |
7 | DAT0 | I/O/PP | Data line0 |
8 | DAT1 | I/O/PP | Data line1 |
9 | DAT2 | I/O/PP | Data line2 |
这个图看起来有点复杂,感觉无从下手,其实仔细瞧瞧就跟我们数电学的状态转移图差不多,框框里面是各种操作,箭头指向下一步操作,直线引出去的部分就是状态发生跳转的条件。首先上电(power-on),然后发出CMD0,再接着发送CMD8,有应答(response)判断为Ver2.00或更新的SDMC,无应答(no response)判断为ver2.00或者更新版SDMC(电压不匹配(voltage mismatch) )或ver1.X SDMC或不是SDMC。若判断为前一种情况,则问询是否为有效应答(valid response),如果不是,判断为无效卡,否则判断为有效卡且开始循环发送ACMD41指令直至SD卡发出power up应答。然后判断CCS(card capacity status——卡容量状态)位,为1则判断为ver2.00或者更新的大容量卡(high capacity SD memory card),否则判断为ver2.00或者更新的标准卡(sandard capacity SD memory card)。至此,power on工作结束。 上文只是对状态图进行了一个解读,真正要控制还是需要知道各个CMD如何发送。SD卡协议提供了几个CMD的格式以及其响应值的格式(如果有相应的话)。下面就对函数的具体编写进行下总结。 (1)初始化SDIO 利用SDIO_Init中的结构体初始化SDIO接口。该结构体有以下几个成员函数:SDIO_ClockDiv、SDIO_ClockEdge、SDIO_ClockBypass、SDIO_ClockPowerSave、SDIO_BusWide、SDIO_HardwareFlowControl。其中比较重要的是SDIO_ClockDiv成员,它确定了SDIO的分频系数,在不使用旁路时钟(ByPass)的时候SDIO_CLK=HCLK/(SDIO_ClockDiv+2),我们在这里将SDIO_CLK设置为400kHZ,就是通过设置分频系数实现的。初始化完成后,就进行第一步,也就是power on,调用SDIO_SetPowerState函数即可。最后别忘记使能SDIO的时钟。 (2)发送CMD0 由STM32向SD卡发送指令可以直接调用SDIO_SendCommand函数,该函数包含一个结构体,该结构体有以下几个成员:SDIO_Argument、SDIO_CmdIndex、SDIO_Response、SDIO_Wait、SDIO_CPSM。跟我们编程密切相关的一个是SDIO_Argument——参数,许多带有响应的命令都需要通过设置参数来实现,由于CMD0没有响应,所以此处设为0x00即可。SDIO_CmdIndex成员是该结构体核心——指令指数。也就是说,如果我要发送CMD0,SDIO_CmdIndex=0,如果发送ACMD41,SDIO_CmdIndex=41,但是注意此处SDIO_CmdIndex的值是等于指令,而且是指令的十进制形式,但是我们在给SDIO_CmdIndex赋值时必须转换成十六进制。SDIO_Wait设置等待中断。填写好结构体后检测是否正确受到CMD0,然后才能进行下一步。为了保证程序正确,这样的检测在每一个指令发送周期都是必须的。 (3)发送CMD8 用同样类似的方式发送CMD8。注意此时CMD8是带有响应的指令,接收到指令后sd返回该SDIO_Argument的值,所以对SDIO_Argument要进行特殊设置,设置后SDIO->ARG寄存器便有了该值。SDIO_Response——把应答模式设置成短应答,这是根据参考指南设置的。同样地,在填写完结构体后检查是否接收到命令。根据SD协议初始化流程图,此时如果有响应则SD遵循2.00协议,否则说明是1.x 或者mmc。无论是哪一种情况,都发送CMD55来检测。 (4)发送CMD55 CMD55是一个特殊的指令,该指令告诉主机下一个command将会是一个应用指令(application command),比如要发送ACMD41之前必须先发送CMD55。此处发送CMD55是为了检测卡的类型。不过暂时没没看懂为什么是发送CMD55,因为关于CMD55的解释中并没有提到它能够检测卡类型,只说了它的应答类型是R1。判断是SD卡或者是不支持的卡。 (5)进入循环,再次发送CMD55 这次发送CMD55就是为发送ACMD41做准备了。为什么要在循环里发送呢?因为SD协议初始化状态图中发送ACMD的过程是一个反复地,指导检测出power up为1为止,所以该处的循环条件设置成!validvoltage,validvoltage为OCR的power up位。需要注意的是例程中的循环条件是(!validvoltage) && (count < SD_MAX_VOLT_TRIAL),也就是说它设置了一个最大检测次数,当超过该检测次数仍然没有power up时就可以跳出,反馈检测失败的信息,这样程序不会死在此处。 (6)发送ACMD41 发送了CMD55后检测到收到指令,紧接着发送ACMD41指令。ACMD41响应类型是R3,该响应返回OCR寄存器的值,稍后我们需要这个寄存器来判断卡容量以及power up状态。注意此处参数格式规定的是HCS和Voltage Window共同确定的。填写完结构体后检查是否正确接收指令,然后检测power up位是不是1,不是的话再次循环,是则根据条件跳出循环。如果达到最大循环次数power up仍然没置位,则返回卡不可用的信息。 (7)检测卡的最终类型 正常上电好后,检测OCR的CCS为,一次来判断卡是SDHC还是SDSC。至此,power on函数功能到此结束。 资料三、 零死角玩转stm32-高级篇之SDIO(4bit + DMA、支持SDHC、带协议分析)
实验描述 | MicroSD卡(SDIO模式)测试实验,采用4bit数据线模式。没有跑文件系统,只是单纯地读block并将测试信息通过串口1在电脑的超级终端上 打印出来。 |
硬件连接 | PC12-SDIO-CLK:CLK PC10-SDIO-D2 :DATA2 PC11-SDIO-D3:CD/DATA3PD2-SDIO-CMD :CMDPC8-SDIO-D0:DATA0 PC9-SDIO-D1:DATA1 |
用到的库文件 | startup/start_stm32f10x_hd.cCMSIS/core_cm3.cCMSIS/system_stm32f10x.cFWlib/stm32f10x_gpio.cFWlib/stm32f10x_rcc.c FWlib/stm32f10x_usart.c FWlib/ stm32f10x_sdio.c FWlib/ stm32f10x_dma.c FWlib/ misc.c |
用户编写的文件 | USER/main.cUSER/stm32f10x_it.cUSER/usart1.cUSER/ sdio_sdcard.c |
文章图片
sdio操作详解
1.2 SDIO简介 野火STM32开发板的CPU ( STM32F103VET6 )具有一个SDIO接口。SD/SDIO/MMC主机接口可以支持MMC卡系统规范4.2版中的3个不同的数据总线模式:1位(默认)、4位和8位。在8位模式下,该接口可以使数据传输速率达到48MHz,该接口兼容SD存储卡规范2.0版。SDIO存储卡规范2.0版支持两种数据总线模式:1位(默认)和4位。
目前的芯片版本只能一次支持一个SD/SDIO/MMC 4.2版的卡,但可以同时支持多个MMC 4.1版或之前版本的卡。除了SD/SDIO/MMC,这个接口完全与CE-ATA数字协议版本1.1兼容。
1.3 SD协议
大多数人原来没有了解过SD协议,又看到SDIO的驱动有2000多行,感觉无从下手。所以野火重新写了这个文档进行详细的解释,帮助大家更快地跨过这道槛。
附资料:《Simplified_Physical_Layer_Spec.pdf》,这个资料包含了SDIO协议中SD存储卡的部分。
下面野火结合STM32的SDIO,分析SD协议,让大家对它先有个大概了解,更具体的说明在代码中展开。
SDIO接口图
文章图片
sdio操作详解
一.从SDIO的时钟说起。
SDIO_CK时钟是通过PC12引脚连接到SD卡的,是SDIO接口与SD卡用于同步的时钟。
SDIO选配器挂载到AHB总线上,通过HCLK二分频输入到适配器得到SDIO_CK的时钟,这时SDIO_CK = HCLK/(2+CLKDIV)。其中CLKDIV是SDIO_CLK(寄存器)中的CLKDIV位。
另外,SDIO_CK也可以由SDIOCLK通过设置bypass模式直接得到,这时SDIO_CK = SDIOCLK=HCLK。
通过下面的库函数来配置时钟:
SDIO_Init(&SDIO_InitStructure);
对SD卡的操作一般是大吞吐量的数据传输,所以采用DMA来提高效率,SDIO采用的是DMA2中的通道4。在数据传输的时候SDIO可向DMA发出请求。
二.讲解SDIO的命令、数据传输方式。
SDIO的所有命令及命令响应,都是通过SDIO-CMD引脚来传输的。
命令只能由host即STM32的SDIO控制器发出。SDIO协议把命令分成了11种,包括基本命令,读写命令还有ACMD系列命令等。其中,在发送ACMD命令前,要先向卡发送编号为CMD55的命令。
参照下面的命令格式图,其中的start bit,transmission bit ,crc7,endbit,都是由STM32中的SDIO硬件完成,我们在软件上配置的时候只需要设置command index和命令参数argument。Command index就是命令索引(编号),如CMD0,CMD1…被编号成0,1...。有的命令会包含参数,读命令的地址参数等,这个参数被存放在argument段。
SD卡命令格式
文章图片
sdio操作详解
可以通过下面的函数来配置、发送命令:
SDIO_SendCommand(&SDIO_CmdInitStructure); //发送命令
SD卡对host的各种命令的回复称为响应,除了CMD0命令外,SD卡在接收到命令都会返回一个响应。对于不同的命令,会有不同的响应格式,共7种,分为长响应型(136bit)和短响应型(48bit)。以下图,响应6(R6)为例:
SD卡命令响应格式(R6)
文章图片
sdio操作详解
SDIO通过CMD接收到响应后,硬件去除头尾的信息,把command index 保存到SDIO_RESPCMD寄存器,把argument field内容保存存储到SDIO_RESPx寄存器中。这两个值可以分别通过下面的库函数得到。
SDIO_GetCommandResponse(); //卡返回接收到的命令
SDIO_GetResponse(SDIO_RESP1); //卡返回的argument field内容
数据写入,读取。请看下面的写数据时序图,在软件上,我们要处理的只是读忙。另外,我们的实验中用的是Micro SD卡,有4条数据线,默认的时候SDIO采用1条数据线的传输方式,更改为4条数据线模式要通过向卡发送命令来更改。
SD卡的多块写入时序图
文章图片
sdio操作详解
三.卡的种类。
STM32的SDIO支持SD存储卡,SD I/O卡 ,MMC卡。
其中SDI/O卡与SD存储卡是有区别的,SDI/O卡实际上就是利用SDIO接口的一些模块,插入SD的插槽中,扩展设备的功能,如:SDI/O wifi, SDI/O cmos相机等。而SD存储卡就是我们平时常见的单纯用于存储数据的卡。
可使用SDIO接口类型的卡
文章图片
sdio操作详解
本实验中使用的Micro SD卡属于SDSC(标准容量,最大两G)卡。介绍卡的种类是因为SD协议中的命令也支持这三种类型的卡,因此对STM32中的SDIO接口进行初始化后,上电后就要对接入的卡进行检测、分类,这个过程是通过向卡发送一系列不同的命令,根据卡不同的响应来进行分类。
下面进入代码展开具体讲解。
1.4 代码分析
首先要添加用的库文件,在工程文件夹下Fwlib下我们需添加以下库文件:
FWlib/stm32f10x_gpio.c
FWlib/stm32f10x_rcc.c
FWlib/stm32f10x_usart.c
FWlib/stm32f10x_sdio.c
FWlib/stm32f10x_dma.c
FWlib/misc.c
还要在 stm32f10x_conf.h中把相应的头文件添加进来:
#include "stm32f10x_dma.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x_sdio.h"
#include "stm32f10x_usart.h"
#include "misc.h"
保持良好的习惯,从main函数开始分析:
int main(void)
{
NVIC_Configuration();
USART1_Config();
Status = SD_Init();
printf( "\r\n 这是一个MicroSD卡实验(没有跑文件系统).........\r\n " );
if(Status == SD_OK) //检测初始化是否成功
{
printf( " \r\n SD_Init 初始化成功 \r\n " );
}
else
{
printf("\r\n SD_Init 初始化失败 \r\n" );
printf("\r\n 返回的Status的值为: %d \r\n",Status );
}
printf( " \r\n CardType is :%d ", SDCardInfo.CardType );
printf( " \r\n CardCapacity is :%d ", SDCardInfo.CardCapacity );
printf( " \r\n CardBlockSize is :%d ", SDCardInfo.CardBlockSize );
printf( " \r\n RCA is :%d ", SDCardInfo.RCA);
printf( " \r\n ManufacturerID is :%d \r\n", SDCardInfo.SD_cid.ManufacturerID );
SD_EraseTest(); //擦除测试
SD_SingleBlockTest(); //单块读写测试
SD_MultiBlockTest(); //多块读写测试
while (1)
{}
}
main函数的流程简单明了:
1、用NVIC_Configuration()初始化好SDIO的中断;
2、用USART1_Config()配置好用于返回调试信息的串口,SD_Init()开始进行SDIO的初始化;
3、最后分别用SD_EraseTest()、SD_SingleBlockTest()、SD_MultiBlockTest()进行擦除,单数据块读写,多数据块读写测试。
下面我们先进入SDIO驱动函数的大头——SD_Init()进行分析:
SD_Error SD_Init(void)
{
SD_Error errorstatus = SD_OK;
GPIO_Configuration();
SDIO_DeInit();
errorstatus = SD_PowerON();
if (errorstatus != SD_OK)
{
return(errorstatus);
}
errorstatus = SD_InitializeCards();
if (errorstatus != SD_OK)//失败返回
{
return(errorstatus);
}
SDIO_InitStructure.SDIO_ClockDiv = SDIO_TRANSFER_CLK_DIV;
SDIO_InitStructure.SDIO_ClockEdge = SDIO_ClockEdge_Rising; //上升沿采集数据
SDIO_InitStructure.SDIO_ClockBypass = SDIO_ClockBypass_Disable; //时钟频率若超过24M,要开启此模式
SDIO_InitStructure.SDIO_ClockPowerSave = SDIO_ClockPowerSave_Disable; //若开启此功能,在总线空闲时关闭sd_clk时钟
SDIO_InitStructure.SDIO_BusWide = SDIO_BusWide_1b; //1位模式
SDIO_InitStructure.SDIO_HardwareFlowControl = SDIO_HardwareFlowControl_Disable; //硬件流,若开启,在FIFO不能进行发送和接收数据时,数据传输暂停
SDIO_Init(&SDIO_InitStructure);
if (errorstatus == SD_OK)
{
errorstatus = SD_GetCardInfo(&SDCardInfo); //用来读取csd/cid寄存器
}
if (errorstatus == SD_OK)
{
errorstatus = SD_SelectDeselect((uint32_t) (SDCardInfo.RCA << 16)); //通过cmd7,rca选择要操作的卡
}
if (errorstatus == SD_OK)
{
errorstatus = SD_EnableWideBusOperation(SDIO_BusWide_4b); //开启4bits模式
}
return(errorstatus);
}
先从整体上了解这个SD_Init()函数:
1.用 GPIO_Configuration()进行SDIO的端口底层配置
2.分别调用了SD_PowerON()和SD_InitializeCards()函数,这两个函数共同实现了上面提到的卡检测、识别流程。
3.调用SDIO_Init(&SDIO_InitStructure)库函数配置SDIO的时钟,数据线宽度,硬件流(在读写数据的时候,开启硬件流是和很必要的,可以减少出错)
4. 调用SD_GetCardInfo(&SDCardInfo)获取sd卡的CSD寄存器中的内容,在main函数里输出到串口的数据就是这个时候从卡读取得到的。
5. 调用SD_SelectDeselect()选定后面即将要操作的卡。
6.调用SD_EnableWideBusOperation(SDIO_BusWide_4b)开启4bit数据线模式
如果SD_Init()函数能够执行完整个流程,并且返回值是SD_OK的话则说明初始化成功,就可以开始进行擦除、读写的操作了。
下面进入SD_PowerON()函数,分析完这个函数大家就能了解SDIO如何接收、发送命令了。
SD_Error SD_PowerON(void)
{
SD_Error errorstatus = SD_OK;
uint32_t response = 0, count = 0, validvoltage = 0;
uint32_t SDType = SD_STD_CAPACITY;
SDIO_InitStructure.SDIO_ClockDiv = SDIO_INIT_CLK_DIV;
SDIO_InitStructure.SDIO_ClockEdge = SDIO_ClockEdge_Rising;
SDIO_InitStructure.SDIO_ClockBypass = SDIO_ClockBypass_Disable; //不使用bypass模式,直接用HCLK进行分频得到SDIO_CK
SDIO_InitStructure.SDIO_ClockPowerSave = SDIO_ClockPowerSave_Disable; // 空闲时不关闭时钟电源
SDIO_InitStructure.SDIO_BusWide = SDIO_BusWide_1b; //1位数据线
SDIO_InitStructure.SDIO_HardwareFlowControl = SDIO_HardwareFlowControl_Disable; //硬件流
SDIO_Init(&SDIO_InitStructure);
SDIO_SetPowerState(SDIO_PowerState_ON);
SDIO_ClockCmd(ENABLE);
SDIO_CmdInitStructure.SDIO_Argument = 0x0;
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_GO_IDLE_STATE; //cmd0
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_No; //无响应
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable; //则CPSM在开始发送命令之前等待数据传输结束。
SDIO_SendCommand(&SDIO_CmdInitStructure); //写命令进命令寄存器
errorstatus = CmdError(); //检测是否正确接收到cmd0
if (errorstatus != SD_OK) //命令发送出错,返回
{
return(errorstatus);
}
SDIO_CmdInitStructure.SDIO_Argument = SD_CHECK_PATTERN; //接收到命令sd会返回这个参数
SDIO_CmdInitStructure.SDIO_CmdIndex = SDIO_SEND_IF_COND; //cmd8
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; //r7
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No; //关闭等待中断
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus = CmdResp7Error();
if (errorstatus == SD_OK)//有响应则card遵循sd协议2.0版本
{
CardType = SDIO_STD_CAPACITY_SD_CARD_V2_0;
SDType = SD_HIGH_CAPACITY; //这个变量用作acmd41的参数,用来询问是sdsc卡还是sdhc卡
}
else//无响应,说明是1.x的或mmc的卡
{
SDIO_CmdInitStructure.SDIO_Argument = 0x00;
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_APP_CMD;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus = CmdResp1Error(SD_CMD_APP_CMD);
}
//为什么在else里和else外面都要发送CMD55?
//发送cmd55,用于检测是sd卡还是mmc卡,或是不支持的卡
SDIO_CmdInitStructure.SDIO_Argument = 0x00;
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_APP_CMD;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; //r1
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus = CmdResp1Error(SD_CMD_APP_CMD); //是否响应,没响应的是mmc或不支持的卡
if (errorstatus == SD_OK) //响应了cmd55,是sd卡,可能为1.x,可能为2.0
{
while ((!validvoltage) && (count < SD_MAX_VOLT_TRIAL))
{
//因为下面要用到ACMD41,是ACMD命令,在发送ACMD命令前都要先向卡发送CMD55
SDIO_CmdInitStructure.SDIO_Argument = 0x00;
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_APP_CMD; //CMD55
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus = CmdResp1Error(SD_CMD_APP_CMD); //检测响应
if (errorstatus != SD_OK)
{
return(errorstatus); //没响应CMD55,返回
}
//acmd41,命令参数由支持的电压范围及HCS位组成,HCS位置一来区分卡是SDSc还是sdhc
SDIO_CmdInitStructure.SDIO_Argument = SD_VOLTAGE_WINDOW_SD | SDType; //参数为主机可供电压范围及hcs位
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SD_APP_OP_COND;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; //r3
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus = CmdResp3Error(); //检测是否正确接收到数据
if (errorstatus != SD_OK)
{
return(errorstatus); //没正确接收到acmd41,出错,返回
}
response = SDIO_GetResponse(SDIO_RESP1); //读取卡寄存器,卡状态
validvoltage = (((response >> 31) == 1) ? 1 : 0); //读取卡的ocr寄存器的pwr_up位,看是否已工作在正常电压
count++; //计算循环次数
}
if (count >= SD_MAX_VOLT_TRIAL) //循环检测超过一定次数还没上电
{
errorstatus = SD_INVALID_VOLTRANGE; //SDIO不支持card的供电电压
return(errorstatus);
}
if (response &= SD_HIGH_CAPACITY)//判断ocr中的ccs位 ,如果是sdsc卡则不执行下面的语句
{
CardType = SDIO_HIGH_CAPACITY_SD_CARD; //把卡类型从初始化的sdsc型改为sdhc型
}
}
return(errorstatus);
}
这个函数的流程就是卡的上电、识别操作,如下图:
文章图片
sdio操作详解
卡的上电,识别流程:
截图来自《Simplified_Physical_Layer_Spec.pdf》 page27
代码中所有的判断语句都是根据这个图的各个识别走向展开的,最终把卡分为1.0版的SD存储卡,2.0版的SDSC卡和2.0版的SDHC卡。
在这个代码流程中有两点要注意一下:
1.初始化的时钟。SDIO_CK的时钟分为两个阶段,在初始化阶段SDIO_CK的频率要小于400KHz,初始化完成后可把SDIO_CK调整成高速模式,高速模式时超过24M要开启bypass模式,对于SD存储卡即使开启bypass,最高频率不能超过25MHz。
2.CMD8命令。
CMD8命令格式。
文章图片
sdio操作详解
CMD8命令中的VHS是用来确认主机SDIO是否支持卡的工作电压的。Check pattern部分可以是任何数值,若SDIO支持卡的工作电压,卡会把接收到的check pattern数值原样返回给主机。
CMD8命令的响应格式R7:
文章图片
sdio操作详解
在驱动程序中调用了CmdResp7Error()来检验卡接收命令后的响应。
3.ACMD41命令。
这个命令也是用来进一步检查SDIO是否支持卡的工作电压的,协议要它在调用它之前必须先调用CMD8,另外还可以通过它命令参数中的HCS位来区分卡是SDHC卡还是SDSC卡。
确认工作电压时循环地发送ACMD41,发送后检查在SD卡上的OCR寄存器中的pwr_up位,若pwr_up位置为1,表明SDIO支持卡的工作电压,卡开始正常工作。
同时把ACMD41中的命令参数HCS位置1,卡正常工作的时候检测OCR寄存器中的CCS位,若CCS位为1则说明该卡为SDHC卡,为零则为SDSC卡。
因为ACMD41命令属于ACMD命令,在发送ACMD命令前都要先发送CMD55.
ACMD41命令格式
文章图片
sdio操作详解
ACMD41命令的响应(R3),返回的是OCR寄存器的值
OCR寄存器的内容
文章图片
sdio操作详解
SD卡上电确认成功后,进入SD_InitializeCards()函数:
SD_Error SD_InitializeCards(void)
{
SD_Error errorstatus = SD_OK;
uint16_t rca = 0x01;
if (SDIO_GetPowerState() == SDIO_PowerState_OFF)
{
errorstatus = SD_REQUEST_NOT_APPLICABLE;
return(errorstatus);
}
if (SDIO_SECURE_DIGITAL_IO_CARD != CardType)//判断卡的类型
{
SDIO_CmdInitStructure.SDIO_Argument = 0x0;
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_ALL_SEND_CID;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Long;
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus = CmdResp2Error();
if (SD_OK != errorstatus)
{
return(errorstatus);
}
CID_Tab[0] = SDIO_GetResponse(SDIO_RESP1);
CID_Tab[1] = SDIO_GetResponse(SDIO_RESP2);
CID_Tab[2] = SDIO_GetResponse(SDIO_RESP3);
CID_Tab[3] = SDIO_GetResponse(SDIO_RESP4);
}
if ((SDIO_STD_CAPACITY_SD_CARD_V1_1 == CardType) ||(SDIO_STD_CAPACITY_SD_CARD_V2_0 == CardType) ||(SDIO_SECURE_DIGITAL_IO_COMBO_CARD == CardType)
||(SDIO_HIGH_CAPACITY_SD_CARD == CardType))//使用的是2.0的卡
{
SDIO_CmdInitStructure.SDIO_Argument = 0x00;
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SET_REL_ADDR; //cmd3
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; //r6
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus = CmdResp6Error(SD_CMD_SET_REL_ADDR, &rca); //把接收到的卡相对地址存起来。
if (SD_OK != errorstatus)
{
return(errorstatus);
}
}
if (SDIO_SECURE_DIGITAL_IO_CARD != CardType)
{
RCA = rca;
SDIO_CmdInitStructure.SDIO_Argument = (uint32_t)(rca << 16);
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SEND_CSD;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Long;
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus = CmdResp2Error();
if (SD_OK != errorstatus)
{
return(errorstatus);
}
CSD_Tab[0] = SDIO_GetResponse(SDIO_RESP1);
CSD_Tab[1] = SDIO_GetResponse(SDIO_RESP2);
CSD_Tab[2] = SDIO_GetResponse(SDIO_RESP3);
CSD_Tab[3] = SDIO_GetResponse(SDIO_RESP4);
}
errorstatus = SD_OK;
return(errorstatus);
}
这个函数向卡发送了CMD2和CMD3命令
1.CMD2
CMD2命令是要求卡返回它的CID寄存器的内容。
命令的响应格式(R2)。
文章图片
sdio操作详解
因为命令格式是136位的,属于长响应。软件接收的信息有128位。在长响应的时候通过SDIO_GetResponse(SDIO_RESP4); 中的不同参数来获取CID中的不同数据段的数据。
文章图片
sdio操作详解
2.CMD3
CMD3命令是要求卡向主机发送卡的相对地址。在接有多个卡的时候,主机要求接口上的卡重新发一个相对地址,这个地址跟卡的实际ID不一样。比如接口上接了5个卡,这5个卡的相对地址就分别为1,2,3,4,5.以后主机SDIO对这几个卡寻址就直接使用相对地址。这个地址的作用就是为了寻址更加简单。
接下来我们回到SD_Init()函数。分析到这里大家应该对SDIO的命令发送和响应比较清楚了。在SD_InitializeCards()之后的SD_GetCardInfo(&SDCardInfo)、 SD_SelectDeselect()和
SD_EnableWideBusOperation(SDIO_BusWide_4b)的具体实现就不再详细分析了,实际就是发送相应的命令,对卡进行相应的操作。
接下来分析main函数中的SD_MultiBlockTest()多块数据读写函数,让大家了解SDIO是怎样传输数据的。
void SD_MultiBlockTest(void)
{
Fill_Buffer(Buffer_MultiBlock_Tx, MULTI_BUFFER_SIZE, 0x0);
if (Status == SD_OK)
{
Status = SD_WriteMultiBlocks(Buffer_MultiBlock_Tx, 0x00, BLOCK_SIZE, NUMBER_OF_BLOCKS);
Status = SD_WaitWriteOperation();
while(SD_GetStatus() != SD_TRANSFER_OK);
}
if (Status == SD_OK)
{
Status = SD_ReadMultiBlocks(Buffer_MultiBlock_Rx, 0x00, BLOCK_SIZE, NUMBER_OF_BLOCKS);
Status = SD_WaitReadOperation();
while(SD_GetStatus() != SD_TRANSFER_OK);
}
if (Status == SD_OK)
{
TransferStatus2 = Buffercmp(Buffer_MultiBlock_Tx, Buffer_MultiBlock_Rx, MULTI_BUFFER_SIZE);
}
if(TransferStatus2 == PASSED)
printf("\r\n 多块读写测试成功! " );
else
printf("\r\n 多块读写测试失败! " );
}
把这个函数拿出来分析最重要的一点就是让大家注意在调用了SD_WriteMultiBlocks()这一类读写操作的函数后,一定要调用SD_WaitWriteOperation()[在读数据时调用SD_WaitReadOperation]和SD_GetStatus()来确保数据传输已经结束再进行其它操作。其中的SD_WaitWriteOperation()是用来等待DMA把缓冲的数据传输到SDIO的FIFO的;而SD_GetStatus()是用来等待卡与SDIO之间传输数据完毕的。
最后进入SD_WriteMultiBlocks()函数分析:
SD_Error SD_WriteMultiBlocks(uint8_t *writebuff, uint32_t WriteAddr, uint16_t BlockSize, uint32_t NumberOfBlocks)
{
SD_Error errorstatus = SD_OK;
__IO uint32_t count = 0;
TransferError = SD_OK;
TransferEnd = 0;
StopCondition = 1;
SDIO->DCTRL = 0x0;
if (CardType == SDIO_HIGH_CAPACITY_SD_CARD)
{
BlockSize = 512;
WriteAddr /= 512;
}
SDIO_CmdInitStructure.SDIO_Argument = (uint32_t) BlockSize;
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SET_BLOCKLEN;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; //r1
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus = CmdResp1Error(SD_CMD_SET_BLOCKLEN);
if (SD_OK != errorstatus)
{
return(errorstatus);
}
SDIO_CmdInitStructure.SDIO_Argument = (uint32_t) (RCA << 16);
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_APP_CMD; // cmd55
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus = CmdResp1Error(SD_CMD_APP_CMD);
if (errorstatus != SD_OK)
{
return(errorstatus);
}
//pre-erased,在多块写入时可发送此命令进行预擦除
SDIO_CmdInitStructure.SDIO_Argument = (uint32_t)NumberOfBlocks; //参数为将要写入的块数目
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SET_BLOCK_COUNT; //cmd23
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus = CmdResp1Error(SD_CMD_SET_BLOCK_COUNT);
if (errorstatus != SD_OK)
{
return(errorstatus);
}
SDIO_CmdInitStructure.SDIO_Argument = (uint32_t)WriteAddr;
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_WRITE_MULT_BLOCK;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus = CmdResp1Error(SD_CMD_WRITE_MULT_BLOCK);
if (SD_OK != errorstatus)
{
return(errorstatus);
}
SDIO_DataInitStructure.SDIO_DataTimeOut = SD_DATATIMEOUT;
SDIO_DataInitStructure.SDIO_DataLength = NumberOfBlocks * BlockSize;
SDIO_DataInitStructure.SDIO_DataBlockSize = (uint32_t) 9 << 4;
SDIO_DataInitStructure.SDIO_TransferDir = SDIO_TransferDir_ToCard;
SDIO_DataInitStructure.SDIO_TransferMode = SDIO_TransferMode_Block;
SDIO_DataInitStructure.SDIO_DPSM = SDIO_DPSM_Enable;
SDIO_DataConfig(&SDIO_DataInitStructure);
SDIO_ITConfig(SDIO_IT_DATAEND, ENABLE);
SDIO_DMACmd(ENABLE);
SD_DMA_TxConfig((uint32_t *)writebuff, (NumberOfBlocks * BlockSize));
return(errorstatus);
}
写操作在发送正式的多块写入命令CMD25前调用了CMD23进行预写,这样有利于提高写入的速度。在代码的最后调用了SDIO_ITConfig(),SDIO的数据传输结束中断就是这个时候开启的,数据传输结束时,就进入到stm32f10x_it.c文件中的中断服务函数SDIO_IRQHandler()中处理了,中断服务函数主要就是负责清中断。
最后讲一下官方原版的驱动中的一个bug。
在官方原版的SDIO驱动的SD_ReadBlock()、SD_ReadMultiBlocks()、SD_WriteBlock()和SD_WriteMultiBlocks()这几个函数中,发送读写命令前,漏掉了发送一个CMD16命令,这个命令用于设置读写SD卡的块大小。缺少这个命令很容易导致程序运行时卡死在循环检测DMA传输结束的代码中,网上很多人直接移植ST官方例程时,用3.5版库函数和这个4.5版的SDIO驱动移植失败,就是缺少了这段用CMD16设置块大小的代码。
到这里,终于讲解完毕啦!这个讲解如果能让你从对SDIO一无所知到大概了解的话,我的目标就达到啦,想要更深入了解还是要好好地配合这个例程中我在代码中的注释和附带资料SD2.0协议《Simplified_Physical_Layer_Spec.pdf》好好研究一番! ^_^
注意:这个例程是没有跑文件系统的,而是直接就去读卡的block,这样的话就会破坏卡的分区,在实验完成之后,你再把卡插到电脑上时,电脑会提示你要重新初始化卡,这是正常想象,并不是本实验把你的卡弄坏了,如果卡原来有资料的请先把数据备份了再进行测试。但跑文件系统时就不会出现这种问题,有关文件系统的操作将在下一讲的教程中讲解。
1.5实验现象
【主流Wifi芯片简要介绍】 将野火STM32开发板供电(DC5V),插上JLINK,插上串口线(两头都是母的交叉线),插上MicroSD卡( 我用的是1G ,经测试,本驱动也适用于2G以上的卡(sdhc卡)),打开超级终端,配置超级终端为115200 8-N-1,将编译好的程序下载到开发板,即可看到超级终端打印出如下信息:
文章图片
sdio操作详解
推荐阅读
- 有生之年
- 5G芯片发热功耗测试(华为、高通和MediaTek,谁拥有最冷的核心())
- 三星电子7月将量产Micro|三星电子7月将量产Micro LED电视,LED芯片谁来供应()
- 动真格!格力10亿元成立芯片公司,团队曝光
- #|ARM裸机开发(汇编LED灯实验(I.MX6UL芯片))
- USB转串口|USB转四串口芯片CH9344
- 自动驾驶关乎安全|自动驾驶关乎安全 四款搭载L2级主流新能源汽车你选谁()
- 国内主流大数据厂商太多不知道怎么选(Smartbi可以满足你的需求)
- 英特尔最强芯片 Alder Lake Core i9 与苹果 M1 Max 跑分结果出炉!
- 8个主流且实用的Python开发工具推荐!