经过将近1个月的时间,终于完成了STM32是FATFS文件系统移植,说来是够艰辛的,SDIO章节是我学习cortex m3以来消耗时间最多的章节。这里说一些个人对于SDIO的看法,其实SDIO属于意法半导体公司在cortex m3内核之外(在芯片之内)添加的功能外设,完全属于意法的杰作了。关于SD卡的读写,分为SPI模式和SD模式(专用模式),这两种模式都必须遵循SD2.0协议。SPI模式控制方法相对较为简单,操作简洁,但失去了速度;SD模式控制方法相对较为复杂一点,操作繁琐,但具有高速的特点。
FATFS文件系统是一种兼容性比较高的文件管理系统,兼容FAT32、FAT16。关于文件系统的细节,如果认真研究的话,应该会觉得作者的伟大,惊叹代码的绝妙。
我们要想移植FATFS,首先要做的是编写基于SDIO模式的SD卡底层驱动,这部份完整的驱动代码较多,大概有2000多行,但我们首先需要克服心理作用,再长的代码只要理解之后,都很简单。意法在参考手册中介绍SDIO时,上下文比较乱,其中还夹杂讲解了一些SD2.0协议,使得初学者云里雾里。因为SDIO是属于一种完全的外设接口,所以在讲解的过程中必须与实际的外设SD卡联系起来。
STM32的SDIO接口兼容性很高,可以兼容SD1.0卡、2.0卡、MMC卡、多媒体卡等,与多媒体卡4.2支持三种不同的数据总线模式:1位、4位和8位,在8位的模式下速度可以达到48MHZ,但在SD2.0协议中只支持两种总数总线模式:1位和4位,在SDIO中存在两种状态机:命令状态机(CPSM)和数据状态机(DPSM),两者的使能信号独立,用于控制外部双向驱动器,命令是通过CMD命令线单线串行发送的,而数据是由于DATx数据线传输(1位或4位),每当CPSM发送一条命令给卡时,如工作正常的话,卡都会有与CPSM中设置响应格式相对应的响应内容(短响应与长响应),两者的细节在下面讲到。
SDIO功能分为两个部分,一个就是SDIO适配器模块,可以实现所有MMC/SD/SD I/O卡的相关功能,如时钟的产生、命令和数据的传输;还有就是AHB总线接口操作SDIO适配器模块中的寄存器,并产生中断和DMA请求信号。上面提到的数据总线的宽度为分为1位或4位(本文是针对SD2.0协议进行主要说明),在上电复位后,系统缺省为1位数据宽度——DAT0用于数据传输。 SD卡或SD I/O卡可以使用两种数据宽度模式,这两种卡的所有数据线都需要设置为复用推挽模式。命令线CMD有两种操作模式:1、MMC卡V3.31或之前版本的卡在初始化时为复用开路模式;2、SD卡或SD I/O卡或MMC V4.2在初始化时命令传输线工作在复用推挽模式。还有就是卡的时钟线(SDIO_CK),这个时钟频率对于不同的卡需要工作在不同的范围,这个我们就不细说了。SDIO的时钟可以分为两个,分为SDIO适配器时钟(SDIOCLK=HCLK)和AHB总线时钟(HCLK/2),还有HCLK就是SYSCLK。既然提到外设接口,那就要清楚一共有几根线与主控芯片相连,分为SDIO_CK\SDIO_CMD\SDIO_D[7:0],本文是对SD2.0卡进行论述,所以只用到了SDIO_CK/SDIO_CMD/SDIO_D[3:0],因为SD卡不支持8线模式。
SDIO适配器主要全为5个部分:适配器寄存器模块、控制单元、命令通道、数据通道、数据FIFO。这5个部分中,不同部分使用的时钟也有不同,适配器寄存器和FIFO使用HCLK/2,控制单元、命令通道和数据通道使用的是SDIOCLK=HCLK。其实这5个部分,对于初学者来说,根本分不清有何具体区别。适配器寄存器模块包含所有系统寄存器,如用于存放状态位的SDIO_STA和清除中断寄存器SDIO_ICR,对相应寄存器写入清除中断标志(产生清除信号);控制单元有于电源管理和卡的时钟分频设置;命令通道(CPSM)主要用于向卡发送相应的命令并从卡接收相应的响应内容,实现主机与卡之间的控制信息交互,这里就必须提到SDIO_CMD命令寄存器,这个寄存器就是负责向发送命令的,与该寄存器相配合的还有一个参数寄存器(发送命令参数)SDIO_ARG,命令寄存器中存放了命令索引(命令号)和对应命令的等待响应设置和CPSM等待中断请求设置(这个设置一般使用无等待,关闭命令超时控制,改为中断清除方式,关于这个命令超时控制就是设定了一定的时间,在这个时间之间必须得到响应,否则状态机将进入空闲状态),还有就是命令响应了,命令响应寄存器SDIO_RESPCMD用于保存接收到的响应命令索引(如命令发送正常,得到响应命令索引和发送的一致,作为一个控制命令的回应),对于一些带响应的命令(只有cmd0没有响应),都会返回一些参数(如返回卡状态寄存器、CSD、CID),这些参数保存在响应寄存(SDIO_RESPx 1--4)中,该寄存器有4个,对于命令的短响应只用到SDIO_RESP1,其他3个寄存器不用,对于命令长响应,4个寄存器都用到,其实命令本身就决定了是否需要响应和响应的类型,48位(短响应:参数只有32位)还是136位(长响应,参数只有127位)。命令通道每发送一个命令,都有相应的通道状态标志来反应当前命令通道的状态,如CMDREND(响应的CRC正确) CCRCFAIL (响应的CRC错误)CMDSENT(命令已经发出,命令指不需要响应的命令)CTIMEOUT(响应超时)CMDACT(正在发送命令)。在我们编写SD卡底层驱动时最常用的就是这些标志了,每发送一次命令,都要观察这个标志位的情况,以便程序员了解当前卡对刚发的命令的反应。数据通道(DPSM)用于主机与卡之间的数据通信,这与CPSM中的命令信息都是一些二进制“数据”,其实数据与命令之间本质上是一样的,但只是用户或设备对这些二进制的定义不同,用于控制设备的二进制串称为命令,而用于交互一些具体信息的二进制串称为数据。DPSM可以选择数据总线宽度,如1位数据宽度是指每个时钟数据线D0有一位数据传输,而4位数据宽度是指每个时钟数据线D4_0有四位数据传输,8位宽度则一次8位数据传输喽(SD卡不能使用8位,一般用于多媒体卡),而系统缺省为1位宽度,还有一点要说明的就是命令线和数据线都是双向传输的,而时钟线是单向传输的。在使能数据发送或接收时,DPSM就是进行Wait_S或Wait_R状态,如发送FIFO中有数据,就会进行发送状态,或接收FIFO接收到开始位,就会进入接收状态。但这个等待发送或等待接收不能一直等,等待时间是有限制的,由相关寄存器SDIO_DTIMER设置,当DPSM进行等待状态(发送或接收)时,一个计数器从该寄存器中加载数据,进行倒计时,当计数到0时,将设置超时标志关进入空闲状态。数据状态机也有反应数据传输状态的标志位,发送FIFO状态标志有TXFIFOF/TXFIFOE/TXFIFOHE/TXDAVL/TXUNDERR和接收FIFO状态标志有RXFIFOF/RXFIFOE/RXFIFOHF/RXDAVL/RXOVERR。关于FIFO这个名词,其实是像数据结构中讲到的队列一样,先进先出,称为数据缓冲区,用户操作时只需直接读取SDIO->FIFO即可。FIFO的数据传输分为块传输和流传输,一般使用块传输。这个数据缓冲区一共有32个字的空间,如流传输下接收的数据超过32个字,而用户没有读取的话,将产生上溢错误。在与SD卡进行数据通信时,为保证数据的高速传输,我们一般使用DMA来将FIFO中的数据搬到内存中,而不需要人为的去读取。SDIO的数据通道是半双工通信,不能同时接收和发送,有两个标志位表示当前数据通道的状态:TXACT和RXACT,前者标志数据通道在发送FIFO,后者标志数据通道正在接收数据,这两个标志位是互斥的。到这里SDIO适配器已经基本了解了,就需要对SD卡通行操作了。
上述内容,作为初学者想真正理解,还是需要在下面对SD卡进行实际操作时才能真正了解其中的含义。
首先应设置与SD相连的GPIO的模式,使用SD卡的话,就需要设置CK/CMD/DAT3/DAT2/DAT1/DAT0为复用推挽输出,开启相应端口的时钟和SDIO时钟,如使用DMA的话,还需开启DMA2时钟。接下来就是对SDIO中断进行配置(中断优先级分组和设置优先级,并在NVIC中使能SDIO向量中断),并对SDIO所有相关寄存器初始化(全部赋0)。
SD卡上电识别过程。需要通过时钟控制寄存器设置SDIO的时钟、数据线宽度和一些设置。SD卡在识别模式时频率低于400KHZ,将CLKDIV位设置为0xb2,SDIOCLK的上升沿产生SDIO_CK时钟,关闭硬件流控制,识别总线宽度为1线,关闭旁路时钟分频器,在空闲时仍输出SDIO_CK时钟,然后给SD上电,最后SDIO_CK时钟使能。这样就将系统时钟设置好了,之后就要向SD卡发送命令来为SD卡上电初始化。发送cmd0命令(无响应,无等待),复位所有卡到空闲状态;发送cmd8(短响应,无等待),发送卡接口条件,命令参数中包涵电压范围和校验码,发送这个命令的同时,SDIO将产生相应的电压,该命令用于询问卡是否支持参数中所指的电压范围(主机的电压),如支持则发送参数中的低八位的CRC校验码原样返回。通过cmd8命令,让我们知道卡是否支持主机电压。之后,就一直向卡发送ACMD41命令,等待卡上电。主机在发送ACMD41命令时,将一直产生主机的供电电压,不支持该电压的卡将自动进入非激活状态。ACMD41命令属于应用命令,需要先发送cmd55命令,再发送ACMD41命令。如SD2.0协议中说明了cmd55命令发送之后,卡将期待一个应用命令,如在cmd55之后发送一个非定义的应用命令,则将发送的命令当作普通命令处理,如是定义的应用命令,则当做应用命令执行。这里发送cmd55命令时,参数应该是RCA地址,而此时卡还没有成功上电,所以先使用0x00代替。ACMD41命令的参数中【31】保留位【30】测试卡容量【29:24】保留位【23:0】电压范围,【30】用于向卡表明主机可以支持的卡容量,在实验中将参数设置成了0x80100000|SD_Type(0x40000000),该命令R3响应,响应内容为ODR寄存器,该寄存器是卡的操作条件寄存器,其中包涵了卡的电压范围。ODR【31】为1表示SD卡成功上电,支持主机的提供的工作电压,【30】为1表明卡是SDHC卡,为0表明卡是SDSC卡。通过ACMD41命令让程序员知道了卡的操作条件(工作电压)和卡的类型,完成卡的上电识别。接下来,我们就可以读取卡的特殊功能寄存器(CSD\CID\RCA地址)。
SD卡特殊寄存器读取。在卡完成上电识别的基础上,通过发送cmd2命令(参数:任意 R2长响应 响应内容为CID寄存器(127位)),通过发送cmd3命令(参数任意,R6短响应,响应内容为RCA地址,就是卡的相对寻址地址),下面通过发送cmd9命令(参数高16位为RCA,低16位为任意《0x0000》,R2长响应,响应内容为CSD寄存器),关于长响应与短响应上面已经提到了,短响应只用到SDIO_RESPx的低32位,高位保留,长响应全部使用。CSD、CID寄存器中包涵了卡的全部信息,如生产厂家,产品序列号等。
在卡完成上电识别之后,随即就读取了卡的特殊功能寄存器,也得到了卡的相对寻址地址RCA,基于完成了卡的初始化。协议规定卡在识别过程中速度必须低于400KHZ,而此时已经完成上电识别,所以需要提高速度,其他时钟设置不变。由于刚刚读取的CSD和CID寄存器在SD卡存放时是以大端模式存放的,所以读取到的数据需要进行一次转换。大端模式是数据的低字节在高字节位,高字节在低字节位,而小端与其他相反,正常使用的都是小端模式(在ARM7中也提到了这个问题)。接下来是需要开启卡的四线模式,因为SD卡支持4位模式。想开启在四线模式,首先应该先选中卡,通过cmd7命令(参数为RCA地址,R1b短响应,R1响应内容都是卡状态寄存器,其他包括了卡的相关的状态)来选中需要操作的卡,靠RCA地址来区别各个卡。主机与卡之间的通信是需要对称的,如卡使用一线模式,那主机必须也要使用一线模式,所以应该先设置卡为四线模式,随后主机再开启四线模式,MMC不支持4线模式。当前卡也不一定支持4线模式的,这个信息通过卡SCR寄存器反应出来,SCR寄存器的值是通过数据方式传送给主机的,而不是像以上的寄存器是以命令响应的方式反应给主机,所以读取之前,还有判断卡是否上锁,这个信息是R1响应的卡状态寄存器中反应出来,如没有上锁,就可以读取SCR寄存器。那这里就不得不提前提到DPSM数据状态机了,读取数据之前一般需要发送一个cmd16命令(参数是数据块的大小《字节单位》,短响应)来设置数据块大小(此处也反应了主机与卡的之间的通信标准,双方相关设置需一样,先设置块大小,再设置主机传输的块大小),在之后的大数据量通信时,我们一般使用DMA方式传输数据,在这种情况下,数据传输之前,不设置块大小,程序将完死在等待DMA传输,这一点在各个电子论坛中已有提到。设置了卡块大小之后,再在SDIO_DCTRL寄存器中开启主机的数据传输块大小,还需要设置数值传输方向(卡到控制器)、传输模式(块传输)、数据块长度(0011 8字节),这里就可以看到SCR寄存器是8个字节的,设置完主机数据控制信息之后,能过命令通道发送ACMD51命令来要求卡向主机发送SCR寄存器数据。这里同样需要将数据转换一下(大小端)。读取了SCR值之后,就可以判断当前卡是否支持4线模式,如是SD2.0卡是支持4线模式的。之后发送ACMD6命令(参数为0x2表示4线,0表示1线,短响应)来开启卡的4线模式,在发送ACMD6命令之前,不要忘记发送一个CMD55命令(参数此时与上电识别时不知RCA地址不同了,需将参数设置为高16位为RCA地址,表示对指定地址的卡操作)。这时,我们已经开启了卡的4线模式,之后就应该开启主机的4线模式了。注:命令是单独在CMD线上单线传输的,与这里的4线模式不相干,这时提到的4线模式是指传输用户数据的数据线DAT【4:0】。开启卡的4线模式之后,卡就可以完成高速率的数据传输了。
以后完成整个卡的初始化,接下来就可以进行正常的数据传输了。需要编写文件系统所需要的卡的底层操作函数了,FATFS文件系统使用非常灵活,完全摆脱了硬件操作,只需要提供卡的底层驱动函数就是将该系统轻松移植到嵌入式处理器上,如ARM、PIC、AVR、51等,基于做到了不需要修改代码,就可以成功移植,虽然整个文件系统的代码也有几万行,这与Linux的移植比起来就简单多了。
文件系统需要程序员提供的函数有:块擦除函数、单数据块读取、多数据块读取、单数据块写入、多数据块写入,如:u8 SD_Erase(u32 startaddr,u32 endaddr);u8 SD_WriteBlock(u8 *WriteBuff,u32 WriteAddr,u32 BlockSize); u8 SD_ReadBlock(u8 *ReadBuff,u32 ReadAddr,u32 BlockSize);u8 SD_ReadMultiBlocks(u8* readbuff,u32 Readaddr,u16 BlockSize,u32 NumberOfBlock);u8 SD_WriteMultiBlocks(u8 *writebuff,u32 Writeaddr,u16 BlockSize,u32 NumberOfBlock);
数据块擦除还需要查看卡是否支持擦除操作,这一信息在CSD寄存器中反应出来,如卡上锁了,那也不能执行擦除操作。执行块擦除操作之后,所谓块擦除肯定关系到块的大小,而SDHC卡的块是固定大小,CMD16命令的设置将无效。通过cmd32命令向卡发送擦除的起始块地址(注:是块地址),通过cmd33命令向卡发送擦除的结束块地址(当前地址的块也擦除),这样做先将需要擦除的范围确定下来,再发送cmd38命令正式执行擦除操作。在程序设计时,刚发送完cmd38命令时,最好延时一段时间,再执行其他操作,因为执行大范围的擦除操作,卡的内部执行需要一段时间,这一段时间卡处于BUSY状态,将忽略其他命令,当卡不处于接收状态,也不处于编程状态(擦除也是这个动作),再执行其他操作。
单数据块读写,数据写之前一定要发送一个cmd16命令设置一下卡的数据块大小,SDHC卡的块大小固定,不受该命令影响,被忽略。然后发送cmd24命令,使进入相对应的接收状态,再设置SDIO_DCTRL数据控制寄存器,为了提高数据传输和程序的效率,将使能DMA,数据控制寄存器需要设置其数据块长度,传输模式(块传输)、传输方向(控制器到卡),还要设置数据长度寄存器(该寄存器在数据传输开始时,值被加载到数据计数器中)和数据定时器寄存器(超时时间)。在对应的DMA设置中也存在一个细节要注意,数据数量寄存器应设置一为数据总字节的四分之一,因为DMA数据宽度最高为32位(4个字节),是按字节数计算数据量的,也因为SDIO_FIFO也是32位的。之后等待DMA数据传输结束,并查询卡的状态,直到卡处于发送状态(这一点我也不是很清楚)。单数据块读取基于跟发送相同,只有传输方向有变化,还有一个就是设置主机,使主机处于Wait_R状态,后通过命令通道发送命令要求卡向主机发送数据。读取单数据块相关命令有cmd17命令(单数据块读取)。
多数据块读写。与单数据块的读写也有相同原理,使用的命令也不同。但主要有两点要注意,第一个就是多数据写入时,为了提高效率,需要发送一个预擦除命令(可以不用),数据的写入,其他卡内部执行了两个操作(先操作,再编程),预擦除只是提前让卡执行擦除操作,这个问题只有在多数据块写入时,需要注意,读取就不需要了。第二点就是数据传输结束时,需要发送一个停止数据传输命令cmd12,停止卡与主机之间的通信,因为多数据块写入命令执行过后,不执行停止命令,卡将一直从主机接收数据,即使主机已经停止,但卡仍认为有数据传输(主机停止时,数据处于释放状态,卡为认为数据全是1),相反读取多数据块数据也相似。多数据读写相关命令有ACMD23(预擦除命令)、CMD25(连续数据块写入)、CMD18(连续数据块读取)。
在以上使用DMA的数据传输的这此函数中,还有一个细节需要注间:SD_DMA_TxConfig((u32 *)writebuff,(NumberOfBlock*BlockSize));其中的参数writebuff在定义的时候u8*类型的,而这里将它强制转换成了u32*,这一点其实没有错误,数组在内存中存放是线性连续的,而本来的u8* writebuff中的writebuff标识的是该数组中内存中存放的首地址,并数据宽度以为1字节存放的,也就是说writebuff是字节地址,使用u32*将其转换成了字地址,如果这块空间中存放的是00 01 02 03 04 05 06 07,这样本为原来的*writebuff的值是00,而转换之后的*writebuff的值将变为00010203,这样再使用DMA传输,设置数据宽度为32位,将大大提高数据传输效率。
以上完成了基于SDIO模式的SD底层驱动函数,现在就可以移植文件系统了,我们将文件的文件拷到当前工程的文件下,添加进工程,再作简单修改就可以使用文件系统的接口函数完成文件的新建、打开、修改和关毕了,如新建一个DEMO.TXT文件,再在里面写入“文件系统成功移植”这样的汉字,再关毕文件,这个文件就存放在SD卡中了,可以由PC机在Windows系统正常打开了,文件的类型可以很多,不一定是TXT,如DOC也是可以的(我亲手测试过)。需要修改的文件系统代码是diskio.c,将SD卡的底层函数按要求填入相应位置,再将diskio.c中的添加头文件sdio.h(这是我的底层函数的头文件)。我在官网下载的程序,发现其中还添加了stm32f10x.h,这个头文件不能添加,需要删掉,不然将出现很多重定义的错误。
到这里就成功完成FATFS文件系统的移植,是不是也感觉很简单呢。刚开始学SDIO时,的确是不懂。因为SDIO是属于一种完全的外设接口,主要功能还需要根据外部器件来使用,所以意法半导体公司在Datasheet中也讲解到了很多SD2.0协议的内容,搞得内容很乱。但真正明白协议之后,就可以拨云见日了。
如需更深的了解,还是需要好好研究一下SD2.0协议的。
以上属于个人见解,如果存在问题,务必请大家指出。
转载
|