北邮基于CPLD的模拟风暖式浴霸控制器的设计与实现

以下内容为北邮2020级数电大实验实验报告,蜂鸣器模块没有报错但是时灵时不灵的,删去后别的代码都可以正常运行。整个报告缺少仿真代码和仿真图片(懒得粘贴了),会贴在个人发布的资源中。

北邮的学弟学妹们有问题欢迎联系qq:1041211873

目录

一、 设计课题的任务要求

1. 实验目的

2. 实验所用仪器及元器件

3. 实验内容

二、 系统设计

设计思路

总体框图

分块设计

三、 仿真波形及波形分析

display模块

segment 模块

button模块

view1模块(换气)

view2模块(风暖)

view3模块(强暖)解读同上

view4模块(干燥动画)解读同上

view_open模块(开机动画)

时钟分频模块

sound模块

四、 源程序

1.顶层模块

2.button模块

3.segment模块

4.display模块

5.开机动画

6. view1模块

7. view2模块

8. view3模块

9. view4模块

10. 声音模块

11. 时钟分频模块(课内教程代码)

12. 按键消抖模块(课内教程代码)

五、 功能说明及资源利用情况

功能说明

资源利用情况

六、 故障及问题分析

七、 总结和结论

  • 设计课题的任务要求
  1. 实验目的
  1. 进一步掌握Verilog和Quartus II软件的使用
  2. 掌握状态机的工作原理和设计方法
  1. 实验所用仪器及元器件
  1. 计算机
  2. MAX  EPM1270T144C5
  1. 实验内容

    

  • 系统设计

  设计思路

简要设计思路为:

  1. 通过一个总体控制拨码开关SW6控制整个虚拟浴霸的开启与关闭,若处于关闭状态,则一切按键和功能都处于失效状态;若处于开启状态,则通过一个旗帜变量start记录浴霸从关到开的状态,我暂且称之为开启状态,在start = 1的情况下进行开机动画,此状态持续2s,此后start = 0,进入按键检测状态。
  2. 开启状态结束后,进入按键检测状态,此时设计一个切换变量switch,通过该变量的变化在对应case语句中进行对应的状态变化。简述为:按键按下-按键消抖产生脉冲-检测脉冲-switch变化-状态变化。由于题目中要求的是可以进行不同状态间的切换,以及通过同一个按钮的两次按下进行状态的开启与关闭,故而想到了一个解决方案:与switch的上一个状态比较,若不同则切换,若相同则关闭,并且进行相应延时(设计done,延时已完成变量,不过多赘述)等操作。流程设计图如下:

  总体框图

  分块设计

在本次设计中包含:顶层模块bath_heater,按键检测模块button,蜂鸣器模块sound,数码管模块segment,动画展示模块display,开机动画模块view_open,换气动画模块view1,风暖动画模块view2,强暖动画模块view3,干燥动画模块view4。

  • 仿真波形及波形分析

display模块

 在该模块中进行对开机动画以及四个功能模块的例化,将对应变量分配给主模块输出管脚进行动画的显示,并且完成对灯的控制以及和数码管模块的连接。

在仿真图片中可以看到,检测到on为1之后,进行开机动画的显示,switch1010为待机状态,1000为换气状态,0100为强暖状态,0010为风暖状态,0001为干燥状态。在仿真图中可以看到随着switch的状态变化,绿色列信号和红色列信号以及行信号均有变化(具体展开波形图在后续动画模块仿真分析中会有展示)。

并且在testbench的编写中,我将lighten(点亮信号)赋值为1-0-1-0,故可以观察到,在开机动画后,light的第六位即led6有对应1-0-1-0的变化。

segment 模块

  该仿真模拟了延时状态下的数码管变化,可以看见,在delay = 4以及 delay = 2的情况下数码管均有不同状态的显示,并且当延时完成时,输出变量done处产生了两个脉冲表示延时完成。

  在顶层模块的模块例化以及参数传递下,当segment模块中的done = 1后,该变量传入button模块中,作为条件判断改变switch状态值,使点阵停止,进入待机状态。

button模块

  在仿真波形中可以看见pulse变量输入窄小脉冲激励代表实际板子中按键按下产生的脉冲,检验到相应脉冲pulse[3],pulse[2],pulse[1],pulse[0]后switch进行相应的变化,代表不同的功能动画状态,并且图中演示了如果按下两下(该脉冲为两个脉冲叠加,故明显粗一些),delay值变为010,即进行2s倒计时,在顶层模块中将该值传入segment模块进行数码管倒计时显示,倒计时结束后进入倒计时状态,switch变为1111。

   其中lighten对应的是灯是否点亮,其对应控制脉冲为pulse[4],在图片中可以看到第一次脉冲产生后,lighten为1,再次产生脉冲(即第二次按下按键)lighten变为0。

  view1模块(换气)

通过行扫描在点阵上显示动画,观察第一个行扫描信号与最后一个行扫描信号之间的绿色列信号,易看出点阵动画为题目所需,行扫描信号为0有效,绿色列信号为1有效。

  

view2模块(风暖)

  在此模块中有红色列信号与绿色列信号,故行信号不便截图给出。我在管脚处配置的列信号管脚与仿真中的高低位相反,故应该倒着看,(当时上板烧制的时候发现图形正好反了,更改整个代码过于复杂,故直接更改管脚配置中的引脚。)便与规定图形相同。(中心对称图形无差距。)

view3模块(强暖)解读同上

view4模块(干燥动画)解读同上

view_open模块(开机动画)

  在检测到浴霸开机后,由仿真波形可以看见绿色列信号与红色列信号均为0-1-0-1,完成四帧闪烁,之后变为全0,点阵全灭,row为全1,行扫描信号一直有效。

   Seg信号一直有效,即所有数码管都处于同一状态,segnum从0100-1111-0100-1111,完成全八点亮到default值控制数码管全灭的过程,完成四帧闪烁。

   Light信号的变化可以看书十六个led灯也为有规律的四帧闪烁。

时钟分频模块

  传入例化参数N=11,WIDTH=4,实现11分频。

  sound模块

