基于STM32+iCE40的电赛训练平台完成dds项目
通过板上的高速DAC配合fpga内部dds的逻辑,生成了波形可调,幅度可调的波形。并在oled上显示当前的波形的形状频率和幅度。
标签
FPGA
DDS
Reast
更新2023-03-28
哈尔滨工程大学
425

项目介绍

本项目是基于STM32+iCE40的电赛训练平台,使用fpga和stm32制作的可调dds。

具体要求:

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

设计思路

查阅原理图可知,DAC与FPGA相连,旋转编码器和屏幕与stm32连接。stm32通过spi向OLED屏幕通信,使之显示当前输出的波形信息。同时旋转编码器和按键用于控制改变DDS的波形。stm32储存波形的相关参数并通过spi发送给FPGA。

FPGA通过spi获得波形参数,通过相位累加器和查找表确定输出的波形信息。将波形信息发送到DAC实现数模转换。

软件流程

STM32完成屏幕的打印和按键信号的接收。通过spi向FPGA发送波形信息,通过控制FPGA来控制DAC的波形输出。FPGA通过spi读取到STM32发来的波形频率幅度信息后对将信息整合,通过查表获得波形的详细信息,控制DAC输出波形。

软件流程图:

FmIYMEsYa9IYPC1HqH073uvnqjTM

硬件介绍

本项目使用了ICE40UP5K FPGA和STM32G031 MCU,通过板载LPC11U35下载器实现FPGA资源的下载。使用用硬禾电赛扩展板上的按键控制,板上的高速DAC输出DDS波形。

实现功能

可通过按键控制DDS波形和频率的变化。将当前波形,频率,幅度显示在OLED上。

OLED显示:

FsDr33fmPzgx9pxtMgRtbro3R1dG

输出波形:

FuewWmFIaowOe4vjl3uOAYZCwwz8

FPGA资源占用报告

FrgCKaNpE-ZPYIpNe673Xj1oUT-m

代码片段

通过按键控制波形并通过spi输出到FPGA,代码如下:

void HAL_GPIO_EXTI_Rising_Callback(uint16_t GPIO_Pin)
{
    uint8_t data[1];
    uint8_t model_data[1];
    if(GPIO_Pin==GPIO_PIN_6)
    {
        if (model == 3){
            model = 0;
        }
        model++;
        data[0] = model;
        model_data[0] = 0b11111111;
        HAL_SPI_Transmit(&hspi1,model_data,1,20);
        HAL_SPI_Transmit(&hspi1,data,1,20);
        __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_6);
    }
    if(GPIO_Pin==GPIO_PIN_11)
    {
        freq++;
        data[0] = freq;
        model_data[0] = 0b11111100;
        HAL_SPI_Transmit(&hspi1,model_data,1,20);
        HAL_SPI_Transmit(&hspi1,data,1,20);
        __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_11);
    }
    if(GPIO_Pin==GPIO_PIN_12)
    {

        freq--;
        data[0] = freq;
        model_data[0] = 0b11111100;
        HAL_SPI_Transmit(&hspi1,model_data,1,20);
        HAL_SPI_Transmit(&hspi1,data,1,20);
        __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_12);
    }

}

通过spi读取波形并改变代码如下:

