tag 标签: 存储

相关帖子
相关博文
  • 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 芯片进行测试的,建议使用在嵌入式系统开发。
  • 2024-9-30 14:34
    38 次阅读|
    0 个评论
    01 概述 W25Q128是一种NOR Flash芯片,掉电后数据不丢失的特点。 W25Q128FV阵列被组织成65,536个可编程页面,每个页面256字节。每次最多可编程256字节。可以以16页为一组(即一个Sector)、128页为一组(8个Sector)、256页为一组(16个Sector)或整个芯片(芯片擦除)进行擦除。 W25Q128FV分别有4,096个可擦除扇区和256个可擦除块。较小的4KB扇区为需要数据和参数存储的应用程序提供了更大的灵活性。 标准SPI通信支持时钟频率高达104MHz,Dual SPI通信支持时钟频率高达208MHz,QSPI通信支持时钟频率高达416MHz。 注意:W25Q128一共为128M bits(16M Byte),又分为256个块(每个块512K bit(64K Byte)),每个块又分为16个扇区(每个扇区32K bit(4 KByte)),每个扇区又分为16页(每个页2K bit(256 Byte)) 02 物理特性 可以将 1 写成 0,但是不能将 0 写成 1,要想将 0 写成 1,必须进行擦除操作。如果要改变数据,就需要先擦除后写数据。 如果想要修改小于扇区大小的数据,需要将整个扇区的数据,在内存中进行备份,然后修改内存中的数据,再将数据写回到原扇区位置。因此,驱动要达到支持自动完成这个过程,用户可以使用驱动修改任意位置的数据。 03 存储结构 W25Q128可以存储16777216字节,存储一个字节占用一个地址,所以寻址范围是0-(16777216-1),对应的16进制为0-0xFFFFFF(所以寄存器地址是24位的) 04 命令总览 05 组件的使用 1 Gitee链接地址 Demo位于amaziot_bloom_os_sdk\sample\3rd\2.1_W25Q128 Gitee源码地址:https://gitee.com/ning./hongdou Github源码地址:https://github.com/ayumid/hongdou 编译指令:.\build.bat -l .\amaziot_bloom_os_sdk\sample\3rd\2.1_W25Q128 2 组件功能介绍 实现软件模拟SPI,驱动W25Q128芯片,实现数据存储。 3 代码讲解 1 drv_w25q128_delay_us 功能:该函数用于,延时。 参数: 参数 释义 count 死循环次数 返回值:无 示例: C //初始化i2c总线 ret = drv_xl9535_i2c_init(); 2 drv_w25q128_gpio_set 功能:该函数用于,模拟SPI设置IO输出电平。 参数: 参数 释义 num 引脚号 val 0 低电平,1 高电平 返回值:0 成功,-1 失败 示例: C drv_w25q128_gpio_set(DRV_w25q128_SPI_CS, DRV_w25q128_GPIO_LOW); 3 drv_w25q128_byte_wr 功能:该函数用于,SPI写读一个字节 mode3。 参数: 参数 释义 byte 发送数据 返回值:flash返回数据 示例: C drv_w25q128_byte_wr(DRV_w25q128_DUMMY_BYTE); 4 drv_w25q128_byte_rd 功能:该函数用于,SPI只读一个字节。 参数:无 返回值:flash返回数据 示例: C drv_w25q128_byte_rd (DRV_w25q128_DUMMY_BYTE); 5 drv_w25q128_busy_wait 功能:该函数用于,W25Q128 忙等待。 参数:无 返回值:无 示例: C while(drv_w25q128_read_reg1() & BIT_BUSY); 6 drv_w25q128_read_reg 功能:该函数用于,读reg。 参数:无 返回值:无 示例: C while(drv_w25q128_read_reg() & BIT_BUSY); 7 drv_w25q128_read_jedecid 功能:该函数用于,读 W25Q128 JEDEC_ID(制造商、类型、容量)。 参数:无 返回值:无 示例: C sample_w25q128_uart_printf("identification is 0x%X, Device id is 0x%X, Manufacturer Device ID is 0x%X", drv_w25q128_read_identification(), drv_w25q128_read_device_id(), drv_w25q128_read_manufacturer_id()); 8 drv_w25q128_read_manufacturer_id 功能:该函数用于,读 W25Q128 制造商 ID。 参数:无 返回值:无 示例: C sample_w25q128_uart_printf("identification is 0x%X, Device id is 0x%X, Manufacturer Device ID is 0x%X", drv_w25q128_read_identification(), drv_w25q128_read_device_id(), drv_w25q128_read_manufacturer_id()); 9 drv_w25q128_read_device_id 功能:该函数用于,读 W25Q128 设备 ID。 参数:无 返回值:无 示例: C sample_w25q128_uart_printf("identification is 0x%X, Device id is 0x%X, Manufacturer Device ID is 0x%X", drv_w25q128_read_identification(), drv_w25q128_read_device_id(), drv_w25q128_read_manufacturer_id()); 10 drv_w25q128_write_enable 功能:该函数用于,写使能。 参数:无 返回值:无 示例: C sample_w25q128_uart_printf("identification is 0x%X, Device id is 0x%X, Manufacturer Device ID is 0x%X", drv_w25q128_read_identification(), drv_w25q128_read_device_id(), drv_w25q128_read_manufacturer_id()); 11 drv_w25q128_write_disable 功能:该函数用于,写失能。 参数:无 返回值:无 示例: C 无 12 drv_w25q128_write_page 功能:该函数用于,页编程(调用本函数写入数据前需要先擦除扇区)。 参数: 参数 释义 pbuf 数据 addr 地址 len 长度 返回值:无 示例: C drv_w25q128_write_page(pbuf, addr, pageremain); 13 drv_w25q128_read 功能:该函数用于,读闪存数据。 参数: 参数 释义 pbuf 数据 addr 地址 len 长度 返回值:无 示例: C drv_w25q128_read((UINT8*)rx_buff1, 8181, strlen(tx_buff1)); 14 drv_w25q128_sector_erase 功能:该函数用于,扇区擦除。 参数: 参数 释义 addr 地址 返回值:无 示例: C drv_w25q128_sector_erase(secpos * DRV_w25q128_SOCTOR_SIZE); 15 drv_w25q128_chip_rease 功能:该函数用于,FLASH整片擦除(为了安全起见,若要调用,请先调用 drv_w25q128_write_enable 函数)。 参数:无 返回值:无 示例: C 无 16 drv_w25q128_powr_down 功能:该函数用于,掉电。 参数:无 返回值:无 示例: C 无 17 drv_w25q128_release_powr_down 功能:该函数用于,读闪存数据。 参数: 参数 释义 pbuf 数据 addr 地址 len 长度 返回值:无 示例: C drv_w25q128_read((UINT8*)rx_buff1, 8181, strlen(tx_buff1)); 18 drv_w25q128_write_nocheck 功能:该函数用于,写数据。 参数: 参数 释义 pbuf 数据 addr 地址 len 长度 返回值:无 示例: C drv_w25q128_write_nocheck(w25q128_buffer, secpos * DRV_w25q128_SOCTOR_SIZE, DRV_w25q128_SOCTOR_SIZE); 19 drv_w25q128_write 功能:该函数用于,写闪存数据,可以使任意地址。 参数: 参数 释义 pbuf 数据 addr 地址 len 长度 返回值:无 示例: C drv_w25q128_write((UINT8*)tx_buff1, 8181, strlen(tx_buff1)); 20 drv_w25q128_init 功能:该函数用于,写数据。 参数:无 返回值:无 示例: C drv_w25q128_init(); 4 Demo实战 4.1 创建一个Demo 复制20.1_file_xtu示例工程,到同一个文件夹下,修改文件名为3.1_SSD1315,如图: 4.2 修改makefile 增加文件组件所在目录头文件路径,和源文件路径,如图: 4.3 增加头文件 使用代码编辑器,将新建的工程文件加入代码编辑器中,打开main.c,修改main.c,加入am.h等头文件,如图: 4.4 修改代码 在Phase2Inits_exit 创建一个任务,如图: 4.1 概述 上电后,按下按键,串口会打印出按下了哪一个按键 4.2 测试 测试步骤: 参考编译教程,和文档开头的编译指令,进行编译 按照编译教程选择对应的选项 烧录 4.3 宏定义介绍 sample_w25q128_uart_printf 输出日志到DEBUG 串口,日志比较少,可以输出到这个串口,如果日志比较多,需要输出到usb口,以免不必要的问题出现 sample_w25q128_catstudio_printf 输出日志到USB 串口,使用catstudio查看,catstudio查看日志需要更新对应版本mdb.txt文件,软件打开filtter过滤日志,只查看用户输出的日志 SAMPLE_W25Q128_STACK_SIZE 栈空间宏定义 4.4 全局变量介绍 sample_w25q128_task_ref 任务指针 4.5 函数介绍 Phase1Inits_enter 底层初始化,本例空 Phase1Inits_exit 底层初始化,本例空 Phase2Inits_enter 底层初始化,本例空 Phase2Inits_exit 创建主任务,初始化INT 引脚 代码片段: C void Phase2Inits_exit(void) { int ret; sample_w25q128_task_stack = malloc(SAMPLE_W25Q128_STACK_SIZE); ret = OSATaskCreate(&sample_w25q128_task_ref, sample_w25q128_task_stack, SAMPLE_W25Q128_STACK_SIZE, 88, "sample_w25q128_task", sample_w25q128_task, NULL); ASSERT(ret == OS_SUCCESS); } sample_w25q128_task 主任务,代码发分为两部分,一部分是发送不定长数据;另一部分是上电后等待其它模块发送的数据,收到后打印到串口。 代码片段: C static void sample_w25q128_task(void *ptr) { int ret = 0; uint32_t identification = 0; // unsigned char writeBuf = {0}; // unsigned char readBuf = {0}; // ret = ql_spi_init(QL_SPI_PORT0, QL_SPI_MODE3, QL_SPI_CLK_812_5KHZ); // sample_w25q128_catstudio_printf("ql_spi_init ret %d", ret); drv_w25q128_init(); identification = drv_w25q128_read_jedecid(); sample_w25q128_uart_printf("identification is 0x%X, Device id is 0x%X, Manufacturer Device ID is 0x%X", drv_w25q128_read_jedecid(), drv_w25q128_read_device_id(), drv_w25q128_read_manufacturer_id()); // while(1) // { // drv_w25q128_gpio_set(DRV_w25q128_SPI_CS, 0); // sample_w25q128_uart_printf("low"); // OSATaskSleep(5*200); // drv_w25q128_gpio_set(DRV_w25q128_SPI_CS, 1); // sample_w25q128_uart_printf("high"); // OSATaskSleep(5*200); // } if(identification != JEDECID) { /* 读取错误处理 */ sample_w25q128_uart_printf("SPI read-write Error, please check the connection between MCU and SPI Flash\n"); } else { //读取成功处理 char tx_buff1 = "abcdefghigklmnopqrstuvwxyz0123456789"; char rx_buff1 = {0}; char tx_buff2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ9876543210"; char rx_buff2 = {0}; int i = 0; //测试跨sector写,并且读出数据,写两次,第二次保留第一次部分数据,证明数据擦除,写入正常 drv_w25q128_write((UINT8*)tx_buff1, 8181, strlen(tx_buff1));//从8181地址开始写数据,需要写第二和第三个扇区 drv_w25q128_read((UINT8*)rx_buff1, 8181, strlen(tx_buff1)); sample_w25q128_uart_printf("read flash:%s", rx_buff1); if(!strncmp(tx_buff1, rx_buff1, strlen(tx_buff1))) { sample_w25q128_uart_printf("SPI read-write succeed 1"); } //验证驱动擦除扇区时,可以保留之前有效内容 drv_w25q128_write((UINT8*)tx_buff2, 8186, strlen(tx_buff2));//从8186地址开始写数据,需要写第二和第三个扇区,同时不能擦掉8181 - 8186的5字节数据 drv_w25q128_read((UINT8*)rx_buff2, 8181, strlen(tx_buff2) + 5); sample_w25q128_uart_printf("read flash:%s", rx_buff2); if(!strncmp(rx_buff2, "abcdeABCDEFGHIJKLMNOPQRSTUVWXYZ9876543210", strlen("abcdeABCDEFGHIJKLMNOPQRSTUVWXYZ9876543210"))) { sample_w25q128_uart_printf("SPI read-write succeed 2"); } } // memset(writeBuf, 0x00, sizeof(writeBuf)); // memset(readBuf, 0x00, sizeof(readBuf)); // writeBuf = 0x9F; while (1) { // ret = ql_spi_write_read(QL_SPI_PORT0, readBuf, writeBuf, 1); // sample_w25q128_catstudio_printf("ql_spi_write_read ret %d, readBuf %02X,%02X,%02X\n", ret, readBuf , readBuf , readBuf ); // ret = ql_spi_write(QL_SPI_PORT0, writeBuf, 1); // sample_w25q128_catstudio_printf("ql_spi_write_read ret %d, readBuf %02X\n", ret, writeBuf ); // ret = ql_spi_read(QL_SPI_PORT0, readBuf, 3); // sample_w25q128_catstudio_printf("ql_spi_write_read ret %d, readBuf %02X,%02X,%02X\n", ret, readBuf , readBuf , readBuf ); OSATaskSleep(5 * 200); } } 4.6 固件 点击下载 Lora Demo固件 5 生态组件链接 SPI NOR FLASH 注:本文部分内容来源于网络,如有侵权,请及时联系我们。 本文章源自奇迹物联开源的物联网应用知识库Cellular IoT Wiki,更多技术干货欢迎关注收藏Wiki: Cellular IoT Wiki 知识库(https://rckrv97mzx.feishu.cn/wiki/wikcnBvAC9WOkEYG5CLqGwm6PHf)
  • 2024-9-30 12:31
    48 次阅读|
    0 个评论
    01 概述 GD25Q32是一种常见的串行闪存器件,它采用SPI(Serial Peripheral Interface)接口协议,具有高速读写和擦除功能,可用于存储和读取数据。GD25Q32芯片容量为32 Mbit(4 MB),其中名称后的数字代表不同的容量选项。不同的型号和容量选项可以满足不同应用的需求,通常被用于嵌入式设备、存储设备、路由器等高性能电子设备中。 GD25Q32闪存芯片的内存分配是按照扇区(Sector)和块(Block)进行的,每个扇区的大小为4KB,每个块包含16个扇区,即一个块的大小为64KB。 02 物理特性 可以将 1 写成 0,但是不能将 0 写成 1,要想将 0 写成 1,必须进行擦除操作。如果要改变数据,就需要先擦除后写数据。 如果想要修改小于扇区大小的数据,需要将整个扇区的数据,在内存中进行备份,然后修改内存中的数据,再将数据写回到原扇区位置。因此,驱动要达到支持自动完成这个过程,用户可以使用驱动修改任意位置的数据。 03 引脚定义和描述 04 命令概览 05 组件的使用 1 Gitee链接地址 Demo位于amaziot_bloom_os_sdk\sample\3rd\2.0_GD25Q20C Gitee源码地址:https://gitee.com/ning./hongdou Github源码地址:https://github.com/ayumid/hongdou 编译指令:.\build.bat -l .\amaziot_bloom_os_sdk\sample\3rd\2.0_GD25Q20C 2 组件功能介绍 实现软件模拟SPI,驱动GD25Q20芯片,实现数据存储 3 代码讲解 1 drv_gd25q20_delay_us 功能:该函数用于,延时。 参数: 参数 释义 count 死循环次数 返回值:无 示例: C //初始化i2c总线 ret = drv_xl9535_i2c_init(); 2 drv_gd25q20_gpio_set 功能:该函数用于,模拟SPI设置IO输出电平。 参数: 参数 释义 num 引脚号 val 0 低电平,1 高电平 返回值:0 成功,-1 失败 示例: C drv_gd25q20_gpio_set(DRV_GD25Q20_SPI_CS, DRV_GD25Q20_GPIO_LOW); 3 drv_gd25q20_byte_wr 功能:该函数用于,SPI写读一个字节 mode3。 参数: 参数 释义 byte 发送数据 返回值:flash返回数据 示例: C drv_gd25q20_byte_wr(DRV_GD25Q20_DUMMY_BYTE); 4 drv_gd25q20_byte_rd 功能:该函数用于,SPI只读一个字节。 参数:无 返回值:flash返回数据 示例: C drv_gd25q20_byte_rd (DRV_GD25Q20_DUMMY_BYTE); 5 drv_gd25q20_busy_wait 功能:该函数用于,GD25Q20 忙等待。 参数:无 返回值:无 示例: C while(drv_gd25q20_read_reg1() & BIT_BUSY); 6 drv_gd25q20_read_reg1 功能:该函数用于,读reg1。 参数:无 返回值:无 示例: C while(drv_gd25q20_read_reg1() & BIT_BUSY); 7 drv_gd25q20_read_identification 功能:该函数用于,读 GD25Q20 JEDEC_ID(制造商、类型、容量)。 参数:无 返回值:无 示例: C sample_gd25q20_uart_printf("identification is 0x%X, Device id is 0x%X, Manufacturer Device ID is 0x%X", drv_gd25q20_read_identification(), drv_gd25q20_read_device_id(), drv_gd25q20_read_manufacturer_id()); 8 drv_gd25q20_read_manufacturer_id 功能:该函数用于,读 GD25Q20 制造商 ID。 参数:无 返回值:无 示例: C sample_gd25q20_uart_printf("identification is 0x%X, Device id is 0x%X, Manufacturer Device ID is 0x%X", drv_gd25q20_read_identification(), drv_gd25q20_read_device_id(), drv_gd25q20_read_manufacturer_id()); 9 drv_gd25q20_read_device_id 功能:该函数用于,读 GD25Q20 设备 ID。 参数:无 返回值:无 示例: C sample_gd25q20_uart_printf("identification is 0x%X, Device id is 0x%X, Manufacturer Device ID is 0x%X", drv_gd25q20_read_identification(), drv_gd25q20_read_device_id(), drv_gd25q20_read_manufacturer_id()); 10 drv_gd25q20_write_enable 功能:该函数用于,写使能。 参数:无 返回值:无 示例: C sample_gd25q20_uart_printf("identification is 0x%X, Device id is 0x%X, Manufacturer Device ID is 0x%X", drv_gd25q20_read_identification(), drv_gd25q20_read_device_id(), drv_gd25q20_read_manufacturer_id()); 11 drv_gd25q20_write_disable 功能:该函数用于,写失能。 参数:无 返回值:无 示例: C 无 12 drv_gd25q20_write_page 功能:该函数用于,页编程(调用本函数写入数据前需要先擦除扇区)。 参数: 参数 释义 pbuf 数据 addr 地址 len 长度 返回值:无 示例: C drv_gd25q20_write_page(pbuf, addr, pageremain); 13 drv_gd25q20_read 功能:该函数用于,读闪存数据。 参数: 参数 释义 pbuf 数据 addr 地址 len 长度 返回值:无 示例: C drv_gd25q20_read((UINT8*)rx_buff1, 8181, strlen(tx_buff1)); 14 drv_gd25q20_sector_erase 功能:该函数用于,扇区擦除。 参数: 参数 释义 addr 地址 返回值:无 示例: C drv_gd25q20_sector_erase(secpos * DRV_GD25Q20_SOCTOR_SIZE); 15 drv_gd25q20_chip_rease 功能:该函数用于,FLASH整片擦除(为了安全起见,若要调用,请先调用 drv_gd25q20_write_enable 函数)。 参数:无 返回值:无 示例: C 无 16 drv_gd25q20_powr_down 功能:该函数用于,掉电。 参数:无 返回值:无 示例: C 无 17 drv_gd25q20_release_powr_down 功能:该函数用于,读闪存数据。 参数: 参数 释义 pbuf 数据 addr 地址 len 长度 返回值:无 示例: C drv_gd25q20_read((UINT8*)rx_buff1, 8181, strlen(tx_buff1)); 18 drv_gd25q20_write_nocheck 功能:该函数用于,写数据。 参数: 参数 释义 pbuf 数据 addr 地址 len 长度 返回值:无 示例: C drv_gd25q20_write_nocheck(gd25q20_buffer, secpos * DRV_GD25Q20_SOCTOR_SIZE, DRV_GD25Q20_SOCTOR_SIZE); 19 drv_gd25q20_write 功能:该函数用于,写闪存数据,可以使任意地址。 参数: 参数 释义 pbuf 数据 addr 地址 len 长度 返回值:无 示例: C drv_gd25q20_write((UINT8*)tx_buff1, 8181, strlen(tx_buff1)); 20 drv_gd25q20_init 功能:该函数用于,写数据。 参数:无 返回值:无 示例: C drv_gd25q20_init(); 4 Demo实战 4.1 概述 上电后,按下按键,串口会打印出按下了哪一个按键 4.2 测试 测试步骤: 参考编译教程,和文档开头的编译指令,进行编译 按照编译教程选择对应的选项 烧录 4.3 宏定义介绍 sample_gd25q20_uart_printf 输出日志到DEBUG 串口,日志比较少,可以输出到这个串口,如果日志比较多,需要输出到usb口,以免不必要的问题出现 sample_gd25q20_catstudio_printf 输出日志到USB 串口,使用catstudio查看,catstudio查看日志需要更新对应版本mdb.txt文件,软件打开filtter过滤日志,只查看用户输出的日志 SAMPLE_GD25Q20_STACK_SIZE 栈空间宏定义 4.4 全局变量介绍 sample_gd25q20_task_ref 任务指针 4.5 函数介绍 Phase1Inits_enter 底层初始化,本例空 Phase1Inits_exit 底层初始化,本例空 Phase2Inits_enter 底层初始化,本例空 Phase2Inits_exit 创建主任务,初始化INT 引脚 代码片段: C void Phase2Inits_exit(void) { int ret; sample_gd25q20_task_stack = malloc(SAMPLE_GD25Q20_STACK_SIZE); ret = OSATaskCreate(&sample_gd25q20_task_ref, sample_gd25q20_task_stack, SAMPLE_GD25Q20_STACK_SIZE, 88, "sample_gd25q20_task", sample_gd25q20_task, NULL); ASSERT(ret == OS_SUCCESS); } _task 主任务,代码发分为两部分,一部分是发送不定长数据;另一部分是上电后等待其它模块发送的数据,收到后打印到串口。 代码片段: C static void sample_gd25q20_task(void *ptr) { int ret = 0; uint32_t identification = 0; // unsigned char writeBuf = {0}; // unsigned char readBuf = {0}; // ret = ql_spi_init(QL_SPI_PORT0, QL_SPI_MODE3, QL_SPI_CLK_812_5KHZ); // sample_gd25q20_catstudio_printf("ql_spi_init ret %d", ret); drv_gd25q20_init(); identification = drv_gd25q20_read_identification(); sample_gd25q20_uart_printf("identification is 0x%X, Device id is 0x%X, Manufacturer Device ID is 0x%X", drv_gd25q20_read_identification(), drv_gd25q20_read_device_id(), drv_gd25q20_read_manufacturer_id()); // while(1) // { // drv_gd25q20_gpio_set(DRV_GD25Q20_SPI_CS, 0); // sample_gd25q20_uart_printf("low"); // OSATaskSleep(5*200); // drv_gd25q20_gpio_set(DRV_GD25Q20_SPI_CS, 1); // sample_gd25q20_uart_printf("high"); // OSATaskSleep(5*200); // } if(identification != RDID) { /* 读取错误处理 */ sample_gd25q20_uart_printf("SPI read-write Error, please check the connection between MCU and SPI Flash\n"); } else { //读取成功处理 char tx_buff1 = "abcdefghigklmnopqrstuvwxyz0123456789"; char rx_buff1 = {0}; char tx_buff2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ9876543210"; char rx_buff2 = {0}; int i = 0; //测试跨sector写,并且读出数据,写两次,第二次保留第一次部分数据,证明数据擦除,写入正常 drv_gd25q20_write((UINT8*)tx_buff1, 8181, strlen(tx_buff1));//从8181地址开始写数据,需要写第二和第三个扇区 drv_gd25q20_read((UINT8*)rx_buff1, 8181, strlen(tx_buff1)); sample_gd25q20_uart_printf("read flash:%s", rx_buff1); if(!strncmp(tx_buff1, rx_buff1, strlen(tx_buff1))) { sample_gd25q20_uart_printf("SPI read-write succeed 1"); } //验证驱动擦除扇区时,可以保留之前有效内容 drv_gd25q20_write((UINT8*)tx_buff2, 8186, strlen(tx_buff2));//从8186地址开始写数据,需要写第二和第三个扇区,同时不能擦掉8181 - 8186的5字节数据 drv_gd25q20_read((UINT8*)rx_buff2, 8181, strlen(tx_buff2) + 5); sample_gd25q20_uart_printf("read flash:%s", rx_buff2); if(!strncmp(rx_buff2, "abcdeABCDEFGHIJKLMNOPQRSTUVWXYZ9876543210", strlen("abcdeABCDEFGHIJKLMNOPQRSTUVWXYZ9876543210"))) { sample_gd25q20_uart_printf("SPI read-write succeed 2"); } } while (1) { // memset(writeBuf, 0x00, sizeof(writeBuf)); // memset(readBuf, 0x00, sizeof(readBuf)); // writeBuf = 0x9F; // ret = ql_spi_write_read(QL_SPI_PORT0, readBuf, writeBuf, 1); // sample_gd25q20_catstudio_printf("ql_spi_write_read ret %d, readBuf %02X,%02X,%02X\n", ret, readBuf , readBuf , readBuf ); // ret = ql_spi_write(QL_SPI_PORT0, writeBuf, 1); // sample_gd25q20_catstudio_printf("ql_spi_write_read ret %d, readBuf %02X\n", ret, writeBuf ); // ret = ql_spi_read(QL_SPI_PORT0, readBuf, 3); // sample_gd25q20_catstudio_printf("ql_spi_write_read ret %d, readBuf %02X,%02X,%02X\n", ret, readBuf , readBuf , readBuf ); OSATaskSleep(5 * 200); } } 4.6 固件 点击下载 Lora Demo固件 注:本文部分内容来源于网络,如有侵权,请及时联系我们。 本文章源自奇迹物联开源的物联网应用知识库Cellular IoT Wiki,更多技术干货欢迎关注收藏Wiki: Cellular IoT Wiki 知识库(https://rckrv97mzx.feishu.cn/wiki/wikcnBvAC9WOkEYG5CLqGwm6PHf)
  • 热度 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
    251 次阅读|
    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 卡,再次下载程序,   可以看到直接显示更新后的数据。
