tag 标签: SD卡NAND

相关帖子
相关博文
  • 2024-11-13 15:27
    0 个评论
      前言:   随着国内市场对芯片的需求日益增加,国产中高端芯片在不断的占领国内市场甚至在国际市场都有一部分,越来越多的企业开始研究自己的芯片,这个芯片的种类繁多,功能性强大,也有一些芯片占有着很大的市场份额,有着不可取代的地位。   随着芯片的发展存储芯片的趋势也开始不甘落后,有着越来越多的新型的芯片问世,这里就要提起SD NAND芯片了,市面上主流的生产厂家就是雷龙。 SD NAND 的简单介绍:   什么是SD NAND?很简单顾名思义就是内部集成了SD卡或TF卡(这里有必要说明SD卡和TF卡除了大小不同,引脚不同之外驱动,功能等内容都是一样的使用SD协议)功能的NAND存储芯片。   当然,他和主流的存储芯片不太一样,它是遵循SD协议的芯片,下图就是SDNAND的引脚图。   下图是SD卡的引脚图。   仔细观察就不难发现他们的引脚功能相似,SD NAND芯片在引脚上遵循SD卡的协议, SD NAND可以理解为是一种可以直接焊接在PCB上的SD卡。   常见存储芯片的类别:   下面就要讨论关于常见存储芯片的主要用途作,和优缺点,在这里会从芯片的功能和实际使用情况来分析,如果只想了解SDNAND可以直接跳转到“SD NAND存储芯片”的目录中。   在这里会以存储芯片做比较,不和RAM存储器比较,以比较性能和使用环境,使用协议为主。   EEPROM存储芯片:   EEPROM存储芯片在1978年就诞生了,在这之前经过了ROM(只读存储器)EPROM(紫外线可擦除存储器)的演化,虽然现在不能成为主流的存储芯片,但是在存储一些简单的数据上还是可以看到它的身影,在单片机的开发上会对简单的数据进行存储。   这里就以AT24C256存储芯片来举例,下图是该芯片的内部结构图 。   可以看出主要是以IIC进行数据传输的(在主流的EEPROM市场中还有一小部分是以SPI作为数据传输的),引脚排列较少,价格较为便宜,但是他们的传输速度较慢,存储空间不是很大,但仍有很多的地方见到它的身影比如说主板的BIOS芯片,主要是在数据暂存,掉电保存重要数据等用途。要是和其他种类的存储芯片比较传输速度和存储空间很显然EEPROM就有些力不从心了。   NAND类存储芯片:   NAND Flash全名为Flash Memory,属于非易失性存储设备(Non-volatile Memory Device),基于浮栅(Floating Gate)晶体管设计,通过浮栅来锁存电荷,这NAND存储芯片指的是使用这个存储结构的芯片,他们和EEPROM有不同的一点是存储数据量较大内部集成度较高,记住NAND并不是存储芯片的一种,而是芯片内部使用存储单元的结构(统称为NAND类存储芯片)。   NAND类存储芯片经常和FLASH联系在一起,或者可以说NAND是FLASH的一种,在实际应用中很常见,下图是SD卡简单化的内部结构。   SD卡存储单元是使用的FLASH,FLASH存储器一般会采用NAND的存储结构,NAND存储芯片有很多优点读写速度快,存储密度更高,擦写速度更快,使用寿命更长。   一般来说NAND存储芯片的连接通讯接口很多,比如SPI,IIC,多数据并行接口等,这个主要取决于它使用使用什么样的控制器来对它进行存储,另外NAND存储技术在国内已经很成熟了,在市场上使用的较为广泛,更是在硬件和PCB系统设计中成为了主流选择。   EMMC存储芯片:   说到NAND类存储芯片就要提到EMMC芯片了,虽说EMMC只是NAND类存储类的一种,但是EMMC芯片在PCB系统设计中却是一个拥有很强性能的芯片,主要是针对手机平板等微型内嵌存储芯片,从内部结构就可以看出有着更强大的控制器,有很多的EMMC都是采用NAND存储结构。   当然强大的传输速度需要很强大的处理器进行连接,因此在芯片的接口上就要比普通的存储芯片复杂很多,下面的这一张图是EMMC存储芯片的引脚图。   从引脚上看Power(供电)就需要两种不同的电压,数据传输DAT 端口至少需要8个端口,CMD负责控制,CLK引脚来提供时钟信号(最快的EMMC时钟信号可以达到200MHZ),这种存储器如果用性能一般点的单片机来控制会有点吃力,当然它通常是BGA封装,所使用的引脚是很多的。 EMMC 存储芯片无论是在传输速度上,在存储容量上,它的的性能都是很强大的,但同时需要很复杂的连接驱动才能使用,所以经常出现在一些较高端的电子产品上使用比如手机,平板,电脑等。   SD NAND存储芯片:   了解了EEPROM存储芯片和NAND芯片以及EMMC存储芯片他们都有各自的有点,比如说现在需要一款芯片是拥有较大的存储空间和较为简单的存储驱动什么样的芯片适合呢?答案是:SD NAND。   SD NAND有着很好的一点是拥有EEPROM那样的简单的外部布局,使无需通过复杂的连接完成存储功能,还拥有着EMMC的大容量存储空间,下图就是雷龙的CSNP4GCR01-AOW(SD NAND)芯片的内部结构图。   通过结构图可以看出内部主要是由SD NAND作为存储,在芯片的输入输出接口部分使用的引脚和SD卡使用的是相同的协议,控制原理基本大差不差,下图是SD卡的内部结构图,引脚相同,控制也相同。   SD NAND芯片使用的数据传输协议相对来说较为简单(和EMMC比起),存储空间较大(和EEPROM比起),或者理解为集成了EMMC和EEPROM的优点,但它的传输性能远不如EMMC那么快(这里根据EMMC协议主时钟最高200MHZ和SD协议主时钟最高50MHZ来定)。   因此SD NAND更适合用于一些用于并不是很复杂的设备开发中,比如单片机数据存取,简单的数据保存和一些轻量化的系统,针对一些掌上电脑,手机,平板等高智能化的产品还是觉得EMMC芯片更能符合这一类的环境。   SD卡测试:   下面就来使用XC7Z020芯片对SD卡进行读写TXT文本实验,就是SD卡而不是SD NAND具体原因在SD卡测试下面会说明,具体实验步骤如下。本次使用的是SD卡。   创建Vivado工程文件,选择对应的芯片型号和内容   本次实验使用的是FPGA内部自带的IP核和对应的硬件串口来实现,所以在IP核的配置中只使用DDR端口和内存端口,IP核其他的多余引脚全部删除。   上图是IP核设定的界面这个对应的SD接口,这个接口是开发板硬件连接决定的,本次实验使用的是SD0,同时还要使能串口外设。   同时不要忘记设定需要的DDR控制器,以便来使用,下一步就要保存生成数据文件。   已经生成好的数据文件要把设定好的导出后,然后就可以进行SDK开发了。   正在启动的SDK开发平台。   在SDK开发平台创建开发工程。   由于本次实验需要添加FATS文件系统,在SDK开发平台上没有默认自带该库函数,因此需要手动添加库文件。   点击左上角的Modify this BSP's Settings 按钮,添加库函数。   选择上图中的库函数,这个库函数是FAT系统库函数,里面包含很多的库函数完全够本次实验使用。   继续把use_lfn这个选项的值改成true,这个选项的作用是使能长文件名,这样就可以针对长文件名进行操作。   在库函数下有一个新加入的库函数这个就是本次实验使用的库。 /***************************** Include Files *********************************/ #include "xparameters.h"/* SDK generated parameters */ #include "xsdps.h"/* SD device driver */ #include "xil_printf.h" #include "ff.h" #include "xil_cache.h" #include "xplatform_info.h" /************************** Function Prototypes ******************************/ int FfsSdPolledExample(void); /************************** Variable Definitions *****************************/ static FIL fil;/* File object */ static FATFS fatfs; static char FileName = "Test.txt"; static char *SD_File; char DestinationAddress ; const char SourceAddress = "hello mizar !"; #define TEST 7 int main(void) { int Status; xil_printf("SD Polled File System Example Test \r\n"); Status = FfsSdPolledExample(); if (Status != XST_SUCCESS) { xil_printf("SD Polled File System Example Test failed \r\n"); return XST_FAILURE; } xil_printf("Successfully ran SD Polled File System Example Test \r\n"); return XST_SUCCESS; } int FfsSdPolledExample(void) { FRESULT Res; UINT NumBytesRead; UINT NumBytesWritten; u32 BuffCnt; BYTE work ; int FileSize = strlen(SourceAddress); TCHAR *Path = "0:/"; //初始化文件系统 Res = f_mount(&fatfs, Path, 0); if (Res != FR_OK) { return XST_FAILURE; } //格式化SD卡 Res = f_mkfs(Path, FM_FAT32, 0, work, sizeof work); if (Res != FR_OK) { return XST_FAILURE; } //打开一个文件,如果文件不存在,则创建一个文件,该文件的权限为可读写 SD_File = (char *)FileName; Res = f_open(&fil, SD_File, FA_CREATE_ALWAYS | FA_WRITE | FA_READ); if (Res) { return XST_FAILURE; } //指针指向文件开头 Res = f_lseek(&fil, 0); if (Res) { return XST_FAILURE; } //向文件中写入数据 Res = f_write(&fil, (const void*)SourceAddress, FileSize, &NumBytesWritten); if (Res) { return XST_FAILURE; } //指针指向文件开头 Res = f_lseek(&fil, 0); if (Res) { return XST_FAILURE; } //从SD卡中的文件读出数据 Res = f_read(&fil, (void*)DestinationAddress, FileSize, &NumBytesRead); if (Res) { return XST_FAILURE; } //比较写入的数据与读出的数据是否相同 for(BuffCnt = 0; BuffCnt < FileSize; BuffCnt++){ if(SourceAddress != DestinationAddress ){ return XST_FAILURE; } } //关闭文件 Res = f_close(&fil); if (Res) { return XST_FAILURE; } return XST_SUCCESS; }   以上是本次实验使用到的部分代码,里面包含了外设,标准的打印函数和SD卡控制器的功能,在程序中先进行格式化SD卡为FAT32格式。   数据上传到FPGA中创建SD卡内部内容。   提示该信息说明创建完成,这个数据是由串口发送的,下载完成就把SD卡通过读卡器连接在电脑上就可以查看刚才创建的文件了,   在SD卡下创建好的文件。   或许在这里会有疑问为什么用FPGA对SD卡进行测试而不是SD NAND进行测试呢?答案很简单就是驱动问题,如果把SD NAND进行和上面SD卡测试相同的实验会发现最后查看Test文件无法完成,因为SD卡在Windows系统上(包括SD卡的读卡器)它的驱动很完善有着很强大的能力,但是把SD NAND芯片单独放到该体统中会显得有一些缺点,但是就并不能否定SD NAND在其他硬件比如单片机中的优点。   像刚才我说的如果在Windows系统中直接拿SD卡和SD NAND来比较性能就有点太欺负人了,因为在驱动层面有着很大的不同,所以说明SD NAND存储芯片要想做U盘就需要很完善的驱动才能进行,因此更适合在嵌入式单片机,FPGA内部程序开发等。   总结:   总的来说SD NAND是内部集成了SD卡协议的芯片,有着和SD卡相同的功能,在实际使用的时候使用的正是SD卡的协议,SD NAND可以焊接在线路板上完成一体化设计,建议使用在并不是需要强大性能的处理器上作为系统存储,因为作为强大系统的存储使用EMMC较好,SD NAND存储芯片多数使用在较为轻量化的系统中。 本次实验使用的是雷龙CSNP32GCR01和 CSNP4GCR01 芯片进行测试的,建议使用在嵌入式系统开发。
  • 热度 1
    2024-7-8 18:12
    135 次阅读|
    0 个评论
    最近收到了一片国产工业级SD NAND,可以替代SD卡,容量大,贴片封装,非常适合做飞控"黑匣子"。 不用写驱动程序自带坏块管理的NAND Flash(贴片式TF卡),尺寸小巧,简单易用,兼容性强,稳定可靠,固件可定制,LGA-8封装,标准SDIO接口,兼容SPI/SD接口,兼容各大MCU平台,可替代普通TF卡/SD卡,尺寸6x8mm毫米,内置SLC晶圆擦写寿命10万次,通过1万次随机掉电测试耐高低温,支持工业级温度-40°~+85°,机贴手贴都非常方便,速度级别Class10(读取速度23.5MB/S写入速度12.3MB/S)标准的SD 2.0协议使得用户可以直接移植标准驱动代码,省去了驱动代码编程环节。支持TF卡启动的SOC都可以用SD NAND,厂商提供了STM32参考例程及原厂技术支持,主流容量:128MB/512MB/2GB/4GB/8GB,比TF卡稳定,比eMMC便宜。 飞控板上ESP32C3的SDIO接口暂时用不了,只能先用SPI接口驱动。 评估板做了个micro SD卡的接口,方便直接插到带卡槽的开发板上进行调试。 ESP32C3的SPI接口是硬件SPI,支持DMA,速度应该还可以,但是我用杜邦线连接的,肯定会影响信号质量,估计时钟很难跑到50MHz了。 接线: 编写测试程序: /* SD card and FAT filesystem example. This example uses SPI peripheral to communicate with SD card. This example code is in the Public Domain (or CC0 licensed, at your option.) Unless required by applicable law or agreed to in writing, this software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. */ #include #include #include #include "esp_vfs_fat.h" #include "sdmmc_cmd.h" #define EXAMPLE_MAX_CHAR_SIZE 64 static const char *TAG = "example"; #define MOUNT_POINT "/sdcard" // Pin assignments can be set in menuconfig, see "SD SPI Example Configuration" menu. // You can also change the pin assignments here by changing the following 4 lines. #define PIN_NUM_MISO CONFIG_EXAMPLE_PIN_MISO #define PIN_NUM_MOSI CONFIG_EXAMPLE_PIN_MOSI #define PIN_NUM_CLK CONFIG_EXAMPLE_PIN_CLK #define PIN_NUM_CS CONFIG_EXAMPLE_PIN_CS static esp_err_t s_example_write_file(const char *path, char *data) { ESP_LOGI(TAG, "Opening file %s", path); FILE *f = fopen(path, "w"); if (f == NULL) { ESP_LOGE(TAG, "Failed to open file for writing"); return ESP_FAIL; } fprintf(f, data); fclose(f); ESP_LOGI(TAG, "File written"); return ESP_OK; } static esp_err_t s_example_read_file(const char *path) { ESP_LOGI(TAG, "Reading file %s", path); FILE *f = fopen(path, "r"); if (f == NULL) { ESP_LOGE(TAG, "Failed to open file for reading"); return ESP_FAIL; } char line ; fgets(line, sizeof(line), f); fclose(f); // strip newline char *pos = strchr(line, '\n'); if (pos) { *pos = '\0'; } ESP_LOGI(TAG, "Read from file: '%s'", line); return ESP_OK; } void app_main(void) { esp_err_t ret; // Options for mounting the filesystem. // If format_if_mount_failed is set to true, SD card will be partitioned and // formatted in case when mounting fails. esp_vfs_fat_sdmmc_mount_config_t mount_config = { #ifdef CONFIG_EXAMPLE_FORMAT_IF_MOUNT_FAILED .format_if_mount_failed = true, #else .format_if_mount_failed = false, #endif // EXAMPLE_FORMAT_IF_MOUNT_FAILED .max_files = 5, .allocation_unit_size = 16 * 1024 }; sdmmc_card_t *card; const char mount_point ; cid.name); ret = s_example_write_file(file_hello, data); if (ret != ESP_OK) { return; } const char *file_foo = MOUNT_POINT"/foo.txt"; // Check if destination file exists before renaming struct stat st; if (stat(file_foo, &st) == 0) { // Delete it if it exists unlink(file_foo); } // Rename original file ESP_LOGI(TAG, "Renaming file %s to %s", file_hello, file_foo); if (rename(file_hello, file_foo) != 0) { ESP_LOGE(TAG, "Rename failed"); return; } ret = s_example_read_file(file_foo); if (ret != ESP_OK) { return; } // Format FATFS ret = esp_vfs_fat_sdcard_format(mount_point, card); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to format FATFS (%s)", esp_err_to_name(ret)); return; } if (stat(file_foo, &st) == 0) { ESP_LOGI(TAG, "file still exists"); return; } else { ESP_LOGI(TAG, "file doesnt exist, format done"); } const char *file_nihao = MOUNT_POINT"/nihao.txt"; memset(data, 0, EXAMPLE_MAX_CHAR_SIZE); cid.name); ret = s_example_write_file(file_nihao, data); if (ret != ESP_OK) { return; } //Open file for reading ret = s_example_read_file(file_nihao); if (ret != ESP_OK) { return; } // All done, unmount partition and disable SPI peripheral esp_vfs_fat_sdcard_unmount(mount_point, card); ESP_LOGI(TAG, "Card unmounted"); //deinitialize the bus after all devices are removed spi_bus_free(host.slot); } 这段代码是使用SPI(串行外设接口)与SD卡进行通信。它展示了如何挂载SD卡、写入文件、读取文件、重命名文件、格式化SD卡,最后卸载SD卡。 代码首先包含了必要的头文件,并定义了一些常量,如最大字符大小、SD卡的挂载点和SPI通信的引脚分配。 然后定义了两个辅助函数:s_example_write_file和s_example_read_file。s_example_write_file函数打开一个给定路径的文件,写入数据,然后关闭文件。如果无法打开文件,它会记录一个错误并返回失败状态。s_example_read_file函数打开一个给定路径的文件,从中读取一行,然后关闭文件。如果无法打开文件,它会记录一个错误并返回失败状态。 app_main函数是程序的主入口点。它首先定义了挂载SD卡的配置。然后初始化SPI总线和SD卡插槽。如果初始化失败,它会记录一个错误并返回。 接下来,它试图挂载SD卡。如果挂载失败,它会记录一个错误并返回。如果挂载成功,它会记录一个成功消息并打印SD卡的属性。 程序然后在SD卡上写入一个文件,检查另一个文件是否存在,如果存在则删除它,然后将第一个文件重命名为第二个文件。然后从第二个文件中读取。 程序接着格式化SD卡,并检查第二个文件是否仍然存在。如果存在,它会记录一个消息并返回。如果不存在,它会记录一个成功消息,写入第三个文件,并从第三个文件中读取。 最后,卸载SD卡并禁用SPI外设。 编译: 烧写到ESP32C3后运行,控制台输出: I (286) app_start: Starting scheduler on CPU0 I (291) main_task: Started on CPU0 I (291) main_task: Calling app_main() I (291) example: Initializing SD card I (301) example: Using SPI peripheral I (301) example: Mounting filesystem I (311) gpio: GPIO | InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0 I (321) sdspi_transaction: cmd=52, R1 response: command not supported I (361) sdspi_transaction: cmd=5, R1 response: command not supported I (391) example: Filesystem mounted Name: CS064 Type: SDHC/SDXC Speed: 20.00 MHz (limit: 20.00 MHz) Size: 7382MB CSD: ver=2, sector_size=512, capacity=15118336 read_bl_len=9 SSR: bus_width=1 I (401) example: Opening file /sdcard/hello.txt I (411) example: File written I (411) example: Renaming file /sdcard/hello.txt to /sdcard/foo.txt I (411) example: Reading file /sdcard/foo.txt I (421) example: Read from file: 'Hello CS064!' I (421) vfs_fat_sdmmc: Formatting card, allocation unit size=16384 I (2911) example: file doesnt exist, format done I (2911) example: Opening file /sdcard/nihao.txt I (2921) example: File written I (2921) example: Reading file /sdcard/nihao.txt I (2921) example: Read from file: 'Nihao CS064!' I (2921) gpio: GPIO | InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0 I (2931) example: Card unmounted I (2931) gpio: GPIO | InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 I (2941) gpio: GPIO | InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 I (2951) gpio: GPIO | InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 I (2961) main_task: Returned from app_main() 可以看到输出了存储卡信息: Name: CS064 Type: SDHC/SDXC Speed: 20.00 MHz (limit: 20.00 MHz) Size: 7382MB CSD: ver=2, sector_size=512, capacity=15118336 read_bl_len=9 SSR: bus_width=1 后面还输出了读写文件相应的信息。
  • 热度 2
    2024-6-21 18:03
    249 次阅读|
    0 个评论
      在此介绍的是使用FPGA实现SD NAND FLASH的读写操作,以雷龙发展提供的CS创世SD NAND FLASH样品为例,分别讲解电路连接、读写时序与仿真和实验结果。 目录   1 视频讲解   2 SD NAND FLASH背景介绍   3 样品申请   4 电路结构与接口协议   4.1 SD NAND   4.2 SD NAND测试板 4.3 FPGA开发 板   5 SD卡协议与时序流程   5.1 SD卡协议   5.2 SD卡2.0版本初始化步骤   5.3 SD卡的读步骤   5.4 SD卡的写步骤   6 模块代码   6.1 sd_card_top   6.2 sd_card_cmd   6.3 sd_card_sec_read_write   6.4 spi_master   6.5 其余代码   6.5.1 sd_card_test   6.5.2 ax_debounce   6.5.3 seg_decoder   6.5.4 seg_scan   7 实验结果   8 参考资料   使用FPGA讲解SD NAND FLASH的文章网上也有很多比较详实的内容,本文的部分思路也是参考了其他博主的博客思路。  1 视频讲解   为了便于更加清晰地讲解内容,本文也将文章的对应部分以视频的形式进行了录制:   (后期正在紧锣密鼓制作ing)  2 SD NAND FLASH背景介绍   目前市面上主流的存储芯片,分为了EEPROM、NOR FLASH、NAND FLASH三种,其中后两种是市面上主要的非易失闪存技术,他们分别具有不同的特点:   1.EEPROM   EEPROM (Electrically Erasable Programmable read only memory)是指带电可擦可编程只读存储器。是一种掉电后数据不丢失的存储芯片。 EEPROM 可以在电脑上或专用设备上擦除已有信息,重新编程。一般用在即插即用设备中。   相较于EEPROM计数,下文提到的FLASH技术,具有更快的速度,工艺上可以分为NOR FLASH和NAND FLASH两种   2.NOR FLASH   NOR FLASH是一种非易失闪存技术。其特点是芯片内执行 (XIP),应用程序可以直接在存储芯片内运行,不必再把代码读到系统 RAM 中。其传输效率较高高,在 1~4MB 的小容量时具有很高的成本效益。   3.NAND FLASH   NAND FLASH内部采用非线性宏单元模式,这种结构能提供极高的单元密度,并且写入和擦除的速度很快。作为当前最热门的存储芯片,目前生活中常见的电子产品都会使用到这种存储芯片,例如数码相机、U盘等等。   由于NAND FLASH在大容量应用中的便利性,因此作为今天介绍的主角~   什么是SD NAND呢(以下省略FLASH)?下面的内容是从雷龙发展官网的介绍中得到:   SD NAND俗称贴片式TF卡,尽管与TF卡名称类似,但是有较大的区别:   相比常见的TF卡,SD NAND是专门为内置存储进行设计,焊接在PCB板上以供工业级产品的应用。因此对品质稳定性、一致性、以及尺寸都有较高的要求。   下图中左侧即为SD NAND、右侧是常见的TF卡。 3 样品申请   本文所使用的CS创世SD NAND是从深圳雷龙发展申请获得,可以在官网中最上面找到申请样品的入口:   深圳市雷龙发展有限公司创立于2008年,专注NAND Flash设计研发13年。创始人均为步步高/华为技术背景出身。是一家专注于存储元器件代理分销商。 如果有一些技术问题也可以和其公司人员进行沟通,相关的工作人员非常专业和热心。   下图是我收到的测试样品:  4 电路结构与接口协议   4.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协议详解传送门)   因此本文主要介绍SPI MODE下各个引脚的功能:   确定了通讯模式后,也就便于我们后文中,利用这种通讯模式按照SD卡的读写时序进行读写操作。 4.2 SD NAND测试板   单独的SD NAND不便于我们使用FPGA进行读写测试,好在官方提供了测试板,如下图所示:   有了它就可以轻松实现SD NAND与我们常见的FPGA开发板上的Micro SD插槽进行连接与测试了。   适用产品:LGA8,6x8mm 封装的SD NAND产品。   测试板尺寸:长度6.22厘米,宽度2.49厘米,接口长度2.53厘米。   使用方法:将芯片焊接至测试板上,可在原有的Micro SD卡座上直接调试和测试。   准备工具:热风枪,锡膏,镊子。温度要求:将热风枪温度调至300摄氏度℃即可焊接。  4.3 FPGA开发板   本文所使用的是黑金的AX301开发板,上面装有一个 Micro SD 卡座, FPGA 通过 SPI 数据总线访问 Micro SD 卡,SD 卡座和 FPGA 的硬件电路连接如下:   借由硬件电路的连接,FPGA可以直接与我们的SD NAND进行通信了。   至此,我们已经实现了SD NANDSPI通信方式方案的确定以及基于此的硬件电路连接,下一步就是根据SD卡的读写时序讲通信方式初始化为SPI模式,并按照SD卡协议进行读写操作。  5 SD卡协议与时序流程  5.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卡命令,下面的表格进行简单介绍,这里可以找到完整版: 5.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 拉高 5.3 SD卡的读步骤   发送 CMD17(单块)或 CMD18(多块)读命令,返回 0X00   接收数据开始令牌 fe(或 fc) +正式数据 512Bytes + CRC 校验 2Bytes(默认正式传输的数据长度是 512Bytes) 5.4 SD卡的写步骤   发送 CMD24(单块)或 CMD25(多块)写命令,返回 0X00   发送数据开始令牌 fe(或 fc) +正式数据 512Bytes + CRC 校验 2Bytes   6 模块代码   本代码所实现的功能,是基于黑金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(数码管控制)   下面主要介绍上述四个加粗的模块以及其功能  6.1 sd_card_top   本模块是SD card的top模块,用来实现不同子模块之间的连接。 // // // // // // Author: meisq // // msq@qq.com // // ALINX(shanghai) Technology Co.,Ltd // // heijin // // WEB: http://www.alinx.cn/ // // BB S: http://www.heijin.org/ // // // // // // // Copyright (c) 2017,ALINX(shanghai) Technology Co.,Ltd // // All rights reserved // // // // This source file may be used and distributed without restriction provided // // that this copyright statement is not removed from the file and that any // // derivative work contains the original copyright notice and the associated // // disclaimer. // // // // //========================================================================== // Revision History: // Date By Revision Change Description //-------------------------------------------------------------------------- // 2017/6/21 meisq 1.0 Original //*************************************************************************/ module sd_card_top #( 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 ) 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 ) ) ( input clk, input rst, output SD_nCS, //SD card chip select (SPI mode) output SD_DCLK, //SD card clock output SD_MOSI, //SD card controller data output input SD_MISO, //SD card controller data input output sd_init_done, //SD card initialization is complete input sd_sec_read, //SD card sector read input sd_sec_read_addr, //SD card sector read address output sd_sec_read_data, //SD card sector read data output sd_sec_read_data_valid, //SD card sector read data valid output sd_sec_read_end, //SD card sector read end input sd_sec_write, //SD card sector write input sd_sec_write_addr, //SD card sector write address input sd_sec_write_data, //SD card sector write data output sd_sec_write_data_req, //SD card sector write data next clock is valid output sd_sec_write_end //SD card sector write end ); wire spi_clk_div; //SPI module clock division parameter wire cmd_req; //SD card command request wire cmd_req_ack; //SD card command request response wire cmd_req_error; //SD card command request error wire cmd; //SD card command wire cmd_r1; //SD card expect response wire cmd_data_len; //SD card command read data length wire block_read_req; //SD card sector data read request wire block_read_valid; //SD card sector data read data valid wire block_read_data; //SD card sector data read data wire block_read_req_ack; //SD card sector data read response wire block_write_req; //SD card sector data write request wire block_write_data; //SD card sector data write data next clock is valid wire block_write_data_rd; //SD card sector data write data wire block_write_req_ack; //SD card sector data write response wire nCS_ctrl; //SPI module chip select control wire spi_wr_req; //SPI module data sending request wire spi_wr_ack; //SPI module data request response wire spi_data_in; //SPI module send data wire spi_data_out; //SPI module data returned wire clk_div; sd_card_sec_read_write #( .SPI_LOW_SPEED_DIV(SPI_LOW_SPEED_DIV), .SPI_HIGH_SPEED_DIV(SPI_HIGH_SPEED_DIV) ) sd_card_sec_read_write_m0( .clk (clk ), .rst (rst ), .sd_init_done (sd_init_done ), .sd_sec_read (sd_sec_read ), .sd_sec_read_addr (sd_sec_read_addr ), .sd_sec_read_data (sd_sec_read_data ), .sd_sec_read_data_valid (sd_sec_read_data_valid ), .sd_sec_read_end (sd_sec_read_end ), .sd_sec_write (sd_sec_write ), .sd_sec_write_addr (sd_sec_write_addr ), .sd_sec_write_data (sd_sec_write_data ), .sd_sec_write_data_req (sd_sec_write_data_req ), .sd_sec_write_end (sd_sec_write_end ), .spi_clk_div (spi_clk_div ), .cmd_req (cmd_req ), .cmd_req_ack (cmd_req_ack ), .cmd_req_error (cmd_req_error ), .cmd (cmd ), .cmd_r1 (cmd_r1 ), .cmd_data_len (cmd_data_len ), .block_read_req (block_read_req ), .block_read_valid (block_read_valid ), .block_read_data (block_read_data ), .block_read_req_ack (block_read_req_ack ), .block_write_req (block_write_req ), .block_write_data (block_write_data ), .block_write_data_rd (block_write_data_rd ), .block_write_req_ack (block_write_req_ack ) ); sd_card_cmd sd_card_cmd_m0( .sys_clk (clk ), .rst (rst ), .spi_clk_div (spi_clk_div ), .cmd_req (cmd_req ), .cmd_req_ack (cmd_req_ack ), .cmd_req_error (cmd_req_error ), .cmd (cmd ), .cmd_r1 (cmd_r1 ), .cmd_data_len (cmd_data_len ), .block_read_req (block_read_req ), .block_read_req_ack (block_read_req_ack ), .block_read_data (block_read_data ), .block_read_valid (block_read_valid ), .block_write_req (block_write_req ), .block_write_data (block_write_data ), .block_write_data_rd (block_write_data_rd ), .block_write_req_ack (block_write_req_ack ), .nCS_ctrl (nCS_ctrl ), .clk_div (clk_div ), .spi_wr_req (spi_wr_req ), .spi_wr_ack (spi_wr_ack ), .spi_data_in (spi_data_in ), .spi_data_out (spi_data_out ) ); spi_master spi_master_m0( .sys_clk (clk ), .rst (rst ), .nCS (SD_nCS ), .DCLK (SD_DCLK ), .MOSI (SD_MOSI ), .MISO (SD_MISO ), .clk_div (clk_div ), .CPOL (1'b1 ), .CPHA (1'b1 ), .nCS_ctrl (nCS_ctrl ), .wr_req (spi_wr_req ), .wr_ack (spi_wr_ack ), .data_in (spi_data_in ), .data_out (spi_data_out ) ); endmodule 6.2 sd_card_cmd   sd_card_cmd 模块主要实验 sd 卡基本命令操作,还有上电初始化的 88 个周期的时钟,数据   块的读写,状态机如下:   代码如下: // // // // // // Author: meisq // // msq@qq.com // // ALINX(shanghai) Technology Co.,Ltd // // heijin // // WEB: http://www.alinx.cn/ // // BBS: http://www.heijin.org/ // // // // // // // Copyright (c) 2017,ALINX(shanghai) Technology Co.,Ltd // // All rights reserved // // // // This source file may be used and distributed without restriction provided // // that this copyright statement is not removed from the file and that any // // derivative work contains the original copyright notice and the associated // // disclaimer. // // // // //========================================================================== // Revision History: // Date By Revision Change Description //-------------------------------------------------------------------------- // 2017/6/21 meisq 1.0 Original //*************************************************************************/ module sd_card_cmd( input sys_clk, input rst, input spi_clk_div, //SPI module clock division parameter input cmd_req, //SD card command request output cmd_req_ack, //SD card command request response output reg cmd_req_error, //SD card command request error input cmd, //SD card command input cmd_r1, //SD card expect response input cmd_data_len, //SD card command read data length input block_read_req, //SD card sector data read request output reg block_read_valid, //SD card sector data read data valid output reg block_read_data, //SD card sector data read data output block_read_req_ack, //SD card sector data read response input block_write_req, //SD card sector data write request input block_write_data, //SD card sector data write data next clock is valid output block_write_data_rd, //SD card sector data write data output block_write_req_ack, //SD card sector data write response output nCS_ctrl, //SPI module chip select control output reg clk_div, output reg spi_wr_req, //SPI module data sending request input spi_wr_ack, //SPI module data request response output spi_data_in, //SPI module send data input spi_data_out //SPI module data returned ); parameter S_IDLE = 0; parameter S_WAIT = 1; parameter S_INIT = 2; parameter S_CMD_PRE = 3; parameter S_CMD = 4; parameter S_CMD_DATA = 5; parameter S_READ_WAIT = 6; parameter S_READ = 7; parameter S_READ_ACK = 8; parameter S_WRITE_TOKEN = 9; parameter S_WRITE_DATA_0 = 10; parameter S_WRITE_DATA_1 = 11; parameter S_WRITE_CRC = 12; parameter S_WRITE_ACK = 13; parameter S_ERR = 14; parameter S_END = 15; reg state; reg CS_reg; reg byte_cnt; reg send_data; wire data_recv; reg wr_data_cnt; assign cmd_req_ack = (state == S_END); assign block_read_req_ack = (state == S_READ_ACK); assign block_write_req_ack= (state == S_WRITE_ACK); assign block_write_data_rd = (state == S_WRITE_DATA_0); assign spi_data_in = send_data; assign data_recv = spi_data_out; assign nCS_ctrl = CS_reg; always@(posedge sys_clk or posedge rst) begin if(rst == 1'b1) begin CS_reg <= 1'b1; spi_wr_req <= 1'b0; byte_cnt <= 16'd0; clk_div <= 16'd0; send_data <= 8'hff; state <= S_IDLE; cmd_req_error <= 1'b0; wr_data_cnt <= 10'd0; end else case(state) S_IDLE: begin state <= S_INIT; clk_div <= spi_clk_div; CS_reg <= 1'b1; end S_INIT: begin //send 11 bytes on power(at least 74 SPI clocks) if(spi_wr_ack == 1'b1) begin = 16'd10) begin byte_cnt <= 16'd0; spi_wr_req <= 1'b0; state <= S_WAIT; end begin byte_cnt <= byte_cnt + 16'd1; end end else begin spi_wr_req <= 1'b1; send_data <= 8'hff; end end S_WAIT: begin cmd_req_error <= 1'b0; wr_data_cnt <= 10'd0; //wait for instruction if(cmd_req == 1'b1) state <= S_CMD_PRE; else if(block_read_req == 1'b1) state <= S_READ_WAIT; else if(block_write_req == 1'b1) state <= S_WRITE_TOKEN; clk_div <= spi_clk_div; end S_CMD_PRE: begin //before sending a command, send an byte 'ff',provide some clocks if(spi_wr_ack == 1'b1) begin state <= S_CMD; spi_wr_req <= 1'b0; byte_cnt <= 16'd0; end else begin spi_wr_req <= 1'b1; CS_reg <= 1'b1; send_data <= 8'hff; end end S_CMD: begin if(spi_wr_ack == 1'b1) begin if((byte_cnt == 16'hffff) || (data_recv != cmd_r1 && data_recv == 1'b0)) begin state <= S_ERR; spi_wr_req <= 1'b0; end else if(data_recv == cmd_r1) begin spi_wr_req <= 1'b0; if(cmd_data_len != 16'd0) begin state <= S_CMD_DATA; byte_cnt <= 16'd0; end else state <= S_END; end else byte_cnt <= byte_cnt + 16'd1; end else begin spi_wr_req <= 1'b1; CS_reg <= 1'b0; if(byte_cnt == 16'd0) send_data <= (cmd | 8'h40); else if(byte_cnt == 16'd1) send_data <= cmd ; else if(byte_cnt == 16'd2) send_data <= cmd ; else if(byte_cnt == 16'd3) send_data <= cmd ; else if(byte_cnt == 16'd4) send_data <= cmd ; else if(byte_cnt == 16'd5) send_data <= cmd ; else send_data <= 8'hff; end end S_CMD_DATA: begin if(spi_wr_ack == 1'b1) begin if(byte_cnt == cmd_data_len - 16'd1) begin state <= S_END; spi_wr_req <= 1'b0; byte_cnt <= 16'd0; end else begin byte_cnt <= byte_cnt + 16'd1; end end else begin spi_wr_req <= 1'b1; send_data <= 8'hff; end end S_READ_WAIT: begin if(spi_wr_ack == 1'b1 && data_recv == 8'hfe) begin spi_wr_req <= 1'b0; state <= S_READ; byte_cnt <= 16'd0; end else begin spi_wr_req <= 1'b1; send_data <= 8'hff; end end S_READ: begin if(spi_wr_ack == 1'b1) begin if(byte_cnt == 16'd513) begin state <= S_READ_ACK; spi_wr_req <= 1'b0; byte_cnt <= 16'd0; end else begin byte_cnt <= byte_cnt + 16'd1; end end else begin spi_wr_req <= 1'b1; send_data <= 8'hff; end end S_WRITE_TOKEN: if(spi_wr_ack == 1'b1) begin state <= S_WRITE_DATA_0; spi_wr_req <= 1'b0; end else begin spi_wr_req <= 1'b1; send_data <= 8'hfe; end S_WRITE_DATA_0: begin state <= S_WRITE_DATA_1; wr_data_cnt <= wr_data_cnt + 10'd1; end S_WRITE_DATA_1: begin if(spi_wr_ack == 1'b1 && wr_data_cnt == 10'd512) begin state <= S_WRITE_CRC; spi_wr_req <= 1'b0; end else if(spi_wr_ack == 1'b1) begin state <= S_WRITE_DATA_0; spi_wr_req <= 1'b0; end else begin spi_wr_req <= 1'b1; send_data <= block_write_data; end end S_WRITE_CRC: begin if(spi_wr_ack == 1'b1) begin if(byte_cnt == 16'd2) begin state <= S_WRITE_ACK; spi_wr_req <= 1'b0; byte_cnt <= 16'd0; end else begin byte_cnt <= byte_cnt + 16'd1; end end else begin spi_wr_req <= 1'b1; send_data <= 8'hff; end end S_ERR: begin state <= S_END; cmd_req_error <= 1'b1; end S_READ_ACK,S_WRITE_ACK,S_END: begin state <= S_WAIT; end default: state <= S_IDLE; endcase end always@(posedge sys_clk or posedge rst) begin if(rst == 1'b1) block_read_valid <= 1'b0; else if(state == S_READ && byte_cnt < 16'd512) block_read_valid <= spi_wr_ack; else block_read_valid <= 1'b0; end always@(posedge sys_clk or posedge rst) begin if(rst == 1'b1) block_read_data <= 8'd0; else if(state == S_READ && spi_wr_ack == 1'b1) block_read_data <= data_recv; end endmodule  6.3 sd_card_sec_read_write   sd_card_sec_read_write 模块继续完成 SD 卡初始化,然后等待扇区读写指令,并完成扇区的   读写操作。 下图为模块的状态机转换图,首先发送 CMD0 命令,然后发送 CMD8 命令,再发送   CMD55,接着发送 ACMD41,如果应答正常, sd 卡初始化完成,等待扇区的读写。   代码如下: // // // // // // Author: meisq // // msq@qq.com // // ALINX(shanghai) Technology Co.,Ltd // // heijin // // WEB: http://www.alinx.cn/ // // BBS: http://www.heijin.org/ // // // // // // // Copyright (c) 2017,ALINX(shanghai) Technology Co.,Ltd // // All rights reserved // // // // This source file may be used and distributed without restriction provided // // that this copyright statement is not removed from the file and that any // // derivative work contains the original copyright notice and the associated // // disclaimer. // // // // //=============================================================================== // Revision History: // Date By Revision Change Description //------------------------------------------------------------------------------- // 2017/6/21 meisq 1.0 Original //*******************************************************************************/ module sd_card_sec_read_write #( parameter SPI_LOW_SPEED_DIV = 248, // spi clk speed = clk speed /((SPI_LOW_SPEED_DIV + 2) * 2 ) parameter SPI_HIGH_SPEED_DIV = 0 // spi clk speed = clk speed /((SPI_HIGH_SPEED_DIV + 2) * 2 ) ) ( input clk, input rst, output reg sd_init_done, input sd_sec_read, input sd_sec_read_addr, output sd_sec_read_data, output sd_sec_read_data_valid, output sd_sec_read_end, input sd_sec_write, input sd_sec_write_addr, input sd_sec_write_data, output sd_sec_write_data_req, output sd_sec_write_end, output reg spi_clk_div, output reg cmd_req, input cmd_req_ack, input cmd_req_error, output reg cmd, output reg cmd_r1, output reg cmd_data_len, output reg block_read_req, input block_read_valid, input block_read_data, input block_read_req_ack, output reg block_write_req, output block_write_data, input block_write_data_rd, input block_write_req_ack ); reg read_data; reg timer; localparam S_IDLE = 0; localparam S_CMD0 = 1; localparam S_CMD8 = 2; localparam S_CMD55 = 3; localparam S_CMD41 = 4; localparam S_CMD17 = 5; localparam S_READ = 6; localparam S_CMD24 = 7; localparam S_WRITE = 8; localparam S_ERR = 14; localparam S_WRITE_END = 15; localparam S_READ_END = 16; localparam S_WAIT_READ_WRITE = 17; localparam S_CMD16 = 18; reg state; reg sec_addr; assign sd_sec_read_data_valid = (state == S_READ) && block_read_valid; assign sd_sec_read_data = block_read_data; assign sd_sec_read_end = (state == S_READ_END); assign sd_sec_write_data_req = (state == S_WRITE) && block_write_data_rd; assign block_write_data = sd_sec_write_data; assign sd_sec_write_end = (state == S_WRITE_END); always@(posedge clk or posedge rst) begin if(rst == 1'b1) begin state <= S_IDLE; cmd_req <= 1'b0; cmd_data_len <= 16'd0; cmd_r1 <= 8'd0; cmd <= 48'd0; spi_clk_div <= SPI_LOW_SPEED_DIV ; block_write_req <= 1'b0; block_read_req <= 1'b0; sec_addr <= 32'd0; sd_init_done <= 1'b0; end else case(state) S_IDLE: begin state <= S_CMD0; sd_init_done <= 1'b0; spi_clk_div <= SPI_LOW_SPEED_DIV ; end S_CMD0: begin if(cmd_req_ack & ~cmd_req_error) begin state <= S_CMD8; cmd_req <= 1'b0; end else begin cmd_req <= 1'b1; cmd_data_len <= 16'd0; cmd_r1 <= 8'h01; cmd <= {8'd0,8'h00,8'h00,8'h00,8'h00,8'h95}; end end S_CMD8: begin if(cmd_req_ack & ~cmd_req_error) begin state <= S_CMD55; cmd_req <= 1'b0; end else begin cmd_req <= 1'b1; cmd_data_len <= 16'd4; cmd_r1 <= 8'h01; cmd <= {8'd8,8'h00,8'h00,8'h01,8'haa,8'h87}; end end S_CMD55: begin if(cmd_req_ack & ~cmd_req_error) begin state <= S_CMD41; cmd_req <= 1'b0; end else begin cmd_req <= 1'b1; cmd_data_len <= 16'd0; cmd_r1 <= 8'h01; cmd <= {8'd55,8'h00,8'h00,8'h00,8'h00,8'hff}; end end S_CMD41: begin if(cmd_req_ack & ~cmd_req_error) begin state <= S_CMD16; cmd_req <= 1'b0; sd_init_done <= 1'b1; spi_clk_div <= SPI_HIGH_SPEED_DIV ; end else if(cmd_req_ack) begin state <= S_CMD55; end else begin cmd_req <= 1'b1; cmd_data_len <= 16'd0; cmd_r1 <= 8'h00; cmd <= {8'd41,8'h40,8'h00,8'h00,8'h00,8'hff}; end end S_CMD16: begin if(cmd_req_ack & ~cmd_req_error) begin state <= S_WAIT_READ_WRITE; cmd_req <= 1'b0; sd_init_done <= 1'b1; spi_clk_div <= SPI_HIGH_SPEED_DIV ; end else if(cmd_req_ack) begin state <= S_CMD55; end else begin cmd_req <= 1'b1; cmd_data_len <= 16'd0; cmd_r1 <= 8'h00; cmd <= {8'd16,32'd512,8'hff}; end end S_WAIT_READ_WRITE: begin if(sd_sec_write == 1'b1) begin state <= S_CMD24; sec_addr <= sd_sec_write_addr; end else if(sd_sec_read == 1'b1) begin state <= S_CMD17; sec_addr <= sd_sec_read_addr; end spi_clk_div <= 16'd0; end S_CMD24: begin if(cmd_req_ack & ~cmd_req_error) begin state <= S_WRITE; cmd_req <= 1'b0; end else begin cmd_req <= 1'b1; cmd_data_len <= 16'd0; cmd_r1 <= 8'h00; cmd <= {8'd24,sec_addr,8'hff}; end end S_WRITE: begin if(block_write_req_ack == 1'b1) begin block_write_req <= 1'b0; state <= S_WRITE_END; end else block_write_req <= 1'b1; end S_CMD17: begin if(cmd_req_ack & ~cmd_req_error) begin state <= S_READ; cmd_req <= 1'b0; end else begin cmd_req <= 1'b1; cmd_data_len <= 16'd0; cmd_r1 <= 8'h00; cmd <= {8'd17,sec_addr,8'hff}; end end S_READ: begin if(block_read_req_ack) begin state <= S_READ_END; block_read_req <= 1'b0; end else begin block_read_req <= 1'b1; end end S_WRITE_END: begin state <= S_WAIT_READ_WRITE; end S_READ_END: begin state <= S_WAIT_READ_WRITE; end default: state <= S_IDLE; endcase end endmodule  6.4 spi_master   这一模块用来完成SPI一个字节的读写。   spi master 状态机设计, 主要完成一个字节 spi 数据的读写,由于是全双工的,写一个字节的   同时也读一个字节。 首先空闲状态“IDLE”接收到写请求后进入“DCLK_IDLE”状态,这个状态为   spi 时钟沿变化保持一定的时间,用来控制 spi 时钟的周期,然后进入 spi 时钟沿的变化状态,一   个字节上升沿和下降沿一共 16 个数据沿。 在最后一个数据沿进入“LAST_HALF_CYCLE”状态,为   让最后一个沿也保持一定的时间,再进入应答状态,完成一次写请求。spi_master 模块中模拟了一个 spi 时钟,在状态机进入到‘DCLK_EDGE’时进行翻转。状态机图示如下:   代码如下: // // // // // // Author: meisq // // msq@qq.com // // ALINX(shanghai) Technology Co.,Ltd // // heijin // // WEB: http://www.alinx.cn/ // // BBS: http://www.heijin.org/ // // // // // // // Copyright (c) 2017,ALINX(shanghai) Technology Co.,Ltd // // All rights reserved // // // // This source file may be used and distributed without restriction provided // // that this copyright statement is not removed from the file and that any // // derivative work contains the original copyright notice and the associated // // disclaimer. // // // // //========================================================================== // Revision History: // Date By Revision Change Description //-------------------------------------------------------------------------- // 2017/6/19 meisq 1.0 Original //*************************************************************************/ module spi_master ( input sys_clk, input rst, output nCS, //chip select (SPI mode) output DCLK, //spi clock output MOSI, //spi data output input MISO, //spi input input CPOL, input CPHA, input nCS_ctrl, input clk_div, input wr_req, output wr_ack, input data_in, output data_out ); localparam IDLE = 0; localparam DCLK_EDGE = 1; localparam DCLK_IDLE = 2; localparam ACK = 3; localparam LAST_HALF_CYCLE = 4; localparam ACK_WAIT = 5; reg DCLK_reg; reg MOSI_shift; reg MISO_shift; reg state; reg next_state; reg clk_cnt; reg clk_edge_cnt; assign MOSI = MOSI_shift ; assign DCLK = DCLK_reg; assign data_out = MISO_shift; assign wr_ack = (state == ACK); assign nCS = nCS_ctrl; always@(posedge sys_clk or posedge rst) begin if(rst) state <= IDLE; else state <= next_state; end always@(*) begin case(state) IDLE: if(wr_req == 1'b1) next_state <= DCLK_IDLE; else next_state <= IDLE; DCLK_IDLE: //half a SPI clock cycle produces a clock edge if(clk_cnt == clk_div) next_state <= DCLK_EDGE; else next_state <= DCLK_IDLE; DCLK_EDGE: //a SPI byte with a total of 16 clock edges if(clk_edge_cnt == 5'd15) next_state <= LAST_HALF_CYCLE; else next_state <= DCLK_IDLE; //this is the last data edge LAST_HALF_CYCLE: if(clk_cnt == clk_div) next_state <= ACK; else next_state <= LAST_HALF_CYCLE; //send one byte complete ACK: next_state <= ACK_WAIT; //wait for one clock cycle, to ensure that the cancel request signal ACK_WAIT: next_state <= IDLE; default: next_state <= IDLE; endcase end always@(posedge sys_clk or posedge rst) begin if(rst) DCLK_reg <= 1'b0; else if(state == IDLE) DCLK_reg <= CPOL; else if(state == DCLK_EDGE) DCLK_reg <= ~DCLK_reg;//SPI clock edge end //SPI clock wait counter always@(posedge sys_clk or posedge rst) begin if(rst) clk_cnt <= 16'd0; else if(state == DCLK_IDLE || state == LAST_HALF_CYCLE) clk_cnt <= clk_cnt + 16'd1; else clk_cnt <= 16'd0; end //SPI clock edge counter always@(posedge sys_clk or posedge rst) begin if(rst) clk_edge_cnt <= 5'd0; else if(state == DCLK_EDGE) clk_edge_cnt <= clk_edge_cnt + 5'd1; else if(state == IDLE) clk_edge_cnt <= 5'd0; end //SPI data output always@(posedge sys_clk or posedge rst) begin if(rst) MOSI_shift <= 8'd0; else if(state == IDLE && wr_req) MOSI_shift <= data_in; else if(state == DCLK_EDGE) if(CPHA == 1'b0 && clk_edge_cnt == 1'b1) MOSI_shift <= {MOSI_shift ,MOSI_shift }; else if(CPHA == 1'b1 && (clk_edge_cnt != 5'd0 && clk_edge_cnt == 1'b0)) MOSI_shift <= {MOSI_shift ,MOSI_shift }; end //SPI data input always@(posedge sys_clk or posedge rst) begin if(rst) MISO_shift <= 8'd0; else if(state == IDLE && wr_req) MISO_shift <= 8'h00; else if(state == DCLK_EDGE) if(CPHA == 1'b0 && clk_edge_cnt == 1'b0) MISO_shift <= {MISO_shift ,MISO}; else if(CPHA == 1'b1 && (clk_edge_cnt == 1'b1)) MISO_shift <= {MISO_shift ,MISO}; end endmodule  6.5 其余代码   6.5.1 sd_card_test // // // // // // Author: meisq // // msq@qq.com // // ALINX(shanghai) Technology Co.,Ltd // // heijin // // WEB: http://www.alinx.cn/ // // BBS: http://www.heijin.org/ // // // // // // // Copyright (c) 2017,ALINX(shanghai) Technology Co.,Ltd // // All rights reserved // // // // This source file may be used and distributed without restriction provided // // that this copyright statement is not removed from the file and that any // // derivative work contains the original copyright notice and the associated // // disclaimer. // // // // //================================================================================ // Revision History: // Date By Revision Change Description //-------------------------------------------------------------------------------- // 2017/6/19 meisq 1.0 Original //*******************************************************************************/ module sd_card_test( input clk, input rst_n, input key1, output SD_nCS, output SD_DCLK, output SD_MOSI, input SD_MISO, output seg_sel, output seg_data ); parameter S_IDLE = 0; parameter S_READ = 1; parameter S_WRITE = 2; parameter S_END = 3; reg state; wire sd_init_done; reg sd_sec_read; wire sd_sec_read_addr; wire sd_sec_read_data; wire sd_sec_read_data_valid; wire sd_sec_read_end; reg sd_sec_write; wire sd_sec_write_addr; reg sd_sec_write_data; wire sd_sec_write_data_req; wire sd_sec_write_end; reg wr_cnt; reg rd_cnt; wire button_negedge; reg read_data; ax_debounce ax_debounce_m0 ( .clk (clk), .rst (~rst_n), .button_in (key1), .button_posedge (), .button_negedge (button_negedge), .button_out () ); wire seg_data_0; seg_decoder seg_decoder_m0( .bin_data (read_data ), .seg_data (seg_data_0) ); wire seg_data_1; seg_decoder seg_decoder_m1( .bin_data (read_data ), .seg_data (seg_data_1) ); seg_scan seg_scan_m0( .clk (clk), .rst_n (rst_n), .seg_sel (seg_sel), .seg_data (seg_data), .seg_data_0 ({1'b1,7'b1111_111}), .seg_data_1 ({1'b1,7'b1111_111}), .seg_data_2 ({1'b1,7'b1111_111}), .seg_data_3 ({1'b1,7'b1111_111}), .seg_data_4 ({1'b1,seg_data_1}), .seg_data_5 ({sd_init_done,seg_data_0}) ); always@(posedge clk or negedge rst_n) begin if(rst_n == 1'b0) wr_cnt <= 10'd0; else if(state == S_WRITE) begin if(sd_sec_write_data_req == 1'b1) wr_cnt <= wr_cnt + 10'd1; end else wr_cnt <= 10'd0; end always@(posedge clk or negedge rst_n) begin if(rst_n == 1'b0) rd_cnt <= 10'd0; else if(state == S_READ) begin if(sd_sec_read_data_valid == 1'b1) rd_cnt <= rd_cnt + 10'd1; end else rd_cnt <= 10'd0; end always@(posedge clk or negedge rst_n) begin if(rst_n == 1'b0) read_data <= 8'd0; else if(state == S_READ) begin if(sd_sec_read_data_valid == 1'b1 && rd_cnt == 10'd0) read_data <= sd_sec_read_data; end else if(state == S_END && button_negedge == 1'b1) read_data <= read_data + 8'd1; end always@(posedge clk or negedge rst_n) begin if(rst_n == 1'b0) sd_sec_write_data <= 8'd0; else if(sd_sec_write_data_req) sd_sec_write_data <= read_data + wr_cnt ; end always@(posedge clk or negedge rst_n) begin if(rst_n == 1'b0) begin state <= S_IDLE; sd_sec_read <= 1'b0; sd_sec_write <= 1'b0; end else if(sd_init_done == 1'b0) begin state <= S_IDLE; end else case(state) S_IDLE: begin state <= S_READ; end S_WRITE: begin if(sd_sec_write_end == 1'b1) begin sd_sec_write <= 1'b0; state <= S_READ; end else sd_sec_write <= 1'b1; end S_READ: begin if(sd_sec_read_end == 1'b1) begin state <= S_END; sd_sec_read <= 1'b0; end else begin sd_sec_read <= 1'b1; end end S_END: begin if(button_negedge == 1'b1) state <= S_WRITE; end default: state <= S_IDLE; endcase end sd_card_top sd_card_top_m0( .clk (clk ), .rst (~rst_n ), .SD_nCS (SD_nCS ), .SD_DCLK (SD_DCLK ), .SD_MOSI (SD_MOSI ), .SD_MISO (SD_MISO ), .sd_init_done (sd_init_done ), .sd_sec_read (sd_sec_read ), .sd_sec_read_addr (sd_sec_read_addr ), .sd_sec_read_data (sd_sec_read_data ), .sd_sec_read_data_valid (sd_sec_read_data_valid ), .sd_sec_read_end (sd_sec_read_end ), .sd_sec_write (sd_sec_write ), .sd_sec_write_addr (sd_sec_write_addr ), .sd_sec_write_data (sd_sec_write_data ), .sd_sec_write_data_req (sd_sec_write_data_req ), .sd_sec_write_end (sd_sec_write_end ) ); endmodule 6.5.2 ax_debounce // // // // // // Author: meisq // // msq@qq.com // // ALINX(shanghai) Technology Co.,Ltd // // heijin // // WEB: http://www.alinx.cn/ // // BBS: http://www.heijin.org/ // // // // // // // Copyright (c) 2017,ALINX(shanghai) Technology Co.,Ltd // // All rights reserved // // // // This source file may be used and distributed without restriction provided // // that this copyright statement is not removed from the file and that any // // derivative work contains the original copyright notice and the associated // // disclaimer. // // // // //================================================================================ // Revision History: // Date By Revision Change Description //-------------------------------------------------------------------------------- // 2017/5/3 meisq 1.0 Original //*******************************************************************************/ `timescale 1 ns / 100 ps module ax_debounce ( input clk, input rst, input button_in, output reg button_posedge, output reg button_negedge, output reg button_out ); ---------------- internal constants -------------- parameter N = 32 ; // debounce timer bitwidth parameter FREQ = 50; //model clock :Mhz parameter MAX_TIME = 20; //ms localparam TIMER_MAX_VAL = MAX_TIME * 1000 * FREQ; ---------------- internal variables --------------- reg q_reg; // timing regs reg q_next; reg DFF1, DFF2; // input flip-flops wire q_add; // control flags wire q_reset; reg button_out_d0; ------------------------------------------------------ contenious assignment for counter control assign q_reset = (DFF1 ^ DFF2); // xor input flip flops to look for level chage to reset counter assign q_add = ~(q_reg == TIMER_MAX_VAL); // add to counter when q_reg msb is equal to 0 combo counter to manage q_next always @ ( q_reset, q_add, q_reg) begin case( {q_reset , q_add}) 2'b00 : q_next <= q_reg; 2'b01 : q_next <= q_reg + 1; default : q_next <= { N {1'b0} }; endcase end Flip flop inputs and q_reg update always @ ( posedge clk or posedge rst) begin if(rst == 1'b1) begin DFF1 <= 1'b0; DFF2 <= 1'b0; q_reg <= { N {1'b0} }; end else begin DFF1 <= button_in; DFF2 <= DFF1; q_reg <= q_next; end end counter control always @ ( posedge clk or posedge rst) begin if(rst == 1'b1) button_out <= 1'b1; else if(q_reg == TIMER_MAX_VAL) button_out <= DFF2; else button_out <= button_out; end always @ ( posedge clk or posedge rst) begin if(rst == 1'b1) begin button_out_d0 <= 1'b1; button_posedge <= 1'b0; button_negedge <= 1'b0; end else begin button_out_d0 <= button_out; button_posedge <= ~button_out_d0 & button_out; button_negedge <= button_out_d0 & ~button_out; end end endmodule   6.5.3 seg_decoder // // // // // // Author: meisq // // msq@qq.com // // ALINX(shanghai) Technology Co.,Ltd // // heijin // // WEB: http://www.alinx.cn/ // // BBS: http://www.heijin.org/ // // // // // // // Copyright (c) 2017,ALINX(shanghai) Technology Co.,Ltd // // All rights reserved // // // // This source file may be used and distributed without restriction provided // // that this copyright statement is not removed from the file and that any // // derivative work contains the original copyright notice and the associated // // disclaimer. // // // // //========================================================================== // Revision History: // Date By Revision Change Description //-------------------------------------------------------------------------- // 2017/6/19 meisq 1.0 Original //*************************************************************************/ module seg_decoder ( input bin_data, // bin data input output reg seg_data // seven segments LED output ); always@(*) begin case(bin_data) 4'd0:seg_data <= 7'b100_0000; 4'd1:seg_data <= 7'b111_1001; 4'd2:seg_data <= 7'b010_0100; 4'd3:seg_data <= 7'b011_0000; 4'd4:seg_data <= 7'b001_1001; 4'd5:seg_data <= 7'b001_0010; 4'd6:seg_data <= 7'b000_0010; 4'd7:seg_data <= 7'b111_1000; 4'd8:seg_data <= 7'b000_0000; 4'd9:seg_data <= 7'b001_0000; 4'ha:seg_data <= 7'b000_1000; 4'hb:seg_data <= 7'b000_0011; 4'hc:seg_data <= 7'b100_0110; 4'hd:seg_data <= 7'b010_0001; 4'he:seg_data <= 7'b000_0110; 4'hf:seg_data <= 7'b000_1110; default:seg_data <= 7'b111_1111; endcase end endmodule   6.5.4 seg_scan // // // // // // Author: meisq // // msq@qq.com // // ALINX(shanghai) Technology Co.,Ltd // // heijin // // WEB: http://www.alinx.cn/ // // BBS: http://www.heijin.org/ // // // // // // // Copyright (c) 2017,ALINX(shanghai) Technology Co.,Ltd // // All rights reserved // // // // This source file may be used and distributed without restriction provided // // that this copyright statement is not removed from the file and that any // // derivative work contains the original copyright notice and the associated // // disclaimer. // // // // //========================================================================== // Revision History: // Date By Revision Change Description //-------------------------------------------------------------------------- // 2017/6/19 meisq 1.0 Original //*************************************************************************/ module seg_scan( input clk, input rst_n, output reg seg_sel, //digital led chip select output reg seg_data, //eight segment digital tube output,MSB is the decimal point input seg_data_0, input seg_data_1, input seg_data_2, input seg_data_3, input seg_data_4, input seg_data_5 ); parameter SCAN_FREQ = 200; //scan frequency parameter CLK_FREQ = 50000000; //clock frequency parameter SCAN_COUNT = CLK_FREQ /(SCAN_FREQ * 6) - 1; reg scan_timer; //scan time counter reg scan_sel; //Scan select counter always@(posedge clk or negedge rst_n) begin if(rst_n == 1'b0) begin scan_timer <= 32'd0; scan_sel <= 4'd0; end = SCAN_COUNT) begin scan_timer <= 32'd0; if(scan_sel == 4'd5) scan_sel <= 4'd0; else scan_sel <= scan_sel + 4'd1; end else begin scan_timer <= scan_timer + 32'd1; end end always@(posedge clk or negedge rst_n) begin if(rst_n == 1'b0) begin seg_sel <= 6'b111111; seg_data <= 8'hff; end else begin case(scan_sel) //first digital led 4'd0: begin seg_sel <= 6'b11_1110; seg_data <= seg_data_0; end //second digital led 4'd1: begin seg_sel <= 6'b11_1101; seg_data <= seg_data_1; end //... 4'd2: begin seg_sel <= 6'b11_1011; seg_data <= seg_data_2; end 4'd3: begin seg_sel <= 6'b11_0111; seg_data <= seg_data_3; end 4'd4: begin seg_sel <= 6'b10_1111; seg_data <= seg_data_4; end 4'd5: begin seg_sel <= 6'b01_1111; seg_data <= seg_data_5; end default: begin seg_sel <= 6'b11_1111; seg_data <= 8'hff; end endcase end end endmodule  7 实验结果   下载实验程序后,可以看到数码管显示一个数字,这个数字是存储在 sd 卡中第一扇区的第一   个数据,数据是随机的,这个时候按键 KEY1 按下,数字加一,并写入了 sd 卡,再次下载程序,   可以看到直接显示更新后的数据。
  • 热度 4
    2024-3-7 18:04
    388 次阅读|
    0 个评论
    前言 NAND Flash 和 NOR Flash是现在市场上两种主要的闪存技术。Intel于1988年首先开发出 NOR Flash 技术,彻底改变了原先由 EPROM 和 EEPROM 一统天下的局面。紧接着,1989年,东芝公司发表了 NAND Flash 结构,后者的单元电路尺寸几乎只是 NOR 器件的一半,可以在给定的芯片尺寸内提供更高的容量,也就相应地降低了价格。 1.NAND Flash ROM NAND Flash ROM 应该是目前最热门的存储芯片了。因为我们生活中经常使用的电子产品都会涉及到它。比如你买手机,肯定会考虑64GB,还是256GB?买笔记本是买256GB,还是512GB容量的硬盘呢?(目前电脑大部分采用了基于 NAND Flash 产品的固态硬盘)。 2.NOR Flash ROM NOR Flash ROM 的特点是以字节为单位随机存取。这样,应用程序可以直接在 Flash ROM 中执行,不必再把程序代码预先读到 RAM 中。NOR Flash ROM 的接口简单,与通常的扩展存储器一样,可以直接连接到处理器的外围总线上。 与 NOR Flash ROM 相比, NAND Flash ROM 以页(行)为单位随机存取,在容量、使用寿命和成本方面有较大优势。但是它的读出速度稍慢,编程较为复杂,因此大多作为数据存储器使用。嵌入式产品中包括数码相机、MP3 随身听记忆卡、体积小巧的U盘等均采用 NAND Flash ROM 。 在存储结构上,NAND Flash 内部采用非线性宏单元模式,全部存储单元被划分为若干个块(类似于硬盘的,一般为8 KB),这也是擦除操作的基本单位。进而,每个块又分为若干个大小为512 B的页,每页的存储容量与硬盘每个扇区的容量相同。也就是说,每页都有512条位线,每条位线连接一个存储元。此时,要修改 NAND 芯片中一个字节,就必须重写整个数据块。当Flash 存储器的容量不同时,其块数量以及组成块的页的数量都将不同。相应地,地址信息包括了列地址、块地址以及相应的页面地址。这些地址通过8位总线分组传输,需要多个时钟周期。当容量增大时,地址信息增加,那么就需要占用更多的寻址周期,寻址时间也就越长。这导致NAND Flash的地址传输开销大,因此并不适合于频繁、小数据量访问的应用。 相比较而言,NAND 型 Flash 存储器具有更高的存储密度、更快的写人速度、更低的价格以及更好的擦写耐用性等优点,非常适用于大量数据的存储。但由于NAND Flash的接口和操作都相对复杂,位交换操作频繁,因此通常还要采用错误探测/错误纠正(EDC/ECC)算法来保护关键性数据。 例如深圳雷龙有限公司的 CSNP32GCR01-AOW 芯片。 一.免驱动使用。SD NAND内置了针对NAND Flash的坏块管理,平均读写,动态和静态的EDC/ECC等算法。 二.性能更稳定。由于NAND Flash内部是先擦后写机制,如果软件处理不当,在突然掉电的时候就会导致数据丢失。而SD NAND内部自带的垃圾回收等机制可以很好的规避这个问题。因此CS创世的二代产品才会通过10K次的随机掉电测试。 三.尺寸更小。目前SD NAND 是68mm 大小,8个pin脚,相比Raw NAND的1220mm大小,48个pin脚,采用SD NAND可以做出更小巧的产品,而且也能节省CPU宝贵的GPIO口(这点对于MCU单片机来说更是重要) 四.SD NAND可选容量更多。目前有128MB/512MB/4GB容量。而SLC 的Raw NAND 主流容量128MB,512MB已经少见,供货周期也很长;单颗4GB的Raw NAND基本都是MLC或者TLC NAND的晶圆,管理起来更复杂。 不用写驱动程序自带坏块管理的 NAND Flash(贴片式TF卡),尺寸小巧,简单易用,兼容性强,稳定可靠,固件可定制,LGA-8 封装,标准SDIO接口,兼容SPI,兼容拔插式TF卡/SD卡,可替代普通 TF卡/SD 卡,尺寸 6.2x8mm ,内置平均读写算法,通过1万次随机掉电测试耐高低温,机贴手贴都非常方便,速度级别Class10(读取速度 23.5MB/S 写入速度 12.3MB/S )标准的 SD2.0 协议普通的SD卡可直接驱动,支持TF卡启动的 SOC 都可以用 SD NAND。 SD NAND原理图如下:
  • 2023-11-23 17:42
    0 个评论
    文章目录 前言 传统SD卡和可贴片SD卡 传统SD卡 可贴片SD卡 实际使用 总结 前言 随着目前时代的快速发展,即使是使用MCU的项目上也经常有大数据存储的需求。可以看到经常有小伙伴这样提问: 大家好,请问有没有SD卡芯片,可以直接焊接到PCB板上的。 项目需要保存900M以上字节,nand flash 比较贵。或者有什么便宜的存储芯片提供。谢谢! 传统做法无非如下几种: 用eMMC芯片,和SD接口基本兼容,细节有区别。一般的操作系统能支持。 东芝还是谁有焊接的嵌入式SD模块,贵。 做TF卡的封装,直接上锡人工焊死。 其实还有一种选择就是可 贴片SD卡 。 传统SD卡和可贴片SD卡传统SD卡 相对于可贴片SD卡,传统SD卡存在一些劣势,包括: 尺寸和体积:传统SD卡相对较大,尺寸较大,占据更多的空间。这在某些紧凑型设备或嵌入式系统中可能会造成问题,因为可贴片SD卡的尺寸更小,更适合于空间受限的应用。 插拔耐久性:传统SD卡需要频繁插拔使用,这可能会导致卡槽和插口的磨损和腐蚀。长期使用后,可能会出现接触不良、断开连接或读写错误的问题。而可贴片SD卡由于直接焊接在PCB上,没有插拔操作,因此在插拔耐久性方面更有优势。 抗震抗振性能:传统SD卡的连接方式是通过卡槽和插口实现的,这种连接方式对于抗震抗振能力较差。在某些震动频繁的应用场景下,传统SD卡可能会出现连接不稳定或数据丢失的问题。而可贴片SD卡由于直接焊接在PCB上,具有更好的抗震抗振性能。 物理保护:传统SD卡的外部没有额外的保护措施,如防水、防尘等功能。在某些恶劣环境中使用时,传统SD卡可能会受到外界因素的影响,导致数据损坏或设备故障。而一些可贴片SD卡提供了防水、防尘和抗静电等功能,以增加物理保护。 传统SD卡和可贴片SD卡在功能和性能上并没有本质的区别,只是封装形式不同。选择哪种类型的SD卡取决于具体的应用需求和设备限制。对于空间受限、插拔频繁或对抗震抗振性能要求较高的场景,可贴片SD卡可能更适合。而对于一般应用,传统SD卡仍然是一种可靠的存储介质。 可贴片SD卡 可贴片SD卡是一种集成电路封装形式的SD(Secure Digital)存储卡。与传统的SD卡相比,可贴片SD卡采用了更为紧凑的封装形式,使其更适合于嵌入式系统和紧凑型设备的应用。 以下是可贴片SD卡的特点和优势: 封装形式:可贴片SD卡采用了表面贴装技术(Surface Mount Technology,SMT)封装,将SD卡的芯片和连接器集成在一个紧凑的封装中,没有外部插口。这种封装形式使得可贴片SD卡可以直接焊接在PCB(Printed Circuit Board)上,节省空间并提高可靠性。 尺寸小巧:可贴片SD卡的尺寸通常比传统的SD卡更小,因此适用于那些对空间要求严格的设备,如嵌入式系统、便携式设备和小型电子产品等。 抗震抗振动:由于可贴片SD卡直接焊接在PCB上,没有外部插口,因此具有更好的抗震抗振动性能。这使得它更适合于应对恶劣环境和振动频繁的应用场景。 可靠性:可贴片SD卡的焊接连接更牢固,减少了插拔引起的接触不良和断开的风险,提高了存储数据的可靠性和稳定性。 实际使用 前主流的存储芯片大致可以分为NOR Flash和 NAND Flash 。NOR Flash容量比较小,所以一般项目对于容量有一定要求的话(512M起步),就会用采用NAND Flash。 一般从成本考虑,多会使用裸的NAND FLASH进行贴片。随着也会带来几个问题: 第一,笔者在项目中经常遇到NAND Flash的坏块问题,即使让厂商增加了出厂检测,仍会有较高的不良率,所以必须要进行坏块管理。 第二,不同品牌之间的NAND Flash,由于Page,Block大小不同,时序不同等。都需要嵌入式工程师重新调试驱动,经常遇到替换供应商后重新添加、修改驱动的问题,费时费力。 笔者所在项目需要再NAND FLASH中存储图片 语音数据,经常因为坏块问题需要去工厂解决问题,编写坏块管理,甚至手动编写平均读写算法……非常的麻烦。之前没有了解到国产有非常优秀的贴片式SD卡产品——雷龙。 自带SD转接测试板,方便在demo阶段直接使用SD卡接口测试。 兼容无压力,在电脑上也可以免驱直接读写,非常方便。 SD NAND 就是这样一款产品。简单来说它的架构如下图。内部使用寿命最长、性能最稳定的SLC NAND Flash晶圆,擦写次数可以达到10万次。另外,内置了特定的Flash控制器和Firmware,硬件对外采用最为通用的SD接口。 完美兼容了基本所有的项目,尺寸小巧,对于开发板甚至核心板这种对于尺寸要求很高的PCB都可以降低成本! SD NAND内置坏块管理,平均读写,动态和静态的EDC/ECC算法等等,除了让产品的质量更稳定,更好的延长寿命,更能减少CPU的负荷。让后续针对NAND Flash的操作,都可以交给SD NAND,CPU可以不用再管了。领导再也不用担心我的NAND Flash驱动了。 总结 实际使用下来感觉非常好,推荐大家在项目初期就考虑使用雷龙的NAND FLASH,可以节约大量硬件成本和人工成本。有兴趣的伙伴,可以随时联系雷龙官方客服。