本帖最后由 FPGA课程 于 2024-10-9 17:15 编辑
软件版本: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概述DAQ7606是一款8通道16bit 200k采样率的高精度ADC,支持串行和并行采集接口。米联客DAQ7606采用串行模式实现200K 8通道同时采样,相比并口方式,串行方式,具有硬件接口简单,节约成本优势。 实验目的: 1:掌握uifdmadbuf配置成非视频模式的情况下的参数设置 2:掌握ADC数据如何通过uifdmadbuf写入到uifdma ip 3:ps实现fdma中断到来,读取DDR缓存中的数据 4:编写波形显示函数,把采集到的波形绘制成波形曲线在显示器上显示 2系统框图
3硬件电路分析硬件接口和子卡模块请阅读“附录1” 配套工程的FPGA PIN脚定义路径为soc_prj/uisrc/04_pin/ fpga_pin.xdc。 4搭建SOC系统工程
4.1PL图形化编程
以上代码中用户数据位宽为128bit(DAQ7606是8通道16bit 所以是128bit位宽), 用户写入的数据经过uifdma_dbuf后进入fdma,之后通过AXI_interconnect 进入到ZYNQ DDR中。 SDK代码中,PS(ARM)读取内存中保存的ADC采样数值,并且通过插值方式绘制波形,波形先绘制到内中,VDMA从这个开辟的波形显存中主动读取数据,显示到显示器上。这样就完成了波形采集显示方案。 下面具体看下关键几个IP的参数设置 1:uifdma_dbuf设置由于输入数据是数据流形式,所以不需要使用视频传输功能,这里也只使用到了写通道,所以读通道也不需要使能。 由于采用DAQ7606的速度相对比较忙,设置3帧缓存就够用。WBaseaddr缓存的基地址只要设置合适的值即可,这里设置0x08000000 = 128MB,这样保留了低128MB给应用程序使用。 WDsizebits设置每个缓存的大小,2^20次方=1MBYTE。 所以下面的参数XSize*YSize*W_Datawidth/8=1MB.其中已知AXI_DATA_WIDTH=128,所以只要正确设置XSize和YSize。通常来说设置越大的XSize传输效率也高,但是需要消耗的资源也会更多。我们这里设置XSize=2048,Ysize设置32代表。2048*32*128/8=1MB
2:uiFDMA设置 Fdma的数据位宽可以设置128这样效率最高。
3:AXI Interconnect设置 设置FIFO可以增加数据的吞吐能力
4:修改system_wrapper.v将自动产生的system_wrapper.v复制到本方案工程路径soc_prj/uisrc/01_rtl/system_wrapper.v并对其修改,修改好的代码如下: - /*******************************MILIANKE*******************************
- *Company : MiLianKe Electronic Technology Co., Ltd.
- *WebSite:https://www.milianke.com
- *TechWeb:https://www.uisrc.com
- *tmall-shop:https://milianke.tmall.com
- *jd-shop:https://milianke.jd.com
- *taobao-shop1: https://milianke.taobao.com
- *Create Date: 2021/10/15
- *Module Name:system_wrapper
- *File Name:system_wrapper.v
- *Description:
- *The reference demo provided by Milianke is only used for learning.
- *We cannot ensure that the demo itself is free of bugs, so users
- *should be responsible for the technical problems and consequences
- *caused by the use of their own products.
- *Copyright: Copyright (c) MiLianKe
- *All rights reserved.
- *Revision: 1.0
- *Signal description
- *1) _i input
- *2) _o output
- *3) _n activ low
- *4) _dg debug signal
- *5) _r delay or register
- *6) _s state mechine
- *********************************************************************/
- `timescale 1 ps / 1 ps
- 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,
- //******************************
- 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 pl_clk;
- wire user_rstn;
- wire user_start;
- wire [127:0]ud_wdata_0;
- wire ud_wde_0;
- wire ud_wclk_0;
- reg [15:0]test_data;
- always@(posedge pl_clk)begin
- if(user_rstn == 1'b0)begin
- test_data <= 12'd0;
- end
- else if(ud_wde_0) begin
- test_data <= test_data + 1'b1;
- end
- end
- 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;
- 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];
- assign ud_wdata_0 = {test_data,ad_ch6,ad_ch5,ad_ch4,ad_ch3,ad_ch2,ad_ch1,ad_ch0};
- assign ud_wde_0 = user_start & ad7606_cap_en;
- assign ud_wclk_0 = pl_clk;
- uispi7606#(
- .SPI_DIV(10'd5),
- .T5US_DIV(10'd499)
- )
- uispi7606_inst
- (
- .ad_clk_i(pl_clk),
- .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 ila0_dg
- (
- .clk(pl_clk),
- .probe0({test_data[15:0],ad_ch0,ud_wde_0,user_start,user_rstn})
- );
- 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),
- .pl_clk(pl_clk),
- .ud_wclk_0(ud_wclk_0),
- .ud_wdata_0(ud_wdata_0),
- .ud_wde_0(ud_wde_0),
- .user_rstn(user_rstn),
- .user_start(user_start)
- );
- endmodule
复制代码
以上代码中,调用了米联客uispi7606 IP CORE采集模拟数据,并且把采集好的数据写入到uifdmadbuf中,为了方便实验中观察数据,把第8个通道的AD数据改成了计数器。实际项目中可以把这个替换成第八个通道的ADC数据。 4.2设置地址分配需要注意uifdma_dbuf的axi-lite接口地址,这个地址我们会在SDK 代码中用到读寄存器。
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_fdma_wave工程
6程序分析
6.1FDMA数据获取原理
每当uifdmadbuf发送的中断后,该函数被调用,通过读取uifdmadbuf axi-lite的寄存器,获取当前哪一个缓存产生了中断(代表数据发送到PS DDR了)。 为了增加数据的吞吐能力,在中断中,不宜进行数据搬运,我们设计了要给结构体,可以用于标记已经写入DDR的数据。 1:PS_RX_intr_Handler
- void PS_RX_intr_Handler(void *param)
- {
- fdma_buf.record[fdma_buf.circle_cnt]= Xil_In32((UINTPTR)FDMA_DBUF_BASE_ADDR); //读取uifdmabuf控制寄存器中标记的内存号
- if(fdma_buf.circle_cnt<2) //3帧缓存环形FIFO
- {
- fdma_buf.circle_cnt ++ ;
- fdma_buf.next = fdma_buf.circle_cnt -1; //读DDR缓存地址延迟当前1帧
- }
- else
- {
- fdma_buf.circle_cnt = 0;
- fdma_buf.next = 2; //读DDR缓存地址延迟当前1帧
- }
- fdma_buf.pkg_done=1; //中断产生,缓存中有数据可以获取
- }
复制代码
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[i][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[i], 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:栅格绘制函数- 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;
- ImgBufPtr[(pixel_x + pixel_y*ImgWidth)*PIXEL_BYTES + 3] = 0xff;
- }
复制代码
3:绘制波形函数
- void DrawLine(u32 ImgWidth, u32 ImgHeight, void *BufPtr, u8 *ImgBufPtr,u8 color)
- {
- u8 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/256 ;//tmp value use to Divide ad by 256
- 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.
- * For example, if it is an 8-bits signed number, then mid_offset=64 for a 16-bits signed number mid_offset=128
- * */
- u8 ymid_offset = 128;//ad7606 is 16-bits signed value
- for(i = 0; i < ImgWidth ; i++)
- {
- if (i == 0)
- {
- last_y = (u8)((u16)(PointBufPtr[i])*tmp + ymid_offset) ;
- curr_y = (u8)((u16)(PointBufPtr[i])*tmp + ymid_offset) ;
- }
- else
- {
- last_y = (u8)((u16)(PointBufPtr[i-1])*tmp + ymid_offset) ;
- curr_y = (u8)((u16)(PointBufPtr[i] )*tmp + ymid_offset) ;
- }
- 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) ;
- }
复制代码
7方案演示
7.1硬件准备
7.2实验结果
|