基于小脚丫FPGA实现的本地控制的DDS任意波形发生器
使用小脚丫fpga step-mxo2-c搭配电赛训练板外置高速DAC,实现的本地控制的DDS任意波形发生器。使用8位波形查找表与28位相位累加器,实现了较好的波形控制。
标签
STM32
FPGA
DDS
matlab
2022寒假在家练
Matt
更新2022-03-03
哈尔滨工程大学
1127

内容介绍

一、项目需求

  1. 通过板上的高速DAC(10bits/125Msps)配合FPGA内部DDS的逻辑,生成波形可调(正弦波、三角波、方波)、频率可调(DC-)、幅度可调的波形
  2. 生成模拟信号的频率范围为DC-20MHz,调节精度为1Hz
  3. 生成模拟信号的幅度为最大1Vpp,调节范围为0.1V-1V
  4. 在OLED上显示当前波形的形状、波形的频率以及幅度
  5. 利用板上旋转编码器和按键能够对波形进行切换、进行参数调节

二、方案论证与比较

  1. 使用单片机配合DAC制作DDS。由单片机存储波形数据控制DAC实现DDS任意波形发生器,好处在于实现流程较为直观,难度低。
  2. 使用fpga配合外置DAC实现DDS。FPGA主频足够高,并且代码效率更高,配合外置高速DAC可以实现更高性能的DDS。
  3. 使用锁相环电路。由鉴相器、环路滤波器与压控振荡器组成锁相环电路,实现预期目标。在频率锁定之后,输出的频率将会比较稳定。

使用单片机实现难度低,但受限于单片机的架构,很难实现高性能的DDS任意波形发生器。即使使用高速外置DAC也会遇到一些麻烦,比如即使使用主频高达480M的stm32h7系列,IO翻转速率也只有16.7M,效果极差,不及定时器中断等外设产生的效果。但即使是STM32H7系列,进出中断的速率也只有12.5M,发挥不出高速DAC的性能,故不宜作为实现DDS的平台。

与锁相环相比,FPGA同样可以达到较好效果,并且实现功能更多更灵活,本次也借助硬禾学堂的学习活动,使用FPGA实现DDS任意波形发生器。经验证,效果很好。

三、方案描述

  1. 在PC中的lattice专属ide diamond中编写程序,编译、综合并生成jed文件。通过stm32l0mcu生成的虚拟U盘,对FPGA进行烧录和调试。
  2. 在FPGA中并行实现OLED屏幕内容的刷新,DAC波形的输出,对按键和旋转编码器输入信号的消抖并作出对应的处理。
  3. 使用逻辑分析仪代替板载调试器对过程中变量进行调试。
  4. 使用定时器+DMA+ADC的方式对stm32进行配置,对DDS生成的波形进行采样,通过UART串口发送回上位机。在上位机上通过matlab进行快速傅里叶变换,绘制波形与频谱,进行结果的验证,并进行进一步调整。

方案描述流程图

四、关键代码与分析

1、FPGA中记录最近一次调整的是频率还是幅度,以便于使用正交编码器进行数据的调整。

reg fre_change_enable,altitude_change_enable;
reg sw2_debounce_reg,sw3_debounce_reg;
wire sw2_debounce_pos,sw3_debounce_pos;
always@(posedge clk_in or negedge rst_n_in)begin
    if(!rst_n_in)  begin      sw2_debounce_reg <= 1'b1;        sw3_debounce_reg <= 1'b1;   end
    else        begin      sw2_debounce_reg<= sw2_debounce; sw3_debounce_reg <= sw3_debounce;   end
end

assign sw2_debounce_pos = !sw2_debounce_reg&&sw2_debounce;
assign sw3_debounce_pos = !sw3_debounce_reg&&sw3_debounce;

always@(posedge clk_in or negedge rst_n_in)begin
    if(!rst_n_in)begin
        fre_change_enable       <=  1'b0;
        altitude_change_enable  <=  1'b0;
    end
    else begin
        if(sw2_debounce_pos)   begin   fre_change_enable <= 1'b1;  altitude_change_enable<=1'b0;   end
        if(sw3_debounce_pos)   begin   fre_change_enable <= 1'b0;  altitude_change_enable<=1'b1;   end
    end