将pulse设置为四位宽的窄脉冲,检验到脉冲,则产生不同频率的时钟信号赋值给beep蜂鸣器,产生频率不同的一段声音。

  • 源程序

1.顶层模块

module bath_heater(

   input clk,//输入选择1MHz的时钟

input on,//为sw6键状态,是否工作

input rst,//设置为按键0

input BTN7,BTN6,BTN5,BTN4,BTN3,//分别实现换气、风暖、强暖、干燥功能

output beep,//蜂鸣器

output  [15:0]light,//LED6为照明灯

output  [7:0] col_r,//红色列

output  [7:0] col_g,//绿色列

output  [7:0] row,//行

output  [7:0] Seg, //选择哪个数码管输出,低电平有效

output  [7:0] Seg_pin//数码管受控端,高电平有效

);

wire [7:0]seg;//选择八个数码管哪一个输出端

wire [3:0]segpin;//选择数码管引脚端

wire done ;//计时完成

wire [2:0]delay ;//是否进行延迟,延迟几秒

wire lighten;//灯是否亮

wire [3:0] switch;//选择播放哪个动画,全1则处于待机状态.对应按键6543

wire start;

wire pulse7,pulse6,pulse5,pulse4,pulse3;

//按键消抖

debounce #(.N(5)) de1(

.clk(clk),

.rst(!rst),

.key({!BTN7,!BTN6,!BTN5,!BTN4,!BTN3}),

.key_pulse({pulse7,pulse6,pulse5,pulse4,pulse3})

);

//声音模块

 sound so(

 .rst(rst),

 .on(on),

 .clk(clk),

 .pulse3(pulse3),.pulse4(pulse4),.pulse5(pulse5),.pulse6(pulse6),.pulse7(pulse7),

 .beep(beep)

 );

 //按键检测

 button b1(

.clk(clk),

.on(on),

.pulse3(pulse3),.pulse4(pulse4),.pulse5(pulse5),.pulse6(pulse6),.pulse7(pulse7),

.lighten(lighten),

.switch(switch),

.delay(delay),

.done(done)

 );

 //数码管模块

 segment s1(

 .on(on),

 .seg(seg),

 .start(start),

 .delay(delay),

 .clk(clk),

 .segpin(segpin),

 .done(done),

 .Seg(Seg),

 .Seg_pin(Seg_pin)

 );

 //动画、功能展示模块

 display di(

 .clk(clk),

 .on(on),

 .lighten(lighten),

 .switch(switch),

 .start(start),

 .light(light),

 .col_r(col_r),

 .col_g(col_g),

 .row(row),

 .segpin(segpin),

 .seg(seg)

);

endmodule

2.button模块

module button (

input clk,

input on,//是否开启,作为判断最优先条件

input pulse3,pulse4,pulse5,pulse6,pulse7,

input done,//由segment模块输出的变量done表示延时是否完成

output reg lighten,//控制灯是否点亮变量

output reg [3:0] switch,//切换变量

output reg [2:0] delay//记录延时时间,若为0则为default值全灭

);

initial begin

 lighten <= 0;

 switch <= 4'b1111;

 delay <= 0;

end

