• 野火FPGA入门(5)


    第17讲:触摸按键控制LED灯

    触摸按键可分为四大类:电阻式、电容式、红外感应式、表面声波式

    电容式触摸按键主要由按键IC部分和电容部分构成;按键IC用于将电容的变化转换为电信号;电容部分指的是由电容极板、地、隔离区等组成触摸按键的电容环境。
    在这里插入图片描述

    模块绘制
    在这里插入图片描述
    波形图绘制:按键未按下时保持高电平,按键按下保持低电平;按下时间与低电平保持时间相同,松开按键返回高电平
    使用按键下降沿控制灯的点亮和熄灭
    在这里插入图片描述

    touch_ctrl_led.v

    `timescale  1ns/1ns
    
    module  touch_ctrl_led
    (
        input   wire    sys_clk     ,   //系统时钟,频率50MHz
        input   wire    sys_rst_n   ,   //复位信号,低电平有效
        input   wire    touch_key   ,   //触摸按键信号
    
        output  reg     led             //led输出信号
    );
    
    //wire  define
    wire    touch_flag    ;   //触摸使能信号
    
    //reg   define
    reg touch_key_dly1  ;   //touch_key延迟一个时钟信号
    reg touch_key_dly2  ;   //touch_key延迟两个时钟信号
    
    //对touch_key信号延迟两个时钟周期用来产生触摸按键信号
    always@(posedge sys_clk or  negedge sys_rst_n)
        if(sys_rst_n == 1'b0)
            begin
                touch_key_dly1  <=  1'b1;
                touch_key_dly2  <=  1'b1;
            end
        else
            begin
                touch_key_dly1  <=  touch_key;
                touch_key_dly2  <=  touch_key_dly1;
            end
    
    //根据触摸按键信号的下降沿判断触摸了触摸按键
    assign  touch_flag    =   ((touch_key_dly1 == 1'b0) && (touch_key_dly2 == 1'b1));
    
    //根据触摸使能信号控制led状态
    always@(posedge sys_clk or negedge sys_rst_n)
        if(sys_rst_n == 1'b0)
            led <=  1'b1;
        else    if(touch_flag == 1'b1)
            led <=  ~led;
        else  
            led <=  led;
            
    endmodule
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    `timescale  1ns/1ns
    
    module  tb_touch_ctrl_led();
    
    //wire  define
    wire    led         ;
    
    //reg   define
    reg     sys_clk     ;
    reg     sys_rst_n   ;
    reg     touch_key   ;
    
    //sys_clk,sys_rst_n初始赋值,模拟触摸按键信号值
    initial
        begin
            sys_clk       =   1'b1 ;
            sys_rst_n    <=   1'b0 ;
            touch_key    <=   1'b1 ;
            #20
            sys_rst_n    <=   1'b1 ;
            #200
            touch_key    <=   1'b0 ;
            #2000
            touch_key    <=   1'b1 ;
            #1000
            touch_key    <=   1'b0 ;
            #3000
            touch_key    <=   1'b1 ;
        end
    
    //clk:产生时钟
    always  #10 sys_clk = ~sys_clk ;
    
    //------------- touch_ctrl_led_inst -------------
    touch_ctrl_led    touch_ctrl_led_inst
    (
        .sys_clk    (sys_clk    ),  //系统时钟,频率50MHz
        .sys_rst_n  (sys_rst_n  ),  //复位信号,低电平有效
        .touch_key  (touch_key  ),  //触摸按键信号
    
        .led        (led        )   //led输出信号
    );
    
    endmodule
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44



    第18讲:流水灯

    模块设计
    在这里插入图片描述
    波形绘制:0.5秒转换一个灯
    在这里插入图片描述

    water_led.v

    `timescale  1ns/1ns
    
    module  water_led
    #(
        parameter CNT_MAX = 25'd24_999_999
    )
    (
        input   wire            sys_clk     ,   //系统时钟50Mh
        input   wire            sys_rst_n   ,  //全局复位
    
        output  wire    [3:0]   led_out        //输出控制led灯
    
    );
    
    //reg   define
    reg     [24:0]  cnt         ;
    reg             cnt_flag    ;
    reg     [3:0]   led_out_reg ;
    
    //cnt:计数器计数500ms
    always@(posedge sys_clk or negedge sys_rst_n)
        if(sys_rst_n == 1'b0)
            cnt <= 25'b0;
        else    if(cnt == CNT_MAX)
            cnt <= 25'b0;
        else
            cnt <= cnt + 1'b1;
    
    //cnt_flag:计数器计数满500ms标志信号
    always@(posedge sys_clk or negedge sys_rst_n)
        if(sys_rst_n == 1'b0)
            cnt_flag <= 1'b0;
        else    if(cnt == CNT_MAX - 1)
            cnt_flag <= 1'b1;
        else
            cnt_flag <= 1'b0;
    
    //led_out_reg:led循环流水
    always@(posedge sys_clk or negedge sys_rst_n)
        if(sys_rst_n == 1'b0)
            led_out_reg <=  4'b0001;
        else    if(led_out_reg == 4'b1000 && cnt_flag == 1'b1)
            led_out_reg <=  4'b0001;
        else    if(cnt_flag == 1'b1)
            led_out_reg <=  led_out_reg << 1'b1; //左移
    
    assign  led_out = ~led_out_reg;
    
    endmodule
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    tb_water_led.v

    `timescale  1ns/1ns
    
    module  tb_water_led();
    
    //wire  define
    wire    [3:0]   led_out     ;
    
    //reg   define
    reg             sys_clk     ;
    reg             sys_rst_n   ;
    
    //初始化系统时钟、全局复位
    initial begin
        sys_clk    = 1'b1;
        sys_rst_n <= 1'b0;
        #20
        sys_rst_n <= 1'b1;
    end
    
    //sys_clk:模拟系统时钟,每10ns电平翻转一次,周期为20ns,频率为50Mhz
    always #10 sys_clk = ~sys_clk;
    
    //-------------------- water_led_inst --------------------
    water_led   
    #(
        .CNT_MAX    (25'd24)
    )
    water_led_inst
    (
        .sys_clk    (sys_clk    ),  //input          sys_clk
        .sys_rst_n  (sys_rst_n  ),  //input          sys_rst_n
                        
        .led_out    (led_out    )   //output  [3:0]  led_out
    );
    
    endmodule
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36



    第19讲:呼吸灯

    LED灯低电平点亮,高电平熄灭。

    模块设计
    在这里插入图片描述
    波形图绘制
    在这里插入图片描述
    在这里插入图片描述

    breath_led.v

    `timescale  1ns/1ns
    
    module  breath_led
    #(
        parameter CNT_1US_MAX = 6'd49   ,
        parameter CNT_1MS_MAX = 10'd999 ,
        parameter CNT_1S_MAX  = 10'd999
    )
    (
        input   wire    sys_clk     ,   //系统时钟50Mhz
        input   wire    sys_rst_n   ,   //全局复位
    
        output  reg     led_out         //输出信号,控制led灯
    );
    
    //reg define
    reg [5:0]   cnt_1us     ;
    reg [9:0]   cnt_1ms     ;
    reg [9:0]   cnt_1s      ;
    reg         cnt_1s_en   ;  //方便点亮到熄灭,熄灭到点亮的取反
    
    //cnt_1us:1us计数器
    always@(posedge sys_clk or negedge sys_rst_n)
        if(sys_rst_n == 1'b0)
            cnt_1us <= 6'b0;
        else    if(cnt_1us == CNT_1US_MAX)
            cnt_1us <= 6'b0;
        else
            cnt_1us <= cnt_1us + 1'b1;
    
    //cnt_1ms:1ms计数器
    always@(posedge sys_clk or negedge sys_rst_n)
        if(sys_rst_n == 1'b0)
            cnt_1ms <= 10'b0;
        else    if(cnt_1ms == CNT_1MS_MAX && cnt_1us == CNT_1US_MAX)
            cnt_1ms <= 10'b0;
        else    if(cnt_1us == CNT_1US_MAX)
            cnt_1ms <= cnt_1ms + 1'b1;
        else   
            cnt_1ms <= cnt_1ms;
            
    //cnt_1s:1s计数器
    always@(posedge sys_clk or negedge sys_rst_n)
        if(sys_rst_n == 1'b0)
            cnt_1s <= 10'b0;
        else    if(cnt_1s == CNT_1S_MAX && cnt_1ms == CNT_1MS_MAX && cnt_1us == CNT_1US_MAX)
            cnt_1s <= 10'b0;
        else    if(cnt_1ms == CNT_1MS_MAX && cnt_1us == CNT_1US_MAX)
            cnt_1s <= cnt_1s + 1'b1;
        else 
            cnt_1s <= cnt_1s;
            
    //cnt_1s_en:1s计数器使能信号
    always@(posedge sys_clk or negedge sys_rst_n)
        if(sys_rst_n == 1'b0)
            cnt_1s_en <= 1'b0;
        else    if(cnt_1s == CNT_1S_MAX && cnt_1ms == CNT_1MS_MAX  && cnt_1us == CNT_1US_MAX)
            cnt_1s_en <= ~cnt_1s_en;
        else   
            cnt_1s_en <= cnt_1s_en;
            
    //led_out:输出信号连接到外部的led灯
    always@(posedge sys_clk or negedge sys_rst_n)
        if(sys_rst_n == 1'b0)
            led_out <= 1'b1;
        else    if((cnt_1s_en == 1'b0 && cnt_1ms <= cnt_1s) || (cnt_1s_en == 1'b1 && cnt_1ms > cnt_1s))
            led_out <= 1'b0;
        else
            led_out <= 1'b1;
    
    endmodule
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71

    tb_breath_led.v

    `timescale  1ns/1ns
    
    module  tb_breath_led();
    
    //wire  define
    wire    led_out     ;
    
    //reg   define
    reg     sys_clk     ;
    reg     sys_rst_n   ;
    
    //初始化系统时钟、全局复位
    initial begin
        sys_clk    = 1'b1;
        sys_rst_n <= 1'b0;
        #20
        sys_rst_n <= 1'b1;
    end
    
    //sys_clk:模拟系统时钟,每10ns电平翻转一次,周期为20ns,频率为50Mhz
    always #10 sys_clk = ~sys_clk;
    
    //-------------------- breath_led_inst --------------------
    breath_led
    #(
        .CNT_1US_MAX(6'd4   ),
        .CNT_1MS_MAX(10'd9  ),
        .CNT_1S_MAX (10'd9  )
    )
    breath_led_inst
    (
        .sys_clk    (sys_clk    ),  //input     sys_clk
        .sys_rst_n  (sys_rst_n  ),  //input     sys_rst_n
    
        .led_out    (led_out    )   //output    led_out
    );
    
    endmodule
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38



    第20讲:状态机

    在这里插入图片描述

    例:简单状态机
    模块设计
    在这里插入图片描述

    输入:投入1元硬币
    输出:出可乐,不出可乐
    状态:投入0元,投入1元,投入2元,投入3元

    状态分析
    在这里插入图片描述
    波形图绘制
    在这里插入图片描述
    simple_fsm.v

    `timescale  1ns/1ns
    
    module  simple_fsm
    (
        input   wire    sys_clk     ,   //系统时钟50MHz
        input   wire    sys_rst_n   ,   //全局复位
        input   wire    pi_money    ,   //投币方式可以为:不投币(0)、投1元(1)
    
        output  reg     po_cola         //po_cola为1时出可乐,po_cola为0时不出可乐
    );
    
    //parameter define
    //只有三种状态,使用独热码
    parameter   IDLE = 3'b001;
    parameter   ONE  = 3'b010;
    parameter   TWO  = 3'b100;
    
    //reg   define
    reg     [2:0]   state;
    
    //第一段状态机,描述当前状态state如何根据输入跳转到下一状态
    always@(posedge sys_clk or negedge sys_rst_n)
        if(sys_rst_n == 1'b0)
            state <= IDLE;  //任何情况下只要按复位就回到初始状态
        else    case(state)
                    IDLE    :   if(pi_money == 1'b1)//判断输入情况
                                    state <= ONE;
                                else
                                    state <= IDLE;
    
                    ONE     :   if(pi_money == 1'b1)
                                    state <= TWO;
                                else
                                    state <= ONE;
    
                    TWO     :   if(pi_money == 1'b1)
                                    state <= IDLE;
                                else
                                    state <= TWO;
                    //如果状态机跳转到编码的状态之外也回到初始状态
                    default :       state <= IDLE;
                endcase
    
    //第二段状态机,描述当前状态state和输入pi_money如何影响po_cola输出
    always@(posedge sys_clk or negedge sys_rst_n)
        if(sys_rst_n == 1'b0)
            po_cola <= 1'b0;
        else    if((state == TWO) && (pi_money == 1'b1))
            po_cola <= 1'b1;
        else
            po_cola <= 1'b0;
    
    endmodule
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    在这里插入图片描述
    在这里插入图片描述

    tb_simple_fsm.v

    `timescale  1ns/1ns
    
    module  tb_simple_fsm();
    
    //reg define
    reg     sys_clk;
    reg     sys_rst_n;
    reg     pi_money;
    
    //wire  define
    wire    po_cola;
    
    //初始化系统时钟、全局复位
    initial begin
        sys_clk    = 1'b1;
        sys_rst_n <= 1'b0;
        #20
        sys_rst_n <= 1'b1;
    end
    
    //sys_clk:模拟系统时钟,每10ns电平翻转一次,周期为20ns,频率为50MHz
    always  #10 sys_clk = ~sys_clk;
    
    //pi_money:产生输入随机数,模拟投币1元的情况
    always@(posedge sys_clk or negedge sys_rst_n)
        if(sys_rst_n == 1'b0)
            pi_money <= 1'b0;
        else
            pi_money <= {$random} % 2;  //取模求余数,产生非负随机数0、1
    
    //------------------------------------------------------------
    //将RTL模块中的内部信号引入到Testbench模块中进行观察
    wire [2:0] state = simple_fsm_inst.state;
    
    initial begin
        $timeformat(-9, 0, "ns", 6);
        $monitor("@time %t: pi_money=%b state=%b po_cola=%b", $time, pi_money, state, po_cola);
    end
    //------------------------------------------------------------
    
    //------------------------simple_fsm_inst------------------------
    simple_fsm  simple_fsm_inst(
        .sys_clk    (sys_clk    ),  //input     sys_clk
        .sys_rst_n  (sys_rst_n  ),  //input     sys_rst_n
        .pi_money   (pi_money   ),  //input     pi_money
    
        .po_cola    (po_cola    )   //output    po_cola
    );
    
    endmodule
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50



    例:复杂状态机
    模块设计
    在这里插入图片描述

    输入:0.5、1
    输出:不出可乐/不找零、出可乐/不找零、出可乐/找零
    状态:0、0.5、1、1.5、2、2.5、3

    状态转移图
    在这里插入图片描述
    波形图绘制
    在这里插入图片描述

    complex_fsm.v

    `timescale  1ns/1ns
    
    module  complex_fsm
    (
        input   wire    sys_clk         ,   //系统时钟50MHz
        input   wire    sys_rst_n       ,   //全局复位
        input   wire    pi_money_one    ,   //投币1元
        input   wire    pi_money_half   ,   //投币0.5元
                        
        output  reg     po_money        ,   //po_money为1时表示找零
                                            //po_money为0时表示不找零
        output  reg     po_cola             //po_cola为1时出可乐
                                            //po_cola为0时不出可乐
    );
    
    //parameter define
    //只有五种状态,使用独热码
    parameter   IDLE     = 5'b00001;
    parameter   HALF     = 5'b00010;
    parameter   ONE      = 5'b00100;
    parameter   ONE_HALF = 5'b01000;
    parameter   TWO      = 5'b10000;
    
    //reg   define
    reg     [4:0]   state;
    
    //wire  define
    wire    [1:0]   pi_money;
    
    //pi_money:为了减少变量的个数,我们用位拼接把输入的两个1bit信号拼接成1个2bit信号
    //投币方式可以为:不投币(00)、投0.5元(01)、投1元(10),每次只投一个币
    assign pi_money = {pi_money_one, pi_money_half};
    
    //第一段状态机,描述当前状态state如何根据输入跳转到下一状态
    always@(posedge sys_clk or negedge sys_rst_n)
        if(sys_rst_n == 1'b0)
            state <= IDLE;  //任何情况下只要按复位就回到初始状态
        else	case(state)
                    IDLE    : if(pi_money == 2'b01)   //判断一种输入情况
                                  state <= HALF;
                              else    if(pi_money == 2'b10)//判断另一种输入情况
                                  state <= ONE;
                              else
                                  state <= IDLE;
        
                    HALF    : if(pi_money == 2'b01)
                                  state <= ONE;
                              else    if(pi_money == 2'b10)
                                  state <= ONE_HALF;
                              else
                                  state <= HALF;
        
                    ONE     : if(pi_money == 2'b01)
                                  state <= ONE_HALF;
                              else    if(pi_money == 2'b10)
                                  state <= TWO;
                              else
                                  state <= ONE;
        
                    ONE_HALF: if(pi_money == 2'b01)
                                  state <= TWO;
                              else    if(pi_money == 2'b10)
                                  state <= IDLE;
                              else
                                  state <= ONE_HALF;
        
                    TWO     : if((pi_money == 2'b01) || (pi_money == 2'b10))
                                  state <= IDLE;
                              else
                                  state <= TWO;
            //如果状态机跳转到编码的状态之外也回到初始状态
                    default :       state <= IDLE;
                endcase
    
    //第二段状态机,描述当前状态state和输入pi_money如何影响po_cola输出
    always@(posedge sys_clk or negedge sys_rst_n)
        if(sys_rst_n == 1'b0)
            po_cola <= 1'b0;
        else if((state==TWO && pi_money==2'b01) || (state==TWO && pi_money==2'b10) || (state==ONE_HALF && pi_money==2'b10))
            po_cola <= 1'b1;
        else
            po_cola <= 1'b0;
    
    //第二段状态机,描述当前状态state和输入pi_money如何影响po_money输出
    always@(posedge sys_clk or negedge sys_rst_n)
        if(sys_rst_n ==	1'b0)
            po_money <= 1'b0;
        else if((state == TWO) && (pi_money == 2'b10))  //只有一种情况需要找零
            po_money <= 1'b1;
        else
            po_money <= 1'b0;
    
    endmodule
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93

    在这里插入图片描述
    tb_complex_fsm.v

    `timescale  1ns/1ns
    module  tb_complex_fsm();
    
    //reg   define
    reg         sys_clk;
    reg         sys_rst_n;
    reg         pi_money_one;
    reg         pi_money_half;
    reg         random_data_gen;
    
    //wire  define
    wire        po_cola;
    wire        po_money;
    
    //初始化系统时钟、全局复位
    initial begin
        sys_clk    = 1'b1;
        sys_rst_n <= 1'b0;
        #20
        sys_rst_n <= 1'b1;
    end
    
    //sys_clk:模拟系统时钟,每10ns电平翻转一次,周期为20ns,频率为50MHz
    always  #10 sys_clk = ~sys_clk;
    
    //random_data_gen:产生非负随机数0、1
    always@(posedge sys_clk or negedge sys_rst_n)
        if(sys_rst_n == 1'b0)
            random_data_gen <= 1'b0;
        else
            random_data_gen <= {$random} % 2;
    
    //pi_money_one:模拟投入1元的情况
    always@(posedge sys_clk or negedge sys_rst_n)
        if(sys_rst_n == 1'b0)
            pi_money_one <= 1'b0;
        else
            pi_money_one <= random_data_gen;
    
    //pi_money_half:模拟投入0.5元的情况
    always@(posedge sys_clk or negedge sys_rst_n)
        if(sys_rst_n == 1'b0)
            pi_money_half <= 1'b0;
        else
        //取反是因为一次只能投一个币,即pi_money_one和pi_money_half不能同时为1
            pi_money_half <= ~random_data_gen;
    
    //------------------------------------------------------------
    //将RTL模块中的内部信号引入到Testbench模块中进行观察
    wire    [4:0]   state    = complex_fsm_inst.state;
    wire    [1:0]   pi_money = complex_fsm_inst.pi_money;
    
    initial begin
        $timeformat(-9, 0, "ns", 6);
        $monitor("@time %t: pi_money_one=%b pi_money_half=%b pi_money=%b state=%b po_cola=%b po_money=%b", $time, pi_money_one, pi_money_half, pi_money, state, po_cola, po_money);
    end
    //------------------------------------------------------------
    
    //------------------------complex_fsm_inst------------------------
    complex_fsm complex_fsm_inst(
        .sys_clk        (sys_clk        ),  //input     sys_clk
        .sys_rst_n      (sys_rst_n      ),  //input     sys_rst_n
        .pi_money_one   (pi_money_one   ),  //input     pi_money_one
        .pi_money_half  (pi_money_half  ),  //input     pi_money_half
                        
        .po_cola        (po_cola        ),  //output    po_money
        .po_money       (po_money       )   //output    po_cola
    );  
    
    endmodule
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70



    第21讲:无源蜂鸣器驱动实验

    蜂鸣器按其结构可分为电磁式蜂鸣器和压电式蜂鸣器两种类型;蜂鸣器按其是否带有信号源又分为有源蜂鸣器和无源蜂鸣器
    有源蜂鸣器的内部装有集成电路,不需要音频驱动电路,只需要接通直流电压就能直接发出声响;而无源蜂鸣器只有外加音频驱动信号才能发出声响。

    无源蜂鸣器与有源蜂鸣器不同,因其内部不带震荡源,所以其无法像有源蜂鸣器那样直接用直流信号驱动,这里需要使用PWM方法才能驱动其发生。
    输入不同频率和占空比的PWM方法发出的声音是不同的,其中频率对音频有影响,占空比对音量大小有影响。

    在这里插入图片描述

    模块设计
    在这里插入图片描述
    波形图绘制
    在这里插入图片描述
    beep.v

    `timescale  1ns/1ns
    module  beep
    #(
        parameter   TIME_500MS =   25'd24999999,   //0.5s计数值
        parameter   DO  =   18'd190839 ,   //"哆"音调分频计数值(频率262)
        parameter   RE  =   18'd170067 ,   //"来"音调分频计数值(频率294)
        parameter   MI  =   18'd151514 ,   //"咪"音调分频计数值(频率330)
        parameter   FA  =   18'd143265 ,   //"发"音调分频计数值(频率349)
        parameter   SO  =   18'd127550 ,   //"梭"音调分频计数值(频率392)
        parameter   LA  =   18'd113635 ,   //"拉"音调分频计数值(频率440)
        parameter   XI  =   18'd101214     //"西"音调分频计数值(频率494)
    )
    (
        input   wire        sys_clk     ,   //系统时钟,频率50MHz
        input   wire        sys_rst_n   ,   //系统复位,低有效
    
        output  reg         beep            //输出蜂鸣器控制信号
    );
    
    //reg   define
    reg     [24:0]  cnt         ;   //0.5s计数器
    reg     [17:0]  freq_cnt    ;   //音调计数器
    reg     [2:0]   cnt_500ms   ;   //0.5s个数计数
    reg     [17:0]  freq_data   ;   //音调分频计数值
    
    //wire  define
    wire    [16:0]  duty_data   ;   //占空比计数值
    
    //cnt:0.5s循环计数器
    always@(posedge sys_clk or  negedge sys_rst_n)
        if(sys_rst_n == 1'b0)
            cnt <=  25'd0;
        else    if(cnt == TIME_500MS )
            cnt <=   25'd0;
        else
            cnt <=  cnt +   1'b1;
    
    //cnt_500ms:对500ms个数进行计数,每个音阶鸣叫时间0.5s,7个音节一循环
    always@(posedge sys_clk or  negedge sys_rst_n)
        if(sys_rst_n == 1'b0)
            cnt_500ms   <=  3'd0;
        else    if(cnt == TIME_500MS && cnt_500ms ==  6)
            cnt_500ms   <=  3'd0;
        else    if(cnt == TIME_500MS)
            cnt_500ms   <=  cnt_500ms + 1'b1;
        else   
            cnt_500ms   <=  cnt_500ms;   //时序逻辑中可以不写,组合逻辑一定要写
    
    //freq_cnt:当计数到音阶计数值或跳转到下一音阶时,开始重新计数
    always@(posedge sys_clk or  negedge sys_rst_n)
        if(sys_rst_n == 1'b0)
            freq_cnt    <=  18'd0;
        else    if(freq_cnt == freq_data || cnt == TIME_500MS)
            freq_cnt    <=  18'd0;
        else
            freq_cnt    <=  freq_cnt +  1'b1;    
                
    //不同时间鸣叫不同的音阶
    always@(posedge sys_clk or  negedge sys_rst_n)
        if(sys_rst_n == 1'b0)
            freq_data   <=  DO;
        else    case(cnt_500ms)
            0:  freq_data   <=   DO;
            1:  freq_data   <=   RE;
            2:  freq_data   <=   MI;
            3:  freq_data   <=   FA;
            4:  freq_data   <=   SO;
            5:  freq_data   <=   LA;
            6:  freq_data   <=   XI;
            default:  freq_data   <=   DO;
        endcase
    
    //设置50%占空比:音阶分频计数值的一半即为占空比的高电平数
    assign  duty_data   =   freq_data   >>    1'b1;
    
    //beep:输出蜂鸣器波形
    always@(posedge sys_clk or  negedge sys_rst_n)
        if(sys_rst_n == 1'b0)
            beep    <=  1'b0;
        else    if(freq_cnt >= duty_data)
            beep    <=  1'b1;
        else
            beep    <=  1'b0;
    
    endmodule
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85

    tb_beep.v

    `timescale  1ns/1ns
    module  tb_beep();
    
    //reg   define
    reg     sys_clk     ;   //时钟
    reg     sys_rst_n   ;   //复位
    
    //对时钟,复位信号赋初值
    initial
        begin
            sys_clk     =   1'b1;
            sys_rst_n   <=  1'b0;
            #100
            sys_rst_n   <=  1'b1;
        end
    
    //产生时钟信号
    always #10 sys_clk =   ~sys_clk;
    
    beep
    #(
        .TIME_500MS(25'd24999),   //0.5s计数值
        .DO        (18'd190  ),   //"哆"音调分频计数值(频率262)
        .RE        (18'd170  ),   //"来"音调分频计数值(频率294)
        .MI        (18'd151  ),   //"咪"音调分频计数值(频率330)
        .FA        (18'd143  ),   //"发"音调分频计数值(频率349)
        .SO        (18'd127  ),   //"梭"音调分频计数值(频率392)
        .LA        (18'd113  ),   //"拉"音调分频计数值(频率440)
        .XI        (18'd101  )    //"西"音调分频计数值(频率494)
    )
    beep_inst
    (
        .sys_clk     (sys_clk   ),   //系统时钟,频率50MHz
        .sys_rst_n   (sys_rst_n ),   //系统复位,低有效
    
        .beep        (beep      )    //输出蜂鸣器控制信号
    );
    
    endmodule
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
  • 相关阅读:
    GitHub推送代码时弹出验证框总是登录失败
    软件测试的风险主要体现在哪里
    设计模式之【工厂模式】
    错过金三银四,找工作4个月,面试15家,终于拿到3个offer,定级P7+
    斗地主发牌程序的 Python 实现与解析
    AtCoder Regular Contest 146 C Even XOR题解
    记一次rabbitmq生产者和消费者高可用的封装
    什么是数字化管理?产业园区如何进行数字化管理?
    学习java的第四十三天,GUI编程监听事件
    [力扣 Hot100]Day38 翻转二叉树
  • 原文地址:https://blog.csdn.net/qq_39236499/article/details/127865604