哈勃!在工作后,我的任务是评估将SD卡连接到FPGA时在SD卡上实现数据存储的可能性。SPI的使用被假定为交互接口,因为它更易于实现。我想分享获得的经验。
由于印刷电路板上的空间总是有限的,因此将以microSD尺寸规格的卡为例对SD卡进行考虑。内容
1.读取规范1.1一般信息1.2初始化1.3擦除信息1.4读取信息1.4.1读取一个数据块1.4.2读取许多数据块1.5写入信息1.5.1写入一个数据块1.5.2写入许多数据块2.实现算法在硬件中2.1物理层的组成部分2.2命令级的组成部分2.3与外界通信的组成部分3.在硬件中的验证4.结果1.阅读规范
1.1总则
快速阅读规格可以了解以下SD卡选项:- 到SD卡的数据传输是一条线上进行的;
- 从SD卡中读取数据是一行完成的;
- 在SPI模式下,电源只能为+ 3.3V;
- 初始化模式下的时钟频率为100-400 kHz;
- 初始化后的时钟频率-25 MHz。
这立即暗示了理论峰值带宽上的一点:25 MHz * 1位= 25 Mb / s,这有点小。第二秒减去使用SD卡的功率+ 3.3V。在计划安装SD卡的印刷电路板上,没有这样的电压。MicroSD尺寸卡可以按容量分为三类:- SDSC 卡容量最大为2 GB(含)。卡内的地址为字节。
- SDHC。卡的容量大于2 GB,最大包括32 GB。卡内的地址表示一个512字节的块。
- SDXC。卡的容量超过32 GB,最高包括128 TB。卡内的地址表示一个512字节的块。
下图显示了地图的总体视图。
根据下图进行连接:
电阻额定值必须为50 kOhm。接通电源后,SD卡处于SDIO模式。要切换到SPI模式,必须执行初始化。与卡配合使用的协议涉及使用控制方案,以CRC算法的形式正确传输数据和命令。在SPI模式下运行时,默认情况下禁用CRC验证。因此,发送到卡的第一个命令将卡切换到SPI模式必须包含正确的CRC值。通过SPI接口传输的命令大小为48位。命令格式:- 位47(最左侧)始终包含值0。
- 位46始终包含值1。
- 45..40位包含命令索引。
- 39..8位包含命令的参数。
- 位7..1包含先前位的CRC。
- 位0始终包含值1。
由于静止的MOSI总线等于1,因此位47和46使该卡能够明确地跟踪事务的开始。
使用SD卡时使用的命令将收到诸如R1,R3,R7之类的答案。
类型为R1的答案,大小为8位。答案类型为R3,大小为40位。答案类型为R7,大小为40位。
所有命令及其答案在《物理层简化规范》中都有详细说明。

