Xilinx ZYNQ 7000+Vivado2015.2系列(九)基于AXI总线的等精度频率计(测量数字信号频率)
上一节我们体验了一把PS和PL是怎样联合开发的,这种ARM和FPGA联合设计是ZYNQ的精华所在。
这一节我们实现一个稍微复杂一点的功能——测量未知信号的频率,
PS和PL通过AXI总线交互数据,实现我们希望的功能。
如何测量数字信号的频率
最简单的办法——在一段时间内计数
在我们设定的时间(Tpr) 内对被测信号的脉冲进行计数, 得Nx, Fx=Nx/Tpr。
Tpr 越大,测频精度越高。
这种方法适合于高频信号,因为这里可能会有一个被测信号周期的误差,测量高频信号时误差小。
另一个变种——在一个周期内计数
在被测信号一个周期内对基准时钟信号计数,得Nx, 基准时钟周期为T, 则Tx=T*Nx, Fx=1/Tx。
被测信号频率越低, 基准时钟频率越高,测量精度越高。
因此这种方法适用于低频信号。
二者结合——多个周期同步计数
这种方法的精髓在于同步二字。
在计数时引入D触发器,在被测信号的上升沿计数(Ntest),
实际测量时间是被测信号周期的整数倍,消除了可能的1 个周期的误差。
引入一个标准时钟信号(Fstd已知),在测量被测信号频率的同时,对标准时钟脉冲进行计数(Nstd)。
它俩的计数时间相同:Nstd/Fstd = Ntest / Ftest,所以Ftest=Fstd*Ntest/Nstd
增大Tpr或提高Fstd,可以提高测量精度。这种方法高低频通吃。
今天我们要采用的是第三种方法,系统框图如下:
可见看见后面两个计数模块的使能信号都是来自D触发器的输出,
D触发器的输入是待测信号,也就是测量时间会是待测信号的整数倍,
然后两个模块分别对待测信号和一个已知频率的时钟信号进行脉冲计数。
设计思路是:
①新建一个工程叫Freq_Meter,
②创建基于AXI总线的频率计数模块
③命名为Freq_EA,默认4个寄存器,添加端口:
④ 添加用户逻辑,复位信号连接到系统的,寄存器0的第0位用于计数清零,第1位用于控制计数时间:
// Add user logic here
reg clr;
reg Tpr;
reg[31:0] Nstd;
reg[31:0] Ntest;
always @( posedge S_AXI_ACLK ) begin
if ( S_AXI_ARESETN == 1'b0 ) begin
clr <= 1'd0;
Tpr <= 1'd0;
end
else begin
clr <= slv_reg0[0]; //复位
Tpr <= slv_reg0[1]; //预置时间
end
end
//-----------------------------
always @(posedge S_AXI_ACLK) begin //标准时钟
if(!clr)
Nstd <= 32'd0;
else if(Tpr == 1'b1)
Nstd <= Nstd + 1'b1;
else
Nstd <= Nstd;
end
//------------------------------
always @(posedge Ftest) begin //待测信号
if(!clr)
Ntest <= 32'd0;
else if(Tpr == 1'b1)
Ntest <= Ntest + 1'b1;
else
Ntest <= Ntest;
end
// User logic ends
endmodule
⑤计数值存到寄存器1,2中,寄存器0留着给控制信号(Tpr,rst):
⑥顶层文件里,添加端口号:
⑦端口调用里的信号补齐:
⑧打包好IP。然后回到之前的工程,添加这个IP到库里。
⑨新建一个Block Design,添加zynq核,
因为我们需要两个信号,一个标准时钟(已知的,这里用的是100M),一个待测信号,
我们都用PS产生,最后我们可以看下这种方法测的到底准不准:
⑩添加Freq_EA,CLK1连接到Ftest上,连接好后的框图如下:
⑪一系列顺序操作,生成比特流后导入到SDK。
SDk部分设计
新建一个应用工程,首先还是找到xparameters.h文件,找到我们的IP的基地址:
列一下,前面的硬件设计是这样的:
寄存器0是给控制i信号的,第0位用于计数复位,第1为用于控制计数时间,
拉高时开始计数,然后拉低时停止计数,
寄存器1存的是标准时钟计数值,
寄存器2是待测信号计数值。
一些相关注释在代码后边:
#include <stdio.h>
#include "xil_io.h"
#include "sleep.h"
#include "xparameters.h"
#include "xil_types.h"
int main(){
u32 N_std,N_test;
double Freq_test;
while(1){
Xil_Out32(XPAR_FREQ_EA_V1_0_0_BASEADDR,0); //0000_0000 低电平复位
usleep(10);
Xil_Out32(XPAR_FREQ_EA_V1_0_0_BASEADDR,3); //0000_0011 10us后开始计数
usleep(100000); //计数0.1s,最高计数32位,不能溢出了
N_std =Xil_In32(XPAR_FREQ_EA_V1_0_0_BASEADDR+4);
N_test =Xil_In32(XPAR_FREQ_EA_V1_0_0_BASEADDR+8);
xil_printf("N_std=%d\r\n",N_std);
xil_printf("N_test=%d\r\n",N_test);
Freq_test =(double)100.0*N_test/N_std; //标准时钟是100MHz
printf("The Frequency is %f MHz\r\n",Freq_test);
sleep(2);
}
return 0;
}
上电,program FPGA,运行软件部分:
可以看见:
对比一下,可以精确到小数点后4位,还是很准的:
小插曲:
打印函数不能用xil_printf(),因为Xilinx提供的打印函数不能打印浮点数。
上面是我一开始用的xil_printf,结果数字出不来,要么计数一些奇怪符号,下面是换成C自带的库函数就可以了
总结:
基于AXI总线的开发是很强大的,PS可以和Pl交互,不管是控制信号还是数据,甚至PL可以控制PS。