当前位置: 首页 > news >正文

基于FPGA实现ADC366X系列芯片配置及数据采集

简介:本文详细讲解如何使用FPGA通过SPI对ADC366X系列芯片进行配置,借助Xilinx的VIO(Virtual Input/Output)快速实现代码的调试,为读者们分享SPI接口逻辑实现以及ADC366X系列在使用基本功能过程中需要注意的地方。通过本文的内容分享,希望读者可以从中有所得,掌握FPGA与ADC之间的通信机制,熟悉VIO在实际调试过程中的应用,并具备在实际项目中进行配置与数据采集的能力,不再为ADC应用而发愁。

1 SPI通信协议基础原理

SPI(Serial Peripheral Interface)是一种高速、全双工、同步串行通信协议,大多数情况下都是通过四根信号线实现工作:
SCLK(Serial Clock):由主设备生成的时钟信号,用于同步数据传输;
MOSI(Master Out Slave In):主设备发送数据到从设备的通道;
MISO(Master In Slave Out):从设备返回数据到主设备的通道;
CS/SS(Chip Select/Slave Select):选择当前通信的从设备,低电平有效。
从它的接口定义我们可以看出SPI是采用主从架构,通信由主设备发起,数据在SCLK的上升沿或下降沿进行采样与输出,具体时序依赖于时钟极性(CPOL)与时钟相位(CPHA)的设置,这也构成了SPI四种不同的时钟模式。那么其实我们对于SPI代码的实现也就是围绕这四根线时序进行开发,在不同ADC配置场景中只需根据具体芯片的寄存器配置时序调整即可。

博主在整理SPI原理相关内容时反复修改总结总觉得差点意思,最后发现一篇总结十分详细的高质量文章在文章末尾分享给大家。


下面就让我们看一看SPI在配置ADC366X系列芯片时是怎么应用的吧。

2 ADC366X系列

2.1 基本概述

ADC366x系列是低噪声、超低功耗、16位分辨率、双通道模数转换器,采样率覆盖0.5MSPS~65MSPS,适合对功耗、噪声、延迟要求严苛的工业、通信、测试测量类应用。
型号差异

2.2 核心特性

2.2.1 性能参数

延迟:
极低延迟,1线SLVDS接口下仅1个采样时钟周期延迟,2线接口下2个周期,适合高速控制环路应用。
精度指标:
无丢码,16位全精度输出
积分非线性(INL):±3LSB(典型值),±5LSB(最大值)
微分非线性(DNL):±0.7LSB(典型值),±0.85LSB(最大值)
输入全量程:3.2Vpp差分,输入共模电压0.95V,输入带宽900MHz(-3dB),支持中频采样。

频谱性能(典型值,f_IN=10MHz):
信噪比(SNR):81.9dBFS
含二次/三次谐波的无杂散动态范围(SFDR):92dBc
排除二次/三次谐波的最差毛刺SFDR:99dBFS
有效位数(ENOB):13.3位

2.2.2 功能特性

a 基准源选项

支持三种基准配置:

  1. 外部1.6V基准直接输入(最高精度、最低温漂)
  2. 外部1.2V基准+内部缓冲增益至1.6V
  3. 内部1.2V基准+内部增益至1.6V(默认上电配置)
b 片上数字滤波器(可选)

● 支持2/4/8/16/32倍抽取,分为实抽取(低通滤波,输出带宽为抽取率的40%)和复抽取(带32位NCO混频,输出带宽为抽取率的80%)
● 抽取滤波器阻带抑制≥85dB,内部计算精度20位,避免量化噪声损失
● 复抽取支持6dB数字增益补偿混频损耗,实抽取支持3dB增益补偿
● 可选FS/4混频功能,将复抽取输出转换为实输出,信号中心频率位于Fout/4

c 数字接口

串行LVDS(SLVDS)接口,支持三种模式:

● 支持输出位宽灵活配置:14/16/18/20位,通过输出位映射器(Bit Mapper)可自定义输出位序
● 可选输出加扰功能,减少数据跳变带来的EMI
● 输出格式支持默认二进制补码/偏移二进制可选

d 其他功能

● 自动调零(Auto-Zero)前端放大器,改善1/f闪烁噪声,ADC3661/2默认开启,ADC3663可通过SPI开启
● 双通道数字平均功能:两通道输入相同信号时,内部平均可降低3dB不相关噪声,提升动态范围
● 单端/差分模拟输入、单端/差分时钟输入可选,单端时钟可额外节省~1mA模拟电流
● 测试模式:支持斜坡(RAMP)测试图案、自定义常量测试图案,方便数字接口调试
● 同步(SYNC)功能:可通过PDN/SYNC引脚或SPI触发,同步多片ADC的抽取滤波器时钟分频器、NCO相位,保证多器件相位对齐

ADC366X系列芯片可配置选项较多,感兴趣的小伙伴可以自行查看官方芯片手册。我们在实际使用中仅考虑基本的功能使用

链接: ADC366X芯片手册

2.2.3 引脚特性

2.3 使用说明

2.3.1 典型电路

这里不便直接放出博主自己的原理图,我们参考官方手册提供的典型应用电路即可实现。

唯一区别就是我们在实际使用中只是将control部分的管脚接到了FPGA端来实现配置ADC驱动。
结合2.2.3小节以及上图可知,ADC366X系列的SPI控制管脚中的读写数据通道复用了一根数据线。这对于我们MISO和MOSI逻辑的开发来讲区别不大,只需要注意控制读写方向以及三态门的使用。

2.3.2 寄存器配置时序

任何芯片上电后都会有自己的默认配置,ADC366X系列也不例外。

但在实际使用过程中,我们往往不仅仅局限于某一特定功能,所以需要能够灵活运用并学会自己配置芯片的寄存器。
写操作:SEN拉低,A15位写0,接着送12位寄存器地址+8位数据,每24个SCLK上升沿锁存一次数据。

读操作:SEN拉低,A15位写1,送12位寄存器地址,随后器件在SCLK下降沿从SDIO输出8位寄存器数据,控制器在SCLK上升沿采样。

对SCLK的时序要求:

因此后续我们代码为了省事并未严格按照50%占空比来分频,只需要满足时序要求即可。

2.3.3 配置流程

手册中给出了一种以16位、单线、8倍复抽取方式的配置流程,我们可作为参考,但具体配置流程还得以我们实际使用为准。

每一个寄存器对应的具体含义感兴趣的可以参考芯片手册8.6小节
我们将在第三节实现过程中使用ADC3662和ADC3663双线16bit工作模式。

3 FPGA实现ADC366X数据采集

3.1 模块划分

我们需要实现的功能包括adc366x系列工作模式的配置、数据的转换和采集(此处我们对于采集后的数据去向不做赘述,主要在于驱动配置以及数据的采集)

和ADC366x数据交互以及时钟相关信号均为差分信号。
我们此处要特别注意:
●管脚属性的约束
●差分转单端
●终端电阻的选择

3.2 代码实现

这一小节我们主要描述代码的具体实现。在2.3.2小节我们已经对与ADC366X系列的读写寄存器时序有详细的了解,我们将根据手册时序进行代码的逻辑实现。

3.2.1 adc366x_drv

主要是对与ADC366X芯片的控制驱动配置相关逻辑。
接口

module adc366x_drv #(REG_ADD_WID=12,//register addrsee widthREG_BIT_NUM=8//data width)(input clk_i,//40MHzinput rstn,input w_cfg_start,input[REG_BIT_NUM-1:0]w_cfg_data,input r_cfg_start,input[REG_ADD_WID-1:0]reg_addr,output adc366X_sclk_o,inout adc366X_sdio_io,output adc366X_sen_o,output adc366X_miso);

配置读写时序实现:(注意三态门的控制逻辑)

