当前位置:   article > 正文

ADC采集调试_ad采集信号电压高如何降低

ad采集信号电压高如何降低

目录

基础知识

转换精度

分辨率

转换误差

采样速度

低速ADC

并行模式

串行模式

高速ADC

设计方法

数据采集处理方法

第一种处理方法:

第二种方法使用IDELAY:

第三种方法使用超采样:

数据滤波处理方法

第一种方法:

第二种方法:

第三种方法:


基础知识

已经学习一些ADC的调试经验,现在特地总结ADC在FPGA上的应用技巧。

ADC(Analog to Digital Converter)模拟到数字的装换。他是连接模拟世界和数字世界的桥梁。

自然界的各种信号都是模拟的,对于这种信号计算机是不能进行处理的。所以我们需要把各种模拟信号转换为数字信号。

初次接触ADC,你会听到很多诸如,这款ADC的精度怎么样,这款ADC的速度怎么样。

转换精度

分辨率

其实很好理解,分辨率就是一款ADC拥有的数据位宽,这说明ADC对输入模拟电压的分辨能力。例如一个数据位宽只有4位的ADC,如使用他去采集0-5V的电压。我们指定4位二进制数最大表示的十进制数为16,这时候就可以计算出,数据的每一位代表的电压值。

LSB=\frac{5V}{2^{4}}=0.3125V

即一位代表的电压值为0.3125V,这时候就可以根据测得数值与LSB相乘。就可以算出测得的电压值。

转换误差

在AD转换过程中,转换误差表示AD转换器实际输出的数字量和理想输出的数字量的差别。例如给出的相对误差为<LSB/2时,表示最大误差不超过不大于最低位的1/2。

采样速度

对于不同的ADC其速度有很大的区别,一般来说速度和精度成反比,对于精度低的ADC最快速度可以达到GSPS。对于高精度的ADC如数据位其速度一般在MSPS期间。

SPS反应了ADC在一秒内所能采集的样本个数。入1MSPS,反应了ADC的采样频率为1MHZ,即每秒能产生100万个数据。说到这里就不得不说一下取样定理,为了能更加清晰的还原波形,就需要满足。

f_{s}\geqslant 2f_{imax}

其中,f_{s}表示取样频率,f_{imax}为待测信号的最高频率。

 然而在工业是一般是取:

 f_{s}\geqslant 5f_{imax}

即取样频率一般要大于待测信号最高频率的五倍,才能更好的还原波形。

如何使用FPGA做好ADC采集相关的电路呢。

对于大多数电路来说,外部的输入信号需要先经过模拟信号的处理,经过处理过的信号在满足ADC采集的安全电压范围内,经过ADC采集后,缓存在FPGA存储的空间,或者存储器上(FIFO或者DDR中)。对于整体的框图一般如下所示。

低速ADC

对于低速ADC例如常见的ADC7606,16位,200ksps ADC。

这一款ADC具有两种数据读取模式,串行和并行数据读取模式。对于逻辑设计来说,首先去看原理图,对ADC采用的模式进行分析。翻看芯片手册,对比其引脚配置和功能描述。详细的检查芯片的引脚模式是否对接正常,如有异常立即和硬件工程师联系。

如果连接模式为并行模式,在芯片手册中找到对应并行连接模式的时序图。分析时序图。根据手册写出逻辑。

 

 

分析时序图,从其中可以看出来,ADC转换开始信号起始于CONVSTA,B两信号的上升沿,ADC检测到该信号上升沿后,启动转换,同时拉高BUSY信号,表示转换正在进行中。等待转换结束,BUSY信号的下降沿,表示装换数据正被锁存至输出数据寄存器,可以读取数据。当然大部分ADC为了提高采样率,可以在BUSY转换期间继续读取数据(因为此时数据并未更新),但是需要保证在转换结束之前读取完成,以免造成数据错误。

在时序图中,CONVSTA,B信号在两次上升沿过程之中的时间间隔,就是ADC所谓的采样率。上图中的 t_{conv}表示ADC的采样速率。即我们所说的SPS。表示ADC的转换时间。对于并行模式CS和RD独立,拉低CS时间为FPGA从ADC中读出数据的读取时间。

这三个时间一般有如下的关系。

逻辑需要关注的几个时间,一是CNV即采样速率,这个直接反应了你ADC所能达到的最高采样率。再就是BUSY和SPI读数据时钟,为了保证读出的数据正常,必须要在下一次转换完成前完成对ADC采集的数据的读取。

对于这里的AD7606的并行模式。从时序图中可以看出来,数据DATA信号在RD信号的下降沿更新在数据总线上,逻辑上可以在RD信号的上升沿读取该数据。一片ADC可以实现8个通道的电压读取。

