基于STM32+iCE40平台实现的DDS任意波形发生器
通过板上的高速DAC(10bits/最高125Msps)配合FPGA内部DDS的逻辑(最高48Msps),生成波形可调(正弦波、三角波、方波)、频率可调(DC-)、幅度可调的波形
标签
2023寒假在家练
DDS任意波形发生器
STM32+iCE40的电赛训练平台
冷月烟
更新2023-03-28
427

内容介绍

 项目介绍

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

设计思路

STM32G031部分:采集板上旋转编码器和按键信号,使用外部中断检测输入信号,中断处理。控制OLED显示界面,包含输出波形的种类(正弦波、三角波、方波)、频率(0-5MHz)、幅度(0-1V),编写控制菜单。作为SPI主机向FPGA从机发送数据信号,内容包括输出波形的种类、频率、幅度,每一次对输出波形控制改变,就要重新发送数据,更新控制。

iCE40UP5K部分:作为SPI从机,接受STM32部分的控制数据,更新输出波形参数,DDS倍频,输出高频率作为高速DAC时钟以及产生DAC数据。计算生成正弦波、三角波、方波波形数据,通过控制数据更新的时间控制频率,通过数学计算控制幅度。

硬件介绍

板卡基于Lattice的ICE40UP5K FPGA和STM32G031 MCU,板载LPC11U35下载器,可以通过USB-C接口进行FPGA的配置,并通过虚拟串口通信配置STM32G031,支持在ICE40UP5K上对RISC-V软核的移植以及开源的FPGA开发工具链,板上RGB三色LED灯用于简单的调试,总计36个IO用于扩展使用,其中14个连接STM32G031 芯片,另外的22根连接ICE40UP5K FPGA芯片。

搭配电赛扩展板,帮助信号源、仪器仪表、控制以及信号处理类题目的训练。板上有通过两个16Pin的插座可以安装高速ADC(16Pin可再用模块/同时支持DIP和邮票孔)、高速DAC(16Pin可再用模块/支持DIP和邮票孔)、板上安装了高速比较器、姿态传感器、旋转编码器以及按键等。

  • 核心模块: FPGA + MCU混合的模块 - 采用STM32G031进行控制输入响应、信息在OLED上的显示、数据的处理;采用FPGA进行高速数据采集、高速DDS信号产生、高速频率计/计数器、数字信号处理(FFT、数字滤波等)
  • 信息显示:128 * 64 OLED,通过SPI总线驱动
  • 控制输入: 旋转编码器/按键
  • 信号采集:10bit/50Msps 高速ADC
  • 信号生成:10bit/120Msps 高速DAC
  • 频率测量:高速比较器
  • 控制输出PWM
  • 传感器三轴姿态感知

FPGA资源占用报告

