基于ZYNQ PS-SPI的Flash驱动开发

这篇具有很好参考价值的文章主要介绍了基于ZYNQ PS-SPI的Flash驱动开发。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

       


        本文使用PS-SPI实现Flash读写,PS-SPI的基础资料参考Xilinx UG1085的文档说明,其基础使用方法是,配置SPI模式,控制TXFIFO/RXFIFO,ZYNQ的IP自动完成发送TXFIFO数据,接收数据到RXFIFO,FIFO深度为128Byte。本文介绍了使用PS-SPI的Flash开发。


软硬件介绍:

  • 硬件平台:Xilinx ZYNQ
  • Flash芯片:华邦W25Q80
  • 软件平台:Vitis Standalone

芯片信息/配置:

  • 容量:8Mbit
  • SPI时钟:25MHZ
  • IO电平:3.3V
  • SPI FIFO深度:128Byte
  • SPI 标准模式

 方案:

        在ZYNQ平台上使用PS的SPI进行读写Flash芯片,约束EMIO芯片管脚,在Vitis上读写SPI总线。


 测试项目:

  • 擦除、读、写功能
  • 芯片容量
  • 擦除、读、写速度

硬件设计 

  • 使能PS端的SPI(SPI0)模块,FIFO位宽8Bit
  • 约束CS/DI/DO/CLK管脚
  • 生成XSA,提供给软件

软件设计

  • 使用PS SPI功能读写寄存器
  • 封装读ID、写使能、读取状态、擦除、读、写接口(C语言)。

 调试和测试流程

  1. 读取芯片ID,验证SPI通路
  2. 验证全片擦除、页写入、读功能
  3. 页写入、读功能, 验证数据读写正确性
  4. 容量测试
  5. 测试读写时间 

 调试手段

  • 发送数据:写PS-SPI对应的写缓存地址,写入数据到“写FIFO缓冲区”,等待发送完成
  • 读取数据:读PS-SPI对应的读缓存地址,读取“读FIFO缓冲区”数据,等待读取完成
  • 信号分析:测试过程中使用逻辑分析仪抓取CS/DI/DO/CLK信号。
#define SPIPS_RECV_BYTE(BaseAddress) \
		Xil_In8((BaseAddress) + XSPIPS_RXD_OFFSET)

#define SPIPS_SEND_BYTE(BaseAddress, Data) \
		Xil_Out8((BaseAddress) + XSPIPS_TXD_OFFSET, (Data))

void spi_read(int byte_count)
{
        int count;
        u32 status_reg;

        status_reg = XSpiPs_ReadReg(g_spi0_handle.Config.BaseAddress,
                                        XSPIPS_SR_OFFSET);

        /*
         * Polling the Rx Buffer for Data
         */
        do{
                status_reg = XSpiPs_ReadReg(g_spi0_handle.Config.BaseAddress,
                                        XSPIPS_SR_OFFSET);
        }while(!(status_reg & XSPIPS_IXR_RXNEMPTY_MASK));


        /*
         * Reading the Rx Buffer
         */

        for(count = 0; count < byte_count; count++){
                g_read_buffer[count] = SPIPS_RECV_BYTE(
                                g_spi0_handle.Config.BaseAddress);
        }

}

void spi_write(u8 *send_buffer, int byte_count)
{
        u32 status_reg;
        int trans_count = 0;

        status_reg = XSpiPs_ReadReg(g_spi0_handle.Config.BaseAddress,
                                XSPIPS_SR_OFFSET);

        while ((byte_count > 0) &&
                (trans_count < XSPIPS_FIFO_DEPTH)) {
                SPIPS_SEND_BYTE(g_spi0_handle.Config.BaseAddress,
                                *send_buffer);
                send_buffer++;
                ++trans_count;
                byte_count--;
        }

        /*
         * Wait for the transfer to finish by polling Tx fifo status.
         */
        do {
                status_reg = XSpiPs_ReadReg(
                                g_spi0_handle.Config.BaseAddress,
                                        XSPIPS_SR_OFFSET);
        } while ((status_reg & XSPIPS_IXR_TXOW_MASK) == 0);

}

