一、项目要求
·本地控制
1、通过板上的高速DAC(10bits/最高125Msps)配合FPGA内部DDS的逻辑(最高48Msps),生成波形可调(正弦波、三角波、方波)、频率可调(DC-)、幅度可调的波形 2、生成模拟信号的频率范围为DC-5MHz,调节精度为1Hz 3、生成模拟信号的幅度为最大1Vpp,调节范围为0.1V-1V 4、在OLED上显示当前波形的形状、波形的频率以及幅度 5、利用板上旋转编码器和按键能够对波形进行切换、进行参数调节
·远程控制
1、通过板上的高速DAC(10bits/125Msps)配合FPGA内部DDS的逻辑,生成2、波形可调(正弦波、三角波、方波)、频率可调、幅度可调的波形 3、生成模拟信号的频率范围为DC-5MHz,调节精度为1Hz 4、生成模拟信号的幅度为最大1Vpp,调节范围为0.1V-1V 5、通过UART同PC连接,在PC上可以使用Matlab、Labview或其它调试工具来控制波形的切换、参数的改变
二、设计思路
因第一次使用FPGA做实际的项目实现,我选择了较为简单的DDS任意波形信号发生器,并且因为本地控制与PC远程控制的区别不是很大,所以我就想着把他们一起实现。 1、尽量利用板卡的单片机+FPGA组合优势,FPGA部分做波形的产生,单片机做人机交互。 2、FPGA的波形产生的思路是硬禾的教程中的相位累加变化和查表; 3、单片机读取按键和旋转编码器的输入对各种波形的参数进行修改,并通过OLED屏幕显示出来,以及通过SPI给FPGA发送控制指令; 4、PC远程控制的上位机使用NET framwork开发,将波形的参数信息进行打包(加上帧头、帧尾及校验和)串口发送,单片机串口接收解包后再控制FPGA的波形产生。
三、实现过程
此前主要是使用单片机做各种事情,对FPGA的了解和使用的比较少。因此先着手FPGA部分的功能实现。
FPGA部分 1、对DDS的学习与实现
DDS(直接数字频率合成)是一种用于通过单个固定频率的参考时钟信号生成任意波形的频率合成器。DDS系统的核心是相位累加器,其内容在每个时钟周期更新。也就是说我们可以通过相位的变化对我们准备好的查找表中的波形数据进行抽样,即实现波形频率的控制。再对波形数据进行移位实现幅度的控制。 DDS一般是借助于一个DAC将数字合成的波形转化为模电信号,我这次使用的板卡上面是有一个高速并行的DAC模块他最大驱动时钟可达125Msps,10位并行输入,单通道输出。
FPGA的效果可通过查看仿真波形与自己预期的结果进行比较,所以我使用Lattice Radiant关联的modelsim仿真。 首先是编写testbench,再在Radiant中建立仿真脚本文件,并把相关联的verilog文件添加进去,再设置好仿真运行时间,就可以在modelsim中看到输出的波形结果了。 我将波形产生这部分单独作为一个模块,输入参数为波形,幅度,频率,输出结果为DAC时钟对应的10位数据。 具体实现就是上面所说的,控制相位累加器的步进值,实现对波形查找表的抽样输出。但因为我对相位的步进只是对输入频率做简单的相乘,导致输出精度会下降。因此,我加入一个简单的反馈部分,对输出波形数据进行频率测量,进行大小比较,再做细微步进值调整。 频率测量的思路为,先将输出的各种波形通过设置触发比较电压转化为矩形波,我们再对这个矩形波进行等精度频率测量(此处参考新起点之FPGA开发指南),等精度频率测量的主要原理公式为 fs_cnt/CLK_FS=GATE_TIME=fx_cnt/clk_fx 通过设置一个预置阀门信号的长度GATE_TIME,而fs_cnt和fx_cnt是2个可控的32位高速计数器,CLK_FS为基准时钟信号频率,被测信号的频率为CLK_FS。测量时生成的GATE信号在被测时钟同步化后用来控制启动和关闭2个计数器,2个计数器分别对被测信号和基准时钟计数。这样就可以在一次实际闸门时间GATE_TIME中得出被测信号的频率。 实现代码如下
module DDS_output (
clk,
clk_48M,
rst_n,
wave_dat,
fre_dat,
amp_dat,
dac_dat,
sin_dat,
squ_dat,
tri_dat,
phase_acc,
phase_acc_modify
);
input clk, clk_48M, rst_n; //48MHz/12MHz
input [7:0] wave_dat, amp_dat;
input [23:0] fre_dat; //使用24位方便计数最大5M,100 1100 0100 1011 0100 0000
input [23:0] phase_acc_modify;
output reg [9:0] dac_dat; //reg
output wire [9:0] sin_dat, squ_dat, tri_dat;
output reg [31:0] phase_acc; //30位相位累加器,精度0.7Hz 48MHZ
wire [23:0] dds_signal_freq; // 被测时钟频率输出
wire dac_pulse;
// wire [23:0] dds_phase_modify;
always @(posedge clk_48M)
if (!rst_n) phase_acc <= 32'd0;
else
phase_acc <= phase_acc + fre_dat *24'd89 +phase_acc_modify; // 在48MHz的主时钟时,输出对应频率的波形 + fre_dat * 24'd91
/*
48m-26位相位累加,频率5M最大23位
相位累加器第18位为1时(18'd262144),输出187.5KHZ=48M /2^8
相位累加器第17位为1时(18'd131072),输出93.75KHZ=48M /2^9
相位累加器第0 位为1时(1'd1), 输出0.7HZ=48m /2^26
*/
lookup_tables_sin u_lookup_table_sin (
.phase (phase_acc[31:24]),
.sin_out(sin_dat)
); //波形查找表 正弦波
lookup_tables_squ u_lookup_table_squ (
.phase (phase_acc[31:24]),
.squ_out(squ_dat)
); //波形查找表 方波
lookup_tables_tri u_lookup_table_tri (
.phase (phase_acc[31:24]),
.tri_out(tri_dat)
); //波形查找表 三角波
wire [ 7:0] a_ver; //用于调幅的因数
reg [17:0] sin_data; //调幅后的波形数据
reg [17:0] squ_data; //调幅后的波形数据
reg [17:0] tri_data; //调幅后的波形数据
reg [ 9:0] DC_data;
always @(*) begin
case (amp_dat)
8'd0: DC_data <= 10'd306;
8'd1: DC_data <= 10'd340;
8'd2: DC_data <= 10'd408; //
8'd3: DC_data <= 10'd442;
8'd4: DC_data <= 10'd476;
8'd5: DC_data <= 10'd511; //
8'd6: DC_data <= 10'd562;
8'd7: DC_data <= 10'd612;
8'd8: DC_data <= 10'd646;
8'd9: DC_data <= 10'd680;
8'd10: DC_data <= 10'd714;
default: DC_data <= DC_data;
endcase
end
amp_to_ver u_mp2ver (
amp_dat,
a_ver
);
always @(posedge clk) begin
sin_data = sin_dat * a_ver;
squ_data = squ_dat * a_ver;
tri_data = tri_dat * a_ver;
end
always @(posedge clk or negedge rst_n) begin
case (wave_dat)
8'd0: begin
dac_dat <= DC_data;
end
8'd1: begin
dac_dat <= squ_data[17:8];
end //方波
8'd2: begin
dac_dat <= tri_data[17:8];
end //三角波
8'd3: begin
dac_dat <= sin_data[17:8];
end //正弦波
endcase
end
endmodule
仿真波形的testbench代码如下:
module adc_in_tb ();
parameter CLK_48M_DELAY = 1; // 2.5 MHz
parameter CLK_12M_DELAY = 4; // 25 MHz
logic clk_48M = 1'b0;
logic clk_12M = 1'b0;
logic r_Rst_L = 1'b1;
logic ad_pulse_gen = 1'b0;
logic [23:0] ad_freq = 24'd0;
logic [ 9:0] ad_vpp = 10'd0;
logic [ 9:0] ad_max = 10'd0;
logic [ 9:0] ad_min = 10'd0;
logic [ 9:0] u_trig_level = 10'd200;
logic [ 7:0] wave_dat = 8'd3;
logic [ 7:0] amp_dat = 8'd10;
logic [23:0] fre_dat = 20'd50000;
logic [ 9:0] dac_dat = 10'd0;
logic [31:0] phase_acc = 32'd0;
logic [23:0] phase_acc_modify = 24'd0;
logic [9:0] sin_dat, squ_dat, tri_dat;
always #(CLK_48M_DELAY) clk_48M = ~clk_48M;
always #(CLK_12M_DELAY) clk_12M = ~clk_12M;
DDS_output dds_out_module (
.clk(clk_12M),
.clk_48M(clk_48M),
.rst_n(r_Rst_L),
.wave_dat(wave_dat),
.fre_dat(fre_dat),
.amp_dat(amp_dat),
.dac_dat(dac_dat),
.sin_dat(sin_dat),
.squ_dat(squ_dat),
.tri_dat(tri_dat),
.phase_acc(phase_acc),
.phase_acc_modify(phase_acc_modify)
);
param_measure u_param_measure (
.clk(clk_48M),
.rst_n(r_Rst_L),
.trig_level(u_trig_level),
.ad_clk(clk_48M),
.ad_data(sin_dat),
.ad_pulse(ad_pulse_gen),
.ad_freq(ad_freq),
.ad_vpp(ad_vpp),
.ad_max(ad_max),
.ad_min(ad_min)
);
dds_phase_modify U_dds_phase_modify(
.clk(ad_pulse_gen),
.rst_n(r_Rst_L),
.target_fre(fre_dat),
.measure_fre(ad_freq),
.phase_acc_modify(phase_acc_modify)
);
initial begin
repeat (10) @(posedge clk_12M);
r_Rst_L = 1'b0;
repeat (10) @(posedge clk_12M);
r_Rst_L = 1'b1;
repeat (10000) @(posedge clk_12M);
repeat (10000) @(posedge clk_12M);
fre_dat = 24'd900000;
repeat (10000) @(posedge clk_12M);
repeat (10000) @(posedge clk_12M);
// fre_dat=24'd2662144;
repeat (10000) @(posedge clk_12M);
repeat (10000) @(posedge clk_12M);
// fre_dat=24'd62144;
modelsim仿真波形图如下:
波形图1
波形图2
波形图1中显示了输出频率超过设置频率,波形图2中显示了通过反馈调节将频率再降低。但当我在实物测试时发现加不加反馈调节的频率区别不大,猜测是相位调整参数的触发时钟与频率测量时间不匹配,具体还需进一步的测试验证。至此,DDS输出的大体上是结束了。
2、FPGA与单片机的SPI通信
这部分主要是解决FPGA作为SPI从机时,接收单片机发来的波形参数。 SPI这种通信方式此前使用较多(自己感觉较为熟悉,故不打算再细写),只需先约定好SPI通信的工作模式(CPOL与CPHA组合成的4种方式之一)。 其次是数据的划分接收,我计划的通信方式将波形参数打包,单片机一次性发送6个字节(帧头一个字节、波行和幅度各一个字节,再加上3个字节的频率信息),FPGA这边通过状态机的方式把数据提取出来。一共四个状态,分别为闲置状态、波形提取状态、幅度提取状态、频率提取状态。一开始处于闲置状态,当检测到帧头0xFF时,进行依次接收,频率提取状态开启了一个计数器,以便在完整的接收3字节频率后返回闲置状态。 具体实现代码如下:
module DDS_SPI_input (
input clk,
input rst_n,
input spi_sck,
input spi_mosi,
input spi_cs,
output spi_miso,
output w_Slave_RX_DV,
input r_Slave_TX_DV,
output [7:0] w_Slave_RX_Byte,
input [7:0] r_Slave_TX_Byte,
output reg [3:0] read_par_state,
output [7:0] wave_dat,
output [7:0] amp_dat,
output [23:0] fre_dat
);
SPI_Slave u_spi_slave (
// Control/Data Signals,
.i_Rst_L (rst_n), // FPGA Reset
.i_Clk (clk), // FPGA Clock
.o_RX_DV (w_Slave_RX_DV), // Data Valid pulse (1 clock cycle)
.o_RX_Byte(w_Slave_RX_Byte), // Byte received on MOSI
.i_TX_DV (r_Slave_TX_DV), // Data Valid pulse
.i_TX_Byte(r_Slave_TX_Byte), // Byte to serialize to MISO (set up for loopback)
// SPI Interface
.i_SPI_Clk (spi_sck),
.o_SPI_MISO(spi_miso),
.i_SPI_MOSI(spi_mosi),
.i_SPI_CS_n(spi_cs)
);
reg [ 7:0] u_wave_dat;
reg [ 7:0] u_amp_dat;
reg [23:0] u_fre_dat; //3 bytes
// reg [3:0] read_par_state;
assign wave_dat = u_wave_dat;
assign amp_dat = u_amp_dat;
assign fre_dat = u_fre_dat;
localparam IDLE = 4'b0001, STATE1 = 4'b0010, STATE2 = 4'b0100, STATE3 = 4'b1000;
reg [1:0] STATE3_counter;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
u_wave_dat <= 8'd0;
u_amp_dat <= 8'd0;
u_fre_dat <= 24'd0;
read_par_state <= IDLE;
STATE3_counter <= 2'd0;
end else
case (read_par_state)
IDLE: begin
if (w_Slave_RX_DV) begin
if (w_Slave_RX_Byte == 8'd255 && read_par_state == IDLE) read_par_state <= STATE1;
else read_par_state <= IDLE;
end
end
STATE1: begin
if (w_Slave_RX_DV) begin
u_wave_dat <= w_Slave_RX_Byte;
read_par_state <= STATE2;
end else read_par_state <= STATE1;
end
STATE2: begin
if (w_Slave_RX_DV) begin
u_amp_dat <= w_Slave_RX_Byte;
read_par_state <= STATE3;
end else read_par_state <= STATE2;
end
STATE3: begin
if (w_Slave_RX_DV) begin
u_fre_dat <= {u_fre_dat[15:0], w_Slave_RX_Byte};
STATE3_counter <= STATE3_counter + 2'b1;
end else if (STATE3_counter == 2'b11) begin
STATE3_counter <= STATE3_counter + 2'b1;
read_par_state <= IDLE;
end else read_par_state <= STATE3;
end
default: begin
read_par_state <= IDLE;
end
endcase
end
endmodule
仿真SPI接收的testbench代码如下:
module SPI_RX_TB ();
parameter SPI_MODE = 0; // CPOL = 0, CPHA = 0
parameter SPI_CLK_DELAY = 20; // 2.5 MHz
parameter MAIN_CLK_DELAY = 4; // 25 MHz
parameter s4_CLK_DELAY = 1;
logic w_CPOL; // clock polarity
logic w_CPHA; // clock phase
assign w_CPOL = (SPI_MODE == 2) | (SPI_MODE == 3);
assign w_CPHA = (SPI_MODE == 1) | (SPI_MODE == 3);
logic r_Rst_L = 1'b1;
logic [7:0] dataPayload [0:255];
logic [7:0] dataLength;
// CPOL=0, clock idles 0. CPOL=1, clock idles 1
// logic r_SPI_Clk = w_CPOL ? 1'b1 : 1'b0;
logic w_SPI_Clk;
logic r_SPI_En = 1'b0;
logic r_Clk = 1'b0;
logic clk_4s = 1'b0;
logic w_SPI_CS_n;
logic w_SPI_MOSI;
logic w_SPI_MISO;
// Master Specific
logic [7:0] r_Master_TX_Byte = 0;
logic r_Master_TX_DV = 1'b0;
logic r_Master_CS_n = 1'b1;
logic w_Master_TX_Ready;
logic r_Master_RX_DV;
logic [7:0] r_Master_RX_Byte;
// Slave Specific
logic w_Slave_RX_DV, r_Slave_TX_DV;
logic [7:0] w_Slave_RX_Byte, r_Slave_TX_Byte;
logic [ 7:0] send_wave_data = 8'd2;
logic [ 7:0] send_amp_data = 8'd10;
logic [23:0] send_fre_data = 24'h0f4240;
logic [ 3:0] read_par_state;
logic [ 7:0] wave_dat;
logic [ 7:0] amp_dat;
logic [23:0] fre_dat;
logic [ 9:0] dac_dat;
logic [9:0] sin_dat, squ_dat, tri_dat;
// Clock Generators:
always #(MAIN_CLK_DELAY) r_Clk = ~r_Clk;
always #(s4_CLK_DELAY) clk_4s = ~clk_4s;
// Instantiate Master to drive Slave
SPI_Master #(
.SPI_MODE(SPI_MODE),
.CLKS_PER_HALF_BIT(2)
) SPI_Master_UUT (
// Control/Data Signals,
.i_Rst_L(r_Rst_L), // FPGA Reset
.i_Clk (r_Clk), // FPGA Clock
// TX (MOSI) Signals
.i_TX_Byte (r_Master_TX_Byte), // Byte to transmit on MOSI
.i_TX_DV (r_Master_TX_DV), // Data Valid Pulse with i_TX_Byte
.o_TX_Ready(w_Master_TX_Ready), // Transmit Ready for Byte
// RX (MISO) Signals
.o_RX_DV (r_Master_RX_DV), // Data Valid pulse (1 clock cycle)
.o_RX_Byte(r_Master_RX_Byte), // Byte received on MISO
// SPI Interface
.o_SPI_Clk (w_SPI_Clk),
.i_SPI_MISO(w_SPI_MISO),
.o_SPI_MOSI(w_SPI_MOSI)
);
DDS_SPI_input u_input (
.clk(r_Clk),
.rst_n(r_Rst_L),
.spi_sck(w_SPI_Clk),
.spi_mosi(w_SPI_MOSI),
.spi_cs(r_Master_CS_n),
.spi_miso(w_SPI_MISO),
.w_Slave_RX_DV(w_Slave_RX_DV),
.r_Slave_TX_DV(w_Slave_RX_DV),
.w_Slave_RX_Byte(w_Slave_RX_Byte),
.r_Slave_TX_Byte(w_Slave_RX_Byte),
.read_par_state(read_par_state),
.wave_dat(wave_dat),
.amp_dat(amp_dat),
.fre_dat(fre_dat)
);
DDS_output u_dds_output (
.clk(r_Clk),
.clk_48M(clk_4s),
.rst_n(r_Rst_L),
.wave_dat(wave_dat),
.fre_dat(fre_dat),
.amp_dat(amp_dat),
.dac_dat(dac_dat),
.sin_dat(sin_dat),
.squ_dat(squ_dat),
.tri_dat(tri_dat)
);
// Sends a single byte from master to slave. Will drive CS on its own.
task SendSingleByte(input [7:0] data);
@(posedge r_Clk);
r_Master_TX_Byte <= data;
r_Master_TX_DV <= 1'b1;
r_Master_CS_n <= 1'b0;
@(posedge r_Clk);
r_Master_TX_DV <= 1'b0;
@(posedge w_Master_TX_Ready);
r_Master_CS_n <= 1'b1;
endtask // SendSingleByte
// Sends a multi-byte transfer from master to slave. Drives CS on its own.
task SendMultiByte(input [7:0] data[0:255], input [7:0] length);
integer ii;
@(posedge r_Clk);
r_Master_CS_n <= 1'b0;
for (ii = 0; ii < length; ii++) begin
@(posedge r_Clk);
r_Master_TX_Byte <= data[ii];
r_Master_TX_DV <= 1'b1;
@(posedge r_Clk);
r_Master_TX_DV <= 1'b0;
@(posedge w_Master_TX_Ready);
end
r_Master_CS_n <= 1'b1;
endtask // SendMultiByte
initial begin
repeat (10) @(posedge r_Clk);
r_Rst_L = 1'b0;
repeat (10) @(posedge r_Clk);
r_Slave_TX_DV <= 1'b0;
repeat (100) @(posedge r_Clk);
dataPayload[0] = 8'd255;
dataPayload[1] = send_wave_data;
dataPayload[2] = send_amp_data;
dataPayload[3] = send_fre_data[23:16];
dataPayload[4] = send_fre_data[15:8];
dataPayload[5] = send_fre_data[7:0];
dataLength = 6;
SendMultiByte(dataPayload, dataLength);
repeat (100) @(posedge r_Clk);
repeat (100) @(posedge r_Clk);
dataPayload[0] = 8'd255;
dataPayload[1] = 8'd3;
dataPayload[2] = 8'd10;
dataPayload[3] = 8'h1;
dataPayload[4] = 8'h86;
dataPayload[5] = 8'ha0;
dataLength = 6;
SendMultiByte(dataPayload, dataLength);
repeat (100) @(posedge r_Clk);
// $finish();
end // initial begin
endmodule // SPI_Slave
modelsim仿真SPI接收的波形图如下:
波形图3
如波形图3所示,每接收一个波形数据,read_par_state向左移一位,并装载对应参数。
3、FPGA顶层文件分析
顶层文件如下:
module terminal_top (
input clk,
input rst_n,
input spi_sck,
input spi_mosi,
input spi_cs,
output spi_miso,
output dac_clk,
output [9:0] dac_data,
input [9:0] ad_data,
output ad_clk
);
wire clk_12M, clk_48M;
wire [7:0] wave_dat, amp_dat;
wire [23:0] fre_dat;
wire [9:0] dac_dat;
reg fpga_Slave_TX_DV;
reg [7:0] fpga_Slave_TX_Byte;
wire [19:0] ad_freq; // 被测时钟频率输出
wire [9:0] ad_vpp; // AD峰峰值
wire [9:0] ad_max; // AD最大值
wire [9:0] ad_min; // AD最小值
wire ad_pulse_gen;
wire [9:0] u_trig_level;
PLL_48M m_pll (
.ref_clk_i(clk),
.rst_n_i(rst_n),
.outcore_o(clk_12M),
.outglobal_o(clk_48M)
);
reg [23:0] sys_counter;
always @(posedge clk_48M) begin
if (!rst_n) sys_counter = 24'd0;
else sys_counter = sys_counter + 24'd1;
end
assign dac_clk = sys_counter[0];
assign adc_clk = sys_counter[0];
DDS_output u_dds_output (
.clk(clk_12M),
.clk_48M(clk_48M),
.rst_n(rst_n),
.wave_dat(wave_dat),
.fre_dat(fre_dat),
.amp_dat(amp_dat),
.dac_dat(dac_dat),
.phase_acc_modify(phase_acc_modify)
);
assign dac_data = dac_dat;
DDS_SPI_input u_dds_spi_input (
.clk(clk_48M),
.rst_n(rst_n),
.spi_sck(spi_sck),
.spi_mosi(spi_mosi),
.spi_cs(spi_cs),
.spi_miso(spi_miso),
.r_Slave_TX_DV (fpga_Slave_TX_DV),
.r_Slave_TX_Byte(fpga_Slave_TX_Byte),
.wave_dat(wave_dat),
.amp_dat (amp_dat),
.fre_dat (fre_dat)
);
param_measure u_param_measure (
.clk(clk_48M),
.rst_n(r_Rst_L),
.trig_level(u_trig_level),
.ad_clk(clk_48M),
.ad_data(ad_data),
.ad_pulse(ad_pulse_gen),
.ad_freq(ad_freq),
.ad_vpp(ad_vpp),
.ad_max(ad_max),
.ad_min(ad_min)
);
dds_phase_modify U_dds_phase_modify (
.clk(ad_pulse_gen),
.rst_n(r_Rst_L),
.target_fre(fre_dat),
.measure_fre(ad_freq),
.phase_acc_modify(phase_acc_modify)
);
endmodule
主要分为PLL倍频,SPI输入,DDS输出,参数测量,相位校正这几部分。本是没有参数测量与相位校正这两部分的,但在实现DDS输出后想把单通道示波器部分也解决掉,所以去了解了频率、峰峰值的测量方法。后来发现输出的DDS频率精度缺失又与交流群里的朋友讨论后,我萌生了做反馈调节的想法,也就是DDS输出部分所提到的,有一点点像锁相环的实现,但还过于简陋,等后面有时间我再继续完善下这个想法。还有就是,频率、峰峰值的测量已经没有太大问题,也算是示波器的实现做了个铺垫吧。示波器的功能实现还差波形的显示没有搞定,本来是想FPGA定时发送一段连续的波形数据,单片机再做抽样显示。但在FPGA发送这一块脑子里一直还没有特别清晰的办法,还需要再查阅一些资料再下决定。 FPGA部分的工作到此结束,最后附资源使用情况图:
STM32单片机部分
使用板卡上的STM32单片机连接了OLED屏幕、旋转编码器和按键(陀螺仪未用到)组成人机交互界面。 按键扫描部分使用的是MultiButton驱动,将每个按键作为一个对象,定时进行按键检测(将按键状态存入缓冲),然后在需要读取时,读取按键状态。 旋转编码器使用外部中断检测,两者代码实现如下:
struct Button key1;
struct Button key2;
struct Button encoder_key;
static uint8_t encoder_variable;
uint8_t encoder_ratate_direction(void)
{
static uint8_t last_encoder_variable;
if (last_encoder_variable > encoder_variable)
{
last_encoder_variable = encoder_variable;
return 2;
}
else if (last_encoder_variable < encoder_variable)
{
last_encoder_variable = encoder_variable;
return 1;
}
else
{
return 0;
}
}
void Soft_Delay(uint16_t ntimes)
{
for (uint16_t i = 0; i < ntimes; i++)
{
for (uint16_t j = 0; j < 100; j++)
;
}
}
void HAL_GPIO_EXTI_Falling_Callback(uint16_t GPIO_Pin)
{
if (Encoder_A_Pin == GPIO_Pin)
{
Soft_Delay(20);
if (0 == HAL_GPIO_ReadPin(Encoder_A_GPIO_Port, Encoder_A_Pin))
{
if (HAL_GPIO_ReadPin(Encoder_B_GPIO_Port, Encoder_B_Pin))
{
// HAL_UART_Transmit(&huart2, "R\n", 2, 0XFFFF);
encoder_variable++;
}
else
{
// HAL_UART_Transmit(&huart2, "L\n", 2, 0XFFFF);
encoder_variable--;
}
}
}
}
uint8_t Get_EncoderValue(void)
{
return encoder_variable;
}
uint8_t read_button1_GPIO()
{
return HAL_GPIO_ReadPin(SW1_GPIO_Port, SW1_Pin);
}
uint8_t read_button2_GPIO()
{
return HAL_GPIO_ReadPin(SW2_GPIO_Port, SW2_Pin);
}
uint8_t read_button_encoder_GPIO()
{
return HAL_GPIO_ReadPin(Encoder_S_GPIO_Port, Encoder_S_Pin);
}
void Button_init(void)
{
button_init(&key1, read_button1_GPIO, 0);
button_init(&key2, read_button2_GPIO, 0);
button_init(&encoder_key, read_button_encoder_GPIO, 0);
button_start(&key1);
button_start(&key2);
button_start(&encoder_key);
}
#define BUTTON_EVENT(m, n) m == PRESS_UP ? n : mkey_none
Key_State Get_Key_State(void)
{
static uint8_t btn1_event_val = 0, btn2_event_val = 0, btn3_event_val = 0;
if (btn1_event_val != get_button_event(&key1))
{
btn1_event_val = get_button_event(&key1);
return BUTTON_EVENT(btn1_event_val, mKey1);
}
else if (btn2_event_val != get_button_event(&key2))
{
btn2_event_val = get_button_event(&key2);
return BUTTON_EVENT(btn2_event_val, mKey2);
}
else if (btn3_event_val != get_button_event(&encoder_key))
{
btn3_event_val = get_button_event(&encoder_key);
return BUTTON_EVENT(btn3_event_val, mKeyEnc);
}
else
return mkey_none;
}
交互界面实现了一个简易的参数切换控制,按下旋转编码器实现不同参数控制的切换,转动编码器进行参数修改,再按下K1发送波形参数给FPGA,主要见DDS_Task_Running函数,上位机控制实现见DDS_UART_Task_Running函数。本想将两部分放一起,实现任务切换,但好像因为我使用的SSD1306驱动占用了太多资源,导致Flash空间不够。不能把我实现的两种控制方式一起写进去。
交互过程代码如下:
/*
* dds_control.c
*
* Created on: 2023年2月6日
* Author: starry
*/
#include "dds_control.h"
#include "spi.h"
#include "usart.h"
#include "main.h"
#include "encoder_handle.h"
#include "ssd1306.h"
#include "app_main.h"
#include "stdio.h"
#define fpga_connect_spi hspi1
static struct _DDS_CONTROL_Parameter dds_con_par; // dds control parameters to fpga
static Key_State dds_key_state;
static uint8_t rotate_direction;
static uint8_t change_chosed_par;
static uint8_t fre_pos_change = 1;
void Uart_Receive_Analysis(void);
void LoadParameters(void)
{
uint8_t spi_tx_buff[6];
spi_tx_buff[0] = 0xff;
spi_tx_buff[1] = dds_con_par.wave_state;
spi_tx_buff[2] = dds_con_par.amplitude;
spi_tx_buff[3] = dds_con_par.frequency >> 16;
spi_tx_buff[4] = dds_con_par.frequency >> 8;
spi_tx_buff[5] = dds_con_par.frequency;
HAL_SPI_Transmit(&fpga_connect_spi, spi_tx_buff, 6, 0xFFFF);
}
void DDS_par_show(void)
{
char str[20];
char wave_form[10];
// SSD1306_COLOR m_color1 = (1 == change_chosed_par) ? Black : White,
// m_color2 = (2 == change_chosed_par) ? Black : White,
// m_color3 = (3 == change_chosed_par) ? Black : White;
ssd1306_Fill(Black);
ssd1306_SetCursor(0, change_chosed_par * 10);
ssd1306_WriteChar('>', Font_6x8, White);
ssd1306_SetCursor(10, 10);
switch (dds_con_par.wave_state)
{
case DC:
sprintf(str, "wave_state:DC");
break;
case SQUARE:
sprintf(str, "wave_state:QUARE");
break;
case TRIANGLE:
sprintf(str, "wave_state:TRIANGLE");
break;
case SIN:
sprintf(str, "wave_state:SIN");
break;
default:
break;
}
ssd1306_WriteString(str, Font_6x8, White);
ssd1306_SetCursor(10, 20);
sprintf(str, "amplitude: %d.%d V", dds_con_par.amplitude / 10, dds_con_par.amplitude % 10);
ssd1306_WriteString(str, Font_6x8, White);
ssd1306_SetCursor(10, 30);
sprintf(str, "frequency: %c", fre_pos_change == 1 ? 'h' : (fre_pos_change == 2 ? 'k' : 'm'));
ssd1306_WriteString(str, Font_6x8, White);
ssd1306_SetCursor(0, 40);
sprintf(str, "%d Hz", dds_con_par.frequency);
ssd1306_WriteString(str, Font_6x8, White);
ssd1306_UpdateScreen();
}
void DDS_Task_Running(void)
{
dds_con_par.frequency = 1000;
dds_con_par.amplitude = 10;
while (sys_state == DDS_KEY_TASK)
{
dds_key_state = Get_Key_State();
rotate_direction = encoder_ratate_direction();
switch (dds_key_state)
{
case mKey1:
// printf("key1\n");
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
LoadParameters();
break;
case mKey2:
// printf("key2\n");
if (fre_pos_change < 3)
fre_pos_change++;
else
fre_pos_change = 1;
break;
case mKeyEnc:
// printf("mKeyEnc\n");
if (change_chosed_par < 3)
change_chosed_par++;
else
change_chosed_par = 1;
break;
default:
break;
}
switch (change_chosed_par)
{
case 1:
if (1 == rotate_direction)
{
// printf("R rotate\n");
if (dds_con_par.wave_state < 3)
dds_con_par.wave_state++;
else
{
dds_con_par.wave_state = 0;
}
}
else if (2 == rotate_direction)
{
// printf("L rotate\n");
if (dds_con_par.wave_state > 1)
dds_con_par.wave_state--;
else
dds_con_par.wave_state = 3;
}
break;
case 2:
if (1 == rotate_direction)
{
dds_con_par.amplitude = dds_con_par.amplitude < 10 ? dds_con_par.amplitude + 1 : dds_con_par.amplitude;
}
else if (2 == rotate_direction)
{
dds_con_par.amplitude = dds_con_par.amplitude > 0 ? dds_con_par.amplitude - 1 : dds_con_par.amplitude;
}
break;
case 3:
if (1 == rotate_direction)
{
dds_con_par.frequency += (fre_pos_change == 3 ? 1 : 0) * 1000000 + (fre_pos_change == 2 ? 1 : 0) * 1000 + (fre_pos_change == 1 ? 1 : 0);
}
else if (2 == rotate_direction)
{
dds_con_par.frequency -= (fre_pos_change == 3 ? 1 : 0) * 1000000 + (fre_pos_change == 2 ? 1 : 0) * 1000 + (fre_pos_change == 1 ? 1 : 0);
}
break;
default:
break;
}
DDS_par_show();
}
}
__IO ITStatus UartReady = RESET;
#define RXBUFFERSIZE 8
uint8_t aRxBuffer[RXBUFFERSIZE];
void DDS_UART_Task_Running(void)
{
while (sys_state == DDS_UART_TASK)
{
// printf("start receive\r\n");
if (HAL_UART_Receive_IT(&huart2, (uint8_t *)aRxBuffer, RXBUFFERSIZE) != HAL_OK)
{
Error_Handler();
}
while (UartReady != SET)
{
}
// printf("receive:1\r\n");
UartReady = RESET;
Uart_Receive_Analysis();
}
}
void Uart_Receive_Analysis(void)
{
uint8_t receive_flag = 0, receive_temp = 0, index = 0;
// for(uint8_t i=0;i<8;i++) {
// printf("Received aRxBuffer[%d]:%x\r\n",i,aRxBuffer[i]);
// }
if (0xA5 == aRxBuffer[0])
{
receive_flag |= 0x1;
}
if (0x5A == aRxBuffer[7])
{
receive_flag |= 0x2;
}
for (index = 1; index < 6; index++)
{
receive_temp += aRxBuffer[index];
}
if (receive_temp == aRxBuffer[6])
{
receive_flag |= 0x4;
}
if (0x7 == receive_flag)
{
printf("received data analysis!\r\n");
printf("wave=%d,amplitude=%d,freq=%d\r\n", aRxBuffer[1], aRxBuffer[2], aRxBuffer[3] * 65536 + aRxBuffer[4] * 256 + aRxBuffer[5]);
dds_con_par.wave_state = aRxBuffer[1];
dds_con_par.amplitude = aRxBuffer[2] / 10;
dds_con_par.frequency = aRxBuffer[3] * 65536 + aRxBuffer[4] * 256 + aRxBuffer[5];
LoadParameters();
}
// printf("receive_flag:%d\r\n",receive_flag);
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *UartHandle)
{
UartReady = SET;
}
上位机部分
基于C#的NET framework支持控件拖拽开发,并且基于Visual Studio进行创建、调试、发布,较为方便。 右上部分的波形仅作分类展示,具体的幅度和频率取决于下方的设置。打开串口后,当设置好后,点Send发送,左边的两个textbox主要用来记录发送数据。 实现的界面如下
四、效果展示与遇到的问题
下面展示生成的DDS信号
1k三角波
1k方波
1k正弦
2m三角波
2m方波
2m正弦
可以看到输出的频率、幅度信息还是有误差,分析了下原因。 我所使用的示波器是之前所购买的1块STM32单片机主控,用于学习的示波器,内部采用的ADC是一块AD9288(8bit 40/80/100MSPS),显示的幅度信息没那么准确(询问过制作者说主要用来看波形。。。。幅度信息不重要),从仿真结果来看我对dac数据进行移位操作实现的幅度变化比例是没问题的,但我手上又缺乏其他的示波器。我只能后面再看下能不能买一个或借一个精度更高的示波器。 频率信息也不准确,这个在之前已提过。
五、未来的计划及建议
1.我之前的DDS频率调节方法还过于粗糙,想要实现精度较高的调节还需进一步学习。 2.示波器的基础功能有了,还要把波形传输显示部分加上进行完善。 3.屏幕驱动占用flash空间过大,考虑删去很多不需要的功能,用于存放其他功能程序。 4.risc-v目前是个新方向,希望自己后面也能把移植这部分实现下。