always@(posedge clk)begin //检测功能按键

 if(on)begin//跟当前状态比较,若输出下一状态与当前状态相同,则进入待机/关闭

    if(pulse7)begin

   if(lighten) lighten <= 0;

   else lighten <= 1;

 end

    else if(pulse3)begin

   if(switch == 4'b0001) switch <= 4'b1111;//进入待机状态

   else switch <= 4'b0001;    

    end

    else if(pulse4)begin //延时4s

   if(switch == 4'b0010) delay <= 4;

   else switch <= 4'b0010;

 end

    else if(pulse5)begin //延时2s

      if(switch == 4'b0100) delay <= 2;

   else switch <= 4'b0100;

 end

    else if(pulse6)begin

     if(switch == 4'b1000) switch <= 4'b1111;//进入待机状态

  else switch <= 4'b1000;

    end

 else if(done)begin

   delay <= 0;

switch <= 4'b1111;//进入待机状态

 end

  end

 else begin//关机则重置

 switch <= 4'b1111;

 lighten <= 0;

 delay <= 0;

 end

end

endmodule

3.segment模块

module segment(

input on,

input start,//开机动画是否开启

input clk,

input [2:0]delay,

input [3:0]segpin,

input [7:0]seg,

output reg done,//接受button模块传来的延时信号delay,将是否完成延时输出

output reg [7:0]Seg,

output reg [7:0]Seg_pin

);

reg [3:0]segnum = 4'b1111;//segnum控制数码管的显示

reg [19:0] delaycnt = 0;

initial begin

  done <= 0;

end

always@(segnum)begin

  case(segnum)

  4'd1:  Seg_pin = 8'b0000_0110;

  4'd2:  Seg_pin = 8'b0101_1011;

  4'd3:  Seg_pin = 8'b0100_1111;

  4'd4:  Seg_pin = 8'b0110_0110;

  4'd8:   Seg_pin = 8'b0111_1111;//全8闪烁,不需要小数点

  default: Seg_pin = 8'b0000_0000;//全灭

  endcase

end

always@(negedge clk)begin //数码管控制模块

 if(on)begin

   if(start)begin

  segnum <= segpin;

  Seg <= seg;

end

   else if(delay != 0)begin //不为0则

     if(segnum != 0)begin

    Seg <= 8'b1111_0111;

    if(delaycnt == 1000000)begin //延时到1s

   delaycnt <= 0;

   if(segnum == 1)begin segnum <= 0;done <= 1; end

else segnum <= segnum-1;

 end

    else delaycnt <= delaycnt + 1;

  end

  else begin segnum <= delay;done <= 0; end

end

   else begin//若不是开机动画,且不需要延时(待机动画)

 Seg <= 8'b1111_1111;

 done <= 0;

    delaycnt <= 0;

 segnum <= 0;

   end

 end//若关机

 else begin

   segnum <= 4'b1111;//为default值

   Seg <= 8'b1111_1111;

 end

end

endmodule

4.display模块

module display(

   input clk,

   input on,

input lighten,

input [3:0]switch,

output start,

   output reg [15:0]light,//LED6为照明灯

output reg [7:0] col_r,//红色列

output reg [7:0] col_g,//绿色列

output reg [7:0] row,//行

output  [3:0]segpin,

output  [7:0]seg

);

   wire [7:0] col_go,col_ro,row_open;

wire [15:0]light_rec1;

   //换气、风暖、强暖、干燥功能动画存储变量

wire [7:0] col_g1,col_r1;//记录第一个动画绿色列信号

wire [7:0] col_g2,col_r2;//记录第二个动画绿色、红色列信号

wire [7:0] col_g3,col_r3;//记录第三个动画绿色列信号

wire [7:0] col_g4,col_r4;//记录第四个动画绿色列信号

wire [7:0] row1,row2,row3,row4;//记录行信号

//开机动画

view_open open(

   .on(on),

   .clk(clk),

.col_r(col_ro),

.col_g(col_go),

.light(light_rec1),

.segnum(segpin),

   .Seg(seg),

.row(row_open),

.start(start)

);

//换气动画

 view1 v1(

 .clk(clk),

 .col_r(col_r1),

 .col(col_g1),

 .row(row1)

 );

 //风暖

 view2 v2(

 .clk(clk),

 .col_r(col_r2),

 .col_g(col_g2),

 .row(row2)

 );

//强暖

 view3 v3(

 .clk(clk),

 .col_r(col_r3),

 .col_g(col_g3),

 .row(row3)

 );

//干燥

view4 v4(

 .clk(clk),

 .col_g(col_g4),

 .col_r(col_r4),

 .row(row4)

 );

always@(posedge clk)//此处通过检测按键,利用条件判断输出哪个动画

begin

 if(on)begin//若开机

   if(start)begin //通过是否

    col_r <= col_ro;

 col_g <= col_go;

 light <= light_rec1;

 row <= row_open;

   end

   else begin//功能动画

 if(lighten) light <= 16'b0000_0000_0100_0000;

 else light <= 16'b0000_0000_0000_0000;

 case (switch)//利用switch的改变检测按键变化,从而进行相应赋值显示相应动画

 4'b1111:begin col_g <= 8'b0000_0000;col_r <= 8'b0000_0000;row <= 8'b1111_1111; end

 4'b0001:begin col_g <= col_g4;col_r <= col_r4;row <= row4; end

 4'b0010:begin col_g <= col_g3;col_r <= col_r3;row <= row3; end

 4'b0100:begin col_g <= col_g2;col_r <= col_r2;row <= row2; end

 4'b1000:begin col_g <= col_g1;col_r <= col_r1;row <= row1; end

endcase

  end

 end

 else begin//若关机

  light <= 16'b0000_0000_0000_0000;

  col_g <= 8'b0000_0000;

  col_r <= 8'b0000_0000;

  row <= 8'b1111_1111;

 end

end

endmodule

5.开机动画

 module view_open(

   input clk,

input on,

output reg [7:0] col_r,

output reg [7:0] col_g,

output reg [15:0]light,

output reg [3:0] segnum,//数码管受控端

output reg [7:0] Seg, //选择哪个数码管输出

output reg [7:0] row,

output reg start

);//开机动画

 reg flag = 1;

 reg [2:0]cnt = 0;

 reg [18:0]period = 0;//记录周期,当达到0.5s时切换下一周期

//开关打开后,点阵全红、全绿交替全亮以2Hz闪烁,八个数码管亮“8”、16个发光二极同时亮以2Hz频率闪烁

//2s后,即红绿交替闪烁两次进入待机状态,点阵、数码管和二极管全灭

initial begin

start <= 1;

end

always @(posedge clk)begin

 if(start)begin

  case(flag)

  2'd1:begin

   light <= 16'b1111_1111_1111_1111;

   col_r <= 8'b1111_1111;

   col_g <= 8'b0000_0000;

   row <= 8'b0000_0000;

   segnum <= 4'd8;

   Seg <= 8'b0000_0000;

  end

  

  2'd0: begin

   light <= 16'b0000_0000_0000_0000;

   col_g <= 8'b1111_1111;

   col_r <= 8'b0000_0000;

   row <= 8'b0000_0000;

   segnum <= 4'b1111;//default全灭

   Seg <= 8'b0000_0000;

   end

  endcase

 end

 else begin

   light <= 16'b0000_0000_0000_0000;

   col_g <= 8'b0000_0000;

   col_r <= 8'b0000_0000;

   row <= 8'b1111_1111;

   segnum <= 0;

   Seg <= 8'b0000_0000;

 end

end

always @(posedge clk)begin//现在需要再次重置start

 if(on)begin//若开机

  if(period == 500000) begin

        if(cnt == 3)begin//如果完成四次转换,则开机动画结束

          start <= 0;//动画结束

        end

  else begin

         cnt <= cnt + 1;//完成一次转换

      flag <= ~flag;//完成帧的转换

      period <= 0;

 end

  end

  else begin

period <= period+1;

  end

 end //若关机

 else begin

  start <= 1;

  cnt <= 0;

  period <= 0;

  flag <= 1;

 end

end

endmodule

  1. view1模块

module view1 //第一个动画

(  input clk,

output reg [7:0] col,//列

output reg [7:0] col_r,//列

output reg [7:0] row//行

);

   reg [2:0]cnt = 0;//每一帧画面的行扫描记录,初始值为default,点阵全灭

reg flag = 0;//每一帧画面是否切换

reg [18:0]period = 0;//记录周期,当达到0.5s时切换下一周期

always@(negedge clk) begin

if(cnt == 8)cnt <= 0;

else cnt<=cnt+1;

if(period == 500000) begin //若使用仿真,该数字有点大,可改小为500

flag <= ~flag;//完成帧的转换

period<=0;//此处period=0.5/t,t为时钟的一个周期,暂设为7,则两帧动画无缝衔接,便于在仿真中观测波形

end

else begin

period<=period+1;

end

end

always@(cnt)

begin

  

//第一帧

if(!flag) case(cnt)//从第七行开始扫描,即最上面一行

3'd0:begin col <= 8'b11100001;col_r <= 8'b00000000;row <= 8'b11111110;end

3'd1:begin col <= 8'b01100011;col_r <= 8'b00000000;row <= 8'b11111101;end

3'd2:begin col <= 8'b00100111;col_r <= 8'b00000000;row <= 8'b11111011;end

3'd3:begin col <= 8'b00011000;col_r <= 8'b00000000;row <= 8'b11110111;end

3'd4:begin col <= 8'b00011000;col_r <= 8'b00000000;row <= 8'b11101111;end

3'd5:begin col <= 8'b11100100;col_r <= 8'b00000000;row <= 8'b11011111;end

3'd6:begin col <= 8'b11000110;col_r <= 8'b00000000;row <= 8'b10111111;end

3'd7:begin col <= 8'b10000111;col_r <= 8'b00000000;row <= 8'b01111111;end

default : begin row <= 8'b11111111;col_r <= 8'b00000000;col <= 8'b00000000;//点阵全灭

end

endcase

else case(cnt)

3'd0:begin col <= 8'b00010000;col_r <= 8'b00000000;row <= 8'b11111110;end

3'd1:begin col <= 8'b00011000;col_r <= 8'b00000000;row <= 8'b11111101;end

3'd2:begin col <= 8'b00010000;col_r <= 8'b00000000;row <= 8'b11111011;end

3'd3:begin col <= 8'b01011111;col_r <= 8'b00000000;row <= 8'b11110111;end

3'd4:begin col <= 8'b11111010;col_r <= 8'b00000000;row <= 8'b11101111;end

3'd5:begin col <= 8'b00001000;col_r <= 8'b00000000;row <= 8'b11011111;end

3'd6:begin col <= 8'b00011000;col_r <= 8'b00000000;row <= 8'b10111111;end

3'd7:begin col <= 8'b00001000;col_r <= 8'b00000000;row <= 8'b01111111;end

default : begin row <= 8'b11111111;col_r <= 8'b00000000;col <= 8'b00000000;//点阵全灭

end

endcase

  end

endmodule

  1. view2模块

module view2(

   input clk,

output reg [7:0] col_r,

output reg [7:0] col_g,

output reg [7:0] row

);

   reg [2:0] cnt = 0;//每一帧画面的行扫描记录,初始值为default,点阵全灭

reg [2:0]flag = 0;//每一帧画面的切换

reg [18:0]period = 0;//记录周期,当达到0.5s时切换下一周期

always@(negedge clk) begin

if(cnt == 8) cnt<=0;

else cnt<=cnt+1;

if(period == 500000) begin//仿真时改为500显示全部图形

period <= 0;

flag <= (flag+1)%4;

end

else period<=period+1;

end

always@(cnt)

begin

case(flag)

//第一帧动画

3'd0:case(cnt)

3'd0:begin col_g <= 8'b11100000;col_r <= 8'b00000111;row <= 8'b01111111;end

3'd1:begin col_g <= 8'b11000000;col_r <= 8'b00000011;row <= 8'b10111111;end

3'd2:begin col_g <= 8'b10100000;col_r <= 8'b00000101;row <= 8'b11011111;end

3'd3:begin col_g <= 8'b00010000;col_r <= 8'b00001000;row <= 8'b11101111;end

3'd4:begin col_g <= 8'b00001000;col_r <= 8'b00010000;row <= 8'b11110111;end

3'd5:begin col_g <= 8'b00000101;col_r <= 8'b10100000;row <= 8'b11111011;end

3'd6:begin col_g <= 8'b00000011;col_r <= 8'b11000000;row <= 8'b11111101;end

3'd7:begin col_g <= 8'b00000111;col_r <= 8'b11100000;row <= 8'b11111110;end

endcase

 3'd1:case(cnt)

//第二帧动画

3'd0:begin col_g <= 8'b00001000;col_r <= 8'b00000000;row <= 8'b01111111;end

3'd1:begin col_g <= 8'b00011100;col_r <= 8'b00000000;row <= 8'b10111111;end

3'd2:begin col_g <= 8'b00001000;col_r <= 8'b01000000;row <= 8'b11011111;end

3'd3:begin col_g <= 8'b00001000;col_r <= 8'b11110010;row <= 8'b11101111;end

3'd4:begin col_g <= 8'b00010000;col_r <= 8'b01001111;row <= 8'b11110111;end

3'd5:begin col_g <= 8'b00010000;col_r <= 8'b00000010;row <= 8'b11111011;end

3'd6:begin col_g <= 8'b00111000;col_r <= 8'b00000000;row <= 8'b11111101;end

3'd7:begin col_g <= 8'b00010000;col_r <= 8'b00000000;row <= 8'b11111110;end

endcase

3'd2:case(cnt)

//第三帧动画

3'd0:begin col_r <= 8'b11100000;col_g <= 8'b00000111;row <= 8'b01111111;end

3'd1:begin col_r <= 8'b11000000;col_g <= 8'b00000011;row <= 8'b10111111;end

3'd2:begin col_r <= 8'b10100000;col_g <= 8'b00000101;row <= 8'b11011111;end

3'd3:begin col_r <= 8'b00010000;col_g <= 8'b00001000;row <= 8'b11101111;end

3'd4:begin col_r <= 8'b00001000;col_g <= 8'b00010000;row <= 8'b11110111;end

3'd5:begin col_r <= 8'b00000101;col_g <= 8'b10100000;row <= 8'b11111011;end

3'd6:begin col_r <= 8'b00000011;col_g <= 8'b11000000;row <= 8'b11111101;end

3'd7:begin col_r <= 8'b00000111;col_g <= 8'b11100000;row <= 8'b11111110;end

endcase

  3'd3:case(cnt)

//第四帧动画

3'd0:begin col_r <= 8'b00001000;col_g <= 8'b00000000;row <= 8'b01111111;end

3'd1:begin col_r <= 8'b00011100;col_g <= 8'b00000000;row <= 8'b10111111;end

3'd2:begin col_r <= 8'b00001000;col_g <= 8'b01000000;row <= 8'b11011111;end

3'd3:begin col_r <= 8'b00001000;col_g <= 8'b11110010;row <= 8'b11101111;end

3'd4:begin col_r <= 8'b00010000;col_g <= 8'b01001111;row <= 8'b11110111;end

3'd5:begin col_r <= 8'b00010000;col_g <= 8'b00000010;row <= 8'b11111011;end

3'd6:begin col_r <= 8'b00111000;col_g <= 8'b00000000;row <= 8'b11111101;end

3'd7:begin col_r <= 8'b00010000;col_g <= 8'b00000000;row <= 8'b11111110;end

endcase

endcase

end

endmodule

  1. view3模块

module view3(

   input clk,

output reg [7:0] col_r,

output reg [7:0] col_g,

output reg [7:0] row

);

   reg [2:0] cnt = 0;//每一帧画面的行扫描记录,初始值为default,点阵全灭

reg [2:0]flag = 0;//每一帧画面的切换

reg [18:0]period = 0;//记录周期,当达到0.5s时切换下一周期

always@(negedge clk) begin

if(cnt == 8) cnt<=0;

else cnt<=cnt+1;

if(period == 500000) begin

period <= 0;

flag <= (flag+1)%4;

end

else period<=period+1;

end

always@(cnt)

begin

case(flag)

//第一帧动画

3'd0:case(cnt)

3'd0:begin col_g <= 8'b11100000;col_r <= 8'b11100111;row <= 8'b01111111;end

3'd1:begin col_g <= 8'b11000000;col_r <= 8'b11000011;row <= 8'b10111111;end

3'd2:begin col_g <= 8'b10100000;col_r <= 8'b10100101;row <= 8'b11011111;end

3'd3:begin col_g <= 8'b00010000;col_r <= 8'b00011000;row <= 8'b11101111;end

3'd4:begin col_g <= 8'b00001000;col_r <= 8'b00011000;row <= 8'b11110111;end

3'd5:begin col_g <= 8'b00000101;col_r <= 8'b10100101;row <= 8'b11111011;end

3'd6:begin col_g <= 8'b00000011;col_r <= 8'b11000011;row <= 8'b11111101;end

3'd7:begin col_g <= 8'b00000111;col_r <= 8'b11100111;row <= 8'b11111110;end

endcase

 3'd1:case(cnt)

//第二帧动画

3'd0:begin col_g <= 8'b00001000;col_r <= 8'b00001000;row <= 8'b01111111;end

3'd1:begin col_g <= 8'b00011100;col_r <= 8'b00011100;row <= 8'b10111111;end

3'd2:begin col_g <= 8'b00001000;col_r <= 8'b01001000;row <= 8'b11011111;end

3'd3:begin col_g <= 8'b00001000;col_r <= 8'b11111010;row <= 8'b11101111;end

3'd4:begin col_g <= 8'b00010000;col_r <= 8'b01011111;row <= 8'b11110111;end

3'd5:begin col_g <= 8'b00010000;col_r <= 8'b00010010;row <= 8'b11111011;end

3'd6:begin col_g <= 8'b00111000;col_r <= 8'b00111000;row <= 8'b11111101;end

3'd7:begin col_g <= 8'b00010000;col_r <= 8'b00010000;row <= 8'b11111110;end

endcase

3'd2:case(cnt)

//第三帧动画

3'd0:begin col_g <= 8'b00000111;col_r <= 8'b11100111;row <= 8'b01111111;end

3'd1:begin col_g <= 8'b00000011;col_r <= 8'b11000011;row <= 8'b10111111;end

3'd2:begin col_g <= 8'b00000101;col_r <= 8'b10100101;row <= 8'b11011111;end

3'd3:begin col_g <= 8'b00001000;col_r <= 8'b00011000;row <= 8'b11101111;end

3'd4:begin col_g <= 8'b00010000;col_r <= 8'b00011000;row <= 8'b11110111;end

3'd5:begin col_g <= 8'b10100000;col_r <= 8'b10100101;row <= 8'b11111011;end

3'd6:begin col_g <= 8'b11000000;col_r <= 8'b11000011;row <= 8'b11111101;end

3'd7:begin col_g <= 8'b11100000;col_r <= 8'b11100111;row <= 8'b11111110;end

endcase

  3'd3:case(cnt)

//第四帧动画

3'd0:begin col_r <= 8'b00001000;col_g <= 8'b00000000;row <= 8'b01111111;end

3'd1:begin col_r <= 8'b00011100;col_g <= 8'b00000000;row <= 8'b10111111;end

3'd2:begin col_r <= 8'b01001000;col_g <= 8'b01000000;row <= 8'b11011111;end

3'd3:begin col_r <= 8'b11111010;col_g <= 8'b11110010;row <= 8'b11101111;end

3'd4:begin col_r <= 8'b01011111;col_g <= 8'b01001111;row <= 8'b11110111;end

3'd5:begin col_r <= 8'b00010010;col_g <= 8'b00000010;row <= 8'b11111011;end

3'd6:begin col_r <= 8'b00111000;col_g <= 8'b00000000;row <= 8'b11111101;end

3'd7:begin col_r <= 8'b00010000;col_g <= 8'b00000000;row <= 8'b11111110;end

endcase

endcase

end

endmodule

  1. view4模块

module view4(

   input clk,

output reg [7:0] col_r,

output reg [7:0] col_g,

output reg [7:0] row

);

   reg [2:0] cnt = 0;//每一帧画面的行扫描记录,初始值为default,点阵全灭

reg [2:0]flag = 0;//每一帧画面的切换

reg [18:0]period = 0;//记录周期,当达到0.5s时切换下一周期

always@(negedge clk) begin

if(cnt == 8) cnt<=0;

else cnt<=cnt+1;

if(period == 500000) begin

period <= 0;

flag <= (flag+1)%4;

end

else period <= period+1;

end

always@(cnt)

begin

case(flag)

//第一帧动画

3'd0:case(cnt)

3'd0:begin col_g <= 8'b11100001;col_r <= 8'b11100000;row <= 8'b11111110;end

3'd1:begin col_g <= 8'b01100011;col_r <= 8'b01100000;row <= 8'b11111101;end

3'd2:begin col_g <= 8'b00100111;col_r <= 8'b00100000;row <= 8'b11111011;end

3'd3:begin col_g <= 8'b00011000;col_r <= 8'b00010000;row <= 8'b11110111;end

3'd4:begin col_g <= 8'b00011000;col_r <= 8'b00001000;row <= 8'b11101111;end

3'd5:begin col_g <= 8'b11100100;col_r <= 8'b00000100;row <= 8'b11011111;end

3'd6:begin col_g <= 8'b11000110;col_r <= 8'b00000110;row <= 8'b10111111;end

3'd7:begin col_g <= 8'b10000111;col_r <= 8'b00000111;row <= 8'b01111111;end

endcase

 3'd1:case(cnt)

//第二帧动画

3'd0:begin col_g <= 8'b00010000;col_r <= 8'b00000000;row <= 8'b11111110;end

3'd1:begin col_g <= 8'b00011000;col_r <= 8'b00000000;row <= 8'b11111101;end

3'd2:begin col_g <= 8'b00010000;col_r <= 8'b00000000;row <= 8'b11111011;end

3'd3:begin col_g <= 8'b01011111;col_r <= 8'b01001111;row <= 8'b11110111;end

3'd4:begin col_g <= 8'b11111010;col_r <= 8'b11110010;row <= 8'b11101111;end

3'd5:begin col_g <= 8'b00001000;col_r <= 8'b00000000;row <= 8'b11011111;end

3'd6:begin col_g <= 8'b00011000;col_r <= 8'b00000000;row <= 8'b10111111;end

3'd7:begin col_g <= 8'b00001000;col_r <= 8'b00000000;row <= 8'b01111111;end

endcase

3'd2:case(cnt)

//第三帧动画

3'd0:begin col_g <= 8'b11100001;col_r <= 8'b00000001;row <= 8'b11111110;end

3'd1:begin col_g <= 8'b01100011;col_r <= 8'b00000011;row <= 8'b11111101;end

3'd2:begin col_g <= 8'b00100111;col_r <= 8'b00000111;row <= 8'b11111011;end

3'd3:begin col_g <= 8'b00011000;col_r <= 8'b00001000;row <= 8'b11110111;end

3'd4:begin col_g <= 8'b00011000;col_r <= 8'b00010000;row <= 8'b11101111;end

3'd5:begin col_g <= 8'b11100100;col_r <= 8'b11100000;row <= 8'b11011111;end

3'd6:begin col_g <= 8'b11000110;col_r <= 8'b11000000;row <= 8'b10111111;end

3'd7:begin col_g <= 8'b10000111;col_r <= 8'b10000000;row <= 8'b01111111;end

endcase

3'd3:case(cnt)

//第四帧动画

3'd0:begin col_g <= 8'b00010000;col_r <= 8'b00010000;row <= 8'b11111110;end

3'd1:begin col_g <= 8'b00011000;col_r <= 8'b00011000;row <= 8'b11111101;end

3'd2:begin col_g <= 8'b00010000;col_r <= 8'b00010000;row <= 8'b11111011;end

3'd3:begin col_g <= 8'b01011111;col_r <= 8'b00010000;row <= 8'b11110111;end

3'd4:begin col_g <= 8'b11111010;col_r <= 8'b00001000;row <= 8'b11101111;end

3'd5:begin col_g <= 8'b00001000;col_r <= 8'b00001000;row <= 8'b11011111;end

3'd6:begin col_g <= 8'b00011000;col_r <= 8'b00011000;row <= 8'b10111111;end

3'd7:begin col_g <= 8'b00001000;col_r <= 8'b00001000;row <= 8'b01111111;end

endcase

endcase

end

endmodule

  1. 声音模块

module sound(

input rst,

input on,

input clk,

input pulse3,pulse4,pulse5,pulse6,pulse7,

output reg beep

);

reg flag = 0;

reg [18:0]timecnt = 0;

wire clk1,clk2,clk3,clk4,clk5;

reg [2:0] temp;

divide  #(.WIDTH(10),.N(1911/2))  u1 (        

.clk(clk),

.rst_n(rst),//该实验板和小脚丫的rst按键有效值不同!需要取反,否则无效                   

.clkout(clk1)

);   

divide #(.WIDTH(10),.N(1702/2)) d2 (         

.clk(clk),

.rst_n(rst),                  

.clkout(clk2)

);  

divide #(.WIDTH(10),.N(1516/2)) d3 (         

.clk(clk),

.rst_n(rst),                   

.clkout(clk3)

);

divide #(.WIDTH(10),.N(1431/2)) d4 (         

.clk(clk),

.rst_n(rst),                   

.clkout(clk4)

);

