北邮数字系统设计实验课 基于 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
管脚分配(仅管脚所在列)

 

如果哪里不明白,需要补充说明的欢迎在评论中提出

posted @ 2022-11-19 19:14  DeltaTUI  阅读(4199)  评论(40)    收藏  举报