番茄钟的实现(基于Xilinx EGO1学习板)
番茄钟设计
一、总体设计
1.番茄工作法简介
番茄工作法由意大利的奇列洛创造。其内容就是:工作25分钟休息5分钟,循环四次后休息15分钟。
本项目就是基于Xilinx Ego1开发板实现一个计时器,该计时器能实现:
- 25分钟工作倒计时
 - 5分钟休息倒计时
 
二、开发板介绍
开发板用户手册(提取码:2019)
板子搭载了8Mbit的SRAM芯片,对于本程序基本不用考虑内存不够的问题。主要看看引脚的定义和数码管部分。还有就是,注意板子的时钟是100MHz,对应P17引脚。
三、系统设计
本系统属于时序系统,所以采用状态机,以下是状态转化图

ok,万事俱备,下面开始写bug。
1.计时系统的构建
1.1 秒脉冲发生器
系统中需要秒脉冲的两个模块是25min倒计时和5min倒计时模块,不妨按下图设计时钟系统

EGO1的时钟是100MHz,构建计时器的关键就是分频得到秒脉冲。由于系统中有多处用到秒脉冲信号,所以不妨单独写一个秒脉冲发生模块:
module sec_gen(
input clk, //系统时钟
input sec_en,  //25min秒脉冲使能信号
output reg sec_out,  //给25min倒计时模块的时钟
output reg sec_con   //给5min倒计时模块的时钟
    );
    reg [25:0] n;
    always @(posedge clk or posedge sec_en)begin
    if(sec_en)
    sec_out<=sec_con;
    else
    sec_out<=sec_out;
    end
    always @(posedge clk)
    begin
        if(n==26'd5000000) //此处将将秒脉冲设置为0.1s了,方便调试
    n<=26'd1;
    else
    n<=(n+26'd1);
    end
    always @(posedge clk)
    begin
    if(n==26'd5000000)
    sec_con<=~sec_con;
    else
    sec_con<=sec_con
    end
endmodule
说明:
- 之所以设置两个时钟输出,是为了方便“时间暂停”的实现:25min倒计时过程中,如果按下暂停键,系统会启动5min等待计时,此时给25min倒计时模块的时钟信号恒为“0”或‘’1“,这样暂停结束后,只需要重新给25min模块提供秒脉冲信号,它就能从暂停的地方继续计时。
 - 对于五分钟倒计时则没有保存断点时间的需求:它要么计时5min后结束,要么中断,下次开始又是从5min开始计时。
 - 由于EGO1的RAM较大,可以使用26位寄存器,所以为了图方便,这里直接分频得到秒脉冲,某些板子的寄存器没有26位,这种情况下,最好对时钟信号进行多次分频。
 
2.2 25min倒计时
//25分钟倒计时模块
module timer(
input sec, //秒脉冲信号
input time_rst,  //重置
output reg carry_25, //25分钟计时结束信号
output reg [15:0]t //低八位为秒,高八位为分
    );
    always @(posedge sec or negedge time_rst)begin
        if(time_rst)
            t<=16'b0010_0101_0000_0000;
        //计时满了25min,carry_25拉高,同时将时钟重置为25min
        else if(t[15:0]==16'd0)begin
            t<=16'b0010_0101_0000_0000;
            carry_25<=1'b1;
        end
        else if(t[11:0]==12'd0)begin
            t[11:8]<=4'd9;
            t[15:12]<=(t[15:12]-4'd1);
        end
        else if(t[7:0]==8'd0)begin
            t[7:4]<=4'd5;
            t[3:0]<=4'd9;
            t[11:8]<=(t[11:8]-4'd1);
            carry_25<=1'b0;
        end
        else if(t[3:0]==4'd0)begin
            t[3:0]<=4'd9;
            t[7:4]<=(t[7:4]-4'd1);
        end
        else t[3:0]<=(t[3:0]-4'd1);
    end      
endmodule
说明:
这里的大寄存器同样可以分解成小寄存器,比较优美的写法是多个always结构并行,这里也是图个方便,直接用多个else if,需要注意的是:
if-else if语句不是并行执行的,而是按照代码前后顺序,挨个执行else if ,且一旦符合判断条件,后面的else if 都不会执行
(所以使用多个always并行是更保守的做法)
2.3 5min倒计时
//五分钟等待模块
module time_5(
input sec,//秒脉冲信号
input min5_en, //五分钟等待的使能信号
output reg carry_5 //五分钟计时结束的信号
    );
    reg [11:0]t;//低八位表示秒,高四位表示分
    always @(posedge sec or negedge min5_en)begin
        if(!min5_en)begin
            t<=16'b0101_0000_0000;
            carry_5<=1'b0;
        end
        else if(t[11:0]==11'd0)begin
            t<=16'b0101_0000_0000;
            carry_5<=1'b1;
        end
        else if(t[7:0]==8'd0)begin
            t[7:4]<=4'd5;
            t[3:0]<=4'd9;
            t[11:8]<=(t[11:8]-4'd1);
            carry_5<=1'b0;
        end
        else if(t[3:0]==4'd0)begin
            t[3:0]<=4'd9;
            t[7:4]<=(t[7:4]-4'd1);
        end
        else t[3:0]<=(t[3:0]-4'd1);
    end      
endmodule
说明:
跟25min模块一样,唯一需要注意的是,传入该模块的秒脉冲是持续有效的(无稳态),而传入25min模块的秒脉冲是可以通过暂停信号来达到稳态的。
2.数码管显示
背景知识:
数码管分为共阴极和共阳极,共阴极就是把所有的管子负极连接到一起,这样当需要某个管子发光时,就给它的正极输入一个高电平(逻辑1);相反共阳极就是将管子的正极都连接起来接到电源上,当需要管子亮时就输入低电平(逻辑0)。EGO1的数码管是共阴极的。
此外,板子上的数码管是四个为一组的,每组数码管受一个四选一多路选择器控制,也就是说同一时间,四个数码管只有一个能使用。所以输出的方式是,四个数码管轮流输出,但是这个轮换的速率很快,快到人眼看不出来,这样人看到就是四个罐子都在发光(显示屏也是这么工作的)。
//数码管控制模块
module pipe(
  input clk, //系统100MHz时钟
  input run_stop,
  input [15:0]x,
  output reg [6:0]a_to_g,  //单个数码管的输出信号
  output reg [3:0]an  //数码管选择信号
    );
    reg [20:0]clkdiv;
    wire [1:0]rank;
    reg  [3:0]digit;
    assign rank=clkdiv[20:19];
    initial clkdiv<=21'd0;
    //对系统100MHz时钟进行分频,作为数码管的扫描频率
    always @(posedge clk or negedge run_stop)begin
    if(!run_stop)
    clkdiv<=21'd0;
    else
    clkdiv<=clkdiv+21'd1;
    end
    always @*begin
    if(!run_stop)
      an=4'd0;
     else
	case(rank)
	2'd0:an=4'b0001;
	2'd1:an=4'b0010;
	2'd2:an=4'b0100;
	2'd3:an=4'b1000;
    endcase
    end
    always @*
      case(rank)
      2'd0:digit=x[3:0];
      2'd1:digit=x[7:4];
      2'd2:digit=x[11:8];
      2'd3:digit=x[15:12];
      default:digit=4'd0;
      endcase
     always @*
     case(digit)
     4'd0:a_to_g=7'b1111110;
     4'd1:a_to_g=7'b0110000;
     4'd2:a_to_g=7'b1101101;
     4'd3:a_to_g=7'b1111001;
     4'd4:a_to_g=7'b0110011;
     4'd5:a_to_g=7'b1011011;
     4'd6:a_to_g=7'b1011111;
     4'd7:a_to_g=7'b1110000;
     4'd8:a_to_g=7'b1111111;
     4'd9:a_to_g=7'b1111011;
     default:a_to_g=7'b1111110;
     endcase
endmodule
3.按键消抖
背景知识:
如下图,按键按下到弹起的过程中,电平是高频抖动的,这样如果不加入按键消抖处理,那么在信号抖动的过程中多次触发(比如我们想要的是按下按键暂停计时,如果不加防抖,那么就可能出现计时暂停、打开多次交替进行)。
消抖的基本原理也很简单:让按键信号必须保持一段时间才算有效。简单点讲,就是我收到一个按键信号(如果按键按下是0),先不着急执行按键按下后的程序,而是等几个时钟周期,再看看按键信号还是不是0,如果不是,说明按键在抖动,这个信号是非稳态信号,我就认为它是无效信号;如果还是0,说明确实收到了按键信号,我就开始执行后面的程序。所以,这个等待的时间就必须比抖动时间长,而比键稳定时间短。
//按键消抖模块
module debounce(
input clk,   //系统时钟
input run_stop,
input key_in,  //输入的按键信号
output wire key_out  //输出消抖后的按键信号
    );
 reg key_value;
 reg key_p_flag;
 reg key_reg;
 reg [20:0]delay_cnt;
 parameter DELAY_TIME=21'd1500000; //按键持续时间1/100M  s
//key_reg:按键输入寄存器
always@(posedge clk or negedge run_stop)
if(!run_stop)
    key_reg<=1'b0;
else
    key_reg<=key_in;
//delay_cnt:延时计数器
always@(posedge clk or negedge run_stop)
    if(!run_stop)
        delay_cnt<=21'b0;
    else if(key_in!=key_reg)
        delay_cnt<=DELAY_TIME;
    else if(delay_cnt>0)
        delay_cnt<=delay_cnt-1'b1;
    else
        delay_cnt<=21'd0;
//key_value
always@(posedge clk or negedge run_stop)
    if(!run_stop)
        key_value<=1'b0;
    else if(delay_cnt==1'b1)
        key_value<=key_in;
    else
        key_value<=key_value;
//key_p_falg:按键上升沿标志信号
always@(posedge clk or negedge run_stop)
    if(!run_stop)
        key_p_flag<=1'b0;
    else if(delay_cnt==1'b1&&key_in==1)
        key_p_flag<=1'b1;
    else
        key_p_flag<=1'b0;
assign key_out=key_p_flag;
endmodule
4.状态机控制器
背景知识:
具体解释看知乎状态机
其实就是个很简单的东西套了一个高大上的名字。以一个大学生为例,假设他只有上课、吃饭、睡觉、上厕所这四个状态,他每天按一定的时序在这四个状态之间切换,而处于每个状态下他就只能做这个状态下规定的事情(比如上课状态下他就不会吃东西,而吃饭状态下也不会排遗),各个状态之间相互独立。
直接上代码:
module states(
input run_stop,  //番茄钟工作开关
input reset_up,  //重置开关
input pause_up, //暂停开关
input clk,      //板载的100MHz时钟
input carry_25, //25分钟计时的进位
input carry_5,  //五分钟等待的进位
output reg min5_en, //五分钟等待计时的控制
output reg sec_out_en, //秒脉冲的控制信号
output reg time_rst   ///时间重置信号
    );
    reg [2:0]nstate;  //next state
    reg [2:0]cstate; //current state
    parameter WORK=3'b001;
    parameter STOP=3'b010;
    parameter WAIT_5=3'b100;
    parameter RESET=3'b101;
   initial nstate=STOP;
    //如果按下了停止,那么现在的状态就保持STOP
    //反之,现在的状态就会变成下一个状态
    always @(posedge clk or negedge run_stop)
    begin
    if(!run_stop)
    cstate<=STOP;
    else
    cstate<=nstate;
    end
	//状态转换(按照当前状态,决定下一个状态是什么)
    always @*
    begin
    case(cstate)
    STOP:begin
      if(run_stop)begin
        nstate=WORK;
        end
       else
        nstate=STOP;
       end
     WORK:begin
        if(run_stop==1'b0)
         	nstate=STOP;
        else if(reset_up==1'b1)
         	nstate=RESET;
        else if(pause_up==1'b1 || carry_25==1'b1)begin
        	nstate=WAIT_5;
         end
        else
            nstate=WORK;
        end
      RESET: nstate=WAIT_5;
      WAIT_5:begin
        if(carry_5==1'b1)
            nstate=WORK;
        else if(reset_up)
         	nstate=RESET;
        else if(pause_up)
         	nstate=WORK;
        else
            nstate=WAIT_5;
        end
       default:nstate=STOP;
       endcase
       end
 //每个状态下,程序要干的事情
       always @*
       begin
       case(cstate)
        //停止状态下:5分钟倒计时停止、25分钟倒计时停止、时间重置
        STOP:begin
        min5_en<=1'b0;
        sec_out_en<=1'b0;
        time_rst<=1'b1;
        end
        //工作状态下:5分钟倒计时停止,25分钟倒计时工作
        WORK:begin
        min5_en<=1'b0;
        time_rst<=1'b0;
        sec_out_en<=1'b1;
        end
        //等待状态下:5分钟倒计时开始,25分钟倒计时暂停
        WAIT_5:begin
        min5_en<=1'b1;
        sec_out_en<=1'b0;
        time_rst<=1'b0;
        end
        //重置状态下:5分钟倒计时停止、25分钟倒计时停止、时间重置
        RESET:begin
        sec_out_en<=1'b0;
        time_rst<=1'b1;
        min5_en<=1'b0;
        end
        default:begin
        sec_out_en<=1'b0;
        time_rst<=1'b1;
        min5_en<=1'b0;
        end
        endcase
       end
endmodule
注意事项:
- 组合逻辑中不要使用非阻塞赋值(状态转换的部分属于组合逻辑),时序逻辑中最好使用非阻塞赋值(描述各个状态下的内容的部分)
 - 阻塞赋值和非阻塞赋值的区别
 
四、资料下载
使用第三部分的内容可以完成基本的番茄钟功能。
这里提供一个加了一点花里胡哨的东西的番茄钟:
https://pan.baidu.com/s/1W9OtsmrOPexmkA-3EEXnqA 提取码:2019
添加了这些东西:
- 倒计时的时候有个八位的跑马灯
 - 五分钟等待计时的时候有五个led灯,每过一分钟熄灭一个
 - 摸鱼状态:按下停止后会进入一个正计时状态,并将时间通过数码管显示,这样就能知道已经多久没有工作了。
 
它的状态转换图如下:

番茄钟的实现(基于Xilinx EGO1学习板)的更多相关文章
- FPGA课设-基于Xilinx Basys2开发板的除法器设计
		
介绍一下Basys开发板: Basys2 FPGA开发板是一个电路设计实现平台,任何人都可以通过它来搭建一个真正的数字电路.Basys2是围绕着一个Spartan-3E FPGA芯片和一个Atmel ...
 - 番茄钟App(Pomodoro Tracker)
		
最近为了学习Swift编程语言,写了一个番茄钟的App(Pomodoro Tracker).刚上线的1.2版本增加了Apple Watch的支持. iPhone版 Apple Watch版 如果你跟我 ...
 - 基于Xilinx FPGA的视频图像采集系统
		
本篇要分享的是基于Xilinx FPGA的视频图像采集系统,使用摄像头采集图像数据,并没有用到SDRAM/DDR.这个工程使用的是OV7670 30w像素摄像头,用双口RAM做存储,显示窗口为320x ...
 - 基于Xilinx Zynq Z7045 SoC的CNN的视觉识别应用
		
基于Xilinx Zynq Z7045 SoC的CNN的视觉识别应用 由 judyzhong 于 星期三, 08/16/2017 - 14:56 发表 作者:stark 近些年来随着科学技术的不断进步 ...
 - [UWP]从头开始创建并发布一个番茄钟
		
1. 自己用的番茄钟自己做 在PC上我一直使用"小番茄"作为我的番茄钟软件,我把它打开后放在副显示器最大化,这样不仅可以让它尽到本分,而且还可以告诉我的同事"我正在专心工 ...
 - PCIE_DMA实例五:基于XILINX XDMA的PCIE高速采集卡
		
PCIE_DMA实例五:基于XILINX XDMA的PCIE高速采集卡 一:前言 这一年关于PCIE高速采集卡的业务量激增,究其原因,发现百度"xilinx pcie dma",出 ...
 - R语言爬虫初尝试-基于RVEST包学习
		
注意:这文章是2月份写的,拉勾网早改版了,代码已经失效了,大家意思意思就好,主要看代码的使用方法吧.. 最近一直在用且有维护的另一个爬虫是KINDLE 特价书爬虫,blog地址见此: http://w ...
 - Emacs 番茄钟 pomidor
		
Windows 10 pomidor:https://github.com/TatriX/pomidor alert :https://github.com/jwiegley/alert toaste ...
 - APP案例分析——嘀嗒番茄钟
		
第一部分 调研, 评测 个人第一次上手体验 一直在用时间管理的软件,但是下载了卸载,来来去去也用了很多个.这个嘀嗒番茄钟也是最近比较喜欢的软件,界面简洁,功能简单,没有那么复杂非常容易上手. 功能性的 ...
 
随机推荐
- SQL的多表查询(笛卡尔积原理)
			
感谢大佬:https://blog.csdn.net/yang5726685/article/details/53538438 MySQL的多表查询(笛卡尔积原理) 先确定数据要用到哪些表. 将多个表 ...
 - 用curl发起https请求
			
使用curl发起https请求 使用curl如果想发起的https请求正常的话有2种做法: 方法一.设定为不验证证书和host. 在执行curl_exec()之前.设置option $ch = cur ...
 - [01-jwt]C# JWT基础知识详解
			
本篇文章将介绍jwt基础概念性知识,不含实操代码展示,特别适合该方面知识空白的人群,大神级别请选择性观看.不喜禁喷,出门右转,谢谢配合. 一.什么是JWT? JWT是简写,全称是JSON Web To ...
 - springBoot2.*使用redis集群/单机方法
			
在springboot1.x系列中,其中使用的是jedis,但是到了springboot2.x其中使用的是Lettuce. 此处springboot2.x,所以使用的是Lettuce.关于jedis跟 ...
 - fuzz——AFL基础使用方法
			
最近打 ctf 的时候感觉有点遇到瓶颈,就来 fuzz 这块看看. AFL 全称为 American huzzy loop,是 Fuzzing 最高级的测试工具之一.这个工具对有源码和无源码的二进制程 ...
 - SQLServer、Mysql、Oracle 创建、删除用户和授予用户权限
			
SQLServer 1.创建用户 CREATE LOGIN [用户名称] WITH PASSWORD='用户密码', DEFAULT_DATABASE=[默认数据库名称], CHECK_EXPIRAT ...
 - 02网络编程( socket套接字+TCP粘包 )
			
目录 02 网络编程 一.socket套接字编程 二.简易代码模板 2.1 服务端 2.2 客户端 三.通信循环及代码优化 四.黏包现象 五.struct模块 六.简易版本报头 七.上传文件数据 * ...
 - 赠送4本《 PHP 程序员面试笔试宝典》
			
< PHP 程序员面试笔试宝典>历时一年,由机械工业出版社出版,在 2018 年 11 月问世.全书共八个章节,涉及 面试笔试经验技巧.PHP 基础知识.PHP 进阶知识,PHP 面向对象 ...
 - Redis 竟然能用 List 实现消息队列
			
分布式系统中必备的一个中间件就是消息队列,通过消息队列我们能对服务间进行异步解耦.流量消峰.实现最终一致性. 目前市面上已经有 RabbitMQ.RochetMQ.ActiveMQ.Kafka等,有人 ...
 - Solution -「多校联训」签到题
			
\(\mathcal{Description}\) Link. 给定二分图 \(G=(X\cup Y,E)\),求对于边的一个染色 \(f:E\rightarrow\{1,2,\dots,c\ ...