基于STM32和FPGA的波形可调频率可调幅度可调的信号发生器
使用STM32和FPGA异构的电赛训练板,实现了单片机与FPGA的数据交互控制显示,能通过DAC发送任意波形
标签
FPGA
MPU
爆肝儿
更新2023-03-28
中山大学
2332

项目3 - DDS任意波形发生器/本地控制

具体要求:

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

 

这是本次寒假一起练项目三的一些要求。

首先分析开发板的原理图及实物图。

FvdwmShivTs2d1SNU7LYOcbEUUIEFgrrAvAyGs2pQGMlS0fKuT1yivHyFiu0lvVBNZtAx1O23JZSZa1Ct0q8

 

从实物图和原理图可以看到,底板上的外设有个旋转编码器、ADC、DAC、OLED屏幕和两个按键,核心板上有STM32芯片和FPGA芯片。

从原理图中可知,OLED的外设是通过SPI总线由STM32控制,也就是说要驱动OLED必须要在ARM端进行,而ADC和DAC则连接到了FPGA的管脚上,因此必须要使用FPGA对这两个芯片配置。

首先进行FPGA这一侧的设计分析,

1、DAC的驱动

DAC的驱动很简单,因为不需要对DAC内部寄存器做配置,也没有总线让用户配置,所以改DAC的驱动则仅需要给芯片时钟和给数据管脚数据,时钟由FPGA内部通过PLL将12M的晶振时钟倍频到48M使用,而数据则由FPGA内部产生

 

2、SPI与STM32数据交互,

FPGA侧作为从机 ,SPI(Serial Peripheral Interface)即串行外设接口,一般其有 4 根线,片选线(cs_n)、时钟线(sck)、输入数据线(sdi)和输出数据线(sdo),分别说明下这四线的含义,CS_n为片选信号,Cs_n(S):片选线,拉低则选中改芯片, Sck:时钟线,根据该信号的上升沿锁存 sdi 发来的数据,根据该信号的下降沿通过 sdo 输出数据;Sdi(D):数据线,为 FPGA 的输出信号. 在本工程中,FPGA和单片机交互的数据有如下几个,波形的形状选择、波形的频率选择、波形的幅度选择,这些数据是单片机通过SPI总线协议的MOSI输出至FPGA,FPGA进行数据的接收。 当然,本文也实现了FPGA通过SDO发送数据到MCU,这个也是可以的,不过要是不做示波器功能的话,这本功能则无关紧要。

 

3、波形的产生

本工程波形的参生是通过FPGA进行的,FPGA产生波形有多种方法,既可以使用DDS IP核也能使用查找表来进行生成,本文选择的方法则为简单的将波形数据存入ROM 中,然后通过读取波形的地址,即可从ROM中读取相对应的数据。

而波形数据的源文件通过Matlab产生,具体产生代码如下:

x = linspace(0,6.28,1024);  
y1 = sin(x);

y1 = y1*512+512;

y2 = round(y1);
y3 = dec2hex(y2); 
figure;
plot(y1)

for i = 1:1024
   if i<=512
      x_fangbo(i)= 1023 ;
   else
      x_fangbo(i) = 0;
   end
    
end

yfangbo = dec2hex(x_fangbo); 

for i = 1:1024
   if i<=512
      x_sanjiao(i)= i*2-1 ;
   else
      x_sanjiao(i) = 2048-i*2;
   end
    
end

y_sanjiao = dec2hex(x_sanjiao); 

这里的matlab代码较为简单,所以没做注释,仅口头说明一下。

通过将2pi内等距离的插入1024个点,然后求出他们对应的正弦值,之后将数据扩大512倍,由于DAC需要的数据均为大于0的,所以还需要将数据整体上移512,此时得到波形的10进制数据,由于 Radiant软件中ROM IP核需要的原始数据只能是 二进制或者16进制,因此还需要将数据转换为16进制的形式。

三角波和方波的产生代码易懂,这里不做解释。

 

 

4、波形种类、 频率、幅度的控制

波形频率的控制,首先我们定义了外设其中的一个按键为波形种类选择按键,当按下时,我们通过单片机发送一个特定的指令 例如0X55,此时若FPGA接收到0X55时,我们就认为按下了波形选择按键,此时切换对应的ROM IP核里的数据显示,以此类推,三种波形分别对于不同三种特殊指令。这就是波形种类变换的原理。