Design Summary
   Number of slice registers: 107 out of  5280 (2%)
   Number of I/O registers:      0 out of   117 (0%)
   Number of LUT4s:           285 out of  5280 (5%)
      Number of logic LUT4s:             177
      Number of inserted feedthru LUT4s:  36
      Number of ripple logic:             36 (72 LUT4s)
   Number of IO sites used:   16 out of 39 (41%)
      Number of IO sites used for general PIOs: 16
      Number of IO sites used for I3Cs: 0 out of 2 (0%)
      Number of IO sites used for PIOs+I3Cs: 16 out of 36 (44%)
      (note: If I3C is not used, its site can be used as general PIO)
      Number of IO sites used for OD+RGB IO buffers: 0 out of 3 (0%)
   Number of DSPs:             5 out of 8 (62%)
   Number of I2Cs:             0 out of 2 (0%)
   Number of High Speed OSCs:  0 out of 1 (0%)
   Number of Low Speed OSCs:   0 out of 1 (0%)
   Number of RGB PWM:          0 out of 1 (0%)
   Number of RGB Drivers:      0 out of 1 (0%)
   Number of SCL FILTERs:      0 out of 2 (0%)
   Number of SRAMs:            0 out of 4 (0%)
   Number of WARMBOOTs:        0 out of 1 (0%)
   Number of SPIs:             0 out of 2 (0%)
   Number of EBRs:             0 out of 30 (0%)
   Number of PLLs:             1 out of 1 (100%)
   Number of Clocks:  3
      Net clk_48m: 17 loads, 17 rising, 0 falling (Driver: Pin
     u_pll_48m.lscc_pll_inst.u_PLL_B/OUTCORE)
      Net sys_clk_12m_c: 46 loads, 46 rising, 0 falling (Driver: Port
     sys_clk_12m)
      Net u_spi_slave/byte_received: 32 loads, 32 rising, 0 falling (Driver: Pin
     u_spi_slave.byte_received_30/Q)
   Number of Clock Enables:  1
      Pin sys_rst_n: 5 loads, 5 SLICEs (Net: sys_rst_n_c)
   Number of LSRs:  2
      Net u_spi_slave/sys_rst_n_N_206: 32 loads, 32 SLICEs
      Net u_spi_slave/n1399: 5 loads, 5 SLICEs
   Top 10 highest fanout non-clock nets:
      Net byte_received_N_246: 33 loads
      Net u_dds/dds_phase[32]: 32 loads
      Net u_spi_slave/sys_rst_n_N_206: 32 loads
      Net u_dds/dds_phase[31]: 30 loads
      Net u_dds/dds_phase[26]: 24 loads
      Net u_dds/dds_phase[25]: 22 loads
      Net VCC_net: 19 loads

      Net spi_data[30]: 18 loads
      Net u_dds/u_sin_table/lut_address[3]: 18 loads
      Net u_dds/dds_phase[27]: 17 loads

主要代码片段及说明

STM32端

OLED界面显示

OLED_Clear();

OLED_ShowString(0,    0, "   �źŷ�����   ", 16, 0);

OLED_ShowString(0,    16, "����:", 16,(menu==0)?1:0);
OLED_ShowString(8*6,  16, sigbal_type_surface[sigbal_type],16,menu==10?1:0);

OLED_ShowString(0,    16*2, "Ƶ��:", 16,(menu==1)?1:0);
OLED_ShowChar16(8*5,  16*2, ((sigbal_freq/1000000)%10)+'0', (menu==31)?1:0);
OLED_ShowChar16(8*6,  16*2, ',', 0);
OLED_ShowChar16(8*7,  16*2, ((sigbal_freq/100000)%10)+'0',  (menu==32)?1:0);
OLED_ShowChar16(8*8,  16*2, ((sigbal_freq/10000)%10)+'0',   (menu==33)?1:0);
OLED_ShowChar16(8*9,  16*2, ((sigbal_freq/1000)%10)+'0',    (menu==34)?1:0);
OLED_ShowChar16(8*10, 16*2, ',', 0);
OLED_ShowChar16(8*11, 16*2, ((sigbal_freq/100)%10)+'0',     (menu==35)?1:0);
OLED_ShowChar16(8*12, 16*2, ((sigbal_freq/10)%10)+'0',      (menu==36)?1:0);
OLED_ShowChar16(8*13, 16*2, ((sigbal_freq/1)%10)+'0',       (menu==37)?1:0);
OLED_ShowString(8*14, 16*2, "Hz",16,0);

OLED_ShowString(0,    16*3, "����:", 16,(menu==2)?1:0);
OLED_ShowChar16(8*8,  16*3, ((sigbal_range/10)%10)+'0', (menu==41)?1:0);
OLED_ShowChar16(8*9,  16*3, '.',                        (menu==41)?1:0);
OLED_ShowChar16(8*10, 16*3, ((sigbal_range/1)%10)+'0',  (menu==41)?1:0);
OLED_ShowChar16(8*11, 16*3, 'V', 0);