/////////////sen//////////////always @(posedge clk_i or negedge rstn)beginif(!rstn)begin r_adc366x_sen<=1'b1;endelseif((r_bit_cnt==24)&&(r_div_shift[0]==1'b1))begin r_adc366x_sen<=1'b1;endelseif((w_cfg_start_neg==1'b1) || (r_cfg_start_neg == 1'b1))begin r_adc366x_sen<=1'b0;endelsebegin r_adc366x_sen<=r_adc366x_sen;end end assign adc366X_sen_o=r_adc366x_sen;
/////////////sclk/////////////////////////将40M时钟进行4分频////////always @(posedge clk_i or negedge rstn)beginif(!rstn)begin r_div_shift<=4'b0001;endelsebegin r_div_shift<={r_div_shift[2:0],r_div_shift[3]};end end always @(posedge clk_i or negedge rstn)beginif(!rstn)begin r_sclk<=1'b0;endelseif((r_adc366x_sen==1'b0) && (r_div_shift[1] == 1'b1))begin r_sclk<=1'b1;endelsebegin r_sclk<=1'b0;end end assign adc366X_sclk_o=r_sclk;
/////////////sdio//////////////////读写的主要差别就在于SDIO的最高位,以及需要注意SDIO管脚的IN和OUT方向的区分//////always @(posedge clk_i or negedge rstn)beginif(!rstn)begin r_bit_cnt<=5'd0;endelseif(!r_adc366x_sen)beginif(r_div_shift[1]==1'b1)begin r_bit_cnt<=r_bit_cnt+1'b1;endelsebegin r_bit_cnt<=r_bit_cnt;end endelsebegin r_bit_cnt<=5'd0;end end assign once_cfg_end=(r_bit_cnt==5'd24) && (r_div_shift[1] == 1'b1);assign rd_cfg_en=(r_bit_cnt>=5'd16) && (rw_cfg_flag == 1'b1);always @(posedge clk_i or negedge rstn)beginif(!rstn)begin rw_cfg_flag<=1'b0;endelseif(w_cfg_start_pos==1'b1)begin rw_cfg_flag<=1'b0;endelseif(r_cfg_start_pos==1'b1)begin rw_cfg_flag<=1'b1;endelseif(once_cfg_end==1'b1)begin rw_cfg_flag<=1'b0;end end always @(posedge clk_i or negedge rstn)beginif(!rstn)begin r_sda<={4'b0000,12'h007,8'h4b};endelseif(r_adc366x_sen==1'b0)beginif((r_div_shift[0]==1'b1) && (r_bit_cnt > 5'd0))begin r_sda<={r_sda[22:0],1'b0};endelsebegin r_sda<=r_sda;end endelsebegin r_sda<={rw_cfg_flag,3'b000,reg_addr,w_cfg_data};end end assign adc366X_sdio_io=(rd_cfg_en==1'b0) ? r_sda[23] : 1'bz;assign adc366X_miso=adc366X_sdio_io;

3.2.2 data_rcv

该模块主要是实现与ADC366X之间的数据采集并提供外部需要时钟。
接口