代码:SPI读写接口 

基于ZYNQ PS-SPI的Flash驱动开发,嵌入式硬件,spi,zynq,psspi,flash

图:逻辑分析仪


 1.读取芯片ID,验证SPI通路

  • 写入"唤醒寄存器0xAB",后面再发送3个字节(数据0),共发送4个字节;再发送一个字节(为了提供时钟),读取FIFO数据5字节。
char release_powerdown_and_read_id()
{
	memset(g_write_buffer, 0x00, sizeof(g_write_buffer));
    g_write_buffer[0] = 0xAB;
    //CS = 1
    set_csn();
    usleep(10);
    //CS = 0
    set_cs0();
    spi_write(g_write_buffer,5);
    set_csn();
    spi_read(5);
    return g_read_buffer[4];
}

代码:读ID 

注意的是,SPI只要有时钟,“读FIFO缓冲区”就会写入数据(MISO)。主机发送5个字节,接着读取5个字节,丢弃前4个数据,第5个就是读到的ID。  
0xFF 0xFF 0xFF 0xFF 0x13

基于ZYNQ PS-SPI的Flash驱动开发,嵌入式硬件,spi,zynq,psspi,flash

小结:读到的ID是0x13,和datasheet一致。 


2.验证全片擦除

执行操作前,先配置写使能能为1,读取状态寄存器
全片擦除,写入"擦除全片寄存器0xC7"
等待BUSY信号为0时结束,判断信号间隔为1ms(参考官方驱动)
注意的是写完后,写时能标记为会置0

int wait_busy(int max_count)
{
	int r,busy,busy_cnt = 0x00;
	r = 0;
	do {
		busy = is_busy();
		if(busy == 1)
			usleep(1000);
		busy_cnt += 1;
		if (max_count > 0)
		{
			if (busy == 0)
			{
				r = 0;
				break;
			}
			if (busy_cnt > max_count)
			{
				r = -1;
				break;
			}
		}
	} while(busy == 1);
	return r;
}

void erase_entire()
{
    set_write_enable();
	g_write_buffer[0] = 0xC7;
	set_csn();
	usleep(10);
	set_cs0();
	spi_write(g_write_buffer,1);
	set_csn();
	spi_read(1);
	wait_busy(1000);
	return ;
}

代码:擦除全片


 ----------------------------------------                                        
...erase entire chip...                                                         
  <erase> entire consume time:831 ms     
----------------------------------------                                

基于ZYNQ PS-SPI的Flash驱动开发,嵌入式硬件,spi,zynq,psspi,flash       
小结:全片擦除用了0.8S,比手册提供的2S典型值小。 


 3.页写入、读功能, 验证数据读写正确性 

页读取

        写入"读页数据寄存器0x03",后面跟一个24位地址,按照手册要求先发送高位地址,即依次发送addr[23:16],addr[15:8],addr[7:0]。主机还要继续写入提供时钟,写入一个页的数据(0)。读取“读FIFO缓冲区”数据,读取一个页的数据量,得到读取内容。

void page_read(int address, unsigned char * recv, int size)
{
	int i;
	set_csn();
	usleep(10);
	set_cs0();
	g_write_buffer[0] = 0x03;
	g_write_buffer[1] = address >> 16;
	g_write_buffer[2] = address >> 8;
	g_write_buffer[3] = address >> 0;
	spi_write(g_write_buffer,4);
	spi_read(4);
	g_write_buffer[0] = 0;

	memset(g_write_buffer, 0x00, sizeof(g_write_buffer));
	if (size > 128)
	{
		spi_write(g_write_buffer,128);
		spi_read(128);
		memcpy(recv, g_read_buffer, 128);
		spi_write(g_write_buffer + 128,size - 128);
		spi_read(size - 128);
		memcpy(recv + 128, g_read_buffer, size - 128);
	}
	else
	{
		spi_write(g_write_buffer, size);
		spi_read(size);
		memcpy(recv, g_read_buffer, size);
	}

	set_csn();
	return;
}

代码:页读取

 基于ZYNQ PS-SPI的Flash驱动开发,嵌入式硬件,spi,zynq,psspi,flash

 页写入

         写入"写页数据寄存器0x02",后面跟一个24位地址,按照手册要求先发送高位地址,即依次发送addr[23:16],addr[15:8],addr[7:0]。主机继续写入一个页的数据,页数据Pattern是一个递增数据。

//Pattern 
u8 data[] = {0x00,0x01,0x02....0x0FF};

读取“读FIFO缓冲区”数据,排空无用“读缓冲数据”。

void page_write(int address, unsigned char * data, int size)
{
	//int i;
	set_write_enable();
	set_csn();
	usleep(10);
	set_cs0();
	g_write_buffer[0] = 0x02;
	g_write_buffer[1] = address >> 16;
	g_write_buffer[2] = address >> 8;
	g_write_buffer[3] = address >> 0;
	spi_write(g_write_buffer,4);
	spi_read(4);

	if (size > 128)
	{
		spi_write(data,128);
		spi_read(128);

		spi_write(data + 128,size - 128);
		spi_read(size - 128);
	}
	else
	{
		spi_write(data,size);
		spi_read(size);
	}

	wait_busy(1000);
	set_csn();
	return;
}

 代码:页写入 

基于ZYNQ PS-SPI的Flash驱动开发,嵌入式硬件,spi,zynq,psspi,flash


验证
  • 执行“擦除”操作
  • 执行“页写入”操作
  • 执行“页读取”操作
  • 打印读取数据,比较写入数据和读取数据内容,使用memcmp进行比较。
void page_rw_test()
{
    char send_data[PAGE_SIZE] = {...};//自行填充
    char recv_data[PAGE_SIZE];
    erase_entire();
    page_write(0x00, send_data, PAGE_SIZE);
    page_read(0x00,  recv_data, PAGE_SIZE);
    r = memcmp(send_data, recv_data, PAGE_SIZE);
    return r;
}

代码:页读写验证 

小结:读写功能正常,数据比较一致。


4.容量测试 

方法:每4个字节当作一个单元,每个单元数据递增1。写入再读出作比较。
芯片共有16x16x16个4K个页,往4K个页写入递增数据(0,262143)。打印部分页内容,对比所有写入数据和读取数据,使用memcmp进行比较。

表:地址和容量地址

byte_address 0 1 2 3 4 5 6 7 .. .. .. .. 8388604 8388605 8388606 8388607
vol_address 0 1 ........ 262143
data 0 1 ........ 262143


    
小结:打印内容符合递增预期,数据比较一致。 


 5.测试读写时间

容量测试加入时间打印,分别记录擦除时间、写入时间和读取时间

时间计数方式,采用读取CPU计数器计数,转换成时间。

write_data/read_data是page_write/page_read的封装,可以写入/读取任意数据。

