基于FPGA实现SD NAND FLASH的SPI协议读写

  论坛元老 | 2024-9-3 23:47:27 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 1734|帖子 1734|积分 5202


基于FPGA实现SD NAND FLASH的SPI协议读写

在此介绍的是使用FPGA实现SD NAND FLASH的读写操作,以雷龙发展提供的CS创世SD NAND FLASH样品为例,分别讲解电路毗连、读写时序与仿真和实验效果。
目录
1 FLASH背景介绍
2 样品申请
3 电路结构与接口协议
3.1 SD NAND
3.2 SD NAND测试板
3.3 FPGA开辟板
4 SD卡协议与时序流程
4.1 SD卡协议
4.2 SD卡2.0版本初始化步骤
4.3 SD卡的读步骤
4.4 SD卡的写步骤
5 模块代码
5.1 sd_card_top
5.2 sd_card_cmd
5.3 sd_card_sec_read_write
5.4 spi_master
5.5 别的代码
5.5.1 sd_card_test
5.5.2 ax_debounce
5.5.3 seg_decoder
5.5.4 seg_scan
6 实验效果
使用FPGA讲解SD NAND FLASH的文章网上也有许多比较详实的内容,本文的部分思路也是参考了其他博主的博客思路。
1 FLASH背景介绍
简介
Flash是近些年应用最广、速率最快的只读存储器,原理是从 EEPROM 基础上改进发展来的,特点是擦除和编程速率快,因此得名为闪速(或闪耀)存储器,简称闪存。
NOR Flash 和 NAND Flash 是如今市场上两种主要的闪存技能。Intel于1988年首先开辟出 NOR Flash 技能,彻底改变了原先由 EPROM 和 EEPROM 一统天下的局面。紧接着,1989年,东芝公司发表了 NAND Flash 结构,后者的单元电路尺寸险些只是 NOR 器件的一半,可以在给定的芯片尺寸内提供更高的容量,也就相应地低落了代价。
NOR Flash 的特点是以字节为单位随机存取。这样,应用步伐可以直接在 Flash中执行,不必再把步伐代码预先读到 RAM 中。NOR Flash的接口一般以SPI为主,与通常的扩展存储器一样,可以直接毗连随处理器的外围总线上。
NAND Flash应该是如今最热门的存储芯片了。因为我们生存中经常使用的电子产物都会涉及到它。比如你买手机,肯定会思量64GB,还是256GB?买条记本是买256GB,还是512GB容量的硬盘呢?(如今电脑大部分采用了基于 NAND Flash 产物的固态硬盘)。
NAND Flash 主要分为SLC,MLC,TLC,3D TLC ,3DQLC等,随时科技的发展和大众的需求,单位面积内的存储容量越来越大。SLC是指单个存储单元中,能容纳1bit 代表2种状态,0或者1. MLC 则是指单个存储单元中,能容纳2bit,代表4种状态 ,00,01,10,11。 TLC 则是指单个存储单元中,能容纳3bit,代表8种状态,000,001,010,011,100,101,110,111。最开始整个存储单元是2D展开的,也就是平面的,随着必要在单位空间内容纳更多的信息,就开始类似盖楼房一样,在3D也就是立体的方面来发展了。
如果在产物中选择nor flash 还是NAND FLASH,更多的时间是从容量角度来考量,如果存储的内容大于128Mbit 就选择NAND Flash ,小于128Mbit就选择 Nor flash。Nor flash受限于本身的工艺,大于128Mbit的容量,代价就会比128MB SLC NANDFLASH的代价还要贵。
将来发展
当前,NAND flash正在从 2D 发展到 3D。对于 2D NAND flash,如果在同一区域实现更多的单元数目,形成更小的工作区和栅级,便能增大存储容量。直至 2010 年初,2D NAND flash中的扩展一直是这项技能的主要焦点所在;然而,由于内部结构的限定,且储存数据会随时间推移而丢失导致使用寿命缩短,2D的技能已无法再实现扩展。
因此,3D NAND flash渐渐取而代之,成为业界关注焦点,如今所有 NAND 制造商都在开辟和制造 3D NAND flash产物。
在 3D NAND flash 的结构中,存储容量会随着三维叠层中堆叠层数的增长而变大,类似盖楼房,一层一层叠加上去。3D NAND flash 使用了堆叠多层氮氧化物的方法,形成一个被称为“塞子”的垂直深孔,在此中形成一个由氧化物-氮化物-氧化物制成的存储设备。通过这种方法,仅需少量工艺即可同时形成大量单元。在 3D NAND flash 中,电流通过位于圆柱单元中央的多晶硅通道,便能根据存储在氮化硅中的电荷范例实现存储编程和擦除信息。虽然2D NAND flash 技能发展的目的是实现形成较小的单元, 3D NAND flash 的核心技能却是实现更多层数的三维堆叠。
由于NAND FLASH在大容量应用中的便利性,因此作为本日介绍的主角~
什么是SD NAND呢(以下省略FLASH)?下面的内容是从雷龙发展官网的介绍中得到:
SD NAND俗称贴片式TF卡,尽管与TF卡名称类似,但是有较大的区别:

相比常见的TF卡,SD NAND是专门为内置存储进行设计,焊接在PCB板上以供工业级产物的应用。因此对品质稳定性、同等性、以及尺寸都有较高的要求。
2 样品申请
本文所使用的CS创世SD NAND是从深圳雷龙发展申请获得,可以在官网中最上面找到申请样品的入口:

雷龙发展有限公司建立于2008年,专注NAND Flash领域13年。首创人均为步步高/华为技能背景身世。如果有技能题目也可以和其公司人员进行沟通,相关的工作人员非常专业和热心。
3 电路结构与接口协议
3.1 SD NAND
本文所使用的产物是CSNP4GCR01-AMW,是雷龙的第二代产物,产物如下图所示:

数据手册可以在立创商城进行下载,其封装与毗连的电路原理参考图如下图所示:


芯片共包罗8个引脚,包罗4根数据线(6、7、1、2);2根电源线(4、8);1根时钟线(3);1根命令控制线(5)
手册中提供了SD NAND的两种使用模式,分别为SD MODE 以及 SPI MODE。他们所对应的引脚定义,如下图所示:

