ZYNQ AXI DMA调试细节

时间:2024-03-19 22:31:28

本文介绍ZYNQ AXI DMA的简单模式使用方法,查询模式(poll),不使用中断,32bit。

1.有关DMA的函数调用,去参照DMA的官方例程。所有的外设都是有ID的,先建立一个结构体,初始化外设,把外设的基地址赋值给结构体,对结构体进行赋值就是写相应的寄存器,控制DMA工作。所有的外设都有寄存器手册,自己去下载,直接看寄存器空间register space就可以了,例如DMA的寄存器手册。DMA有两种方式,我只学习了使用简单模式,Scatter / Gather Mode可以看其他博主的介绍。下面的代码是DMA的初始化,非常简单,以及开启一次DMA接收,数据从axis stream fifo到DMA到DDR3。
Xil_In32 和Xil_Out32直接读和写寄存器,还有Xil_In8和Xil_Out8,是按byte操作。

void DMA_INIT(void)
{
	//初始化
        int Status;
	XAxiDma_Config *Config;
	XScuGic_Config *IntcConfig;
	Config = XAxiDma_LookupConfig(XPAR_AXIDMA_0_DEVICE_ID);
	Status = XAxiDma_CfgInitialize(&AxiDma, Config);
        //不使能中断
        XAxiDma_IntrDisable(&AxiDma, XAXIDMA_IRQ_ALL_MASK,XAXIDMA_DMA_TO_DEVICE);
        XAxiDma_IntrDisable(&AxiDma, XAXIDMA_IRQ_ALL_MASK,XAXIDMA_DEVICE_TO_DMA);
        //初始化DMA,初不初始化无所谓,跟进去就知道本质都在写寄存器
	XAxiDma_Reset(&AxiDma);
	while(!XAxiDma_ResetIsDone(&AxiDma));
        //打印寄存器  
        //0x4040 0000是DMA的基地址,在block design的address中可以看到,后面是偏移地址,
        //window->address editor
	xil_printf("CR is %d\r\n", Xil_In32(0x40400000 + 0x30));
	xil_printf("SR is %d\r\n", Xil_In32(0x40400000 + 0x34));
	xil_printf("LENGTH is %d\r\n", Xil_In32(0x40400000 + 0x58));
        //开始传输
	Xil_Out32((0x40400000 + 0x30),1); //start dma
	Xil_Out32((0x40400000 + 0x48),base_addr); //set da address
	Xil_Out32((0x40400000 + 0x58),16383); //set length
        //再次打印,看寄存器的状态
	xil_printf("CR is %d\r\n", Xil_In32(0x40400000 + 0x30));
	xil_printf("SR is %d\r\n", Xil_In32(0x40400000 + 0x34));
	xil_printf("LENGTH is %d\r\n", Xil_In32(0x40400000 + 0x58));
}

2. Xilinx的例程代码向来都是异常复杂,调到最后不免都要去看寄存器,翻阅手册我们看到DMA简单模式如何使用。要注意,MM2S是说 memory map to stream,S2MM是stream to memory map,memory map当然是指DDR3内存了,根据名称我们就知道MM2S是说DMA把数据从DDR3搬移到stream FIFO中。 从下面手册中我们看到了寄存器偏移地址从00h-28h 是mm2s的,30h-58h是s2mm的。30h是CR寄存器,有RS,reset等寄存器位,后面都有说明。

ZYNQ AXI DMA调试细节

ZYNQ AXI DMA调试细节

3.DMA的使用方法也在手册中直接说明了。

ZYNQ AXI DMA调试细节

ZYNQ AXI DMA调试细节

ZYNQ AXI DMA调试细节

A.DMA发送在例程里也有说明,xaxidma_example_simple_poll.c,

XAxiDma_SimpleTransfer(&AxiDma,(UINTPTR) TxBufferPtr,MAX_PKT_LEN,XAXIDMA_DMA_TO_DEVICE);

注意,XAXIDMA_DMA_TO_DEVICE就是DDR3 到 FIFO,从TxBufferPtr发送MAX_PKT_LEN个字节到FIFO(不一定是FIFO),

判断DMA发送的SR寄存器idle是否为1,为1则idle,说明发送结束。

while (XAxiDma_Busy(&AxiDma,XAXIDMA_DMA_TO_DEVICE))

B.发送很简单,接收有点复杂,主要是tlast信号的问题,要先了解DMA的接收过程。下图是AXI DMA的配置图。

ZYNQ AXI DMA调试细节

a.不使能scatter gather和miro DMA,使用简单模式,前者可以配置多个地址,一次开启多次传输。width of buffer length register这个是指接收或发送的最长长度,14位最大就是16384,实际上在函数中判断是16383,该参数设置过长, XAxiDma_SimpleTransfer函数就会返回一个错误。