oled_triangle(12,  16*3, (menu==20)?1:0);
oled_triangle(8*5,  16*3, (menu==21)?1:0);
oled_triangle(8*7,  16*3, (menu==22)?1:0);
oled_triangle(8*8,  16*3, (menu==23)?1:0);
oled_triangle(8*9,  16*3, (menu==24)?1:0);
oled_triangle(8*11, 16*3, (menu==25)?1:0);
oled_triangle(8*12, 16*3, (menu==26)?1:0);
oled_triangle(8*13, 16*3, (menu==27)?1:0);

 

按键、编码器逻辑处理

if(rotate == ROTATE_RIGHT)
{
	rotate = ROTATE_NULL;
	if(menu == 10)
	{
		if(sigbal_type < 3)
			sigbal_type++;
		
		sigbal_spi_data();
	}
	else if(menu >= 31 && menu <= 37)
	{
		if((sigbal_freq+reduce_freq[menu-31]) <= 5000000)
			sigbal_freq += reduce_freq[menu-31];
		else
			sigbal_freq = 5000000;
		
		sigbal_spi_data();
	}
	else if(menu == 41)
	{
		if(sigbal_range < 10)
			sigbal_range ++;
		
		sigbal_spi_data();
	}
	else if(menu != 2 && menu != 27)
		menu++;
}
else if(rotate == ROTATE_LEFT)
{
	rotate = ROTATE_NULL;
	if(menu == 10)
	{
		if(sigbal_type > 0)
			sigbal_type--;
		
		sigbal_spi_data();
	}
	else if(menu >= 31 && menu <= 37)
	{
		if(sigbal_freq > reduce_freq[menu-31])
			sigbal_freq -= reduce_freq[menu-31];
		
		sigbal_spi_data();
	}
	else if(menu == 41)
	{
		if(sigbal_range > 0)
			sigbal_range --;
		
		sigbal_spi_data();
	}
	else if(menu != 0 && menu != 20)
		menu--;
}
	
if(key_ok == KEY_PRESS)
{
	key_ok = KEY_UP;
	if(menu == 0)
		menu = 10;
	else if(menu == 1)
		menu = 20;
	else if(menu == 2)
		menu = 41;
	else if(menu == 10)
		menu = 0;
	else if(menu == 20)
		menu = 1;
	else if(menu >= 21 && menu <= 27)
		menu += 10;
	else if(menu >= 31 && menu <= 37)
		menu -= 10;
	else if(menu == 41)
		menu = 2;
}

 

FPGA端

波形形状、频率、幅度控制

module dds(
	input sys_clk,
	input sys_rst_n,
	input [2:0] wave_type,
	input [23:0] wave_freq,
	input [4:0] wave_range,
	output [9:0] wave_dac
	);

reg [32:0] dds_phase;//33位相位累加器
always @(posedge sys_clk) dds_phase <= dds_phase + wave_freq * 33'd179;   //在60MHz的主时钟时,输出对应频率的波形
	
wire [9:0] sin_data; //sin波
wire [9:0] square_data; //方波
wire [9:0] trig_data; //三角波
reg [15:0] wave_data;
reg [9:0] a_ver;

sin_table u_sin_table(
	.address(dds_phase[32:25]),
	.sin(sin_data)
);
assign square_data = {10{dds_phase[32]}};
assign trig_data = dds_phase[32] ? ~dds_phase[31:22]: dds_phase[31:22];
always @(*)begin
	case(wave_type)
		3'd0: wave_data = 0;
		3'd1: wave_data = square_data * a_ver;
		3'd2: wave_data = trig_data * a_ver;
		3'd3: wave_data = sin_data * a_ver;
	endcase
end