否则经常会调整错数据,调试时造成较大麻烦。

2、matlab中快速傅里叶变换,进行波形与频谱的绘制。

clc;
clear all;
ad_read = importdata('1.2M.txt');
ad_data = ad_read';

ad_fft_1 = fft(ad_data);
ad_fft = fftshift(ad_fft_1);
figure(1)
subplot(211)
stem(ad_fft);

subplot(212)
plot(ad_data);

      清屏后,对文件的数据进行读取。数据文件的形式最好是每行只有一个数据,一个数据占据一行的位置,此处文件名可以进行修改,数据文件须与matlab脚本文件处于同一文件夹下。

      之后使用fft函数与fft_shift函数对方才读取的数据进行快速傅里叶变换,通过subplot函数将频谱与波形绘制在同一张Figure上,便于查看与分析。效果如下图所示。

Ft5TeGTDaLqXz9rD0PJ8lkjigwH8

3、信号消抖

always@(posedge clk,negedge rst_n)begin
    if(!rst_n)begin
        count <= 0;
        clk_10ms <= 1'b0;
    end
    else begin
        if(count < 32'd12_000)begin//10ms消抖,25MCLK
            count <= count + 1'b1;
            clk_10ms <= 1'b0;
        end
        else begin
            count <= 0;
            clk_10ms <= 1'b1;
        end
    end
end

always@(posedge clk,negedge rst_n)begin
    if(!rst_n)begin
        signal0 <= 1'b0;
        signal1 <= 1'b0;
    end
    else begin
        if(clk_10ms)begin
            signal0 <= signal;
            signal1 <= signal0;
        end
    end
end

assign signal_debounce = signal&&signal0&&signal1;

常规操作,没啥好说的,三个信号同时做与操作,可以更有效的消抖,避免受到劣质按键的干扰。

4、旋转编码器

always@(posedge clk,negedge rst_n)begin
    if(!rst_n)begin
        rotary_right <= 1'b1;
        rotary_left <= 1'b1;
    end
    else begin
        if(A_pos && !B)     rotary_right <= 1'b1;//A的上升沿时候如果B为低电平,则旋转编码器是向右转
        if(A_pos && B)      rotary_left <= 1'b1;//A上升沿时候如果B为低电平,则旋转编码器是向左转
        if(A_neg && B)      rotary_right <= 1'b0;//A的下降沿B为高电平,则向右转结束
        if(A_neg && !B)     rotary_left <= 1'b0;//A的下降沿B为低电平,则向左转结束
    end
end

      主要参考电子森林中的例程并进行少许修改。在代码中只需在A或B中选择一个信号进行上升沿或下降沿的处理,同时判断另一个信号的高低电平即可判断旋转的方向。最好对A,B信号也进行消抖,以免受到干扰。

      可以将debounce消抖部分的代码进行ip的封装,后续调用更加方便,而且不需要多次综合,在工程开发的过程中带来极大便利。

5、使用独热码与移位操作进行当前模式的切换

always@(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        mode <= 8'b1111_1110;
    end
    else if(btn_pos)    mode <= {mode[0],mode[7:1]};
end

      使用独热码可以将模式state实时反映在核心板的一排小灯上,便于调试。使用移位操作而不是状态机mux,能节省很多资源。

6、频率数据的控制

wire         rotary_event;
assign rotary_event = rotary_right_pos || rotary_left_pos;//转动标志位

always@(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        bit_val_0_reg <= 4'b0;
        bit_val_1_reg <= 4'b0;
        bit_val_2_reg <= 4'b0;
        bit_val_3_reg <= 4'b0;
        bit_val_4_reg <= 4'b10;
        bit_val_5_reg <= 4'b0;
        bit_val_6_reg <= 4'b0;
        bit_val_7_reg <= 4'b0;
    end
    else if(rotary_event&&enable)begin
        if(rotary_right_pos)begin
            case(bit_mode)
                8'b1111_1110:
                    //bit_val_0_reg <= {bit_val_0_reg[0],bit_val_0_reg[3:1]};
                    if(bit_val_0_reg >= 4'd2)   bit_val_0_reg <= 4'd0;
                    else                        bit_val_0_reg <= bit_val_0_reg + 1'b1;
                8'b1111_1101:
                    if(bit_val_1_reg >= 4'd9)   bit_val_1_reg <= 4'd0;
                    else                        bit_val_1_reg <= bit_val_1_reg + 1'b1;
                8'b1111_1011:
                    if(bit_val_2_reg >= 4'd9)   bit_val_2_reg <= 4'd0;
                    else                        bit_val_2_reg <= bit_val_2_reg + 1'b1;
                8'b1111_0111:
                    if(bit_val_3_reg >= 4'd9)   bit_val_3_reg <= 4'd0;
                    else                        bit_val_3_reg <= bit_val_3_reg + 1'b1;
                8'b1110_1111:
                    if(bit_val_4_reg >= 4'd9)   bit_val_4_reg <= 4'd0;
                    else                        bit_val_4_reg <= bit_val_4_reg + 1'b1;
                8'b1101_1111:
                    if(bit_val_5_reg >= 4'd9)   bit_val_5_reg <= 4'd0;
                    else                        bit_val_5_reg <= bit_val_5_reg + 1'b1;
                8'b1011_1111:
                    if(bit_val_6_reg >= 4'd9)   bit_val_6_reg <= 4'd0;
                    else                        bit_val_6_reg <= bit_val_6_reg + 1'b1;
                8'b0111_1111: 
                    if(bit_val_7_reg >= 4'd9)   bit_val_7_reg <= 4'd0;
                    else                        bit_val_7_reg <= bit_val_7_reg + 1'b1;
            endcase 
        end
        if(rotary_left_pos)begin
            case(bit_mode)
                8'b1111_1110:
                    if(bit_val_0_reg == 4'd0)   bit_val_0_reg <= 4'd2;
                    else                        bit_val_0_reg <= bit_val_0_reg - 1'b1;
                8'b1111_1101:
                    if(bit_val_1_reg == 4'd0)   bit_val_1_reg <= 4'd9;
                    else                        bit_val_1_reg <= bit_val_1_reg - 1'b1;
                8'b1111_1011:
                    if(bit_val_2_reg == 4'd0)   bit_val_2_reg <= 4'd9;
                    else                        bit_val_2_reg <= bit_val_2_reg - 1'b1;
                8'b1111_0111:
                    if(bit_val_3_reg == 4'd0)   bit_val_3_reg <= 4'd9;
                    else                        bit_val_3_reg <= bit_val_3_reg - 1'b1;
                8'b1110_1111:
                    if(bit_val_4_reg == 4'd0)   bit_val_4_reg <= 4'd9;
                    else                        bit_val_4_reg <= bit_val_4_reg - 1'b1;
                8'b1101_1111:
                    if(bit_val_5_reg == 4'd0)   bit_val_5_reg <= 4'd9;
                    else                        bit_val_5_reg <= bit_val_5_reg - 1'b1;
                8'b1011_1111:
                    if(bit_val_6_reg == 4'd0)   bit_val_6_reg <= 4'd9;
                    else                        bit_val_6_reg <= bit_val_6_reg - 1'b1;
                8'b0111_1111: 
                    if(bit_val_7_reg == 4'd0)   bit_val_7_reg <= 4'd9;
                    else                        bit_val_7_reg <= bit_val_7_reg - 1'b1;
            endcase 
        end
    end
end

      增加旋转标志位,能同时在正时针旋转与逆时针旋转的时候进行判断,可同时增减数据,调试与使用更方便。

      给每一位数据都单独开一个4位二进制数进行存储会耗费更多资源,但可以单独操作十进制数据中的每一位,不至于说要靠旋转从100hz调整到20Mhz,大幅节省时间。

7、将几个4位二进制数转换成一个ascii码构成的字符串

assign char = {bit_val_0+8'd48 , 
                bit_val_1+8'd48 , 
                bit_val_2+8'd48 , 
                bit_val_3+8'd48 , 
                bit_val_4+8'd48 , 
                bit_val_5+8'd48 , 
                bit_val_6+8'd48 , 
                bit_val_7+8'd48};

      转换为ASCII的形式,便于在OLED显示的过程中进行字模的选取,个人认为比BCD更合适使用。

8、DDS部分关键代码

     

wire [9:0] cos_dac;
wire [9:0] square_dac;
wire [9:0] trig_dac;
always@(posedge sys_clk or negedge sys_rst_n)begin
	if(!sys_rst_n)begin
		wave_dac <= 10'd0;
	end else begin
		case(wave_type)
			3'b110: wave_dac <= cos_dac;
			3'b011: wave_dac <= square_dac;
			3'b101: wave_dac <= trig_dac;
		endcase
	end
end

      波形模式的选取,对三种波形的数据分开进行存储。

assign dds_phase_add = (wave_freq << 1) + (wave_freq >> 3) + (wave_freq >> 4) + (wave_freq >> 5) + (wave_freq >> 6) + 
		(wave_freq >> 9) + (wave_freq >> 11) + (wave_freq >> 13); 

      通过移位相加的方式,在120Mhz主频的情况下,将目标波形的频率转化为相位累加器的增幅频率字。

      此处不使用乘法器的module,好处在于可以省下非常多的资源,并且以wire的形式计算,可以直接产生对应结果。而乘法器起码需要一个周期,甚至几个周期都无法得到结果,将会大大影响系统的稳定性。

assign square_dac = {10{dds_phase[27]}};
assign trig_dac = dds_phase[27] ? ~dds_phase[26:17] : dds_phase[26:17];

      方波与三角波对应的dac数据的产生方式如上。

always@(address)begin
	case(section)
		2'b00: begin
					lut_address = address[5:0];
					cos = 9'h1ff + lut_cos;
			   end
		2'b01: begin
					lut_address = ~address[5:0];
					cos = 9'h1ff + lut_cos;
			   end
		2'b10: begin
					lut_address = address[5:0];
					cos = 9'h1ff - lut_cos;
			   end
		2'b11: begin
					lut_address = ~address[5:0];
					cos = 9'h1ff - lut_cos;
			   end			   
	endcase
end

      取address的前两位做case,可以使用对用的1/4波形数据查找表,在不使用block ram与ROM的情况下可以省下很多lut,以作其他用途。

五、测试结果

      测试正弦波的波形和频谱如下所示。

Ft5TeGTDaLqXz9rD0PJ8lkjigwH8

      测试三角波的波形和频谱如下所示。

FpwIEKbwQOuiIyEJOLGCw8evoMsr

      测试方波的波形和频谱如下所示。

FvlBjf4r9zfgFm7KkWyIOwAshx2L

六、FPGA资源占用报告

Fjgu3UcV3fjyNnuxmALZMKg7Zk1p

七、结果分析与不足

由于疫情原因,学校电子电工实验室暂时封闭了,只好现学stm32做了一个简单的波形采集。由于DMA使用不熟练,所以采样率一直上不去,最后大概是个100ksps左右的采样率,并且具有较大毛刺,因此数据并不好看。

      但结合matlab对数据进行处理分析并绘图以后,同样可以获得较为直观的分析,并可得频谱较为干净,实验结果符合预期。

      另一方面,由于数据存储方案选择的失误,忽略了verilog对乘法的支持问题,导致最后面临移位乘法的近似处理与标准乘法器方案的抉择。一个会导致小幅误差,一个需要多周期进行运算,影响系统稳定性。最终选择放弃了部分精度。造成这个问题的主要原因还是对fpga与verilog的不熟悉,好在吃一堑长一智,类似的问题下次就不会再犯了。

 

附件下载

final.zip
fpga工程
BO.m
matlab分析波形的代码
1.2M.txt
配套matlab使用的波形数据文件

团队介绍

哈尔滨工程大学 电子信息工程学院 王阳
团队成员
王阳
一个菜逼

评论

0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号