基于STM32+iCE40的电赛训练平台设计的DDS任意波形发生器(本地控制)
本项目是基于STM32+iCE40的电赛训练平台设计的DDS任意波形发生器/本地控制
标签
嵌入式系统
FPGA
DDS
2023寒假在家练
sqzr
更新2023-03-28
南京信息工程大学
315

项目需求

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

结果展示

  • 复位
    按下如下所示从左往右第二个按键进行复位,使用前务必先摁下复位键以进行初始化
  • ppVDjDe.jpg
  • 波形显示
    可以生成正弦波,方波与三角波。通过开发板上的按键K1可以切换波形,同时oled屏幕上会显示当前波形。
  • 正弦波
  • 方波
  • 三角波
  • 调节幅度与频率
    通过旋转旋钮可以调节幅度与频率,按下旋钮以切换调节频率与调节幅度,通过指示灯可显示当前调节的是幅值还是频率,指示灯亮表示调节的是频率;指示灯灭表示调节的是幅值
    频率调节步长为0.7Hz,当到达5MHz时,一个周期内仍有9.6个采样点,可保证波形不失真;幅值调节步长为0.203125v
  • ppVsUeg.md.jpg
  • 幅值

 

FPGA资源占用截图

FPGA资源占用截图

设计思路

  • 波形产生通过查找表的方式实现
  • fpga负责查找表并驱动ADC产生波形
  • stm32单片机负责控制fpga产生的波形、pinlv与幅值,并把相关信息显示到oled屏幕上

实现过程 FPGA部分

  1. 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方进行判断,目前的思路是可以考虑给发送的有效数据设置标识位。如果有更好的想法欢迎进行讨论与交流

未来的计划

    1. 通过串口将数据发送到上位机,在上位机进行显示,可以制作出更加绚丽的前端界面
    2. 通过旋钮调节幅值与频率十分不便,考虑通过上位机串口发送要设置幅值与频率,再配合前端界面可以简单的调节幅值与频率

 

附件下载
DDS_Test.7z
verilog源码与可直接烧录的二进制文件
stm32源码.7z
stm32源码(sw4stm32)
MyFirstProject.bin
团队介绍
南京信息工程大学
团队成员
sqzr
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号