divide #(.WIDTH(10),.N(1275/2)) d5 (         

.clk(clk),

.rst_n(rst),                   

.clkout(clk5)

);

always@(posedge clk)begin

  case (temp)

   3'd0:beep <= 0;

   3'd1:beep <= clk1;

3'd2:beep <= clk2;

3'd3:beep <= clk3;

3'd4:beep <= clk4;

3'd5:beep <= clk5;

  endcase

end

always@(posedge clk)begin //检测功能按键

 if(on)begin

    if(pulse7)begin

   temp <= 1;

flag <= 1;

 end

    else if(pulse3)begin

   temp <= 2;

flag <= 1;

    end

    else if(pulse4)begin

   temp <= 3;

flag <= 1;

 end

    else if(pulse5)begin

   temp <= 4;

flag <= 1;

 end

    else if(pulse6)begin

   temp <= 5;

flag <= 1;

    end

 else begin

  if(flag)begin

   if(timecnt == 500000)begin temp <= 0;timecnt <= 0; flag <= 0; end

else timecnt <= timecnt + 1;

  end

 end

 /*else temp <= 0;always@(*)是时钟沿到来和变量发生变化的时候均会扫描,所以不能采用该语句*/

  end

 else begin//关机则重置

 temp <= 0;

 end

end