并行模式

下面是关于此并行模式参考代码。(楼主仿照AX309上面代码改动的),该模块实现了两个ADC对十六路数据的采集。

  1. // *********************************************************************************/
  2. // Project Name :
  3. // Author : i_huyi
  4. // Email : i_huyi@qq.com
  5. // Creat Time : 2021/8/12 14:59:23
  6. // File Name : .v
  7. // Module Name :
  8. // Called By :
  9. // Abstract :
  10. //
  11. // CopyRight(c) 2020, xxx xxx xxx Co., Ltd..
  12. // All Rights Reserved
  13. //
  14. // *********************************************************************************/
  15. // Modification History:
  16. // 1. initial
  17. // *********************************************************************************/
  18. // *************************
  19. // MODULE DEFINITION
  20. // *************************
  21. `timescale 1 ns / 1 ps
  22. module ad7606#(
  23. parameter U_DLY = 1
  24. )
  25. (
  26. input wire clk ,//50mhz
  27. input wire rst_n ,
  28. input wire[15:0] ad_data ,//ad7606 采样数据
  29. input wire ad_busy ,//ad7606 忙标志位
  30. input wire first_data ,//ad7606 第一个数据标志位
  31. output wire[2:0] ad_os ,//ad7606 过采样倍率选择
  32. output reg ad_cs ,//ad7606 AD_1 cs
  33. output reg ad_cs2 ,//ad7606 AD_2 cs
  34. output reg ad_rd ,//ad7606 AD data read
  35. output reg ad_reset ,//ad7606 AD reset
  36. output reg ad_convstab ,//ad7606 AD convert start
  37. output reg [4:0] state ,
  38. output reg [15:0] ad_ch1 ,//AD_11通道的数据
  39. output reg [15:0] ad_ch2 ,//AD_12通道的数据
  40. output reg [15:0] ad_ch3 ,//AD_13通道的数据
  41. output reg [15:0] ad_ch4 ,//AD_14通道的数据
  42. output reg [15:0] ad_ch5 ,//AD_15通道的数据
  43. output reg [15:0] ad_ch6 ,//AD_16通道的数据
  44. output reg [15:0] ad_ch7 ,//AD_17通道的数据
  45. output reg [15:0] ad_ch8 ,//AD_18通道的数据
  46. output reg [15:0] ad2_ch1 ,//AD_21通道的数据
  47. output reg [15:0] ad2_ch2 ,//AD_22通道的数据
  48. output reg [15:0] ad2_ch3 ,//AD_23通道的数据
  49. output reg [15:0] ad2_ch4 ,//AD_24通道的数据
  50. output reg [15:0] ad2_ch5 ,//AD_25通道的数据
  51. output reg [15:0] ad2_ch6 ,//AD_26通道的数据
  52. output reg [15:0] ad2_ch7 ,//AD_27通道的数据
  53. output reg [15:0] ad2_ch8 //AD_28通道的数据
  54. );
  55. //--------------------------------------
  56. // localparam
  57. //--------------------------------------
  58. parameter IDLE =5'd0 ;
  59. parameter AD_CONV =5'd1 ;
  60. parameter Wait_1 =5'd2 ;
  61. parameter Wait_busy =5'd3 ;
  62. parameter READ_CH1 =5'd4 ;
  63. parameter READ_CH2 =5'd5 ;
  64. parameter READ_CH3 =5'd6 ;
  65. parameter READ_CH4 =5'd7 ;
  66. parameter READ_CH5 =5'd8 ;
  67. parameter READ_CH6 =5'd9 ;
  68. parameter READ_CH7 =5'd10 ;
  69. parameter READ_CH8 =5'd11 ;
  70. parameter AD1_TO_AD2 =5'd12 ;//从ad1芯片装换到ad2芯片
  71. parameter READ_2_CH1 =5'd13 ;//AD2芯片读数据
  72. parameter READ_2_CH2 =5'd14 ;
  73. parameter READ_2_CH3 =5'd15 ;
  74. parameter READ_2_CH4 =5'd16 ;
  75. parameter READ_2_CH5 =5'd17 ;
  76. parameter READ_2_CH6 =5'd18 ;
  77. parameter READ_2_CH7 =5'd19 ;
  78. parameter READ_2_CH8 =5'd20 ;
  79. parameter READ_DONE =5'd21 ;
  80. //--------------------------------------
  81. // register
  82. //--------------------------------------
  83. reg [15:0] cnt ;
  84. reg [15:0] i ;
  85. //reg [3:0] state;
  86. reg [15:0] cnt_conv ;
  87. reg [7:0] ad_busy_cnt ;
  88. //--------------------------------------
  89. // wire
  90. //--------------------------------------
  91. wire ad_busy_1 ;
  92. //--------------------------------------
  93. // assign
  94. //--------------------------------------
  95. assign ad_os = 3'b000 ;
  96. //模拟AD busy信号计时250次后模拟busy拉低
  97. assign ad_busy_1 =(ad_busy_cnt<=8'd250)?1'b1:1'b0;
  98. //------------------------------------------------------------
  99. //------------------------------------------------------------
  100. always@(posedge clk)
  101. begin
  102. if(!rst_n)
  103. ad_busy_cnt <= 8'd0;
  104. else if(state == Wait_busy)
  105. ad_busy_cnt <= ad_busy_cnt + 8'b1;
  106. else
  107. ad_busy_cnt <= 8'd0;
  108. end
  109. //AD 复位电路
  110. always@(posedge clk)
  111. begin
  112. if(cnt<16'hffff)
  113. begin
  114. cnt<=cnt+1;
  115. ad_reset<=1'b1;
  116. end
  117. else
  118. ad_reset<=1'b0;
  119. end
  120. always @(posedge clk)
  121. begin
  122. if (ad_reset==1'b1) begin
  123. state<=IDLE;
  124. ad_ch1<=0;
  125. ad_ch2<=0;
  126. ad_ch3<=0;
  127. ad_ch4<=0;
  128. ad_ch5<=0;
  129. ad_ch6<=0;
  130. ad_ch7<=0;
  131. ad_ch8<=0;
  132. ad_cs<=1'b1;
  133. ad_cs2<=1'b1;//ad2的片选
  134. ad_rd<=1'b1;
  135. ad_convstab<=1'b1;
  136. i<=0;
  137. end
  138. else begin
  139. case(state)
  140. IDLE: begin
  141. ad_cs<=1'b1;
  142. ad_cs2<=1'b1;
  143. ad_rd<=1'b1;
  144. ad_convstab<=1'b1;
  145. if(i==12500) begin
  146. i<=0;
  147. state<=AD_CONV;
  148. end
  149. else
  150. i<=i+1'b1;
  151. end
  152. AD_CONV: begin
  153. if(i==2) begin //等待2个clock
  154. i<=0;
  155. state<=Wait_1;
  156. ad_convstab<=1'b1;
  157. end
  158. else begin
  159. i<=i+1'b1;
  160. ad_convstab<=1'b0; //启动AD转换
  161. end
  162. end
  163. Wait_1: begin
  164. if(i==5) begin //等待5个clock, 等待busy信号为高
  165. i<=0;
  166. state<=Wait_busy;
  167. end
  168. else
  169. i<=i+1'b1;
  170. end
  171. Wait_busy: begin
  172. if(ad_busy_1==1'b0) begin //等待busy信号为低
  173. i<=0;
  174. state<=READ_CH1;
  175. end
  176. end
  177. READ_CH1: begin
  178. ad_cs<=1'b0; //cs信号有效
  179. if(i==3) begin
  180. ad_rd<=1'b1;
  181. i<=0;
  182. ad_ch1<=ad_data; //读CH1
  183. state<=READ_CH2;
  184. end
  185. else begin
  186. ad_rd<=1'b0;
  187. i<=i+1'b1;
  188. end
  189. end
  190. READ_CH2: begin
  191. if(i==3) begin
  192. ad_rd<=1'b1;
  193. i<=0;
  194. ad_ch2<=ad_data; //CH2
  195. state<=READ_CH3;
  196. end
  197. else begin
  198. ad_rd<=1'b0;
  199. i<=i+1'b1;
  200. end
  201. end
  202. READ_CH3: begin
  203. if(i==3) begin
  204. ad_rd<=1'b1;
  205. i<=0;
  206. ad_ch3<=ad_data; //读CH3
  207. state<=READ_CH4;
  208. end
  209. else begin
  210. ad_rd<=1'b0;
  211. i<=i+1'b1;
  212. end
  213. end
  214. READ_CH4: begin
  215. if(i==3) begin
  216. ad_rd<=1'b1;
  217. i<=0;
  218. ad_ch4<=ad_data; //CH4
  219. state<=READ_CH5;
  220. end
  221. else begin
  222. ad_rd<=1'b0;
  223. i<=i+1'b1;
  224. end
  225. end
  226. READ_CH5: begin
  227. if(i==3) begin
  228. ad_rd<=1'b1;
  229. i<=0;
  230. ad_ch5<=ad_data; //读CH5
  231. state<=READ_CH6;
  232. end
  233. else begin
  234. ad_rd<=1'b0;
  235. i<=i+1'b1;
  236. end
  237. end
  238. READ_CH6: begin
  239. if(i==3) begin
  240. ad_rd<=1'b1;
  241. i<=0;
  242. ad_ch6<=ad_data; //CH6
  243. state<=READ_CH7;
  244. end
  245. else begin
  246. ad_rd<=1'b0;
  247. i<=i+1'b1;
  248. end
  249. end
  250. READ_CH7: begin
  251. if(i==3) begin
  252. ad_rd<=1'b1;
  253. i<=0;
  254. ad_ch7<=ad_data; //读CH7
  255. state<=READ_CH8;
  256. end
  257. else begin
  258. ad_rd<=1'b0;
  259. i<=i+1'b1;
  260. end
  261. end
  262. READ_CH8: begin
  263. if(i==3) begin
  264. ad_rd<=1'b1;
  265. i<=0;
  266. ad_ch8<=ad_data; //CH8
  267. state<=AD1_TO_AD2;
  268. end
  269. else begin
  270. ad_rd<=1'b0;
  271. i<=i+1'b1;
  272. end
  273. end
  274. AD1_TO_AD2:begin
  275. ad_rd<=1'b1;
  276. ad_cs<=1'b1;
  277. state<=READ_2_CH1;
  278. end
  279. READ_2_CH1: begin
  280. ad_cs2<=1'b0; //cs2信号有效
  281. if(i==3) begin
  282. ad_rd<=1'b1;
  283. i<=0;
  284. ad2_ch1<=ad_data; //读AD2CH1
  285. state<=READ_2_CH2;
  286. end
  287. else begin
  288. ad_rd<=1'b0;
  289. i<=i+1'b1;
  290. end
  291. end
  292. READ_2_CH2: begin
  293. if(i==3) begin
  294. ad_rd<=1'b1;
  295. i<=0;
  296. ad2_ch2<=ad_data; //读AD2的CH2
  297. state<=READ_2_CH3;
  298. end
  299. else begin
  300. ad_rd<=1'b0;
  301. i<=i+1'b1;
  302. end
  303. end
  304. READ_2_CH3: begin
  305. if(i==3) begin
  306. ad_rd<=1'b1;
  307. i<=0;
  308. ad2_ch3<=ad_data; //读AD2CH3
  309. state<=READ_2_CH4;
  310. end
  311. else begin
  312. ad_rd<=1'b0;
  313. i<=i+1'b1;
  314. end
  315. end
  316. READ_2_CH4: begin
  317. if(i==3) begin
  318. ad_rd<=1'b1;
  319. i<=0;
  320. ad2_ch4<=ad_data; //读AD2的CH4
  321. state<=READ_2_CH5;
  322. end
  323. else begin
  324. ad_rd<=1'b0;
  325. i<=i+1'b1;
  326. end
  327. end
  328. READ_2_CH5: begin
  329. if(i==3) begin
  330. ad_rd<=1'b1;
  331. i<=0;
  332. ad2_ch5<=ad_data; //读AD2CH5
  333. state<=READ_2_CH6;
  334. end
  335. else begin
  336. ad_rd<=1'b0;
  337. i<=i+1'b1;
  338. end
  339. end
  340. READ_2_CH6: begin
  341. if(i==3) begin
  342. ad_rd<=1'b1;
  343. i<=0;
  344. ad2_ch6<=ad_data; //读AD2的CH6
  345. state<=READ_2_CH7;
  346. end
  347. else begin
  348. ad_rd<=1'b0;
  349. i<=i+1'b1;
  350. end
  351. end
  352. READ_2_CH7: begin
  353. if(i==3) begin
  354. ad_rd<=1'b1;
  355. i<=0;
  356. ad2_ch7<=ad_data; //读AD2CH7
  357. state<=READ_2_CH8;
  358. end
  359. else begin
  360. ad_rd<=1'b0;
  361. i<=i+1'b1;
  362. end
  363. end
  364. READ_2_CH8: begin
  365. if(i==3) begin
  366. ad_rd<=1'b1;
  367. i<=0;
  368. ad2_ch8<=ad_data; //读AD2的CH8
  369. state<=READ_DONE;
  370. end
  371. else begin
  372. ad_rd<=1'b0;
  373. i<=i+1'b1;
  374. end
  375. end
  376. READ_DONE:begin
  377. ad_rd<=1'b1;
  378. ad_cs2<=1'b1;
  379. state<=IDLE;
  380. end
  381. default: state<=IDLE;
  382. endcase
  383. end
  384. end
  385. //------------------------------------------------------------
  386. //------------------------------------------------------------
  387. //------------------------------------------------------------
  388. //------------------------------------------------------------
  389. endmodule

对于ADC7606的串行模式。在串行模式下,ADC输入的数据总线将降低至两根,分别为DoutA和DoutB。

 

从时序图中可以看出来,输入的串行数据线有两个,DA采集V1,V2,V3,V4的数据,DB采集V5,V6,V7,V8的数据并从时序图中可以看出来,ADC输出的数据在SCLK的上升沿跟新数据,FPGA内部在SCLK的上升沿可以读出稳定的数据。

串行模式

部分核心代码如下。

  1. //------------------------------------------------------------
  2. //------------------------------------------------------------
  3. //复位
  4. always@(posedge clk ,negedge rst_n)
  5. begin
  6. if(rst_n == 1'b0)
  7. begin
  8. cnt_reset <= 8'b0;
  9. reset <= 1'b0;
  10. end
  11. else if(cnt_reset <= RESRT)
  12. begin
  13. cnt_reset <= cnt_reset + 1'b1;
  14. reset <= 1'b1;
  15. end
  16. else
  17. begin
  18. cnt_reset <= cnt_reset;
  19. reset <= 1'b0;
  20. end
  21. end
  22. //控制信号打拍
  23. always@(posedge clk ,negedge rst_n)
  24. begin
  25. if(rst_n == 1'b0)
  26. begin
  27. en_start_r <= 4'd0;
  28. en_stop_r <= 4'd0;
  29. end
  30. else
  31. begin
  32. en_start_r <= {en_start_r[2:0],en_start};
  33. en_stop_r <= {en_stop_r[2:0],en_stop};
  34. end
  35. end
  36. //开始采集
  37. always@(posedge clk ,negedge rst_n)
  38. begin
  39. if(rst_n == 1'b0)
  40. ad_start <= 1'b0;
  41. else if(en_start_r[1] == 1'b1 && en_start_r[2] == 1'b0)
  42. ad_start <= 1'b1;
  43. else if(en_stop_r[1] == 1'b1 && en_stop_r[2] == 1'b0)
  44. ad_start <= 1'b0;
  45. else
  46. ad_start <= ad_start;
  47. end
  48. //AD启动标志信号
  49. always@(posedge clk ,negedge rst_n)
  50. begin
  51. if(rst_n == 1'b0)
  52. ad_start_flog <= 1'b0;
  53. else if(ad_start == 1'b1)
  54. ad_start_flog <= 1'b1;
  55. else if(ad_start == 1'b0 && cnt_sample == 16'd0)
  56. ad_start_flog <= 1'b0;
  57. else
  58. ad_start_flog <= ad_start_flog;
  59. end
  60. //开始计数产生cnv
  61. always@(posedge clk ,negedge rst_n)
  62. begin
  63. if(rst_n == 1'b0)
  64. cnt_sample <= 16'd0;
  65. else if(ad_start_flog == 1'b1)
  66. begin
  67. if(cnt_sample >= CNV_MAX )
  68. cnt_sample <= 16'd0;
  69. else
  70. cnt_sample <= cnt_sample + 1'b1;
  71. end
  72. else
  73. cnt_sample <= 16'd0;
  74. end
  75. //产生采样时钟20khz,cnv最小25ns
  76. always@(posedge clk ,negedge rst_n)
  77. begin
  78. if(rst_n == 1'b0)
  79. signal_cnv <= 1'd1;
  80. else if(cnt_sample >= CNV_LOW && cnt_sample <= CNV_HIGH)
  81. signal_cnv <= 1'd0;
  82. else
  83. signal_cnv <= 1'd1;
  84. end
  85. //检测busy信号下降沿,busy信号一般值为4us,而本次采样率要求20khz,满足要求
  86. //busy信号打拍
  87. always@(posedge clk ,negedge rst_n)
  88. begin
  89. if(rst_n == 1'b0)
  90. busy_r <= 8'd0;
  91. else
  92. busy_r <= {busy_r[14:0],busy};
  93. end
  94. //采集busy下降沿开始读数
  95. always@(posedge clk ,negedge rst_n)
  96. begin
  97. if(rst_n == 1'b0)
  98. sclk_flog <= 1'b0;
  99. else if(busy_r[14] == 1'b0 && busy_r[15] == 1'b1)
  100. sclk_flog <= 1'b1;
  101. else if(cnt_sclk == 8'd64)
  102. sclk_flog <= 1'b0;
  103. else
  104. sclk_flog <= sclk_flog;
  105. end
  106. //产生CS标志
  107. always@(posedge clk ,negedge rst_n)
  108. begin
  109. if(rst_n == 1'b0)
  110. cs_flog <= 1'b1;
  111. else if(busy_r[1] == 1'b0 && busy_r[2] == 1'b1)
  112. cs_flog <= 1'b0;
  113. else if(cnt_sclk == 8'd64)
  114. cs_flog <= 1'b1;
  115. else
  116. cs_flog <= cs_flog;
  117. end
  118. //sclk_flog_r打拍
  119. //这一段代码看看
  120. //这里需要将SCLK延迟输出30ns左右,保证数据正常进入
  121. always@(posedge clk ,negedge rst_n)
  122. begin
  123. if(rst_n == 1'b0)
  124. sclk_flog_r <= 1'b0;
  125. else if(sclk_spi == 1'b1)
  126. sclk_flog_r <= sclk_flog;
  127. else
  128. sclk_flog_r <= sclk_flog_r;
  129. end
  130. //产生10mhz时钟
  131. always@(posedge clk ,negedge rst_n)
  132. begin
  133. if(rst_n == 1'b0)
  134. begin
  135. cnt_sclk_spi <= 8'd0;
  136. sclk_spi <= 1'b0;
  137. end
  138. else if(cnt_sclk_spi == SPI_CLK)
  139. begin
  140. cnt_sclk_spi <= 8'd1;
  141. sclk_spi <= ~sclk_spi;
  142. end
  143. else
  144. begin
  145. cnt_sclk_spi <= cnt_sclk_spi + 1'b1;
  146. sclk_spi <= sclk_spi;
  147. end
  148. end
  149. //AD复位
  150. always@(posedge clk ,negedge rst_n)
  151. begin
  152. if(rst_n == 1'b0)
  153. ad_reset_n <= 1'b1;
  154. else if(busy == 1'b1)
  155. ad_reset_n <= 1'b0;
  156. else
  157. ad_reset_n <= 1'b1;
  158. end
  159. //spi时钟上升沿计数
  160. always@(posedge clk ,negedge rst_n)
  161. begin
  162. if(rst_n == 1'b0)
  163. cnt_sclk <= 8'd0;
  164. else if(ad_reset_n == 1'b0)
  165. cnt_sclk <= 8'd0;
  166. else if(sclk_flog_r == 1'b1 && sclk_spi == 1'b0 && cnt_sclk_spi == SPI_CLK)
  167. cnt_sclk <= cnt_sclk + 1'b1;
  168. else
  169. cnt_sclk <= cnt_sclk;
  170. end
  171. //spi时钟下降沿读数
  172. always@(posedge clk ,negedge rst_n)
  173. begin
  174. if(rst_n == 1'b0)
  175. begin
  176. data_a <= 64'd0;
  177. data_b <= 64'd0;
  178. end
  179. else if(ad_reset_n == 1'b0)
  180. begin
  181. data_a <= 64'd0;
  182. data_b <= 64'd0;
  183. end
  184. else if(sclk_flog_r == 1'b1 && sclk_spi == 1'b0 && cnt_sclk_spi == 8'd3 )
  185. begin
  186. data_a <= {data_a[62:0],d_a};
  187. data_b <= {data_b[62:0],d_b};
  188. end
  189. else
  190. begin
  191. data_a <= data_a;
  192. data_b <= data_b;
  193. end
  194. end
  195. //产生数据有效
  196. always@(posedge clk ,negedge rst_n)
  197. begin
  198. if(rst_n == 1'b0)
  199. sample_valid <= 1'b0;
  200. else if(sclk_flog == 1'b0 && sclk_flog_r == 1'b1)
  201. sample_valid <= 1'b1;
  202. else
  203. sample_valid <= 1'b0;
  204. end
  205. 数据赋值
  206. always@(posedge clk ,negedge rst_n)
  207. begin
  208. if(rst_n == 1'b0)
  209. begin
  210. dout_1 <= 16'd0;
  211. dout_2 <= 16'd0;
  212. dout_3 <= 16'd0;
  213. dout_4 <= 16'd0;
  214. dout_5 <= 16'd0;
  215. dout_6 <= 16'd0;
  216. dout_7 <= 16'd0;
  217. dout_8 <= 16'd0;
  218. dout_valid <= 1'b0;
  219. end
  220. else if(sample_valid == 1'b1 )
  221. begin
  222. dout_1 <= data_a[63:48];
  223. dout_2 <= data_a[47:32];
  224. dout_3 <= data_a[31:16];
  225. dout_4 <= data_a[15:0];
  226. dout_5 <= data_b[63:48];
  227. dout_6 <= data_b[47:32];
  228. dout_7 <= data_b[31:16];
  229. dout_8 <= data_b[15:0];
  230. dout_valid <= 1'b1;
  231. end
  232. else
  233. begin
  234. dout_1 <= 16'd0;
  235. dout_2 <= 16'd0;
  236. dout_3 <= 16'd0;
  237. dout_4 <= 16'd0;
  238. dout_5 <= 16'd0;
  239. dout_6 <= 16'd0;
  240. dout_7 <= 16'd0;
  241. dout_8 <= 16'd0;
  242. dout_valid <= 1'b0;
  243. end
  244. end
  245. //

仿真如下。

 前面说明了针对低速ADC采集做了详细的介绍。

高速ADC

设计方法

但是一般的项目中对ADC的要求在MHZ。下面就记录一下我在这方面踩的坑。

对于硬件工程师,在实现对多路信号采样时。往往在外部使用多路选择器将外部输入信号切换输入,以实现对信号多路采集。一般模型如下:

对硬件的模式做简单的分析。外部输入的信号首先经过衰减电路,发送到多路选择器上,FPGA控制多路选择器将那一路的数据输入进来。(逻辑需要注意此多路选择器切换稳定时间)。在此期间逻辑需要协调启动ADC的采集,保证ADC采集的电压是切换后稳定的电压。(这里实际情况往往和芯片手册叙述的有一点差距逻辑需要注意)

用这种模式可以实现用低速ADC跑高速采集的效果。效果就是N个ADC采样速率的和。

例如,先在有一款ADC最高速率是1mhz。但是实际需要达到3MHZ的采样率,但是3mhz采样率的ADC价钱又贵了几倍,这时候就可以采用这种方式。

 从图中可以看出来,为了实现要求的3mhz的采样率,我们采用了三个相同的ADC来进行数据采集。在总的采样率为3mhz的情况下,每一个ADC都能发挥1mhz的采样速率。这样可以实现对外部输入信号的高精度采样。

在设计上需要注意的是,需要确定多路选择器的切换到切换后电压稳定的时间小于3mhz采样率所需要的时间。这样才能保证输入的电压稳定,ADC采集的电压值才能准确反应这个通道的电压值。

当然也可以选择直接使用高速ADC来实现对数据的采集。

整体的框图如下。

即使用单路的ADC来实现对数据的采集,这时候往往需要更加高速的SPI来读取ADC转化后的数据,如,使用一款采样率为5msps的ADC,在数据转换完成后,需要使用300mhz的SPI总线来读取ADC返回的数据。才能保证读取的数据正确性。

但是使用高速的SPI来读取数据,往往会出现数据和时钟不对其的情况(这里跟外部的电路的设计有关系)。即你在芯片手册上看到的是当你给出SPI的时钟SCLK的下降沿时,ADC接受到时钟下降沿,将第一个数据发送到SPI的串行数据总线上,逻辑部分需要在上升沿对该数据进行读取,并保存。但是往往实际情况并不是这样的。

而是在给出SPI时钟后,数据并未立马更新,而是在你逻辑内部快到时钟上升沿的时候才开始更新数据。这样你在逻辑内部使用上升沿读出的数据就会亚稳态。如果在你读取时钟的上升沿之前数据更新完毕,即可以读出稳定的数据。如果在你数据的上升沿之后数据才更新,那么你将获取不到正确的数据。

 这里有几种处理不同的处理方法,是我在实际中使用的。

数据采集处理方法

第一种处理方法:

这也是最简单的处理方法,使用PLL产生主时钟的相移,偏移度数为0°,45°,90°,135°。在读数据的时候,分别使用这四个时钟去读取串行输入的数据。

使用0°时钟读取。

  1. //接收BUF、接收数据个数
  2. always@(posedge clk or posedge rst)
  3. begin
  4. if(DataReset == 1 || rst == 1'b1)
  5. RecBuf <= 32'hFFFF_FFFF;
  6. else if(DataReceive)
  7. RecBuf <= {RecBuf[30:0],AD7961_DATA};
  8. else
  9. RecBuf <= RecBuf;
  10. end

使用45°时钟读取。

  1. //接收BUF、接收数据个数
  2. always@(posedge clk_45 or posedge rst)
  3. begin
  4. if(DataReset == 1 || rst == 1'b1)
  5. RecBuf <= 32'hFFFF_FFFF;
  6. else if(DataReceive)
  7. RecBuf <= {RecBuf[30:0],AD7961_DATA};
  8. else
  9. RecBuf <= RecBuf;
  10. end

使用90°时钟读取。

  1. //接收BUF、接收数据个数
  2. always@(posedge clk_90 or posedge rst)
  3. begin
  4. if(DataReset == 1 || rst == 1'b1)
  5. RecBuf <= 32'hFFFF_FFFF;
  6. else if(DataReceive)
  7. RecBuf <= {RecBuf[30:0],AD7961_DATA};
  8. else
  9. RecBuf <= RecBuf;
  10. end

使用135°时钟读取。

  1. //接收BUF、接收数据个数
  2. always@(posedge clk_135 or posedge rst)
  3. begin
  4. if(DataReset == 1 || rst == 1'b1)
  5. RecBuf <= 32'hFFFF_FFFF;
  6. else if(DataReceive)
  7. RecBuf <= {RecBuf[30:0],AD7961_DATA};
  8. else
  9. RecBuf <= RecBuf;
  10. end

这样你就获取了同一个数据的四种反馈数据。需要观察并使用ILA采集看看那一路数据处于最稳定的状态。同时保存当前通道的数据。

第二种方法使用IDELAY:

在FPGA中一般拥有IDELA和ODELAY两种,他们可以控制你输入和输出数据的延迟值。这部分的介绍可以看我的另一篇文章《IDELAY输入延迟分析》

(16条消息) IDELAY输入延迟分析_hy_520520的博客-CSDN博客

注意IDELAY拥有几种模式,可以先在VAR_LOAD模式下调试出最准确的值,然后使用FIXED模式固定延时值。注意在IDELAY模式下,使用200mhz的参考时钟,拥有0.6ns的基础延时,在写满延迟参数的情况下拥有0.6ns+78ps*31=3.018ns的延迟。

例化ILELAY模块

  1. //ADC输入的数据延迟
  2. idelay u_idelay_p(
  3. .clk (clk ),
  4. .rst (rst ),
  5. //需要延时的信号
  6. .data_in (AD7961_DATA ),
  7. //延迟数据
  8. .data_out (AD7961_DATA_DALAY ),
  9. //
  10. .delay_data (reg_ai_delay_data ),
  11. .delay_valid (reg_ai_delay_valid ));

第三种方法使用超采样:

超采样和第一种方法比较类似,同样是使用不同相位的时钟对同一信号的采样。

 如图所示,同样的,使用FPGA的MMCM产生四组周期相同相位不同的时钟,在CLK的下降沿ADC读取到SPI,开始在数据总线上更新数据,由于使用的不同相位的时钟对数据进行了采样,即在同一点对同一位数据进行了四次采样。所以叫做超采样。对于这种做法采集后的数据往往更加准确。逻辑需要对采集后的数据进行处理后在进行保存。

使用以上三种采集方法一般就能保证采集到准确的数据,不会因为时许原因产生数据读出错造成的错误。

但是ADC是一种非常敏感的器件,往往外部电路的一些波动会操作采集的数据波动,造成使用ADC采集的数据还原出的波形呈现波动。这时候就需要进行对采集的数据进行滤波处理,对于滤波我也总结了几种常用的方法。

数据滤波处理方法

第一种方法:

均值滤波。适合高采样率数据量大的情况,对波形有很好的平滑处理。但是对个别极值效果不明显。使用5msps的采样率去采集1khz的正弦波,一个周期将产生5000个数据点。采集的波形不平,导致还原出来的波形出现明显的波动。这时候就可以使用均值滤波器对输入的数据进行滤波处理。如使用十六次平均滤波。滤波后产生的数据点数将降低至300个左右,这时候在还原出波形。由于使用了平均数代替原数据,对波形的峰峰值将产生一定的影响。

第二种方法:

滑动滤波器。使用滑动滤波器对波形有很好的还原,同样的的对波形的峰峰值有一定的影响。设计思路是使用一个双口RAM来实现对原始数据的存储,第一次到达设定个数后,下一次进来前,将第一个进来的数据删除,同时将新数据放入末尾。对数据进行均值运算后输出均值。

设计模型。

 计算模型。

 即每一次计算都需要将数据框右移一位,然后依次输入计算后的均值。

第三种方法:

滑动去极滤波器。在使用滑动滤波器数据进行处理的时候,发现对数据出现的个别尖峰值处理并不好。所以设计了这种在滑动处理的过程中使用均值代替出现的尖峰值。然后再输出均值。

设计模型。

思路如下,使用N(奇数)个寄存器分别寄存输入的数据。带第一将寄存器写满后,进行去极值。使用中心数据前后数据的均值和中心数据进行比较,如果数据远大于中心数据就使用均值将中心数据替换。在求N个数据的均值输出。

实际输入一个正弦波,在其中一个数据产生尖峰,滤波效果如图所示。

到这里对ADC的调试总结的差不多了。再接再厉。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Li_阴宅/article/detail/803369
推荐阅读
相关标签
  

闽ICP备14008679号