always @(posedge clk or negedge rst_n) begin
  if (!rst_n) begin
    // reset
    rd_data_o_r <= 0;
  end
  else if (wave_set == 2'b00) begin
    rd_data_o_r <= rd_addr_1_reg;
  end
  else if (wave_set == 2'b01) begin
    rd_data_o_r <= rd_addr_2_reg;
  end
  else if (wave_set == 2'b11) begin
    rd_data_o_r <= rd_addr_3_reg;
  end
  else 
  rd_data_o_r <= rd_data_o;
end



//ROM例化
rom_1024x10b u_rom_1024x10b(
  .rd_clk_i(clk),
    .rst_i(~rst_n),
    .rd_en_i( ),
    .rd_clk_en_i( ),
    .rd_addr_i(rd_addr_i),
    .rd_data_o(rd_addr_1_reg)
  );


rom_fangbo u_rom_fangbo(
    .rd_clk_i( clk),
    .rst_i(~rst_n ),
    .rd_en_i( ),
    .rd_clk_en_i( ),
    .rd_addr_i(rd_addr_i ),
    .rd_data_o(rd_addr_2_reg )
    );

sanjiaobo u_sanjiaobo(
    .rd_clk_i(clk ),
    .rst_i( ~rst_n),
    .rd_en_i( ),
    .rd_clk_en_i( ),
    .rd_addr_i(rd_addr_i ),
    .rd_data_o(rd_addr_3_reg )
    );

接下来是波形频率变换的原理,首先需要从旋转编码器中读取到当前的频率设定数值,通过SPI传入FPGA进行读取,FPGA读取到的频率数值作为选取ROM数据地址的间隔,间隔越大,则波形出来的频率也就越高,以此类推,越小,则出来的频率越小,三种波形都是如此。

//频率调节计数器
always @(posedge clk_i or negedge rst_n_i) begin
	if(rst_n_i == 1'b0)
		freq_cnt <= 10'b0;
	else if(freq_cnt == FREQ_ADJ)
		freq_cnt <= 10'd0;
	else
		freq_cnt <= freq_cnt + 10'd1;
end

//相位累加器累加
always @(posedge clk_i or negedge rst_n_i) begin
	if(rst_n_i == 1'b0)
		phase_acc <= 31'b0;
	else begin
		if(freq_cnt == FREQ_ADJ) begin
			phase_acc <= phase_acc + freq_set;
		end
	end
end

 

然后是幅度的控制,幅度的控制有多种做法,可以将不同幅度的信号对于的原始数据分别存入ROM中,然后若是接收到幅度调制按键,这里由于外设只有两个按键,一个是频率间隔使用,一个是波形种类选择使用,则没有多余按键可供幅度使用,于是我们使用多次按下其中一个按键(5次)来作为波形幅度调节的控制触发。然后根据不同的波形幅度触发选用不同的波形数据。 还有一种节省资源的做法是接收到幅度变化指令后,通过将此时输出数据右移来改变不同的数据大小,因为对数据右移相当于对数据进行除法,而FPGA中除法器资源比较稀少,所以使用右移来代替除法。

always @(posedge clk or negedge rst_n) begin
  if (!rst_n) begin
    // reset
    rd_data_o <= 0;
  end
  else if (fudu_set == 2'b00) begin
    rd_data_o <= rd_data_o_r>>2;
  end
  else if (fudu_set == 2'b01) begin
    rd_data_o <= rd_data_o_r>>1;
  end
  else if (fudu_set == 2'b11) begin
    rd_data_o <= rd_data_o_r;
  end
  else 
  rd_data_o <= rd_data_o;
end

FuB_u0YK2lPqVv7mNcN8hlBzg_an

FjYp7LQy8rubyJUziSWEc_ljvBFn

上图为fpga侧的 RTL 综合图和FPGA的资源消耗图, 每个模块都有在前文中讲过,此处不再赘述。

 

下面是单片机侧的程序说明

单片机侧使用的是STM32cubemx软件来创建工程,这个软件的优点是可以帮助我们自动的生成底层代码,初始化之类的函数,我们只需要关注main函数的内容即可,选择创建的是MDK-KEIL的工程,在工程中主要包括 OLED的驱动,此部分由厂家提供的原始驱动代码以及电子森林其他小伙伴的代码中可获得,外部中断设置,当按下按键时触发外部中断,调用HAL库中的中断函数来实现,当按下按键后,通过switch语句,分别来执行每次按下按键后的操作,有更新OLED的显示文字并刷新,通过HAL库中的SPI发送函数想FPGA发送对于的指令。

OLED中文字显示用的是PCT的取模软件,设置为C51格式,将需要显示的汉字先储存在数组中,在使用时调用相应的数据数据即可,这里注意,重新写入OLED数据后需要对OLED进行刷屏处理,否则将不会正常显示。

如若想实现串口与上位机的功能,笔者做了一部分调试,由于并未有上位机开发经验所以放弃了,但是实现了单片机与上位机的通信,这里也是调用HAL 库的 HAL_UART_Receive_DMA以及 __HAL_UART_ENABLE_IT(&hlpuart1,UART_IT_IDLE);这两个函数实现收发控制操作,通过DMA的方式进行传输数据,这样当设置最大传输数量后,上位机可以接收不同长度的数据,

下面几张图是实测,能产生频率可调、幅度可调、波形可调的DDS信号发生器。

ForQl6F6VTs1Sdjf9vJiQ-Ewjdsy

FpaXWGlsuC72McO2ThZU7cGa10LK

 

FpkHWvWTHNrqwzzee4Z2_bBM0s7e

 

对本次项目的建议:

本次开发板采用FPGA+STM32异构的电赛训练板,极大的提升了板子的可玩性和实际用途,具有较好的实际工程指引的影响,该板子优点不少但是该板子也有不少缺点,第一,单片机程序下载太不优雅,每次需要断电按着按键然后上电进行下载,在实际调试过程中需要不断的拔插操作,这点让人非常难受。其二就是FPGA芯片太初级,很多人看到是lattice的FPGA就放弃的做下去的念头,因为市场和大家平时接触到的都是主流的Intel 和AMD的FPGA,这两款FPAG配套的EDA软件完整,这个Radiant很是简陋。如果说是考虑到成本问题的话,建议可以使用国产的FPGA芯片,这样成本问题和EDA工具问题都可以解决。

 

该工程的源代码放在百度网盘里了

链接:https://pan.baidu.com/s/1cWIctsrhEAfeUOF7AVSdIg?pwd=9y07 
提取码:9y07

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