Output, Control, Display, and Sound: A Versatile FPGA Signal Experiment Platform

Click the blue text to follow us

Output, Control, Display, and Sound: A Versatile FPGA Signal Experiment Platform

“From signal to melody, all it takes is a palm-sized FPGA signal generator.” This project is not just an electronic practice piece, but a miniature experimental platform that can “produce sound.” It is based on the “Little Foot” FPGA, capable of outputting sine waves, triangle waves, and square waves, with a frequency range covering 100Hz to 200kHz, precise amplitude adjustment, and even the ability to drive a buzzer to produce sound, while displaying waveform parameters in real-time and supporting PC-side LabVIEW/MATLAB interface control. With expertise in hand, one can play with the world of waveforms.

Output, Control, Display, and Sound: A Versatile FPGA Signal Experiment Platform

1. Project Requirements

Output, Control, Display, and Sound: A Versatile FPGA Signal Experiment Platform

Comprehensive topic – Create a signal generator that can be controlled by a host computer, with adjustable waveform, amplitude, and frequency output.

1. Capable of generating sine waves, triangle waves, and square waves, with waveform switching controlled by a toggle switch on the FPGA.2. The amplitude of the generated signal can be adjusted between 0-3Vpp, with a resolution of 10mV, adjustable via a potentiometer.3. The frequency of the generated signal can be adjusted between 100Hz – 200KHz, with a frequency adjustment resolution of up to 10Hz.4. Signals in the frequency range of 1KHz – 10KHz are sent to the buzzer simultaneously, driving the buzzer to produce sound; adjusting the frequency and amplitude of the signal will change the buzzer’s sound output.5. The generated waveform (as illustrated), its amplitude, and frequency are displayed in real-time on an OLED screen. A control interface can be written on the PC using LabVIEW, MATLAB, or other tools, allowing waveform, amplitude, and frequency parameters to be changed via the interface, with settings transmitted to the FPGA board via UART for waveform adjustment.6. Both PC control and board control are effective simultaneously.Output, Control, Display, and Sound: A Versatile FPGA Signal Experiment Platform

2. Overall Module Schematic

Output, Control, Display, and Sound: A Versatile FPGA Signal Experiment PlatformOutput, Control, Display, and Sound: A Versatile FPGA Signal Experiment Platform

  • Onboard data processing module: processes data from onboard buttons, toggle switches, and potentiometers, outputting control signal types (board_signal), signal frequency (three parameters: board_x10_n, board_1k_n, board_10k_n), signal amplitude (board_range), and UART ready-to-send flag signal (board_change_flag) to the top-level module.
  • UART transceiver module: responsible for UART transmission and reception, primarily using ASCII code.
  • OLED display module: responsible for displaying the current waveform type, frequency, and amplitude.
  • Waveform driving module: responsible for outputting the currently calibrated control signal type, frequency, and amplitude as a 10-bit digital signal, and outputting the corresponding triangle wave (unmodulated) to the buzzer driving module.
  • Buzzer driving module: responsible for driving the buzzer tone when the current frequency is in the range of 1k~10k, while introducing the amplitude value to adjust the buzzer’s pitch.
  • Top-level module: responsible for scheduling various modules and controlling flag signals.

Output, Control, Display, and Sound: A Versatile FPGA Signal Experiment Platform

3. Onboard Data Processing Module and Physical Display

Output, Control, Display, and Sound: A Versatile FPGA Signal Experiment Platform

Mainly includesbuttons,toggle switches,rotary potentiometer data processing. The onboard control schematic is as follows:

Output, Control, Display, and Sound: A Versatile FPGA Signal Experiment Platform

The buttons are defined from left to right asButton 1 ±10Hz,Button 2 ±1kHz,Button 3 ±10kHz, andReset button. Control is as shown in the figure above.

