目录
1. 功能
通过电脑串口发出命令,FPGA处理后连接DA转换器产生幅度、频率及相位可调的模拟信号。
2. 输入信号
电脑通过串口输入的命令或数据。
1)需要多字节输入:由于输入到FPGA的数据应该包含3种信息,其中,频率所实现的可调范围较广,从hz--->Mhz,光传输50MHZ这个频率的信息需要26位二进制表示,而串口协议一次传输8bit,因此需要传输4个字节(32bit),因此需要输入多个字节;
2)需要地址:由于PC通过串口输入,无法判断每次输入的信息是幅度还是频率,因此需要不同的标记信号,这里称为地址表示,而最少需要1个字节数据表示;
3)需要开始和结束标志:为了区别一次传输的开始和结束,需要至少2个字节来区别。
输入信号的格式如下:
Byte0 | Byte1 | Byte2 | Byte3 | Byte4 | Byte5 | Byte6 | Byte7 |
帧头0 | 帧头1 | 寄存器地址 | 数据 | 数据 | 数据 | 数据 | 帧尾 |
0xAA | 0x03 | Addr | Data[7:0] | Data[15:8] | Data[23:16] | Data[31:24] | 0x88 |
3. 输出信号
供DA转换模块工作的DA_CLK,DA_CS,DA_DIN;
4. 模块划分
UART_CTRL模块:
主要将电脑输入的8bit的串口信号接收并解码成两个通道的幅度、频率和相位数据输出。
难点:
1. 多个字节接收如何实现?
2. 目前常见的是uart传输8bit信息,但是如果要传输一帧,多个字节时,他们之间的间隔将多长?
为很好的解决这个问题,先对uart_rev这部分先编程、仿真并调试。 然后在上面的基础上进行连续串口信号的接收、仿真及调试。
参考博文:实战篇:UART串口连续接收实现_weixin_41155462的博客-CSDN博客
DDS_CTRL模块:
主要用解码输出的频率、相位、幅度信号,控制rom内寄存的正弦波曲线采样输出需要的曲线波形。
1)通过自定义正弦波曲线的.mif文件,生成rom存储器。
2)主要是将正弦波分成个相位,然后利用相位累加器输出每个相位对应的幅值。输出频率主要通过频率控制位B,N以及系统时钟决定,.
因此,需要三个输入变量,N位相位累加器,频率控制位B,以及模块时钟,来实现调频。调相,只需要在输出时增加一个偏移量。调幅,只需要对正弦波的幅值进行乘除运算。
以上,只需要在ROM中存入一个正弦波,通过控制ROM的地址,使其输出不同的值,即可实现对该波形的调频调相和调幅。
3)由于使双通道,因此输入到DA模块的信号应该有两路不同的波形,即两路DDS模块应该有不同的两路控制信号的输入。为区分两路不同的控制信号,给两路dds信号进行地址区分,如下:
地址(十六进制) | 寄存器名 | 寄存器位宽 | 寄存器功能 |
10 | Fword0 | 32 | 通道0频率控制字寄存器 |
11 | Fword1 | 32 | 通道1频率控制字寄存器 |
12 | Pword0 | 12 | 通道0频率相位字寄存器 |
13 | Pword1 | 12 | 通道1频率相位字寄存器 |
14 | Aword0 | 4 | 通道0频率幅度字寄存器 |
15 | Aword1 | 4 | 通道1频率幅度字寄存器 |
DA_CTRL_TOP模块
由于DA模块采用TLV5618,其是双通道的数模转换,且通过SPI协议进行通讯,输入包含SCLK, DIN, CS三个信号。其中通道的选择在DIN的第13和第16Bit位控制。因此,除上面的对控制信号进行划分通道外,还需要产生DA模块通讯的时钟信号和不同通道所需的DIN通道选择和电源、速度的控制信号。
此部分,通道的选择放在DA模块,专门用于两路信号输入,转换成串口信号输出到DA模块,代码如下:
module DA_CTRL_TOP(
input clk,rst_n,
input [9:0]data_a,data_b,
output wire SCLK,CS,DIN,
output wire da_done);
reg [15:0]da_data;
wire DA_state; //DA模块的转换状态,=1表示模块正在转换成串口信号输出,=0表示空闲,可以继续往DA模块中输入并行数据
//开始信号的产生
reg start; //当输入的10bit信号转换为不同通道的da_data时,生成开始信号,da模块收到start信号后,开始将并行数据转换为串行数据输出//
always @(posedge clk or negedge rst_n) begin
if(~rst_n) start<=0;
else if(start==0) start<=1; //start =1,表示可以开始输出需要转换的数据给DA模块
else if(da_done) start<=0;
end
// 4bit控制字产生
wire [3:0]ctrl_world;
assign ctrl_world=(channel)?4'b0101:4'b1100;
// 通道选择
reg channel;
always @(posedge clk or negedge rst_n) begin
if(~rst_n) channel<=0;
else if(da_done) channel<=channel+1'b1;
end
// 信号输出
always @(posedge clk or negedge rst_n) begin
if(~rst_n) da_data<=16'd0;
else begin
case(channel)
0: da_data<={ctrl_world,2'b00,data_a};
1: da_data<={ctrl_world,2'b00,data_b};
endcase
end
end
da_ctrl da_ctrl(
.clk(clk),
.rst_n(rst_n),
.data_in(da_data),
.start(start),
.SCLK(SCLK),
.CS(CS),
.DIN(DIN),
.da_done(da_done),
.DA_state(DA_state));
endmodule
问题点:
1. CLK和EN应该如何选取?
因为EN=1时,从ROM中依次读取数据,而DA模块输出串行的数字信号需要的时钟最大为20M,这样的话从DDS模块输出的data频率不宜太快,否则数据来不及转换。
因此,在DA转换模块输出每次转换结束的标志为da_done,收到此信号后,dds模块从rom中提取一个数据并进行频率相位幅度的转换,然后输出给DA模块。所以CLK为系统的时钟即可,所有的模块可以公用这一个时钟信号,EN为DDS片选信号使能,EN=1,选择DDS模块,是这一路数据成功输出并传给DA模块。
2. 代码如何区分两个dds的地址输入?
通过decoder模块,将输入的信号通过地址解析出2个通道6个信号的数据,然后分别连接到两个dds模块。
decoder模块
代码如下:
module decoder(
input clk,rst_n,
input [63:0]uart_fram_din,
input uart_fram_begin,uart_fram_end,
output reg [31:0]Fword0,Fword1,
output reg [11:0]Pword0,Pword1,
output reg [3:0]Aword0,Aword1,
output reg data_error,data_valid);
// data address
parameter Fword0_addr=8'h10,
Fword1_addr=8'h11,
Pword0_addr=8'h12,
Pword1_addr=8'h13,
Aword0_addr=8'h14,
Aword1_addr=8'h15;
parameter IDLE=4'b0000,
fram_begin=4'b0001,
fram_data=4'b0010,
fram_wait=4'b0100,
fram_end=4'b1000;
reg [3:0]main_state;
//input data register
reg [63:0]uart_fram_din_r;
reg uart_fram_end_r,uart_fram_begin_r;
wire uart_fram_end_flag,uart_fram_begin_flag;
wire [7:0]Addr; //输入地址寄存,用于判断输入数据的类型
assign Addr=uart_fram_din_r[23:16];
assign uart_fram_end_flag=(~uart_fram_end_r & uart_fram_end)? 1'b1:1'b0;
assign uart_fram_begin_flag=(~uart_fram_begin_r & uart_fram_begin)? 1'b1:1'b0;
always @(posedge clk or negedge rst_n)begin
if(~rst_n)begin
uart_fram_din_r<=64'd0;
uart_fram_end_r<=0;
uart_fram_begin_r<=0;
end
else begin
uart_fram_din_r<=uart_fram_din;
uart_fram_end_r<=uart_fram_end;
uart_fram_begin_r<=uart_fram_begin;
end
end
//输入数据判断,并产生频率、相位、幅度控制信号
always @(posedge clk or negedge rst_n)begin
if(~rst_n) main_state<=IDLE;
else case (main_state)
IDLE:begin
if(uart_fram_begin_flag) main_state<=fram_begin;
else main_state<=IDLE;
end
fram_begin:begin
if (uart_fram_din_r[15:0]==16'h0x03AA) main_state<=fram_data;
else main_state<=IDLE;
end
fram_data:begin
main_state<=fram_wait;
end
fram_wait:begin
if(uart_fram_din_r[63:56]==8'h0x88) main_state<=fram_end;
else main_state<=IDLE;
end
fram_end:begin
main_state<=IDLE;
end
endcase
end
always @(*)begin
if(~rst_n)begin
Fword0<=32'd0;
Fword1<=32'd0;
Pword0<=12'd0;
Pword1<=12'd0;
Aword0<=4'd0;
Aword1<=4'd0;
end
else begin
case(main_state)
IDLE:begin
data_valid<=0;
data_error<=0;
end
fram_begin:begin
if(uart_fram_din_r[15:0]==16'h0x03AA) data_error<=1;
else data_error<=data_error;
end
fram_data:begin
data_valid<=1;
case(Addr)
Fword0_addr: begin Fword0<=uart_fram_din_r[55:24]; end
Fword1_addr: begin Fword1<=uart_fram_din_r[55:24]; end
Pword0_addr: begin Pword0<=uart_fram_din_r[35:24]; end
Pword1_addr: begin Pword1<=uart_fram_din_r[35:24]; end
Aword0_addr: begin Aword0<=uart_fram_din_r[27:24]; end
Aword1_addr: begin Aword1<=uart_fram_din_r[27:24]; end
endcase
end
fram_wait:begin
if(uart_fram_din_r[63:56]==8'h0x88) data_valid<=1;
else begin
data_error<=1;
data_valid<=data_valid;
end
end
endcase
end
end
endmodule
DA_CTRL模块:
这里采用双通道的DA模块TL5618,通过两个DDS_CTRL模块提供两路数据。
问题点:
1. DA模块开始信号如何提供?DA什么时候处于工作状态?
在DA_CTRL_TO模块中,复位后生成开始start信号给DA模块,DA模块收到起始标志信号后开始进行数据转换输出,在输出结束后,产生结束标志信号反馈给DDS模块。DDS模块收到反馈信号再次读取波形数据,传给DA模块。形成闭环。
此部分代码如下:
module da_ctrl(
input clk,rst_n,
input [15:0]data_in, //通过同一个总线输入两路信号,经过DA模块转换输出两路模拟信号
input start,
output reg SCLK,CS,
output reg DIN,
output wire da_done,
output reg DA_state); //DA模块的转换状态,=1表示模块正在输出,=0表示空闲,可以继续接收信号
reg [15:0]data_in_r;
reg [1:0]start_r;
reg [5:0]clk_cnt;
reg [5:0]da_clk_cnt;
reg da_clk; //产生2.5M的时钟
wire start_flag;
parameter [4:0]da_fre_cnt_num=5'd20; //
assign start_flag = (!start_r[1] & start_r[0])? 1'b1:1'b0;
assign da_done = (da_clk_cnt==6'd33)?1'b1:1'b0;
// input data register //当start_flag=1,才开始寄存输入的信号,并转换为DA模块所需要的数据输出,没有开始标志信号,输入信号维持不变
always @(posedge clk or negedge rst_n)begin
if(~rst_n) data_in_r<=0;
else if (start_flag) data_in_r<=data_in;
else data_in_r<=data_in_r;
end
always @(posedge clk or negedge rst_n)begin
if(~rst_n)begin
start_r<=0;
end
else begin
start_r[0]<=start;
start_r[1]<=start_r[0];
end
end
always @(posedge clk or negedge rst_n)begin
if(~rst_n) DA_state<=0;
else if(start_flag) DA_state<=1;
else if(da_done) DA_state<=0;
else DA_state<=DA_state;
end
always @(posedge clk or negedge rst_n)begin
if(~rst_n) clk_cnt<=0;
else if(DA_state) begin
if(clk_cnt==da_fre_cnt_num-1'b1) clk_cnt<=0;
else clk_cnt<=clk_cnt+1'b1;
end
else clk_cnt<=0;
end
always @(posedge clk or negedge rst_n)begin
if(~rst_n) da_clk<=0;
else if(DA_state &(clk_cnt<=da_fre_cnt_num/2-1)) da_clk<=1;
else da_clk<=0;
end
always @(posedge clk or negedge rst_n)begin
if(~rst_n) da_clk_cnt<=0;
else if(da_clk_cnt>=6'd33) da_clk_cnt<=0;
else begin
if(DA_state & (clk_cnt==da_fre_cnt_num-1)) da_clk_cnt<=da_clk_cnt+1'b1;
else da_clk_cnt<=da_clk_cnt;
end
end
always @(posedge clk or negedge rst_n)begin
if(~rst_n)begin
SCLK<=0;
DIN<=0;
CS<=1;
end
else begin
case(da_clk_cnt)
6'd0: begin SCLK<=0; DIN<=data_in_r[15]; CS<=1; end
6'd1: begin SCLK<=1; DIN<=data_in_r[15]; CS<=0; end
6'd3: begin SCLK<=1; DIN<=data_in_r[14]; end
6'd5: begin SCLK<=1; DIN<=data_in_r[13]; end
6'd7: begin SCLK<=1; DIN<=data_in_r[12]; end
6'd9: begin SCLK<=1; DIN<=data_in_r[11]; end
6'd11: begin SCLK<=1; DIN<=data_in_r[10]; end
6'd13: begin SCLK<=1; DIN<=data_in_r[9]; end
6'd15: begin SCLK<=1; DIN<=data_in_r[8]; end
6'd17: begin SCLK<=1; DIN<=data_in_r[7]; end
6'd19: begin SCLK<=1; DIN<=data_in_r[6]; end
6'd21: begin SCLK<=1; DIN<=data_in_r[5]; end
6'd23: begin SCLK<=1; DIN<=data_in_r[4]; end
6'd25: begin SCLK<=1; DIN<=data_in_r[3]; end
6'd27: begin SCLK<=1; DIN<=data_in_r[2]; end
6'd29: begin SCLK<=1; DIN<=data_in_r[1]; end
6'd31: begin SCLK<=1; DIN<=data_in_r[0]; end
6'd2,6'd4,6'd6,6'd8,6'd10,6'd12,6'd14,6'd16,6'd18,6'd20,6'd22,6'd24,6'd26,6'd28,6'd30,6'd32,6'd34:
begin SCLK<=0; end
6'd33: begin SCLK<=0; CS<=1;end
default: ;
endcase
end
end
endmodule