LTC2308 是将模拟信号转换成数字信号的器件,称为模数转换器(简称A/D转换器或ADC,Analog to Digital Converter),是亚德诺半导体公司( Analog Devices Inc,ADI) 的一款低噪声 12 位高精度逐次逼近型模数转换芯片,最多可拥有 8 个模拟输入通道,具有高达 500 kSPS 的采样速率,支持串行外设接口( Serial Peripheral Interface,SPI) 。

一、LTC2308的功能框图及引脚定义

 

LTC2308的功能框图:

 

 

 引脚定义(来源参考 LTC2308 datasheet LTC2308fb.pdf 的第7页):

信号名称 定义
CH0-CH7 8个ADC通道输入端
COM

参考点。

对于单极转换,它必须连接到GND,对于双极转换,它必须连接到GND和REFCOMP之间。

REFCOMP   参考Buffer输出。通过10uF和0.1uF旁路到GND时,额定输出电压为4.096V
 CONVST  该信号上升沿的到来可触发转换
SCK 串行传输时钟
 SDI  串行数据输入
 SDO  串行数据输出

二、LTC2308的配置信号定义

先参考LTC2308 datasheet LTC2308fb.pdf 的第10页:

 

 

也就是说LTC2308的各种工作模式是可通过6位DIN字可编程设定的。SDI数据位加载在SCK的上升沿上,S/D bit 加载在第一个上升沿上,SLP bit 加载在第六个上升沿上。6位DIN字定义如下:

 

表达式
描述
S/D
SIGNAL-sENDED / DIFFERENTIAL BIT
为1选择单端输入
为0选择差分输入
O/S
ODD / SIGN BIT
为1选择奇数通道
为0选择偶数通道
S1
ADDRESS SELECT BIT 1
地址选择位1
S0
ADDRESS SELECT BIT 0
地址选择位0
UNI
UNIPOLAR / BIPOLAR BIT
为1选择单极性
为0选择双极性
SLP
SLEEP MODE BIT
睡眠模式选项,以便在非活动期间节省电力

 继续参考参考LTC2308 datasheet LTC2308fb.pdf 的第10页的Table1可得到通道选择的配置字:

所以 adc_ltc2308.v 模块里面的配置代码如下:

三、通道的选择

先看通道的硬件配置(设置通道的极性)。

DE10-Nano_v.1.3.8_HWrevC_SystemCD\Schematic\de10-nano.pdf 电路上COM引脚 是直接接地了。所以板子硬件上已经固定为单极性。

 

 

 

DE10-Nano硬件上用3个开关SW[2:0]控制着通道选择。

DE10_NANO_QSYS模块里面添加了PIO IP(名称为SW组件,输入宽度设定为10bit),该IP连接到DE10-Nano的开关SW上。

0

参考DE10-Nano_v.1.3.8_HWrevC_SystemCD\Demonstrations\FPGA\ADC\software\DE10_NANO_ADC\main.c,因为SW组件设置的位宽是10-bit,所以读SW寄存器的值先和0x07相与,也就是取低三位的SW[2:0]的值写到寄存器0:

0从adc_ltc2308_fifo.v模块的寄存器设计来看,寄存器0是选择通道数的,寄存器1是寄存数据有多少个(nReadNum ):
0
这里的三行代码对应前面的CONVST信号,有一个20ns的脉冲:
 
IOWR(ADC_LTC2308_BASE, 0x00, (ch << 1) | 0x00);
IOWR(ADC_LTC2308_BASE, 0x00, (ch << 1) | 0x01);
IOWR(ADC_LTC2308_BASE, 0x00, (ch << 1) | 0x00);

 

假如SW[2:0]=001,左移一位,变成0010,然后和0x00相或,还是0010;
第0位作为复位信号measure_fifo_start,前面的001作为通道数measure_fifo_ch。
0

 

四、LTC2308的SPI协议时序理解

参考LTC2308fb.pdf第17页, 该器件SPI协议时序图有两个。

第一个:

第二个:

 

 

DE10-Nano_v.1.3.8_HWrevC_SystemCD\Demonstrations\FPGA\ADC 这个案例里面我们使用的是第二个时序图,原因是为了LTC2308 表现的性能更好,尽量选择:
1. 在转换开始之后的40ns内返回低电平
2. 在转换结束之后返回低电平
 
关于这个的描述请参考LTC2308fb.pdf第14页:

 所以实验里面果断选择第二个时序图。

第二个时序图中的各个时间定义(参考LTC2308fb.pdf的第5页):

 

把上面的表格摘出并翻译如下:

符号
描述
单位
tWHCONV
CONVST高电平的时间
MIN=20
ns
tHCONVST
从最后一个SCK下降沿开始,CONVST保持低的时间
MIN=20
ns
tCONV
转换时间
TYP=1.3、MAX=1.6
us
tACQ
数据采集的时间
第七个SCK上升沿和CONVST上升沿之间的时间
MIN=240
ns
tCYC
总周期时间
TYP=2
us

 

关于时序的理解请参考LTC2308的这段话描述:

 

对应译文:第二个时序图显示了在转换时间结束前,CONVST信号回到低电平。在此模式下,ADC和所有内部电路保持通电状态。当转换完成后,SDO的输出数据序列的MSB将在数据总线被启用后有效。在CONVST上升沿后1.3μs之后,随着脉冲SCK的节奏,SDO将从LTC2308中输出数据,SDI则将配置数据加载到LTC2308中。第一个SCK上升沿将S/D位加载到LTC2308中。SDO在每个SCK脉冲的下降沿输出数据。

 

总结一句话:送出convrt trigger后,经过tCONV 时间后才能送出SPI clock, 通过SDO 接收ADC结果,并同时通过SDI 设定下次convert的参数,SPI clock结束后,至少要等tHCONVST 时间,才能送出下一个convrt trigger。

 

六、如何设定采样率100Ksps?

通过LTC2308数据手册我们知道最高采样率是500ksps, SCK最高能达到40MHz(40MHz的时间周期是25ns)。DE10-Nano开发板手册写道如果想设置采样率100Ksps(不超过500Ksps就行)只需设置tHCONVST 为 320(fsample = 100KHz)即可。为什么设置tHCONVST 为320呢?

 通过LTC2308数据手册我们知道 tCONV  的典型值是1.3 us,最大值是 1.6 us,那么1.6us/25ns(假设时钟是40MHz)= 64, 也就是tCONV  最多占用64个时钟周期。而整个采样过程要占用 400 个周期( 10us(100Ksps)/   25ns(40MHz)=400)。

 

根据时序图和代码来看,64+12+320=396大约是400 。LTC2308的数据手册提到 如果驱动电路的源阻抗低,则可以直接驱动ADC输入。否则,对于阻抗较高的源,应该允许更多的采集时间。所以这里时间多预留一点可以, 少则不行。

 

五、DE10-Nano的ADC案例的代码解读

 

下图是DE10-Nano_v.1.3.8_HWrevC_SystemCD\Demonstrations\FPGA\ADC案例的模块框图:

(1)解读adc_ltc2308.v

adc_ltc2308.v的模块框图如下:

 

 信号列表如下:

信号  方向 位宽 功能
clk 输入 1 模块时钟,设置40MHz(由DE10_Standard_QSYS.qsys里面PLL IP 产生)
measure_start  输入 1  转换和采集的触发信号
measure_ch  输入 3 采集通道的选择 
measure_done  输出 1  一次采集完成标志,1表示一次采集完成
measure_dataread  输出 12  采集的数据经缓存后输出
ADC_CONVST  输出 1  标记出 measure_start 信号高电平
ADC_SCK 输出 1 LTC2308的SCK时钟
ADC_SDI 输出 6 LTC2308的SDI控制字
ADC_SDO 输入 12 采集的数据

 对LTC2308的一次完整转换和采集过程所需的时钟周期数进行数节拍:

 检测触发信号measure_start的上升沿,一旦检测到该信号的上升沿就转换成LTC2308模块的系统复位信号:

 标记出 measure_start 信号高电平(也就是标记出tWHCONV时间段):

当电路处在采集数据的读取过程当中,则输出SCK时钟(ADC_SCK周期也为40MHz,但每一次只有12个周期输出),否则输出低电平0 :

 在clk下降沿时刻,将采集的数据ADC_SDO缓存到read_data寄存器:

measure_done是一次采集完成标志,每次检测到LTC2308的触发信号上升沿时measure_done信号归0,当一次采集完成后置1:

reset_n、clk_enable、ADC_SCK、ADC_CONVST、measure_done和ADC_CONVST信号波形标出如下:

设置不同的配置字到寄存器config_cmd,然后根据输入(measure_ch)的通道选择配置字:

然后根据LTC2308的时序图标记出配置字的三个阶段(三个状态):config_init(配置初始化时间段) 、config_enable(可配置时间段) 和 config_done(配置完成时间段)。

config_init(配置初始化时间段):初始状态下,将配置字的高字节赋给ADC_SDI

config_enable(可配置时间段):将配置字剩余的5个bit逐个赋给ADC_SDI

