樱木花道长

不忘初心,方得始终

数字钟二----走时与调时融洽相处

1
阅读(2054)

PRJ_6_Dig_CLK.zip

     昨天大致说了一下数字钟的大体模块划分,以及为什么要进行模块化设计。其中几个模块都是比较常见而且简单的,系统时钟模块就是对板级时钟通过计数的方式分频,分成各种需要用到的慢时钟,其中用到的一个比较重要的语法就是带参数的模块例化,因为我们需要用到一个1HZ的时钟作为走时的秒时钟,另外需要一个1K的时钟给数码管做扫描时钟,以及按键消抖时钟,但是分频的模块都是一样的,只是计数的参数不同而已,这里我们就不需要把同一个模块去写两次了,只要对这个模块进行两次例化,两次传递不同的参数就好了。

blob.png

这是分频的模块,当输出的频率为1HZ的时候,我们需要定义的计数参数是25_000_000,但是输出1K频率的时钟时,需要定义成25_000,我们总不能因为两个参数就把代码写两遍吧?这样太不灵活了。

blob.png 

这里就是用到了刚才说的带参数的例化,由于例化的模块中只有一个参数,所以,在#后面的()中可以直接写需要传递的参数值就好了。但是如果是多个参数值时,又该怎么做呢?我们假如刚才的模块里面有两个参数:

FreqDiv #(

.parameter_name1(value1),

.parameter_name2(value2),

)  U1(

.CLK(CLK),

.RSTN(RSTN),

.CLK_DIV(CLK_1HZ)

);

是不是和端口的例化有点像?没错就是这么简单,哈哈。

另外说一下消抖的模块,以前见到的都是延时消抖的方式,检测到按键按下的边沿之后,延时10ms读取键值。这种方法比较常见吧,因为在单片机中经常见到。但是我最近在周立功的一本书上看到一种更加简单暴力的消抖方法。

blob.png

不知道各位看官有没有看明白,按周立功的说法,按键抖动的宽度一般在几百us到1ms之间,所以用1K的时钟,按键低电平有效,如果连续3个周期按键持续低电平,那么我们就认为按键按下了,至于这里为什么对key_out取反,是因为后面的模块需要,我在后面写到再解释,大家如果需要借鉴这个模块,可以根据自己的需要来决定要不要取反,当然,你们如果觉得3拍不保险,想多观察几拍也是可以的,但是根据我的观察,3拍已经足够了。另外这里我也用到了parameter参数化的定义,也是为了方便移植,如果有5个按键需要消抖,直接在例化的时候传递参数5,不用改一句代码,是不是很方便呢?嘻嘻。

    最后再说说这个设计的核心,也就是走时和调时的模块,大家知道一个变量是不能再多个always中同时赋值的,因为每个always是并行的,那样会导致系统矛盾,时间是无法综合的,那么系统正常走时生成的时间值和调整之后的时间值怎么才能愉快相处呢?

很多人会说在同一个always中进行处理,最多定义一个状态机,当然那样可以实现,但是代码冗长复杂,我自己是不喜欢的,先看一下我时间模块的RTL图吧,剩下的再慢慢解释。

blob.png  

为了看的清,我只截了一部分出来了,后面一部分的原理其实是一样的。最左边的模块叫set_time,就是在调时和走时之间切换的,set_time有一个4位的输出flag,有4种状态,每种只有一位为1,分别代表的走时、调小时、调分钟、调秒。下面我说一下调秒的部分,剩下的是一样的。我把1HZ的时钟和set_add按键都接到一个二选一模块上,而没有直接接到秒模块,利用刚才set_time输出的flag决定是哪一个接到秒模块:

assign value = (mode) ? set_add : CLK;

如果flag == 0,那么接上CLK,如果==1,那么接上set_add,按键按一下,秒加一。秒模块代码如下

blob.png 

这里秒计数满59会输出一个周期的脉冲作为分钟的输入时钟,后面的分钟,小时类似。最后说一下刚才的key_out为什么要取反,这里posedge是上升沿有效,按键没按下默认高电平,我们在由正常走时切换到调时的时候,clk原来是低电平(因为时和分的时钟都是前一个模块生成的一周期脉冲比较短,低电平可能性更大),切换到set_add(没按下,高电平),就自动形成了一个上升沿,时间值加1,这不是我们预期的结果。所以对按键取反,让他默认状态是低电平,那么时钟切过来还是低电平,就不会形成上升沿,也就不会加1 了,切回去的时候也是低电平切低电平,这样就消灭掉了这个bug.

工程代码已上传至附件,有什么批评建议欢迎指正