The driving code for the potentiometer and ADS7868 was directly referenced from the example code on the project page (Simple Voltmeter Design:https://www.eetree.cn/wiki/voltage_meter_sadc_verilog).

This module has two key points:

1. The three buttons and toggle switch control the frequency of the waveform.

2. Obtain the onboard data change flag (used to trigger UART transmission).

The code is as follows:

module board_data(    input clk,    input rst,    input [2:0]key,    input  [2:0]sw,
    input      dat,    // SPI bus SDA    output         cs,    // SPI bus CS    output       sclk,    // SPI bus SCK
    output  reg [1:0]board_signal,    output  reg [6:0]board_x10_n,    output  reg [3:0]board_x1k_n,    output  reg [4:0]board_x10k_n,    output  reg [7:0]board_range,
    output  reg board_change_flag);
    wire [2:0]key_pulse; // Instantiate debounce module    debounce #(.N(3)) u0(        .clk(clk),        .rst(rst),        .key(key),        .key_pulse(key_pulse)    );

    wire       adc_done;    wire  [7:0]adc_data;    wire adc_cs,adc_clk;       assign    cs   =  adc_cs;  assign    sclk =  adc_clk;    ADC7868_driver uadc(        .clk(clk),        .rst_n(rst),        .adc_cs(adc_cs),        .adc_clk(adc_clk),        .adc_dat(dat),        .adc_done(adc_done),        .adc_data(adc_data)    );
//---------------- Onboard data change flag judgment --------------------    reg [2:0]key_pulse_pre;    always @(posedge clk)        key_pulse_pre <= key_pulse;
    reg [2:0]sw_pre;    always @(posedge clk)        sw_pre <= sw;
    reg [16:0]cnt10ms;    always @(posedge clk or negedge rst) begin        if(!rst)            cnt10ms <= 17'd0;        else if(cnt10ms == 17'd120_000)            cnt10ms <= 17'd0;        else            cnt10ms <= cnt10ms + 1'b1;    end
    reg [7:0]board_range_gap;    reg [7:0]board_range_pre;    always @(posedge cnt10ms[16])        board_range_pre <= board_range;
    always @(posedge clk) begin        if(board_range_pre >= board_range)            board_range_gap <= board_range_pre - board_range;        else            board_range_gap <= board_range - board_range_pre;    end

    always @(posedge clk or negedge rst) begin        if(!rst)            board_change_flag <= 1'b0;                else if(key_pulse_pre != key_pulse)            board_change_flag <= 1'b1;        else if(sw_pre != sw)            board_change_flag <= 1'b1;        //else if(board_range_gap >= 8'd3)//board_change_flag <= 1'b1;        else            board_change_flag <= 1'b0;    end
// Waveform type selection (onboard)//reg [1:0]board_signal;    always @(*)case(sw[1:0])2'b00: board_signal <= 2'b00;2'b01: board_signal <= 2'b01;2'b10: board_signal <= 2'b10;default:;        endcase

