基于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读写接口 

图:逻辑分析仪


 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 = 1set_csn();usleep(10);//CS = 0set_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

小结:读到的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     
----------------------------------------                                

       
小结:全片擦除用了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;
}

代码:页读取

 

 页写入

         写入"写页数据寄存器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;
}

 代码:页写入 


验证
  • 执行“擦除”操作
  • 执行“页写入”操作
  • 执行“页读取”操作
  • 打印读取数据,比较写入数据和读取数据内容,使用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_address01234567........8388604838860583886068388607
vol_address01........262143
data01........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(冗余)+4448

表:WQ25Q80地址

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


关于CS使用

     使用芯片时候需要把CS引脚拉低,在命令写完成后需要把CS引脚拉高。手册里都会有"The instruction is completed by driving /CS high"的说明,这也成为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;}

 


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/722905.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

word转PDF的方法 简介快速

在现代办公环境中&#xff0c;文档格式转换已成为一项常见且重要的任务。其中&#xff0c;将Word文档转换为PDF格式的需求尤为突出&#xff0c;将Word文档转换为PDF格式具有多方面的优势和应用场景。无论是为了提高文档的可读性和稳定性、保障文档的安全性和保护机制、还是为了…

IDEA运行大项目启动卡顿问题

我打开了很多项目&#xff0c;然后又启动了一个大型项目时&#xff0c;启动到一半&#xff0c;弹出一个窗口&#xff0c;告诉我idea内存不够&#xff0c;怎么解决这个问题&#xff1f; 1、先把多余的项目关掉&#xff0c;再启动这个大项目&#xff0c; 2、如果还是不行就去修改…

一文帮助快速入门Django

文章目录 创建django项目应用app配置pycharm虚拟环境打包依赖 路由传统路由include路由分发namenamespace 视图中间件orm关系对象映射操作表数据库配置model常见字段及参数orm基本操作 cookie和sessiondemo 创建django项目 指定版本安装django&#xff1a;pip install django3.…

Unity使用UnityWebRequest读取音频长度不对的解决方法

在开发的过程中碰到这样一个问题&#xff0c;有的音频文件通过UnityWebRequest读取出来后&#xff0c;AudioClip的Length会不对&#xff0c;比如本身有7秒&#xff0c;读出来只有3秒。代码如下&#xff1a; IEnumerator TestEnumerator() {UnityWebRequest www UnityWebReque…

gitlab的安装

1、下载rpm 安装包 (1)直接命令下载 wget https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/yum/el7/gitlab-ce-11.6.10-ce.0.el7.x86_64.rpm&#xff08;2&#xff09;直接去服务器上下载包 Index of /gitlab-ce/yum/el7/ | 清华大学开源软件镜像站 | Tsinghua Open Source…

【图论】图的遍历 - 构建领接表(无向图)

文章目录 例题&#xff1a;受限条件下可到达节点的数目题目描述代码与注释模板抽象 例题&#xff1a;受限条件下可到达节点的数目 题目链接&#xff1a;2368. 受限条件下可到达节点的数目 题目描述 代码与注释 func reachableNodes(n int, edges [][]int, restricted []int)…

网络编程:select、poll

.1、select完成TCP并发服务器 程序代码&#xff1a; #include <myhead.h> #define SER_IP "192.168.125.234" //服务端IP #define SER_PORT 8888 //服务端端口号int main(int argc, const char *argv[]) {//1.创建用于连接的套接字int sfds…

HTML实体字符列表,必看

HTML、CSS、JS三大部分都起什么作用&#xff1f; HTML内容层&#xff0c;它的作用是表示一个HTML标签在页面里是个什么角色&#xff1b;CSS样式层&#xff0c;它的作用是表示一块内容以什么样的样式&#xff08;字体、大小、颜色、宽高等&#xff09;显示&#xff1b;JS行为层…

基于QGIS的研究区域遥感影像裁切下载方法-以岳麓区为例

目录 前言 一、数据说明 1、遥感影像 2、矢量范围 二、按矢量范围导出 1、第一步、导出影像 2、第二步、设置输出格式 3、设置裁切范围 4、设置分辨率 三、按矢量范围掩膜 1、第一步、打开裁剪工具 2、第二步、参数设置 ​编辑 3、执行掩膜 四、webgis支持 1、生成运行…

