FPGA的ADC采集部分学习整理
adc采集部分还是很有用的,模数转换在很多地方都用得到。
使用的EDA模块上的ADC芯片是adc128s102。逐次逼近型ADC(一般单片机用的都是逐次逼近型,速度较快,成本低)。8通道以及12位分辨率。
这边手册上说模拟电源的VA输入范围为2.7V~5.25V
ADC芯片,接入8个模拟输入引脚,输入模拟量。IN0~IN7
引脚DIN是串行数据输入引脚,SCLK上升沿采样输入
引脚DOUT转换结果输出脚,SCLK下降沿输出
SCLK时钟输入,频率范围8~16MHZ,这边采用12.5MHZ,因为是50MHZ的四分之一
CS,片选,下降沿开始测量转换,低电平状态为正在转换
12位数据,是DOUT传出来的,4096分量
所以:
电压值与ADC输出值的的关系为 电压U=adc输出值*3.3V /4096
这边3.3是引脚的实际电压值。
线性序列机方式,我的理解就是找到其中一个电平时间作为参照物。
这边CS片选,高电平下降到低电平,然后开始工作
SCLK时钟,一帧16个上升沿和下降沿。也是先高到低
一开始三个周期是Track,采样模式
后面13个周期是hold保持模式,转换完成并且完成数据输出
这边以SCLK为线性序列机的时钟参照。以SCLK的时间参照,记录在每个SCLK时间单位到来时,其他寄存器的值去对应。
第三个时钟到第五个时钟下降沿开始选择输入通道。这三个值组成8位通道数 000~111 0~7
看到最后的仿真图,更容易理解,
在lsm_cnt在0~34计数的时候,这边序列机的时间单位是40ns
这边就是记录每个lsm_cnt的值达到时候的寄存器的值。(这边lsm_cnt计数满都是最右边在div_cnt两个时候)
这边主要是din中addr值的确定和dout中的12为adc输出数据的写入data(外部模拟量写入)。
代码中对addr和data的值,做了寄存处理,防止端口电平变化造成的一些影响(之前遇到的),锁存操作经常用到。
下面是驱动的框图,这边我准备重新设计一下。
这边我重新设计了一下系统框图。
就是FPGA给驱动时钟信号clk,复位信号reset_n,转换使能信号conv_go(可以用按键触发),通道地址信号addr(8通道用3个拨码开关表示)
外部模拟量通过模拟引脚通过adc芯片转换后adc_dout输出给驱动。每次只有一通道可以采集,这个adc_dout是串行数据输入给驱动
然后驱动输出给adc芯片的有,adc时钟sclk作为线性序列机的参考时钟,输出cs片选信号控制转换开始停止,输出adc_din作为选择通道
最后将模拟量的串行输入信号并行传输给FPGA,以及发送转换停止信号告诉主控(fpga)。
FPGA在收到转换后的并行data数据之后,就可以进一步处理,就是通过之前的数码管显示出来。因为data数据之后12位,所以4数据一组16进制,3个数码管就够用了。
通过按键触发conv_go转换开始标志。data数据就留给数码管显示
ADC的上面的data数据转给disp_data[31:0],因为只有12位,所以高20位全部为0,只用低位3个数码管显示
下面就是数码管的知识了(之前的随笔里面写过)
最后还有一个按键触发的转换开始(是按键消除抖动那边的,也不难)
module adc128s102(
clk,
reset_n,
conv_go, //使能单次转换,高脉冲使能一次
addr, //通道
conv_done,//单次转换结束,转换结束后产生一个时钟的高脉冲
data, //ADC转换结果
adc_sclk,
adc_cs_n,
adc_din,
adc_dout
);
input clk;
input reset_n;
input conv_go;
input [2:0] addr;
output reg conv_done;
output reg [11:0] data;
output reg adc_sclk;
output reg adc_cs_n;
output reg adc_din;
input adc_dout; //输入类型的端口
parameter CLOCK_FREQ = 50_000_000;
parameter SCLK_FREQ = 12_500_000; //8~16mhz 取12.5Mhz 因为好算
parameter MCNT_DIV_CNT = CLOCK_FREQ/(SCLK_FREQ * 2) - 1;//1 40ns
reg [7:0] div_cnt;
reg [5:0] lsm_cnt;//计数到34
reg [2:0] r_addr;
reg [11:0] data_r;
reg conv_en;
always @(posedge clk or negedge reset_n)
if(!reset_n)
conv_en <= 1'd0;
else if(conv_go)
conv_en <= 1'd1; //分频计数器使能
// else if((lsm_cnt <= 6'd34) && (div_cnt == MCNT_DIV_CNT))
else if(conv_done)
conv_en <= 1'd0;
else
conv_en <= conv_en;
//确保端口传输时电平的变化造成的传输没完成会影响最后传输
//先锁存一下
// always @(posedge clk)
// if(conv_go)
// r_addr <= addr;
// else
// r_addr <= r_addr;
always @(posedge clk or negedge reset_n)
if(!reset_n)
r_addr <= 3'd0;
else if(conv_go)
r_addr <= addr;
else
r_addr <= r_addr;
//最小单位计数器
//40ns
always @(posedge clk or negedge reset_n)
if(!reset_n)
div_cnt <= 0;
else if(conv_en) begin //计数使能信号
if(div_cnt == MCNT_DIV_CNT)
div_cnt <= 0;
else
div_cnt <= div_cnt + 1'd1;
end
else
div_cnt <= 0;
//序列计数器,线性序列击0~34
always @(posedge clk or negedge reset_n)
if(!reset_n)
lsm_cnt <= 6'd0;
else if(div_cnt == MCNT_DIV_CNT) begin
if(lsm_cnt == 6'd34)
lsm_cnt <= 6'd0;
else
lsm_cnt <= lsm_cnt + 1'd1;
end
else
lsm_cnt <= lsm_cnt;
always @(posedge clk or negedge reset_n)
if(!reset_n)begin
data_r <= 12'd0;
adc_sclk <= 1'd1;
adc_din <= 1'd1;
adc_cs_n <= 1'd1;
//adc_dout <= 1'd1;
end
else if(div_cnt == MCNT_DIV_CNT)begin
case(lsm_cnt)
0 : begin adc_cs_n <= 1'd1; adc_sclk <= 1'd1; end
1 : begin adc_cs_n <= 1'd0; end
2 : begin adc_sclk <= 1'd0; end
3 : begin adc_sclk <= 1'd1; end
4 : begin adc_sclk <= 1'd0; end
5 : begin adc_sclk <= 1'd1; end
6 : begin adc_sclk <= 1'd0; adc_din <= r_addr[2]; end
7 : begin adc_sclk <= 1'd1; end
8 : begin adc_sclk <= 1'd0; adc_din <= r_addr[1]; end
9 : begin adc_sclk <= 1'd1; end
10 : begin adc_sclk <= 1'd0; adc_din <= r_addr[0]; end
11 : begin adc_sclk <= 1'd1; data_r[11] <= adc_dout; end
12 : begin adc_sclk <= 1'd0; end
13 : begin adc_sclk <= 1'd1; data_r[10] <= adc_dout; end
14 : begin adc_sclk <= 1'd0; end
15 : begin adc_sclk <= 1'd1; data_r[9] <= adc_dout; end
16 : begin adc_sclk <= 1'd0; end
17 : begin adc_sclk <= 1'd1; data_r[8] <= adc_dout; end
18 : begin adc_sclk <= 1'd0; end
19 : begin adc_sclk <= 1'd1; data_r[7] <= adc_dout; end
20 : begin adc_sclk <= 1'd0; end
21 : begin adc_sclk <= 1'd1; data_r[6] <= adc_dout; end
22 : begin adc_sclk <= 1'd0; end
23 : begin adc_sclk <= 1'd1; data_r[5] <= adc_dout; end
24 : begin adc_sclk <= 1'd0; end
25 : begin adc_sclk <= 1'd1; data_r[4] <= adc_dout; end
26 : begin adc_sclk <= 1'd0; end
27 : begin adc_sclk <= 1'd1; data_r[3] <= adc_dout; end
28 : begin adc_sclk <= 1'd0; end
29 : begin adc_sclk <= 1'd1; data_r[2] <= adc_dout; end
30 : begin adc_sclk <= 1'd0; end
31 : begin adc_sclk <= 1'd1; data_r[1] <= adc_dout; end
32 : begin adc_sclk <= 1'd0; end
33 : begin adc_sclk <= 1'd1; data_r[0] <= adc_dout; end
34 : begin adc_cs_n <= 1'd1; end
default : adc_cs_n <= 1'd1;
endcase
end
always @(posedge clk or negedge reset_n)
if(!reset_n)begin
data <= 12'd0;
conv_done <= 0;
end
else if((lsm_cnt == 34) && (div_cnt == MCNT_DIV_CNT))begin
conv_done <= 1'd1; //转换完成标志信号
data <= data_r;
end
else begin
conv_done <= 1'd0;
data <= data;
end
endmodule
本文来自博客园,作者:祈愿树下,转载请注明原文链接:https://www.cnblogs.com/cjl520/p/18057139