int entire_volume_test(const int value_start, int step)
{
	int blkn, r, value, i,last_value;
	int escape;
	XTime start,end;
	blkn = BLOCK_NUMBER;
	printf("[ entire volume test ]\r\n");
	printf("[information]:\r\n");
	printf("----------------------------------------\r\n");
	printf("      Capicity( Bit ):%d\r\n", blkn * BLOCK_SIZE * 8);
	printf("      Capicity(1Byte):%d\r\n", blkn * BLOCK_SIZE);
	printf("      Capicity(4Byte):%d   [*]\r\n", blkn * BLOCK_SIZE/4);
	printf("      SPI CLK        :%d MHZ\r\n", 25);
	printf("----------------------------------------\r\n");
	printf("[test parttern] value start:%d,step:%d\n", value_start, step);
	printf("[test parttern] value range:(%d , %d)\n", value_start, step * (blkn * BLOCK_SIZE/4 - 1));
	printf("----------------------------------------\r\n");
	printf("...erase entire chip...\r\n");
	start = get_sys_count();
	erase_entire();
	end = get_sys_count();
	escape =  get_useconds(start, end);
	printf("<erase> entire consume time:%02d ms \r\n", escape/1000);
	usleep(200000);
	last_value = step * (blkn * BLOCK_SIZE/4 - 1);

	printf("    fill data\r\n");
	value = value_start;
	for (i = 0; i < blkn * BLOCK_SIZE/4; i++)
	{
		g_data[i * 4 + 3 ] = (value >> 24) & 0xFF;
		g_data[i * 4 + 2 ] = (value >> 16) & 0xFF;
		g_data[i * 4 + 1 ] = (value >>  8) & 0xFF;
		g_data[i * 4 + 0 ] = (value >>  0) & 0xFF;
		value += step;
	}
	printf("    write data sequence\r\n");
	start = get_sys_count();
	write_data(0, g_data,        blkn * BLOCK_SIZE);
	end = get_sys_count();
	escape =  get_useconds(start, end);
	printf("<write> data consume time:%02d ms \r\n", escape/1000);

	printf("    reading.....\r\n");
	start = get_sys_count();
	read_data (0, g_recv_buffer,blkn * BLOCK_SIZE);
	end = get_sys_count();
	escape =  get_useconds(start, end);
	printf("<read> consume time:%02d ms \r\n", escape/1000);

	printf("    dump last 2 page \r\n");
	printf("value will range:(%08d , %08d)\r\n", 1 + last_value - 2 * PAGE_SIZE/ 4, last_value - 1 * PAGE_SIZE/ 4);
	dec_print(g_recv_buffer + (blkn * BLOCK_SIZE - 2 * PAGE_SIZE) , PAGE_SIZE/4);

	printf("value will range:(%08d , %08d)\r\n", 1 + last_value - 1 * PAGE_SIZE/ 4, last_value - 0 * PAGE_SIZE/ 4);
	dec_print(g_recv_buffer + (blkn * BLOCK_SIZE - 1 * PAGE_SIZE) , PAGE_SIZE/4);

	printf("compare <write data> and <read data> values, compare size:%d Bytes\n", blkn * BLOCK_SIZE);
	printf("----------------------------------------\r\n");
	if (memcmp(g_data, g_recv_buffer, blkn * BLOCK_SIZE) == 0)
	{
		printf("  [*] <pass> volume test !!!\r\n");
		printf("----------------------------------------\r\n");
		return 0;
	}
	printf("[*] !!<fail> volume test !!!\r\n");
	printf("----------------------------------------\r\n");
	return -1;
}

代码:容量测试


    <write> page data consume time:1346 us 
    <read> page data consume time:275 us 

    ...erase entire chip...                                                         
    <erase> entire consume time:831 ms                                              
    fill data                                                                   
    write data sequence                                                         
    <write> data consume time:5509 ms                                               
    reading.....                                                                
    <read> consume time:1127 ms  

    ----------------------------------------
          [*] <pass> volume test !!!
    ----------------------------------------
记录测试结果到下表

测试项目 测试值(ms)          参考值[典型值,最大值](ms)
页写入时间 1.34 [0.8, 3]
页读取时间 0.28 /
全片擦除时间 831 [2000, 6000]
全片写入时间 5509 [3276,24576]
全片读取时间 1127 /

表:测试结果

总结:擦除速度比datasheet参考值快,其他均正常。  


其他相关


Flash读写特性

        Flash的特性是,写数据只能将1写为0,0不能写为1。擦除数据是将所有数据都写为1。因此如果想在已经数据的flash上写入新的数据,则必须先擦除

Flash相关知识学习记录(以W25Q128为例)


芯片地址相关

        以WQ25Q80为例,一个地址24位,由块地址、扇地址、页地址、页内偏移组成。

#define ADDRESS(block, sector, page, offset) ((block) << 16 | (sector) << 12 | (page) << 8 | (offset))

代码:使用C语言表示芯片地址 

地址项 块地址 扇区地址 页地址 页内偏移
地址大小(bit) 4(冗余)+4 4 4 8

表:WQ25Q80地址

比如一个地址0x04E3AA,表示块地址0x04,扇区地址0xE,页地址0x03,页内偏移0xAA。


