内容介绍
内容介绍
项目需求
- 通过板上的高速DAC(10bits/最高125Msps)配合FPGA内部DDS的逻辑(最高48Msps),生成波形可调(正弦波、三角波、方波)、频率可调(DC-5MHz)、幅度可调的波形
- 生成模拟信号的频率范围为DC-5MHz,调节精度为1Hz
- 生成模拟信号的幅值为最大1Vpp,调节范围为0.1V-1V
- 在OLED上显示当前波形的形状、波形的频率以及幅度
- 利用板上旋转编码器和按键能够对波形进行切换、进行参数调节
结果展示
- 复位
按下如下所示从左往右第二个按键进行复位,使用前务必先摁下复位键以进行初始化 - 波形显示
可以生成正弦波,方波与三角波。通过开发板上的按键K1可以切换波形,同时oled屏幕上会显示当前波形。 - 调节幅度与频率
通过旋转旋钮可以调节幅度与频率,按下旋钮以切换调节频率与调节幅度,通过指示灯可显示当前调节的是幅值还是频率,指示灯亮表示调节的是频率;指示灯灭表示调节的是幅值
频率调节步长为0.7Hz,当到达5MHz时,一个周期内仍有9.6个采样点,可保证波形不失真;幅值调节步长为0.203125v
FPGA资源占用截图
设计思路
- 波形产生通过查找表的方式实现
- fpga负责查找表并驱动ADC产生波形
- stm32单片机负责控制fpga产生的波形、pinlv与幅值,并把相关信息显示到oled屏幕上
实现过程 FPGA部分
- FPGA项目结构如下
│ top_control.v 顶层控制模块
│
├─spi SPI通信相关
│ spi_control.v
│ spi_rx_byte.v
│ spi_tx_byte.v
│
└─wave 波形产生相关
DAC_PWM.v
DAC_SIN.v
DAC_TRI.v
DDS_Control.v
1.1 波形产生:通过查找表产生波形,通过ROM存储1/4周期的波表, phase_acc的最高两位确定所处哪1/4周期,step为相位累加器步长,amp_factor为幅值因子,用于改变幅值,两者大小都由单片机控制
DAC_SIN.v
module DAC_SIN
(
input clk,
input rst,
input [15:0] step,
input [15:0] amp_factor,
output [9:0] dac_data
);
reg [25:0] phase_acc; //相位累加器reg [9:0] sin_out; //输出幅值wire [7:0] lut_out; //查表结果reg [13:0] sin_out_amp;
reg [11:0] address;
wire [1:0] cs;
wire rst_p = !rst;
/*开个寄存器做一些延迟,不然会有bug*/reg [25:0] step_temp;
always @(posedge clk or negedge rst) begin
if (!rst) step_temp <= 'b0;
else step_temp <= step;
endalways @(posedge clk or negedge rst) begin
if (!rst) phase_acc <= 'b0;
else phase_acc <= phase_acc + step;
endalways @(cs or lut_out or rst) begin
if (!rst) begin
sin_out <= 'b0;
address <= 'b0;
end
case (cs)
2'b00: begin
sin_out <= 8'd128 + lut_out;
address <= phase_acc[23:12];
end
2'b01: begin
sin_out <= 8'd128 + lut_out;
address <= ~phase_acc[23:12];
end
2'b10: begin
sin_out <= 8'd128 - lut_out;
address <= phase_acc[23:12];
end
2'b11: begin
sin_out <= 8'd128 - lut_out;
address <= ~phase_acc[23:12];
end
endcaseendalways @(posedge clk or negedge rst) begin
if (!rst) sin_out_amp <= 'b0;
else sin_out_amp <= sin_out*amp_factor[7:0];
end
Sin_LUT sin(
.rst_i (rst_p),
.rd_en_i (1'b1),
.rd_clk_en_i (1'b1),
.rd_clk_i (clk),
.rd_addr_i (address),
.rd_data_o (lut_out)
);
assign dac_data = sin_out_amp[13:4];
assign cs = phase_acc[25:24];
endmodule //DAC_SIN
1.2 波形选择,通过一个计数器来控制
DDS_Control.v
/*改变波形*/always @(posedge sys_clk or negedge rst) begin
if (!rst) begin
dac_data <= 'b0;
end
else begin
if (wave_type == 'b00) dac_data <= dac_data_sin;
else if (wave_type == 'b01) dac_data <= dac_data_pwm;
else if (wave_type == 'b10) dac_data <= dac_data_tri;
else ;
end
end
top_control.v
// 切换波形reg [1:0] wave_type;
wire wave_type_max = (wave_type == 'd2)?'b1:'b0;
always @(posedge wave_type_change or negedge rst) begin
if (!rst) begin
wave_type <= 'b0;
end
else begin
if (wave_type_max) wave_type <= 'b0;
else wave_type <= wave_type + 'b1;
end
end
1.3 SPI通信,由于stm32方面配置的是一次收发16bit,所以FPGA这边一次收发16bit
spi_rx_2byte.v
always@(posedge SCK)
begin
if(CS == 1) begin //CS为高,从机不响应
Rec_Data <= 'b0;
end
else //CS为低,从机开始接收数据
begin
if(SCK == 1)
begin
case(Data_State)
D15_State:begin Rec_Data[15] <= MOSI; Data_State<= D14_State;rx_complete <= 'b0;end
D14_State:begin Rec_Data[14] <= MOSI; Data_State<= D13_State;rx_complete <= 'b0;end
D13_State:begin Rec_Data[13] <= MOSI; Data_State<= D12_State;rx_complete <= 'b0;end
D12_State:begin Rec_Data[12] <= MOSI; Data_State<= D11_State;rx_complete <= 'b0;end
D11_State:begin Rec_Data[11] <= MOSI; Data_State<= D10_State;rx_complete <= 'b0;end
D10_State:begin Rec_Data[10] <= MOSI; Data_State<= D9_State;rx_complete <= 'b0;end
D9_State:begin Rec_Data[9] <= MOSI; Data_State<= D8_State;rx_complete <= 'b0;end
D8_State:begin Rec_Data[8] <= MOSI; Data_State<= D7_State;rx_complete <= 'b0;end
D7_State:begin Rec_Data[7] <= MOSI; Data_State<= D6_State;rx_complete <= 'b0;end
D6_State:begin Rec_Data[6] <= MOSI; Data_State<= D5_State;rx_complete <= 'b0;end
D5_State:begin Rec_Data[5] <= MOSI; Data_State<= D4_State;rx_complete <= 'b0;end
D4_State:begin Rec_Data[4] <= MOSI; Data_State<= D3_State;rx_complete <= 'b0;end
D3_State:begin Rec_Data[3] <= MOSI; Data_State<= D2_State;rx_complete <= 'b0;end
D2_State:begin Rec_Data[2] <= MOSI; Data_State<= D1_State;rx_complete <= 'b0;end
D1_State:begin Rec_Data[1] <= MOSI; Data_State<= D0_State;rx_complete <= 'b0;end
D0_State:begin Rec_Data[0] <= MOSI; Data_State<= D15_State;rx_complete <= 'b1;end
default:Data_State<= D15_State;
endcase
end
end
end
top_control.v
always @(posedge sys_clk or negedge rst) begin
if (!rst) begin
Rec_Data_d <= 'b0;
end
else begin
if (rx_complete_pos) begin
Rec_Data_d <= Rec_Data;
end
end
end
1.4 接收stm32发送的命令:
- 0xdcba: 切换修改频率与修改幅值
- 0xdddd: 切换波形
top_control.v
wire en_change = (Rec_Data_d == 'hdcba)?1'b1:1'b0; // 切换改变幅值与频率命令
wire wave_type_change = (Rec_Data_d == 'hdddd)?1'b1:1'b0; // 改变显示波形命令
DDS_Control.v
/*切换修改幅值与修改频率*/always @(posedge sys_clk or negedge rst) begin
if (!rst) begin
step <= 'b0;
amp_factor <= 'd0;
end
else begin
if (!selector) begin if (encoder_val < 'd56506) step <= encoder_val; end // selector为0时调节频率
else begin if (encoder_val < 'd56506) amp_factor <= encoder_val; end // selector为1时调节幅值
end
end
stm32单片机部分
2.0 全局变量
static uint16_t step_val, amp_val; // 相位计数器的步幅与幅值调节因子
static uint8_t encoder_s = 0; //旋转编码器按键的状态
static uint8_t wave_type; //当前波形
2.1 旋转编码器正交解码
/**
* 定时器回调函数,用于旋转编码器
* @param htim 定时器句柄
*/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
uint8_t a_val, b_val;
static char lock=0;
static uint16_t knob_dir =0;
a_val = HAL_GPIO_ReadPin(Encoder_A_GPIO_Port, Encoder_A_Pin);
b_val = HAL_GPIO_ReadPin(Encoder_B_GPIO_Port, Encoder_B_Pin);
if((a_val == 0 && b_val ==1)&(!lock))
{
lock =1;
knob_dir= 1;
if (!encoder_s) step_val++;
else amp_val++;
}
else if((b_val ==0 && a_val==1)&(!lock))
{
lock =1;
knob_dir = 2;
if(!encoder_s && step_val > 0) step_val--;
else if (encoder_s && amp_val > 0) amp_val--;
}
else if((b_val == 1 && a_val==1))
{
lock =0;
knob_dir= 0;
}
}
2.2 发送切换波形命令与切换调节幅值与调节频率命令,Rec_Selector为接收的从机的返回数据,如果为0x0000表示当前FPGA在调节频率,0xffff表示FPGA在调节幅值
/*while循环中*/
Rec_Selector = SPI1_ReadWriteTwoByte(0xffff);
if (Rec_Selector == 0x0000) SPI1_ReadWriteTwoByte(step_val);
else if (Rec_Selector == 0xffff) SPI1_ReadWriteTwoByte(amp_val);
2.3 SPI通信相关
uint8_t SPI1_ReadWriteByte(uint8_t TxData)
{
uint8_t Rxdata;
HAL_SPI_TransmitReceive(&hspi1,&TxData,&Rxdata,1, 1000);
return Rxdata; //返回收到的数据
}
/* //////////////////////////////////SPI相关//////////////////////////////////////// */
uint16_t SPI1_ReadWriteTwoByte(uint16_t TxData)
{
uint16_t Rxdata;
// HAL_GPIO_WritePin(SPI1_NSS_GPIO_Port, SPI1_NSS_Pin, 0);
HAL_SPI_TransmitReceive(&hspi1,(uint8_t *)&TxData,(uint8_t *)&Rxdata,1, 1000);
// HAL_GPIO_WritePin(SPI1_NSS_GPIO_Port, SPI1_NSS_Pin, 1);
// printf("Receive: %d", Rxdata);
return Rxdata; //返回收到的数据
}
/**
* 发送命令给fpga,0xdcba表示切换调节频率与调节幅值, 0xdddd表示切换显示的波形
* @param command 要发送的命令
* @return fpga返回值
*/
uint16_t SPI1_ReadWriteCommand(uint16_t command)
{
// printf("%x\r\n", command);
uint16_t Rxdata;
// HAL_GPIO_WritePin(SPI1_NSS_GPIO_Port, SPI1_NSS_Pin, 0);
HAL_SPI_TransmitReceive(&hspi1,(uint8_t *)&command,(uint8_t *)&Rxdata,1, 1000);
// HAL_GPIO_WritePin(SPI1_NSS_GPIO_Port, SPI1_NSS_Pin, 1);
// printf("%x\r\n", Rxdata);
if (Rxdata == 0xdcba){
return 0;
}
return 1;
}
2.4 OLED相关
void OLED_ShowAMP(float amp_now)
{
uint8_t inter = (uint8_t)amp_now;
uint8_t decimal = (uint8_t)((amp_now - inter)*10);
OLED_ShowNum(46, 48, inter, 1, 12);
OLED_ShowChar(52, 48, '.', 12);
OLED_ShowNum(58, 48, decimal, 1, 12);
}
void OLED_ShowReverseAMP(float amp_now)
{
uint8_t inter = (uint8_t)amp_now;
uint8_t decimal = (uint8_t)((amp_now - inter)*10);
OLED_ReverseShowNum(46, 48, inter, 1, 12);
OLED_ShowReverseChar(52, 48, '.', 12);
OLED_ReverseShowNum(58, 48, decimal, 1, 12);
}
/*while循环中*/
amp_now = 0.040625*amp_val;
freq_now = 0.715*step_val;
if (!encoder_s){
OLED_ReverseShowNum(46, 24, freq_now, 7,12);
OLED_ShowAMP(amp_now);
}
else {
OLED_ShowNum(46, 24, freq_now, 7,12);
OLED_ShowReverseAMP(amp_now);
}
switch (wave_type) {
case 0: OLED_ClearSquare(46, 0, 127, 16);OLED_ShowString(46, 0, (unsigned char*)"sin", 12);break;
case 1: OLED_ClearSquare(46, 0, 127, 16);OLED_ShowString(46, 0, (unsigned char*)"square", 12);break;
case 2: OLED_ClearSquare(46, 0, 127, 16);OLED_ShowString(46, 0, (unsigned char*)"triangle", 12);break;
default: break;
}
OLED_Refresh();
遇到的问题
- SPI通信时要注意FPGA与单片机的模式要一致
- SPI发送前会先发送0xFF产生SCK,这一个数据是无效的。从机不需要接收
初步解决:接收数据后进行次判断,只接收小于0xdcba的数据(大于等于0xdcba的数据为命令)
- STM32SPI连续读写多个字节会有时延
解决:由于本项目只需一次收发16bit即可,因此只需将stm32一次收发的字节数由一字节转换为两字节即可。
如果需要收发多字节,则需要在FPGA方进行判断,目前的思路是可以考虑给发送的有效数据设置标识位。如果有更好的想法欢迎进行讨论与交流
未来的计划
-
- 通过串口将数据发送到上位机,在上位机进行显示,可以制作出更加绚丽的前端界面
- 通过旋钮调节幅值与频率十分不便,考虑通过上位机串口发送要设置幅值与频率,再配合前端界面可以简单的调节幅值与频率
附件下载
DDS_Test.7z
verilog源码与可直接烧录的二进制文件
stm32源码.7z
stm32源码(sw4stm32)
MyFirstProject.bin
团队介绍
南京信息工程大学
团队成员
sqzr
评论
0 / 100
查看更多
猜你喜欢
基于STM32+iCE40的电赛训练平台实现的DDS任意波形发生器/本地控制本次使用的项目使用基于STM32+iCE40的电赛训练平台完成。使用FPGA控制高速DAC产生波形,使用MCU控制OLED屏幕和读取按键、旋转编码器输入并进行处理。完成的任务为:项目3 - DDS任意波形发生器/本地控制
MALossov
1181
基于STM32+iCE40的电赛训练平台实现DDS任意波形发生器/本地控制使用FPGA+MCU架构完成任意波形发生器,DAC 并行数模转换芯片速度比较高 ,数据发送用FPGA做比较合理;而OLED显示、按键、旋转编码器的控制使用用MCU处理较为简单;发挥两者的优势,高效简捷地实现任意波形发生器。
且听风吟123
2533
基于STM32+iCE40电赛训练平台的DDS任意波形发生器(本地控制)板卡主要包括单片机部分和FPGA部分。单片机部分主要用于显示、控制和复杂运算;FPGA主要用于数据的运输和处理。两者以SPI为桥梁进行通信。最终完成了硬禾所要求的《DDS任意波形发生器/本地控制》的要求。
zhiwu
1106