对于两种模式的切换,官方给出了初始化的方式。下文在代码的时序部分也会涉及到相关内容。
在对SD卡数据读写速率要求不高的情况下,选用SPI通信模式可以说是一种最佳方案。因为在该模式下,同只必要通过四根线就是可以完成所有的数据交换,可以为我们节流出宝贵的FPGA I/O资源。下图给出了SPI一对一通信时,主设备与从设备之间的毗连关系。

因此本文主要介绍SPI MODE下各个引脚的功能:

确定了通讯模式后,也就便于我们后文中,使用这种通讯模式按照SD卡的读写时序进行读写操作。
3.2 SD NAND测试板
单独的SD NAND不便于我们使用FPGA进行读写测试,幸亏官方提供了测试板,如下图所示:

有了它就可以轻松实现SD NAND与我们常见的FPGA开辟板上的Micro SD插槽进行毗连与测试了。
实用产物GA8,6x8mm 封装的SD NAND产物。
测试板尺寸:长度6.22厘米,宽度2.49厘米,接口长度2.53厘米。
使用方法:将芯片焊接至测试板上,可在原有的Micro SD卡座上直接调试和测试。
准备工具:热风枪,烙铁,锡膏,镊子。
焊接方式: 先用烙铁将芯片的8个PIN脚上锡,中央的一个PIN脚不必要上锡保持NC即可。再将接板板上,对应芯片的8个PIN上锡。
末了用镊子将芯片放到PCB上,热风枪温度调至350℃ 在芯片表面均匀加热即可焊接。

​ 别的事项:测试板上别的元器件无需理会,直接将芯片焊接在测试板上即可当SD卡一样调试。
焊接好后,可以将转接板插入到读卡器,再将读卡器毗连到电脑上看看是否能精确辨认到容量,通过这个方式来判定芯片是否已经焊接正常。
3.3 FPGA开辟板
本文所使用的是黑金的AX301开辟板,上面装有一个 Micro SD 卡座, FPGA 通过 SPI 数据总线访问 Micro SD 卡,SD 卡座和 FPGA 的硬件电路毗连如下:

借由硬件电路的毗连,FPGA可以直接与我们的SD NAND进行通信了。
至此,我们已经实现了SD NANDSPI通信方式方案简直定以及基于此的硬件电路毗连,下一步就是根据SD卡的读写时序讲通信方式初始化为SPI模式,并按照SD卡协议进行读写操作。
4 SD卡协议与时序流程
4.1 SD卡协议
以下内容来自黑金的实验手册:
SD 卡的协议是一种简单的命令/响应的协议。全部命令由主机发起, SD 卡接收到命令后并返
反响应数据。根据命令的不同,返回的数据内容和长度也不同。 SD 卡命令是一个 6 字节组成的命
令包,此中第一个字节为命令号, 命令号高位 bit7 和 bit6 为固定的“01“,别的 6 个 bit 为具体
的命令号。第 2 个字节到第 5 个字节为命令参数。第 6 个字节为 7 个 bit 的 CRC 校验加 1 个 bit 的竣事位。 如果在 SPI 模式的时间, CRC 校验位为可选。 如下图所示, Command 表示命令,通常使用十进制表示名称,比方 CMD17,这个时间 Command 就是十进制的 17。
对于具体的SD卡协议内容,可以参考传送门中的相关内容,给出了比较具体的表明。

SD 卡对每个命令会返回一个响应,每个命令有一定的响应格式。响应的格式跟给它的命令号
有关。在 SPI 模式中,有三种响应格式: R1, R2, R3。

在进行SD NAND的SPI模式读写操作时,主要使用到了以下几种SD卡命令,下面的表格进行简单介绍,这里可以找到完整版:

4.2 SD卡2.0版本初始化步骤
上电后延时至少 74clock,等待 SD 卡内部操作完成
片选 CS 低电平选中 SD 卡
发送 CMD0,必要返回 0x01,进入 Idle 状态
为了区别 SD 卡是 2.0 还是 1.0,或是 MMC 卡,这里根据协议向上兼容的,首先发送只有SD2.0 才有的命令 CMD8,如果 CMD8 返回无错误,则初步判定为 2.0 卡,进一步循环发送命令 CMD55+ACMD41,直到返回 0x00,确定 SD2.0 卡
如果 CMD8 返回错误则判定为 1.0 卡还是 MMC 卡,循环发送 CMD55+ACMD41,返回无错误,则为 SD1.0 卡,到此 SD1.0 卡初始乐成,如果在一定的循环次数下,返回为错误,则进一步发送 CMD1 进行初始化,如果返回无错误,则确定为 MMC 卡,如果在一定的次数下,返回为错误,则不能辨认该卡,初始化竣事。 (通过 CMD16 可以改变 SD 卡一次性读写的长度)
CS 拉高
4.3 SD卡的读步骤
发送 CMD17(单块)或 CMD18(多块)读命令,返回 0X00
接收数据开始令牌 fe(或 fc) +正式数据 512Bytes + CRC 校验 2Bytes(默认正式传输的数据长度是 512Bytes)
4.4 SD卡的写步骤
发送 CMD24(单块)或 CMD25(多块)写命令,返回 0X00
发送数据开始令牌 fe(或 fc) +正式数据 512Bytes + CRC 校验 2Bytes
5 模块代码
本代码所实现的功能,是基于黑金AX301B,实现对SD NAND FLASH的数据写入与读取,并显示在开辟板的数码管上。当按下开辟板上的按键时,会自动将数据加一操作,并进行同步显示。
前文介绍的是SD NAND的协议以及初始化、读写操作的流程,下面介绍代码的组成部分,整个工程主要由以下部分模块构成:
sd_card_test(top模块)
ax_debounce:ax_debounce_m0(按键消抖模块)
sd_card_top:sd_card_top_m0(SD卡top模块)
sd_card_cmd:sd_card_cmd_m0(SD卡指令)
sd_card_sec_read_write:sd_card_sec_read_write_m0(SD卡读写)
spi_master:spi_master_m0(SPI一个字节读写)
seg_decoder:seg_decoder_m0(数码管控制)
seg_decoder:seg_decoder_m1(数码管控制)
seg_scan:seg_scan_m0(数码管控制)
下面主要介绍上述四个加粗的模块以及其功能
5.1 sd_card_top
本模块是SD card的top模块,用来实现不同子模块之间的毗连。
  1. //  Author: meisq                                                               //
  2. //          msq@qq.com                                                          //
  3. //          ALINX(shanghai) Technology Co.,Ltd                                  //
  4. //          heijin                                                              //
  5. //     WEB: http://www.alinx.cn/                                                //
  6. //     BBS: http://www.heijin.org/                                              //
  7. //                                                                              //
  8. //
  9. //                                                                              //
  10. // Copyright (c) 2017,ALINX(shanghai) Technology Co.,Ltd                        //
  11. //                    All rights reserved                                       //
  12. //                                                                              //
  13. // This source file may be used and distributed without restriction provided    //
  14. // that this copyright statement is not removed from the file and that any      //
  15. // derivative work contains the original copyright notice and the associated    //
  16. // disclaimer.                                                                  //
  17. //                                                                              //
  18. //
  19. //==========================================================================
  20. //  Revision History:
  21. //  Date          By            Revision    Change Description
  22. //--------------------------------------------------------------------------
  23. //  2017/6/21    meisq         1.0         Original
  24. //*************************************************************************/
  25. module sd_card_top
  26. #(
  27. parameter  SPI_LOW_SPEED_DIV = 248,         // SD card low speed mode frequency division parameter,spi clk speed = clk speed /((SPI_LOW_SPEED_DIV + 2) * 2 )
  28. parameter  SPI_HIGH_SPEED_DIV = 0           // SD card high speed mode frequency division parameter,spi clk speed = clk speed /((SPI_HIGH_SPEED_DIV + 2) * 2 )
  29. )
  30. (
  31. input            clk,
  32. input            rst,
  33. output           SD_nCS,                    //SD card chip select (SPI mode)
  34. output           SD_DCLK,                   //SD card clock
  35. output           SD_MOSI,                   //SD card controller data output
  36. input            SD_MISO,                   //SD card controller data input
  37. output           sd_init_done,              //SD card initialization is complete
  38. input            sd_sec_read,               //SD card sector read
  39. input[31:0]      sd_sec_read_addr,          //SD card sector read address
  40. output[7:0]      sd_sec_read_data,          //SD card sector read data
  41. output           sd_sec_read_data_valid,    //SD card sector read data valid
  42. output           sd_sec_read_end,           //SD card sector read end
  43. input            sd_sec_write,              //SD card sector write
  44. input[31:0]      sd_sec_write_addr,         //SD card sector write address
  45. input[7:0]       sd_sec_write_data,         //SD card sector write data
  46. output           sd_sec_write_data_req,     //SD card sector write data next clock is valid
  47. output           sd_sec_write_end           //SD card sector write end
  48. );
  49. wire[15:0]           spi_clk_div;               //SPI module clock division parameter
  50. wire                 cmd_req;                   //SD card command request
  51. wire                 cmd_req_ack;               //SD card command request response
  52. wire                 cmd_req_error;             //SD card command request error
  53. wire[47:0]           cmd;                       //SD card command
  54. wire[7:0]            cmd_r1;                    //SD card expect response
  55. wire[15:0]           cmd_data_len;              //SD card command read data length
  56. wire                 block_read_req;            //SD card sector data read request
  57. wire                 block_read_valid;          //SD card sector data read data valid
  58. wire[7:0]            block_read_data;           //SD card sector data read data
  59. wire                 block_read_req_ack;        //SD card sector data read response
  60. wire                 block_write_req;           //SD card sector data write request
  61. wire[7:0]            block_write_data;          //SD card sector data write data next clock is valid
  62. wire                 block_write_data_rd;       //SD card sector data write data
  63. wire                 block_write_req_ack;       //SD card sector data write response
  64. wire                 nCS_ctrl;                  //SPI module chip select control
  65. wire                 spi_wr_req;                //SPI module data sending request
  66. wire                 spi_wr_ack;                //SPI module data request response
  67. wire[7:0]            spi_data_in;               //SPI module send data
  68. wire[7:0]            spi_data_out;              //SPI module data returned
  69. wire[15:0]           clk_div;
  70. sd_card_sec_read_write
  71. #(
  72. .SPI_LOW_SPEED_DIV(SPI_LOW_SPEED_DIV),
  73. .SPI_HIGH_SPEED_DIV(SPI_HIGH_SPEED_DIV)
  74. )
  75. sd_card_sec_read_write_m0(
  76. .clk                            (clk                    ),
  77. .rst                            (rst                    ),
  78. .sd_init_done                   (sd_init_done           ),
  79. .sd_sec_read                    (sd_sec_read            ),
  80. .sd_sec_read_addr               (sd_sec_read_addr       ),
  81. .sd_sec_read_data               (sd_sec_read_data       ),
  82. .sd_sec_read_data_valid         (sd_sec_read_data_valid ),
  83. .sd_sec_read_end                (sd_sec_read_end        ),
  84. .sd_sec_write                   (sd_sec_write           ),
  85. .sd_sec_write_addr              (sd_sec_write_addr      ),
  86. .sd_sec_write_data              (sd_sec_write_data      ),
  87. .sd_sec_write_data_req          (sd_sec_write_data_req  ),
  88. .sd_sec_write_end               (sd_sec_write_end       ),
  89. .spi_clk_div                    (spi_clk_div            ),
  90. .cmd_req                        (cmd_req                ),
  91. .cmd_req_ack                    (cmd_req_ack            ),
  92. .cmd_req_error                  (cmd_req_error          ),
  93. .cmd                            (cmd                    ),
  94. .cmd_r1                         (cmd_r1                 ),
  95. .cmd_data_len                   (cmd_data_len           ),
  96. .block_read_req                 (block_read_req         ),
  97. .block_read_valid               (block_read_valid       ),
  98. .block_read_data                (block_read_data        ),
  99. .block_read_req_ack             (block_read_req_ack     ),
  100. .block_write_req                (block_write_req        ),
  101. .block_write_data               (block_write_data       ),
  102. .block_write_data_rd            (block_write_data_rd    ),
  103. .block_write_req_ack            (block_write_req_ack    )
  104. );
  105. sd_card_cmd sd_card_cmd_m0(
  106. .sys_clk                        (clk                    ),
  107. .rst                            (rst                    ),
  108. .spi_clk_div                    (spi_clk_div            ),
  109. .cmd_req                        (cmd_req                ),
  110. .cmd_req_ack                    (cmd_req_ack            ),
  111. .cmd_req_error                  (cmd_req_error          ),
  112. .cmd                            (cmd                    ),
  113. .cmd_r1                         (cmd_r1                 ),
  114. .cmd_data_len                   (cmd_data_len           ),
  115. .block_read_req                 (block_read_req         ),
  116. .block_read_req_ack             (block_read_req_ack     ),
  117. .block_read_data                (block_read_data        ),
  118. .block_read_valid               (block_read_valid       ),
  119. .block_write_req                (block_write_req        ),
  120. .block_write_data               (block_write_data       ),
  121. .block_write_data_rd            (block_write_data_rd    ),
  122. .block_write_req_ack            (block_write_req_ack    ),
  123. .nCS_ctrl                       (nCS_ctrl               ),
  124. .clk_div                        (clk_div                ),
  125. .spi_wr_req                     (spi_wr_req             ),
  126. .spi_wr_ack                     (spi_wr_ack             ),
  127. .spi_data_in                    (spi_data_in            ),
  128. .spi_data_out                   (spi_data_out           )
  129. );
  130. spi_master spi_master_m0(
  131. .sys_clk                        (clk                    ),
  132. .rst                            (rst                    ),
  133. .nCS                            (SD_nCS                 ),
  134. .DCLK                           (SD_DCLK                ),
  135. .MOSI                           (SD_MOSI                ),
  136. .MISO                           (SD_MISO                ),
  137. .clk_div                        (clk_div                ),
  138. .CPOL                           (1'b1                   ),
  139. .CPHA                           (1'b1                   ),
  140. .nCS_ctrl                       (nCS_ctrl               ),
  141. .wr_req                         (spi_wr_req             ),
  142. .wr_ack                         (spi_wr_ack             ),
  143. .data_in                        (spi_data_in            ),
  144. .data_out                       (spi_data_out           )
  145. );
复制代码
5.2 sd_card_cmd
sd_card_cmd 模块主要实验 sd 卡根本命令操作,还有上电初始化的 88 个周期的时钟,数据
块的读写,状态机如下:

代码如下:
  1. //
  2. //                                                                              //
  3. //                                                                              //
  4. //  Author: meisq                                                               //
  5. //          msq@qq.com                                                          //
  6. //          ALINX(shanghai) Technology Co.,Ltd                                  //
  7. //          heijin                                                              //
  8. //     WEB: http://www.alinx.cn/                                                //
  9. //     BBS: http://www.heijin.org/                                              //
  10. //                                                                              //
  11. //
  12. //                                                                              //
  13. // Copyright (c) 2017,ALINX(shanghai) Technology Co.,Ltd                        //
  14. //                    All rights reserved                                       //
  15. //                                                                              //
  16. // This source file may be used and distributed without restriction provided    //
  17. // that this copyright statement is not removed from the file and that any      //
  18. // derivative work contains the original copyright notice and the associated    //
  19. // disclaimer.                                                                  //
  20. //                                                                              //
  21. //
  22. //==========================================================================
  23. //  Revision History:
  24. //  Date          By            Revision    Change Description
  25. //--------------------------------------------------------------------------
  26. //  2017/6/21     meisq         1.0         Original
  27. //*************************************************************************/
  28. module sd_card_cmd(
  29. input                       sys_clk,
  30. input                       rst,
  31. input[15:0]                 spi_clk_div,                  //SPI module clock division parameter
  32. input                       cmd_req,                      //SD card command request
  33. output                      cmd_req_ack,                  //SD card command request response
  34. output reg                  cmd_req_error,                //SD card command request error
  35. input[47:0]                 cmd,                          //SD card command
  36. input[7:0]                  cmd_r1,                       //SD card expect response
  37. input[15:0]                 cmd_data_len,                 //SD card command read data length
  38. input                       block_read_req,               //SD card sector data read request
  39. output reg                  block_read_valid,             //SD card sector data read data valid
  40. output reg[7:0]             block_read_data,              //SD card sector data read data
  41. output                      block_read_req_ack,           //SD card sector data read response
  42. input                       block_write_req,              //SD card sector data write request
  43. input[7:0]                  block_write_data,             //SD card sector data write data next clock is valid
  44. output                      block_write_data_rd,          //SD card sector data write data
  45. output                      block_write_req_ack,          //SD card sector data write response
  46. output                      nCS_ctrl,                     //SPI module chip select control
  47. output reg[15:0]            clk_div,
  48. output reg                  spi_wr_req,                   //SPI module data sending request
  49. input                       spi_wr_ack,                   //SPI module data request response
  50. output[7:0]                 spi_data_in,                  //SPI module send data
  51. input[7:0]                  spi_data_out                  //SPI module data returned
  52. );
  53. parameter S_IDLE         = 0;
  54. parameter S_WAIT         = 1;
  55. parameter S_INIT         = 2;
  56. parameter S_CMD_PRE      = 3;
  57. parameter S_CMD          = 4;
  58. parameter S_CMD_DATA     = 5;
  59. parameter S_READ_WAIT    = 6;
  60. parameter S_READ         = 7;
  61. parameter S_READ_ACK     = 8;
  62. parameter S_WRITE_TOKEN  = 9;
  63. parameter S_WRITE_DATA_0 = 10;
  64. parameter S_WRITE_DATA_1 = 11;
  65. parameter S_WRITE_CRC    = 12;
  66. parameter S_WRITE_ACK    = 13;
  67. parameter S_ERR          = 14;
  68. parameter S_END          = 15;
  69. reg[3:0]                      state;
  70. reg                           CS_reg;
  71. reg[15:0]                     byte_cnt;
  72. reg[7:0]                      send_data;
  73. wire[7:0]                     data_recv;
  74. reg[9:0]                      wr_data_cnt;
  75. assign cmd_req_ack = (state == S_END);
  76. assign block_read_req_ack = (state == S_READ_ACK);
  77. assign block_write_req_ack= (state == S_WRITE_ACK);
  78. assign block_write_data_rd = (state == S_WRITE_DATA_0);
  79. assign spi_data_in = send_data;
  80. assign data_recv = spi_data_out;
  81. assign nCS_ctrl = CS_reg;
  82. always@(posedge sys_clk or posedge rst)
  83. begin
  84. if(rst == 1'b1)
  85. begin
  86. CS_reg <= 1'b1;
  87. spi_wr_req <= 1'b0;
  88. byte_cnt <= 16'd0;
  89. clk_div <= 16'd0;
  90. send_data <= 8'hff;
  91. state <= S_IDLE;
  92. cmd_req_error <= 1'b0;
  93. wr_data_cnt <= 10'd0;
  94. end
  95. else
  96. case(state)
  97. S_IDLE:
  98. begin
  99. state <= S_INIT;
  100. clk_div <= spi_clk_div;
  101. CS_reg <= 1'b1;
  102. end
  103. S_INIT:
  104. begin
  105. //send 11 bytes on power(at least 74 SPI clocks)
  106. if(spi_wr_ack == 1'b1)
  107. begin
  108. if(byte_cnt >= 16'd10)
  109. begin
  110. byte_cnt <= 16'd0;
  111. spi_wr_req <= 1'b0;
  112. state <= S_WAIT;
  113. end
  114. begin
  115. byte_cnt <= byte_cnt + 16'd1;
  116. end
  117. end
  118. else
  119. begin
  120. spi_wr_req <= 1'b1;
  121. send_data <= 8'hff;
  122. end
  123. end
  124. S_WAIT:
  125. begin
  126. cmd_req_error <= 1'b0;
  127. wr_data_cnt <= 10'd0;
  128. //wait for  instruction
  129. if(cmd_req == 1'b1)
  130. state <= S_CMD_PRE;
  131. else if(block_read_req == 1'b1)
  132. state <= S_READ_WAIT;
  133. else if(block_write_req == 1'b1)
  134. state <= S_WRITE_TOKEN;
  135. clk_div <= spi_clk_div;
  136. end
  137. S_CMD_PRE:
  138. begin
  139. //before sending a command, send an byte 'ff',provide some clocks
  140. if(spi_wr_ack == 1'b1)
  141. begin
  142. state <= S_CMD;
  143. spi_wr_req <= 1'b0;
  144. byte_cnt <= 16'd0;
  145. end
  146. else
  147. begin
  148. spi_wr_req <= 1'b1;
  149. CS_reg <= 1'b1;
  150. send_data <= 8'hff;
  151. end
  152. end
  153. S_CMD:
  154. begin
  155. if(spi_wr_ack == 1'b1)
  156. begin
  157. if((byte_cnt == 16'hffff) || (data_recv != cmd_r1 && data_recv[7] == 1'b0))
  158. begin
  159. state <= S_ERR;
  160. spi_wr_req <= 1'b0;
  161. end
  162. else if(data_recv == cmd_r1)
  163. begin
  164. spi_wr_req <= 1'b0;
  165. if(cmd_data_len != 16'd0)
  166. begin
  167. state <= S_CMD_DATA;
  168. byte_cnt <= 16'd0;
  169. end
  170. else
  171. state <= S_END;
  172. end
  173. else
  174. byte_cnt <=  byte_cnt + 16'd1;
  175. end
  176. else
  177. begin
  178. spi_wr_req <= 1'b1;
  179. CS_reg <= 1'b0;
  180. if(byte_cnt == 16'd0)
  181. send_data <= (cmd[47:40] | 8'h40);
  182. else if(byte_cnt == 16'd1)
  183. send_data <= cmd[39:32];
  184. else if(byte_cnt == 16'd2)
  185. send_data <= cmd[31:24];
  186. else if(byte_cnt == 16'd3)
  187. send_data <= cmd[23:16];
  188. else if(byte_cnt == 16'd4)
  189. send_data <= cmd[15:8];
  190. else if(byte_cnt == 16'd5)
  191. send_data <= cmd[7:0];
  192. else
  193. send_data <= 8'hff;
  194. end
  195. end
  196. S_CMD_DATA:
  197. begin
  198. if(spi_wr_ack == 1'b1)
  199. begin
  200. if(byte_cnt == cmd_data_len - 16'd1)
  201. begin
  202. state <= S_END;
  203. spi_wr_req <= 1'b0;
  204. byte_cnt <= 16'd0;
  205. end
  206. else
  207. begin
  208. byte_cnt <= byte_cnt + 16'd1;
  209. end
  210. end
  211. else
  212. begin
  213. spi_wr_req <= 1'b1;
  214. send_data <= 8'hff;
  215. end
  216. end
  217. S_READ_WAIT:
  218. begin
  219. if(spi_wr_ack == 1'b1 && data_recv == 8'hfe)
  220. begin
  221. spi_wr_req <= 1'b0;
  222. state <= S_READ;
  223. byte_cnt <= 16'd0;
  224. end
  225. else
  226. begin
  227. spi_wr_req <= 1'b1;
  228. send_data <= 8'hff;
  229. end
  230. end
  231. S_READ:
  232. begin
  233. if(spi_wr_ack == 1'b1)
  234. begin
  235. if(byte_cnt == 16'd513)
  236. begin
  237. state <= S_READ_ACK;
  238. spi_wr_req <= 1'b0;
  239. byte_cnt <= 16'd0;
  240. end
  241. else
  242. begin
  243. byte_cnt <= byte_cnt + 16'd1;
  244. end
  245. end
  246. else
  247. begin
  248. spi_wr_req <= 1'b1;
  249. send_data <= 8'hff;
  250. end
  251. end
  252. S_WRITE_TOKEN:
  253. if(spi_wr_ack == 1'b1)
  254. begin
  255. state <= S_WRITE_DATA_0;
  256. spi_wr_req <= 1'b0;
  257. end
  258. else
  259. begin
  260. spi_wr_req <= 1'b1;
  261. send_data <= 8'hfe;
  262. end
  263. S_WRITE_DATA_0:
  264. begin
  265. state <= S_WRITE_DATA_1;
  266. wr_data_cnt <= wr_data_cnt + 10'd1;
  267. end
  268. S_WRITE_DATA_1:
  269. begin
  270. if(spi_wr_ack == 1'b1 && wr_data_cnt == 10'd512)
  271. begin
  272. state <= S_WRITE_CRC;
  273. spi_wr_req <= 1'b0;
  274. end
  275. else if(spi_wr_ack == 1'b1)
  276. begin
  277. state <= S_WRITE_DATA_0;
  278. spi_wr_req <= 1'b0;
  279. end
  280. else
  281. begin
  282. spi_wr_req <= 1'b1;
  283. send_data <= block_write_data;
  284. end
  285. end
  286. S_WRITE_CRC:
  287. begin
  288. if(spi_wr_ack == 1'b1)
  289. begin
  290. if(byte_cnt == 16'd2)
  291. begin
  292. state <= S_WRITE_ACK;
  293. spi_wr_req <= 1'b0;
  294. byte_cnt <= 16'd0;
  295. end
  296. else
  297. begin
  298. byte_cnt <= byte_cnt + 16'd1;
  299. end
  300. end
  301. else
  302. begin
  303. spi_wr_req <= 1'b1;
  304. send_data <= 8'hff;
  305. end
  306. end
  307. S_ERR:
  308. begin
  309. state <= S_END;
  310. cmd_req_error <= 1'b1;
  311. end
  312. S_READ_ACK,S_WRITE_ACK,S_END:
  313. begin
  314. state <= S_WAIT;
  315. end
  316. default:
  317. state <= S_IDLE;
  318. endcase
  319. end
  320. always@(posedge sys_clk or posedge rst)
  321. begin
  322. if(rst == 1'b1)
  323. block_read_valid <= 1'b0;
  324. else if(state == S_READ && byte_cnt < 16'd512)
  325. block_read_valid <= spi_wr_ack;
  326. else
  327. block_read_valid <= 1'b0;
  328. end
  329. always@(posedge sys_clk or posedge rst)
  330. begin
  331. if(rst == 1'b1)
  332. block_read_data <= 8'd0;
  333. else if(state == S_READ && spi_wr_ack == 1'b1)
  334. block_read_data <= data_recv;
  335. end
复制代码
5.3 sd_card_sec_read_write
sd_card_sec_read_write 模块继续完成 SD 卡初始化,然后等待扇区读写指令,并完成扇区的
读写操作。 下图为模块的状态机转换图,首先发送 CMD0 命令,然后发送 CMD8 命令,再发送
CMD55,接着发送 ACMD41,如果应答正常, sd 卡初始化完成,等待扇区的读写。

代码如下:
  1. //
  2. //                                                                              //
  3. //                                                                              //
  4. //  Author: meisq                                                               //
  5. //          msq@qq.com                                                          //
  6. //          ALINX(shanghai) Technology Co.,Ltd                                  //
  7. //          heijin                                                              //
  8. //     WEB: http://www.alinx.cn/                                                //
  9. //     BBS: http://www.heijin.org/                                              //
  10. //                                                                              //
  11. //
  12. //                                                                              //
  13. // Copyright (c) 2017,ALINX(shanghai) Technology Co.,Ltd                        //
  14. //                    All rights reserved                                       //
  15. //                                                                              //
  16. // This source file may be used and distributed without restriction provided    //
  17. // that this copyright statement is not removed from the file and that any      //
  18. // derivative work contains the original copyright notice and the associated    //
  19. // disclaimer.                                                                  //
  20. //                                                                              //
  21. //
  22. //===============================================================================
  23. //  Revision History:
  24. //  Date          By            Revision    Change Description
  25. //-------------------------------------------------------------------------------
  26. //  2017/6/21     meisq         1.0         Original
  27. //*******************************************************************************/
  28. module sd_card_sec_read_write
  29. #(
  30. parameter  SPI_LOW_SPEED_DIV = 248,         // spi clk speed = clk speed /((SPI_LOW_SPEED_DIV + 2) * 2 )
  31. parameter  SPI_HIGH_SPEED_DIV = 0           // spi clk speed = clk speed /((SPI_HIGH_SPEED_DIV + 2) * 2 )
  32. )
  33. (
  34. input            clk,
  35. input            rst,
  36. output reg       sd_init_done,
  37. input            sd_sec_read,
  38. input[31:0]      sd_sec_read_addr,
  39. output[7:0]      sd_sec_read_data,
  40. output           sd_sec_read_data_valid,
  41. output           sd_sec_read_end,
  42. input            sd_sec_write,
  43. input[31:0]      sd_sec_write_addr,
  44. input[7:0]       sd_sec_write_data,
  45. output           sd_sec_write_data_req,
  46. output           sd_sec_write_end,
  47. output reg[15:0] spi_clk_div,
  48. output reg       cmd_req,
  49. input            cmd_req_ack,
  50. input            cmd_req_error,
  51. output reg[47:0] cmd,
  52. output reg[7:0]  cmd_r1,
  53. output reg[15:0] cmd_data_len,
  54. output reg       block_read_req,
  55. input            block_read_valid,
  56. input[7:0]       block_read_data,
  57. input            block_read_req_ack,
  58. output reg       block_write_req,
  59. output[7:0]      block_write_data,
  60. input            block_write_data_rd,
  61. input            block_write_req_ack
  62. );
  63. reg[7:0] read_data;
  64. reg[31:0] timer;
  65. localparam S_IDLE               = 0;
  66. localparam S_CMD0               = 1;
  67. localparam S_CMD8               = 2;
  68. localparam S_CMD55              = 3;
  69. localparam S_CMD41              = 4;
  70. localparam S_CMD17              = 5;
  71. localparam S_READ               = 6;
  72. localparam S_CMD24              = 7;
  73. localparam S_WRITE              = 8;
  74. localparam S_ERR                = 14;
  75. localparam S_WRITE_END          = 15;
  76. localparam S_READ_END           = 16;
  77. localparam S_WAIT_READ_WRITE    = 17;
  78. localparam S_CMD16              = 18;
  79. reg[4:0]                       state;
  80. reg[31:0]                      sec_addr;
  81. assign sd_sec_read_data_valid = (state == S_READ) && block_read_valid;
  82. assign sd_sec_read_data = block_read_data;
  83. assign sd_sec_read_end = (state == S_READ_END);
  84. assign sd_sec_write_data_req = (state == S_WRITE) && block_write_data_rd;
  85. assign block_write_data = sd_sec_write_data;
  86. assign sd_sec_write_end = (state == S_WRITE_END);
  87. always@(posedge clk or posedge rst)
  88. begin
  89. if(rst == 1'b1)
  90. begin
  91. state <= S_IDLE;
  92. cmd_req <= 1'b0;
  93. cmd_data_len <= 16'd0;
  94. cmd_r1 <= 8'd0;
  95. cmd <= 48'd0;
  96. spi_clk_div <= SPI_LOW_SPEED_DIV[15:0];
  97. block_write_req <= 1'b0;
  98. block_read_req <= 1'b0;
  99. sec_addr <= 32'd0;
  100. sd_init_done <= 1'b0;
  101. end
  102. else
  103. case(state)
  104. S_IDLE:
  105. begin
  106. state <= S_CMD0;
  107. sd_init_done <= 1'b0;
  108. spi_clk_div <= SPI_LOW_SPEED_DIV[15:0];
  109. end
  110. S_CMD0:
  111. begin
  112. if(cmd_req_ack & ~cmd_req_error)
  113. begin
  114. state <= S_CMD8;
  115. cmd_req <= 1'b0;
  116. end
  117. else
  118. begin
  119. cmd_req <= 1'b1;
  120. cmd_data_len <= 16'd0;
  121. cmd_r1 <= 8'h01;
  122. cmd <= {8'd0,8'h00,8'h00,8'h00,8'h00,8'h95};
  123. end
  124. end
  125. S_CMD8:
  126. begin
  127. if(cmd_req_ack & ~cmd_req_error)
  128. begin
  129. state <= S_CMD55;
  130. cmd_req <= 1'b0;
  131. end
  132. else
  133. begin
  134. cmd_req <= 1'b1;
  135. cmd_data_len <= 16'd4;
  136. cmd_r1 <= 8'h01;
  137. cmd <= {8'd8,8'h00,8'h00,8'h01,8'haa,8'h87};
  138. end
  139. end
  140. S_CMD55:
  141. begin
  142. if(cmd_req_ack & ~cmd_req_error)
  143. begin
  144. state <= S_CMD41;
  145. cmd_req <= 1'b0;
  146. end
  147. else
  148. begin
  149. cmd_req <= 1'b1;
  150. cmd_data_len <= 16'd0;
  151. cmd_r1 <= 8'h01;
  152. cmd <= {8'd55,8'h00,8'h00,8'h00,8'h00,8'hff};
  153. end
  154. end
  155. S_CMD41:
  156. begin
  157. if(cmd_req_ack & ~cmd_req_error)
  158. begin
  159. state <= S_CMD16;
  160. cmd_req <= 1'b0;
  161. sd_init_done <= 1'b1;
  162. spi_clk_div <= SPI_HIGH_SPEED_DIV[15:0];
  163. end
  164. else if(cmd_req_ack)
  165. begin
  166. state <= S_CMD55;
  167. end
  168. else
  169. begin
  170. cmd_req <= 1'b1;
  171. cmd_data_len <= 16'd0;
  172. cmd_r1 <= 8'h00;
  173. cmd <= {8'd41,8'h40,8'h00,8'h00,8'h00,8'hff};
  174. end
  175. end
  176. S_CMD16:
  177. begin
  178. if(cmd_req_ack & ~cmd_req_error)
  179. begin
  180. state <= S_WAIT_READ_WRITE;
  181. cmd_req <= 1'b0;
  182. sd_init_done <= 1'b1;
  183. spi_clk_div <= SPI_HIGH_SPEED_DIV[15:0];
  184. end
  185. else if(cmd_req_ack)
  186. begin
  187. state <= S_CMD55;
  188. end
  189. else
  190. begin
  191. cmd_req <= 1'b1;
  192. cmd_data_len <= 16'd0;
  193. cmd_r1 <= 8'h00;
  194. cmd <= {8'd16,32'd512,8'hff};
  195. end
  196. end
  197. S_WAIT_READ_WRITE:
  198. begin
  199. if(sd_sec_write ==  1'b1)
  200. begin
  201. state <= S_CMD24;
  202. sec_addr <= sd_sec_write_addr;
  203. end
  204. else if(sd_sec_read == 1'b1)
  205. begin
  206. state <= S_CMD17;
  207. sec_addr <= sd_sec_read_addr;
  208. end
  209. spi_clk_div <= 16'd0;
  210. end
  211. S_CMD24:
  212. begin
  213. if(cmd_req_ack & ~cmd_req_error)
  214. begin
  215. state <= S_WRITE;
  216. cmd_req <= 1'b0;
  217. end
  218. else
  219. begin
  220. cmd_req <= 1'b1;
  221. cmd_data_len <= 16'd0;
  222. cmd_r1 <= 8'h00;
  223. cmd <= {8'd24,sec_addr,8'hff};
  224. end
  225. end
  226. S_WRITE:
  227. begin
  228. if(block_write_req_ack == 1'b1)
  229. begin
  230. block_write_req <= 1'b0;
  231. state <= S_WRITE_END;
  232. end
  233. else
  234. block_write_req <= 1'b1;
  235. end
  236. S_CMD17:
  237. begin
  238. if(cmd_req_ack & ~cmd_req_error)
  239. begin
  240. state <= S_READ;
  241. cmd_req <= 1'b0;
  242. end
  243. else
  244. begin
  245. cmd_req <= 1'b1;
  246. cmd_data_len <= 16'd0;
  247. cmd_r1 <= 8'h00;
  248. cmd <= {8'd17,sec_addr,8'hff};
  249. end
  250. end
  251. S_READ:
  252. begin
  253. if(block_read_req_ack)
  254. begin
  255. state <= S_READ_END;
  256. block_read_req <= 1'b0;
  257. end
  258. else
  259. begin
  260. block_read_req <= 1'b1;
  261. end
  262. end
  263. S_WRITE_END:
  264. begin
  265. state <= S_WAIT_READ_WRITE;
  266. end
  267. S_READ_END:
  268. begin
  269. state <= S_WAIT_READ_WRITE;
  270. end
  271. default:
  272. state <= S_IDLE;
  273. endcase
  274. end
  275. endmodule
复制代码
5.4 spi_master
这一模块用来完成SPI一个字节的读写。
spi master 状态机设计, 主要完成一个字节 spi 数据的读写,由于是全双工的,写一个字节的
同时也读一个字节。 首先空闲状态“IDLE”接收到写请求后进入“DCLK_IDLE”状态,这个状态为
spi 时钟沿变化保持一定的时间,用来控制 spi 时钟的周期,然后进入 spi 时钟沿的变化状态,一
个字节上升沿和降落沿一共 16 个数据沿。 在末了一个数据沿进入“LAST_HALF_CYCLE”状态,为
让末了一个沿也保持一定的时间,再进入应答状态,完成一次写请求。spi_master 模块中模拟了一个 spi 时钟,在状态机进入到‘DCLK_EDGE’时进行翻转。状态机图示如下:

代码如下:
  1. //  Author: meisq                                                               //
  2. //          msq@qq.com                                                          //
  3. //          ALINX(shanghai) Technology Co.,Ltd                                  //
  4. //          heijin                                                              //
  5. //     WEB: http://www.alinx.cn/                                                //
  6. //     BBS: http://www.heijin.org/                                              //
  7. //                                                                              //
  8. //
  9. //                                                                              //
  10. // Copyright (c) 2017,ALINX(shanghai) Technology Co.,Ltd                        //
  11. //                    All rights reserved                                       //
  12. //                                                                              //
  13. // This source file may be used and distributed without restriction provided    //
  14. // that this copyright statement is not removed from the file and that any      //
  15. // derivative work contains the original copyright notice and the associated    //
  16. // disclaimer.                                                                  //
  17. //                                                                              //
  18. //
  19. //==========================================================================
  20. //  Revision History:
  21. //  Date          By            Revision    Change Description
  22. //--------------------------------------------------------------------------
  23. //  2017/6/19     meisq         1.0         Original
  24. //*************************************************************************/
  25. module spi_master
  26. (
  27. input                       sys_clk,
  28. input                       rst,
  29. output                      nCS,       //chip select (SPI mode)
  30. output                      DCLK,      //spi clock
  31. output                      MOSI,      //spi data output
  32. input                       MISO,      //spi input
  33. input                       CPOL,
  34. input                       CPHA,
  35. input                       nCS_ctrl,
  36. input[15:0]                 clk_div,
  37. input                       wr_req,
  38. output                      wr_ack,
  39. input[7:0]                  data_in,
  40. output[7:0]                 data_out
  41. );
  42. localparam                   IDLE            = 0;
  43. localparam                   DCLK_EDGE       = 1;
  44. localparam                   DCLK_IDLE       = 2;
  45. localparam                   ACK             = 3;
  46. localparam                   LAST_HALF_CYCLE = 4;
  47. localparam                   ACK_WAIT        = 5;
  48. reg                          DCLK_reg;
  49. reg[7:0]                     MOSI_shift;
  50. reg[7:0]                     MISO_shift;
  51. reg[2:0]                     state;
  52. reg[2:0]                     next_state;
  53. reg [15:0]                   clk_cnt;
  54. reg[4:0]                     clk_edge_cnt;
  55. assign MOSI = MOSI_shift[7];
  56. assign DCLK = DCLK_reg;
  57. assign data_out = MISO_shift;
  58. assign wr_ack = (state == ACK);
  59. assign nCS = nCS_ctrl;
  60. always@(posedge sys_clk or posedge rst)
  61. begin
  62. if(rst)
  63. state <= IDLE;
  64. else
  65. state <= next_state;
  66. end
  67. always@(*)
  68. begin
  69. case(state)
  70. IDLE:
  71. if(wr_req == 1'b1)
  72. next_state <= DCLK_IDLE;
  73. else
  74. next_state <= IDLE;
  75. DCLK_IDLE:
  76. //half a SPI clock cycle produces a clock edge
  77. if(clk_cnt == clk_div)
  78. next_state <= DCLK_EDGE;
  79. else
  80. next_state <= DCLK_IDLE;
  81. DCLK_EDGE:
  82. //a SPI byte with a total of 16 clock edges
  83. if(clk_edge_cnt == 5'd15)
  84. next_state <= LAST_HALF_CYCLE;
  85. else
  86. next_state <= DCLK_IDLE;
  87. //this is the last data edge
  88. LAST_HALF_CYCLE:
  89. if(clk_cnt == clk_div)
  90. next_state <= ACK;
  91. else
  92. next_state <= LAST_HALF_CYCLE;
  93. //send one byte complete
  94. ACK:
  95. next_state <= ACK_WAIT;
  96. //wait for one clock cycle, to ensure that the cancel request signal
  97. ACK_WAIT:
  98. next_state <= IDLE;
  99. default:
  100. next_state <= IDLE;
  101. endcase
  102. end
  103. always@(posedge sys_clk or posedge rst)
  104. begin
  105. if(rst)
  106. DCLK_reg <= 1'b0;
  107. else if(state == IDLE)
  108. DCLK_reg <= CPOL;
  109. else if(state == DCLK_EDGE)
  110. DCLK_reg <= ~DCLK_reg;//SPI clock edge
  111. end
  112. //SPI clock wait counter
  113. always@(posedge sys_clk or posedge rst)
  114. begin
  115. if(rst)
  116. clk_cnt <= 16'd0;
  117. else if(state == DCLK_IDLE || state == LAST_HALF_CYCLE)
  118. clk_cnt <= clk_cnt + 16'd1;
  119. else
  120. clk_cnt <= 16'd0;
  121. end
  122. //SPI clock edge counter
  123. always@(posedge sys_clk or posedge rst)
  124. begin
  125. if(rst)
  126. clk_edge_cnt <= 5'd0;
  127. else if(state == DCLK_EDGE)
  128. clk_edge_cnt <= clk_edge_cnt + 5'd1;
  129. else if(state == IDLE)
  130. clk_edge_cnt <= 5'd0;
  131. end
  132. //SPI data output
  133. always@(posedge sys_clk or posedge rst)
  134. begin
  135. if(rst)
  136. MOSI_shift <= 8'd0;
  137. else if(state == IDLE && wr_req)
  138. MOSI_shift <= data_in;
  139. else if(state == DCLK_EDGE)
  140. if(CPHA == 1'b0 && clk_edge_cnt[0] == 1'b1)
  141. MOSI_shift <= {MOSI_shift[6:0],MOSI_shift[7]};
  142. else if(CPHA == 1'b1 && (clk_edge_cnt != 5'd0 && clk_edge_cnt[0] == 1'b0))
  143. MOSI_shift <= {MOSI_shift[6:0],MOSI_shift[7]};
  144. end
  145. //SPI data input
  146. always@(posedge sys_clk or posedge rst)
  147. begin
  148. if(rst)
  149. MISO_shift <= 8'd0;
  150. else if(state == IDLE && wr_req)
  151. MISO_shift <= 8'h00;
  152. else if(state == DCLK_EDGE)
  153. if(CPHA == 1'b0 && clk_edge_cnt[0] == 1'b0)
  154. MISO_shift <= {MISO_shift[6:0],MISO};
  155. else if(CPHA == 1'b1 && (clk_edge_cnt[0] == 1'b1))
  156. MISO_shift <= {MISO_shift[6:0],MISO};
  157. end
  158. endmodule
复制代码
6 实验效果
下载实验步伐后,可以看到数码管显示一个数字,这个数字是存储在 sd 卡中第一扇区的第一个数据,数据是随机的,这个时间按键 KEY1 按下,数字加一,并写入了 sd 卡,再次下载步伐,可以看到直接显示更新后的数据。


人之所以能,是信赖能

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表