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上。
参考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:
IOWR(ADC_LTC2308_BASE, 0x00, (ch << 1) | 0x00);
IOWR(ADC_LTC2308_BASE, 0x00, (ch << 1) | 0x01);
IOWR(ADC_LTC2308_BASE, 0x00, (ch << 1) | 0x00);
四、LTC2308的SPI协议时序理解
参考LTC2308fb.pdf第17页, 该器件SPI协议时序图有两个。
第一个:
第二个:
所以实验里面果断选择第二个时序图。
第二个时序图中的各个时间定义(参考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 | 采集的数据 |
(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
}