相关资源
  • 所需E币: 0
    时间: 2023-12-12 15:27
    大小: 3.18KB
    上传者: 开心就很好了
    今天给大家讲解关于Qt的内容,我会在文章里面带着大家从0到1为你系统构建Qt知识体系,然后全流程实战开发项目“云对象存储浏览器”,让大家少走弯路,更快速的掌握Qt技术。那么我们先来认识一下,什么是QT,他的具体作用是什么,应用在哪些方面?Qt是一个1991年由QtCompany开发的跨平台C++图形用户界面应用程序开发框架。它既可以开发GUI程序,也可用于开发非GUI程序,比如控制台工具和服务器。QT之所以能够在全世界范围内得到广大软件开发者的青睐和使用,一个很大的原因是QT入门确实是非常容易。很少的代码就能折腾出一个比较复杂的软件界面。QT已经支持传统模式下的软件界面开发技术体系以及新模式下的软件界面开发技术体系。传统模式比如QTWidgets,这种模式下的竞争对手比如MFC早就“躺平”了表示不再升级更新了。接下来,我们开始代码实战,编写mainwindows.cppMainWindow::MainWindow(QWidget*parent)  :QMainWindow(parent)  ,ui(newUi::MainWindow){  ui->setupUi(this);  //一般在qt的构造函数中进行初始化操作  //显示当前窗口的时候,显示另外一个窗口TestWidget#if1  //创建窗口对象,没有给W对象指定父对象  //要显示这个窗口必须要进行show()操作  TestWidget*w=newTestWidget;  w->show();#else  //创建窗口对象,给W对象指定父对象  //explicitTestWidget(QWidget*parent=nullptr);  //如果创建一个窗口对象的时候给其指定了父对象,这个窗口就不是一个独立窗口  //这样的话当前父窗口显示的时候,子窗口就一并被显示出来了  //这时候子窗口是没有边框的  TestWidget*w=newTestWidget(this);#endif  //创建对话框窗口  Dialog*dlg=newDialog();  //非模态  dlg->show();}#include"mainwindow.h"#include"ui_mainwindow.h"#include"testwidget.h"MainWindow::MainWindow(QWidget*parent)  :QMainWindow(parent)  ,ui(newUi::MainWindow){  ui->setupUi(this);     //一般在qt的构造函数中进行初始化操作  //显示当前窗口的时候,显示另外一个窗口TestWidget  //创建窗口对象,没有给W对象指定父对象  TestWidget*w=newTestWidget;  w->show();}MainWindow::~MainWindow(){  deleteui;}获取类的属性constQMetaObject*metaobject=object->metaObject();intcount=metaobject->propertyCount();for(inti=0;i<count;++i){  QMetaPropertymetaproperty=metaobject->property(i);  constchar*name=metaproperty.name();  QVariantvalue=object->property(name);  qDebug()<<name<<value;}在common中引入的坐标依赖<dependency>  <groupId>org.apache.commons</groupId>  <artifactId>commons-lang3</artifactId></dependency><dependency>  <groupId>com.fasterxml.jackson.core</groupId>  <artifactId>jackson-core</artifactId></dependency><dependency>  <groupId>com.fasterxml.jackson.core</groupId>  <artifactId>jackson-annotations</artifactId></dependency><dependency>  <groupId>com.fasterxml.jackson.core</groupId>  <artifactId>jackson-databind</artifactId></dependency><dependency>  <groupId>com.fasterxml.jackson.datatype</groupId>  <artifactId>jackson-datatype-jsr310</artifactId></dependency>迭代查询代码,一级缓存与二级缓存结合:@GetMapping("query")publicObjectquery(Stringid){  StringarticleKey="article:"+id;  StringarticleKeyRedis="REDIS_ARTICLE:"+id;  Articlearticle=cache.get(articleKey,s->{    System.out.println("文章id为"+id+"的没有查询到,则从Redis中查询后返回...");    ArticlearticleReal=null;    StringarticleJsonStr=redis.get(articleKeyRedis);    //判断从redis中查询到的文章数据是否为空    if(StringUtils.isBlank(articleJsonStr)){      System.out.println("Redis中不存在该文章,将从数据库中查询...");      //如果为空,则进入本条件,则从数据库中查询数据      articleReal=articleService.queryArticleDetail(id);      //手动把文章数据设置到redis中,后续再次查询则有值      StringarticleJson=JsonUtils.objectToJson(articleReal);      redis.set(articleKeyRedis,articleJson);    }else{      System.out.println("Redis中存在该文章,将直接返回...");      //如果不为空,则直接转换json类型article再返回即可      articleReal=JsonUtils.jsonToPojo(articleJsonStr,Article.class);    }    returnarticleReal;  });  returnarticle;}@ResourceprivateIArticleTypeServicearticleTypeService;@ResourceprivateCache<String,List<ArticleType>>articleTypeCache;@ResourceprivateRedisOperatorredis;@Overridepublicvoidrun(String...args)throwsException{  System.out.println("缓存预热。。。");  //1.查询所有分类数据  List<ArticleType>types=articleTypeService.list();  System.out.println(types);  StringarticleTypeKey="articleTypeList";  //2.设置分类数据到本地缓存  articleTypeCache.put(articleTypeKey,types);  //3.设置分类数据到redis  redis.set(articleTypeKey,JsonUtils.objectToJson(types));}修改hostname,可以区分每个tab是哪台虚拟机--获得http的协议版本号--ngx.say("http协议版本:"..ngx.req.http_version());--获得http的请求方法--ngx.say("http的请求method:"..ngx.req.get_method());--http的请求头内容--ngx.say("http的原始请求头内容:"..ngx.req.raw_header());--获得http的请求头信息localmyHeader=ngx.req.get_headers();--ngx.say("token:"..myHeader.token);--ngx.say("uid:"..myHeader.uid);--获得请求中的参数localargs=ngx.req.get_uri_args();--ngx.say("age:"..args["age"]);--ngx.say("birthday:"..args["birthday"]);--获得请求体body中的数据ngx.req.read_body();localbody_data=ngx.req.get_post_args();forkey,valueinpairs(body_data)do  ngx.say(key,value);end目标:先查询缓存,如果缓存有数据,则在网关中判断返回即可。如果网关中缓存不存在,则把请求向后端服务转发。--导入工具类引用localhttp=require('http');localredis=require("redis_utils")localget=http.get;localargs=ngx.req.get_uri_args();localarticleId=args["id"];localarticleKey="REDIS_ARTICLE:"..articleId;localarticleDetail=redis.get(articleKey);ifarticleDetail~=ngx.nullthen  ngx.say("byopenresty:"..articleDetail);  return;end--发送请求localresult=get("/article/query",args);--返回响应ngx.say("byspringboot:"..result);--ngx.say("测试负载均衡-106");
  • 所需E币: 1
    时间: 2023-7-10 15:44
    大小: 380.51KB
    上传者: 张红川
    19存储期.pdf
  • 所需E币: 1
    时间: 2023-4-24 10:23
    大小: 279.57KB
    上传者: 张红川
    基于单片机的通用示波器存储功能扩展设计.pdf
  • 所需E币: 0
    时间: 2023-3-30 16:14
    大小: 516.49KB
    多传感器动态参数实时采集与存储方法研究
  • 所需E币: 1
    时间: 2023-3-22 10:08
    大小: 2.13MB
    上传者: 张红川
    基于ide硬盘的数字图像存储技术研究.pdf
  • 所需E币: 5
    时间: 2023-2-12 22:17
    大小: 428.83KB
    上传者: ZHUANG
    嵌入式DSP系统中SDF模型的层次化存储优化方法
  • 所需E币: 0
    时间: 2023-2-12 22:14
    大小: 291.14KB
    上传者: ZHUANG
    一种面向实时图像处理应用的基于非共享存储的多DSP系统设计
  • 所需E币: 5
    时间: 2023-2-12 21:31
    大小: 11.58MB
    上传者: ZHUANG
    基于DSPARM的音视频同步压缩存储实时传输系统设计
  • 所需E币: 4
    时间: 2023-2-11 14:42
    大小: 1.72MB
    上传者: ZHUANG
    基于网络存储系统中虚拟存储分级存储技术的研究.
  • 所需E币: 5
    时间: 2023-2-7 22:27
    大小: 193.98KB
    上传者: ZHUANG
    数据在计算机内存中的存储形式及实验验证
  • 所需E币: 0
    时间: 2022-12-13 14:14
    大小: 1.22MB
    上传者: kaokaohe
    半导体存储的最强入门科普
  • 所需E币: 1
    时间: 2022-12-13 14:15
    大小: 793.32KB
    上传者: kaokaohe
    数据中心存储发展趋势分析
  • 所需E币: 3
    时间: 2022-10-15 11:51
    大小: 120.34KB
    上传者: czd886
    数字视频监控系统中多硬盘自动转盘存储的实现
  • 所需E币: 0
    时间: 2022-10-13 16:55
    大小: 1.86MB
    上传者: czd886
    网络视频监控(NVR)存储技术的分析及其应用
  • 所需E币: 5
    时间: 2022-10-11 16:43
    大小: 137.48KB
    上传者: czd886
    大数据时代的高清视频监控存储
  • 所需E币: 5
    时间: 2022-10-11 12:44
    大小: 1.64MB
    上传者: czd886
    浅论视频监控系统存储技术的发展.
  • 所需E币: 4
    时间: 2022-10-8 17:41
    大小: 144.73KB
    上传者: ZHUANG
    视频监控系统中的图像采集和视频有效存储
  • 所需E币: 4
    时间: 2022-10-8 16:21
    大小: 367.49KB
    上传者: ZHUANG
    一种网络视频监控系统的专用存储方案
  • 所需E币: 3
    时间: 2022-10-7 22:37
    大小: 428.83KB
    上传者: ZHUANG
    基于SAN的视频监控存储文件系统设计
  • 所需E币: 0
    时间: 2022-10-7 22:37
    大小: 122.9KB
    上传者: ZHUANG
    关于高校校园视频监控存储方案的探讨