一,原理
(1) Quartus
Altera Quartus II 作为一种可编程逻辑的设计环境, 由于其强大的设计能力和直观易用的接口,受到数字系统设计者的欢迎, Altera Quartus II设计软件是业界唯一提供FPGA和固定功能HardCopy器件统一设计流程的设计工具。
(2)ModelSim
ModelSim是业界最优秀的HDL语言仿真软件,它能提供友好的仿真环境,是业界唯一的单内核支持VHDL和Verilog混合仿真的仿真器。它采用直接优化的编译技术、Tcl/Tk技术、和单一内核仿真技术,编译仿真速度快,编译的代码与平台无关,便于保护IP核,个性化的图形界面和用户接口,为用户加快调错提供强有力的手段,是FPGA/ASIC设计的首选仿真软件。
(3) led灯
led灯就是发光二极管,在正向电压作用下电阻很小,处于导通状态相当于一只接通的开关;在反向电压作用下,由于电阻很大,科研堪称一只断开的开关。发光二极管在导通的时候发光,在没有导通的时候不发光。发光二极管有点像我们初中做的物理实验。单向开关串联一个灯泡,开关闭合的时候,电流流过灯泡,灯泡发光。开关断开的时候,灯泡也就不亮了;
(4) 有限状态机
有限状态机(Finite State Machine, FSM),又称有限状态自动机,简称状态机,是指在有限个状态之间按照一定规律转换的时序电路。有限状态机主要包括:米利状态机和穆尔状态机。输出与输入变量直接相关的状态机称为米利状态机
输出与输入变量无直接关系的状态机称为穆尔状态机。
(5)蜂鸣器
蜂鸣器的发声原理由振动装置和谐振装置组成,而蜂鸣器又分为无源他激型与有源自激型。区别:有缘蜂鸣器内部有震荡源,无缘蜂鸣器内部没有震荡源。
按照工作原理可分为:压电式蜂鸣器和电磁式蜂鸣器。
按照音源可分为:有源蜂鸣器和无源蜂鸣器。
有源蜂鸣器:内部有振荡源,直接通以直流电即可发出声音。
无源蜂鸣器:内部无振荡源,需要通以方波、PWM信号才能发出声音。无源蜂鸣器需要输入一定频率的方波或者脉冲宽度调制(Pulse Width Modulation,PWM)信号,蜂鸣器就可以发出声音。输入不同频率的信号,蜂鸣器可以发出不同音色的声音。
(6)按键消抖
按键抖动:机械弹性开关会出现按键抖动问题,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开。因而在闭合及断开的瞬间均伴随有一连串的抖动。当按下一次按键时,在之后的时刻会检测到不止一次低电平,那么按下一次按键,抖动可能会误以为按下多次按键。按键消抖的目的就是未来解决这种抖动给我们程序带来的影响;按键消抖解决方案有延迟采样和信号变化频率平稳后并且持续一段时间(通常取20ms)则采样。
(7)数码管
数码管分七段数码管和八段数码管。七段和八段的区别在于,是否包括小数点DP(Digital Point)。本实验中使用的是数码管是8段数码管,每段是由led组成。通过控制每段led的亮灭,来控制数码管显示不同的数字和字母。
数码管的连接方式分为共阴极和共阳极:当输入端全部在二极管的正极,二极管的负极共同接地,只有当输入端为高电平的时候,二极管才导通,然后对应的段发亮,这种连接方式称为共阴极;相反,当输入端全部在二极管的负极,二极管正极共同接高电平,只有当输入端为低电平,二极管才导通,然后对应的段发亮,这种连接方式称为共阳极
要显示不同的数字或者字母,就要选择对应的led段,真值表如下:
字形 | DP | g | f | e | d | c | b | a |
. | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
2 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 1 |
3 | 1 | 0 | 1 | 1 | 0 | 0 | 0 | 0 |
4 | 1 | 0 | 0 | 1 | 1 | 0 | 0 | 1 |
5 | 1 | 0 | 0 | 1 | 0 | 0 | 1 | 0 |
6 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
7 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 |
8 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
A | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 |
B | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 1 |
C | 1 | 1 | 0 | 0 | 0 | 1 | 1 | 0 |
D | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 1 |
E | 1 | 0 | 0 | 0 | 0 | 1 | 1 | 0 |
F | 1 | 0 | 0 | 0 | 1 | 1 | 1 | 0 |
对于数码管动态显示,由于段选信号公用,所以在同一时刻只能选择一个数码管,具体选择十位还是个位由位选信号决定,用此种方式实现多种数码管就是利用视觉暂留现象。
(8)乐谱知识
音频(Audio),指人耳可以听到的声音频率在20HZ~20kHz之间的声波。乐普是由音符组成的,不同的音符拥有不同的频率。可以利用音频率算出音符振动周期(晶振50MHZ,振动一次20ns时振动周期=1000/(20*音符频率)
二,建立项目
(1) 目录结构
(2) 新建项目
项目命名:
开发板选型:
EDA设置:
3. led流水灯实验
输入信号为时钟和复位,led灯的状态没0.2s改变一次,同一时刻下只能有一只led灯亮,其余的led灯灭。最后通过移位寄存器输出信号给四个led灯。
新建verilog文件
工程代码:
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 | module led_test( input wire clk, input wire rst_n, output reg [3:0]led //4个led灯 ); reg [25:0]cnt; //计数器 always@(posedge clk or negedge rst_n)begin if(!rst_n)begin cnt <= 26'd0; end else if(cnt == 40_000_000 - 1)begin//从0开始计数,所以只记到39_999_999 cnt <= 26'd0; end else begin cnt = cnt + 1'd1; end end always@(posedge clk or negedge rst_n)begin if(!rst_n)begin led <= 4'b0000;//led灯高电平有效,低电平无效,1亮0不亮 end else if(cnt == 10 -1 )begin led <= 4'b0001; end else if(cnt == 20 -1 )begin led <= 4'b0010; end else if(cnt == 30 -1 )begin led <= 4'b0100; end else if(cnt == 40 -1 )begin led <= 4'b1000; end else begin led <= led; end end endmodule |
注:编译前设置如下(遇到的问题:在没有设置这个时编译一直报错,经过老师讲解,明白如果不设置即使编译通过了烧录也会出问题):
分析综合:
引脚布线:
编译通过:
仿真路径设置(刚开始使用时一直报错“ERRO:cannot launch the modelsim-altera software because you did not specify the path...”很明显时路径出了问题,检查路径发现路径选择不全,经过修改解决问题)
功能仿真代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | `timescale 1ns/1ns//单位/精度 module led_test_tb(); reg clk ;//时钟信号 reg rst_n;//复位信号 wire [3:0] led ;//4个led灯 always #10 clk = ~clk;//每10ns翻转一次时钟信号 initial begin clk = 0;//初始化时钟信号为0 rst_n = 0;//初始化复位信号为0 #10 rst_n = 1;//10ns后将复位信号置1 #1000 $stop;//1000ns后停止,可以观看4个led灯的信号 end led_test u_led_test(//实例化 .clk (clk) ,//50MHz .rst_n (rst_n),//reset negetive复位信号下降沿有效 .led (led)//4个led灯 ); endmodule |
modelsim仿真
)
仿真结果
4. 点亮led灯实验
建立工程过程与上文相同。
工程代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | module led( input clk, input rst_n, output reg ld ); reg [25:0]cnt; //计时器模块 always@(negedge rst_n or posedge clk)begin if(!rst_n)begin cnt <= 26'd0;//初始化计时器为0 ld <= 1'b1;//初始化led灯,高电平有效 end else if(cnt == 26'd50_000_000-1)begin cnt <= 26'd0; ld <= ~ld;//1s钟led取反 end else begin cnt <= cnt + 26'd1; ld <= ld;//其他时刻,led等于其自身 end end endmodule |
5. 有限状态机实现流水灯
建立工程项目过程与上文相同
项目代码
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 | module fsm_led( input clk, input rst_n, output reg[3:0] led ); parameter TIME = 50_000_000;//间隔1s parameter S_LED0 =2'd0;//led存在的状态 parameter S_LED1 =2'd1; parameter S_LED2 =2'd2; parameter S_LED3 =2'd3; parameter LED0 = 4'b0001;//led输出 parameter LED1 = 4'b0010; parameter LED2 = 4'b0100; parameter LED3 = 4'b1000; reg [25:0] cnt; reg [1:0] stat; always@(posedge clk or negedge rst_n)begin if (!rst_n) begin cnt <= 26'd0; end else if (cnt ==TIME - 1'd1)begin cnt <= 26'd0; end else begin cnt <= cnt +1; end end always@(posedge clk or negedge rst_n)begin if(!rst_n)begin stat <= 2'b0; end else begin case(stat) S_LED0:begin if (cnt ==TIME - 1)begin led <= LED1; stat <= S_LED1; end else begin led <= LED0; stat <= S_LED0; end end S_LED1:begin if (cnt ==TIME - 1)begin led <= LED2; stat <= S_LED2; end else begin led <= LED1; stat <= S_LED1; end end S_LED2:begin if (cnt ==TIME - 1)begin led <= LED3; stat <= S_LED3; end else begin led <= LED2; stat <= S_LED2; end end S_LED3:begin if (cnt ==TIME - 1)begin led <= LED0; stat <= S_LED0; end else begin led <= LED3; stat <= S_LED3; end end default:; endcase end end endmodule |
引脚:
新建verilog文件,写入测试代码程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | `timescale 1ns/1ns module fsm_led_tb(); reg clk; reg rst_n; wire [3:0] led; parameter TIME = 4'd10; always #10 clk = ~clk; initial begin //初始化 clk = 1'b0; rst_n = 1'b0; #10 rst_n = 1'b1;//延迟10s // #1000 $stop end fsm_led #(.TIME(TIME))u_fsm_led( .clk(clk), .rst_n(rst_n), .led(led) ); endmodule |
仿真结果:
6. 按键控制led灯
使用开发板上的四个按键控制四个led灯。按下不同的按键时,四个LED灯显示不同的效果
按键状态 | LED显示效果 |
无按键按下 | 四个LED灯全灭 |
按下KEY0 | 自右向左的流水灯 |
按下KEY1 | 自左向右的流水灯 |
按下KEY2 | 四个LED灯同时闪烁 |
按下KEY3 | 四个LED灯全亮 |
四个按键外加时钟和复位信号作为输入,两个计数器模块分别用于0.2s时间的计数和状态的计数。led模式选择模块根据状态计数器的改变,来改变四个led的状态,形成不同的样式。
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 | module key_led( input clk ,//时钟50MHz input rst_n,//复位信号,下降沿有效negtive input [3:0] key ,//四个按键 output reg [3:0] led //四个led灯 ); parameter TIME = 26'd49_999999;//0.2S reg [25:0] cnt ;//计数器0.2S reg [1:0] stat;//记录四个按键状态 //0.2s计数器模块 always@(posedge clk or negedge rst_n)begin if(!rst_n)begin//复位 cnt <= 25'd0;//计数器清0 end else if(cnt == TIME )begin//记满10_000_000,0~9_999_999 cnt <= 25'd0;//计数器清0 end else begin cnt <= cnt + 1'd1;//其他情况下计数器加1 end end //状态计数模块 always@(posedge clk or negedge rst_n)begin if(!rst_n)begin//复位信号 stat <= 2'd0;//状态清0 end else if(cnt == TIME )begin//记满10_000_000,0~9_999_999 0.2s stat <= stat + 2'd1;//状态加1 end else begin stat <= stat;//其他情况状态保持不变 end end //状态控制led模块 always@(posedge clk or negedge rst_n)begin if(!rst_n)begin//复位信号 led <= 4'b0000;//led全灭 end else if(key[0] == 0)begin//右边第1个按键按下,按键低电平0有效,从右往左得亮,0.2秒亮一个接着亮 case(stat)//判断状态的值 2'd0: led <= 4'b0001; 2'd1: led <= 4'b0010; 2'd2: led <= 4'b0100; 2'd3: led <= 4'b1000; default:; endcase end else if(key[1] == 0)begin//右边第2个按键按下,按键低电平0有效,从左像右亮 case(stat) 2'd0: led <= 4'b1000; 2'd1: led <= 4'b0100; 2'd2: led <= 4'b0010; 2'd3: led <= 4'b0001; default:; endcase end else if(key[2] == 0)begin//右边第3个按键按下,按键低电平0有效,四个灯同时闪烁 case(stat) 2'd0: led <= 4'b1111;//全亮 2'd1: led <= 4'b0000;//全灭 2'd2: led <= 4'b1111;//全亮 2'd3: led <= 4'b0000;//全灭 default:; endcase end else if(key[3] == 0)begin//右边第4个按键按下,按键低电平0有效 case(stat) 2'd0: led <= 4'b1111;//全亮 2'd1: led <= 4'b1111;//全亮 2'd2: led <= 4'b1111;//全亮 2'd3: led <= 4'b1111;//全亮 default:; endcase end else begin led <= 4'b0000;//其他情况默认4个led灯全灭 end end 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 | `timescale 1ns/1ns module key_led_tb(); reg clk; reg rst_n; reg [3:0] key; wire [3:0] led; parameter TIME = 10 ; parameter CYCLE = 20; //周期20ns always #(CYCLE/2) clk = ~clk; //10ns转一次 initial begin clk = 1'b0; rst_n = 1'b0; #(CYCLE);//时钟20ns rst_n = 1'b1; #(CYCLE * TIME * 4);//4个led灯,4个间隔,所以乘以4 key = 4'b1110; #(CYCLE * TIME * 4); key = 4'b1101; #(CYCLE * TIME * 4); key = 4'b1011; #(CYCLE * TIME * 4); key = 4'b0111; #(CYCLE * TIME * 4); $stop; end key_led #(.TIME(TIME)) u_key_led//参数传递 u_key_led可以换 ( .clk(clk), .rst_n(rst_n), .key(key), .led(led) ); endmodule |
7. 按键消抖+蜂鸣器
本实验是按键控制蜂鸣器发音。蜂鸣器的初始状态为鸣叫状态。
1.如果是鸣叫状态,按下按键后就停止鸣叫。
2.如果是停止鸣叫状态,按下按键后就重新开始鸣叫。
本实验一共三个模块,分别用于按键消抖,控制蜂鸣器发声和顶层文件调用。
按键消抖模块代码:
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 | module key_beep( input clk ,// input rst_n,//复位信号,下降沿有效 input key ,//按键 output reg flag ,//0表示还在抖动,1表示抖动结束 output reg key_value//消抖完的有效电平 ); parameter DELAY = 20'd1000_000;//20ms reg [19:0]delay_cnt;//延迟计数器 reg key_reg; always@(posedge clk or negedge rst_n)begin if(!rst_n)begin delay_cnt <= 20'd0;//0 key_reg <= 1'b1;//低电平有效 end else begin key_reg <= key; if(key_reg != key)begin//抖动 delay_cnt <= DELAY; end else begin//没有抖动 if(delay_cnt > 20'd0)begin delay_cnt <= delay_cnt - 20'd1;//开始倒数计时 end else begin delay_cnt <= 20'd0;//小于0置0 end end end end always@(posedge clk or negedge rst_n)begin if(!rst_n)begin flag <= 1'b0; key_value<=1'b1; end else if (delay_cnt == 20'd1)begin//消抖完成 flag <= 1'b1; key_value <=key; end else begin flag <= 1'b0; key_value <= key_value; end end endmodule |
控制蜂鸣器发声代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | module beep_control( input clk, input rst_n, input flag, input key_value, output reg beep ); always@(posedge clk or negedge rst_n)begin if (!rst_n)begin beep <= 1'b0; end else begin if (flag && ~key_value)begin beep <= ~beep; end else beep <=beep; end end endmodule |
顶层文件调用代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | module top_key_beep( input clk, input rst_n, input key, output wire beep ); wire flag; wire key_value; key_beep u_key_beep( .clk(clk), .rst_n(rst_n), .key(key), .flag(flag), .key_value(key_value) ); beep_control U_beep_control( .clk(clk), .rst_n(rst_n), .flag(flag), .key_value(key_value), .beep(beep) ); endmodule |
8. 数码管静态显示
六个数码管同时间隔0.5s显示0-f。要求:使用一个顶层模块,调用计时器模块和数码管静态显示模块。
模块原理图(Tools——>Netlist Viewers——>RTL Viewer)
计时器模块代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | module time_count( input clk, input rst_n, output reg flag ); parameter MAX_NUM = 25'd25_000_000;//计时0.5s reg[24:0] cnt; always@(posedge clk or negedge rst_n)begin if (!rst_n)begin flag <= 1'b0; cnt <= 25'b0; end else if (cnt<MAX_NUM-25'd1)begin flag <= 1'd0; cnt <=cnt +25'd1; end else begin flag <= 1'b1; cnt <= 25'd0; end end 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 | module seg_led_static( input clk, input rst_n, input flag, output reg [5:0] sel,//数码管位选信号 output reg [7:0] seg //数码管段选信号 ); reg [3:0] num; always@(posedge clk or negedge rst_n)begin if(!rst_n) begin sel<=6'b111111; end else begin sel<=6'b000000; end end always@(posedge clk or negedge rst_n)begin if (!rst_n)begin num<=4'h0; end else if(flag)begin if(num < 4'hf) num <= num + 1'h1; else num <= 4'h0; end else begin num <= num; end end always @(posedge clk or negedge rst_n)begin if (!rst_n) seg<=8'b0; else begin case (num) 4'h0: seg <= 8'b1100_0000;//匹配到后参考共阳极真值表 4'h1: seg <= 8'b1111_1001; 4'h2: seg <= 8'b1010_0100; 4'h3: seg <= 8'b1011_0000; 4'h4: seg <= 8'b1001_1001; 4'h5: seg <= 8'b1001_0010; 4'h6: seg <= 8'b1000_0010; 4'h7: seg <= 8'b1111_1000; 4'h8: seg <= 8'b1000_0000; 4'h9: seg <= 8'b1001_0000; 4'ha: seg <= 8'b1000_1000; 4'hb: seg <= 8'b1000_0011; 4'hc: seg <= 8'b1100_0110; 4'hd: seg <= 8'b1010_0001; 4'he: seg <= 8'b1000_0110; 4'hf: seg <= 8'b1000_1110; default : seg <= 8'b1100_0000; endcase end end endmodule |
顶层文件代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | module top_seg_led_static( input clk, input rst_n, output [5:0]sel, output [7:0]seg ); parameter MAX_NUM = 25'd25_000_000;// 数码管变化的时间间隔0.5s wire add_flag;// 数码管变化的通知信号 time_count #(.MAX_NUM(MAX_NUM)) u_time_count( .clk(clk),//50MHz时钟信号 .rst_n(rst_n),//复位信号 .flag(add_flag)//一个时钟周期的脉冲信号 ); seg_led_static u_seg_led_static( .clk(clk), .rst_n(rst_n), .sel(sel), .seg(seg), .flag(add_flag) ); endmodule |
9. led跑马灯
跑马灯又叫走马灯、串马灯。由毛竹编织成马头,马尾,属于灯笼的一种。是传统特色手工艺品,亦是传统节日玩具之一。本实验利用FPGA开发板上的4个led灯实现20ms的跑马灯。
工程代码:
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 | module horse_led( input wire clk, input wire rst_n, output wire [3:0] led ); parameter MAX_NUM = 21'd1_999_999;//200ms reg [19:0] cnt; reg [7:0] led_r; //计数功能 always@(posedge clk or negedge rst_n)begin if(!rst_n)begin cnt <= 21'd0; end else if(cnt == MAX_NUM)begin cnt <= 20'd0; end else begin cnt <= cnt + 21'd1; end end //led_r移位操作 always@(posedge clk or negedge rst_n)begin if(!rst_n)begin led_r <= 8'b11110000;//初始化 end else if(cnt == MAX_NUM)begin//20ms led_r <= {led_r[0],led_r[7:1]};//移位 end else begin led_r <= led_r; end end //取led_r的前四位给led assign led = led_r[3:0]; endmodule |
测试代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | `timescale 1ns/1ns//单位/精度 module horse_led_tb(); parameter MAX_NUM = 4'd10;//记最大数,10x20=200ns parameter CYCLE = 5'd20;//时钟周期,20ns reg clk; reg rst_n; wire [3:0] led; always#(CYCLE/2) clk = ~clk;//模拟时钟 initial begin clk = 1'b0; rst_n = 1'b0; #(CYCLE) ; rst_n = 1'b1; #(MAX_NUM * CYCLE * 4);//观察4个led的状态 $stop; end //实例化待测模块 horse_led#(.MAX_NUM (MAX_NUM)) horse_led_inst( .clk (clk), .rst_n (rst_n), .led (led) ); endmodule |
10. 数码管动态显示
通过使用6位数码管显示,实现一个计时器,每0.5s变化一次,系统框图如下:
计时器模块:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | module time_count( input clk,//时钟频率,50MHz input rst_n,//复位信号,下降沿有效 output reg flag//一个时钟周期的脉冲信号 ); parameter MAX_NUM = 25'd25_000_000;//0.5s时钟 reg [24:0] cnt ;//时钟计数器 //计时器对时钟计数,每0.5s输出一个时钟周期脉冲信号 always@(posedge clk or negedge rst_n)begin if(!rst_n)begin//按下复位信号时 cnt <= 25'd0;//计数器设置位0 flag <= 1'b0;//周期脉冲信号设置位0 end else if(cnt == MAX_NUM - 1'd1)begin//如果时间到 cnt <= 25'd0;//计数器清零 flag <= 1'b1;//周期脉冲信号输出为1,表示记慢一个周期 end else begin//如果时间没有到 cnt <= cnt + 1'd1;//计数器不停计数 flag <= 1'b0;//周期脉冲信号不变 end end 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 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 | module sel_led_dynamic( input clk,//时钟,50MHz input rst_n,//复位信号,下降沿有效 input flag, output reg [5:0] sel , output reg [7:0] seg ); reg[2:0] cstat;//当前 reg[2:0] nstat;//现在 always@(posedge clk or negedge rst_n)begin if(!rst_n)begin cstat <= 3'd0; end else begin cstat <=nstat; end end always@(*)begin case(cstat) 3'd0: if(flag)begin nstat = 3'd1; end else begin nstat = 3'd0; end 3'd1: if(flag) nstat = 3'd2; else nstat = 3'd1; 3'd2: if(flag)begin nstat = 3'd3; end else begin nstat = 3'd2; end 3'd3: if(flag)begin nstat = 3'd4; end else begin nstat = 3'd3; end 3'd4: if(flag)begin nstat = 3'd5; end else begin nstat = 3'd4; end 3'd5: if(flag)begin nstat = 3'd0; end else begin nstat = 3'd5; end default: nstat= 3'd1; endcase end reg[2:0]value; always@(*)begin if(!rst_n)begin value <= 3'd0; end else begin case (cstat) 3'd0:begin sel = 6'b111_110;//选择最右边数码管。 sel低有效 value = 3'd1; end 3'd1: begin sel = 6'b111_101; value = 3'd2; end 3'd2: begin sel = 6'b111_011; value = 3'd3; end 3'd3:begin sel = 6'b110_111; value = 3'd4; end 3'd4: begin sel = 6'b101_111; value = 3'd5; end 3'd5: begin sel = 6'b011_111; value = 3'd6; end default: begin//默认就第1种情况 sel = 6'b111_110; value = 3'd1; end endcase end end always@(*)begin if(!rst_n)begin seg = 8'b11111111; end else begin case (value) 3'd1: seg = 8'b11111001;//根据数码管真值表查找 3'd2: seg = 8'b10100100; 3'd3: seg = 8'b10110000; 3'd4: seg = 8'b10011001; 3'd5: seg = 8'b10010010; 3'd6: seg = 8'b10000010; default:seg = 8'b00000000; endcase end end endmodule |
11. 蜂鸣器播放两只老虎
两只老虎乐谱一共有34个音符,1对应DO,2对应RE,3对应MI…。一个音符持续的时间很短,需要设置一个持续时间,重复播放该音符,这样我们才能听得出来。本实验中设置音符持续时间(节拍)300毫秒
音符选择模块代码
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 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 | module freq_select( input wire clk , input wire rst_n, output reg flag ); parameter CNT_MAX = 24'd14_999_999;//300ms切换一个音符 parameter NUM_FRE = 6'd33; parameter D1 = 16'd47750;//1 parameter D2 = 16'd42250;//2 parameter D3 = 16'd37900;//3 parameter D4 = 16'd37550;//4 parameter D5 = 16'd31850;//5 parameter D6 = 16'd28400;//6 parameter D7 = 16'd25400;//7 reg [23:0] cnt_delay ;//计数器 reg [5:0] lut_data ;//乐谱数据 reg [15:0] cnt_freq ;//音符音频计数器 reg [15:0] freq_data;//音符数据寄存器 wire [14:0] duty_data;//占空比 wire end_note ;//结束标记 wire end_spectrum;//音谱结束 //持续时间计时 always@(posedge clk or negedge rst_n)begin if(!rst_n)begin cnt_delay <= 24'd0; end else if(cnt_delay == CNT_MAX)begin cnt_delay <= 24'd0; end else begin cnt_delay <= cnt_delay+1'd1; end end always@(posedge clk or negedge rst_n)begin//每个音符震动次数的模块 if(!rst_n)begin cnt_freq <= 16'd0; end else if(end_note)begin cnt_freq <= 16'd0; end else begin cnt_freq <= cnt_freq + 1'd1;//音符切换,从上一个音符切换到下一个音符 end end //音谱计时 always@(posedge clk or negedge rst_n)begin if(!rst_n)begin lut_data <= 6'd0; end else if(end_spectrum)begin lut_data <= 6'd0; //回到第一个音符 end else if(cnt_delay == CNT_MAX)begin lut_data <= lut_data + 1'd1; //计数器达到300ms,计数器切换到下一位 end else begin lut_data <= lut_data; end end always@(posedge clk or negedge rst_n)begin//对音符进行排序 if(!rst_n)begin freq_data <= D1; end else begin case(lut_data)//对音符进行赋值 6'd0: freq_data <= D1; 6'd1: freq_data <= D2; 6'd2: freq_data <= D3; 6'd3: freq_data <= D1; 6'd4: freq_data <= D1; 6'd5: freq_data <= D2; 6'd6: freq_data <= D3; 6'd7: freq_data <= D1; 6'd8: freq_data <= D3; 6'd9: freq_data <= D4; 6'd10: freq_data <= D5; 6'd11: freq_data <= D3; 6'd12: freq_data <= D4; 6'd13: freq_data <= D5; 6'd14: freq_data <= D5; 6'd15: freq_data <=D6; 6'd16: freq_data <= D5; 6'd17: freq_data <= D4; 6'd18: freq_data <= D3; 6'd19: freq_data <= D1; 6'd20: freq_data <= D5; 6'd21: freq_data <=D6; 6'd22: freq_data <= D5; 6'd23: freq_data <= D4; 6'd24: freq_data <= D3; 6'd25: freq_data <= D1; 6'd26: freq_data <= D2; 6'd27: freq_data <= D5; 6'd28: freq_data <= D1; 6'd29: freq_data <= D1; 6'd30: freq_data <= D2; 6'd31: freq_data <= D5; 6'd32: freq_data <= D1; 6'd33: freq_data <= D1; default:freq_data <= D1; default:freq_data <= D1; endcase end end assign duty_data = freq_data >> 1;//右移一位,得到的就是当前值的一半 assign end_note = cnt_freq == freq_data || cnt_delay == CNT_MAX; //计时器等于寄存器或者延时等于了max代表播放完成 assign end_spectrum = lut_data == NUM_FRE && cnt_delay == CNT_MAX; //乐谱的结束标志,记号到达了最大音符序号并且到达的最后一个音符并且结束 //pwm信号产生模块 always@(posedge clk or negedge rst_n)begin if(!rst_n)begin flag <= 1'b0; end else begin flag <= (cnt_freq >= duty_data) ? 1'b1 : 1'b0; //当震动次数大于占空比数据就输出1,否则输出0 前半段低电平,后半段高电平 end end endmodule |
PWM产生模块代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | module gen_pwm ( input wire clk , input wire rst_n, input wire flag, output reg beep ); always@(posedge clk or negedge rst_n)begin//pwm控制蜂鸣器模块 if(!rst_n)begin beep <= 1'b0; end else if(flag)begin beep <= 1'b1; end else begin beep <= 1'b0;//停止鸣叫 end end 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 | module pwm_beep( input wire clk , input wire rst_n, output wire beep ); parameter CNT_MAX = 24'd14_999_999;//300ms parameter D1 = 16'd47750 ;//1 parameter D2 = 16'd42250 ;//2 parameter D3 = 16'd37900 ;//3 parameter D4 = 16'd37550 ;//4 parameter D5 = 16'd31850 ;//5 parameter D6 = 16'd28400 ;//6 parameter D7 = 16'd25400 ;//7 wire flag; //实例化音频选择模块 freq_select#( .CNT_MAX (CNT_MAX), .D1 (D1) , .D2 (D2) , .D3 (D3) , .D4 (D4) , .D5 (D5) , .D6 (D6) , .D7 (D7) ) u_freq_select( .clk (clk) , .rst_n (rst_n), .flag (flag) ); //实例化pwm产生模块 gen_pwm u_gen_pwm ( .clk (clk) , .rst_n (rst_n), .flag (flag) , .beep (beep) ); endmodule |
引脚