config_done(配置完成时间段):配置完成阶段将0赋给ADC_SDI

 

(2)解读adc_ltc2308_fifo.v模块

adc_ltc2308_fifo.v模块里面例化了adc_ltc2308.v模块和adc_data_fifo.v模块。

adc_ltc2308_fifo.v模块是将adc_ltc2308.v模块进行了一个封装,转换成avalon接口,以便在Qsys里面调用该模块。同时,adc_ltc2308_fifo.v模块也调用了adc_data_fifo.v模块,让ADC采集过来的数据缓存在FIFO里面。

 

 

信号 方向 位宽 描述
slave_clk 输入 1 总线时钟,所有端口信号都同步于该时钟信号
slave_reset_n 输入 1  复位输入信号
slave_chipselect_n 输入 1  片选信号,当该信号有效时,IP 核的所有寄存器才能被访问
slave_addr 输入 1  
地址,该地址信号指定了 IP 核被访问的寄存器编号,通过给该信号赋予不同的值,就能选择访问不
同的寄存器
slave_read_n 输入 1  
读请求信号,低电平有效,当该信号有效(低电平)时, address 指定的寄存器中的数据会被读出到readdata 
slave_wrtie_n 输入 1  
写请求信号,低电平有效,当该信号有效(低电平)时,writedata 端口上的数据会被写入 address 指
定的寄存器中
slave_readdata 输出 12  
读数据端口,当 chipselect 有效时,该端口上的值为 address 指定的寄存器中的值
slave_wriredata 输入 15  
写数据信号,当 write_n 信号有效时,该端口上的数据会被写入 address 指定的寄存器中
adc_clk 输入 1  40MHz
ADC_CONVST 输出 1   标记出 measure_start 信号高电平
ADC_SCK 输出 1  LTC2308的SCK时钟
ADC_SDI 输出 15  LTC2308的SDI控制字
ADC_SDO 输入 12  采集的数据

 

 

检测触发信号CONVST上升沿。main.c代码里面给出了CONVST(即measure_fifo_start,来自slave_wriredata[0])。
这里实现边沿捕获功能的方式就是最常用的边沿检测方式,即使用 2 个 D 触发器分别存储前后两个时钟时输入 IO 上的电平值,然后比较两个 D 触发器中的值来判断是否捕获到了边沿:

 

 

 

 

 

 

 

 

 

(3)解读adc_data_fifo.v模块

 

(4)解读DE10_NANO_QSYS模块

 PLL组件(系统50MHz时钟 通过PLL产生出40MHz时钟给DE10_NANO_QSYS模块里面的各个小模块使用):

 

 

PIO组件SW配置(这里SW设置的是10bit,但是通道选择只需要3bit,所以取值的时候只取前面3bit,后面7bit屏蔽):

 

 DE10_NANO_QSYS模块里面各个组件的地址情况:

 

 

#define SW_BASE 0x0

#define ADC_LTC2308_BASE 0x81010

#include <stdio.h>
#include <io.h>
#include <unistd.h>

#include "system.h" //定义了寄存器地址信息等

void main(void){
    int ch = 0;
    // 设置测量数据个数,最大设置1024
    const int nReadNum = 10;  
    int i, Value, nIndex=0;

    printf("ADC Demo\r\n");
    while(1){
    //读取SW[2:0]的值作为通道的选择信号
        ch = IORD(SW_BASE, 0x00) & 0x07; 

        printf("======================= %d, ch=%d\r\n", nIndex++, ch);

        // ADC转换的测量个数写入DE10_NANO_QSYS模块的0x01寄存器
        IOWR(ADC_LTC2308_BASE, 0x01, nReadNum);    


        // 开始测量,左移一位是因为预留出第一位给触发信号
        //slave_wriredata[3:1]为SW的值,slave_wriredata[0]为触发信号
        //或操作是为了得到一个触发脉冲,即产生CONVST的触发信号

        IOWR(ADC_LTC2308_BASE, 0x00, (ch << 1) | 0x00);
        IOWR(ADC_LTC2308_BASE, 0x00, (ch << 1) | 0x01);
        IOWR(ADC_LTC2308_BASE, 0x00, (ch << 1) | 0x00);
        usleep(1);

        //等待测量结束
        //
        while ((IORD(ADC_LTC2308_BASE,0x00) & 0x01) == 0x00);

        // 读取ADC的值
        for(i=0;i<nReadNum;i++){
            Value = IORD(ADC_LTC2308_BASE, 0x01);
            printf("CH%d=%.3fV (0x%04x)\r\n", ch, (float)Value/1000.0, Value);
        }

        usleep(200*1000);
    } // while
}