moduleadc3663_152v2_drive_u13(input high_clk_i,// 160Mhzinput rstn_i,input da0_p_i,input da0_n_i,input da1_p_i,input da1_n_i,input db0_p_i,input db0_n_i,input db1_p_i,input db1_n_i,input fclk_p_i,input fclk_n_i,input dclk_p_i,input dclk_n_i,output dclkin_p_o,// default 40Mhzoutput dclkin_n_o,output clk_p_o,output clk_n_o,// default 10Mhzoutput[15:0]adc_cha_tdata_o,output[15:0]adc_chb_tdata_o,output adc_data_tvalid_o);

由于ADC366X芯片工作出了正常的供电以及配置寄存器之外还需要外部提供采样时钟CLKP/M、外部串行LVDS输入时钟DCLKINP/M。

对于差分信号的处理我们直接使用I/OBUFDS原语来进行处理。
对于I/OBUFDS原语我们可以参考vivado提供的用法,在tools的Language Template打开直接搜索相关原语即可

//--------- output dclkin --------------------------always @(posedge high_clk_i or negedge rstn_i)beginif(rstn_i==1'b0)begin r_dclkin<=1'd0;endelseif((r_div_shfit[1])||(r_div_shfit[3])||(r_div_shfit[5])||(r_div_shfit[7]))begin r_dclkin<=~r_dclkin;endelsebegin r_dclkin<=r_dclkin;end end OBUFDS #(.IOSTANDARD("DEFAULT"),// Specify the output I/O standard.SLEW("FAST")// Specify the output slew rate)OBUFDS_dclkin(.O(dclkin_p_o),// Diff_p output (connect directly to top-level port).OB(dclkin_n_o),// Diff_n output (connect directly to top-level port).I(r_dclkin)// Buffer input);//-------- output clk -------------------------------------always @(posedge high_clk_i or negedge rstn_i)beginif(rstn_i==1'b0)begin r_clk<=1'd0;endelseif(r_div_shfit[7])begin r_clk<=~r_clk;endelsebegin r_clk<=r_clk;end end OBUFDS #(.IOSTANDARD("DEFAULT"),// Specify the output I/O standard.SLEW("FAST")// Specify the output slew rate)OBUFDS_clk(.O(clk_p_o),// Diff_p output (connect directly to top-level port).OB(clk_n_o),// Diff_n output (connect directly to top-level port).I(r_clk)// Buffer input);

我们实际调试过程中使用的是芯片的2-wire模式,因此数据的解析和采集也是根据手册2-wire模式来处理。


差分信号的转换

//------- da0 -------------------------------------------------------IBUFDS #(.DIFF_TERM("TRUE"),// Differential Termination.IBUF_LOW_PWR("FALSE"),// Low power="TRUE", Highest performance="FALSE".IOSTANDARD("LVDS")// Specify the input I/O standard)u1_IBUFDS_da0(.O(w_da0),// Buffer output.I(da0_p_i),// Diff_p buffer input (connect directly to top-level port).IB(da0_n_i)// Diff_n buffer input (connect directly to top-level port));//------- da1 -------------------------------------------------------IBUFDS #(.DIFF_TERM("TRUE"),// Differential Termination.IBUF_LOW_PWR("FALSE"),// Low power="TRUE", Highest performance="FALSE".IOSTANDARD("LVDS")// Specify the input I/O standard)u1_IBUFDS_da1(.O(w_da1),// Buffer output.I(da1_p_i),// Diff_p buffer input (connect directly to top-level port).IB(da1_n_i)// Diff_n buffer input (connect directly to top-level port));//------- db0 -------------------------------------------------------IBUFDS #(.DIFF_TERM("TRUE"),// Differential Termination.IBUF_LOW_PWR("FALSE"),// Low power="TRUE", Highest performance="FALSE".IOSTANDARD("LVDS")// Specify the input I/O standard)u1_IBUFDS_db0(.O(w_db0),// Buffer output.I(db0_p_i),// Diff_p buffer input (connect directly to top-level port).IB(db0_n_i)// Diff_n buffer input (connect directly to top-level port));//------- db1 -------------------------------------------------------IBUFDS #(.DIFF_TERM("TRUE"),// Differential Termination.IBUF_LOW_PWR("FALSE"),// Low power="TRUE", Highest performance="FALSE".IOSTANDARD("LVDS")// Specify the input I/O standard)u1_IBUFDS_db1(.O(w_db1),// Buffer output.I(db1_p_i),// Diff_p buffer input (connect directly to top-level port).IB(db1_n_i)// Diff_n buffer input (connect directly to top-level port));

由于从芯片给FPGA的信号和FPGA内部不属于同一时钟域,因此需要在FPGA内部使用一个高速的时钟(满足采样定理)进行跨时钟域处理。

//-------- delay 2clk -------------------------------------always @(posedge high_clk_i or negedge rstn_i)beginif(rstn_i==1'b0)begin r_da0_r1<=1'd0;r_da0_r2<=1'd0;r_da1_r1<=1'd0;r_da1_r2<=1'd0;r_db0_r1<=1'd0;r_db0_r2<=1'd0;r_db1_r1<=1'd0;r_db1_r2<=1'd0;endelsebegin r_da0_r1<=w_da0;r_da0_r2<=r_da0_r1;r_da1_r1<=w_da1;r_da1_r2<=r_da1_r1;r_db0_r1<=w_db0_inv;// w_db0r_db0_r2<=r_db0_r1;r_db1_r1<=w_db1;r_db1_r2<=r_db1_r1;end end

数据转换

//-------- r_cha/b_tdata -------------------------------------always @(posedge high_clk_i or negedge rstn_i)beginif(rstn_i==1'b0)begin r_cha_tdata<=16'd0;r_chb_tdata<=16'd0;endelseif((r_div_shfit==8'h01) || (r_div_shfit == 8'h04)||(r_div_shfit==8'h10) || (r_div_shfit == 8'h40))begin r_cha_tdata<={r_cha_tdata[13:0],r_da1_r2,r_da0_r2};r_chb_tdata<={r_chb_tdata[13:0],r_db1_r2,r_db0_r2};endelsebegin r_cha_tdata<=r_cha_tdata;r_chb_tdata<=r_chb_tdata;end end assign adc_cha_tdata_o=r_cha_tdata;assign adc_chb_tdata_o=r_chb_tdata;
/////////////data_valid/////////always @(posedge high_clk_i or negedge rstn_i)beginif(rstn_i==1'b0)begin r_fclk_r1<=1'd0;r_fclk_r2<=1'd0;r_fclk_r3<=1'd0;endelsebegin r_fclk_r1<=w_fclk;// w_fclkr_fclk_r2<=r_fclk_r1;r_fclk_r3<=r_fclk_r2;end end assign w_fclk_pos=r_fclk_r2&&(!r_fclk_r3);assign w_fclk_neg=(!r_fclk_r2)&&r_fclk_r3;assign w_tvalid=w_fclk_pos|w_fclk_neg;

3.2.3 配置流程

参考2.3.2小节,对于ADC3663我们上电工作后默认配置即可实现2-wire 16bit工作模式,对于ADC3662我们则需要对其进行配置(即给对应寄存器写配置数据)

此处的代码较为简单,我们可以按照上述配置流程自己尝试着实现部分逻辑。按照上述配置流程实现adc366x_drv模块接口的w_cfg_start、w_cfg_data、reg_addr信号逻辑,确保正常上电之后开始配置ADC即可。本文验证阶段主要想让大家能了解VIO IP的使用。

ps:感兴趣想要完整工程或有疑惑的地方没看懂可以联系博主沟通交流学习

3.2.4 时钟及复位

我们此处以Xilinx的PLL IP为例。

外部晶振差分输入时钟200MHz。

这里需要特别注意输入信号source的选择。

输出160MHz作为数据采集模块主时钟,40MHz作为SPI配置模块的输入时钟,lock信号标志是否失锁,也用做上述两个模块的复位。

3.3 测试与验证

我们在测试验证环接为了能更快速的验证逻辑的准确性,使用ILA+VIO的方式来验证ADC366X是否正常工作。

3.3.1 VIO

VIO(Virtual Input/Output)是一款可定制化的内核,能够实时监控和驱动FPGA(现场可编程门阵列)内部信号。其输入和输出端口的数量及位宽均可根据需求定制,以便与FPGA设计进行接口对接。
新建IP首先确定IP名,然后第一个界面我们需要确定我们需要用到的端口数量,我们验证过程中将寄存器的地址和数值以及读和写的指令通过VIO来控制因此我们需要4个OUTPUT接口。

根据实际情况我们确定每个接口的位宽以及初值。

我们打开debug界面将VIO参数添加进界面,设置好我们需要发送的地址和数据,点击写或读使能。

3.3.2 时序验证

使用在线调试,设置好触发条件,抓取我们所需观察的信号线。

a 写寄存器时序

写寄存器地址07,值为4B。

b 读寄存器时序

读寄存器地址07的值:

c 功能验证

我们为了更便于查看芯片是否配置正确并工作正常,选择使用ADC366X芯片的测试模式。
测试模式配置主要涉及0x14、0x15、0x16三个寄存器。


我们这里将测试模式配置为16bit、递增数加1的工作模式。
因此需要将0x14寄存器配置为0x04;0x15寄存器配置为0x00;0x16寄存器配置为0x48。

可以看出在数据有效时,从ADC366X芯片采集到的数据是一个加1的递增数。

4 总结

从3.2节SPI主从数据传输时序的实现可以看出SPI通信的灵活性很大程度上取决于其时钟模式的选择,其中CPOL(Clock Polarity)和CPHA(Clock Phase)的组合决定了数据在时钟边沿的采样和发送时机。这也构成了SPI的不同工作模式。
我们如果接触到的ADC芯片比较多的话就可以发现,其实有一大部分芯片的驱动都是可以通过SPI方式来实现配置。只要我们认真吸收上面所讲的原理和逻辑就能举一反三轻松拿下SPI接口的应用。


SPI基本原理参考链接: FPGA通过SPI实现ADC配置技术详解

http://www.jsqmd.com/news/868657/

相关文章:

  • 终极指南:快速掌握Vue 3树形结构组件的完整使用技巧
  • Paper2Poster深度解析:多智能体架构如何重塑学术海报生成范式
  • 【电池】插电式混合动力汽车PHEVs性能的模拟【含Matlab源码 15452期】
  • 你的 FlashAttention 真的在跑吗?几个简单方法确认
  • Linux库制作与使用(二):ELF文件与链接过程
  • 2026年靠谱的温州卡包批量定做公司哪家好 - 品牌宣传支持者
  • Android动态换肤终极指南:5分钟掌握零入侵皮肤切换框架
  • 快速复习C语言
  • 【飞机】数据驱动的多传感器飞机健康监测系统【含Matlab源码 15551期】
  • 3大实战技巧:使用mootdx高效获取与处理通达信财务数据
  • 老木匠、临界质量与Log曲线——一个46岁架构师的AI生存哲学
  • 2026聚氨酯砂浆生产厂家哪家好?聚氨酯砂浆定制厂家技术全解析 - 栗子测评
  • ascend-transformer-boost (ATB) - Transformer推理加速实战
  • JDK6→JDK7→JDK8 重点技术更新(精简背诵版)
  • 【仅限首批200名开发者】Gemini多模态搜索性能诊断工具包(含Latency Heatmap生成器+跨模态Embedding可视化插件)
  • TranslucentTB:重构Windows任务栏视觉体验的技术架构深度解析
  • 陈,跳台记录仪 大鼠跳台记录仪 小鼠跳台记录仪
  • 安装docker和显卡支持
  • 【图像重建】交替方向乘子法ADMM深度图重建三维重建【含Matlab源码 15543期】
  • java学习笔记(3)
  • PHP 的 resource(如数据库连接、文件句柄)不能被序列化。
  • 【Linux】Socket编程UDP
  • 如何快速安装TrollStore:iOS 14-16.6.1设备一键安装的终极指南
  • 水性聚氨酯砂浆厂家推荐:2026水性聚氨酯砂浆定制供应商口碑实力推荐 - 栗子测评
  • 设计模式系列文章(基础篇第 1 篇):初识设计模式——从重复踩坑到优雅编码
  • 从Python到微调:6个月小白也能掌握的大模型应用开发路线图(收藏版)
  • 6G时代下的语义通信:重塑信息交互的未来图景
  • 29个月未修!Google意外泄露Chromium永久驻留漏洞:浏览器秒变JS僵尸网络
  • MySQL 部门表:树结构 (自关联) vs 非树结构 (扁平化 / 冗余字段)
  • 二叉搜索树(BST)详解