北邮数字系统设计实验课 基于 CPLD 的空气炸锅控制器的设计与实现
博主的话
个人比较喜欢实验类以及编程相关的课程,比如这次实验课,做出些实物还是很有成就感的(๑¯∀¯๑),感觉这次做的还算不错,便想发个博客分享一下,这是我头一次写博客,如有缺陷还请多多包涵,当然也欢迎指出,我会尽力改善的(>▽<)。
题干
题目 1 基于 CPLD 的空气炸锅控制器的设计与实现
设计并实现一个空气炸锅控制器,模拟空气炸锅的工作过程。
基本要求:
1、 拨码开关 SW3 作为控制器的整机开关,SW3=0 为关机状态,点阵、数码管和发光 二极管全灭,打开 SW3(SW3=1)后进入待机状态,点阵、数码管全灭,发光二 极管 LD15 亮表示已开机。
2、 待机状态(SW3=1)下,空气炸锅的工作流程为:设置火力档位→设置加热时间 →加热工作→加热完成回到待机状态。
3、 空气炸锅的火力有大、中、小三档可选。用按键 BTN4 实现火力的选择,用数码管 DISP4 显示火力档位(3→大火,2→中火,1→小火),DISP4 的显示随着按键的按下 次数而变化。
4、 空气炸锅的加热工作时间在 10 秒-99 秒之间可选,用按键 BTN7 和 BTN6 分别设置 加热时间的各位数值,用数码管 DISP7 和 DISP6 显示加热时间。
5、 将按键 BTN0 设置为加热开始键,按一下按键 BTN0,空气炸锅按照设置好的火力 和加热时间开始加热工作。加热工作过程中,数码管 DISP7 和 DISP6 倒计时显示剩 余时间, 8×8 双色点阵显示空气炸锅加热过程的模拟动画,点阵的模拟动画为如 图 1-1 所示四个显示状态按顺序循环显示,一个循环的完成时间为 4 秒,即动画中 每一帧的显示时间是 1 秒。
6、 加热过程中,不能修改火力和加热时间。
7、 加热完成(倒计时到 00)后,蜂鸣器至少鸣响三声以提醒使用者加热已结束,点 阵和数码管熄灭,空气炸锅回到待机状态。
8、 设置复位键,任何时候按下复位键可以取消加热,回到待机状态,只有在待机状 态下才可以修改火力和加热时间。
9、 系统工作流程合理,工作稳定。
提高要求:
1、 用 8 个 LED 灯作为加热进度显示条,随着加热时间的增加匀速增加点亮 LED 灯的 个数,无论加热时间是多长,最后都必须将 8 个 LED 灯全部点亮。
2、 增加暂停功能:设置一个暂停键,加热过程中按一下暂停键进入暂停状态(数码 管停止倒计时,点整动画暂停),在暂停状态下可以修改火力和加热时间,修改完 成后,再按一下暂停键按照新的火力和加热时间继续加热工作到结束。
3、 基于空气炸锅的应用场景,自拟其它功能。
模块电路要求:
在 8×8 双色点阵上设计并实现图 1-1A、B、C 其中一种动画,要求完成仿真并在实验 板上下载显示。
题目分析
工作流程:
通过题干我们可以画出空气炸锅的工作流程:(这个流程是考虑了提高要求的,基础要求6与暂停功能冲突,故舍去)
设计主模块时就可以参考这里的工作流程进行
模块拆分:
同时通过题干我们也可以知道需要哪些功能,然后从中分离出单独的模块,这里只是大体分一下,后续可能有所删改
代码实现
实现顺序:
个人认为比较好的实现顺序是先实现子模块再去写主模块的逻辑,写子模块时可适当考虑主模块调用事宜
消抖模块:
1 module debounce (clk,key,key_pulse); 2 3 parameter CNT_BIT = 18;//20ms计数器位宽 4 parameter CNT_NUM = 18'h3ffff;//20ms对应时钟周期数 5 parameter N = 1; 6 input clk; 7 input [N-1:0] key; 8 output [N-1:0] key_pulse; 9 reg [N-1:0] key_rst_pre; 10 reg [N-1:0] key_rst; 11 wire [N-1:0] key_edge; 12 13 always @(posedge clk) 14 begin 15 key_rst <= key; 16 key_rst_pre <= key_rst; 17 end 18 19 assign key_edge = key_rst_pre & (~key_rst); 20 21 reg [CNT_BIT-1:0] cnt; 22 23 always @(posedge clk) 24 begin 25 if(key_edge) 26 cnt <= 18'h0; 27 else 28 cnt <= cnt + 1'h1; 29 end 30 31 reg [N-1:0] key_sec_pre; 32 reg [N-1:0] key_sec; 33 34 always @(posedge clk) 35 begin 36 if (cnt==CNT_NUM) 37 key_sec <= key; 38 key_sec_pre <= key_sec; 39 end 40 41 assign key_pulse = key_sec_pre & (~key_sec); 42 43 endmodule
这里用的是之前在小脚丫上用的消抖模块,实验板的按键是按下输出高电平,与小脚丫完全相反,用的时候给输入信号取反就行
虽然是个常用模块,不过貌似还是有很多同学不明白消抖模块是怎么实现消抖的,这里顺便说一下
以时钟周期为间隔采样寻找下降沿:
1 always @(posedge clk) 2 begin 3 key_rst <= key; 4 key_rst_pre <= key_rst; 5 end 6 7 assign key_edge = key_rst_pre & (~key_rst);
always里通过非阻塞赋值实现了一个2位宽的移位寄存器,key_rst每个时钟周期记录一次key的输入值,key_rst_pre每个时钟周期记录key_rst上一时刻的值(确保key_edge只输出一个时钟周期的高电平)
assign语句实现当找到下降沿(key_rst_pre为1且key_rst为0)时key_edge输出1
以20ms(+)为间隔采样寻找下降沿:
1 reg [CNT_BIT-1:0] cnt; 2 3 always @(posedge clk) 4 begin 5 if(key_edge) 6 cnt <= 18'h0; 7 else 8 cnt <= cnt + 1'h1; 9 end 10 11 always @(posedge clk) 12 begin 13 if (cnt==CNT_NUM) 14 key_sec <= key; 15 key_sec_pre <= key_sec; 16 end 17 18 assign key_pulse = key_sec_pre & (~key_sec);
第一个always实现每当找到上升沿(key_edge为1)时清零20ms计时器(cnt)
第二个always实现每当cnt计满20ms时令key_sec采样一次,key_sec_pre则是每个时钟周期记录key_sec上一时刻的值(确保key_pulse只输出一个时钟周期高电平)
assign语句实现当找到下降沿(key_sec_pre为1且key_sec为0)时key_pulse输出1
图解:
注意事项:20ms计时器cnt本质上是个计数器,通过记录时钟数来实现计时,故20ms所对应的CNT_NUM要与输入的clk对应
倒计时模块:
为了追求通用性和模块功能的专一性,把原计划中的十进制倒计时模块拆分成二进制倒计时模块和bin2dec数码转换模块
二进制倒计时器模块:
1 module countdown_timer( 2 input clk_1k,//1khz时钟 3 input start,//边沿触发 4 input [NUM_BIT-1:0]start_num,//起始数字 5 output reg progress, 6 output reg [NUM_BIT-1:0]cntd_bin//倒计时当前数(bin) 7 ); 8 9 parameter NUM_BIT = 16; 10 11 reg start_pre; 12 reg [NUM_BIT-1:0]cntd_progress; 13 14 always @(posedge clk_1k) 15 begin 16 if(start_pre^start) {cntd_progress,cntd_bin} = {start_num[NUM_BIT-1:3],start_num};//收到开始信号重置倒计时起始数 17 cntd_progress = (|cntd_progress)?cntd_progress-1:start_num[NUM_BIT-1:3]; 18 cntd_bin = cntd_bin-(|cntd_bin); 19 progress = (|cntd_progress)?0:1; 20 start_pre = start; 21 end 22 23 endmodule
规定输入的时钟为1kHz后,cntd_bin计数器实际上就变成了以毫秒为单位的二进制计时器,只需在cntd_bin非零时的每个时钟上升沿减一即可实现倒计时的功能(行18)
为了方便在主模块中调用,通过start_pre^start和后面的start_pre = start;(行20)检测start的变化,实现在start信号的边沿设置cntd_bin = start_num(行16)
提升要求中的进度条功能可以通过设置一个定时为原时间的1/8的计时器实现,从start_num的第三位取到最高位(行16)相当于对start_num进行了除8操作(地板除),随后cntd_progress开始循环计时(行17),cntd_progress的计时≦原时间的1/8,但在一次倒计时中最多只会有8(次循环)x7ms=56ms的误差,可以忽略不计,cntd_progress每循环计时一次就在重置时(cntd_progress为0)给progress输出一周期的高电平(行19)
bin2dec数码转换模块:
1 module bin2dec( 2 input clk, 3 input [N-1:0]bin, 4 output reg [4*M-1:0]dec//8421编码 5 ); 6 7 parameter N = 16;//输入的二进制位数 8 parameter M = 8;//输出的十进制位数 9 parameter W = 3;//W>=log2(M) 10 11 reg [N-1:0]temp; 12 reg [W-1:0]pos; 13 reg flag; 14 15 always @(posedge clk) 16 begin 17 {flag,pos,temp} = bin; 18 while({flag,pos}!=M) 19 begin 20 {dec[4*pos+3],dec[4*pos+2],dec[4*pos+1],dec[4*pos]} = temp%10; 21 temp = temp/10; 22 {flag,pos} = pos+1; 23 end 24 end 25 26 endmodule
2进制数码转10进制8421bcd码的实现逻辑就很简单了,循环模10取余输出即可
图解:

点阵模块:
1 module dm_draw(clk,enable,color,index,dm_row,dm_colR,dm_colG);//checked----ok 2 3 //以下参数不可在实例化时更改,仅作为常量方便编程 4 parameter L = 16;//基础点阵行L种 5 parameter M1 = 4;//大于等于L的2进制位宽(用于索引) M1=[log2(L)]+1 改了这里要改initial里的位宽(python replace一下) 6 parameter N = 16;//N张图像 7 parameter M2 = 4;//大于等于N的2进制位宽(用于索引) M2=[log2(N)]+1 8 9 input clk; 10 input enable;//使能 11 input [1:0]color;//0灭 1红 2黄 3绿 12 input [M2-1:0]index;//点阵图像索引 13 output reg [7:0]dm_row;//点阵行 0亮 上至下:7-0 14 output reg [7:0]dm_colR;//点阵列红 1亮 左至右:7-0 15 output reg [7:0]dm_colG;//点阵列绿 1亮 16 17 reg [2:0]row;//行索引 18 reg [7:0]col[L-1:0];//基础点阵行L种 19 reg [8*M1-1:0]data[N-1:0];//图像数据 (M位基础行索引)*8行(一张图像),共N张图像 20 reg [M1-1:0]cur_data;//当前行的数据 21 22 reg [1:0]duty; 23 24 initial 25 begin 26 27 //基础点阵行 28 col[0] = 8'b_0000_0000; 29 col[1] = 8'b_0001_1000; 30 col[2] = 8'b_0010_0100; 31 col[3] = 8'b_0011_1100; 32 33 col[4] = 8'b_0100_0010; 34 col[5] = 8'b_0111_1110; 35 //col[] = 8'b; 36 37 //图像 38 data[0] = 32'h0021_1200; 39 data[1] = 32'h0033_3300; 40 data[2] = 32'h0433_3340; 41 data[3] = 32'h0555_5550; 42 43 end 44 45 always @(posedge clk) 46 begin 47 cur_data = data[index]>>M1*row; 48 {dm_row, dm_colR, dm_colG} = {(enable)?~(8'h1<<row):8'hff, (^color)&(&duty)?col[cur_data]:8'h0, (color[1])?col[cur_data]:8'h0}; 49 duty = duty+~|row; 50 row = row+1; 51 end 52 53 endmodule
虽然题干中要求点阵动画,但动画需要计时器控制,如果在点阵模块里内置计时器,就会与倒计时器模块产生功能上的重复,取舍之下我选择在主模块中调用时实现点阵动画,点阵模块只需展示图像
点阵图像的存储:
1 reg [7:0]col[L-1:0];//基础点阵行L种 (L=16) 2 reg [8*M1-1:0]data[N-1:0];//图像数据 (M位基础行索引)*8行(一张图像),共N张图像 (M1=4 N=16) 3 initial 4 begin 5 6 //基础点阵行 7 col[0] = 8'b_0000_0000; 8 col[1] = 8'b_0001_1000; 9 col[2] = 8'b_0010_0100; 10 col[3] = 8'b_0011_1100; 11 12 col[4] = 8'b_0100_0010; 13 col[5] = 8'b_0111_1110; 14 //col[] = 8'b; 15 16 //图像 17 data[0] = 32'h0021_1200; 18 data[1] = 32'h0033_3300; 19 data[2] = 32'h0433_3340; 20 data[3] = 32'h0555_5550; 21 22 end
通过对题目的分析可知,我们实际上只需要存储4张图像,颜色可以另行控制(因为整张图像的颜色一样),再分析这4张图像并结合点阵显示的特性,可以发现这4张图像只由6种不同的点阵行构成
因此,我们可以只存储6种基础点阵行,然后通过基础点阵行的索引值存储图像,这样既省了很多空间,同时也方便图像的录入
存储示意图:
声明col数组大小为16位宽时,它的索引值正好占4位宽,对应一个16进制数,存储图像时可以通过‘h声明随后的数码为16进制,这样就简化了图像的录入
点阵图像的显示:
1 input clk; 2 input enable;//使能 3 input [1:0]color;//0灭 1红 2黄 3绿 4 input [M2-1:0]index;//点阵图像索引(M2=4) 5 output reg [7:0]dm_row;//点阵行 0亮 上至下:7-0 6 output reg [7:0]dm_colR;//点阵列红 1亮 左至右:7-0 7 output reg [7:0]dm_colG;//点阵列绿 1亮 8 9 reg [2:0]row;//行索引 10 reg [M1-1:0]cur_data;//当前行的数据(M1=4) 11 12 reg [1:0]duty; 13 14 always @(posedge clk) 15 begin 16 cur_data = data[index]>>M1*row; 17 {dm_row, dm_colR, dm_colG} = {(enable)?~(8'h1<<row):8'hff, (^color)&(&duty)?col[cur_data]:8'h0, (color[1])?col[cur_data]:8'h0}; 18 duty = duty+~|row; 19 row = row+1; 20 end

由于红/绿色点阵同一时刻只能输出8位的数据(同一列上数据相同),故想要显示一张图需要通过扫描显示
即输出某一行数据的时候让对应行使能端为0,其他为1,通过不断切换显示内容和对应行来显示一张完整的图像
这里我通过定义一个3位宽计数器row作为行索引,用于指示当前显示的行数;4位宽变量index为图像索引值,cur_data为4位宽变量,通过 cur_data = data[index]>>M1*row 取出当前显示行的数据;
| index | row | data>>4*row | cur_data |
| 0 | 0 | 32'h0021_1200 | 4'h0 |
| 0 | 1 | 32'h0002_1120 | 4'h0 |
| 0 | 2 | 32'h0000_2112 | 4'h2 |
| 0 | ... | ... | ... |
随后通过位拼接操作符同时为dm_row,dm_colR,dm_colG赋值(虽然其实没必要这么写,最开始以为这样写能使这三个赋值操作同时进行,但好像并不是这样)
{dm_row, dm_colR, dm_colG} = {(enable)?~(8'h1<<row):8'hff, (^color)&(&duty)?col[cur_data]:8'h0, (color[1])?col[cur_data]:8'h0};
dm_row = (enable)?~(8'h1<<row):8'hff; //题目要求中在电源开关置0时所有输出器间关闭(无输出),故这些模块均有使能信号enable
| enable | row | dm_row |
| 0 | x | 8'b1111_1111 |
| 1 | 0 | 8'b1111_1110 |
| 1 | 1 | 8'b1111_1101 |
| 1 | 2 | 8'b1111_1011 |
| 1 | ... | ... |
由于输出颜色有3种,故需要至少2位宽变量color来控制颜色,我规定 00为灭 01为红色 10为黄色 11为绿色,故红色的输出条件为(^color)即对color进行归约异或,绿色的输出条件为(color[1])
其实最初这个模块到这里就结束了,但在实际烧录以后发现点阵的红色led亮度比绿led亮很多,导致输出的黄色看上去是橘红色,由于无法增加绿色led的亮度,便通过减少红色led的占空比来降低红色的亮度
duty = duty+~|row;// ~|row:对row归并或后取反,只在row=0时输出1,否则输出0
声明一个2位宽计数器duty,每当row为0的时候加1(其实为0到7之间任何一个数都可以,因为一个循环都只出现一次)
每当duty为11时才让红色输出&(&duty),这样红色的占空比为原来的1/4,在实验板上显示正常的黄色
如果觉得这时单独显示红色的亮度太低的话,可将(^color)&(&duty)改为((color==2'b10)&(&duty))|(color==2'b01),这样在只显示红色的时候亮度为原亮度
数码管模块:
1 module seg_scan_disp(clk,enable,data,point_pos,seg_num,seg_en);//checked----ok 2 3 //实例化时依据需求更改 4 parameter DISP_NUM = 2;//显示位数 最大为16 5 parameter EN_BIT = 3;//大于等于DISP_NUM的位宽 最大为4 6 7 input clk; 8 input enable;//使能 9 input [6*DISP_NUM-1:0]data;//一个字符数据宽六位 10 input [DISP_NUM-1:0]point_pos;//小数点位置 11 output reg [7:0]seg_num; 12 output reg [DISP_NUM-1:0]seg_en; 13 14 reg [6:0]seg[63:0];//0-9 a-z 15 reg [EN_BIT-1:0]en_cnt; 16 17 initial 18 begin 19 20 //数字部分 6'h00-6'h0f 21 seg[0] = 7'b_011_1111; 22 seg[1] = 7'b_000_0110; 23 seg[2] = 7'b_101_1011; 24 seg[3] = 7'b_100_1111; 25 seg[4] = 7'b_110_0110; 26 seg[5] = 7'b_110_1101; 27 seg[6] = 7'b_111_1101; 28 seg[7] = 7'b_000_0111; 29 seg[8] = 7'b_111_1111; 30 seg[9] = 7'b_110_1111; 31 seg[10] = 7'b_111_0111;//a 32 seg[11] = 7'b_111_1100;//b 33 seg[12] = 7'b_011_1001;//c 34 seg[13] = 7'b_101_1110;//d 35 seg[14] = 7'b_111_1001;//e 36 seg[15] = 7'b_111_0001;//f 37 38 //特殊字符 6'h10-6'h1f 39 seg[6'h10] = 7'b_000_0000;//空格 40 seg[6'h11] = 7'b_000_0001;//上划线 41 seg[6'h12] = 7'b_100_0000;//破折号(减号) - 42 seg[6'h13] = 7'b_000_1000;//下划线 _ 43 44 //字母部分 6'h20-6'h39 45 seg[6'h20] = 7'b_111_0111;//a 46 seg[6'h21] = 7'b_111_1100;//b 47 seg[6'h22] = 7'b_011_1001;//c 48 seg[6'h23] = 7'b_101_1110;//d 49 seg[6'h24] = 7'b_111_1001;//e 50 seg[6'h25] = 7'b_111_0001;//f 51 seg[6'h26] = 7'b_011_1101;//g 52 53 seg[6'h27] = 7'b_111_0110;//h 54 seg[6'h28] = 7'b_001_0000;//i 55 seg[6'h29] = 7'b_000_1110;//j 56 seg[6'h2a] = 7'b_111_1010;//k 57 seg[6'h2b] = 7'b_011_1000;//l 58 seg[6'h2c] = 7'b_101_0101;//m 59 seg[6'h2d] = 7'b_101_0100;//n 60 61 seg[6'h2e] = 7'b_101_1100;//o 62 seg[6'h2f] = 7'b_111_0011;//p 63 seg[6'h30] = 7'b_110_0111;//q 64 seg[6'h31] = 7'b_101_0000;//r 65 seg[6'h32] = 7'b_110_0100;//s 66 seg[6'h33] = 7'b_111_1000;//t 67 68 seg[6'h34] = 7'b_011_1110;//u 69 seg[6'h35] = 7'b_001_1100;//v 70 seg[6'h36] = 7'b_001_1101;//w 71 seg[6'h37] = 7'b_011_0110;//x 72 seg[6'h38] = 7'b_110_1110;//y 73 seg[6'h39] = 7'b_101_0010;//z 74 75 end 76 77 always @(posedge clk) 78 begin 79 {seg_en,seg_num} = {enable?~(16'h0001<<en_cnt):16'hffff,point_pos[en_cnt],seg[data>>(6*en_cnt)]}; 80 en_cnt = (en_cnt!=DISP_NUM-1)?en_cnt + 1: 0; 81 end 82 83 endmodule
这个模块相对简单,由于实验板上的8个数码管A-P段分别并联在一起,只有使能端分开,故也需要扫描显示
七段数码管显示的内容通过data变量传入,小数点通过point_pos变量控制(虽然这里用不到),扫描显示的思路与点阵基本一致,这里就不再重复了
不过这个模块其实可以改成单个字符4位宽,只储存数字和空格就行,这里还存了一堆其他东西相当于扩展了
蜂鸣器模块:
由于题目要求中的蜂鸣器功能过于简单,取舍后选择在主模块中配合倒计时器模块使用
主模块:
1 module air_fryer( 2 input clk_1k, 3 input [5:0]btn_ori,//{暂停,复位,开始,十位加,个位加,选档} 4 input sw3,//电源开关 5 output ld15,//电源指示灯 6 output [7:0]dm_row, 7 output [7:0]dm_colR, 8 output [7:0]dm_colG, 9 output [7:0]seg_num, 10 output [7:0]seg_en,//seg 7,1,0 11 output [7:0]led, 12 output buzzer 13 ); 14 15 wire [5:0]btn; 16 reg [1:0]func; 17 reg power_pre; 18 19 //模块 20 reg [1:0]color; 21 reg [1:0]index; 22 23 reg [6*8-1:0]text; 24 25 reg cntd_start; 26 reg [16:0]start_num; 27 wire [16:0]cntd_bin; 28 wire [4*5-1:0]cntd_dec; 29 wire progress; 30 31 reg buzzer_en; 32 33 //功能 34 reg [1:0]mode;//档位 35 reg [3:0]tens;//十位 36 reg [3:0]units;//个位 37 reg [3:0]led_progress; 38 39 reg sec; 40 reg delay; 41 42 debounce #(.N(6),.CNT_BIT(5),.CNT_NUM(19)) de( 43 .clk(clk_1k), 44 .key(~btn_ori), 45 .key_pulse(btn) 46 ); 47 48 dm_draw draw( 49 .clk(clk_1k), 50 .enable(sw3), 51 .color(color), 52 .index(index), 53 .dm_row(dm_row), 54 .dm_colR(dm_colR), 55 .dm_colG(dm_colG) 56 ); 57 58 seg_scan_disp #(.DISP_NUM(8)) seg_disp( 59 .clk(clk_1k), 60 .enable(sw3), 61 .data(text), 62 .point_pos(8'b0), 63 .seg_num(seg_num), 64 .seg_en(seg_en) 65 ); 66 67 countdown_timer #(.NUM_BIT(17)) ctd( 68 .clk_1k(clk_1k),//1khz时钟 69 .start(cntd_start),//边沿触发 70 .start_num(start_num),//起始数字 71 .progress(progress),//进度输出 72 .cntd_bin(cntd_bin)//倒计时当前数(bin) 73 ); 74 75 bin2dec #(.M(5)) b2d( 76 .clk(clk_1k), 77 .bin(cntd_bin),//倒计时当前数(bin) 78 .dec(cntd_dec)//倒计时当前数(8421bcd) 79 ); 80 81 initial 82 begin 83 cntd_start = 0; 84 end 85 86 always @(posedge clk_1k) 87 begin 88 func = ((~power_pre)&sw3)|btn[4]?2'd0:func;//复位键或电源键进行复位 89 90 case(func) 91 2'd0: {led_progress,buzzer_en,units,index,color,mode,func,tens} = {2'd1,2'd1,4'd1};//鍒濆鍖? 92 2'd1://待机(选择挡位、时间) 93 begin 94 case(btn[3:0]) 95 4'b0001: mode = &mode?1:mode+1; 96 4'b0010: units = (units==4'd9)?0:units+1; 97 4'b0100: tens = (tens==4'd9)?1:tens+1; 98 4'b1000: 99 begin 100 start_num = tens*10000+units*1000; 101 case(mode) 102 2'b01: color = 2'b11; 103 2'b10: color = 2'b10; 104 2'b11: color = 2'b01; 105 endcase; 106 {led_progress,delay,func,cntd_start} = {4'd0,1'd0,2'd2,~cntd_start}; 107 end 108 endcase 109 text = {2'd0,tens,2'd0,units,6'd16,4'd0,mode,6'd18,6'd16,6'd18,6'd16}; 110 end 111 2'd2://加热 112 begin 113 text = {2'd0,cntd_dec[19:16],2'd0,cntd_dec[15:12],6'd16,4'd0,mode,6'd18,6'd16,6'd18,6'd16}; 114 if(btn[5]) {func,tens,units} = {2'd1,cntd_dec[19:12]};//鏆傚仠 115 index = index+(cntd_dec[12]^sec); 116 led_progress = led_progress+progress; 117 sec = cntd_dec[12]; 118 if(~|cntd_bin&delay) {func,start_num,cntd_start} = {2'd3,17'd3000,~cntd_start}; 119 delay = 1; 120 end 121 2'd3://完成 122 begin 123 buzzer_en = cntd_dec[10]; 124 if(~|cntd_bin&!delay) func = 2'd0; 125 {delay,led_progress} = 4'h8; 126 end 127 endcase 128 129 power_pre = sw3; 130 end 131 132 assign ld15 = sw3; 133 assign led = ~{8'hff<<led_progress}; 134 assign buzzer = clk_1k&buzzer_en; 135 136 endmodule
实例化模块时,直接把各个输出模块的使能端与sw3(电源开关)连接,配合(行132)完成基本要求1
通过题目分析时画的工作流程图可知,空气炸锅在开机后只有初始化、设定工作参数、开始加热、加热结束4种状态,于是我定义2位宽状态变量func用于指示当前炸锅的工作状态,并依此区分功能
(行88)(行129)及初始化部分实现按键复位、开机自动复位功能,完成基本要求8
case(func)实现不同状态及其下功能的切换
2’b1: 设定工作参数
1 case(btn[3:0]) 2 4'b0001: mode = &mode?1:mode+1; 3 4'b0010: units = (units==4'd9)?0:units+1; 4 4'b0100: tens = (tens==4'd9)?1:tens+1; 5 4'b1000: 6 begin 7 start_num = tens*10000+units*1000; 8 case(mode) 9 2'b01: color = 2'b11; 10 2'b10: color = 2'b10; 11 2'b11: color = 2'b01; 12 endcase; 13 {led_progress,delay,func,cntd_start} = {4'd0,1'd0,2'd2,~cntd_start}; 14 end 15 endcase 16 text = {2'd0,tens,2'd0,units,6'd16,4'd0,mode,6'd18,6'd16,6'd18,6'd16};
case(btn[3:0])实现不同按钮的功能:btn[3]开始键 btn[2]十位加 btn[1]个位加 btn[0]选档
(行2-4)(行16)完成基本要求3、基本要求4
(行5-14)实现开始键按下后设定倒计时起始数、点阵显示图像的颜色、{进度条清零,功能切换延时置零,切换至开始加热状态、发出开始计时信号},完成基本要求5前半
这里如果调整一下点阵模块的颜色对应关系可以省掉(行8-12)的case语句(变成color=mode),不过我懒得改了( ╯▽╰)
2‘b2: 开始加热
1 text = {2'd0,cntd_dec[19:16],2'd0,cntd_dec[15:12],6'd16,4'd0,mode,6'd18,6'd16,6'd18,6'd16}; 2 if(btn[5]) {func,tens,units} = {2'd1,cntd_dec[19:12]};//鏆傚仠 3 index = index+(cntd_dec[12]^sec); 4 led_progress = led_progress+progress; 5 sec = cntd_dec[12]; 6 if(~|cntd_bin&delay) {func,start_num,cntd_start} = {2'd3,17'd3000,~cntd_start}; 7 delay = 1;
(行1)显示倒计时数
(行3)(行5)实现动画,每秒切换一次图像(cntd_dec[15:12]为毫秒数千位数的8421bcd码,即表示秒数的个位,其最低位cntd_dec[12]每秒改变一次,通过cntd_dec[12]^sec使图像每秒只切换1次),完成基本要求5后半
(行4)实现进度值每经过总时间的1/8增加一次,配合完整代码的(行133)实现进度条功能,完成提高要求1
(行6-7)实现计时器归零后自动切换至加热结束状态并设置该状态持续时长,以及功能切换延时
功能切换延时:第n个clk在设定工作参数状态收到开始键按下的信号后,第n+1个clk就会进入本状态语句块,而由于模块之间通信会有一个clk的延时,倒计时器此时未完成赋值,cntd_bin仍为0,若无功能切换延时(delay)延时1个clk,则会直接自动跳转至下一状态
(行2)实现当暂停键按下时回到设定工作参数状态且输出模块暂停改变,完成提高要求2
2’b3: 加热结束
1 buzzer_en = cntd_dec[10]; 2 if(~|cntd_bin&!delay) func = 2'd0; 3 {delay,led_progress} = 4'h8;
(行1)使蜂鸣器使能信号成为周期1s占空比40%的周期信号,在加热结束状态持续的三秒内可使蜂鸣器响三声,配合(行134)实现基本要求7前半
(行2)实现功能切换延时,以及计时器归零后自动切换至初始化(初始化状态会自动切换至设定工作参数状态),实现基本要求7后半
(行3)功能切换延时,以及确保进度条在加热结束时是满的
2‘b0: 初始化
{led_progress,buzzer_en,units,index,color,mode,func,tens} = {2'd1,2'd1,4'd1};
初始化虽然是最先写的,但其内容随不同状态下要实现的功能而增加,故将其放在最后讲
如上,除档位默认置1、状态自动切换至设定工作参数状态、倒计时十位默认置1(基本要求4)以外,其他变量均置零进行重置
至此,所有要求均已实现
模块电路仿真
Quartus II 自带仿真
模块电路仿真时可以新建一个工程专门用来仿真也可以直接在原工程进行仿真
在原工程进行仿真只需将要仿真的模块设为顶层模块,然后重新编译一次即可,比如这里的点阵
新建工程只需将项目名设为要仿真的模块名,再把原工程中的对应模块复制粘贴进来,然后重新编译即可
随后的仿真操作是一样的
新建VWF波形仿真文件

插入网点/总线

在打开的窗口中点击node finder
过滤器选择pins:all
然后点击list将所有节点列出来
选中所有输入输出(有总线的都选总线)
点击ok完成选择,然后进制基数选择Hexadecimal(16进制),方便查看总线的输出情况,点击ok
此时节点便插入完成
设置各输入信号
首先应先设置一下结束时间,默认是1us,这显然不太够用,设定的结束时间应与时钟相匹配,若使用1khz时钟,2s就肯定够了,若仿真时间内时钟周期过多会使仿真时间拉的很长,而且只是单纯的浪费资源

设置1kHz时钟信号(也可以用左侧工具栏中的按钮调出时钟信号设置窗口)

此时看上去好像没有变化,实际是因为时间轴放的太大了,用缩放工具在波形图里多右键几次就能看到设完的波形

index设置为计数器信号,每32ms加1,这样可以看出红色的占空比为1/4,由于我们不需要4及以上的计数信号,将其展开把后两根线置零即可将其改为0-3循环的计数器,一个计数周期为128ms

color也设置为计数器信号,每128ms加1,这样就能看出各颜色下输出的变化,一个计数周期为512ms

enable设为半周期512ms(周期1024ms)的时钟即可覆盖所有需要查看的情况。

波形仿真
先保存波形仿真文件,名字随意,然后打开仿真工具

选中刚才保存的波形文件,然后点击start开始仿真
后续仿真结果要等下次去实验室才能加上来了,我的quartus ii会报错说证书不支持当前器件(EPM1270T144C5)
modelsim仿真
先新建一个Verilog文件写一个test bentch
1 `timescale 1us/1ps 2 3 module dm_draw_tb; 4 5 reg clk; 6 reg enable; 7 reg [1:0]index; 8 reg [1:0]color; 9 wire [7:0]dm_row; 10 wire [7:0]dm_colR; 11 wire [7:0]dm_colG; 12 13 dm_draw draw( 14 .clk(clk), 15 .enable(enable), 16 .color(color), 17 .index({2'b0,index}),//因为实际只需要index在0-3之间变化 18 .dm_row(dm_row), 19 .dm_colR(dm_colR), 20 .dm_colG(dm_colG) 21 ); 22 23 initial 24 begin 25 {clk,index,color,enable} = 1; 26 #2_000_000 $stop;//2s后结束仿真 27 end 28 29 always #500 clk = ~clk;//1kHz时钟 30 31 always #32_000 index = index+1;//每32ms切换至下一张图,计数周期128ms 32 33 always #128_000 color = color+1;//每128ms切换一次颜色,周期512ms 34 35 always #512_000 enable = ~enable; 36 37 endmodule
由于modelsim对变量的初值要求比较严格,需要在点阵模块的初始化部分加上对row和duty置零,否则输出会出现很多不确定(xxxxx)
1 module dm_draw(clk,enable,color,index,dm_row,dm_colR,dm_colG); 2 3 parameter L = 16; 4 parameter M1 = 4; 5 parameter N = 16; 6 parameter M2 = 4; 7 8 input clk; 9 input enable; 10 input [1:0]color; 11 input [M2-1:0]index; 12 output reg [7:0]dm_row; 13 output reg [7:0]dm_colR; 14 output reg [7:0]dm_colG; 15 16 reg [2:0]row; 17 reg [7:0]col[L-1:0]; 18 reg [8*M1-1:0]data[N-1:0]; 19 reg [M1-1:0]cur_data; 20 21 reg [1:0]duty; 22 23 initial 24 begin 25 row = 0; 26 duty = 0; 27 28 col[0] = 8'b_0000_0000; 29 col[1] = 8'b_0001_1000; 30 col[2] = 8'b_0010_0100; 31 col[3] = 8'b_0011_1100; 32 33 col[4] = 8'b_0100_0010; 34 col[5] = 8'b_0111_1110; 35 //col[] = 8'b; 36 37 data[0] = 32'h0021_1200; 38 data[1] = 32'h0033_3300; 39 data[2] = 32'h0433_3340; 40 data[3] = 32'h0555_5550; 41 42 end 43 44 always @(posedge clk) 45 begin 46 cur_data = data[index]>>M1*row; 47 {dm_row, dm_colR, dm_colG} = {(enable)?~(8'h1<<row):8'hff, (^color)&(&duty)?col[cur_data]:8'h0, (color[1])?col[cur_data]:8'h0}; 48 duty = duty+~|row; 49 row = row+1; 50 end 51 52 endmodule
重新编译后,开始设置仿真文件









设置完仿真文件就可以开始仿真了
仿真结果:
可以看出红色的占空比是绿色的1/4,把输出总线展开后也能直接在波形图看到图像
单独的仿真工程文件
>>Quartus II<<
>>modelsim<<
其他
>>实机效果<<
(视频中使用的实验板蜂鸣器坏了,之后可能会换个好板子录像)
>>工程文件<<
>>数码管模块文本数据转换<<
btn_ori[5] Input PIN_124 btn_ori[4] Input PIN_123 btn_ori[3] Input PIN_122 btn_ori[2] Input PIN_121 btn_ori[1] Input PIN_91 btn_ori[0] Input PIN_20 buzzer Output PIN_60 clk_1k Input PIN_18 dm_colG[7] Output PIN_45 dm_colG[6] Output PIN_44 dm_colG[5] Output PIN_43 dm_colG[4] Output PIN_42 dm_colG[3] Output PIN_41 dm_colG[2] Output PIN_40 dm_colG[1] Output PIN_39 dm_colG[0] Output PIN_38 dm_colR[7] Output PIN_22 dm_colR[6] Output PIN_21 dm_colR[5] Output PIN_16 dm_colR[4] Output PIN_15 dm_colR[3] Output PIN_14 dm_colR[2] Output PIN_13 dm_colR[1] Output PIN_12 dm_colR[0] Output PIN_11 dm_row[7] Output PIN_1 dm_row[6] Output PIN_2 dm_row[5] Output PIN_3 dm_row[4] Output PIN_4 dm_row[3] Output PIN_5 dm_row[2] Output PIN_6 dm_row[1] Output PIN_7 dm_row[0] Output PIN_8 ld15 Output PIN_137 led[7] Output PIN_73 led[6] Output PIN_74 led[5] Output PIN_75 led[4] Output PIN_76 led[3] Output PIN_77 led[2] Output PIN_78 led[1] Output PIN_79 led[0] Output PIN_80 seg_en[7] Output PIN_31 seg_en[6] Output PIN_30 seg_en[5] Output PIN_70 seg_en[4] Output PIN_69 seg_en[3] Output PIN_68 seg_en[2] Output PIN_67 seg_en[1] Output PIN_66 seg_en[0] Output PIN_63 seg_num[7] Output PIN_51 seg_num[6] Output PIN_52 seg_num[5] Output PIN_53 seg_num[4] Output PIN_55 seg_num[3] Output PIN_57 seg_num[2] Output PIN_58 seg_num[1] Output PIN_59 seg_num[0] Output PIN_62 sw3 Input PIN_131
PIN_124
PIN_123
PIN_122
PIN_121
PIN_91
PIN_20
PIN_60
PIN_18
PIN_45
PIN_44
PIN_43
PIN_42
PIN_41
PIN_40
PIN_39
PIN_38
PIN_22
PIN_21
PIN_16
PIN_15
PIN_14
PIN_13
PIN_12
PIN_11
PIN_1
PIN_2
PIN_3
PIN_4
PIN_5
PIN_6
PIN_7
PIN_8
PIN_137
PIN_73
PIN_74
PIN_75
PIN_76
PIN_77
PIN_78
PIN_79
PIN_80
PIN_31
PIN_30
PIN_70
PIN_69
PIN_68
PIN_67
PIN_66
PIN_63
PIN_51
PIN_52
PIN_53
PIN_55
PIN_57
PIN_58
PIN_59
PIN_62
PIN_131

浙公网安备 33010602011771号