关于CS使用

     使用芯片时候需要把CS引脚拉低,在命令写完成后需要把CS引脚拉高。手册里都会有"The instruction is completed by driving /CS high"的说明,这也成为Flash芯片操作的通用操作。

基于ZYNQ PS-SPI的Flash驱动开发,嵌入式硬件,spi,zynq,psspi,flash

关于PS-SPI软件配置

    可以配置CS控制模式、时钟频率,时钟频率通过SPI主频分频得到,分频系数可配置。

int spi_init() {
        unsigned int config_value;
        int status;
        char spi_dev_id = SPI_DEVICE_ID;
        XSpiPs_Config *spi_config;


        /*
         * Initialize the SPI device.
         */

        spi_config = XSpiPs_LookupConfig(spi_dev_id);
        if (NULL == spi_config) {
                return XST_FAILURE;
        }


        status = XSpiPs_CfgInitialize(&g_spi_handle, spi_config, spi_config->BaseAddress);
        if (status != XST_SUCCESS) {
                return XST_FAILURE;
        }


        /*
         * Perform a self-test to check hardware build.
         */

        status = XSpiPs_SelfTest(&g_spi_handle);
        if (status != XST_SUCCESS) {
                return XST_FAILURE;
        }

        XSpiPs_ResetHw(spi_config->BaseAddress);

        printf("%s self test succ\r\n", __func__);

        status = XSpiPs_SetOptions(&g_spi_handle, XSPIPS_MASTER_OPTION | XSPIPS_FORCE_SSELECT_OPTION);
        //status = XSpiPs_SetOptions(&g_spi_handle, XSPIPS_MASTER_OPTION);
        if (status != XST_SUCCESS) {
                printf("%s XSpiPs_SetOptions fail\n", __func__);
                return XST_FAILURE;

        }
        /*
         * PS SPI CLK DOMAIN 200MHZ
         * */
        status = XSpiPs_SetClkPrescaler(&g_spi_handle, XSPIPS_CLK_PRESCALE_8);
        if (status != XST_SUCCESS) {
                printf("%s XSpiPs_SetClkPrescaler fail\n", __func__);
                return XST_FAILURE;
        }

        XSpiPs_Enable(&g_spi_handle);
        printf("spi <%d> config finish\r\n", spi_dev_id);

        //config_value =         
        XSpiPs_ReadReg(g_spi_handle.Config.BaseAddress,XSPIPS_CR_OFFSET);
        //printf("config_value :0x%08X\n", config_value);

        return XST_SUCCESS;

}

 关于发送间隔设置

我在测试多路SPI的时候,发现同型号芯片读写时间差别大,测试时钟SCLK,表现上间隔不一致。查阅寄存器表后发现,“发送间隔”配置不一致。

“发送间隔”修改成一致后,测试读写时间一致。

基于ZYNQ PS-SPI的Flash驱动开发,嵌入式硬件,spi,zynq,psspi,flash

图:SPI控制器寄存器列表文章来源地址https://www.toymoban.com/news/detail-842644.html


