项目介绍
-
通过板上的高速DAC(10bits/最高125Msps)配合FPGA内部DDS的逻辑(最高48Msps),生成波形可调(正弦波、三角波、方波)、频率可调(DC-)、幅度可调的波形
- 生成模拟信号的频率范围为DC-5MHz,调节精度为1Hz
- 生成模拟信号的幅度为最大1Vpp,调节范围为0.1V-1V
- 在OLED上显示当前波形的形状、波形的频率以及幅度
- 利用板上旋转编码器和按键能够对波形进行切换、进行参数调节
设计思路
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
输出三角波、频率1MHz、幅度1V
输出正弦波、频率1MHz、幅度1V
改变频率。输出方波、频率720KHz、幅度1V
改变幅度。输出三角波、频率720KHz、幅度0.5V
遇到的主要难题及解决方法
一开始计划也输出锯齿波,但是输出的波形总是有杂波(像是DAC数据信号有先有后输出,然后时钟信号来的时候数据也都没有完全准备好),要不然锯齿波有问题、要不然正弦波、三角波有问题。
未解决,怀疑是时序的问题,下一步计划进一步研究约束文件的编写,试着约束生成,让各种输出信号尽可能同步。
未来的计划或建议
开发过程中,下载STM32部分的程序简直折磨,不能调试,而且每次下载都要按着boot按键重新拔插电源,实在太麻烦了。建议把核心板STM32部分的SWD调试口引出了,或者增加一键下载功能。
希望硬禾能多多举办类似的活动。