endmodule

  1. 时钟分频模块(课内教程代码)

module divide (clk,rst_n,clkout);

   input clk,rst_n;                       //输入信号,其中clk连接到FPGA

   output clkout;                          //输出信号,可以连接到LED观察分频的时钟

    //parameter是verilog里常数语句

parameter WIDTH = 3;             //计数器的位数,计数的最大值为 2**WIDTH-1

parameter N = 5;             //分频系数,请确保 N < 2**WIDTH-1,否则计数会溢出

reg [WIDTH-1:0] cnt_p,cnt_n;     //cnt_p为上升沿触发时的计数器,cnt_n为下降沿触发时的计数器

reg clk_p = 0,clk_n;     //clk_p为上升沿触发时分频时钟,clk_n为下降沿触发时分频时钟

//上升沿触发时计数器的控制

always @ (posedge clk or negedge rst_n )         //posedge和negedge是verilog表示信号上升沿和下降沿

                                                         //当clk上升沿来临或者rst_n变低的时候执行一次always里的语句

begin

if(!rst_n)

cnt_p<=0;

else if (cnt_p==(N-1))

cnt_p<=0;

else cnt_p<=cnt_p+1;             //计数器一直计数,当计数到N-1的时候清零,这是一个模N的计数器

end

         //上升沿触发的分频时钟输出,如果N为奇数得到的时钟占空比不是50%;如果N为偶数得到的时钟占空比为50%

    always @ (posedge clk or negedge rst_n)

