通过SPI接口使用SD卡。VHDL实施

哈勃!在工作后,我的任务是评估将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字节的块。

下图显示了地图的总体视图。


联系电话名称一种描述
1个RSV--未使用
2CS输入芯片选择
3DI输入来自主设备(MOSI)的数据线
4Vdd营养电源电压
5时钟输入时钟信号
6VS营养土地
7输出量数据线到主设备(MISO)
8RSV--未使用

根据下图进行连接:


电阻额定值必须为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. 接通电源后,至少等待1 ms。
  2. 至少为该卡生成74个时钟切换。CS和MOSI线必须处于逻辑单元状态。
  3. 生成命令CMD0。CMD0命令重置SD卡。
  4. CMD0. R1. , R1 16 , 3. R1 , 0x01 ( ), 3, 5.
  5. CMD8. .
  6. CMD8. R7. , , , 7, , , 17, 13.
  7. CMD55. CMD55 , .
  8. CMD55. R1. 0x01 ( ), 7, 9.
  9. ACMD41. ACMD41 , SDSC ( 0x00000000) .
  10. ACMD41. R1. , , 11. , 0x00 ( ) 14, 7.
  11. CMD1. CMD1 ACMD41.
  12. CMD1. R1. , , 13, , 0x00 ( ), 14, 11.
  13. . , . .
  14. CMD16. , 512 .
  15. CMD16. R1. , 0x00 ( ) 16, 13.
  16. . . .
  17. CMD55. CMD55 , .
  18. CMD55. R1. 0x01 ( ), 17, 19.
  19. ACMD41. ACMD41. ACMD41 , SDHC ( 0x40000000) .
  20. ACMD41. R1. , , 13. , 0x00 ( ) 21, 17.
  21. CMD58. .
  22. 等待对CMD58命令的响应。答案是R3类型的响应。如果响应中将卡设置为512字节的块地址,则设置为1,请转到步骤16,否则转到步骤14。

初始化完成后,该卡可以512字节的块形式运行,时钟频率为25 MHz。这是初始化算法的完整版,涵盖所有类型的卡。就我而言,使用16 GB卡时,初始化算法包括步骤1-6、17-22、16。

图形算法

1.3删除信息


Micro SD规格卡支持擦除命令。执行擦除命令后,根据卡的不同,指定的擦除地址的值将填充为0xFF或0x00。

信息擦除算法

  1. CMD32. .
  2. CMD32. R1. 0x00 ( - ), 3, 4.
  3. . , .
  4. CMD33. . , CMD32.
  5. CMD33. R1. 0x00 ( - ), 3, 6
  6. 发送CMD38命令。从选定块中擦除信息的命令。除值0x00000001、0x00000002外,任何4个字节都应作为参数发送。
  7. 等待对CMD38命令的响应。答案是类型R1b的响应。当卡生成R1响应,然后将MISO线绘制为零时(表示芯片正忙),这是答案的扩展版本。必须等到单位值出现在MISO行上。如果答案不是0x00(执行命令时出现错误),请转到步骤3,否则转到步骤8
  8. 完成擦除算法。

图形算法

1.4阅读信息


可以通过两种方式通过SPI从SD卡读取信息。

1.4.1读取单个数据块


当读取一个数据块时,主设备为SD卡生成一个命令以读取一个数据块,等待该命令已被处理的响应,并等待来自该卡的数据包。从卡接收到包含数据的数据包后,读取的事务结束。


读取一个数据块的事务的一般视图。

最初,实现了这样的选项,但是最终的速度非常令人不快(速度比较会更低)。

1.4.2读取多个数据块


当读取多个数据块时,主设备为SD卡生成一个命令以读取多个数据块,等待命令已被处理的响应,并期望从卡中获取数据包。发送数据包后,卡将发送下一个数据包。这将继续进行,直到从主设备接收到命令以完成读取为止。


读取许多数据块的事务的一般视图。


数据包结构

其中:

  • 数据令牌 读取命令使用值0xFE。
  • 数据块。包含从卡读取的数据。
  • CRC 包含来自先前字段的校验和。

如果读取时发生错误,则卡(而不是数据令牌)将返回错误令牌。错误令牌大小为1个字节。位7..5包含零值,位4..0编码错误类型。