b.address width是32位,就是对应到DDR3上,一次操作是32bit。read channel 和write channel很不好记,要理解DMA工作时是独立于CPU的,DMA读写都是指对DDR3的操作,由于我的应用是从底层产生数据存到FIFO中,等DMA来读取,因此只需要开启写DDR3通道即可。

c.max burst size,突发传输长度。突发的意思,就是传送一次地址,取多个数据的长度。这一点不好理解:当CPU通过AXI lite写DMA寄存器开启DMA接收后,DMA的tready拉高,开始传输数据,每接收到突发长度后,拉低一次tready,把数据写到DDR3中,写完之后再拉高再接收数据。

d.allow unaligned transfers。地址对齐,这个很重要,发送和接收通道都有,不开启,你发送和接收都必须要从4byte对齐的位置开始,必须从0x00,0x04,0x08等位置发送或接收,否则DMA不会正常工作。

我们再回去看接收的编程说明。

a.写S2MM_DMACR.RS =1,然后DMASR.Halted = 0 ,DMA表示在运行了。

b.写目的地地址到DA寄存器中。

c.写LENGTH寄存器,必须要最后写。写完这个寄存器后,DMA就开始传输了。传输完毕后,该寄存器的值是实际接收到的字节数。

d.判断DMASR.idle = 1,后表示接收结束。

	Xil_Out32((0x40400000 + 0x30),1); //start dma
	Xil_Out32((0x40400000 + 0x48),base_addr); //set da address
	Xil_Out32((0x40400000 + 0x58),16383); //set length

对照代码看,是不是很简单,看到这里,要是以为你就会用DMA发送和接收,扭头就去写代码了,小伙子你还是太年轻了。

4. DMA何时才会接收结束?

DMA的读写接口都是axis stream,是强制有tlast的,就是靠tlast信号来判断接收结束的,而不是填写的length值。发送接收值,最后一个tdata会同时伴随一个tlast高电平。接收时,设置接收5000字节,并不是说接收满5000个字节就结束了,而是开启接收后,接收到1个tlast高电平就结束了,也就是说,你可能会只接收到2000个字节,手册中明确说了,当设置的length = 0,或者接收到的数据大于设置的length,就会引起DMAIntErr置1,接收错误。

所以,当设置每次接收5000字节时,tlast就应该每隔<5000字节时插入。我的应用中,DMA前是一个AXIS DATA FIFO,写入是自定义的时序,但接收时还是报错,接收得太多了。为什么呢?因为DMA是自己拉高tready,而fifo的valid也为高就开始计数了,跟FIFO随后的valid 无关(我瞎猜的),DMA读的速度远高于我写入速度,我还没写到tlast,DMA就计数到16383了,DMA就报错,怎么办呢,开启fifo的packet模式。然后DMA接收就ok了。

5. 实际应用

DMA的使用上我遇到的坑,实际上就是地址对齐,tlast的给定上,因为我每次接收完后都会reset下,导致看不出来dma哪儿有问题,但数据总是不对。

DMA的发送一般没有什么问题,有个小技巧,可以用DMA的reset_out引脚去初始化fifo,我有一个应用就2个dma交替发送数据到pl的2个fifo,fifo的存取上老有问题,写768个字节,读768个字节,fifo有时还会有留存数据,导致下一次取数据多了一个,也没细想过问题,DMA发送前先复位一下,顺便把fifo复位一下,就解决这个问题。

另一个应用是pl采集spi的数据,但数据不知道什么时候来,每一包也不知道有多少个字节。我在ps中开了一个定时器,1ms一次,开定时器前,先开启DMA接收,在定时器中断中查询是否接收完成(idle),idle后就把接收地址base_addr加上1个length,再开启下一次传输。而pl端,每隔4096个字节就插一个tlast。又有一个问题,要是有5000个字节呢?剩下的4个怎么办?我又加了个定时器,超过2s,没有数据来,且fifo的data_count不等于0,我就加入aa aa aa数据,并在最后一个aa时拉高一次tlast,表示一次传输完成。在ps端,开启freertos多线程,设置一个send_addr和base_addr初始值相同,当send_addr小于base_addr时,用udp把数据发送出去。又有一个问题,base_addr不能一直往上上加啊,加到大于某个值的时候,且send_addr = base_addr,把数据拷贝到初始值去,重置这2个指针。