begin

if(!rst_n)

clk_p<=0;

else if (cnt_p<(N>>1))          //N>>1表示右移一位,相当于除以2去掉余数

clk_p<=0;

else

clk_p<=1;               //得到的分频时钟正周期比负周期多一个clk时钟

   end

        //下降沿触发时计数器的控制        

always @ (negedge clk or negedge rst_n)

begin

if(!rst_n)

cnt_n<=0;

else if (cnt_n==(N-1))

cnt_n<=0;

else cnt_n<=cnt_n+1;

end

        //下降沿触发的分频时钟输出,和clk_p相差半个时钟

always @ (negedge clk)

begin

if(!rst_n)

clk_n<=0;

else if (cnt_n<(N>>1))  

clk_n<=0;

else

clk_n<=1;                //得到的分频时钟正周期比负周期多一个clk时钟

end

        assign clkout = (N==1)?clk:(N[0])?(clk_p&clk_n):clk_p;      //条件判断表达式

                                                                    //当N=1时,直接输出clk

                                                                    //当N为偶数也就是N的最低位为0,N(0)=0,输出clk_p

                                                                    //当N为奇数也就是N最低位为1,N(0)=1,输出clk_p&clk_n。正周期多所以是相与

endmodule     

  1. 按键消抖模块(课内教程代码)

module debounce (clk,rst,key,key_pulse);

        parameter N  =  1;                      //要消除的按键的数量

    input    clk;

        input    rst;

        input [N-1:0]   key;                        //输入的按键

    output  [N-1:0]   key_pulse;                  //按键动作产生的脉冲

        reg     [N-1:0]   key_rst_pre;                //定义一个寄存器型变量存储上一个触发时的按键值

        reg     [N-1:0]   key_rst;                    //定义一个寄存器变量储存储当前时刻触发的按键值

        wire    [N-1:0]   key_edge;                   //检测到按键由低到高变化是产生一个高脉冲

        //利用非阻塞赋值特点,将两个时钟触发时按键状态存储在两个寄存器变量中

        always @(posedge clk  or  negedge 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 [15:0]   cnt;                       //产生延时所用的计数器,系统时钟1MHz,要延时20ms左右时间,至少需要16位计数器     

        //产生20ms延时,当检测到key_edge有效时计数器清零开始计数

        always @(posedge clk or negedge rst)

           begin

             if(!rst)

                cnt <= 16'h0;

             else if(key_edge)

                cnt <= 16'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  negedge rst)

          begin

             if (!rst)

                 key_sec <= {N{1'b1}};                

             else if (cnt==16'hffff)

                 key_sec <= key;  

          end

       always @(posedge clk  or  negedge rst)

          begin

             if (!rst)

                 key_sec_pre <= {N{1'b1}};

             else                   

                 key_sec_pre <= key_sec;             

         end      

       assign  key_pulse = key_sec_pre & (~key_sec);     

endmodule

  • 功能说明及资源利用情况

功能说明

该工程实现了基础浴霸的风扇功能,可以通过按键控制进行对风扇功能的开启、切换以及停止,并且可以实现对灯亮灭的控制。

此下介绍几个主要模块的功能。

button模块主要实现了对按钮脉冲的检测,从而使风扇进入不同的状态,即点阵动画以及灯的状态传递。

display模块主要是对从button模块中传过来的状态进行展示,即对板子引脚的赋值,最终实现呈现在实验板上的效果,主要是控制动画的展示、切换、停止以及灯的亮灭。

segment模块控制数码管的状态传递以及数码管的显示,并且完成延时功能,将是否完成延时的状态返回给display模块,从而实现对延时动画的控制。

资源利用情况

使用引脚65/116(56%)。

总逻辑宏单元655/1270(52%)。

当时验收的时候没有删去一些不需要的模块(想做提高功能)以及多余代码和变量声明,故逻辑宏单元近1000位,之后进行删改并且减少了时钟分频例化模块,减小计时变量位宽(一开始都非常豪迈的设成了32位),故最终逻辑宏单元减少为655。

  • 故障及问题分析

1.点阵动画只显示第一行

考虑问题产生的原因:当烧制板子的时候动画始终只能出来第一行,便考虑是不是case语句或者是计数器出了问题

问题根源:在模块中引入了rst变量,但是在实际分配引脚时并未分配该管脚,处于悬空状态,便一直重置,导致动画显示不完全。

解决方案:配置rst引脚。

2.拆分模块后功能不正确

考虑问题产生的原因:其实这是一个我在写代码过程中经常碰见的一个问题,就是各个模块之间的连接问题。

问题根源:(1)位宽设置不正确,在模块接口时,声明变量时若没有设置正确的位宽,则会导致只有一位0和1的变化;

(2)模块接口中output接口只能设置为wire型变量;

(3)在拆分模块的时候,要实现规划好每个模块的功能,并且对模块之间的连接有一个初步的构思,不要遗忘引脚的分配。

3.按键控制动画不灵敏

考虑问题产生的原因:是按键消抖模块出了问题,或者是条件判断语句出错。

问题根源:使用的按键消抖为原来max10系列的芯片的教程代码,该芯片固定时钟为12MHz,而本实验中采用的频率是1MHz,相对而言按键消抖时间过长,无法产生有效脉冲。

解决方案:将原来延时的位宽缩小至16位,便能正确产生按键消抖效果。

解决了按键消抖问题后,觉得应该是always块的判断条件出了问题,再经过细心调试后,发现是always@(*)与always@(posedge clk)判断语句执行的问题。在Verilog中always@(*)语句的意思是always模块中的任何一个输入信号或电平发生变化时,该语句下方的模块将被执行。

以下为查阅资料得:

1、always语句有两种触发方式。第一种是电平触发,例如always @(a or b or c),a、b、c均为变量,当其中一个发生变化时,下方的语句将被执行。

2、第二种是沿触发,例如always @(posedge clk or negedge rstn),即当时钟处在上升沿或下降沿时,语句被执行。

3、而对于always@(*),意思是以上两种触发方式都包含在内,任意一种发生变化都会触发该语句。

4.在板子上实验现象正确时,仿真还是无法出现正确波形

考虑问题产生的原因:给予变量的数值不正确,或者是代码逻辑错误等

问题根源:(1)本工程中许多模块相互传递参数,则在仿真时也要仔细设定正确的延时以及对应的参变量,否则无法出现正确的波形。

(2)在仿真中本来应该是以稳定电平形式出现的波形出现时钟类型震荡,是因为在仿真中给的参数不对,例如在button模块,条件判断是以窄小脉冲形式,在仿真中应该给予窄脉冲形式的激励

5.数码管延时问题

此处将数码管出现的问题整合

(1)动画要播放完整后才进入待机状态/动画不进行延时

 if(segnum == 0) begin segnum <= delay; done <=0;end//在delay不为0的状态下,如果segnum为0则进行倒计时

语句如上,陷入死循环。原来的意图是,如果delay不为0,则将数码管的初始数值赋为delay之后在时钟的作用下进行减法计数器。更改条件判断语句即可。注意在if-else语句,尤其是多种选择嵌套的时候,一定要理顺优先级关系,否则及其容易出现逻辑错误,有时通过仿真和现象都很难排查。

(2)数码管一直进入倒计时死循环212121,且无法切入进待机状态,考虑是条件判断分支出了问题。

解决方法:因为按键按下,并且经过按键消抖后会产生一个暂时性脉冲,判断延时完成的条件分支不可以放在检测按键脉冲的大条件分支下。

(3)将数码管控制部分写成模块后,又无法进行切换,由于之前功能完好,故怀疑是模块内部出现问题,定义输入/输出/reg/wire变量一定记得位宽!!!!

  • 总结和结论

总结

在整个实验过程中,我加深了对verilog语言的理解以及状态机的构思,并且能够熟练编写工程,编写testbench,运用modelsim-altera进行仿真。

这个数电实验相对于数电课内实验来说复杂得多,在烧录一个工程的时候能写出近十个模块也是我第一次经历的。

其实浴霸相对而言是一个比较简单的题目,基础的逻辑思维设计我其实一开始花个两三个小时就比较轻松的写了出来,但是最花时间的是debug的过程,一开始并不擅长使用testbench,所以debug对我来说就是一个烧板子看现象的过程,但是这次的代码牵扯到的引脚很多,一次写完所有模块的代码看现象,即一次性正确的可能性实在是太低。由于实验板不在手边,我一次性酣畅淋漓地写完了所有代码,但是第一次烧录到板子上的时候什么现象也没有,不由得傻眼,但是后来就把代码一部分一部分地注释掉,逐段调试。

写工程的本质还是语言,最重要的还是逻辑语言。我还记得第一次验收模块电路的时候老师看一眼我的代码说if-else其实是容易出错的。我在整个实验过程中都深刻体会到了这一点,而且quartus没有c++那么简洁的debug机制,着实是让人头疼。

其实一开始对老师所说的调整红绿光比例从而调出黄色点阵是很感兴趣的,这部分其实是我花时间最多的一个地方,查阅资料发现调整红绿光占空比即可实现黄色光,在静态点阵中可以实现黄色光,但是本工程中点阵动画为动态的,还没有调试出来。由于期末周和实验巧妙撞车,衡量之下还是决定专心复习。

该实验的难点我可以总结为两点:1.代码量大,看完题目后最好能在心里先构思一个模块分块以及各个功能代码实现的基础逻辑。并且由于是第一次接触到这种对我而言的大工程,想要思路中断一次性写完是比较难的,如果没有遵循一个主线去做的话,每次写实验的时候都要重新思考并且延续之前的思路,其实是一个低效率的做法,并且很容易在之前的基础上出错。2.逻辑易错,其实代码整体思路不难,但是在各个模块之间的数据传递以及条件判断循环之间比较容易出错,而且一旦出错需要花费大量时间去改错,也有可能跟我不擅长使用这个软件有关系,不太擅长利用仿真去查错。

结论:1.在建立工程的时候一定要先对题目分析,列出状态图以及流程图,大概规划出自己需要几个模块以及代码的大致框架;2.在对模块进行分割的时候,要注意各个模块之间的逻辑连接情况。

  • 1
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值