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

注意first_rd_en产生rd_en,需要在qspi传输完成后才能执行。也就是说,在qspi正在传输期间,是要屏蔽first_rd_en的。
注意其中,主要增加了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;
}
}
}


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

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

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