void Timer_ISR(void *CallBackRef)
{
	lock = 1;
	if(Xil_In32(0x40400000 + 0x34) & 0x02) //idle = 10
	{
		Xil_DCacheFlushRange(base_addr,Xil_In32(0x40400000 + 0x58));
	//	xil_printf("length is %d\r\n",Xil_In32(0x40400000 + 0x58));
		unsigned int len = Xil_In32(0x40400000 + 0x58);
		if((send_addr == base_addr) &&(send_addr != 0x1000000) && (base_addr >= 0x1F400000))
		{
			memcpy(0x1000000,base_addr,len);// dest,src,len
			base_addr = 0x1000000;
			send_addr = 0x1000000;
			base_addr = base_addr + len;
			Xil_Out32((0x40400000 + 0x48),base_addr); //set da address
			Xil_Out32((0x40400000 + 0x58),8192); //set da address
		}
		else
		{
			base_addr = base_addr + len;
			Xil_Out32((0x40400000 + 0x48),base_addr); //set da address
			Xil_Out32((0x40400000 + 0x58),8192); //set da address
		}

	}
	lock = 0;
//	xil_printf("count is %d\r\n",XGpio_DiscreteRead(&COUNT,1));
//	xil_printf("CR is %d\r\n", Xil_In32(0x40400000 + 0x30));
//	xil_printf("SR is %d\r\n", Xil_In32(0x40400000 + 0x34));
//	xil_printf("LENGTH is %d\r\n", Xil_In32(0x40400000 + 0x58));
}

要注意DMA操作DDR3,CPU是不知道的,要用dcacheflush及时刷新。这个刷新速度很慢的,要注意控制刷新的长度。

	while(1)
	{
		if(lock == 0)
		{
			if(send_addr < base_addr)
			{
				unsigned int len = base_addr - send_addr;
				if(len > 16384)
					len = 16384;
				msg_udp_send((UINTPTR)send_addr,len);
				send_addr = send_addr + len;
			}

		}
		vTaskDelay(10);
	}
[email protected](posedge clk)
    if((!en) ||(delay_over == 32'd400_000_000))
        byte_cnt <= 1'd0;
    else if(tvalid)
    begin
        if(byte_cnt == 32'd4095)
            byte_cnt <= 1'd0;
        else
            byte_cnt <= byte_cnt + 1'd1; 
    end
    
    assign m_axis_tlast = (tvalid && (byte_cnt == 32'd4095 || i == 8'd13))?1'd1:1'd0;
    
    
    reg [31:0] delay_over = 1'd0;
    assign mode = reg_mode;
    [email protected](posedge clk)
    if(!en)
        begin
            i <= 1'd0;
            reg_mode <= 1'd0;
            tvalid <= 1'd0;
            tdata <= 1'd0;
        end
    else
        case(i)
        0:
        if(cs_fall_edge && m_axis_tready&&(fifo_count <= 32'd36700) ) //fifo ready && cs fall
        begin
            i <= i + 1'd1;
            delay_over = 1'd0;
        end
        else if(fifo_count != 1'd0)
        begin
            if(delay_over == 32'd400_000_000) //超时2s 
                begin
                    delay_over = 1'd0;
                    i <= 8'd10;
                end
            else
                delay_over = delay_over + 1'd1;
        end
        1:
        if(data_cs)
            i <= 1'd0;
        else if(valid)
            begin
                case (data)
                8'h03,8'h0b:
                    reg_mode <= 4'd0;   
                8'h05,8'h9f:
                    reg_mode <= 4'd1; 
                default:
                    reg_mode <= 4'd1; 
                endcase
                i <= i + 1'd1;
            end
        else
            i <= i;
        2:
        begin
            tvalid<= 1'd1;
            tdata <= 8'hee;
            if(m_axis_tready&&(fifo_count <= 32'd36700))
                i <= i + 1'd1;
            else
                i <= 8'd7;
        end
        3:
        begin
            tvalid<= 1'd1;
            tdata <= 8'h55;
            if(m_axis_tready&&(fifo_count <= 32'd36700))
                i <= i + 1'd1;
            else
                i <= 8'd7;
        end
        4:
        begin
            tvalid<= 1'd1;
            tdata <= data;
            if(m_axis_tready&&(fifo_count <= 32'd36700))
                i <= i + 1'd1;
            else
                i <= 8'd7;
        end
        5:
        begin
            tvalid<= 1'd0;
            i <= i + 1'd1;
        end
        6:
        if(data_cs)
           i <= i + 1'd1;
        else if(valid)
            begin
                tvalid<= 1'd1;
                tdata <= data;
                if(m_axis_tready&&(fifo_count <= 32'd36700))
                    i <= 8'd5;
                else
                    i <= 8'd7;
            end
        else
            i <= i;
        7:
        begin
            tvalid<= 1'd1;
            tdata <= 8'haa;
            i <= i + 1'd1;
        end
        8:
        begin
            tvalid<= 1'd0;
            i <= i + 1'd1;
            if(isr_en)
                isr_start <= 1'd1;
        end
        9:
        begin
            i <= 1'd0;
            isr_start <= 1'd0;
        end
        10,11,12:
        begin
            tvalid<= 1'd1;
            tdata <= 8'haa;
            i <= i + 1'd1;
        end
        13:
        begin
            tvalid<= 1'd0;
            i <= 1'd0;
        end
        default:   
        i <= 1'd0;
        endcase
        

       assign m_axis_tvalid =  tvalid;
       assign m_axis_tdata  =  tdata;

坑有无数,愿你抗住!