1.2初始化
初始化算法:- 接通电源后,至少等待1 ms。
- 至少为该卡生成74个时钟切换。CS和MOSI线必须处于逻辑单元状态。
- 生成命令CMD0。CMD0命令重置SD卡。
- CMD0. R1. , R1 16 , 3. R1 , 0x01 ( ), 3, 5.
- CMD8. .
- CMD8. R7. , , , 7, , , 17, 13.
- CMD55. CMD55 , .
- CMD55. R1. 0x01 ( ), 7, 9.
- ACMD41. ACMD41 , SDSC ( 0x00000000) .
- ACMD41. R1. , , 11. , 0x00 ( ) 14, 7.
- CMD1. CMD1 ACMD41.
- CMD1. R1. , , 13, , 0x00 ( ), 14, 11.
- . , . .
- CMD16. , 512 .
- CMD16. R1. , 0x00 ( ) 16, 13.
- . . .
- CMD55. CMD55 , .
- CMD55. R1. 0x01 ( ), 17, 19.
- ACMD41. ACMD41. ACMD41 , SDHC ( 0x40000000) .
- ACMD41. R1. , , 13. , 0x00 ( ) 21, 17.
- CMD58. .
- 等待对CMD58命令的响应。答案是R3类型的响应。如果响应中将卡设置为512字节的块地址,则设置为1,请转到步骤16,否则转到步骤14。
初始化完成后,该卡可以512字节的块形式运行,时钟频率为25 MHz。这是初始化算法的完整版,涵盖所有类型的卡。就我而言,使用16 GB卡时,初始化算法包括步骤1-6、17-22、16。1.3删除信息
Micro SD规格卡支持擦除命令。执行擦除命令后,根据卡的不同,指定的擦除地址的值将填充为0xFF或0x00。信息擦除算法- CMD32. .
- CMD32. R1. 0x00 ( - ), 3, 4.
- . , .
- CMD33. . , CMD32.
- CMD33. R1. 0x00 ( - ), 3, 6
- 发送CMD38命令。从选定块中擦除信息的命令。除值0x00000001、0x00000002外,任何4个字节都应作为参数发送。
- 等待对CMD38命令的响应。答案是类型R1b的响应。当卡生成R1响应,然后将MISO线绘制为零时(表示芯片正忙),这是答案的扩展版本。必须等到单位值出现在MISO行上。如果答案不是0x00(执行命令时出现错误),请转到步骤3,否则转到步骤8
- 完成擦除算法。
1.4阅读信息
可以通过两种方式通过SPI从SD卡读取信息。1.4.1读取单个数据块
当读取一个数据块时,主设备为SD卡生成一个命令以读取一个数据块,等待该命令已被处理的响应,并等待来自该卡的数据包。从卡接收到包含数据的数据包后,读取的事务结束。
读取一个数据块的事务的一般视图。最初,实现了这样的选项,但是最终的速度非常令人不快(速度比较会更低)。1.4.2读取多个数据块
当读取多个数据块时,主设备为SD卡生成一个命令以读取多个数据块,等待命令已被处理的响应,并期望从卡中获取数据包。发送数据包后,卡将发送下一个数据包。这将继续进行,直到从主设备接收到命令以完成读取为止。
读取许多数据块的事务的一般视图。
数据包结构其中:- 数据令牌 读取命令使用值0xFE。
- 数据块。包含从卡读取的数据。
- CRC 包含来自先前字段的校验和。
如果读取时发生错误,则卡(而不是数据令牌)将返回错误令牌。错误令牌大小为1个字节。位7..5包含零值,位4..0编码错误类型。读取多个数据块的算法:- 发送CMD18命令。该命令告诉卡将读取多个块。
- 等待对CMD18命令的响应。答案是R1类型的响应。如果答案不是0x00(执行命令时出现错误),请转到步骤3,否则转到步骤4。
- 错误状态。读取失败,退出读取算法。
- 等待卡中的令牌。如果从卡收到错误令牌,请转到步骤3,否则转到步骤5。
- 从数据块卡接收512字节大小。
- 从卡接收CRC字段的大小为2个字节。
- . , 8, 4.
- CMD12. . , Data Packet, .
- CMD12. R1b. R1, MISO , . MISO , . 0x00 ( - ), 3, 10.
- .
读取算法略有细微差别。芯片选择信号(CS)必须在生成CMD18命令之前设置为逻辑零,并在收到对CMD12命令的响应之后设置为逻辑1。1.5记录信息
有两种版本可以通过SPI接口将信息写入SD卡。1.5.1编写单个数据块
当记录一个数据块时,主设备会为SD卡生成一个命令以写入一个数据块,等待卡中对该命令已进行处理的响应,将包含要记录的数据的数据包发送到卡中,并期望卡中写入数据的响应完成交易。
一个数据块的写事务的一般视图。与读取一样,最初实现了一个数据块的记录。速度结果不令人满意。1.5.2写入多个数据块
当记录多个数据块时,主设备会生成一个命令来记录多个数据块,等待卡中对该命令已进行处理的响应,将包含要记录的数据的数据包发送到卡中,并期望卡中记录了该数据的响应。收到主设备响应后,设备将带有以下数据的数据包发送到卡上进行记录。这将持续到主设备发送停止数据令牌为止。
多个数据块的写事务的一般视图。数据包的结构类似于读取数据时数据包的结构。格式:- 数据令牌 对于写命令,使用值0xFC。
- 数据块。包含写入卡的数据。
- CRC 包含来自先前字段的校验和。
用于完成写命令的Stop Tran令牌的大小为1个字节,等于0xFD。数据包的最后一位被推入卡后,卡以数据记录的状态-数据响应来响应下一个时钟。数据响应的大小为1个字节,位7..5可以为任意,位4始终为零,位0始终等于1,位3..1编码数据记录的状态。卡返回数据包后,卡将MISO线拉到零,表示卡正忙。逻辑单元级别在MISO线上后,您可以将下一个数据包传输到卡上。记录多个数据块的算法:- 发送CMD25命令。该命令告诉卡将写入多个块。
- CMD25. R1. 0x00 ( - ), 3, 4.
- . , .
- .
- 512 .
- CRC 2 .
- Data Response . , 3, 8.
- . , 9, 4.
- Stop Tran Token. , .
- 等待卡的响应。Stop Tran Token上的卡将MISO线拉到零,表示卡正忙。必须在MISO行上等待单位值,这将指示命令的结束
- 记录算法的完成。
记录算法也有细微差别。芯片选择信号(CS)必须在生成CMD25命令之前设置为逻辑零,并在收到对Stop Tran令牌的响应后设置为逻辑1。2.该算法在硬件中的实现
阅读规范的结果是,我们获得了需要在硬件中实现的算法的某些特性。可能的操作模式:- 初始频率形成。CS和MOSI线必须处于逻辑单元状态。
- 将数据传输到SD卡。
- 从SD卡接收数据。
- 等待团队的完成。在生成响应后,SD卡将MISO线拉至零以等待单元出现时使用。
- 写入数据时读取响应。
- 在读取数据时等待令牌。
可能的时钟模式:从我的角度来看,该算法使用以下三个组件进行了最佳实现:- 直接连接到SD卡的物理层组件生成SCLK,CS,DI信号,并通过DO读取。
- 一个命令级组件,为物理层中的组件准备所有数据。
- 与外界通信的一个组件,它隐藏了整个内部设备,并为命令(读取,写入,擦除)和数据提供了接口。
2.1物理层组件
entity SDPhy is
generic ( gTCQ : time := 2 ns );
port (
iPhyTxData : in std_logic_vector( 9 downto 0);
iPhyMode : in std_logic_vector( 4 downto 0);
iPhyTxWrite : in std_logic;
oPhyTxReady : out std_logic;
oPhyRxData : out std_logic_vector( 7 downto 0);
oPhyRxWrite : out std_logic;
oPhyCmdEnd : out std_logic;
oSdCS : out std_logic;
oSdClk : out std_logic;
oSdMosi : out std_logic;
oSdMosiT : out std_logic;
iSdMiso : in std_logic;
sclk : in std_logic;
pclk : in std_logic;
rst : in std_logic );
end SDPhy;
哪里:- iPhyTxData , iPhyMode , .
- iPhyTxWrite , iPhyTxData iPhyMode .
- oPhyTxReady , . FULL FIFO, .
- oPhyRxData , SD-.
- oPhyRxWrite , oPhyRxData .
- oPhyCmdEnd , .
- oSdCS (CS) SD-.
- oSdClk SD-.
- oSdMosi SD-.
- oSdMosiT SD-.
- iSdMiso SD-.
- sclk SD- (50 ).
- pclk , .
- 第一个复位信号,有效电平为一。
在FPGA中,有一些用于处理时钟信号的特殊单元(PLL,MMCM),但是,从其输出接收小于5 MHz的时钟信号是有问题的。结果,物理层以50 MHz的频率运行。随同iPhyMode信号中的每个数据一起,接收到一个位,指示这些数据应以什么频率传输到SD卡(或从SD卡接收)。根据速度位,生成时钟使能信号。在物理层的组件中实现了两个自动机,用于将数据传输到SD卡并从中接收数据。数据传输的机器代码:github。- SDummy状态提供初始频率整形,128个切换。
- STxBits状态提供数据到SD卡的传输。
用于接收数据的机器代码:github。- sRxBits状态提供来自SD卡的数据接收。
- 忙状态确保SD卡准备就绪(卡将MISO线释放到设备级别)。
- sResp状态实现了在写入数据时读取响应。
- sToken状态在读取数据时实现令牌等待。
2.2命令级组件
entity SdCommand is
generic ( gTCQ : time := 2 ns );
port (
oSdInitComp : out std_logic;
oSdInitFail : out std_logic;
iSdAddress : in std_logic_vector(31 downto 0);
iSdStartErase : in std_logic;
iSdStartRead : in std_logic;
iSdStartWrite : in std_logic;
oSdCmdFinish : out std_logic_vector( 1 downto 0);
oSdhcPresent : out std_logic;
oSdReadData : out std_logic;
iSdDataR : in std_logic_vector(31 downto 0);
oSdWriteData : out std_logic;
oSdDataW : out std_logic_vector(32 downto 0);
oSdCS : out std_logic;
oSdClk : out std_logic;
oSdMosi : out std_logic;
oSdMosiT : out std_logic;
iSdMiso : in std_logic;
pclk : in std_logic;
sclk : in std_logic;
rst : in std_logic );
哪里:- oSdInitComp标志完成SD卡的初始化。
- oSdInitFail初始化失败的迹象。
- SD卡中的iSdAddress地址以执行命令。
- iSdStartErase开始擦除命令执行。
- iSdStartRead开始读取命令执行。
- iSdStartWrite开始执行写命令。
- oSdCmdFinish团队完成状态。零位等于1,命令成功完成。第一位为1,命令完成并显示错误。
- oSdhcPresent SDHC / SDXC卡检测标志。
- oSdReadData读取数据以写入SD卡。
- 用于写入SD卡的iSdDataR数据。
- oSdWriteData标志用于写入从SD卡读取的数据。
- 从SD卡读取的oSdDataW数据。
其余信号与物理层的信号一致。该组件具有5台自动机。- smSdInit(github)-SD卡的初始化。
- smSdErase(github)-从SD卡中删除数据。
- smSdRead(github)-从SD卡读取数据。
- smSdWrite(github)-将数据写入SD卡。
- smSdCommand(github)-根据生成的功能为所有以前的机器的物理层准备数据。
2.3与外界沟通的组成部分
entity SdHost is
generic ( gTCQ : time := 2 ns );
port (
iSdCommand : in std_logic_vector( 2 downto 0);
iSdAddress : in std_logic_vector(31 downto 0);
iSdStart : in std_logic;
oSdStatus : out std_logic_vector( 1 downto 0);
oSdInitFail : out std_logic;
iSdTxData : in std_logic_vector(31 downto 0);
iSdTxValid : in std_logic;
iSdTxLast : in std_logic;
oSdTxReady : out std_logic;
oSdRxData : out std_logic_vector(31 downto 0);
oSdRxValid : out std_logic;
oSdRxLast : out std_logic;
iSdRxReady : in std_logic;
oSdCS : out std_logic;
oSdClk : out std_logic;
oSdMosi : out std_logic;
oSdMosiT : out std_logic;
iSdMiso : in std_logic;
pclk : in std_logic;
sclk : in std_logic;
rst : in std_logic );
哪里:- 要执行的iSdCommand命令代码。
- iSdAddress是执行命令的地址。
- iSdStart启动命令执行。
- oSdStatus团队的完成状态。零位等于一-命令完成。第一位为1-命令完成但有错误。
- oSdInitFail初始化失败的迹象。
- iSdTxData。Axi-Stream接口,用于将数据写入SD卡。带有数据的端口。
- iSdTxValid。Axi-Stream接口,用于将数据写入SD卡。具有写信号的端口。
- iSdTxLast。Axi-Stream接口,用于将数据写入SD卡。带有数据中最后一个dw的符号的端口。
- oSdTxReady。Axi-Stream接口,用于将数据写入SD卡。端口已准备好接收数据。
- oSdRxData。Axi-Stream接口,用于从SD卡读取数据。带有数据的端口。
- oSdRxValid。Axi-Stream接口,用于从SD卡读取数据。具有写信号的端口。
- oSdRxLast。Axi-Stream接口,用于从SD卡读取数据。带有数据中最后一个dw的符号的端口。
- iSdRxReady。Axi-Stream接口,用于从SD卡读取数据。端口已准备好接收数据。
其余信号与物理层的信号一致。该组件实现一台smSdControl(github)机器。- 空闲状态。等待初始化和命令完成。
- sWaitCmd的状态。检查命令类型。
- sReadCmd. FIFO, , SD- .
- sWriteCmd. , FIFO SD-, .
- sEraseCmd. .
- sWaitEnd. .
- sFinish. , .
3.
该算法已编写,并在模拟器中进行了验证。现在有必要检查铁。Digilent的Zybo板从现有的产品中浮出水面,
它在电压为+ 3.3V的组中具有免费的FPGA端子,您可以轻松地将其连接到外部设备。是的,使用的FPGA类型是Zynq-7000,这意味着有一个处理器核心。您可以使用C编写测试,这将简化测试任务。
因此,我们通过GP端口将实现的算法连接到处理器内核(可以进行4字节操作,类似于PIO)。我们不会打扰您;我们实施了计时器轮询。
在处理器模块上工作时,数据记录算法如下:
- 在SD卡中设置地址。
- 设置命令代码2。
- 将数据写入位于可编程逻辑中的缓冲区。
- 运行命令。
- 等待命令完成。
- 重置团队完成状态。
实施测试:for (SectorAddress = 0; SectorAddress < 1048576; SectorAddress ++)
{
if ((SectorAddress % 1024) == 0)
{
xil_printf("Data write to %d sector \n\r", SectorAddress);
}
Xil_Out32(0x43c00008, SectorAddress);
Xil_Out32(0x43c00004, 2);
for (int32_t i = 0; i < 1024; i++)
{
Xil_Out32(0x43c00014, cntrData);
cntrData++;
}
Xil_Out32(0x43c00000, 1);
for (;;)
{
status = Xil_In32(0x43c0000c);
if (status == 0x01 || status == 0x03)
{
if (status == 0x03)
{
xil_printf("Error in write \n\r");
}
break;
}
else
{
cntrDuration++;
usleep(100);
}
}
durationWrite += cntrDuration;
if (cntrDuration > MaxWrite )
{
MaxWrite = cntrDuration;
}
cntrDuration = 0x00;
Xil_Out32(0x43c00000, 0);
SectorAddress += 7;
}
关于为什么在循环中使用1024的外部边界的问题,块数设置为8,一个块的大小为512字节。8个数据块的总大小为8 * 512字节= 4096字节。处理器模块和可编程逻辑之间的总线大小为4个字节。事实证明,要将4096个字节的4字节字节从处理器模块发送到可编程逻辑,必须执行4096/4 = 1024写操作。在处理器模块上工作时,数据读取算法如下:- 在SD卡中设置地址。
- 设置命令代码1。
- 运行命令。
- 等待命令完成。
- 重置团队完成状态。
- 以可编程逻辑从缓冲区读取数据。
实施测试:for (SectorAddress = 0; SectorAddress < 1048576; SectorAddress++)
{
if ((SectorAddress % 1024) == 0)
{
xil_printf("Data read from %d sector \n\r", SectorAddress);
}
Xil_Out32(0x43c00008, SectorAddress);
Xil_Out32(0x43c00004, 1);
Xil_Out32(0x43c00000, 1);
for (;;)
{
status = Xil_In32(0x43c0000c);
if (status == 0x01 || status == 0x03)
{
if (status == 0x03)
{
xil_printf("Error in read \n\r");
}
break;
}
else
{
cntrDuration++;
usleep(100);
}
}
durationRead += cntrDuration;
if (cntrDuration > MaxRead )
{
MaxRead = cntrDuration;
}
cntrDuration = 0x00;
Xil_Out32(0x43c00000, 0);
for (int32_t i = 0; i < 1024; i++)
{
DataR = Xil_In32(0x43c0001c);
if (DataR != cntrData)
{
xil_printf("Data corrupt! \n\r");
}
DataR = Xil_In32(0x43c00020);
cntrData++;
}
SectorAddress += 7;
}
在处理器模块上工作时,数据擦除算法如下:- 在SD卡中设置地址。
- 设置命令代码4。
- 运行命令。
- 等待命令完成。
- 重置团队完成状态。
实施测试:for (SectorAddress = 0; SectorAddress < 1048576; SectorAddress++)
{
if ((SectorAddress % 1024) == 0)
{
xil_printf("Data erase from %d sector \n\r", SectorAddress);
}
Xil_Out32(0x43c00008, SectorAddress);
Xil_Out32(0x43c00004, 4);
Xil_Out32(0x43c00000, 1);
for (;;)
{
status = Xil_In32(0x43c0000c);
if (status == 0x01 || status == 0x03)
{
if (status == 0x03)
{
xil_printf("Error in write! \n\r");
}
break;
}
else
{
cntrDuration++;
usleep(100);
}
}
durationErase += cntrDuration;
if (cntrDuration > MaxErase )
{
MaxErase = cntrDuration;
}
cntrDuration = 0x00;
Xil_Out32(0x43c00000, 0);
SectorAddress += 7;
}
在github上完全测试。4.结果
使用了16 GB卡。在测试过程中,记录了2 GB的数据,读取了2 GB的数据,并擦除了2 GB的数据。结论令人失望。使用FPGA时,除非非常需要在不提出速度要求的情况下存储大量数据的情况下,否则在SPI模式下使用SD卡毫无意义。