总体框架
AD7606_Module主要由3个模块组成组成,AD7606_Data_Pkt和AD7606_Drive以及AD7606_ctrl。
1.AD7606_Data_Pkt主要作用是把AD芯片数据组好数据包,然后发送给上位机;
2.AD7606_Drive主要负责和芯片的交互部分
3.AD7606_ctrl控制模块的作用接受命令报文,然后把里面具体的控制信号解析出来,把控制信号给到数据组包模块。
一、数据组包模块AD7606_Data_Pkt
数据格式如图所示,其中每一个通道的数据由3个字节组成,第一个字节代表该通道的通道数,后两个字节是AD采集到的16位数据,CRC暂时不设计
组包设计思路是,
1.输入定义一个r_cnt寄存器,当查询指令进来或者计数到0到4时候自增,在计数到4时候等待有效信号进来才继续自增,等到读完所有通道数3+3*channel-1这时候才清零.
always @(posedge i_clk or posedge i_rst )
begin
if(i_rst)
r_cnt <= 'd0 ;
else if(r_cnt == 2 + (ri_cap_channel + ri_cap_channel + ri_cap_channel))//读完就是26,一个通道占用3个字节
r_cnt <= 'd0 ;
else if (r_cnt > 4)
r_cnt <= r_cnt + 1 ;
else if (r_cnt == 4 && ri_user_valid_1)
r_cnt <= r_cnt + 1 ;//这里是确保第一个数据进来了
else if(ri_cap_seek || (r_cnt > 0 && r_cnt < 4 ) )
r_cnt <= r_cnt + 1 ; //查询指令来了之后才开始组包,算到4就真正开始进来数据了
else
r_cnt <= r_cnt ;
end
2.使用一个case在计数器r_cnt自增时候就开始组包
always @(posedge i_clk or posedge i_rst )
begin
if(i_rst)
r_adc_per_data <= 'd0 ;
else case(r_cnt)
0 : r_adc_per_data <= 8 'h55 ; //前导码
1 : r_adc_per_data <= 'd5 ;//指令,代表通道电压采集结果查询
2 : r_adc_per_data <= ri_cap_channel + ri_cap_channel + ri_cap_channel; //长度信息,一个通道需要3个字节 , 3*通道数 ,3 *ri_cap_channel,乘法在fpga里面一个周期算不出来
3 : r_adc_per_data <= 1 ; //代表通道1
4 : r_adc_per_data <= ri_user_data_1[15 : 8] ; //数据高字节
5 : r_adc_per_data <= ri_user_data_1[7 : 0] ; //数据低字节
6 : r_adc_per_data <= 2 ;
7 : r_adc_per_data <= ri_user_data_2[15 : 8] ;
8 : r_adc_per_data <= ri_user_data_2[7 : 0] ;
9 : r_adc_per_data <= 3 ;
10 : r_adc_per_data <= ri_user_data_3[15 : 8] ;
11 : r_adc_per_data <= ri_user_data_3[7 : 0] ;
12 : r_adc_per_data <= 4 ;
13 : r_adc_per_data <= ri_user_data_4[15 : 8] ;
14 : r_adc_per_data <= ri_user_data_4[7 : 0] ;
15 : r_adc_per_data <= 5 ;
16 : r_adc_per_data <= ri_user_data_5[15 : 8] ;
17 : r_adc_per_data <= ri_user_data_5[7 : 0] ;
18 : r_adc_per_data <= 6 ;
19 : r_adc_per_data <= ri_user_data_6[15 : 8] ;
20 : r_adc_per_data <= ri_user_data_6[7 : 0] ;
21 : r_adc_per_data <= 7 ;
22 : r_adc_per_data <= ri_user_data_7[15 : 8] ;
23 : r_adc_per_data <= ri_user_data_7[7 : 0] ;
24 : r_adc_per_data <= 8 ;
25 : r_adc_per_data <= ri_user_data_8[15 : 8] ;
26 : r_adc_per_data <= ri_user_data_8[7 : 0] ;
endcase
end
3.然后把r_adc_per_data输入进去fifo里面,fifo输出就是组好的包,记住fifo的读使能要打一拍,才和输出数据同步,同时定义一个r_sent_cnt,发一个数据加1,可以作为o_adc_last信号拉高的条件
二、AD控制模块AD7606_ctrl
i_cmd_data信号进来,需要把解包,读取指令数据,数据包的格式依然是图1 所示,重点关注指令那一块,当r_cnt数到1的时候,读取指令type,type就是决定后面的数据包含什么信息,然后把信息输出给ad_drive和ad_pkt
解包设计思路:
1.定义一个计数器r_cnt,有效信号valid来的时候就加1
2.取出type
always @(posedge i_clk or posedge i_rst )
begin
if(i_rst)
r_type <= 'd0 ;
else if(r_cnt == 1 && ri_cmd_valid)
r_type <= ri_cmd_data ;
else
r_type <= r_type ;
end
3.然后取出指令,以取采样率为例,由于是二十四位,采用串转并操作,w_system_pos是上电启动,这部分还没开发,i_adc_speed 最后悬空的。
always @(posedge i_clk or posedge i_rst )
begin
if(i_rst)
ro_cap_speed <= 'd0 ;
else if (w_system_pos)
ro_cap_speed <= i_adc_speed ;
else if(ri_cmd_valid && r_cnt >= 3 && r_cnt <= 2 + r_payload && r_type == 2)
ro_cap_speed <= {ro_cap_speed [15 : 0] ,ri_cmd_data} ; //串并转换
else
ro_cap_speed <= ro_cap_speed ;
end
二、AD驱动模块AD7606_drive
特点 :
1.同步采样
2.模拟通道数 :8
3.分辨率 : 16 bit , 5V / 2的16次方 = 0.00007V 理论值 1 2
4.有效位数 ENOB : 真正的分辨率 ,16 bit - 3~4 = 13bit 左右
5.数字量输出形式 : 二进制补码
FPGA控制引脚:
1.PAR / SER / BYTE SEL : 并行 、串行 、字节选择 ,本项目使用并行 ,设置为0
2.STBY :睡眠控制 , 0电平睡眠
3.CONVST A/B :驱动ADC模拟信号控制引脚,A控制第一半,B控制高一半
4.RESET : 复位,高有效,持续50ns以上
5.RD:读数据控制信号
6.BUSY :繁忙指示信号
6.CS: 片选信号
7.FRSTDATA :第一通道指示信号
8.DB0~DB15:读数据通道
时序图:
- t reset 需要50ns,状态机设置r_st_cnt == 10 ,一共200ns
- CONSVT同时拉低,手册上说CONSVT A 和CONVST B之间上升沿相差最大为 0.5ms
- CONSVT 后大于40ns才能拉高 busy,状态机设置r_st_cnt >= 10 ,然后读busy状态再调到读状态。
4.busy拉低了之后可以立刻拉低CS,就是读状态了。
读状态的时候就可以产生RD信号了,CS拉低后RD不需要延时,另外,由于RD需要接受8bit,需要计数器计数16,翻转状态下降沿的时候输入输出数据,上升沿读数据。
5.读完数据等待触发,等待时间用T cycle - T cony - 16*20ns = 5us - 3.45 us - 0.32 us =1.23us 等于 1230ns 也就是至少计数62个周期。
使用状态机,构建上面所示的信号图。
always@(*)
begin
case(r_st_current)
P_ST_RESET : r_st_next = r_st_cnt == 10 ? P_ST_CONSVT : P_ST_RESET ;//手册上持续50ns就行,这里一个时钟20ns,算够了200ns
P_ST_CONSVT : r_st_next = ri_user_ctrl & ((!ri_trig_mode) ||(ri_trig_mode && ri_extrig[2] && ri_extrig[1]))
? P_ST_BUSY : P_ST_CONSVT ;//(!ri_trig_mode)自触发就直接执行,外部触发就看ri_extrig是不是高
P_ST_BUSY : r_st_next = r_st_cnt >= 10 & !ri_ad_busy ? P_ST_READ : P_ST_BUSY ;//要求是consvt后大于40ns才能拉高,等待200ns后才读busy状态
P_ST_READ : r_st_next = r_st_cnt == 16 - 1 ? P_ST_WAIT : P_ST_READ ;
P_ST_WAIT : r_st_next = r_st_cnt == ri_cap_speed ? P_ST_CONSVT : P_ST_WAIT ;//手册上是5us,时钟是50Mhz,一个周期就是20ns,5除以0.02等于250,减去上面用25,留余量设置成230,,
default : r_st_next = P_ST_RESET ;
endcase
end
//触发模式寄存器ri_trig_mode
always@(posedge i_clk,posedge i_rst)
begin
if(i_rst)
ri_trig_mode <= 'd0;
else
ri_trig_mode <= i_trig_mode;//阈值限制,最小就是230
end
数据读入代码设计
//通道标识
always@(posedge i_clk,posedge i_rst)
begin
if(i_rst)
ro_user_channel <= 'd0;
else if(r_st_current == P_ST_CONSVT)
ro_user_channel <= 'd0;
else if(ro_ad_rd && !ro_ad_rd_1d)
ro_user_channel <= ro_user_channel + 1;
else
ro_user_channel <= ro_user_channel;
end
always@(posedge i_clk,posedge i_rst)
begin
if(i_rst)
ro_user_data_1 <= 'd0;
else if(ro_ad_rd && !ro_ad_rd_1d && ro_user_channel == 0)
ro_user_data_1 <= i_ad_data;
else
ro_user_data_1 <= 'd0;
end
设计小技巧
1.外部触发信号输入进来需要打拍处理
//外部触发打拍ri_extig
always@(posedge i_clk,posedge i_rst)
begin
if(i_rst)
ri_extrig <= 'd0;
else
ri_extrig <= {ri_extrig[2:0],i_extrig};//阈值限制,最小就是230
end
2.ad7606有过采样功能,所谓过采样就是在单位时间内多采样几个点,然后他会取平均值,通过osc引脚控制。