第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
`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
第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
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
第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
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
第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
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
例:复杂状态机
模块设计
输入: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
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
第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
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