当前位置:首页 > > ZYNQ
		


本文介绍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等寄存器位,后面都有说明。

3. 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的配置图。

  • 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);
}
always@(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;
 always@(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;
本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
关闭