// Waveform modulation factor (onboard)//reg [7:0]board_range; // Onboard button frequency adjustment logic carry borrow//reg [6:0]board_x10_n;//reg [3:0]board_x1k_n;//reg [4:0]board_x10k_n;    always @(posedge clk or negedge rst)        if(!rst) begin            board_x10_n <= 7'd10;            board_x1k_n <= 4'd0;            board_x10k_n <= 5'd0;        end        else begin            case({sw[2],key_pulse})                4'b0100: begin                    if(board_x10_n == 7'd99) begin        // + 10                                                if(board_x1k_n == 4'd9)begin                            if(board_x10k_n < 5'd20) begin                                board_x10_n <= 7'd0;                                board_x1k_n <= 4'd0;                                board_x10k_n <= board_x10k_n + 1'b1;endendelse begin                            board_x10_n <= 7'd0;                            board_x1k_n <= board_x1k_n + 1'b1;endendelse if(board_x10k_n < 5'd21)                        board_x10_n <= board_x10_n + 1'b1;end
4'b0010: begin                            //+ 1k                    if(board_x1k_n == 4'd9)     begin                        if(board_x10k_n < 5'd21) begin                            board_x1k_n <= 4'd0;                            board_x10k_n <= board_x10k_n + 1'b1;                        end                    end                    else if(board_x1k_n < 4'd9) begin                        if(board_x10k_n < 5'd21)                            board_x1k_n <= board_x1k_n + 1'b1;endend
4'b0001:                    if(board_x10k_n < 5'd20)                        board_x10k_n <= board_x10k_n + 1'b1; 
                4'b1100: begin                    if(board_x10_n > 7'd10)                        board_x10_n <= board_x10_n - 1'b1;else begin                        if(board_x10_n == 7'd0)                            if(board_x1k_n == 4'd0)                                if(board_x10k_n == 5'd0)                                    ;                                else begin                                    board_x10k_n <= board_x10k_n - 1'b1;                                    board_x1k_n <= 4'd9;                                    board_x10_n <= 7'd99;endelse begin                                board_x1k_n <= board_x1k_n - 1'b1;                                board_x10_n <= 7'd99;endelse                            if(board_x1k_n == 4'd0 && board_x10k_n == 5'd0)                                ;else                                board_x10_n <= board_x10_n - 1'b1;                                        end                end
                4'b1010: begin                    if(board_x1k_n == 4'd0) begin                        if(board_x10k_n == 5'd0)                            ;else if(board_x10k_n > 5'd0)begin                            board_x10k_n <= board_x10k_n - 1'b1;                            board_x1k_n <= 4'd9;                        end                    end                    else if(board_x1k_n == 4'd1) begin                        if(board_x10k_n == 5'd0) begin                            if(board_x10_n >= 7'd10)                                board_x1k_n <= board_x1k_n - 1'b1;                        end                        else if(board_x10k_n > 5'd0)begin                            board_x1k_n <= board_x1k_n - 1'b1;                        end                    end                    else if(board_x1k_n >= 4'd2) begin                        board_x1k_n <= board_x1k_n - 1'b1;                    end                end
                4'b1001: begin                    if(board_x10k_n == 5'd1) begin                        if(board_x10_n >= 7'd10)                            board_x10k_n <= board_x10k_n - 1'b1;                        else if(board_x1k_n != 4'd0)                            board_x10k_n <= board_x10k_n - 1'b1;                                                end                    else if(board_x10k_n >= 5'd2) begin                        board_x10k_n <= board_x10k_n - 1'b1;                    end                end
                default: ;            endcase        end
// Waveform amplitude selection    always @(posedge clk or negedge rst) begin        if(adc_done == 1'b1)                board_range <= adc_data;    end
endmodule

Output, Control, Display, and Sound: A Versatile FPGA Signal Experiment Platform

4. UART Transceiver Module

Output, Control, Display, and Sound: A Versatile FPGA Signal Experiment PlatformThe UART transceiver 1-byte driving code directly references the example code on the project page (Reference UART Monitoring System Design:https://www.eetree.cn/wiki/%E4%B8%B2%E5%8F%A3%E7%9B%91%E8%A7%86%E7%B3%BB%E7%BB%9F%E8%AE%BE%E8%AE%A1), and the following figure shows the RTL view of transmitting and receiving one byte of data.

Output, Control, Display, and Sound: A Versatile FPGA Signal Experiment Platform

This design is a signal generator that customizes the UART protocol.

  • 1. Characters “s” / “t” / “q” represent sine wave/triangle wave/square wave respectively.

  • 2. Character “F” represents frequency.

  • 3/4/5/6/7. Characters “0”~”9″ represent the first five digits of the frequency value.

  • 8. Character “U” represents amplitude.

  • 9/10/11/12. Characters “0”~”3″ represent amplitude factors “——” “——” “——” “——” (high~low, two digits represent one character, totaling 8 digits, or 4 characters).

  • 13. End character “$”.

The code is as follows:

module uart(    input clk,    input rst,    input uart_rxd_pin,    input txd_ready_flag,          // UART ready-to-send flag    input [1:0]uart_t_signal,      // Waveform name    input [6:0]uart_t_x10_n,    input [3:0]uart_t_x1k_n,    input [4:0]uart_t_x10k_n,    input [7:0]uart_t_range,       // Amplitude factor
    output uart_txd_pin,    output reg rxd_finish_flag,    // UART reception complete flag    output [1:0]uart_r_signal, // Waveform name    output [6:0]uart_r_x10_n,    output [3:0]uart_r_x1k_n,    output [4:0]uart_r_x10k_n,    output [7:0]uart_r_range   // Amplitude factor);
    //------------------ UART send and receive state machine ---------------------//-----------------------------------------------------------    reg [7:0]txd_state,txd_next_state;    localparam  // UART send state        txd_s1  = 8'b0000_0001,        txd_s2  = 8'b0000_0010,        txd_s3  = 8'b0000_0100,        txd_s4  = 8'b0000_1000,        txd_s5  = 8'b0001_0000,        txd_s6  = 8'b0010_0000,        txd_s7  = 8'b0100_0000,        txd_s8  = 8'b1000_0000;

    reg [4:0]rxd_state;            localparam  // UART receive state        rxd_s1  = 4'b0001,        rxd_s2  = 4'b0010,        rxd_s3  = 4'b0100,        rxd_s4  = 4'b1000;//-----------------------------------------------------------

//---------------- UART send byte interval counter ---------------------//-----------------------------------------------------------    reg    [7:0]byteinterval_cnt;  // UART send byte interval counter    reg         byteinterval_flag; // UART send byte interval flag signal    localparam  byte_interval = 8'd80;
// UART send interval counter counting control    always @(posedge clk or negedge rst) beginif(!rst)            byteinterval_cnt <= 0;else if(byteinterval_flag == 1'b1)            byteinterval_cnt <= byteinterval_cnt + 1'b1;else if(byteinterval_flag == 1'b0)            byteinterval_cnt <= 0;    end//------------------------------------------------------------

//-------------------- Instantiate data conversion module -----------------------//-----------------------------------------------------------    reg [1:0]rxd_signal;    reg [19:0]rxd_frequency;    reg [7:0]rxd_range;
    wire [7:0]txd_signal;    wire [19:0]txd_frequency;    wire [15:0]txd_range; 
    wire [1:0]uart_r_signal0;    wire [6:0]uart_r_x10_n0;    wire [3:0]uart_r_x1k_n0;    wire [4:0]uart_r_x10k_n0;    wire [7:0]uart_r_range0;    assign uart_r_signal = uart_r_signal0;    assign uart_r_x10_n = uart_r_x10_n0;    assign uart_r_x1k_n = uart_r_x1k_n0;    assign uart_r_x10k_n = uart_r_x10k_n0;    assign uart_r_range = uart_r_range0;
serial_data_conversion uconversion(        // UART send data (before conversion)        .uart_t_signal(uart_t_signal),      // Waveform name        .uart_t_x10_n(uart_t_x10_n),        .uart_t_x1k_n(uart_t_x1k_n),        .uart_t_x10k_n(uart_t_x10k_n),        .uart_t_range(uart_t_range),       // Amplitude factor// UART receive data (before conversion)        .rxd_signal(rxd_signal),        .rxd_frequency(rxd_frequency),        .rxd_range(rxd_range),
// UART send data (after conversion)        .txd_signal(txd_signal),        .txd_frequency(txd_frequency),        .txd_range(txd_range),  
// UART receive data (after conversion)        .uart_r_signal(uart_r_signal0), // Waveform name        .uart_r_x10_n(uart_r_x10_n0),        .uart_r_x1k_n(uart_r_x1k_n0),        .uart_r_x10k_n(uart_r_x10k_n0),        .uart_r_range(uart_r_range0)   // Amplitude factor    );//-----------------------------------------------------------

//------------------ Instantiate UART receive 1-byte module ---------------------//-----------------------------------------------------------    wire [7:0]rx_1byte_data;    reg  [7:0]tx_1byte_data;    reg  tx_data_valid;    wire rx_data_valid;    wire uart_tx_pin;    wire tx1byte_finish_flag;     // During transmission = 0  Transmission complete and idle time = 1    assign uart_txd_pin = uart_tx_pin;uart_1byte uart_1byte0(        .clk(clk),        .rst(rst),        .uart_rxd_pin(uart_rxd_pin),      // UART receive pin
        .rx_data_out(rx_1byte_data),      // UART received data        .rx_data_valid(rx_data_valid),    // UART received data flag        .tx_data_in(tx_1byte_data),       // Data to be sent via UART        .tx_data_vaild(tx_data_valid),    // UART ready-to-send pulse flag signal 
        .uart_txd_pin(uart_tx_pin),        // UART send pin                .tx1byte_finish_flag(tx1byte_finish_flag)    );//-----------------------------------------------------------

//--------------------------- UART send protocol module ------------------------------------//-------------------------------------------------------------------------------    reg [2:0]nX4_flag;    reg tx_data_valid;    reg [7:0]tx_1byte_data;    reg [19:0]txd_f;    reg [15:0]txd_r;    always @(posedge clk or negedge rst) beginif(!rst) begin            txd_state <= txd_s1;            tx_data_valid <= 1'b0;            nX4_flag <= 3'd0;endelse begincase(txd_state)                txd_s1: beginif(txd_ready_flag) begin                        txd_r <= txd_range;                        txd_f <= txd_frequency;
                        tx_1byte_data <= txd_signal;  // s/t/q waveform type                        tx_data_valid <= 1'b1;                        txd_state      <= txd_s2;                        txd_next_state <= txd_s3;                    endelse begin                        tx_data_valid <= 1'b0;                        txd_state <= txd_s1;                        end                end
                txd_s2: txd_state <= txd_next_state;  // UART send buffer state                txd_s3: beginif(tx1byte_finish_flag) begin                        byteinterval_flag <= 1'b1; // Start interval counter flag signal (can be understood as delay)if(byteinterval_cnt >= byte_interval) begin  // Check if the byte interval counter has reached the requirement (the counter is counted in another always block)                            tx_1byte_data <= 8'd70;        // Assign the next data to be sent as 'F'                            tx_data_valid <= 1'b1;                   // Set send flag signal to 1                            txd_state <= txd_s2;                            txd_next_state <= txd_s4;                            byteinterval_flag <= 1'b0;                // Turn off byte interval counter flag signal                        end    else                            txd_state <= txd_s3;                    endelse begin                        tx_data_valid <= 1'b0;                        txd_state <= txd_s3;                    end                end
                txd_s4: beginif(nX4_flag >= 3'd5) begin                        txd_state <= txd_s5;                        nX4_flag  <= 3'd0;endelse beginif(tx1byte_finish_flag) begin                            byteinterval_flag <= 1'b1; // Start interval counter flag signal (can be understood as delay)if(byteinterval_cnt >= byte_interval) begin  // Check if the byte interval counter has reached the requirement (the counter is counted in another always block)                                tx_1byte_data <= {4'd3,txd_f[19:16]};    // Assign the next data to be sent                                txd_f <= txd_f << 4;                                tx_data_valid <= 1'b1;                   // Set send flag signal to 1                                txd_state <= txd_s2;                                                            byteinterval_flag <= 1'b0;                // Turn off byte interval counter flag signal                                                    txd_next_state <= txd_s4;                                nX4_flag <= nX4_flag + 1'b1;
                            end    else                                txd_state <= txd_s4;                        endelse begin                            tx_data_valid <= 1'b0;                            txd_state <= txd_s4;                        end                    end                end
                txd_s5: beginif(tx1byte_finish_flag) begin                        byteinterval_flag <= 1'b1; // Start interval counter flag signal (can be understood as delay)if(byteinterval_cnt >= byte_interval) begin  // Check if the byte interval counter has reached the requirement (the counter is counted in another always block)                            tx_1byte_data <= 8'd85;        // Assign the next data to be sent as 'U'                            tx_data_valid <= 1'b1;                   // Set send flag signal to 1                            txd_state   <= txd_s2;                            txd_next_state <= txd_s6;                            byteinterval_flag <= 1'b0;                // Turn off byte interval counter flag signal                        end    else                            txd_state <= txd_s5;                    endelse begin                        tx_data_valid <= 1'b0;                        txd_state <= txd_s5;                    end                end
                txd_s6: beginif(nX4_flag >= 3'd4) begin                        txd_state <= txd_s7;                        nX4_flag  <= 3'd0;endelse beginif(tx1byte_finish_flag) begin                            byteinterval_flag <= 1'b1; // Start interval counter flag signal (can be understood as delay)if(byteinterval_cnt >= byte_interval) begin  // Check if the byte interval counter has reached the requirement (the counter is counted in another always block)                                tx_1byte_data <= {4'd3,txd_r[15:12]};    // Assign the next data to be sent                                txd_r <= txd_r << 4;                                tx_data_valid <= 1'b1;                   // Set send flag signal to 1                                txd_state <= txd_s2;                                                            byteinterval_flag <= 1'b0;                // Turn off byte interval counter flag signal                                                    txd_next_state <= txd_s6;                                nX4_flag <= nX4_flag + 1'b1;                            end    else                                txd_state <= txd_s6;                        endelse begin                            tx_data_valid <= 1'b0;                            txd_state <= txd_s6;                        end                    end                end
                txd_s7: beginif(tx1byte_finish_flag) begin                        byteinterval_flag <= 1'b1; // Start interval counter flag signal (can be understood as delay)if(byteinterval_cnt >= byte_interval) begin  // Check if the byte interval counter has reached the requirement (the counter is counted in another always block)                            tx_1byte_data <= 8'd36;        // Assign the next data to be sent as '$'                            tx_data_valid <= 1'b1;                   // Set send flag signal to 1                            txd_state   <= txd_s2;                            txd_next_state <= txd_s8;                            byteinterval_flag <= 1'b0;                // Turn off byte interval counter flag signal                        end    else                            txd_state <= txd_s7;                    endelse begin                        tx_data_valid <= 1'b0;                        txd_state <= txd_s7;                    end                end
                txd_s8: beginif(tx1byte_finish_flag) begin                        byteinterval_flag <= 1'b1; // Start interval counter flag signal (can be understood as delay)if(byteinterval_cnt >= byte_interval)begin   // Check if the byte interval counter has reached the requirement (the counter is counted in another always block)                                byteinterval_flag <= 1'b0;                // Turn off byte interval counter flag signal                            txd_state <= txd_s1;                        endelse                            txd_state <= txd_s8;                    endelse begin                        tx_data_valid <= 1'b0;                        txd_state <= txd_s8;                    end                end
default: txd_state <= txd_s1;            endcase        end    end//-------------------------------------------------------------------------------

//--------------------------- UART receive protocol module ------------------------------------//-------------------------------------------------------------------------------    reg [19:0]rxd_f;    reg [ 7:0]rxd_r;    reg [2:0]yn_flag;    reg yi_flag;    always @(posedge clk or negedge rst) beginif(!rst) begin            rxd_state <= rxd_s1;            rxd_finish_flag <= 1'b0;            yn_flag <= 3'd0;            yi_flag <= 1'b0;endelse begincase(rxd_state)                rxd_s1:beginif(rx_data_valid) begin                        rxd_finish_flag <= 1'b0;                        rxd_state <= rxd_s2;if(rx_1byte_data == 8'd115)      // 's'                            rxd_signal <= 2'b00;  else if(rx_1byte_data == 8'd116) // 't'                            rxd_signal <= 2'b01; else if(rx_1byte_data == 8'd113) // 'q'                            rxd_signal <= 2'b10;                     endelse begin                        rxd_state <= rxd_s1;                        rxd_finish_flag <= 1'b0;                        end                end
                rxd_s2: begin  // Receive integer partif(rx_data_valid ) beginif(rx_1byte_data == 8'd70)    // Ensure received character is 'F'                            rxd_state <= rxd_s3;   else                            rxd_state <= rxd_s1;                                           endelse                        rxd_state <= rxd_s2;                                             // Continue waiting for reception                end
                rxd_s3: begin  if(rx_data_valid ) beginif(rx_1byte_data >= 8'd48 && rx_1byte_data < 8'd58) begin   // Ensure received character is a digit                            rxd_f[3:0] <= {rx_1byte_data[3:0]};  // Store frequency in register                            yi_flag <= 1'b1;                            yn_flag <= yn_flag + 1'b1;                            rxd_state <= rxd_s3;endelse if(rx_1byte_data == 8'd85)begin     // Ensure received character is 'U'                            rxd_state <= rxd_s4;                             yn_flag <= 3'd0;  // Shift count                            yi_flag <= 1'b0;  // Shift flag                            rxd_frequency <= rxd_f;                            end                    endelse beginif(yi_flag == 1'b1 && yn_flag < 3'd5) begin    // Ensure high 4 bits are received, then shift on the next clock pulse                                                    yi_flag <= 1'b0;                            rxd_f <= rxd_f << 4;                        end                        rxd_state <= rxd_s3;                   // Continue waiting for reception                    end                end
                rxd_s4: begin  if(rx_data_valid ) beginif(rx_1byte_data >= 8'd48 && rx_1byte_data < 8'd58) begin   // Ensure received character is a digit                            rxd_r[1:0] <= {rx_1byte_data[1:0]};  // Store frequency in register                                                        yi_flag <= 1'b1;                            yn_flag <= yn_flag + 1'b1;                            rxd_state <= rxd_s4;endelse if(rx_1byte_data == 8'd36)begin     // Ensure received character is end character '$'                            rxd_state <= rxd_s1;                             rxd_range <= rxd_r;                                rxd_finish_flag <= 1'b1;                            yn_flag <= 3'd0;  // Shift count                            yi_flag <= 1'b0;  // Shift flagend                    endelse beginif(yi_flag == 1'b1 && yn_flag < 3'd4) begin    // Ensure high 4 bits are received, then shift on the next clock pulse                                                    yi_flag <= 1'b0;                            rxd_r <= rxd_r << 2;                        end                        rxd_state <= rxd_s4;                   // Continue waiting for reception                    end                end
default:rxd_state <= rxd_s1;            endcase        end    end

endmodule

Output, Control, Display, and Sound: A Versatile FPGA Signal Experiment Platform

5. Waveform Driving Module

Output, Control, Display, and Sound: A Versatile FPGA Signal Experiment Platform

The principle of the DDS signal generator (can directly refer to the DDS generating arbitrary frequency waveform principle and example code:https://www.eetree.cn/wiki/dds_verilog).

Mainly used to drive and generate digital signals for three types of waveforms (after modulation) and the current frequency triangle wave digital signal (before modulation). The code is as follows:

module waveform_point_drive(    input clk,    input rst,    input [1:0]signal,    input [6:0]x10_n,        input [3:0]x1k_n,    input [4:0]x10k_n,    input [7:0]range,
    output  [9:0]buzzer_use,    output  [9:0]signal_out);
pll_clk120M  uclk120m(        .CLKI(clk),        .CLKOP(clk_120M)    );// Frequency adjustment accumulator    reg [31:0]phase_acc;    reg [23:0]add;    always @(posedge clk_120M)             phase_acc <= phase_acc + add;// Generate three types of waveform data    wire [9:0] sin_dat;     lookup_sin u1(        .phase(phase_acc[31:24]),        .sin_out(sin_dat)    );
    wire [9:0] tri_dat;    assign buzzer_use = tri_dat;lookup_tri u2(        .phase(phase_acc[31:24]),        .triangle_out(tri_dat)    );
    wire [9:0] squ_dat;lookup_squ u3(        .phase(phase_acc[31:24]),         .square_out(squ_dat)    );

// Select waveform type data    reg [9:0]signal_dat;    always @(*)case(signal)2'b00: signal_dat <= sin_dat;2'b01: signal_dat <= tri_dat;2'b10: signal_dat <= squ_dat;default:;        endcase

// Set according to frequency    reg [15:0]add1;    reg [18:0]add2;    reg [22:0]add3;
    always @(x10_n or x1k_n or x10k_n) begin        add1 <= x10_n*9'd358;        add2 <= x1k_n*16'd35791;        add3 <= x10k_n*19'd357914;    end
    always @(posedge clk or negedge rst)if(!rst) add <= 24'd0;elseadd <= add1 + add2 + add3;

    reg [17:0] amp_dat; // Amplitude-modulated waveform data    always @(posedge clk) amp_dat = signal_dat * (range + 1'b1);  // Multiply waveform data by modulation factor    assign signal_out = amp_dat[17:8]; // Output the high ten bits, equivalent to right shift by 8 bits
endmodule

Output, Control, Display, and Sound: A Versatile FPGA Signal Experiment Platform

6. OLED Display Module

Output, Control, Display, and Sound: A Versatile FPGA Signal Experiment Platform

Here, the example code for OLED driving instructions and Verilog code instances is directly referenced:https://www.eetree.cn/wiki/oled_spi_verilog

The display here is subject to change······

Output, Control, Display, and Sound: A Versatile FPGA Signal Experiment Platform

7. Buzzer Driving Module

Output, Control, Display, and Sound: A Versatile FPGA Signal Experiment Platform

Frequency controls the tone

Amplitude controls the pitch

This design does not directly generate the corresponding frequency PWM wave, but uses the triangle wave output from the waveform driving module (after frequency modulation, before amplitude modulation). Then, it compares the input amplitude parameter – modulation factor (range) with it to output the PWM wave (thus ensuring the corresponding frequency and duty cycle).

The code is as follows:

module buzzer(    input clk,    input rst,    input buzzer_en,    input [7:0]range,    input [9:0]buzzer_use,    output reg buzzer);
    reg [15:0] liuliu; // Amplitude-modulated waveform data    always @(posedge clk) liuliu = range * 96;  // Multiply waveform data by modulation factor        always @(posedge clk or negedge rst) beginif(buzzer_en == 1'b1) beginif(buzzer_use[9:3] >= liuliu[14:8])                buzzer <= 1'b0; else                buzzer <= 1'b1;endelse if(buzzer_en == 1'b0)            buzzer <= 1'b0;    endendmodule 

Output, Control, Display, and Sound: A Versatile FPGA Signal Experiment Platform

8. Top-Level Module

Output, Control, Display, and Sound: A Versatile FPGA Signal Experiment Platform

The focus of this module is mainly on the following aspects:

Declaration: The output of the waveform and the OLED display depend on the parameters of the same state, and both are completely consistent at any time.

Background 1: The output of the waveform and the OLED display are controlled by two modules, namelyonboard module and host computer module. The current waveform parameters are displayed in two places, the OLED display and the host computer display.

Key Point 1: Which input module should determine the current output of the waveform and the OLED display?

Solution 1: Set up the onboard parameter change flag board_change_flag and the UART reception complete flag rxd_finish_flag as two flag signals. If either flag is 1, the current output will be executed by that module; in idle state (both flags are 0), the output will be the corresponding module of the last flag that was 1, until a flag is 1 again, changing to the corresponding module output.

Key Point 2: When switching modules for related parameter input, the actual output of the waveform and the OLED display may change significantly.

Solution 2: Currently, when inputting parameters from the onboard module, not only should the relevant parameters be output to the waveform output module, but also the relevant parameters should be input to the host computer, allowing the host computer to synchronize with the current signal output. Similarly, when adjusting parameters from the host computer, not only should the relevant parameters be output to the waveform output module, but also the relevant parameters should be input to the onboard parameter storage register. This ensures synchronization and coordination among multiple modules.

Existing Issues::

1. Due to the onboard parameter input, such as potentiometers and toggle switches, which can only be adjusted by external force, the amplitude and waveform type changes cannot be synchronized. However, the buttons adjust the frequency parameters through accumulated pulse input, so frequency parameters can be synchronized. In summary, some onboard parameters (waveform type and amplitude) cannot synchronize with the host computer (frequency can synchronize); while the host computer can synchronize with all onboard parameters.

2. The blue potentiometer adjusts the amplitude of the waveform, and there will still be data loss and synchronization issues when synchronizing with the host computer and onboard.

3. At high frequencies, some waveforms exhibit significant distortion, especially during amplitude modulation, which should be improved in hardware by adding a DAC module.

Output, Control, Display, and Sound: A Versatile FPGA Signal Experiment Platform

Click “Read the original text” to view the project

Leave a Comment