手搓qspi接口,封装成AXI4‐ip,用zynq驱动(增加fifo) - minichao9901/TangNano-20k-Zynq-7020 GitHub Wiki

手搓qspi接口,封装成AXI4‐ip,用zynq驱动(增加fifo)

对AXI封装代码的修改,增加fifo

my_qspi_ip_v1_0_S00_AXI.v

硬件写寄存器部分

	always @( posedge S_AXI_ACLK )
	begin
	  if ( S_AXI_ARESETN == 1'b0 )
	    begin
	      slv_reg0 <= 0;
	      slv_reg1 <= 0;
	      slv_reg2 <= 0;
	      slv_reg3 <= 0;
	      slv_reg4 <= 0;
	      slv_reg5 <= 0;
	      slv_reg6 <= 0;
	      slv_reg7 <= 0;
	    end 
	  if(xfer_end) begin
	       slv_reg6<=32'h1;
	  end 
	  else if(full) begin
	       slv_reg7[0]<=1;
	  end
	  else if(almost_full) begin
	       slv_reg7[1]<=1;	  
	  end 	 	  	    
	  else begin
	    if (slv_reg_wren)
	      begin
            ......

用户定义代码部分

	// Add user logic here
    (*mark_debug="true"*)wire [7 : 0] din;
	(*mark_debug="true"*)wire rd_en;
	(*mark_debug="true"*)reg wr_en;	
    (*mark_debug="true"*)wire [7 : 0] dout;
    (*mark_debug="true"*)wire full;
    (*mark_debug="true"*)wire almost_full;
    (*mark_debug="true"*)wire empty;
    (*mark_debug="true"*)wire almost_empty;
    (*mark_debug="true"*)wire [9 : 0] data_count;  
	(*mark_debug="true"*)wire fifo_en;
    (*mark_debug="true"*)reg xfer_start;
    
    //read logic
    reg empty_s;
    reg state;
    always @(posedge S_AXI_ACLK)
    if(S_AXI_ARESETN==0)
        empty_s<=0;
    else
        empty_s<=empty;
    
    assign first_rd_en=(~empty) & empty_s;            //第一次读,由empty信号下降沿自动启动   

    always @(posedge S_AXI_ACLK)
    if(S_AXI_ARESETN==0)
        state <= 0;
    else case(state)
        0: if(xfer_start) state<=1;  
        1: if(xfer_end) state<=0;  //is working ,do not reponse first_rd_en
     endcase        
    
    assign rd_en=(state==0)&first_rd_en | xfer_end&~empty; //fifo不为空的时候才可以读    
    
    always @( posedge S_AXI_ACLK )
    if ( S_AXI_ARESETN == 1'b0 )
        xfer_start<= 0;
    else if(~fifo_en)
        xfer_start <= (slv_reg_wren && axi_awaddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB]==3'h0)? 1:0;    
    else
        xfer_start<=rd_en;  
    
    assign fifo_en=slv_reg5[1];
    
    // wirte logic
    //full --> slv_reg7[0]<=1;
    //almost_full --> slv_reg7[1]<=1;
    assign din=slv_reg3[7:0];

    always @( posedge S_AXI_ACLK )
    if ( S_AXI_ARESETN == 1'b0 )
        wr_en<= 0;    
    else
        wr_en <=(~almost_full && slv_reg_wren && axi_awaddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB]==3'h3)? 1:0;  
        
    qspi qspi_inst (
       // Input Ports - Single Bit
       .sys_clk       (S_AXI_ACLK),    
       .sys_rst_n     (S_AXI_ARESETN),  
       .xfer_start    (xfer_start),       
       // Input Ports - Busses
       .cmd(slv_reg1[7:0]),
       .addr(slv_reg2[23:0]),
//       .data_in (slv_reg3[7:0]),
        .data_in(dout),
       .length  (slv_reg4[17:0]),
       .mode(slv_reg5[0]),
       // Output Ports - Single Bit
       .csx           (csx),        
       .sck           (sck),            
       .xfer_end      (xfer_end),   
       // Output Ports - Busses
       .do     (do)   
       // InOut Ports - Single Bit
       // InOut Ports - Busses
    );     
    
    fifo_generator_0 your_instance_name (
  .clk(S_AXI_ACLK),                    // input wire clk
  .srst(~S_AXI_ARESETN),                  // input wire srst  
  .din(din),                    // input wire [7 : 0] din
  .wr_en(wr_en),                // input wire wr_en
  .rd_en(rd_en),                // input wire rd_en
  .dout(dout),                  // output wire [7 : 0] dout
  .full(full),                  // output wire full
  .almost_full(almost_full),    // output wire almost_full
  .empty(empty),                // output wire empty
  .almost_empty(almost_empty),  // output wire almost_empty
  .data_count(data_count)      // output wire [9 : 0] data_count
);
	// User logic ends
image

注意first_rd_en产生rd_en,需要在qspi传输完成后才能执行。也就是说,在qspi正在传输期间,是要屏蔽first_rd_en的。

c代码

注意其中,主要增加了set_fifo_en()函数。另外底层函数做了适当修改。

#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
#include "xparameters.h"
#include "xil_io.h"
#include "sleep.h"
#include "my_qspi_ip.h"
#include "ff.h"
#include "xil_cache.h"
#include "xtime_l.h"

#define QSPI_BASE XPAR_MY_QSPI_IP_0_S00_AXI_BASEADDR

#define  RED        0xF800
#define  ORANGE     0xFC00
#define  YELLOW     0xFFE0
#define  GREEN      0x07E0
#define  CYAN       0x07FF
#define  BLUE       0x001F
#define  PURPPLE    0xF81F
#define  BLACK      0x0000
#define  WHITE      0xFFFF
#define  GRAY       0xD69A

const u16 colors[] = {
	RED,
	GREEN,
	BLUE,
	ORANGE,
	YELLOW,
	CYAN,
	PURPPLE,
	BLACK,
	WHITE,
	GRAY
};

typedef struct{
	u8 txbuf[50];
	u8 rxbuf[50];
	u8 length;
}t_buf;

t_buf WriteBuffer[]={
{ {0xfe} , {0x0} , 1 },
{ {0xef} , {0x0} , 1 },
{ {0x80, 0x11} , {0x0} , 2 },
{ {0x81, 0x70} , {0x0} , 2 },
{ {0x82, 0x9} , {0x0} , 2 },
{ {0x83, 0x3} , {0x0} , 2 },
{ {0x84, 0x62} , {0x0} , 2 },
{ {0x89, 0x18} , {0x0} , 2 },
{ {0x8a, 0x40} , {0x0} , 2 },
{ {0x8b, 0xa} , {0x0} , 2 },
{ {0x3a, 0x5} , {0x0} , 2 },
{ {0x36, 0x40} , {0x0} , 2 },
{ {0xec, 0x7} , {0x0} , 2 },
{ {0x74, 0x1, 0x80, 0x0, 0x0, 0x0, 0x0} , {0x0} , 7 },
{ {0x98, 0x3e} , {0x0} , 2 },
{ {0x99, 0x3e} , {0x0} , 2 },
{ {0xa1, 0x1, 0x4} , {0x0} , 3 },
{ {0xa2, 0x1, 0x4} , {0x0} , 3 },
{ {0xcb, 0x2} , {0x0} , 2 },
{ {0x7c, 0xb6, 0x24} , {0x0} , 3 },
{ {0xac, 0x74} , {0x0} , 2 },
{ {0xf6, 0x80} , {0x0} , 2 },
{ {0xb5, 0x9, 0x9} , {0x0} , 3 },
{ {0xeb, 0x1, 0x81} , {0x0} , 3 },
{ {0x60, 0x38, 0x6, 0x13, 0x56} , {0x0} , 5 },
{ {0x63, 0x38, 0x8, 0x13, 0x56} , {0x0} , 5 },
{ {0x61, 0x3b, 0x1b, 0x58, 0x38} , {0x0} , 5 },
{ {0x62, 0x3b, 0x1b, 0x58, 0x38} , {0x0} , 5 },
{ {0x64, 0x38, 0xa, 0x73, 0x16, 0x13, 0x56} , {0x0} , 7 },
{ {0x66, 0x38, 0xb, 0x73, 0x17, 0x13, 0x56} , {0x0} , 7 },
{ {0x68, 0x0, 0xb, 0x22, 0xb, 0x22, 0x1c, 0x1c} , {0x0} , 8 },
{ {0x69, 0x0, 0xb, 0x26, 0xb, 0x26, 0x1c, 0x1c} , {0x0} , 8 },
{ {0x6a, 0x15, 0x0} , {0x0} , 3 },
{ {0x6e, 0x8, 0x2, 0x1a, 0x0, 0x12, 0x12, 0x11, 0x11, 0x14, 0x14, 0x13, 0x13, 0x4, 0x19, 0x1e, 0x1d, 0x1d, 0x1e, 0x19, 0x4, 0xb, 0xb, 0xc, 0xc, 0x9, 0x9, 0xa, 0xa, 0x0, 0x1a, 0x1, 0x7} , {0x0} , 33 },
{ {0x6c, 0xcc, 0xc, 0xcc, 0x84, 0xcc, 0x4, 0x50} , {0x0} , 8 },
{ {0x7d, 0x72} , {0x0} , 2 },
{ {0x70, 0x2, 0x3, 0x9, 0x7, 0x9, 0x3, 0x9, 0x7, 0x9, 0x3} , {0x0} , 11 },
{ {0x90, 0x6, 0x6, 0x5, 0x6} , {0x0} , 5 },
{ {0x93, 0x45, 0xff, 0x0} , {0x0} , 4 },
{ {0xc3, 0x15} , {0x0} , 2 },
{ {0xc4, 0x36} , {0x0} , 2 },
{ {0xc9, 0x3d} , {0x0} , 2 },
{ {0xf0, 0x47, 0x7, 0xa, 0xa, 0x0, 0x29} , {0x0} , 7 },
{ {0xf2, 0x47, 0x7, 0xa, 0xa, 0x0, 0x29} , {0x0} , 7 },
{ {0xf1, 0x42, 0x91, 0x10, 0x2d, 0x2f, 0x6f} , {0x0} , 7 },
{ {0xf3, 0x42, 0x91, 0x10, 0x2d, 0x2f, 0x6f} , {0x0} , 7 },
{ {0xf9, 0x30} , {0x0} , 2 },
{ {0xbe, 0x11} , {0x0} , 2 },
{ {0xfb, 0x0, 0x0} , {0x0} , 3 },
{ {0x11} , {0x0} , 1 },
{ {0xff} , {0x0} , 0 },
{ {0x29} , {0x0} , 1 },
{ {0xff} , {0x0} , 0 },
{ {0x2c, 0x0, 0x0, 0x0, 0x0} , {0x0} , 5 },
{ {0x2c, 0x0, 0x0, 0x0, 0x0} , {0x0} , 5 },
};

/*************************************************
 * reg rw functions
 ************************************************/
/*
reg0[0]---xfer_start soft
reg1[7:0]---cmd
reg2[23:0]---addr
reg3[7:0]---data
reg4[17:0]---length
reg5[0]---mode,  reg5[1]---fifo_en
reg6[0]---xfer_end
reg7[0]---full, reg7[1]----almost_full
*/

void set_cmd(u8 data){
    Xil_Out32(QSPI_BASE+0x4, data);
}

void set_addr(u32 data){
    Xil_Out32(QSPI_BASE+0x8, data);
}

void set_data(u8 data){
    Xil_Out32(QSPI_BASE+0xc, data);
}

void set_length(u32 data){
    Xil_Out32(QSPI_BASE+0x10, data);
}

void set_line_mode(u8 data){
	u32 tmp=Xil_In32(QSPI_BASE+0x14);
	tmp&=~0x1;
	tmp|=data&0x1;
    Xil_Out32(QSPI_BASE+0x14, tmp);
}

void set_fifo_en(u8 data){
	u32 tmp=Xil_In32(QSPI_BASE+0x14);
	tmp&=~0x2;
	tmp|=(data&0x1)<<1;
    Xil_Out32(QSPI_BASE+0x14, tmp);
}

void set_xfer_start(){
    Xil_Out32(QSPI_BASE+0x00, 0x1);
}

void poll_xfer_end(){
    while(Xil_In32(QSPI_BASE+0x18)==0x0);
    Xil_Out32(QSPI_BASE+0x18, 0x00);
}

/*************************************************
 * basic xfer functions
 ************************************************/
void xfer_data(u8 data){
	set_data(data);
//	set_xfer_start();
//	poll_xfer_end();
}

void xfer_data_no_poll(u8 data){
	set_data(data);
//	set_xfer_start();
//	poll_xfer_end();
}

void xfer_cmd_pdata8(u8 cmd, u8* pdata, u32 length){
	set_fifo_en(0);
	set_length(length+1);
	set_cmd(0x02);
	set_addr(cmd<<8);
	set_xfer_start();
	poll_xfer_end();

	set_fifo_en(1);
    while(length--){
    	xfer_data(*pdata++);
    }
}

void xfer_cmd_pdata16(u8 cmd, u16* pdata, u32 length){
	set_fifo_en(0);
	set_length(2*length+1);
	set_cmd(0x32);
	set_addr(cmd<<8);
	set_xfer_start();
	poll_xfer_end();

	set_fifo_en(1);
    while(length--){
    	xfer_data_no_poll(*pdata>>8);
    	xfer_data_no_poll(*pdata&0xff);
    	pdata++;
    }
}

void xfer_cmd_cdata8(u8 cmd, u8 data, u32 length){
	set_fifo_en(0);
	set_length(length+1);
	set_cmd(0x02);
	set_addr(cmd<<8);
	set_xfer_start();
	poll_xfer_end();

	set_fifo_en(1);
    while(length--){
    	xfer_data(data);
    }
}

void xfer_cmd_cdata16(u8 cmd, u16 data, u32 length){
	set_fifo_en(0);
	set_length(2*length+1);
	set_cmd(0x32);
	set_addr(cmd<<8);
	set_xfer_start();
	poll_xfer_end();

	set_fifo_en(1);
    while(length--){
    	xfer_data(data>>8);
    	xfer_data(data&0xff);
    }
}

void xfer_cmd(u8 cmd){
	set_length(0x1);
	set_cmd(0x02);
	set_addr(cmd<<8);
	set_xfer_start();
}


void seqs_init() {
	set_line_mode(0);

	for (int i = 0; i < sizeof(WriteBuffer) / sizeof(*WriteBuffer); i++) {
		if (WriteBuffer[i].length == 0) {
			usleep(WriteBuffer[i].txbuf[0] * 1000);
			continue;
		}

		u8 cmd=WriteBuffer[i].txbuf[0];
		u8 *pdata=WriteBuffer[i].txbuf+1;
		u16 length=WriteBuffer[i].length-1;

		xfer_cmd_pdata8(cmd, pdata, length);
		usleep(100); //this delay is need
		xil_printf("Exec CMD=%x\r\n\r\n", cmd);
	}
}

void xfer_win(u16 xstart, u16 ystart, u16 width, u16 height)
{
    u8 cmd_list[2][5]={
    		{0x2a, xstart>>8, xstart&0xff, (xstart+width-1)>>8, (xstart+width-1)&0xff},
    		{0x2b, ystart>>8, ystart&0xff, (ystart+height-1)>>8, (ystart+height-1)&0xff},
    };

	set_line_mode(0);
    xfer_cmd_pdata8(cmd_list[0][0], &(cmd_list[0][1]),4);
    xfer_cmd_pdata8(cmd_list[1][0], &(cmd_list[1][1]),4);
}

void xfer_color(u16 color, u16 width, u16 height){
	u32 length=width*height;
	set_line_mode(1);
	xfer_cmd_cdata16(0x2C, color, length);
}

void xfer_pic(u16 *pcolor, u16 width, u16 height){
	u32 length=width*height;
	set_line_mode(1);
	xfer_cmd_pdata16(0x2C, pcolor, length);
}

/*************************************************
 * application functions
 ************************************************/
u8 frame_src[1920*1080*3];
u16 frame_target[320*386];

void load_sd_bmp(u8 *frame, const char *bmp_name);
void convertRGB888toRGB565(
		const uint8_t *input,
		uint16_t *output,
		int inputWidth, int inputHeight,
		int startX, int startY,
		int outputWidth, int outputHeight
		) ;

void dram_eight_colors()
{
	for(int i=0; i<sizeof(colors)/sizeof(*colors); i++){
		xfer_win(0,0,320,386);
		xfer_color(colors[i],320,386);
		sleep(1);
	}
}


void draw_bmp()
{
	load_sd_bmp(frame_src, "aa_320x480.bmp"); //320x386
	convertRGB888toRGB565(
			frame_src,
			frame_target,
			320,480,
			0,0,
			320,386
			);
	xfer_win(0,0,320,386);
	xfer_pic(frame_target,320,386);

	sleep(2);

	load_sd_bmp(frame_src, "bb_320x480.bmp"); //320x386
	convertRGB888toRGB565(
			frame_src,
			frame_target,
			320,480,
			0,0,
			320,386
			);
	xfer_win(0,0,320,386);
	xfer_pic(frame_target,320,386);

	load_sd_bmp(frame_src, "shatan.bmp");	//1920x1080
	convertRGB888toRGB565(
			frame_src,
			frame_target,
			1920,1080,
			0,0,
			320,386
			);
	xfer_win(0,0,320,386);
	xfer_pic(frame_target,320,386);
}

void draw_movie()
{
	load_sd_bmp(frame_src, "shatan.bmp");	//1920x1080
	int x=300,y=400;
	int dir_x=1, dir_y=1;
	while(1){
		if(dir_x) x+=100; else x-=100;
		if(dir_y) y+=50; else y-=50;

		if(x+320>1920) {x=1920-320; dir_x=0;} else if(x<0) {x=0; dir_x=1;}
		if(y+386>1080) {y=1080-386; dir_y=0;} else if(y<0) {y=0; dir_y=1;}
//		xil_printf("x=%d, y=%d\r\n", x,y);

		XTime_SetTime(0);
		XTime t1,t2,t3;
		XTime_GetTime(&t1);
		convertRGB888toRGB565(
				frame_src,
				frame_target,
				1920,1080,
				x,y,
				320,386
				);
		XTime_GetTime(&t2);
		xfer_win(0,0,320,386);
		xfer_pic(frame_target,320,386);
		XTime_GetTime(&t3);

		XTime dt1 = ((t2-t1) * 1000000) / (COUNTS_PER_SECOND);
		XTime dt2 = ((t3-t2) * 1000000) / (COUNTS_PER_SECOND);
		xil_printf("dt1=%d, dt2=%d\r\n", dt1,dt2);
	}
}


int main()
{
    init_platform();

    print("Hello World\n\r");

    //MY_QSPI_IP_Reg_SelfTest((void *)QSPI_BASE);

	seqs_init();

    //dram_eight_colors();
    //draw_bmp();
    draw_movie();

    cleanup_platform();
    return 0;
}

//从SD卡中读取BMP图片
void load_sd_bmp(u8 *frame, const char *bmp_name)
{
	static 	FATFS fatfs;
	FIL 	fil;
	u8		bmp_head[54];
	UINT 	bmp_width,bmp_height,bmp_size;
	UINT 	br;

	f_mount(&fatfs,"",1);//挂载文件系统

	f_open(&fil, bmp_name,FA_READ);	//打开文件, 注意是bmp_24bits格式
	xil_printf("open bmp\n\r");
	f_lseek(&fil,0);//移动文件读写指针到文件开头

	f_read(&fil,bmp_head,54,&br);//读取BMP文件头

	//BMP图片的分辨率和大小
	bmp_width  = *(UINT *)(bmp_head + 0x12);
	bmp_height = *(UINT *)(bmp_head + 0x16);
	bmp_size   = *(UINT *)(bmp_head + 0x22);
	xil_printf("bmp information:\n\r");
	xil_printf(" width  = %d,\n\r height = %d,\n\r size   = %d bytes \n\r",
			bmp_width,bmp_height,bmp_size);

	//读出图片,写入DDR
	f_read(&fil, frame, bmp_width*bmp_height*3,&br);
	xil_printf("br=%d\r\n", br);

	for(int i=0; i<20; i++){
		xil_printf("%x\r\n", frame[i]);
	}

	//关闭文件
	f_close(&fil);

	Xil_DCacheFlush();     //刷新Cache,将数据更新至DDR3中
	xil_printf("display bmp\n\r");
}

void convertRGB888toRGB565(const uint8_t *input, uint16_t *output, int inputWidth, int inputHeight, int startX, int startY, int outputWidth, int outputHeight)
{
    int i, j;

    for (i = 0; i < outputHeight; i++) {
        for (j = 0; j < outputWidth; j++) {
            // Calculate the index in the RGB888 array
            int inputIndex = ((startY + i) * inputWidth + startX + j) * 3;

            // Extract RGB888 components
            uint8_t b = input[inputIndex];
            uint8_t g = input[inputIndex + 1];
            uint8_t r = input[inputIndex + 2];

            // Convert to RGB565 format
            uint16_t rgb565 = ((r>>3) << 11) | ((g>>2) << 5) | (b>>3);

            // Store in the output array
            output[i * outputWidth + j] = rgb565;
        }
    }
}

ILA和逻辑分析仪抓的波形

image image

发图期间,从rd_en到xfer_end只需要7个cycle,但是从rd_en到下一次rd_en需要24个cycle。原因是CPU通过AXI-Lite写太慢了。 根据这个速度预估帧率为F=1e6/(32038660224*20ns)=8.4Hz

image

实际抓到的帧率是11Hz左右,差不多。

效果

image

这里仅给出图片的效果。动画效果不太好展示,这里就忽略了。

⚠️ **GitHub.com Fallback** ⚠️