到了这里,关于基于ZYNQ PS-SPI的Flash驱动开发的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 全志F1C200S嵌入式驱动开发(spi-nor image制作)

    【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】         一般soc系统里面添加spi-nor flash芯片,特别是对linux soc来说,都是把它当成文件系统来使用的。spi-nor flash和spi-nand flash相比,虽然空间小了点,但是胜在稳定,这是很多工业场景所必须

    2024年02月16日
    浏览(40)
  • 嵌入式Linux驱动开发 04:基于设备树的驱动开发

    前面文章 《嵌入式Linux驱动开发 03:平台(platform)总线驱动模型》 引入了资源和驱动分离的概念,这篇文章将在前面基础上更进一步,引入设备树的概念。 在平台总线驱动模型中资源和驱动已经从逻辑上和代码组织上进行了分离,但每次调整资源还是会涉及到内核,所以现

    2024年02月16日
    浏览(65)
  • 【嵌入式Linux内核驱动】SPI子系统 | 硬件原理 | 应用编程 | 内核驱动 | 总体框架

    1.1 SPI通信协议 SPI(Serial Peripheral Interface)是由Motorola公司开发的一种通用数据总线 四根通信线:SCK(Serial Clock)、MOSI(Master Output Slave Input)、MISO(Master Input Slave Output)、SS(Slave Select) 同步,全双工 支持总线挂载多设备(一主多从) 1.2 硬件连接 多NSS独立片选方式 菊花

    2024年02月16日
    浏览(63)
  • 【正点原子FPGA连载】第二十三章PS通过VDMA驱动LCD显示实验 摘自【正点原子】DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南

    1)实验平台:正点原子MPSoC开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id=692450874670 3)全套实验源码+手册+视频下载地址: http://www.openedv.com/thread-340252-1-1.html AXI VDMA是Xilinx专门针对视频应用提供的一种高带宽的解决方案,旨在实现AXI4-Stream视频接口和AXI4接口之间的高

    2024年02月04日
    浏览(61)
  • 嵌入式Linux开发实操(十五):nand flash接口开发(2)

    通用NAND驱动程序支持几乎所有基于NAND的芯片,并将它们连接到Linux内核的内存技术设备(MTD)子系统。这个接口走的是nand的并口,可以在shell的/dev中看到设备,比如/mtd0、/mtd0ro…,mtdblock0、mtdblock1… sysfs在设备层次结构中提供了几个视角。设备必须挂在某条总线bus上才能与

    2024年02月10日
    浏览(33)
  • linux嵌入式开发-Zynq开发板配置usb_gadget模拟HID鼠标

    在PC上使用VMWare,在ubuntu下创建petalinux工程,编译内核,vmware、vivdado、petalinux的安装详见alinx官方教程course4-linux实验中的步骤 创建petalinux工程,在工程目录下打开终端,输入命令准备编译内核 然后编译内核: 进入Devicedrivers - USB Support,勾选USB Gadget Support(按Y) 进入USB Gadge

    2024年02月04日
    浏览(53)
  • 【全志V3s】SPI NAND Flash 驱动开发

    芯片:W25N01GVZEIG datasheet上的描述: SLC工艺 2KB*65536页 10万次擦写次数 焊好以后用CH341A读了一下 上面这张图描述了soc启动的四个顺序,分别是usb启动、sd卡启动、spi norflash启动、spi nandflash启动四种方式。前面的测试中一直走的是sd卡启动,但是从成本和稳定性上说,spi nandfla

    2024年02月07日
    浏览(46)
  • 嵌入式开发--STM32G4系列片上FLASH的读写

    这个玩意吧,说起来很简单,就是几行代码的事,但楞是折腾了我大半天时间才搞定。原因后面说,先看代码吧: 读操作很简单,以32位方式读取的时候是这样的: 需要注意的是,当以32位方式读取时,地址需要是4的整数倍,即32位。 8位或16位方式类似操作即可 需要注意的

    2024年01月18日
    浏览(66)
  • 嵌入式Linux裸机开发(七)UART串口、IIC、SPI通信

    大概学完这三种通信后,之后就先去学系统移植,其他的先暂时放下 串口全称叫做串行接口,通常也叫做 COM 接口。 UART:(Universal Asynchronous Receiver/Trasmitter)通用异步串行收发器。 USART:(Universal Synchronous/Asynchronous Receiver/Transmitter)通用同步/异步串行收发器,相比 UART多了

    2024年02月07日
    浏览(72)
  • stm32基于HAL库驱动外部SPI flash制作虚拟U盘

    📌参考文章: https://xiaozhuanlan.com/topic/6058234791 🎞实现效果演示: 🔖上图中的读到的 FLASH_ID 所指的是针对不同容量,所对应的ID。 🔖在电脑端,支持对虚拟出来的存储器进行读写操作。 ✨如果设计成一块PCB,可以制作成一个微小容量的移动U盘。 🌿基于STM32F103,HAL库生成

    2024年02月11日
    浏览(44)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包