always @(*)begin
	begin
		case(wave_range)
			5'd0: a_ver = 10'd0;
			5'd1: a_ver = 10'd3;
			5'd2: a_ver = 10'd5;
			5'd3: a_ver = 10'd8;
			5'd4: a_ver = 10'd11;
			5'd5: a_ver = 10'd13;
			5'd6: a_ver = 10'd16;
			5'd7: a_ver = 10'd19;
			5'd8: a_ver = 10'd21;
			5'd9: a_ver = 10'd24;
			5'd10: a_ver = 10'd26;
		endcase
	end
end

assign wave_dac = wave_data[15:6];

endmodule

 

SPI从机

module spi_slave(
	input sys_clk,
	input sys_rst_n,
	input SCK,
	input MOSI,
	input SSEL,
	output reg [31:0] freq_set
	);

//同步信号
//----------------------------------------------------------------------------------------
// 使用3位移位寄存器将SCK与时钟信号同步
reg [2:0] SCKr;  always @(posedge sys_clk) SCKr <= {SCKr[1:0], SCK};
wire SCK_risingedge = (SCKr[2:1]==2'b01);  // now we can detect SCK rising edges

// 同步SSEL
reg [2:0] SSELr;  always @(posedge sys_clk) SSELr <= {SSELr[1:0], SSEL};
wire SSEL_active = ~SSELr[1];  // SSEL is active low

// 同步MOSI
reg [1:0] MOSIr;  always @(posedge sys_clk) MOSIr <= {MOSIr[0], MOSI};
wire MOSI_data = MOSIr[1];
//----------------------------------------------------------------------------------------

//接收部分
//----------------------------------------------------------------------------------------
// 3位计数器,计数接收比特
reg [4:0] bitcnt;
reg byte_received;  // high when a byte has been received
reg [31:0] byte_data_received; //接收数据寄存器,32bit

always @(posedge sys_clk or negedge sys_rst_n)
begin
  if(~sys_rst_n)
	byte_data_received <= 32'd0;
  else
  if(~SSEL_active)
    bitcnt <= 3'b000;
  else
  if(SCK_risingedge)
  begin
    bitcnt <= bitcnt + 3'b001;

    // implement a shift-left register (since we receive the data MSB first)
    byte_data_received <= {byte_data_received[30:0], MOSI_data};
  end
end

always @(posedge sys_clk) byte_received <= SSEL_active && SCK_risingedge && (bitcnt==5'b11111);

always @(posedge byte_received) freq_set <= byte_data_received;
//----------------------------------------------------------------------------------------
endmodule

实现的功能及图片展示

输出方波、频率1MHz、幅度1V

FippiAYymkr5-TZoggeKodEZGteW

输出三角波、频率1MHz、幅度1V

Fu9B024OE_bnqv5c589XBCki_2GG

输出正弦波、频率1MHz、幅度1V

FsJfPct1qOyQvlkUxpnes5NIWxoz

改变频率。输出方波、频率720KHz、幅度1V

Fr66LpOuZbPTFF10rp5JfQucxoWL

改变幅度。输出三角波、频率720KHz、幅度0.5V

FjMyGV2o-kvYNXaobZDC36CkIAL5

遇到的主要难题及解决方法

一开始计划也输出锯齿波,但是输出的波形总是有杂波(像是DAC数据信号有先有后输出,然后时钟信号来的时候数据也都没有完全准备好),要不然锯齿波有问题、要不然正弦波、三角波有问题。

未解决,怀疑是时序的问题,下一步计划进一步研究约束文件的编写,试着约束生成,让各种输出信号尽可能同步。

未来的计划或建议

开发过程中,下载STM32部分的程序简直折磨,不能调试,而且每次下载都要按着boot按键重新拔插电源,实在太麻烦了。建议把核心板STM32部分的SWD调试口引出了,或者增加一键下载功能。

希望硬禾能多多举办类似的活动。

附件下载

iCE40UP5K代码.zip
fpga代码
STM32G031G8代码.zip
mcu代码

团队介绍

爱摸鱼的工程师

评论

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