读取多个数据块的算法:

  1. 发送CMD18命令。该命令告诉卡将读取多个块。
  2. 等待对CMD18命令的响应。答案是R1类型的响应。如果答案不是0x00(执行命令时出现错误),请转到步骤3,否则转到步骤4。
  3. 错误状态。读取失败,退出读取算法。
  4. 等待卡中的令牌。如果从卡收到错误令牌,请转到步骤3,否则转到步骤5。
  5. 从数据块卡接收512字节大小。
  6. 从卡接收CRC字段的大小为2个字节。
  7. . , 8, 4.
  8. CMD12. . , Data Packet, .
  9. CMD12. R1b. R1, MISO , . MISO , . 0x00 ( - ), 3, 10.
  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	(	-- Control bus
			iPhyTxData	: in	std_logic_vector( 9 downto 0);
			iPhyMode	: in	std_logic_vector( 4 downto 0);
			iPhyTxWrite	: in	std_logic;
			oPhyTxReady	: out	std_logic; 
			-- Out Data
			oPhyRxData	: out	std_logic_vector( 7 downto 0); 
			oPhyRxWrite	: out	std_logic;
			oPhyCmdEnd	: out	std_logic;
			-- Spi
			oSdCS		: out	std_logic;
			oSdClk		: out	std_logic;
			oSdMosi		: out	std_logic;
			oSdMosiT	: out	std_logic;
			iSdMiso		: in	std_logic;
			-- system
			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	(	-- Command from host
			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;
			-- Data
			oSdReadData	: out	std_logic;
			iSdDataR	: in	std_logic_vector(31 downto 0);
			oSdWriteData	: out	std_logic;
			oSdDataW	: out	std_logic_vector(32 downto 0);
			-- Spi
			oSdCS		: out	std_logic;
			oSdClk		: out	std_logic;
			oSdMosi		: out	std_logic;
			oSdMosiT	: out	std_logic;
			iSdMiso		: in	std_logic;
			-- system
			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	(	-- Sd Host command
			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;
			-- Write data to card
			iSdTxData	: in	std_logic_vector(31 downto 0);
			iSdTxValid	: in	std_logic;
			iSdTxLast	: in	std_logic;
			oSdTxReady	: out	std_logic;
			-- Read data from card
			oSdRxData	: out	std_logic_vector(31 downto 0);
			oSdRxValid	: out	std_logic;
			oSdRxLast	: out	std_logic;
			iSdRxReady	: in	std_logic;
			-- Spi
			oSdCS		: out	std_logic;
			oSdClk		: out	std_logic;
			oSdMosi		: out	std_logic;
			oSdMosiT	: out	std_logic;
			iSdMiso		: in	std_logic;
			-- system
			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.


该算法已编写,并在模拟器中进行了验证。现在有必要检查铁。DigilentZybo现有的产品中浮出水面, 它在电压为+ 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);
	}

	/** Set address */
	Xil_Out32(0x43c00008, SectorAddress);

	/** Set command */
	Xil_Out32(0x43c00004, 2);

	/** Write data to PL */
	for (int32_t i = 0; i < 1024; i++)
	{
		Xil_Out32(0x43c00014, cntrData);
		cntrData++;
	}

	/** Start */
	Xil_Out32(0x43c00000, 1);

	/** Wait end of operation */
	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);
		}
	}

	/** Duration operation */
	durationWrite += cntrDuration;

	if (cntrDuration > MaxWrite )
	{
		MaxWrite = cntrDuration;
	}

	cntrDuration = 0x00;

	/** Clear start */
	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);
	}

	/** Set address */
	Xil_Out32(0x43c00008, SectorAddress);

	/** Set command */
	Xil_Out32(0x43c00004, 1);

	/** Start */
	Xil_Out32(0x43c00000, 1);

	/** Wait end of operation */
	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);
		}
	}

	 /** Duration operation */
	 durationRead += cntrDuration;

	 if (cntrDuration > MaxRead )
	 {
		 MaxRead = cntrDuration;
	 }

	cntrDuration = 0x00;

	/** Clear start */
	Xil_Out32(0x43c00000, 0);

	/** Read data from PL */
	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);
	}

	/** Set address */
	Xil_Out32(0x43c00008, SectorAddress);

	/** Set command */
	Xil_Out32(0x43c00004, 4);

	/** Start */
	Xil_Out32(0x43c00000, 1);

	/** Wait end of operation */
	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);
		}
	}

	/** Duration operation */
	durationErase += cntrDuration;

	if (cntrDuration > MaxErase )
	{
		MaxErase = cntrDuration;
	}

	cntrDuration = 0x00;

	/** Clear start */
	Xil_Out32(0x43c00000, 0);

	SectorAddress += 7;
}

在github上完全测试。

4.结果


数据量阅读记录擦除
1个块(512字节)4.7 Mbps1.36 Mbps的0.58 Mbps的
8个块(4096字节)15.4 Mbps的6.38 Mbps的4.66 Mbps的
16个块(8192字节)18.82 Mbps的11.26 Mbps的9.79 Mbps

使用了16 GB卡。在测试过程中,记录了2 GB的数据,读取了2 GB的数据,并擦除了2 GB的数据。

结论令人失望。使用FPGA时,除非非常需要在不提出速度要求的情况下存储大量数据的情况下,否则在SPI模式下使用SD卡毫无意义。

All Articles