[FPGA] Verilog 燃气灶控制器的设计与实现
燃气灶控制器的设计与实现
一、引述
本次实验所用可编程器件型号为MAXII EPM1270T144C5(其引脚表见本人另一博文:可编程实验板EPM1270T144C5使用说明),通过可编程实验板实现一个基本的模拟燃气灶。
二、设计课题的基本要求
1、 燃气灶的控制按键有三个:点火/关闭按键 BTN7、火力调节按键 BTN6(火力增大) 和 BTN5(火力减小)
2、 用 8×8 双色点阵模拟显示燃气灶的灶眼,用如图 1 所示的四个点阵显示状态分别表示火力的四个档位,从左到右依次为微火、小火、中火和大火,点阵没有任何显示表示熄火状态
3、 燃气灶上电时灶眼应处于熄火状态,在熄火状态下按一下按键 BTN7 点燃燃气灶进入燃烧状态,在燃烧状态下按一下按键 BTN7 则熄灭燃气灶进入熄灭状态
4、 燃气灶在熄灭状态下,按一下按键 BTN7 进入燃烧状态时的初始火力为小火
5、 在燃烧状态下用按键 BTN6(火力增大)和 BTN5(火力减小)改变火力的大小, 每按一次按键火力增大或减小一档(对应的点阵显示改变一档);在大火状态下按键BTN6 不起效,在微火状态下按键 BTN5 不起效,而且在大火状态下按 BTN6 或微火状态下按 BTN5 要有报警声,提示火力已到极限
三、系统设计
1、设计思路:1)点阵的图案显示通过快速扫描达到人眼能够定格看到稳定的图案
2)点阵显示图案的转换通过状态机实现
3)蜂鸣器声音的产生通过按下按键产生一个方波脉冲实现,声音的频率则需根据自定义的音调换算成频率
4)为贴近生活,引入计数器功能,实现倒计时结束,燃气灶自动关闭的效果
5)为方便使用者明了的知道使用时燃气灶火焰的大小状态,引入 LCD 液晶屏功能,在不同的火焰状态下对应显示不同的英文显示
2、总体框图
3、分块设计:1)状态机:状态转移情况的罗列
2)点阵显示
3)产生方波脉冲控制蜂鸣器
4)数码管显示
5)倒计时器
6)LCD1602液晶屏显示
四、功能说明
1、燃气灶上电时灶眼应处于熄火状态,在熄火状态下按一下按键 BTN7 点燃燃气灶进入燃烧状态,在燃烧状态下按一下按键 BTN7 则熄灭燃气灶进入熄灭状态;燃气灶在熄灭状态下,按一下按键 BTN7 进入燃烧状态时的初始火力为小火;在燃烧状态下用按键 BTN6(火力增大)和 BTN5(火力减小)改变火力的大小, 每按一次按键火力增大或减小一档(对应的点阵显示改变一档),顺序为从微火、小火、中火、大火依次增大;在大火状态下按键BTN6 不起效,在微火状态下按键 BTN5 不起效,而且在大火状态下按 BTN6 或微火状态下按 BTN5 有报警声,提示火力已到极限。
2、倒计时器设定的计时时间为20s到0s,按下BTN3倒计时器开始倒计时,倒计时过程中,再按一下BTN3实现计时暂停,再按一下BTN3则会又开始倒计时;任意时候按下复位键(BTN2)会立马复位到初始设定的20s;当倒计时到0时,无论燃气灶处于何种燃烧状态,都会立马自动熄灭。
3、燃气灶上电后向上拨动拨码开关sw7,在LCD1602液晶屏上会出现“Welcome to use The Gas stove”的英文提示;在不同的燃烧状态下会对应显示不同的提示语,微火、小火、中火、大火分别对应“Fire Level Micro Fire”、“Fire Level Little Fire”、“Fire Level Medium Fire ”、“Fire Level Big Fire”。
五、模块代码
1、LCD1602液晶屏
这里要郑重感谢一下方清欢大佬的技术与理论支持,关于这部分的解释详见方清欢大佬的博文:[FPGA]浅谈LCD1602字符型液晶显示器(Verilog)
下面就来看一下在燃气灶不同的火焰状态下对应液晶屏显示不同文字的效果图吧!
1)熄灭状态显示欢迎使用提示语
2)微火状态
3)小火状态
4)中火状态
5)大火状态
2、状态机中的内容显示(包含点阵显示、蜂鸣器的方波脉冲产生和液晶屏显示内容的赋值)
蜂鸣器的音调可调,从低音到中音到高音,每个音段里包含do ri mi fa so la xi do这7个音调可供选择
(各音调的不同频率参见本人另一博文:可编程实验板EPM1270T144C5蜂鸣器音调频率选择 ,共包含了21个标准音调,读者们也可修改这21个标准音调之外的值)
always@(posedge clk) //状态机内容的显示 begin case(sc) s0: //不显示,关机状态 begin row_1<=" Welcome to use "; //对液晶屏显示的内容进行赋值 row_2<=" The Gas stove "; G_col<=8'b0; R_col<=8'b0; row<=8'b11111111; end s1: //显示小火 begin row_1<=" Fire Level "; row_2<=" Little Fire "; G_col<=8'b0; case(cnt1) 3'b000:begin row<=8'b11111110;R_col<=8'b00000000; end 3'b001:begin row<=8'b11111101;R_col<=8'b00000000; end 3'b010:begin row<=8'b11111011;R_col<=8'b00011000; end 3'b011:begin row<=8'b11110111;R_col<=8'b00100100; end 3'b100:begin row<=8'b11101111;R_col<=8'b00100100; end 3'b101:begin row<=8'b11011111;R_col<=8'b00011000; end 3'b110:begin row<=8'b10111111;R_col<=8'b00000000; end 3'b111:begin row<=8'b01111111;R_col<=8'b00000000; end endcase end s2: //显示中火 begin row_1<=" Fire Level "; row_2<=" Medium Fire "; G_col<=8'b0; case(cnt1) 3'b000:begin row<=8'b11111110;R_col<=8'b00000000; end 3'b001:begin row<=8'b11111101;R_col<=8'b00011000; end 3'b010:begin row<=8'b11111011;R_col<=8'b00100100; end 3'b011:begin row<=8'b11110111;R_col<=8'b01011010; end 3'b100:begin row<=8'b11101111;R_col<=8'b01011010; end 3'b101:begin row<=8'b11011111;R_col<=8'b00100100; end 3'b110:begin row<=8'b10111111;R_col<=8'b00011000; end 3'b111:begin row<=8'b01111111;R_col<=8'b00000000; end endcase end s3: //显示大火,大火中的橙色采用红、绿色扫描叠加实现 begin row_1<=" Fire Level "; row_2<=" Big Fire "; case(cnt1) 3'b000:begin row<=8'b11111110;R_col<=8'b00011000;G_col<=8'b00011000;end 3'b001:begin row<=8'b11111101;R_col<=8'b00100100;G_col<=8'b00100100;end 3'b010:begin row<=8'b11111011;R_col<=8'b01011010;G_col<=8'b01000010;end 3'b011:begin row<=8'b11110111;R_col<=8'b10111101;G_col<=8'b10000001;end 3'b100:begin row<=8'b11101111;R_col<=8'b10111101;G_col<=8'b10000001;end 3'b101:begin row<=8'b11011111;R_col<=8'b01011010;G_col<=8'b01000010;end 3'b110:begin row<=8'b10111111;R_col<=8'b00100100;G_col<=8'b00100100;end 3'b111:begin row<=8'b01111111;R_col<=8'b00011000;G_col<=8'b00011000;end endcase end s4: //显示微火 begin row_1<=" Fire Level "; row_2<=" Micro Fire "; G_col<=8'b0; case(cnt1) 3'b000:begin row<=8'b11111110;R_col<=8'b00000000; end 3'b001:begin row<=8'b11111101;R_col<=8'b00000000; end 3'b010:begin row<=8'b11111011;R_col<=8'b00000000; end 3'b011:begin row<=8'b11110111;R_col<=8'b00011000; end 3'b100:begin row<=8'b11101111;R_col<=8'b00011000; end 3'b101:begin row<=8'b11011111;R_col<=8'b00000000; end 3'b110:begin row<=8'b10111111;R_col<=8'b00000000; end 3'b111:begin row<=8'b01111111;R_col<=8'b00000000; end endcase end s5: //大火报警 begin row_1<=" Fire Level "; row_2<=" Big Fire "; if(BTN6) begin cnt2<=cnt2+1; if(cnt2==8'd253) begin cnt2<=0; beep<=~beep; end else beep<=beep; end case(cnt1) 3'b000:begin row<=8'b11111110;R_col<=8'b00011000;G_col<=8'b00011000;end 3'b001:begin row<=8'b11111101;R_col<=8'b00100100;G_col<=8'b00100100;end 3'b010:begin row<=8'b11111011;R_col<=8'b01011010;G_col<=8'b01000010;end 3'b011:begin row<=8'b11110111;R_col<=8'b10111101;G_col<=8'b10000001;end 3'b100:begin row<=8'b11101111;R_col<=8'b10111101;G_col<=8'b10000001;end 3'b101:begin row<=8'b11011111;R_col<=8'b01011010;G_col<=8'b01000010;end 3'b110:begin row<=8'b10111111;R_col<=8'b00100100;G_col<=8'b00100100;end 3'b111:begin row<=8'b01111111;R_col<=8'b00011000;G_col<=8'b00011000;end endcase end s6: //微火报警 begin row_1<=" Fire Level "; row_2<=" Micro Fire "; G_col<=8'b0; if(BTN5) begin cnt2<=cnt2+1; if(cnt2==8'd253) //最高音的do(H7) begin cnt2<=0; beep<=~beep; end else beep<=beep; end case(cnt1) 3'b000:begin row<=8'b11111110;R_col<=8'b00000000; end 3'b001:begin row<=8'b11111101;R_col<=8'b00000000; end 3'b010:begin row<=8'b11111011;R_col<=8'b00000000; end 3'b011:begin row<=8'b11110111;R_col<=8'b00011000; end 3'b100:begin row<=8'b11101111;R_col<=8'b00011000; end 3'b101:begin row<=8'b11011111;R_col<=8'b00000000; end 3'b110:begin row<=8'b10111111;R_col<=8'b00000000; end 3'b111:begin row<=8'b01111111;R_col<=8'b00000000; end endcase end endcase end
3、倒计时器模块
always@(posedge clk_1hz or posedge rst_n) //倒计时器功能进程 begin if(rst_n) begin ud<=4'b0000; td<=3'b010; end else if(flag) begin if(ud==4'b0000&&td==3'b000) begin ud<=4'b0000; td<=3'b000; end else if(ud==4'b0000) begin ud<=4'b1001; td<=td-1; end else ud<=ud-1; end else begin ud<=ud; td<=td; end end
4、数码管显示模块
always@(posedge clk) //数码管扫描时钟产生进程 begin if(count2==2000) begin count2<=0; clk_scan<=~clk_scan; end else count2<=count2+1; end always @(posedge clk_scan) begin if(cnt3==2'b11) cnt3<=2'b00; else cnt3<=cnt3+1; end always @(ud or td or cnt3) begin if(sc==s0) DS<=8'b1111_1111; else if(cnt3==2'b01) begin DS<=8'b1111_1110; case(ud) //倒计时器个位数显示 4'b0000:begin duan=8'b0011_1111;end 4'b0001:begin duan=8'b0000_0110;end 4'b0010:begin duan=8'b0101_1011;end 4'b0011:begin duan=8'b0100_1111;end 4'b0100:begin duan=8'b0110_0110;end 4'b0101:begin duan=8'b0110_1101;end 4'b0110:begin duan=8'b0111_1101;end 4'b0111:begin duan=8'b0000_0111;end 4'b1000:begin duan=8'b0111_1111;end 4'b1001:begin duan=8'b0110_1111;end endcase end else if(cnt3==2'b10) begin DS<=8'b1111_1101; case(td) //倒计时器十位数显示 3'b000:duan=8'b0011_1111; 3'b001:duan=8'b0000_0110; 3'b010:duan=8'b0101_1011; 3'b011:duan=8'b0100_1111; 3'b100:duan=8'b0110_0110; 3'b101:duan=8'b0110_1101; 3'b110:duan=8'b0111_1101; endcase end else if(cnt3==2'b11) DS<=8'b1111_1111; end
5、按键消抖模块
module debounce1(clk,rst,key,key_pulse1); //消抖模块1 parameter N = 1; //要消除的按键的数量 input clk; input rst; input [N-1:0] key; //输入的按键 output [N-1:0] key_pulse1; //按键动作产生的脉冲 reg [N-1:0] key_rst_pre; //定义一个寄存器型变量存储上一个触发时的按键值 reg [N-1:0] key_rst; //定义一个寄存器变量储存储当前时刻触发的按键值 wire [N-1:0] key_edge; //检测到按键由高到低变化是产生一个高脉冲 //利用非阻塞赋值特点,将两个时钟触发时按键状态存储在两个寄存器变量中 always @(posedge clk or posedge rst) begin if(rst) begin key_rst <= {N{1'b1}}; //初始化时给key_rst赋值全为1,{}中表示N个1 key_rst_pre <= {N{1'b1}}; end else begin key_rst <= key; //第一个时钟上升沿触发之后key的值赋给key_rst,同时key_rst的值赋给key_rst_pre key_rst_pre <= key_rst; //非阻塞赋值。相当于经过两个时钟触发,key_rst存储的是当前时刻key的值,key_rst_pre存储的是前一个时钟的key的值 end end assign key_edge = (~key_rst_pre) & key_rst; //脉冲边沿检测。当key检测到上升沿时,key_edge产生一个时钟周期的高电平 reg[13:0] cnt; //产生延时所用的计数器,系统时钟1MHz,要延时15ms左右时间,至少需要14位计数器 //产生20ms延时,当检测到key_edge有效是计数器清零开始计数 always@(posedge clk or posedge rst) begin if(rst) cnt <= 14'h0; else if(key_edge) cnt <= 14'h0; else cnt <= cnt + 1'h1; end reg [N-1:0] key_sec_pre; //延时后检测电平寄存器变量 reg [N-1:0] key_sec; //延时后检测key,如果按键状态变高产生一个时钟的高脉冲。如果按键状态是低的话说明按键无效 always@(posedge clk or posedge rst) begin if(rst) key_sec <= {N{1'b1}}; else if (cnt==14'h3a98) key_sec <= key; end always@(posedge clk or posedge rst) begin if(rst) key_sec_pre <= {N{1'b1}}; else key_sec_pre <= key_sec; end assign key_pulse1 = (~key_sec_pre) & key_sec; endmodule
六、完整代码
module Gasstove(clk,rst,rst_n,BTN7,BTN6,BTN5,BTN3,row,R_col,G_col,beep,duan,DS,_RST,LCD_E,LCD_RS,LCD_DATA); //定义燃气灶模块 input clk,rst,rst_n,BTN7,BTN6,BTN5,BTN3,_RST; //BTN7做总开关,BTN6为加大火力键,BTN5为减小火力键,BTN3控制计时开始or暂停,rst赋BTN4,rst_n赋BTN2 output row,R_col,G_col,beep,duan,DS,LCD_E,LCD_RS,LCD_DATA; reg [7:0] row,R_col,G_col; //点阵的行、红灯列和绿灯列显示 reg [2:0] cnt1; //计数器1,用于点阵的行、列扫描 reg [7:0] cnt2; //计数器2,用于产生蜂鸣器的音调频率 reg [1:0] cnt3; //计数器3,用于分位显示数码管 reg [2:0] sc; //state_current表示现态 reg [2:0] sn; //state_next表示次态 reg beep; //蜂鸣器 reg flag; //启动暂停标志 reg clk_1hz; //1HZ(1s)时钟信号 reg clk_scan; //数码管扫描时钟 reg [3:0] ud; //数码管个位数(unit's digit) reg [2:0] td; //数码管十位数(ten's digit) reg [7:0] duan; //数码管段码 reg [7:0] DS; //数码管位码 reg LCD_RS; //LCD1602液晶屏数据指令寄存器选择控制端 reg [7:0] LCD_DATA; //LCD1602液晶屏八位并行数据 reg [127:0] row_1; //LCD1602液晶屏第一行显示内容(最多可显示16个字符) reg [127:0] row_2; //LCD1602液晶屏第二行显示内容(最多可显示16个字符) integer count; //1HZ时钟计数器 integer count2; //扫描时钟计数器 parameter s0=3'b0; //熄灭状态 parameter s1=3'b001; //小火状态 parameter s2=3'b010; //中火状态 parameter s3=3'b011; //大火状态 parameter s4=3'b100; //微火状态 parameter s5=3'b101; //大火下的报警状态 parameter s6=3'b110; //微火下的报警状态 wire key_pulse1; //消抖后的BTN7 wire key_pulse2; //消抖后的BTN6 wire key_pulse3; //消抖后的BTN5 wire key_pulse4; //消抖后的BTN3 debounce1 u1 ( //例化消抖模块1,为BTN7按键消抖 .clk (clk), .rst (rst), .key (BTN7), .key_pulse1 (key_pulse1) ) ; debounce2 u2 ( //例化消抖模块2,为BTN6按键消抖 .clk (clk), .rst (rst), .key (BTN6), .key_pulse2 (key_pulse2) ) ; debounce3 u3 ( //例化消抖模块3,为BTN5按键消抖 .clk (clk), .rst (rst), .key (BTN5), .key_pulse3 (key_pulse3) ) ; debounce4 u4 ( //例化消抖模块4,为BTN3按键消抖 .clk (clk), .rst (rst), .key (BTN3), .key_pulse4 (key_pulse4) ) ; parameter TIME_500HZ=2000; //工作周期,可编程器件实验板MAXII EPM1270T144C5选择时钟晶振为1MHz reg[19:0]cnt_500hz; parameter TIME_20MS=20000; //需要20ms以达上电稳定(初始化) reg[19:0]cnt_20ms; parameter IDLE=8'h00; parameter SET_FUNCTION=8'h01; parameter DISP_OFF=8'h03; parameter DISP_CLEAR=8'h02; parameter ENTRY_MODE=8'h06; parameter DISP_ON=8'h07; parameter ROW1_ADDR=8'h05; parameter ROW1_0=8'h04; parameter ROW1_1=8'h0C; parameter ROW1_2=8'h0D; parameter ROW1_3=8'h0F; parameter ROW1_4=8'h0E; parameter ROW1_5=8'h0A; parameter ROW1_6=8'h0B; parameter ROW1_7=8'h09; parameter ROW1_8=8'h08; parameter ROW1_9=8'h18; parameter ROW1_A=8'h19; parameter ROW1_B=8'h1B; parameter ROW1_C=8'h1A; parameter ROW1_D=8'h1E; parameter ROW1_E=8'h1F; parameter ROW1_F=8'h1D; parameter ROW2_ADDR=8'h1C; parameter ROW2_0=8'h14; parameter ROW2_1=8'h15; parameter ROW2_2=8'h17; parameter ROW2_3=8'h16; parameter ROW2_4=8'h12; parameter ROW2_5=8'h13; parameter ROW2_6=8'h11; parameter ROW2_7=8'h10; parameter ROW2_8=8'h30; parameter ROW2_9=8'h31; parameter ROW2_A=8'h33; parameter ROW2_B=8'h32; parameter ROW2_C=8'h36; parameter ROW2_D=8'h37; parameter ROW2_E=8'h35; parameter ROW2_F=8'h34; reg[5:0]c_state; //current state,当前状态 reg[5:0]n_state; //next state,下一状态 always@(posedge clk or negedge _RST) begin if(!_RST) cnt_20ms<=1'b0; else if(cnt_20ms==TIME_20MS-1'b1) cnt_20ms<=cnt_20ms; else cnt_20ms<=cnt_20ms+1'b1 ; end wire delay_done=(cnt_20ms==TIME_20MS-1'b1)?1'b1:1'b0; //上电延时完毕 always@(posedge clk or negedge _RST) begin if(!_RST) cnt_500hz<=1'b0; else if(delay_done) if(cnt_500hz==TIME_500HZ-1'b1) cnt_500hz<=1'b0; else cnt_500hz<=cnt_500hz+1'b1; else cnt_500hz<=1'b0; end assign LCD_E=(cnt_500hz>(TIME_500HZ-1'b1)/2)?1'b0:1'b1; //使能端,每个工作周期一次下降沿,执行一次命令 wire write_flag=(cnt_500hz==TIME_500HZ-1'b1)?1'b1:1'b0; //每到一个工作周期,write_flag置高一周期 always@(posedge clk or negedge _RST) begin if(!_RST) c_state<=IDLE; else if(write_flag) //每一个工作周期改变一次状态 c_state<=n_state; else c_state<=c_state; end always@(*) begin case(c_state) IDLE:n_state=SET_FUNCTION; SET_FUNCTION:n_state=DISP_OFF; DISP_OFF:n_state=DISP_CLEAR; DISP_CLEAR:n_state=ENTRY_MODE; ENTRY_MODE:n_state=DISP_ON; DISP_ON:n_state=ROW1_ADDR; ROW1_ADDR:n_state=ROW1_0; ROW1_0:n_state=ROW1_1; ROW1_1:n_state=ROW1_2; ROW1_2:n_state=ROW1_3; ROW1_3:n_state=ROW1_4; ROW1_4:n_state=ROW1_5; ROW1_5:n_state=ROW1_6; ROW1_6:n_state=ROW1_7; ROW1_7:n_state=ROW1_8; ROW1_8:n_state=ROW1_9; ROW1_9:n_state=ROW1_A; ROW1_A:n_state=ROW1_B; ROW1_B:n_state=ROW1_C; ROW1_C:n_state=ROW1_D; ROW1_D:n_state=ROW1_E; ROW1_E:n_state=ROW1_F; ROW1_F:n_state=ROW2_ADDR; ROW2_ADDR:n_state=ROW2_0; ROW2_0:n_state=ROW2_1; ROW2_1:n_state=ROW2_2; ROW2_2:n_state=ROW2_3; ROW2_3:n_state=ROW2_4; ROW2_4:n_state=ROW2_5; ROW2_5:n_state=ROW2_6; ROW2_6:n_state=ROW2_7; ROW2_7:n_state=ROW2_8; ROW2_8:n_state=ROW2_9; ROW2_9:n_state=ROW2_A; ROW2_A:n_state=ROW2_B; ROW2_B:n_state=ROW2_C; ROW2_C:n_state=ROW2_D; ROW2_D:n_state=ROW2_E; ROW2_E:n_state=ROW2_F; ROW2_F:n_state=ROW1_ADDR; //循环到1-1进行扫描显示 default:; endcase end always@(posedge clk or negedge _RST) begin if(!_RST) LCD_DATA<=1'b0; else if(write_flag) case(n_state) IDLE:LCD_DATA<=8'hxx; SET_FUNCTION:LCD_DATA<=8'h38; //8'b0011_1000,工作方式设置:DL=1(DB4,8位数据接口),N=1(DB3,两行显示),L=0(DB2,5x8点阵显示). DISP_OFF:LCD_DATA<=8'h08; //8'b0000_1000,显示开关设置:D=0(DB2,显示关),C=0(DB1,光标不显示),D=0(DB0,光标不闪烁) DISP_CLEAR:LCD_DATA<=8'h01; //8'b0000_0001,清屏 ENTRY_MODE:LCD_DATA<=8'h06; //8'b0000_0110,进入模式设置:I/D=1(DB1,写入新数据光标右移),S=0(DB0,显示不移动) DISP_ON:LCD_DATA<=8'h0c; //8'b0000_1100,显示开关设置:D=1(DB2,显示开),C=0(DB1,光标不显示),D=0(DB0,光标不闪烁) ROW1_ADDR:LCD_DATA<=8'h80; //8'b1000_0000,设置DDRAM地址:00H->1-1,第一行第一位 //将输入的row_1以每8-bit拆分,分配给对应的显示位 ROW1_0:LCD_DATA<=row_1[127:120]; ROW1_1:LCD_DATA<=row_1[119:112]; ROW1_2:LCD_DATA<=row_1[111:104]; ROW1_3:LCD_DATA<=row_1[103: 96]; ROW1_4:LCD_DATA<=row_1[ 95: 88]; ROW1_5:LCD_DATA<=row_1[ 87: 80]; ROW1_6:LCD_DATA<=row_1[ 79: 72]; ROW1_7:LCD_DATA<=row_1[ 71: 64]; ROW1_8:LCD_DATA<=row_1[ 63: 56]; ROW1_9:LCD_DATA<=row_1[ 55: 48]; ROW1_A:LCD_DATA<=row_1[ 47: 40]; ROW1_B:LCD_DATA<=row_1[ 39: 32]; ROW1_C:LCD_DATA<=row_1[ 31: 24]; ROW1_D:LCD_DATA<=row_1[ 23: 16]; ROW1_E:LCD_DATA<=row_1[ 15: 8]; ROW1_F:LCD_DATA<=row_1[ 7: 0]; ROW2_ADDR:LCD_DATA<=8'hc0; //8'b1100_0000,设置DDRAM地址:40H->2-1,第二行第一位 ROW2_0:LCD_DATA<=row_2[127:120]; ROW2_1:LCD_DATA<=row_2[119:112]; ROW2_2:LCD_DATA<=row_2[111:104]; ROW2_3:LCD_DATA<=row_2[103: 96]; ROW2_4:LCD_DATA<=row_2[ 95: 88]; ROW2_5:LCD_DATA<=row_2[ 87: 80]; ROW2_6:LCD_DATA<=row_2[ 79: 72]; ROW2_7:LCD_DATA<=row_2[ 71: 64]; ROW2_8:LCD_DATA<=row_2[ 63: 56]; ROW2_9:LCD_DATA<=row_2[ 55: 48]; ROW2_A:LCD_DATA<=row_2[ 47: 40]; ROW2_B:LCD_DATA<=row_2[ 39: 32]; ROW2_C:LCD_DATA<=row_2[ 31: 24]; ROW2_D:LCD_DATA<=row_2[ 23: 16]; ROW2_E:LCD_DATA<=row_2[ 15: 8]; ROW2_F:LCD_DATA<=row_2[ 7: 0]; endcase else LCD_DATA<=LCD_DATA; end always@(posedge clk or negedge _RST) begin if(!_RST) LCD_RS<=1'b0; //为0时输入指令,为1时输入数据 else if(write_flag) //当状态为七个指令任意一个,将RS置为指令输入状态 if((n_state==SET_FUNCTION)||(n_state==DISP_OFF)||(n_state==DISP_CLEAR)||(n_state==ENTRY_MODE)||(n_state==DISP_ON)||(n_state==ROW1_ADDR)||(n_state==ROW2_ADDR)) LCD_RS<=1'b0; else LCD_RS<=1'b1; else LCD_RS<=LCD_RS; end initial //初始设定数码管显示20 begin ud=4'b0000; td=3'b010; end always@(posedge clk or posedge rst) //计数器用于点阵显示 begin if(rst) cnt1<= 3'b0; else cnt1<=cnt1+1'b1; end always@(posedge clk or posedge rst_n) //BTN3产生标志信号,BTN3控制计时开始or暂停 begin if(rst_n) flag = 1'b0; else if(key_pulse4) flag = ~flag; else flag = flag; end always@(posedge clk) //1HZ时钟进程 begin if(count==500000) begin clk_1hz=~clk_1hz; count<=0; end else count<=count+1'b1; end always@(posedge clk_1hz or posedge rst_n) //倒计时器功能进程 begin if(rst_n) begin ud<=4'b0000; td<=3'b010; end else if(flag) begin if(ud==4'b0000&&td==3'b000) begin ud<=4'b0000; td<=3'b000; end else if(ud==4'b0000) begin ud<=4'b1001; td<=td-1; end else ud<=ud-1; end else begin ud<=ud; td<=td; end end always@(posedge clk) //数码管扫描时钟产生进程 begin if(count2==2000) begin count2<=0; clk_scan<=~clk_scan; end else count2<=count2+1; end always @(posedge clk_scan) begin if(cnt3==2'b11) cnt3<=2'b00; else cnt3<=cnt3+1; end always @(ud or td or cnt3) begin if(sc==s0) DS<=8'b1111_1111; else if(cnt3==2'b01) begin DS<=8'b1111_1110; case(ud) //倒计时器个位数显示 4'b0000:begin duan=8'b0011_1111;end 4'b0001:begin duan=8'b0000_0110;end 4'b0010:begin duan=8'b0101_1011;end 4'b0011:begin duan=8'b0100_1111;end 4'b0100:begin duan=8'b0110_0110;end 4'b0101:begin duan=8'b0110_1101;end 4'b0110:begin duan=8'b0111_1101;end 4'b0111:begin duan=8'b0000_0111;end 4'b1000:begin duan=8'b0111_1111;end 4'b1001:begin duan=8'b0110_1111;end endcase end else if(cnt3==2'b10) begin DS<=8'b1111_1101; case(td) //倒计时器十位数显示 3'b000:duan=8'b0011_1111; 3'b001:duan=8'b0000_0110; 3'b010:duan=8'b0101_1011; 3'b011:duan=8'b0100_1111; 3'b100:duan=8'b0110_0110; 3'b101:duan=8'b0110_1101; 3'b110:duan=8'b0111_1101; endcase end else if(cnt3==2'b11) DS<=8'b1111_1111; end always @(posedge clk or posedge rst) begin if(rst) begin sc<=s0; end else begin sc<=sn; //状态转移设定,不按下复位键时采用非阻塞赋值将次态赋给现态 end end always@(key_pulse1 or key_pulse2 or key_pulse3) //采用状态机描述七个状态的转移 begin case(sc) //对每一个状态可能的次态进行列举,本质上就是状态转移图的代码化 s0: begin if(key_pulse1) sn=s1; //采用阻塞赋值,将目标状态赋给次态 else if(key_pulse2) sn=s0; else if(key_pulse3) sn=s0; else if(ud==4'b0000&&td==3'b000) sn=s0; else sn=s0; end s1: begin if(key_pulse1) sn=s0; else if(key_pulse2) sn=s2; else if(key_pulse3) sn=s4; else if(ud==4'b0000&&td==3'b000) sn=s0; else sn=s1; end s2: begin if(key_pulse1) sn=s0; else if(key_pulse2) sn=s3; else if(key_pulse3) sn=s1; else if(ud==4'b0000&&td==3'b000) sn=s0; else sn=s2; end s3: begin if(key_pulse1) sn=s0; else if(key_pulse2) sn=s5; else if(key_pulse3) sn=s2; else if(ud==4'b0000&&td==3'b000) sn=s0; else sn=s3; end s4: begin if(key_pulse1) sn=s0; else if(key_pulse2) sn=s1; else if(key_pulse3) sn=s6; else if(ud==4'b0000&&td==3'b000) sn=s0; else sn=s4; end s5: //大火报警 begin if(key_pulse1) sn=s0; else if(key_pulse2) sn=s5; else if(key_pulse3) sn=s2; else if(ud==4'b0000&&td==3'b000) sn=s0; else sn=s5; end s6: //微火报警 begin if(key_pulse1) sn=s0; else if(key_pulse2) sn=s1; else if(key_pulse3) sn=s6; else if(ud==4'b0000&&td==3'b000) sn=s0; else sn=s6; end endcase end always@(posedge clk) //状态机内容的显示 begin case(sc) s0: //不显示,关机状态 begin row_1<=" Welcome to use "; //对液晶屏显示的内容进行赋值 row_2<=" The Gas stove "; G_col<=8'b0; R_col<=8'b0; row<=8'b11111111; end s1: //显示小火 begin row_1<=" Fire Level "; row_2<=" Little Fire "; G_col<=8'b0; case(cnt1) 3'b000:begin row<=8'b11111110;R_col<=8'b00000000; end 3'b001:begin row<=8'b11111101;R_col<=8'b00000000; end 3'b010:begin row<=8'b11111011;R_col<=8'b00011000; end 3'b011:begin row<=8'b11110111;R_col<=8'b00100100; end 3'b100:begin row<=8'b11101111;R_col<=8'b00100100; end 3'b101:begin row<=8'b11011111;R_col<=8'b00011000; end 3'b110:begin row<=8'b10111111;R_col<=8'b00000000; end 3'b111:begin row<=8'b01111111;R_col<=8'b00000000; end endcase end s2: //显示中火 begin row_1<=" Fire Level "; row_2<=" Medium Fire "; G_col<=8'b0; case(cnt1) 3'b000:begin row<=8'b11111110;R_col<=8'b00000000; end 3'b001:begin row<=8'b11111101;R_col<=8'b00011000; end 3'b010:begin row<=8'b11111011;R_col<=8'b00100100; end 3'b011:begin row<=8'b11110111;R_col<=8'b01011010; end 3'b100:begin row<=8'b11101111;R_col<=8'b01011010; end 3'b101:begin row<=8'b11011111;R_col<=8'b00100100; end 3'b110:begin row<=8'b10111111;R_col<=8'b00011000; end 3'b111:begin row<=8'b01111111;R_col<=8'b00000000; end endcase end s3: //显示大火,大火中的橙色采用红、绿色扫描叠加实现 begin row_1<=" Fire Level "; row_2<=" Big Fire "; case(cnt1) 3'b000:begin row<=8'b11111110;R_col<=8'b00011000;G_col<=8'b00011000;end 3'b001:begin row<=8'b11111101;R_col<=8'b00100100;G_col<=8'b00100100;end 3'b010:begin row<=8'b11111011;R_col<=8'b01011010;G_col<=8'b01000010;end 3'b011:begin row<=8'b11110111;R_col<=8'b10111101;G_col<=8'b10000001;end 3'b100:begin row<=8'b11101111;R_col<=8'b10111101;G_col<=8'b10000001;end 3'b101:begin row<=8'b11011111;R_col<=8'b01011010;G_col<=8'b01000010;end 3'b110:begin row<=8'b10111111;R_col<=8'b00100100;G_col<=8'b00100100;end 3'b111:begin row<=8'b01111111;R_col<=8'b00011000;G_col<=8'b00011000;end endcase end s4: //显示微火 begin row_1<=" Fire Level "; row_2<=" Micro Fire "; G_col<=8'b0; case(cnt1) 3'b000:begin row<=8'b11111110;R_col<=8'b00000000; end 3'b001:begin row<=8'b11111101;R_col<=8'b00000000; end 3'b010:begin row<=8'b11111011;R_col<=8'b00000000; end 3'b011:begin row<=8'b11110111;R_col<=8'b00011000; end 3'b100:begin row<=8'b11101111;R_col<=8'b00011000; end 3'b101:begin row<=8'b11011111;R_col<=8'b00000000; end 3'b110:begin row<=8'b10111111;R_col<=8'b00000000; end 3'b111:begin row<=8'b01111111;R_col<=8'b00000000; end endcase end s5: //大火报警 begin row_1<=" Fire Level "; row_2<=" Big Fire "; if(BTN6) begin cnt2<=cnt2+1; if(cnt2==8'd253) begin cnt2<=0; beep<=~beep; end else beep<=beep; end case(cnt1) 3'b000:begin row<=8'b11111110;R_col<=8'b00011000;G_col<=8'b00011000;end 3'b001:begin row<=8'b11111101;R_col<=8'b00100100;G_col<=8'b00100100;end 3'b010:begin row<=8'b11111011;R_col<=8'b01011010;G_col<=8'b01000010;end 3'b011:begin row<=8'b11110111;R_col<=8'b10111101;G_col<=8'b10000001;end 3'b100:begin row<=8'b11101111;R_col<=8'b10111101;G_col<=8'b10000001;end 3'b101:begin row<=8'b11011111;R_col<=8'b01011010;G_col<=8'b01000010;end 3'b110:begin row<=8'b10111111;R_col<=8'b00100100;G_col<=8'b00100100;end 3'b111:begin row<=8'b01111111;R_col<=8'b00011000;G_col<=8'b00011000;end endcase end s6: //微火报警 begin row_1<=" Fire Level "; row_2<=" Micro Fire "; G_col<=8'b0; if(BTN5) begin cnt2<=cnt2+1; if(cnt2==8'd253) //最高音的do(H7) begin cnt2<=0; beep<=~beep; end else beep<=beep; end case(cnt1) 3'b000:begin row<=8'b11111110;R_col<=8'b00000000; end 3'b001:begin row<=8'b11111101;R_col<=8'b00000000; end 3'b010:begin row<=8'b11111011;R_col<=8'b00000000; end 3'b011:begin row<=8'b11110111;R_col<=8'b00011000; end 3'b100:begin row<=8'b11101111;R_col<=8'b00011000; end 3'b101:begin row<=8'b11011111;R_col<=8'b00000000; end 3'b110:begin row<=8'b10111111;R_col<=8'b00000000; end 3'b111:begin row<=8'b01111111;R_col<=8'b00000000; end endcase end endcase end endmodule module debounce1(clk,rst,key,key_pulse1); //消抖模块1 parameter N = 1; //要消除的按键的数量 input clk; input rst; input [N-1:0] key; //输入的按键 output [N-1:0] key_pulse1; //按键动作产生的脉冲 reg [N-1:0] key_rst_pre; //定义一个寄存器型变量存储上一个触发时的按键值 reg [N-1:0] key_rst; //定义一个寄存器变量储存储当前时刻触发的按键值 wire [N-1:0] key_edge; //检测到按键由高到低变化是产生一个高脉冲 //利用非阻塞赋值特点,将两个时钟触发时按键状态存储在两个寄存器变量中 always @(posedge clk or posedge rst) begin if(rst) begin key_rst <= {N{1'b1}}; //初始化时给key_rst赋值全为1,{}中表示N个1 key_rst_pre <= {N{1'b1}}; end else begin key_rst <= key; //第一个时钟上升沿触发之后key的值赋给key_rst,同时key_rst的值赋给key_rst_pre key_rst_pre <= key_rst; //非阻塞赋值。相当于经过两个时钟触发,key_rst存储的是当前时刻key的值,key_rst_pre存储的是前一个时钟的key的值 end end assign key_edge = (~key_rst_pre) & key_rst; //脉冲边沿检测。当key检测到上升沿时,key_edge产生一个时钟周期的高电平 reg[13:0] cnt; //产生延时所用的计数器,系统时钟1MHz,要延时15ms左右时间,至少需要14位计数器 //产生20ms延时,当检测到key_edge有效是计数器清零开始计数 always@(posedge clk or posedge rst) begin if(rst) cnt <= 14'h0; else if(key_edge) cnt <= 14'h0; else cnt <= cnt + 1'h1; end reg [N-1:0] key_sec_pre; //延时后检测电平寄存器变量 reg [N-1:0] key_sec; //延时后检测key,如果按键状态变高产生一个时钟的高脉冲。如果按键状态是低的话说明按键无效 always@(posedge clk or posedge rst) begin if(rst) key_sec <= {N{1'b1}}; else if (cnt==14'h3a98) key_sec <= key; end always@(posedge clk or posedge rst) begin if(rst) key_sec_pre <= {N{1'b1}}; else key_sec_pre <= key_sec; end assign key_pulse1 = (~key_sec_pre) & key_sec; endmodule module debounce2(clk,rst,key,key_pulse2); //消抖模块2 parameter N = 1; //要消除的按键的数量 input clk; input rst; input [N-1:0] key; //输入的按键 output [N-1:0] key_pulse2; //按键动作产生的脉冲 reg [N-1:0] key_rst_pre; //定义一个寄存器型变量存储上一个触发时的按键值 reg [N-1:0] key_rst; //定义一个寄存器变量储存储当前时刻触发的按键值 wire [N-1:0] key_edge; //检测到按键由高到低变化是产生一个高脉冲 //利用非阻塞赋值特点,将两个时钟触发时按键状态存储在两个寄存器变量中 always @(posedge clk or posedge rst) begin if(rst) begin key_rst <= {N{1'b1}}; //初始化时给key_rst赋值全为1,{}中表示N个1 key_rst_pre <= {N{1'b1}}; end else begin key_rst <= key; //第一个时钟上升沿触发之后key的值赋给key_rst,同时key_rst的值赋给key_rst_pre key_rst_pre <= key_rst; //非阻塞赋值。相当于经过两个时钟触发,key_rst存储的是当前时刻key的值,key_rst_pre存储的是前一个时钟的key的值 end end assign key_edge = (~key_rst_pre) & key_rst; //脉冲边沿检测。当key检测到上升沿时,key_edge产生一个时钟周期的高电平 reg[13:0] cnt; //产生延时所用的计数器,系统时钟1MHz,要延时15ms左右时间,至少需要14位计数器 //产生20ms延时,当检测到key_edge有效是计数器清零开始计数 always@(posedge clk or posedge rst) begin if(rst) cnt <= 14'h0; else if(key_edge) cnt <= 14'h0; else cnt <= cnt + 1'h1; end reg [N-1:0] key_sec_pre; //延时后检测电平寄存器变量 reg [N-1:0] key_sec; //延时后检测key,如果按键状态变高产生一个时钟的高脉冲。如果按键状态是低的话说明按键无效 always@(posedge clk or posedge rst) begin if(rst) key_sec <= {N{1'b1}}; else if (cnt==14'h3a98) key_sec <= key; end always@(posedge clk or posedge rst) begin if(rst) key_sec_pre <= {N{1'b1}}; else key_sec_pre <= key_sec; end assign key_pulse2 = (~key_sec_pre) & key_sec; endmodule module debounce3(clk,rst,key,key_pulse3); //消抖模块1 parameter N = 1; //要消除的按键的数量 input clk; input rst; input [N-1:0] key; //输入的按键 output [N-1:0] key_pulse3; //按键动作产生的脉冲 reg [N-1:0] key_rst_pre; //定义一个寄存器型变量存储上一个触发时的按键值 reg [N-1:0] key_rst; //定义一个寄存器变量储存储当前时刻触发的按键值 wire [N-1:0] key_edge; //检测到按键由高到低变化是产生一个高脉冲 //利用非阻塞赋值特点,将两个时钟触发时按键状态存储在两个寄存器变量中 always @(posedge clk or posedge rst) begin if(rst) begin key_rst <= {N{1'b1}}; //初始化时给key_rst赋值全为1,{}中表示N个1 key_rst_pre <= {N{1'b1}}; end else begin key_rst <= key; //第一个时钟上升沿触发之后key的值赋给key_rst,同时key_rst的值赋给key_rst_pre key_rst_pre <= key_rst; //非阻塞赋值。相当于经过两个时钟触发,key_rst存储的是当前时刻key的值,key_rst_pre存储的是前一个时钟的key的值 end end assign key_edge = (~key_rst_pre) & key_rst; //脉冲边沿检测。当key检测到上升沿时,key_edge产生一个时钟周期的高电平 reg[13:0] cnt; //产生延时所用的计数器,系统时钟1MHz,要延时15ms左右时间,至少需要14位计数器 //产生20ms延时,当检测到key_edge有效是计数器清零开始计数 always@(posedge clk or posedge rst) begin if(rst) cnt <= 14'h0; else if(key_edge) cnt <= 14'h0; else cnt <= cnt + 1'h1; end reg [N-1:0] key_sec_pre; //延时后检测电平寄存器变量 reg [N-1:0] key_sec; //延时后检测key,如果按键状态变高产生一个时钟的高脉冲。如果按键状态是低的话说明按键无效 always@(posedge clk or posedge rst) begin if(rst) key_sec <= {N{1'b1}}; else if (cnt==14'h3a98) key_sec <= key; end always@(posedge clk or posedge rst) begin if(rst) key_sec_pre <= {N{1'b1}}; else key_sec_pre <= key_sec; end assign key_pulse3 = (~key_sec_pre) & key_sec; endmodule module debounce4(clk,rst,key,key_pulse4); //消抖模块1 parameter N = 1; //要消除的按键的数量 input clk; input rst; input [N-1:0] key; //输入的按键 output [N-1:0] key_pulse4; //按键动作产生的脉冲 reg [N-1:0] key_rst_pre; //定义一个寄存器型变量存储上一个触发时的按键值 reg [N-1:0] key_rst; //定义一个寄存器变量储存储当前时刻触发的按键值 wire [N-1:0] key_edge; //检测到按键由高到低变化是产生一个高脉冲 //利用非阻塞赋值特点,将两个时钟触发时按键状态存储在两个寄存器变量中 always @(posedge clk or posedge rst) begin if(rst) begin key_rst <= {N{1'b1}}; //初始化时给key_rst赋值全为1,{}中表示N个1 key_rst_pre <= {N{1'b1}}; end else begin key_rst <= key; //第一个时钟上升沿触发之后key的值赋给key_rst,同时key_rst的值赋给key_rst_pre key_rst_pre <= key_rst; //非阻塞赋值。相当于经过两个时钟触发,key_rst存储的是当前时刻key的值,key_rst_pre存储的是前一个时钟的key的值 end end assign key_edge = (~key_rst_pre) & key_rst; //脉冲边沿检测。当key检测到上升沿时,key_edge产生一个时钟周期的高电平 reg[13:0] cnt; //产生延时所用的计数器,系统时钟1MHz,要延时15ms左右时间,至少需要14位计数器 //产生20ms延时,当检测到key_edge有效是计数器清零开始计数 always@(posedge clk or posedge rst) begin if(rst) cnt <= 14'h0; else if(key_edge) cnt <= 14'h0; else cnt <= cnt + 1'h1; end reg [N-1:0] key_sec_pre; //延时后检测电平寄存器变量 reg [N-1:0] key_sec; //延时后检测key,如果按键状态变高产生一个时钟的高脉冲。如果按键状态是低的话说明按键无效 always@(posedge clk or posedge rst) begin if(rst) key_sec <= {N{1'b1}}; else if (cnt==14'h3a98) key_sec <= key; end always@(posedge clk or posedge rst) begin if(rst) key_sec_pre <= {N{1'b1}}; else key_sec_pre <= key_sec; end assign key_pulse4 = (~key_sec_pre) & key_sec; endmodule
七、总结
到这里整个模拟燃气灶就完成啦!本人编程水平、时间有限,这篇文章到这里就要结束啦,欢迎广大读者评论留言,更欢迎大家指出本人的不足,希望能通过交流自身得到提高。最后感谢大家的耐心阅读!
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。