软件版本:VIVADO2021.1 
操作系统:WIN10 64bit 
硬件平台:适用 XILINX A7/K7/Z7/ZU/KU 系列 FPGA 
实验平台:米联客-MLK-H3-CZ08-7100开发板 
板卡获取平台:https://milianke.tmall.com/ 
登录“米联客”FPGA社区 http://www.uisrc.com 视频课程、答疑解惑! 
 
  
1概述DAQ001是一款8通道16bit 200k采样率的高精度ADC,支持串行和并行采集接口。米联客DAQ001采用串行模式实现200K 8通道同时采样,相比并口方式,串行方式,具有硬件接口简单,节约成本优势。 实验目的: 1:掌握ui_axisbufw配置成非视频模式的情况下的参数设置 2:掌握ADC数据如何通过ui_axisbufw写入到AXI-DMA IP 3:掌握AXI-DMA IP中断的产生方法,以及AXIS时序接口 4:编写波形显示函数,把采集到的波形绘制成波形曲线在显示器上显示 2系统框图 
 
 
3硬件电路分析硬件接口和子卡模块请阅读“附录1” 配套工程的FPGA PIN脚定义路径为soc_prj/uisrc/04_pin/ fpga_pin.xdc。 4搭建SOC系统工程 
4.1PL图形化编程 
 
 
以上代码中用户数据位宽为128bit(daq001是8通道16bit 所以是128bit位宽), 用户写入的数据经过ui_axisbufw后进入axi-dma,之后通过AXI_interconnect 进入到ZYNQ DDR中。 SDK代码中,PS(ARM)读取内存中保存的ADC采样数值,并且通过插值方式绘制波形,波形先绘制到内存中,HDMI驱动从这个开辟的波形显存中主动读取数据,显示到显示器上。这样就完成了波形采集显示方案。 下面具体看下关键几个IP的参数设置 1:ui_axisbufw设置VIVADO_ENABLE:用于设置时序需要支持视频帧同步功能,对于数据流模式设置为0 AXI_DATA_WIDTH:用于设置AXI-Stream数据接口的位宽,这里设置128 W_BUFDEPTH:用于设置FIFO的深度,越大消耗资源越多,这里设置2048 W_XSIZE和W_YSIZE:配合可以一次DMA完成大量数据传输,而不需要太多的FIFO,并且通过W_XSIZE和W_YSIZE合理设置既可以满足速度要求,又可以减少FIFO使用。比如这里设置1024*64也可以设置512*128当设置512*128,那么FIFO就可以设置最大1024足够使用了。 通过这些参数设置,可以看出来,我们一次DMA完成1024*64*128/8=1MB数据传输,也就是8个通道每个通道,完成采集了64K的波形点采集。 
 
 
2:AXI-DMA设置 其中关键参数是设置合适的Width of Buffer Length Register 大小为23bit 代表最大DMA可以支持2^23=8MB数据,足够使用。 另外我们只用到写通道,所以只勾选写通道即可。 
 
 
3:修改system_wrapper.v将自动产生的system_wrapper.v复制到本方案工程路径soc_prj/uisrc/01_rtl/system_wrapper.v并对其修改,修改好的代码如下:  module system_wrapper( 
inout wire [14:0]DDR_addr, 
inout wire [2:0]DDR_ba, 
inout wire DDR_cas_n, 
inout wire DDR_ck_n, 
inout wire DDR_ck_p, 
inout wire DDR_cke, 
inout wire DDR_cs_n, 
inout wire [3:0]DDR_dm, 
inout wire [31:0]DDR_dq, 
inout wire [3:0]DDR_dqs_n, 
inout wire [3:0]DDR_dqs_p, 
inout wire DDR_odt, 
inout wire DDR_ras_n, 
inout wire DDR_reset_n, 
inout wire DDR_we_n, 
inout wire FIXED_IO_ddr_vrn, 
inout wire FIXED_IO_ddr_vrp, 
inout wire [53:0]FIXED_IO_mio, 
inout wire FIXED_IO_ps_clk, 
inout wire FIXED_IO_ps_porb, 
inout wire FIXED_IO_ps_srstb, 
//****************************** 
output wire HDMI_TX_CLK_N, 
output wire HDMI_TX_CLK_P, 
 
output wire [2:0]HDMI_TX_N, 
output wire [2:0]HDMI_TX_P, 
/***********************daq7606******************************/ 
input wire ad7606_busy_i, 
output wire ad7606_cs_o, //ad7606 AD cs 
output wire ad7606_sclk_o, //ad7606 AD data read 
output wire ad7606_rst_o, //ad7606 AD reset 
output wire ad7606_convsta_o, //ad7606 AD convert start 
output wire ad7606_convstb_o, //ad7606 AD convert start 
output wire ad7606_range_o, 
input wire ad7606_out_a_i, 
input wire ad7606_out_b_i, 
output wire ad7606card_en 
); 
assign ad7606card_en=1'b1; 
wire user_rstn; 
wire user_start; 
wire pl_clk0; 
reg [15:0] test_data; 
wire ad7606_cap_en; 
wire [63:0] ad7606_out_a,ad7606_out_b; 
wire [15:0] ad_ch0,ad_ch1,ad_ch2,ad_ch3,ad_ch4,ad_ch5,ad_ch6,ad_ch7; 
//sensor input -W_FIFO-------------- 
wire W_wclk_i = pl_clk0; 
wire W_FS_i = user_start; 
wire W_wren_i = ad7606_cap_en && user_start; 
wire [127: 0] W_data_i = {test_data,ad_ch6,ad_ch5,ad_ch4,ad_ch3,ad_ch2,ad_ch1,ad_ch0}; 
//----------axis signals write------- 
wire axis_clk =pl_clk0; 
wire [127: 0] axis_wdata; 
wire axis_wvalid; 
wire axis_wready; 
wire axis_last; 
always @(posedge W_wclk_i)begin 
if(user_rstn == 1'b0) 
test_data <= 0; 
else if(W_wren_i) 
test_data <= test_data + 1'b1; 
end 
assign ad_ch0 = ad7606_out_a[63:48]; 
assign ad_ch1 = ad7606_out_a[47:32]; 
assign ad_ch2 = ad7606_out_a[31:16]; 
assign ad_ch3 = ad7606_out_a[15: 0]; 
assign ad_ch4 = ad7606_out_b[63:48]; 
assign ad_ch5 = ad7606_out_b[47:32]; 
assign ad_ch6 = ad7606_out_b[31:16]; 
assign ad_ch7 = ad7606_out_b[15: 0]; 
/***********************调用 daq7606 IP 这里采用了 SPI 接口********************/ 
 
 
uispi7606#( 
.SPI_DIV(10'd5), 
.T5US_DIV(10'd499) 
) 
uispi7606_inst 
( 
.ad_clk_i(pl_clk0), 
.ad_rst_i(user_rstn==1'b0), 
.ad_busy_i(ad7606_busy_i), 
.ad_cs_o(ad7606_cs_o), 
.ad_sclk_o(ad7606_sclk_o), 
.ad_rst_o(ad7606_rst_o), 
.ad_convsta_o(ad7606_convsta_o), 
.ad_convstb_o(ad7606_convstb_o), 
.ad_range_o(ad7606_range_o), 
.ad_out_a_i(ad7606_out_a_i), 
.ad_out_b_i(ad7606_out_b_i), 
.ad_out_a(ad7606_out_a), 
.ad_out_b(ad7606_out_b), 
.ad_cap_en(ad7606_cap_en) 
); 
ila_0 ila_debug ( 
.clk(pl_clk0), // input wire cl 
.probe0({ad7606_cap_en,user_rstn,user_start}), // input wire [15:0] probe0 
.probe1({test_data,ad_ch6,ad_ch5,ad_ch4,ad_ch3,ad_ch2,ad_ch1,ad_ch0}) 
); 
/*********************** 
一次 DMA 传输 128*1024*64 字节大小的数据 
**************************/ 
ui_axisbufw#( 
.VIDEO_ENABLE(0), 
.AXI_DATA_WIDTH(128), 
.W_BUFDEPTH(2048), 
.W_DATAWIDTH(128), 
.W_XSIZE(1024), 
.W_YSIZE(64) 
) 
ui_axisbufw_inst 
( 
.ui_rstn(user_rstn), 
//sensor input -W_FIFO-------------- 
.W_wclk_i(W_wclk_i), 
.W_FS_i(W_FS_i), 
.W_wren_i(W_wren_i), 
.W_data_i(W_data_i), 
//----------axis signals write------- 
.axis_clk(axis_clk), 
.axis_wdata(axis_wdata), 
.axis_wvalid(axis_wvalid), 
.axis_wready(axis_wready), 
.axis_last(axis_last) 
 
 
); 
system system_i 
( 
.DDR_addr(DDR_addr), 
.DDR_ba(DDR_ba), 
.DDR_cas_n(DDR_cas_n), 
.DDR_ck_n(DDR_ck_n), 
.DDR_ck_p(DDR_ck_p), 
.DDR_cke(DDR_cke), 
.DDR_cs_n(DDR_cs_n), 
.DDR_dm(DDR_dm), 
.DDR_dq(DDR_dq), 
.DDR_dqs_n(DDR_dqs_n), 
.DDR_dqs_p(DDR_dqs_p), 
.DDR_odt(DDR_odt), 
.DDR_ras_n(DDR_ras_n), 
.DDR_reset_n(DDR_reset_n), 
.DDR_we_n(DDR_we_n), 
.FIXED_IO_ddr_vrn(FIXED_IO_ddr_vrn), 
.FIXED_IO_ddr_vrp(FIXED_IO_ddr_vrp), 
.FIXED_IO_mio(FIXED_IO_mio), 
.FIXED_IO_ps_clk(FIXED_IO_ps_clk), 
.FIXED_IO_ps_porb(FIXED_IO_ps_porb), 
.FIXED_IO_ps_srstb(FIXED_IO_ps_srstb), 
.HDMI_TX_CLK_N(HDMI_TX_CLK_N), 
.HDMI_TX_CLK_P(HDMI_TX_CLK_P), 
.HDMI_TX_N(HDMI_TX_N), 
.HDMI_TX_P(HDMI_TX_P), 
.S_AXIS_S2MM_0_tdata(axis_wdata), 
.S_AXIS_S2MM_0_tkeep(16'hffff), 
.S_AXIS_S2MM_0_tlast(axis_last), 
.S_AXIS_S2MM_0_tready(axis_wready), 
.S_AXIS_S2MM_0_tvalid(axis_wvalid), 
.pl_clk(pl_clk0), 
.user_rstn(user_rstn), 
.user_start(user_start) 
); 
endmodule 
 
 |  
  
以上代码中,调用了米联客uispi7606 IP CORE采集模拟数据,并且把采集好的数据写入到ui_axisbufw中,为了方便实验中观察数据,把第八个通道的AD数据改成了计数器。实际项目中可以把这个替换成第八个通道的ADC数据。 4.2设置地址分配需要注意axi-lite接口地址,这个地址我们会在SDK 代码中使用AXI-GPIO控制传输的复位和启动。  
 
 
4.3添加PIN约束1:选中PROJECT  MANAGERà Add SourcesàAdd or create constraints,添加XDC约束文件。 
 
 
2:打开提供例程,复制约束文件中的管脚约束到XDC文件,或者查看原理图,自行添加管脚约束,并保存。 以下是添加配套工程路径下已经提供的pin脚文件。配套工程的pin脚约束文件在uisrc/04_pin路径 4.4编译并导出平台文件1:单击Block文件à右键àGenerate the Output ProductsàGlobalàGenerate。 2:单击Block文件à右键à Create a HDL wrapper(生成HDL顶层文件)àLet vivado manager wrapper and auto-update(自动更新)。 3:生成Bit文件。 4:导出到硬件: FileàExport HardwareàInclude bitstream 5:导出完成后,对应工程路径的soc_hw路径下有硬件平台文件:system_wrapper.xsa的文件。根据硬件平台文件system_wrapper.xsa来创建需要Platform平台。 
 
 
5搭建Vitis-sdk工程创建soc_base sdk platform和APP工程的过程不再重复,如果不清楚请参考本章节第一个demo。  5.1创建SDK Platform工程 
 
 
5.2创建daq001_dma_wave工程 
 
 
6程序分析 
6.1DMA数据获取原理 
 
 
每当AXI-DMA发送的中断后,DMA中断函数被调用。  1:PS_RX_intr_Handler 
 static void DMA_RxIntrHandler(void *Callback) 
{ 
u32 IrqStatus; 
u32 Status; 
XAxiDma *AxiDmaInst = (XAxiDma *)Callback; 
/* Read pending interrupts */ 
IrqStatus = XAxiDma_IntrGetIrq(AxiDmaInst, XAXIDMA_DEVICE_TO_DMA); 
/* Acknowledge pending interrupts */ 
XAxiDma_IntrAckIrq(AxiDmaInst, IrqStatus, XAXIDMA_DEVICE_TO_DMA); 
/* 
* If no interrupt is asserted, we do not do anything 
*/ 
if (!(IrqStatus & XAXIDMA_IRQ_ALL_MASK)) { 
return; 
} 
/* 
* If error interrupt is asserted, raise error flag, reset the 
* hardware to recover from the error, and return with no further 
* processing. 
*/ 
if ((IrqStatus & XAXIDMA_IRQ_ERROR_MASK)) { 
xil_printf("rx error! \r\n"); 
return; 
} 
/* 
* If completion interrupt is asserted, then set RxDone flag 
*/ 
if ((IrqStatus & XAXIDMA_IRQ_IOC_MASK)) { 
/* 
* 这里设计了一种环形结构的缓存处理方式 
*/ 
if(fdma_buf.circle_cnt<2) 
{ 
fdma_buf.next = fdma_buf.circle_cnt; 
fdma_buf.circle_cnt ++ ; 
} 
else 
{ 
fdma_buf.circle_cnt = 0; 
fdma_buf.next = 2; 
} 
Status = XAxiDma_SimpleTransfer(&AxiDma, (UINTPTR)RxBufAddr[fdma_buf.circle_cnt],DMA_DATA_SIZE,  
XAXIDMA_DEVICE_TO_DMA); 
if (Status != XST_SUCCESS) 
{ 
xil_printf("axi dma failed! 0 %d\r\n", Status); 
} 
fdma_buf.pkg_done=1; //fdma_buf.pkg_done 代表了当前DMA传输完毕 
} 
} 
 |  
  
2:fdma_buf_st结构体 
 typedef struct fdma_buf_st 
{ 
 
u8 record[16];//用于记录被标记的内存号 
u8 circle_cnt;//环形计数器 
u8 next;//指向下一个需要被读取的内存号 
u8 pkg_done; //代表数据已经被接收 
u16 fram_cnt; //帧计数器,记录当前的帧号,用于统计 
u16 pkg_cnt; //包计数器,为了均衡CPU负载,可以把一包大数据分成多个小数据操作 
}fdma_buf_st; 
 
 |  
  
3:FdmaAdcWave函数以下程序中,核心部分除了波形绘制部分,重点是数据的获取方式。当中断到来pkg_done=1,这里把1MB数据分成64份,这样每份代表8个通道的1024个数据采样点,把这个1024个数据点在内存中绘制波形。  int FdmaAdcWave(u32 WaveWidth, u8 *ScreenFrame) 
{ 
int i,j ; 
RxBufAddr[0] =RX_BUFFER0_BASE; /*设置第一帧起始地址 */ 
RxBufAddr[1] =RX_BUFFER1_BASE; /*设置第二帧起始地址*/ 
RxBufAddr[2] =RX_BUFFER2_BASE; /*设置第三帧起始地址*/ 
InitPen(); 
DrawGrids(WaveWidth, WAVE_HEIGHT,GridBuf) ;/* 调用绘制栅格函数,绘制栅格 */ 
while(1) { 
if(fdma_buf.pkg_done == 1) //adc=16bit*8 total_data_size=16bits*8*2048*32 divide in to 64 times as each  
time 16bits*8*1024 
{ 
fdma_buf.pkg_done =0; 
while(fdma_buf.pkg_cnt<64) //分成64份,每次取8*2*1024大小的数据。 
{ 
if(fdma_buf.pkg_cnt==0) 
{ 
BufStartPtr = (u16*)(RxBufAddr[fdma_buf.record[fdma_buf.next]]);//get new buffer address 
Xil_DCacheInvalidateRange((u32)BufStartPtr, PKG_SIZE); 
} 
else 
{ 
BufStartPtr = BufStartPtr + PKG_SIZE/2; //指针16位指针BufStartPtr,注意地址的计算方式 
Xil_DCacheInvalidateRange((u32)BufStartPtr, PKG_SIZE); 
} 
for(i = 0; i < 8 ; i++)//重新排列ADC的数据 
{ 
for(j = 0 ; j < WaveWidth ; j++) 
{ 
DBufTmp[j] = BufStartPtr[8*j + i] ; 
} 
} 
memcpy(WaveBuf, GridBuf, WAVE_HSIZE*WAVE_HEIGHT*PIXEL_BYTES) ;/* 复制之前的栅格到内存*/ 
for(i = 0; i < 8 ; i++)/* Wave Overlay */ 
{ 
DrawLine(WaveWidth, WAVE_HEIGHT, (void *)DBufTmp, WaveBuf, i); /* 在栅格背景上绘制波形*/ 
} 
/* 内存中的波形复制VDMA显存 */ 
ImgCopy(WaveWidth, WAVE_HEIGHT, WAVE_STRIDE, WAVE_OFFSET_X, WAVE_OFFSET_Y, ScreenFrame, WaveBuf) ; 
fdma_buf.pkg_cnt++; 
} 
fdma_buf.pkg_cnt = 0; 
fdma_buf.fram_cnt++; 
 
//usleep(100) ;/* sleep 100ms */ 
}  
} 
} 
 
 |  
  
6.2波形绘制原理 
1:栅格绘制函数 
#define  AD_RANGE_VAL65536 
#define PIXEL_BYTES 4 
#define SIGNED_VAL 1 
#define RANGE_DIV_PARAM 256 // AD_RANGE_VAL/RANGE_DIV_PARAM must be less than or equal to ImgHeight,Therefore, it is  
necessary to set a reasonable only 
void DrawGrids(u32 ImgWidth, u32 ImgHeight, u8 *ImgBufPtr) 
{ 
PEN pen; 
u32 pixel_x, pixel_y; 
for(pixel_y = 0; pixel_y < ImgHeight; pixel_y++) 
{ 
for(pixel_x = 0; pixel_x < ImgWidth; pixel_x++) 
{ 
if (((pixel_y == 0 || (pixel_y+1)%64 == 0) && (pixel_x == 0 || (pixel_x+1)%8 == 0)) || ((pixel_x == 0 ||  
(pixel_x+1)%64 == 0) && (pixel_y+1)%8 == 0)) 
{ 
/* gray point */ 
pen.Red = 150; 
pen.Green = 150; 
pen.Blue = 150; 
} 
else 
{ 
/* Black point*/ 
pen.Red = 0; 
pen.Green = 0; 
pen.Blue = 0; 
} 
DrawPoint(ImgBufPtr, pixel_x, pixel_y, ImgWidth,pen); 
} 
} 
} 
 |  
  
((pixel_y == 0 || (pixel_y+1)%64 == 0) && (pixel_x == 0 || (pixel_x+1)%8 == 0)),Y方向每间隔64个像素点,绘制水平线,水平虚线间距8个像素点。 (pixel_x+1)%8 == 0)) || ((pixel_x == 0 || (pixel_x+1)%64 == 0) && (pixel_y+1)%8 == 0),X方向每间隔64个像素点,绘制垂直虚线,垂直虚线间距8个像素点。 2:描点函数 
 void DrawPoint(u8 *ImgBufPtr, u32 pixel_x, u32 pixel_y, u32 ImgWidth, PEN pen) 
{ 
ImgBufPtr[(pixel_x + pixel_y*ImgWidth)*PIXEL_BYTES + 0] = pen.Red; 
ImgBufPtr[(pixel_x + pixel_y*ImgWidth)*PIXEL_BYTES + 1] = pen.Green; 
ImgBufPtr[(pixel_x + pixel_y*ImgWidth)*PIXEL_BYTES + 2] = pen.Blue; 
I 
 |   3:绘制波形函数 
 void DrawLine(u32 ImgWidth, u32 ImgHeight, void *BufPtr, u8 *ImgBufPtr,u8 color) 
{ 
u16 last_y ,curr_y ; 
u32 i,j ; 
short *PointBufPtr ;//adc one point need 16bit 
//For 16bit signed AD, the range is -32,768~+32,767 
float tmp = 1.00/RANGE_DIV_PARAM ;//tmp value use to Divide ad by YMID 
PointBufPtr = (short *)BufPtr ; 
/* 
* For signed AD, in order to display the waveform normally, 
* just re-adjust the center position to make all the data become positive. 
* */ 
//ad7606 is 16-bits signed value,set wave offset 
u16 YOFFSET = SIGNED_VAL ? ImgHeight>>1 : 0; // 
 
for(i = 0; i < ImgWidth ; i++) 
{ 
if (i == 0) 
{ 
last_y = (u16)(PointBufPtr*tmp + YOFFSET) ; 
curr_y = (u16)(PointBufPtr*tmp + YOFFSET) ; 
} 
else 
{ 
last_y = (u16)(PointBufPtr[i-1]*tmp + YOFFSET) ; 
curr_y = (u16)(PointBufPtr *tmp + YOFFSET) ; 
} 
if (curr_y >= last_y) 
{ 
for (j = 0 ; j < (curr_y - last_y + 1) ; j++) 
DrawPoint(ImgBufPtr, i, (ImgHeight - 1 - curr_y) + j, ImgWidth, pen[color]) ; 
} 
else 
{ 
for (j = 0 ; j < (last_y - curr_y + 1) ; j++) 
DrawPoint(ImgBufPtr, i, (ImgHeight - 1 - last_y) + j, ImgWidth, pen[color]) ; 
} 
} 
} 
 
 |  
  
4:复制波形到显存 
 void ImgCopy(u32 ImgWidth, u32 ImgHeight, u32 ImgStride, int OffSetX, int OffSetY, u8 *ImgFrame, u8 *ImgBufPtr) 
{ 
int i ; 
u32 ImgSrcOffset; 
u32 FramDestOffset ; 
u32 CopyLineLen = ImgWidth*PIXEL_BYTES ; 
for(i = 0 ; i < ImgHeight; i++) 
{ 
ImgSrcOffset = i*ImgWidth*PIXEL_BYTES ; 
FramDestOffset = (OffSetY+i)*ImgStride + OffSetX*PIXEL_BYTES ; 
memcpy(ImgFrame+FramDestOffset, ImgBufPtr+ImgSrcOffset, CopyLineLen) ; 
} 
Xil_DCacheFlushRange((INTPTR) ImgFrame+OffSetY*ImgStride, ImgHeight*ImgStride) ; 
} 
 |  
  
以上函数负责把绘制好再内存中的波形数据复制到VDMA的视频缓存中,其中OffSetX 和OffSetY参数用于控制绘制波形在屏幕中的位置。 
 
 
7方案演示 
7.1硬件准备 
 
 
7.2实验结果 
 
 
 
 
 
 
 
 
 
 |