always@(byte_rec)begin
    case(flag_t)
        2'b01: begin   //01代表type改变
            data_byte <= byte_rec;
            type_ctrl_reg <= data_byte[1:0];
            freq_ctrl_reg <= data_byte[1:0]*100*data_f;
            data_f=data_f+2;
            flag_t <= 2'b00;
        end
        2'b10: begin   //10代表freq改变
            data_byte <= byte_rec;
            freq_ctrl_reg <= data_byte<<5;
            flag_t <= 2'b00;
        end
        2'b11: begin   //11代表amp改变
            data_byte <= byte_receive;
            amp_ctrl_reg <= byte_receive[3:0];
            flag_t <= 2'b00;
        end
        default: begin //如果是00那就是判断接下来应该是哪个类型
            if (byte_rec == 8'b11111111) begin
                flag_t <= 2'b01;
            end
            else if (byte_rec == 8'b11111100) begin
                flag_t <= 2'b10;
            end
            else if (byte_rec == 8'b11110000) begin
                flag_t <= 2'b11;
            end
        end
    endcase
end

dds实现和波形信息代码:

module dds(

    input sys_clk, //The clock rate is same to sample rate, and we set it to SAMPLE_RATE.
    input sys_rst_n,
    input [1:0] wave_type, //The 00:Cos wave;01:Square wave;10:Trig wave;11:Saw wave.
    input [31:0] wave_freq,
   
    output reg [9:0] wave_dac
);
//MUX to choose suitable wave
wire [9:0] cos_dac;
wire [9:0] square_dac;
wire [9:0] trig_dac;
wire [9:0] saw_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)
            2'b00: wave_dac <= cos_dac;
            2'b01: wave_dac <= square_dac;
            2'b10: wave_dac <= trig_dac;
            2'b11: wave_dac <= saw_dac;
        endcase
    end
end


// Code below is designed to generate Cos wave
reg [27:0] dds_phase;
wire [27:0] dds_phase_add;
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); //wave_freq*2.2369384765625(10.00111100101010);
always@(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)begin
        dds_phase <= 28'd0;
    end else begin
        dds_phase <= dds_phase + dds_phase_add;
    end
end


lut u_lut(
    .address(dds_phase[27:20]),
    .cos(cos_dac)
);


//Code below is designed to generate Square wave
assign square_dac = {10{dds_phase[27]}};
//Code below is designed to generate Trig wave
assign trig_dac = dds_phase[27] ? ~dds_phase[26:17] : dds_phase[26:17];
//Code below is designed to generate Saw wave
assign saw_dac = dds_phase[27:18];


endmodule
module lut(
   input [7:0] address,
   output reg [9:0] cos
);


wire [1:0] section;
reg [5:0] lut_address;
reg [8:0] lut_cos;
assign section = address[7:6]; //Get the sections of cos wave to reduce memory


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


always @(lut_address) begin
   case(lut_address)
      6'h0: lut_cos=9'h0;
      6'h1: lut_cos=9'hC;
      6'h2: lut_cos=9'h19;
      6'h3: lut_cos=9'h25;
      6'h4: lut_cos=9'h32;
      6'h5: lut_cos=9'h3E;
      6'h6: lut_cos=9'h4B;
      6'h7: lut_cos=9'h57;
      6'h8: lut_cos=9'h63;
      6'h9: lut_cos=9'h70;
      6'ha: lut_cos=9'h7C;
      6'hb: lut_cos=9'h88;
      6'hc: lut_cos=9'h94;
      6'hd: lut_cos=9'hA0;
      6'he: lut_cos=9'hAC;
      6'hf: lut_cos=9'hB8;
      6'h10: lut_cos=9'hC3;
      6'h11: lut_cos=9'hCF;
      6'h12: lut_cos=9'hDA;
      6'h13: lut_cos=9'hE6;
      6'h14: lut_cos=9'hF1;
      6'h15: lut_cos=9'hFC;
      6'h16: lut_cos=9'h107;
      6'h17: lut_cos=9'h111;
      6'h18: lut_cos=9'h11C;
      6'h19: lut_cos=9'h126;
      6'h1a: lut_cos=9'h130;
      6'h1b: lut_cos=9'h13A;
      6'h1c: lut_cos=9'h144;
      6'h1d: lut_cos=9'h14E;
      6'h1e: lut_cos=9'h157;
      6'h1f: lut_cos=9'h161;
      6'h20: lut_cos=9'h16A;
      6'h21: lut_cos=9'h172;
      6'h22: lut_cos=9'h17B;
      6'h23: lut_cos=9'h183;
      6'h24: lut_cos=9'h18B;
      6'h25: lut_cos=9'h193;
      6'h26: lut_cos=9'h19B;
      6'h27: lut_cos=9'h1A2;
      6'h28: lut_cos=9'h1A9;
      6'h29: lut_cos=9'h1B0;
      6'h2a: lut_cos=9'h1B7;
      6'h2b: lut_cos=9'h1BD;
      6'h2c: lut_cos=9'h1C3;
      6'h2d: lut_cos=9'h1C9;
      6'h2e: lut_cos=9'h1CE;
      6'h2f: lut_cos=9'h1D4;
      6'h30: lut_cos=9'h1D9;
      6'h31: lut_cos=9'h1DD;
      6'h32: lut_cos=9'h1E2;
      6'h33: lut_cos=9'h1E6;
      6'h34: lut_cos=9'h1E9;
      6'h35: lut_cos=9'h1ED;
      6'h36: lut_cos=9'h1F0;
      6'h37: lut_cos=9'h1F3;
      6'h38: lut_cos=9'h1F6;
      6'h39: lut_cos=9'h1F8;
      6'h3a: lut_cos=9'h1FA;
      6'h3b: lut_cos=9'h1FC;
      6'h3c: lut_cos=9'h1FD;
      6'h3d: lut_cos=9'h1FE;
      6'h3e: lut_cos=9'h1FF;
      6'h3f: lut_cos=9'h1FF;
   endcase
end

endmodule

主要难题

  1. 第一次学习stm32,学习使用stm32开发工具时经常出现问题,依靠网上教程解决。使用STM32cubemx时配置错误导致的问题在前期开发时消耗了大量的时间。
  2. 开始移植ssd1306驱动时完全使用四线spi协议导致无法实现通信,经过研究后改为三线手动片选控制后成功移植。
  3. stm32编写中断服务函数和spi发送函数时,由于没有调试器,只能通过屏幕大概了解程序执行情况,大大增加了开发难度。
  4. 由于stm32的encoder代码配置失败,只能采用三个按键控制,导致无法控制幅度。
  5. 第一次接触FPGA,学习了Verilog和FPGA开发流程,但是在使用radiant时依然遇到了大量的配置错误问题。观看直播视频和参考例程解决。
  6. FPGA实现DDS的方案思考,参考了互联网上实现案例。通过查表和相位累加器确定dac输出波形信息。
  7. FPGA移植spi时,由于没有调试器,无法明确知道fpga引脚接受到的信号和内部处理时的信息。参考Github上spi-slave代码实现通信,由于未知错误,只有单个信号控制可用。

未来计划

  1. 继续学习fpga,了解fpga高级应用。尝试实现示波器和risc-V软核移植。
  2. 继续学习stm32开发工具使用。尝试旋转编码器配置和OLED中文字库移植。

 

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