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

软硬件介绍:

硬件平台:Xilinx ZYNQFlash芯片:华邦W25Q80软件平台:Vitis Standalone

芯片信息/配置:

容量:8MbitSPI时钟:25MHZIO电平:3.3VSPI FIFO深度:128ByteSPI 标准模式

 方案:

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

 测试项目:

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

硬件设计 

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

软件设计

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

 调试和测试流程

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

 调试手段

发送数据:写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 = 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

小结:读到的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...                                                             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........8388604838860583886068388607vol_address01........262143data01........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(" 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(" 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(" 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 and values, compare size:%d Bytes\n", blkn * BLOCK_SIZE);

printf("----------------------------------------\r\n");

if (memcmp(g_data, g_recv_buffer, blkn * BLOCK_SIZE) == 0)

{

printf(" [*] volume test !!!\r\n");

printf("----------------------------------------\r\n");

return 0;

}

printf("[*] !! volume test !!!\r\n");

printf("----------------------------------------\r\n");

return -1;

}

代码:容量测试

    page data consume time:1346 us      page data consume time:275 us 

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

    ----------------------------------------           [*] 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;

}

 关于发送间隔设置

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

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

图:SPI控制器寄存器列表

相关阅读

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: