明德扬吴老师 发表于 2020-7-22 15:05:58

【每周FPGA案例】至简设计系列_闹钟 编号:001600000064

至简设计系列_闹钟
本案例的编号为:001600000064如果有疑问,请按编号在下面贴子查找答案:MDY案例交流【汇总贴】_FPGA-明德扬科教 (mdy-edu.com)
本文为明德扬原创及录用文章,转载请注明出处!闹钟【上板现象】
闹钟在MP801开发板效果
https://www.bilibili.com/video/BV1Af4y117H4?p=12
闹钟在点拨开发板上板效果
https://www.bilibili.com/video/BV1Af4y117H4?p=13


1.1 总体设计1.1.1 概述数字时钟是采用数字电路技术实现时、分、秒计时显示的装置,可以用数字同时显示时,分,秒的精确时间并实现准确校时,具备体积小、重量轻、抗干扰能力强、对环境要求高、高精确性、容易开发等特性,在工业控制系统、智能化仪器表、办公自动化系统等诸多领域取得了极为广泛的应用,诸如自动报警、按时自动打铃、时间程序自动控制、定时广播、自定启闭路灯、定时开关烘箱、通断动力设备、甚至各种定时电器的自动启用等。与传统表盘式机械时钟相比,数字时钟具有更高的准确性和直观性,由于没有机械装置,其使用寿命更长。1.1.2 设计目标设计一款具有闹钟功能的数字时钟,具体要求如下1、用8个数码管实现,四个一组,每组有分钟和秒。左边一组是时间显示,右边一组用来做闹钟时间。2、当左边时间等于右边时,蜂鸣器响5秒。3、闹钟时间和显示时间均可通过3个按键设置。设置方法:按下按键1,时钟暂停,跳到设置时间状态,再按下按键1,回到正常状态。通过按键2,选择要设置的位置,初始设置秒个位,按一下,设置秒十位,再按下,设置分个位,以此类推,循环设置。通过按键3,设置数值,按一下数值加1,如果溢出则重新变为0。1.1.3 系统结构框图系统结构框图如下所示:结构图共分两个,如果使用的开发板上是矩阵键盘的时候,对应的结构图是图一。如果使用的开发板上是普通按键的时候,对应的结构图是图二。
图一

图二1.1.4模块功能Ø按键检测模块实现功能1、将外来异步信号打两拍处理,将异步信号同步化。2、实现20ms按键消抖功能,并输出有效按键信号。Ø矩阵键盘模块实现功能1、将外来异步信号打两拍处理,将异步信号同步化。2、实现20ms按键消抖功能。3、实现矩阵键盘的按键检测功能,并输出有效按键信号。Ø时间产生模块实现功能1、产生显示时间数据。2、产生闹钟时间数据,3、根据接收到的不同的按键信号,产生暂停、开启、设置时间的功能。Ø数码管显示模块实现功能1、对接收到的时间数据进行译码。Ø蜂鸣器模块实现功能1、将接受到的显示时间数据与闹钟时间数据进行比较,控制蜂鸣器的开启。
1.1.5顶层信号

信号名接口方向定义
clk输入系统时钟,50Mhz
rst_n输入低电平复位信号
Key输入3位按键信号,开发板按键为矩阵键盘时,不需要该信号
Key_col输入4位矩阵键盘列信号,默认高电平,开发板按键为普通按键时,不需要该信号
Key_row输出4位矩阵键盘行信号,默认低电平,开发板按键为普通按键时,不需要该信号
Segment输出8位数码管段选信号
Seg_sel输出8位数码管位选信号
beep输出1位蜂鸣器控制信号

1.1.6参考代码
下面是使用普通按键的顶层代码:modulealarm_clock(
    clk       ,
    rst_n   ,
    key       ,
    segment   ,
    seg_sel   ,
    beep
);
input                   clk             ;
input                   rst_n         ;
input             key             ;
output          segment         ;
output          seg_sel         ;
output                  beep            ;

wire              segment         ;
wire              seg_sel         ;
wire                  beep            ;
wire              xs_sec_low      ;
wire              xs_sec_high   ;
wire              xs_min_low      ;
wire              xs_min_high   ;
wire              sec_low         ;
wire              sec_high      ;
wire              min_low         ;
wire              min_high      ;
wire              counter         ;
wire              key_vld         ;
wire                  flag_set      ;

key_module u0(
             .clk         (clk         ),
             .rst_n       (rst_n       ),
             .key_in      (key         ),
             .key_vld   (key_vld   )
             );
time_data u1(
             .clk         (clk         ),
             .rst_n       (rst_n       ),
             .key_vld   (key_vld   ),
             .flag_set    (flag_set    ),
             .end_counter (end_counter ),
             .sec_low   (sec_low   ),
             .sec_high    (sec_high    ),
             .min_low   (min_low   ),
             .min_high    (min_high    ),
             .xs_sec_low(xs_sec_low),
             .xs_sec_high (xs_sec_high ),
             .xs_min_low(xs_min_low),
             .xs_min_high (xs_min_high )
             );
beep u2(
             .clk         (clk         ),
             .rst_n       (rst_n       ),
             .flag_set    (flag_set    ),
             .end_counter (end_counter ),
             .beep      (beep      ),
             .sec_low   (sec_low   ),
             .sec_high    (sec_high    ),
             .min_low   (min_low   ),
             .min_high    (min_high    ),
             .xs_sec_low(xs_sec_low),
             .xs_sec_high (xs_sec_high ),
             .xs_min_low(xs_min_low),
             .xs_min_high (xs_min_high )
             );
seg_disp u3(
             .clk         (clk         ),
             .rst_n       (rst_n       ),
             .segment_data({xs_min_high,xs_min_low,xs_sec_high,xs_sec_low,min_high,min_low,sec_high,sec_low}),
             .segment   (segment   ),
             .seg_sel   (seg_sel   )
);


endmodule


下面是使用矩阵键盘的顶层代码:modulealarm_clock_jvzhen(
    clk       ,
    rst_n   ,
    key_col   ,
    key_row   ,
    segment   ,
    seg_sel   ,
    beep
);
input                   clk             ;
input                   rst_n         ;
input             key_col         ;
output          key_row         ;
output          segment         ;
output          seg_sel         ;
output                  beep            ;

wire              segment         ;
wire              seg_sel         ;
wire                  beep            ;
wire              xs_sec_low      ;
wire              xs_sec_high   ;
wire              xs_min_low      ;
wire              xs_min_high   ;
wire              sec_low         ;
wire              sec_high      ;
wire              min_low         ;
wire              min_high      ;
wire                  end_counter   ;
wire              key_vld         ;
wire                  flag_set      ;
wire              key_out         ;

key_scanu0(
             .clk         (clk         ),
             .rst_n       (rst_n       ),
             .key_col   (key_col   ),
             .key_row   (key_row   ),
             .key_en      (key_vld   )
             );
time_data u1(
             .clk         (clk         ),
             .rst_n       (rst_n       ),
             .key_vld   (key_vld   ),
             .flag_set    (flag_set    ),
             .end_counter (end_counter ),
             .sec_low   (sec_low   ),
             .sec_high    (sec_high    ),
             .min_low   (min_low   ),
             .min_high    (min_high    ),
             .xs_sec_low(xs_sec_low),
             .xs_sec_high (xs_sec_high ),
             .xs_min_low(xs_min_low),
             .xs_min_high (xs_min_high )
             );
beep u2(
             .clk         (clk         ),
             .rst_n       (rst_n       ),
             .flag_set    (flag_set    ),
             .end_counter (end_counter ),
             .beep      (beep      ),
             .sec_low   (sec_low   ),
             .sec_high    (sec_high    ),
             .min_low   (min_low   ),
             .min_high    (min_high    ),
             .xs_sec_low(xs_sec_low),
             .xs_sec_high (xs_sec_high ),
             .xs_min_low(xs_min_low),
             .xs_min_high (xs_min_high )
             );
seg_disp u3(
             .clk         (clk         ),
             .rst_n       (rst_n       ),
             .segment_data({xs_min_high,xs_min_low,xs_sec_high,xs_sec_low,min_high,min_low,sec_high,sec_low}),
             .segment   (segment   ),
             .seg_sel   (seg_sel   )
);


endmodul


1.2 按键检测模块设计1.2.1 接口信号

信号接口方向定义
clk输入系统时钟
rst_n输入低电平复位信号
key_in输入按键输入
key_vld输出按键按下指示信号

1.2.2 设计思路在前面的案例中已经有按键检测的介绍,所以这里不在过多介绍,详细介绍请看下方链接:【每周FPGA案例】至简设计系列_按键控制数字时钟
1.2.3参考代码
使用明德扬的计数器模板,可以很快速很熟练地写出按键消抖模块。always@(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
      cnt <= 20'b0;
    end
    else if(add_cnt)begin
      if(end_cnt)
            cnt <= 20'b0;
      else
            cnt <= cnt + 1'b1;
    end
    else begin
      cnt <= 0;
    end
end

assign add_cnt = flag_add==1'b0 && (&key_in_ff1==0);
assign end_cnt = add_cnt && cnt == TIME_20MS - 1;




always@(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
      flag_add <= 1'b0;
    end
    else if(end_cnt)begin
      flag_add <= 1'b1;
    end
    else if(&key_in_ff1==1)begin
      flag_add <= 1'b0;
    end
end




always@(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
      key_in_ff0 <= {{KEY_W}{1'b1}};
      key_in_ff1 <= {{KEY_W}{1'b1}};
    end
    else begin
      key_in_ff0 <= key_in    ;
      key_in_ff1 <= key_in_ff0;
    end
end




always@(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
      key_vld <= 0;
    end
    else if(end_cnt)begin
      key_vld <= ~key_in_ff1;
    end
    else begin
      key_vld <= 0;
    end
end



endmodule

1.3 矩阵键盘模块设计1.3.1接口信号
信号接口方向定义
clk输入系统时钟
rst_n输入低电平复位信号
key_col输入矩阵键盘列输入信号
Key_row输出矩阵键盘行输出信号
Key_en输出按键按下指示信号

1.3.2 设计思路
在前面的案例中已经有矩阵键盘的介绍,所以这里不在过多介绍,详细介绍请看下方链接:http://fpgabbs.com/forum.php?mod=viewthread&tid=310
1.3.3参考代码
always@(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
      key_col_ff0 <= 4'b1111;
      key_col_ff1 <= 4'b1111;
    end
    else begin
      key_col_ff0 <= key_col    ;
      key_col_ff1 <= key_col_ff0;
    end
end


always @(posedge clk or negedge rst_n) begin
    if (rst_n==0) begin
      shake_cnt <= 0;
    end
    else if(add_shake_cnt) begin
      if(end_shake_cnt)
            shake_cnt <= 0;
      else
            shake_cnt <= shake_cnt+1 ;
   end
end
assign add_shake_cnt = key_col_ff1!=4'hf;
assign end_shake_cnt = add_shake_cnt&& shake_cnt == TIME_20MS-1 ;


always@(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
      state_c <= CHK_COL;
    end
    else begin
      state_c <= state_n;
    end
end

always@(*)begin
    case(state_c)
      CHK_COL: begin
                     if(col2row_start )begin
                         state_n = CHK_ROW;
                     end
                     else begin
                         state_n = CHK_COL;
                     end
               end
      CHK_ROW: begin
                     if(row2del_start)begin
                         state_n = DELAY;
                     end
                     else begin
                         state_n = CHK_ROW;
                     end
               end
      DELAY :begin
                     if(del2wait_start)begin
                         state_n = WAIT_END;
                     end
                     else begin
                         state_n = DELAY;
                     end
               end
      WAIT_END: begin
                     if(wait2col_start)begin
                         state_n = CHK_COL;
                     end
                     else begin
                         state_n = WAIT_END;
                     end
                  end
       default: state_n = CHK_COL;
    endcase
end
assign col2row_start = state_c==CHK_COL&& end_shake_cnt;
assign row2del_start = state_c==CHK_ROW&& row_index==3 && end_row_cnt;
assign del2wait_start= state_c==DELAY    && end_row_cnt;
assign wait2col_start= state_c==WAIT_END && key_col_ff1==4'hf;

always@(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
      key_row <= 4'b0;
    end
    else if(state_c==CHK_ROW)begin
      key_row <= ~(1'b1 << row_index);
    end
    else begin
      key_row <= 4'b0;
    end
end





always @(posedge clk or negedge rst_n) begin
    if (rst_n==0) begin
      row_index <= 0;
    end
    else if(add_row_index) begin
      if(end_row_index)
            row_index <= 0;
      else
            row_index <= row_index+1 ;
   end
   else if(state_c!=CHK_ROW)begin
       row_index <= 0;
   end
end
assign add_row_index = state_c==CHK_ROW && end_row_cnt;
assign end_row_index = add_row_index&& row_index == 4-1 ;


always @(posedge clk or negedge rst_n) begin
    if (rst_n==0) begin
      row_cnt <= 0;
    end
    else if(add_row_cnt) begin
      if(end_row_cnt)
            row_cnt <= 0;
      else
            row_cnt <= row_cnt+1 ;
   end
end
assign add_row_cnt = state_c==CHK_ROW || state_c==DELAY;
assign end_row_cnt = add_row_cnt&& row_cnt == 16-1 ;



always@(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
      key_col_get <= 0;
    end
    else if(state_c==CHK_COL && end_shake_cnt ) begin
      if(key_col_ff1==4'b1110)
            key_col_get <= 0;
      else if(key_col_ff1==4'b1101)
            key_col_get <= 1;
      else if(key_col_ff1==4'b1011)
            key_col_get <= 2;
      else
            key_col_get <= 3;
    end
end


always@(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
      key_out <= 0;
    end
    else if(state_c==CHK_ROW && end_row_cnt)begin
      key_out <= {row_index,key_col_get};
    end
    else begin
      key_out <= 0;
    end
end

always@(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
      key_vld <= 1'b0;
    end
    else if(state_c==CHK_ROW && end_row_cnt && key_col_ff1==1'b0)begin
      key_vld <= 1'b1;
    end
    else begin
      key_vld <= 1'b0;
    end
end


always@(*)begin
    if(rst_n==1'b0)begin
      key_en = 0;
    end
    else if(key_vld && key_out==0)begin
      key_en = 4'b0001;
    end
    else if(key_vld && key_out==1)begin
      key_en = 4'b0010;
    end
    else if(key_vld && key_out==2)begin
      key_en = 4'b0100;
    end
    else begin
      key_en = 0;
    end
end


endmodule

1.4 时间产生模块设计1.4.1接口信号
信号接口方向定义
clk输入系统时钟
rst_n输入低电平复位信号
key_vld输入按键按下指示信号
end_counter输出时钟计数器结束条件,表示1秒的时间
Xs_sec_low输出显示的秒低位计数器
Xs_sec_high输出显示的秒高位计数器
Xs_min_low输出显示的分低位计数器
Xs_min_high输出显示的分高位计数器
Sec_low输出闹钟的秒低位计数器
Sec_high输出闹钟的秒高位计数器
Min_low输出闹钟的分低位计数器
Min_high输出闹钟的分高位计数器
Flag_set输出设置状态指示信号

1.4.2设计思路
根据题目功能要求可知,要显示的时间就是在完整的数字时钟的基础上,减少了时的高位和低位的显示,再介绍架构之前,先了解一下本模块其他几个信号的作用。设置状态指示信号flag_set:该信号初始状态为低电平,表示模块处于正常工作状态,当按下按键key1时,设置状态指示信号进行翻转,变为高电平,表示进入到设置状态。设置位计数器sel_cnt:该计数器表示要设置的位,初始状态为0,表示可以设置闹钟的秒低位,当其为1时表示可以设置闹钟的秒的高位,按照这样的顺序依次类推,当其为7的时候,表示可以设置显示时间的分高位。加一条件为key_vld==1’b1,表示按下按键key2的时候加一;结束条件为8,显示时间的四个数码管加上闹钟的四个数码管共8个,所以数8个就清零。由此可提出5个计数器的架构,如下图所示:
该架构由5个计数器组成:时钟计数器counter、秒低位计数器xs_sec_low、秒高位计数器xs_sec_high、分低位计数器xs_min_low、分高位计数器xs_min_high。时钟计数器counter:用于计算1秒的时钟个数,加一条件为flag_set==1'b0,表示刚上电时开始计数,key1按下之后,进入设置模式,停止计数,再按下又重新开始计数;结束条件为50000000,表示数到1秒就清零。秒低位计数器xs_sec_low:用于对1秒进行计数,加一条件为(sel_cnt==5-1&& set_en) || end_counter,表示在设置状态下可通过按键key3来控制加一,或者在正常状态时数到1秒就加1;结束条件为10,表示数到10秒就清零。秒高位计数器xs_sec_high:用于对10秒进行计数,加一条件为(sel_cnt==6-1&& set_en) || end_xs_sec_low,表示在设置状态下可通过按键key3来控制加一,或者在正常状态时数到10秒就加1;结束条件为6,表示数到60秒就清零。分低位计数器xs_min_low:用于对1分进行计数,加一条件为(sel_cnt==7-1&& set_en) || end_xs_sec_high,表示在设置状态下可通过按键key3来控制加一,或者在正常状态时数到1分就加1;结束条件为10,表示数到10分就清零。分高位计数器xs_min_high:用于对10分进行计数,加一条件为(sel_cnt==8-1&& set_en) || end_xs_min_low,表示在设置状态下可通过按键key3来控制加一,或者在正常状态时数到10分就加1;结束条件为6,表示数到60分就清零。上面介绍了显示时间的计数器架构,下面我们来思考一下闹钟部分的架构。我们都知道闹钟的工作原理,它本身不会自动计数,需要我们手动设置。根据本设计的功能要求,有四个数码管来显示设置的闹钟秒的高低位和分的高低位,因此我们提出四个计数器组成的架构,这四个计数器相互独立,互不干涉,结构图如下:
该架构由4个计数器组成:秒低位计数器sec_low、秒高位计数器sec_high、分低位计数器min_low、分高位计数器min_high。秒低位计数器sec_low:用于对闹钟秒的低位进行计数,加一条件为sel_cnt==1-1 && set_en,表示在设置状态下通过按键key3来控制加一;结束条件为10,表示最大能设置为9,超过之后便清零。秒高位计数器sec_high:用于对闹钟秒的高位进行计数,加一条件为sel_cnt==2-1 && set_en,表示在设置状态下可通过按键key3来控制加一;结束条件为6,表示最大能设置为5,超过之后便清零。分低位计数器min_low:用于对闹钟分的低位进行计数,加一条件为sel_cnt==3-1 && set_en,表示在设置状态下可通过按键key3来控制加一;结束条件为10,表示最大能设置为9,超过之后便清零。分高位计数器min_high:用于对闹钟分高位进行计数,加一条件为sel_cnt==4-1 && set_en,表示在设置状态下可通过按键key3来控制加一;结束条件为6,表示最大能设置为5,超过之后便清零。

1.4.3参考代码
使用明德扬的计数器模板,可以很快速很熟练地写出时间产生模块。always@(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
      flag_set<=1'b0;
    end
    else if(key_vld==1'b1)begin
      flag_set<=~flag_set;
    end
    else begin
      flag_set<=flag_set;
    end
end


always @(posedge clk or negedge rst_n) begin
    if (rst_n==0) begin
      sel_cnt <= 0;
    end
    else if(add_sel_cnt) begin
      if(end_sel_cnt)
            sel_cnt <= 0;
      else
            sel_cnt <= sel_cnt+1 ;
   end
end
assign add_sel_cnt = key_vld==1'b1;
assign end_sel_cnt = add_sel_cnt&& sel_cnt == 8-1 ;




always@(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
      set_en<=1'b0;
    end
    else if(flag_set==1'b1 && key_vld==1'b1)begin
      set_en<=1'b1;
    end
    else begin
      set_en<=1'b0;
    end
end


always @(posedge clk or negedge rst_n) begin
    if (rst_n==0) begin
      counter <= 0;
    end
    else if(add_counter) begin
      if(end_counter)
            counter <= 0;
      else
            counter <= counter+1 ;
   end
end
assign add_counter = flag_set==1'b0;
assign end_counter = add_counter&& counter == 26'd5000_0000-1;


always @(posedge clk or negedge rst_n) begin
    if (rst_n==0) begin
      sec_low <= 0;
    end
    else if(add_sec_low) begin
      if(end_sec_low)
            sec_low <= 0;
      else
            sec_low <= sec_low+1 ;
   end
end
assign add_sec_low = sel_cnt==1-1 && set_en;
assign end_sec_low = add_sec_low&& sec_low == 10-1 ;


always @(posedge clk or negedge rst_n) begin
    if (rst_n==0) begin
      sec_high <= 0;
    end
    else if(add_sec_high) begin
      if(end_sec_high)
            sec_high <= 0;
      else
            sec_high <= sec_high+1 ;
   end
end
assign add_sec_high = sel_cnt==2-1 && set_en;
assign end_sec_high = add_sec_high&& sec_high == 6-1 ;


always @(posedge clk or negedge rst_n) begin
    if (rst_n==0) begin
      min_low <= 0;
    end
    else if(add_min_low) begin
      if(end_min_low)
            min_low <= 0;
      else
            min_low <= min_low+1 ;
   end
end
assign add_min_low = sel_cnt==3-1 && set_en;
assign end_min_low = add_min_low&& min_low == 10-1 ;

always @(posedge clk or negedge rst_n) begin
    if (rst_n==0) begin
      min_high <= 0;
    end
    else if(add_min_high) begin
      if(end_min_high)
            min_high <= 0;
      else
            min_high <= min_high+1 ;
   end
end
assign add_min_high = sel_cnt==4-1 && set_en;
assign end_min_high = add_min_high&& min_high == 6-1 ;




always @(posedge clk or negedge rst_n) begin
    if (rst_n==0) begin
      xs_sec_low <= 0;
    end
    else if(add_xs_sec_low) begin
      if(end_xs_sec_low)
            xs_sec_low <= 0;
      else
            xs_sec_low <= xs_sec_low+1 ;
   end
end
assign add_xs_sec_low = (sel_cnt==5-1 && set_en) || end_counter;
assign end_xs_sec_low = add_xs_sec_low && xs_sec_low == 10-1 ;




always @(posedge clk or negedge rst_n) begin
    if (rst_n==0) begin
      xs_sec_high <= 0;
    endalways@(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
      key_col_ff0 <= 4'b1111;
      key_col_ff1 <= 4'b1111;
    end
    else begin
      key_col_ff0 <= key_col    ;
      key_col_ff1 <= key_col_ff0;
    end
end


always @(posedge clk or negedge rst_n) begin
    if (rst_n==0) begin
      shake_cnt <= 0;
    end
    else if(add_shake_cnt) begin
      if(end_shake_cnt)
            shake_cnt <= 0;
      else
            shake_cnt <= shake_cnt+1 ;
   end
end
assign add_shake_cnt = key_col_ff1!=4'hf;
assign end_shake_cnt = add_shake_cnt&& shake_cnt == TIME_20MS-1 ;


always@(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
      state_c <= CHK_COL;
    end
    else begin
      state_c <= state_n;
    end
end

always@(*)begin
    case(state_c)
      CHK_COL: begin
                     if(col2row_start )begin
                         state_n = CHK_ROW;
                     end
                     else begin
                         state_n = CHK_COL;
                     end
               end
      CHK_ROW: begin
                     if(row2del_start)begin
                         state_n = DELAY;
                     end
                     else begin
                         state_n = CHK_ROW;
                     end
               end
      DELAY :begin
                     if(del2wait_start)begin
                         state_n = WAIT_END;
                     end
                     else begin
                         state_n = DELAY;
                     end
               end
      WAIT_END: begin
                     if(wait2col_start)begin
                         state_n = CHK_COL;
                     end
                     else begin
                         state_n = WAIT_END;
                     end
                  end
       default: state_n = CHK_COL;
    endcase
end
assign col2row_start = state_c==CHK_COL&& end_shake_cnt;
assign row2del_start = state_c==CHK_ROW&& row_index==3 && end_row_cnt;
assign del2wait_start= state_c==DELAY    && end_row_cnt;
assign wait2col_start= state_c==WAIT_END && key_col_ff1==4'hf;

always@(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
      key_row <= 4'b0;
    end
    else if(state_c==CHK_ROW)begin
      key_row <= ~(1'b1 << row_index);
    end
    else begin
      key_row <= 4'b0;
    end
end





always @(posedge clk or negedge rst_n) begin
    if (rst_n==0) begin
      row_index <= 0;
    end
    else if(add_row_index) begin
      if(end_row_index)
            row_index <= 0;
      else
            row_index <= row_index+1 ;
   end
   else if(state_c!=CHK_ROW)begin
       row_index <= 0;
   end
end
assign add_row_index = state_c==CHK_ROW && end_row_cnt;
assign end_row_index = add_row_index&& row_index == 4-1 ;


always @(posedge clk or negedge rst_n) begin
    if (rst_n==0) begin
      row_cnt <= 0;
    end
    else if(add_row_cnt) begin
      if(end_row_cnt)
            row_cnt <= 0;
      else
            row_cnt <= row_cnt+1 ;
   end
end
assign add_row_cnt = state_c==CHK_ROW || state_c==DELAY;
assign end_row_cnt = add_row_cnt&& row_cnt == 16-1 ;



always@(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
      key_col_get <= 0;
    end
    else if(state_c==CHK_COL && end_shake_cnt ) begin
      if(key_col_ff1==4'b1110)
            key_col_get <= 0;
      else if(key_col_ff1==4'b1101)
            key_col_get <= 1;
      else if(key_col_ff1==4'b1011)
            key_col_get <= 2;
      else
            key_col_get <= 3;
    end
end


always@(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
      key_out <= 0;
    end
    else if(state_c==CHK_ROW && end_row_cnt)begin
      key_out <= {row_index,key_col_get};
    end
    else begin
      key_out <= 0;
    end
end

always@(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
      key_vld <= 1'b0;
    end
    else if(state_c==CHK_ROW && end_row_cnt && key_col_ff1==1'b0)begin
      key_vld <= 1'b1;
    end
    else begin
      key_vld <= 1'b0;
    end
end


always@(*)begin
    if(rst_n==1'b0)begin
      key_en = 0;
    end
    else if(key_vld && key_out==0)begin
      key_en = 4'b0001;
    end
    else if(key_vld && key_out==1)begin
      key_en = 4'b0010;
    end
    else if(key_vld && key_out==2)begin
      key_en = 4'b0100;
    end
    else begin
      key_en = 0;
    end
end


endmodule;

1.5 数码管显示模块设计1.5.1接口信号
信号接口方向定义
clk输入系统时钟
rst_n输入低电平复位信号
Segment_data输入时间数据
Segment输出数码管段选信号
Seg_sel输出数码管位选信号


1.5.2设计思路
在前面的案例中已经有数码管显示的介绍,所以这里不在过多介绍,详细介绍请看下方链接:http://fpgabbs.com/forum.php?mod=viewthread&tid=399
1.5.3参考代码
always @(posedge clk or negedge rst_n) begin
    if (rst_n==0) begin
      delay <= 0;
    end
    else if(add_delay) begin
      if(end_delay)
            delay <= 0;
      else
            delay <= delay+1 ;
   end
end
assign add_delay = 1;
assign end_delay = add_delay&& delay == 2000-1 ;




always @(posedge clk or negedge rst_n) begin
    if (rst_n==0) begin
      delay_time <= 0;
    end
    else if(add_delay_time) begin
      if(end_delay_time)
            delay_time <= 0;
      else
            delay_time <= delay_time+1 ;
   end
end
assign add_delay_time = end_delay;
assign end_delay_time = add_delay_time&& delay_time == 8-1 ;


assign segment_tmp= segment_data[(1+delay_time)*4-1 -:4];
always@(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
      segment <= ZERO;
    end
    else begin
      case(segment_tmp)
            4'd0:segment <= ZERO;
            4'd1:segment <= ONE;
            4'd2:segment <= TWO;
            4'd3:segment <= THREE;
            4'd4:segment <= FOUR ;
            4'd5:segment <= FIVE ;
            4'd6:segment <= SIX;
            4'd7:segment <= SEVEN;
            4'd8:segment <= EIGHT;
            4'd9:segment <= NINE ;
            default:begin
                segment <= segment;
            end
      endcase
    end
end


always@(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
      seg_sel <= 8'b1111_1111;
    end
    else begin
      seg_sel <= ~(8'b1<<delay_time);
    end
end


endmodule

1.6 蜂鸣器模块设计1.6.1接口信号
信号接口方向定义
clk输入系统时钟
rst_n输入低电平复位信号
Key1_func输入设置状态指示信号
end_counter输入时钟计数器结束条件,表示1秒的时间
Xs_sec_low输入显示的秒低位计数器
Xs_sec_high输入显示的秒高位计数器
Xs_min_low输入显示的分低位计数器
Xs_min_high输入显示的分高位计数器
Sec_low输入闹钟的秒低位计数器
Sec_high输入闹钟的秒高位计数器
Min_low输入闹钟的分低位计数器
Min_hign输入闹钟的分高位计数器
beep输出蜂鸣器信号
1.6.2设计思路本模块主要通过将显示时间与设置的闹钟时间进行比较,如果相同的话,就控制beep拉低,持续时间为5秒。由此提出一个计数器的架构,如下图所示。
该架构由蜂鸣器控制信号beep、秒计数器miao和闹钟触发指示信号flag_add组成。秒计数器秒:用于对5秒的时间进行计数,加一条件为flag_add && end_counter,表示当闹钟被触发,并且经过1秒的时间就加一;结束条件为5,表示数完5秒就清零。闹钟触发指示信号flag:当其为高电平时表示闹钟被触发,低电平表示没有被触发。初始状态为低电平,从低变高的条件为sec_low==xs_sec_low&&sec_high==xs_sec_high&&min_low==xs_min_low&&min_high==xs_min_high&&init&&!key1_func,表示当显示时间的秒高低位、分高低位和闹钟设置的秒高低位、分高低位相等,同时不处于刚上电的初始状态和设置状态时,闹钟被触发;从高变低的条件为end_miao,表示当5秒数完之后,就拉低。蜂鸣器控制信号beep:当其为低电平时,控制蜂鸣器响,为高电平时不响。初始状态为高电平,从高变低的条件为flag_add,表示计数器开始计数之后便将其拉低,当检测到flag_add=0的时候,便将其拉高。
1.6.3参考代码
always@(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
      flag_add <= 0;
    end
    else if(sec_low==xs_sec_low&&sec_high==xs_sec_high&&min_low==xs_min_low&&min_high==xs_min_high&&init&&flag_set==0)begin
      flag_add <= 1;
    end
    else if(end_miao)begin
      flag_add <= 0;
    end
end


always@(*)begin
    if(!sec_low&&!sec_high&&!min_low&&!min_high)begin
      init=0;
    end
    else begin
      init=1;
    end
end


always @(posedge clk or negedge rst_n) begin
    if (rst_n==0) begin
      miao <= 0;
    end
    else if(add_miao) begin
      if(end_miao)
            miao <= 0;
      else
            miao <= miao+1 ;
   end
end
assign add_miao = flag_add && end_counter;
assign end_miao = add_miao&& miao == 5-1 ;


always@(posedge clk or negedge rst_n)begin
   if(rst_n==1'b0)begin
      beep<=1'b1;
    end
    else if(flag_add)begin
      beep<=1'b0;
    end
    else
      beep<=1'b1;
end


endmodule


1.7 效果和总结下图是该工程在mp801开发板上的现象
其中按键s4控制数字时钟的暂停与开始,按键s3来选择需要设置的位,按键s2设置数值。左边四个数码管显示的是时钟的时间,右边四个数码管显示的是闹钟设置的时间。
下图是该工程在db603开发板上的现象
其中按键s1控制数字时钟的暂停与开始,按键s2来选择需要设置的位,按键s3设置数值。左边四个数码管显示的是时钟的时间,右边四个数码管显示的是闹钟设置的时间。
下图是该工程在ms980试验箱上的现象
其中按键s1控制数字时钟的暂停与开始,按键s2来选择需要设置的位,按键s3设置数值。左边四个数码管显示的是时钟的时间,右边四个数码管显示的是闹钟设置的时间。



由于该项目的上板现象是动态的,开始、暂停、时间设置等现象无法通过图片表现出来,想观看完整现象的朋友可以看一下现象演示的视频。

【设计教程下载】

【设计视频教程】
https://www.bilibili.com/video/BV1Af4y117H4?p=11
【设计代码下载】


感兴趣的朋友也可以访问明德扬论坛(http://www.fpgabbs.cn/)进行FPGA相关工程设计学习,也可以看一下我们往期的文章:《基于FPGA的密码锁设计》《波形相位频率可调DDS信号发生器》《基于FPGA的曼彻斯特编码解码设计》《基于FPGA的出租车计费系统》《数电基础与Verilog设计》《基于FPGA的频率、电压测量》《基于FPGA的汉明码编码解码设计》《关于锁存器问题的讨论》《阻塞赋值与非阻塞赋值》《参数例化时自动计算位宽的解决办法》

1.8 公司简介明德扬是一家专注于FPGA领域的专业性公司,公司主要业务包括开发板、教育培训、项目承接、人才服务等多个方向。点拨开发板——学习FPGA的入门之选。
MP801开发板——千兆网、ADDA、大容量SDRAM等,学习和项目需求一步到位。网络培训班——不管时间和空间,明德扬随时在你身边,助你快速学习FPGA。周末培训班——明天的你会感激现在的努力进取,升职加薪明德扬来助你。就业培训班——七大企业级项目实训,获得丰富的项目经验,高薪就业。专题课程——高手修炼课:提升设计能力;实用调试技巧课:提升定位和解决问题能力;FIFO架构设计课:助你快速成为架构设计师;时序约束、数字信号处理、PCIE、综合项目实践课等你来选。项目承接——承接企业FPGA研发项目。人才服务——提供人才推荐、人才代培、人才派遣等服务。



页: [1]
查看完整版本: 【每周FPGA案例】至简设计系列_闹钟 编号:001600000064