计算机基础(面试重点)

文章目录 1. 计算机网络1.1 TCP/IP四层模型与OSI七层模型1.2 HTTP协议原理1.2.1 消息结构1.2.2 HTTP常见请求方法1.2.3 常见状态码 1.3 HTTPS协议原理1.4 TCP与UDP1.4.1 TCP三次握手1.4.2 TCP四次挥手1.4.3 常见热点问题1.4.4 浏览器输入URL并回车的过程以及相关协议&#xff1…

mac上更改vscode快捷键

以移动当前行代码为例 mac上的vscode&#xff0c;默认移动当前行代码的快捷键是⌥↑即option↑按键 现在我想改成command↑ 步骤如下 1.打开vscode-code-首选项-键盘快捷键 2.打开快捷键列表 3.输入move line&#xff0c;找到要改动的这个快捷键 当前行-右键-更改键绑定&…

pyg-创建消息传递网络

创建消息传递网络 — pytorch_geometric 文档 (pytorch-geometric.readthedocs.io) https://arxiv.org/abs/1801.07829 import torch from torch.nn import Sequential as Seq, Linear, ReLU from torch_geometric.nn import MessagePassing class EdgeConv(MessagePassing): …

微信小程序云开发教程——墨刀原型工具入门(页面交互+交互案例教程)

引言 作为一个小白&#xff0c;小北要怎么在短时间内快速学会微信小程序原型设计&#xff1f; “时间紧&#xff0c;任务重”&#xff0c;这意味着学习时必须把握微信小程序原型设计中的重点、难点&#xff0c;而非面面俱到。 要在短时间内理解、掌握一个工具的使用&#xf…

web前端之uniApp实现选择时间功能

MENU 1、孙子组件1.1、html部分1.2、JavaScript部分1.3、css部分 2、子组件2.1、html部分2.2、JavaScript部分2.3、css部分 3、父组件3.1、html部分3.2、JavaScript部分 4、效果图 1、孙子组件 1.1、html部分 <template><view><checkbox-group change"ch…

蓝桥杯-大小写转换

转换方法 toLowerCase() String类的toLowerCase()方法可以将字符串中的所有字符全部转换成小写&#xff0c;而非字母的字符不受影响&#xff0c;语法格式如下&#xff1a; 字符串名.toLowerCase() //将字符串中的字母全部转成小写&#xff0c;非字母不受影响。 package chap…

python 绘制三维图

参考&#xff1b;Create 3D histogram of 2D data — Matplotlib 3.8.3 documentation

Elasticsearch:使用 Streamlit、语义搜索和命名实体提取开发 Elastic Search 应用程序

作者&#xff1a;Camille Corti-Georgiou 介绍 一切都是一个搜索问题。 我在 Elastic 工作的第一周就听到有人说过这句话&#xff0c;从那时起&#xff0c;这句话就永久地印在了我的脑海中。 这篇博客的目的并不是我出色的同事对我所做的相关陈述进行分析&#xff0c;但我首先…

【Numpy】给数组增加一个维度

【Numpy】给数组增加一个维度 &#x1f308; 个人主页&#xff1a;高斯小哥 &#x1f525; 高质量专栏&#xff1a;Matplotlib之旅&#xff1a;零基础精通数据可视化、Python基础【高质量合集】、PyTorch零基础入门教程&#x1f448; 希望得到您的订阅和支持~ &#x1f4a1; 创…

html实体字符,看完这篇彻底明白了

二.技术基础知识 基础知识一直都是重点考察的内容&#xff0c;包含有HTML&#xff08;5&#xff09;、CSS&#xff08;3&#xff09;、JavaScript到 戳这里领取完整开源项目&#xff1a;【一线大厂前端面试题解析核心总结学习笔记Web真实项目实战最新讲解视频】 Vue&#xff0…

MATLAB读取.nc(数据集)文件

MATLAB读取.nc(数据集)文件 以中国1km逐月潜在蒸散发数据集&#xff08;1901-2022&#xff09;为例 首先用FileZilla下载特定年份的数据集 用matlab进行处理&#xff0c;代码如下&#xff1a; clear;clc;ncdisp("pet_2022.nc") %读数据集的具体信息和变量eva ncr…