【连载】【FPGA黑金开发板】Verilog HDL那些事儿--RTC接口封装(二十二)

声明:本文为原创作品,版权归akuei2及黑金动力社区(http://www.heijin.org)共同所有,如需转载,请注明出处http://www.cnblogs.com/kingst/

2

5.8 实验二十一:RTC接口

5.1章中,笔者说过“每一件硬件资源的封装,都有自己的考虑”。

key_interface.v 考虑了“5个拥有同样功能(滤抖)按键”。

smg_interface.v 考虑了“6位数码管的动态显示”。

beep_interface.v , ps2_interface.v 考虑了“利用FIFO,来拜托对上一层模块的束缚”。

tx_interface.v, rx_interface.v 和上述蜂鸣器接口和PS2接口考虑同样的事情。

vga_interface.v , lcd_interface.v 考虑了“利用RAM来打破了显示图像信息的极限”。

以上的接口都包含两个定义:“最后建模工程”和“使独立性特质”。

 

当然 RTC 接口的封装也离不开“封装的定义”,但是 RTC 接口会比较特别。 RTC 接口包含了 ds1302 的实时时钟芯片,如果只是“单纯的驱动”而已,那么 ds1302 模块已经是切切有余。

当我们封装RTC接口的时候,我们到底要“考虑什么呢?”。笔者的想法很简单,我们只要为 ds1302芯片 加入“可驱动,可配置,可输出”即可。

clip_image002[14]

 

上图是组合模块 rtc_interface.v,该组合模块包含了“可驱动”的ds1302模块,“可显示”和“可配置”的RTC控制模块。在4.3章中(实验十三)中建模成功的 ds1302模块,我们知道它可以支持8种命令,然而我们可以基于这些事儿之上,来决定“可配置”的设计方案。

RTC控制模块扮演着“可配置”和“可显示”的同时,它也扮演着 ds1302模块的控制操作。这也是为什么笔者在实验六中一直强调 控制模块”的重要性。

我们先来复习 ds1302模块 可以支持的8种命令:

RTC_Start_Sig[ 7..0 ]

位命令

功能

1000_0000

关闭写保护

0100_0000

变更时寄存器

0010_0000

变更分寄存器

0001_0000

变更秒寄存器

0000_1000

开启写保护

0000_0100

读取时寄存器

0000_0010

读取分寄存器

0000_0001

读取秒寄存器

RTC控制模块除了“可驱动”以外,还有“可输出”这个工作。在“图形”中的RTC_Sig 信号,包含了24位位宽,然而位的分配如下:

image 

接下来就是“可配置”的方案了。 Config_Sig 包含 5 位位宽,然而位分配如下:  

Config_Sig[4..0]

位分配

意义

[4]

进入配置模式|退出配置莫斯

[3]

+1操作

[2]

-1操作

[1]

向左切换 <= <=

[0]

向右切换 => =>

Config_Sig 信号的每一个“”都对“高脉冲敏感”,换句话说,如果某“位”接收“一个高脉冲”就有“一次性的操作”。

假设Config_Sig[4]接收一个高脉冲就“进入配置模式”。然后隔一段时间之后 Config_Sig[4] 再接收一个高脉冲就会“退出配置模式”。

(在这里笔者需要强调一点,在这里所谓的“时钟”不是物体的“时钟”,而是“时分秒”中的“时钟”。)

笔者先大致的说明一下 RTC 接口的工作原理: 在一开始的时候 RTC 接口 初始化 DS1302 芯片,将时钟,分钟,秒钟都配置为 00

然后 RTC接口,会从 00-00-00 开始计时。换句话说,在初始的状态 RTC_Sig 的输出是24'h00_00_00

假设 Config_Sig[4] 接收一个高脉冲, DS1302 芯片就停止计时,然而 RTC_Sig 输出当前的计时状态,这时候 RTC 接口就进入配置模式。  

在配置模式中“时钟”作为默认配置的“时间”。如果Config_Sig[3] 接收一个高脉冲,当前的“时钟值”就会递增。反之,如果 Config_Sig[2] 接收一个高脉冲,当前的“时钟值”就会递减。“时钟值”最大的值是24, 最小值是00

假设我要配置“分钟”,Config_Sig[0] 就要接收一个高脉冲,从“配置时钟”向右切换“配置分钟”。和“配置时钟”同样的原理。如果此时Config_Sig[3] 接收一个高脉冲,当前的“分钟值”就会递增,反之 Config_Sig[2] 接收一个高脉冲会使得当前的“分钟值”递减。“分钟值”最大的值是59, 最小值是00

 

如果接下来我要配置“秒钟”, Config_Sig[0] 就要接收一个高脉冲。如果接下来我又要配置“时钟”, Config_Sig[1] 就要接收一个高脉冲。“秒钟值”的最大值是 59 ,最小值是 00

 

在这里有一点必须注意的是:

 

在配置模式中,被“配置的时间”会在 RTC_Sig 输出。假设我在当前的配置模式中,我将“时钟值”配置为 23 ,那么 RTC_Sig 就会输出 24'h23_00_00

 

时钟”会作为“配置时间”的默认选择。换句话说,当一进入“配置模式”,立即进入“配置时钟”。

 

配置时钟”作为“切换”的最左边,“配置秒钟”作为“切换”的最右边。也就是说:

时钟 <=> 分钟 <=> 秒钟。

 

最后,假设我最终配置的时间是 12 - 20 - 12 ,然而我已经满足时间的配置了。这时候 RTC_Sig 的输出是 24'h12_20_12 。然后我要退出配置模式 , 那么 Config_Sig[4] 需要接收一个高脉冲, RTC 接口就会从“配置模式”退出至“正常模式”。

 

当返回“正常模式”, RTC 接口会从 12-20-12 开始计时。

 

rtc_control_module.v

clip_image004

3~13行是 rtc_control_module.v 的输入输出定义。 

clip_image005

 

在第 18 行定义了 isConfig 的标志寄存器。 isConfig 的默认值是逻辑 0, 也就是说在初始化的状态下, RTC 接口会进入“正常模式”( 22 行)。如果 Config_Sig[4] 接收到一个高脉冲 isConfig 的值就会介于 0~1 之间切换( 23~24 ),亦即 isConfig 逻辑 0 代表“正常模式”,逻辑 1 代表“配置模式”。

clip_image006

 

27~50 行是核心部分有关的寄存器声明和寄存器初始化。 rData 值作为 Time_Write_Data 的驱动。 Hour , Min, Sec 是作为“时分秒种”的暂存器。 Temp Comp 只作为“时间值”递增和递减操作的暂存器。 Go 寄存器是步骤 i 的返回指示。 isStart 是作为驱动 ds1302 模块的命令寄存器。所有寄存器的初始化都是清零状态。

clip_image007

 

在一开始的时候, RTC 时钟写进入初始化状态:

 

步骤 0 56~58 行)关闭 DS1302 芯片的写保护。步骤 1 60~62 行)是初始化 DS1302 芯片的“时钟值”。步骤 2 64~66 行)是初始化 DS1302 芯片的“分钟值”。步骤 3 68~70 行)是初始化 DS1302 芯片的“秒钟值”,同时也是启动 DS1302 芯片开始计数。所以说,初始化状态的“时间”是 24'h00_00_00

clip_image008

步骤4~7, 是正常模式。当进入步骤474~76),if条件就会先判断,isConfig是否被拉高?如果isConfig不被拉高,就进入下一个步骤。步骤5是执行“读时钟”的命令,步骤6是执行“读分钟”的操作,步骤7是读秒钟的操作。最后会返回步骤4。换句话说步骤4~7是正常模式的循环。但是,一旦步骤4if条件成立的话,就会进入“配置模式”。 

clip_image009

 

步骤 8 是预配置 , 也就是说在关闭 ds1302 芯片计时的同时,初始化 Temp 寄存器为 Hour 寄存器的值,这一点很重要,因为下一个步骤的操作需要。(在 DS1302 秒寄存器的最高位写入 1 ,亦即关闭计时)。可能你会想“在为 DS1302 的秒寄存器写入 8'b1000_0000 ,的时候,会不会破坏当前秒寄存器的值呢? ”。秒寄存器的值已经被暂存在 Sec

clip_image010

 

步骤 9~11 是配置模式。在 103 行笔者是否看见这样一句代码,改代码表示将 Temp 的值赋予 Hour 寄存器。读者尝试想象,如果在预配置至下(步骤 8 ),没有为 Temp 赋予 Hour 的值。那么当进入配置模式,无疑 103 行的代码会被执行, Temp 的初值为 0 Hour 的值已不是被破坏了?此外 111 118 行类似的代码到底有什么作用 ...

 

步骤 9 是“配置时钟”,在 99 行的 if 条件先判断 isConfig 是否为逻辑 0 ,如果是就退出配置模式,如果不是就处于“配置时钟”的状态。 103 行的代码,会作为默认一直被执行着。

 

Config_Sig[3] 接收一个高脉冲,亦即“时钟值递增”的操作。 Temp 会暂存 Hour 的值,然后 Comp 寄存器陪暂存 Hour 的最大值,也就是 8'h23 Go 寄存器指示着步骤 9 ,然后 i 寄存器被赋予 4'd12 ,该表示下一个执行步骤为 12

clip_image012

 

步骤 12~14 是“值递增”操作。在 123 if 条件会先判断,如果 Temp 的值小于 Comp 的值(如果当前的时钟值小于最大的时钟值的话), Temp 会递增。然后会进入步骤 13

否则的话 Temp 的值会赋予 Comp的值(当前时钟值赋予最大的时钟值),然后返回Go指示的步骤(如果当前是“配置时钟”,就会返回“配置时钟的步骤”,这也是为什么在100行,Go会指向当前的执行步骤)。

 

步骤 13 是进位操作,在 127 if 条件会判断 Temp 的个位(时钟的个位)是否大于 9

如果是就执行进位操作,然后i递增以示下一个步骤。否则i也会递增以示下一个步骤。

 

步骤 14 ,在 131 行的 if 条件会判断 Temp 的十位(当前“时钟值”的十位)是否大于 Comp 的十位(“时钟值”最大值的十位),如果是(亦即“当前时钟值”大于“时钟值最大值”) Temp 就赋予 Comp 的值(当前“时钟值”赋予“时钟值最大值”)。然后 i 赋予 Go 的值,以示返回“配置时钟”的步骤,亦即步骤 9 。否则,同样 i 会赋予 Go 的值

,返回步骤9 

clip_image013

 

在步骤 9 时,如果 Config_Sig[2] 接收一个高脉冲, Temp 就会暂存 Hour 的值( Temp 暂存当前的“时钟值”),然后 Go 寄存器指向当前步骤,亦即步骤 9 i 寄存器被赋予 4'd15, 也就是说下一个步骤会进入步骤 15

clip_image014

 

步骤 15 是递减操作。在 137 if 条件会先判断 Temp 的个位(当前“时钟值”的个位)大于 0 。如果是 Temp 的个位就会递减(当前“时钟值”的个位递减 1 )。然后 i 寄存器会指向 Go 的值,亦即返回步骤 9 ,返回“配置时钟”。

 

如果 137 行的 if 不成立,就会判断 138 行的 if 条件。 Temp 的个位等于 0 的同时, Temp 的十位又大于 0 (当前“时钟值”的个位等于 0, 而且当前“时钟值”的十位大于 0 ),就会将 Temp 的十位递减, Temp 的个位赋予 4'd9 (将当前“时钟值”的十位递减 1 ,当前“时钟值”的个位赋予 9 )。最后 i 寄存器会指向返回 Go 的值,亦即步骤 9

 

如果 137 行和 138 行的 if 条件不成立( 139 行),即表示 Temp 的值(当前的“时钟值”是 8'h00 )。 i 寄存器会指向返回 Go 的值,亦即步骤 9

clip_image015

 

103 行的这段代码很重要,因为无论是递增操作,或者是递减操作。最后操作的结果( Temp 值)都要更新于 Hour 寄存器的值。

 

如果 Config_Sig[0] 接收一个高脉冲( 102 行),亦即是向右边切换,换句话说就是从“时钟配置”向右切换到“分钟配置”。此时 Temp 的值必须更行为 Min 的值。和 103 行同样的道理。当 Config_Sig[0] 就收一个高脉冲,步骤 9 会递增至步骤 10

 

在步骤 10 105~111 行),无疑在 111 行的代码会被执行,如果在 102 Temp 值没有被更新为 Min 的值,不难想象得到 Min 寄存器的值与 Hour 寄存器的值是一样的,这显然是严重的 BUG

 

分钟配置”和“时钟配置”的“递增操作”或者“递减操作”都是大同小异。 Config_Sig[3] 如果被触发,就会进入步骤 12, 亦即“递增操作的步骤”。 Config_Sig[2] 被触发就会进入步骤 15 的“递减操作”。

 

不同之处是: Temp 暂存的再也不是 Hour 而是 Min 的值, Comp 的最大值是 8'h59

Go的寄存器指向“分钟配置”的步骤。

 

如果 Config_Sig[0] 接收一个高脉冲, i 会递增,“分钟配置”向右切换至“秒钟配置”( 108 行), Temp 会暂存 Sec 的值。如果 Config_Sig[1] 接收一个高脉冲, i 会递减,“分钟配置”向左切换至“时钟配置”( 109 行), Temp 会暂存 Hour 的值。

 

在步骤 9 98~103 行)没有 Config_Sig[1] ,在步骤 11 113~118 行)没有 Congfig_Sig[0]

这也表示 时钟配置 <=> 分钟配置 <=> 秒钟配置”,配置模式会在这3个时间配置之间切换。换句话说“时钟配置”是“向左切换的最边”,然而“秒钟配置”是“向右切换的最边”这一个事实。  

clip_image016

 

在步骤 12~14 的递增操作和在步骤 15 的递增操作,在某种程度上,可以把它们看成为“递增函数和递减函数”。根据一些常用的设计方法,我们必定会建立一个“时钟递增”,“分钟递增”,“秒钟递增”,“时钟递减”,“分钟递减”和“秒钟递减”等的多个操作步骤。这无疑是会消耗许多的逻辑资源。

 

在某程度的根本上时钟递增”,“分钟递增”,“秒钟递增”,“时钟递减”,“分钟递减”和“秒钟递减”等步骤,都可以共用一个“递增步骤”和“递减步骤”。但是问题就在于“参数传递”和“参数返回”是有关“代码概念”的操作。我们知道 Verilog HDL 语言,是“硬件描述语言”,它没有“代码的概念 ...

 

这时候我们必须把思路往后推移。笔者还记得自己在学习“微处理器”的时候(在笔者的心目中微处理器是悲剧,和单片机是不同的东西),为了是两个值相加,必须将两个值载入“操作空间”,然后使用指令是它们相加。如果使用这个思路反映到步骤 12~14 和步骤 15 的“递增递减操作”。寄存器 Temp , 寄存器 Comp , 和寄存器 Go 等就有所谓的“操作空间”的意义。

clip_image017

 

clip_image018

 

clip_image019

 

在步骤 9 10 11 期间,如果 99 106 114 行的 if 条件判断到 isConfig 被拉低的话。估计只有一件事情要发生,那就是“退出配置模式”。   

clip_image020

我们知道要进入“配置模式 isConfig 必须是逻辑 1 ,然后在“通常模式”中,如果步骤 4 75 if 条件检测到,才会进入“预配置模式”(步骤 8 ),执行预设置的操作。

当“退出配置模式”之后 i寄存器会赋值为 4'd1 ,亦即表示返回步骤1

clip_image021

 

步骤 1 ,是初始化 Hour ,但是关键点就是在 58 行的 rData <= Hour 。同样的步骤 2 66 行和步骤 3 70 行都是同样的意义。就是把配置后的 Hour Min Sec 作为输入数据,然后调用 DS1302 模块的命令,针对 DS1302 芯片的时寄存器,分寄存器和秒寄存器执行更新。

clip_image022

 

最后步骤 i 的流程也会进入 4~7 之间,也就是说 从“配置模式的退出”,会进入步骤 1 执行时间值更新的操作,然后会进入“普通模式”。

clip_image023

 

148 RTC_Start_Sig isStart 命令寄存器驱动。在 149 Time_Write_Data rData 寄存器驱动。在 150 RTC_Sig Hour Min Sec 寄存器驱动。

clip_image024

 

clip_image025

这是完成的代码,好好的浏览一番吧。

rtc_interface.v

clip_image002

 

rtc_interface.v 组合模块和“图形”是一模一样。

实验二十一说明:

 

说实话 RTC 接口的封装,却是有一点难度。 RTC 接口的“可驱动”是由 DS1302 模块负责但是由 RTC 控制模块控制。然而“可显示”和“可配置”却是由 RTC 控制模块负责。

 

对于 RTC 接口的配置控制,我们只要知道如何调用 Config_Sig 信号。 Config_Sig 信号的“每一位”都有“配置的用意”。笔者不得不承认“可配置”的设计方面,确实有一点难度。但是困难归苦难,为了未来,就要克服。

 

读者还是把重点放在“ RTC 控制模块如何操作 ”,因为只要读者明白了“ RTC 控制模块如何操作 ”,自然而然会明白“ RTC 控制模块的设计思路 ”。

 

完成后的扩展图:

 

clip_image004

实验二十一结论:

 

这一章实验主要是讲解如何为“ DS1302 芯片 ”执行封装。

总结:

第五章终于完结了。在这里笔者来个总结:

 

低级建模中所谓的“最后工程”是针对某个硬件“有考虑”的封装。

 

低级建模中所谓的“后期建模”是为后期建模的做好准备。如第二章 ~ 第四章的“基础建模”就是为“封装”做好准备,故“封装”可以称为“基础建模”的后期建模。然而“封装”又为什么做好准备?那就是下一章的主题 - 系统”建模。

 

说道“封装”,我们必须考虑 - 经过“封装”后的模块都有“独立性”的特质。我们知道 Verilog HDL 语言硬件描述语言,“封装”的行为会很有效的将“ Verilog HDL ”语言的特性带出来。因为经过“封装”以后的模块,都能“独立”的运行起来,这也好比“并行操作”的概念。

 

经过第五章的洗礼,读者们是不是领悟到从第一章到第五章的所有内容,任何一段信息都是在为下一章做好准备。在第二章中,笔者就提及过:“低级建模是模仿管理系统”的一种建模方法。一个大部分是由许多小部分组成。

 

如果换成另外一个角度去想象:

l  每一个简单的功能模块,可以看似一位员工。

l  每一个简单的控制模块,可以看似一位领导。

l  每一个简单的组合模块,可以看似一组小组。

l  每一个组合模块再组合起来,可以看似一个大组。

l  每一个大组为某种“目的”存在,而且有独立运行的能力,就成为部门(接口)。

l  最后由许多接口组合起来,就成为一个“系统”。

 

这就是“低级建模”最基本思路。

 

低级建模对于每一个模块都有区分“特质”。这好比员工是员工,领导是领导,小组是小组,大组是大组,部门是部门,各个都有自己的特征。

 

无论是什么样子管理系统,员工和员工之间,领导和员工之间,部门经理和领导之间,必须和谐共处。这好比是“低级建模”的“代码风格”吧。从实验一到实验二一,读者可能会发现到笔者所使用的“代码风格”都是清一色,笔者会很脸皮的告诉你 , 这是低级建模的固有代码结构”。

笔者知晓,很多读者一开始的时候,都会把“步骤i”看成是某个状态机的状态。在创建“低级建模”的初头,笔者老是觉得“典型的状态机”用法实在是太魏硕了。如果是小代码量的建模,那么“典型状态机”是没有问题。但是遇见“多级建模”或者“多状态”的时候“典型的状态机”就是“见鬼”。
笔者索性就建立自己的“代码风格”,在“低级建模”里笔者使用“步骤”来取代“状态机”。果真这个决定是对的。这样的设定,给笔者在后期的实验带来许多方便。在网上有一位网友很可爱的为笔者“Verilog HDL建模技巧 - 低级建模之仿顺序操作·思路篇”评价:

 

“俺觉得,每一个有仿顺序操作结构的模块,都可以理解成为一个子状态机 ...

 

他的理解没有一丝错误,不过是在理解上有不同的立场。这也难怪,因为当时笔者在写这一本笔记的时候,正是“低级建模”的创建初头。除了“步骤 i ”具有显性的代码风格以外,还有许多许多的代码风格 ...

 

话说远了 ... 下一章就是大团圆的一章笔记了。在进入下一章之前,请确保读者本身已经理解,掌握“低级建模是什么”这一点重点。如果还没有很好的理解“低级建模时什么”,请务必加强对“低级建模”的理解和掌握。

 

学习最重要不是要得到好成绩,也不是学习速度,而是掌握学习的重点 ......

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
I2C接口RTC实时时钟pcf8563读写Verilog驱动源码Quartus工程文件,FPGA型号Cyclone4E系列中的EP4CE10F17C8,Quartus版本18.0。 module rtc( //system clock input sys_clk , // 系统时钟 input sys_rst_n , // 系统复位 //pcf8563 interface output rtc_scl , // i2c时钟线 inout rtc_sda , // i2c数据线 //user interface input key2 , // 开关按键 output [5:0] sel , // 数码管位选 output [7:0] seg_led // 数码管段选 ); //parameter define parameter SLAVE_ADDR = 7'h51 ; // 器件地址 parameter BIT_CTRL = 1'b0 ; // 字地址位控制参数(16b/8b) parameter CLK_FREQ = 26'd50_000_000; // i2c_dri模块的驱动时钟频率(CLK_FREQ) parameter I2C_FREQ = 18'd250_000 ; // I2C的SCL时钟频率 parameter POINT = 6'b010100 ; // 控制点亮数码管小数点的位置 //初始时间设置,从高到低为年到秒,各占8bit parameter TIME_INI = 48'h18_05_23_09_30_00; //wire define wire clk ; // I2C操作时钟 wire i2c_exec ; // i2c触发控制 wire [15:0] i2c_addr ; // i2c操作地址 wire [ 7:0] i2c_data_w; // i2c写入的数据 wire i2c_done ; // i2c操作结束标志 wire i2c_ack ; // I2C应答标志 0:应答 1:未应答 wire i2c_rh_wl ; // i2c读写控制 wire [ 7:0] i2c_data_r; // i2c读出的数据 wire [23:0] num ; // 数码管要显示的数据 wire key_value ; // 按键消抖后的数据 //***************************************************** //** main code //***************************************************** //例化i2c_dri,调用IIC协议 i2c_dri #( .SLAVE_ADDR (SLAVE_ADDR), // slave address从机地址,放此处方便参数传递 .CLK_FREQ (CLK_FREQ ), // i2c_dri模块的驱动时钟频率(CLK_FREQ) .I2C_FREQ (I2C_FREQ ) // I2C的SCL时钟频率 ) u_i2c_dri( //global clock .clk (sys_clk ), // i2c_dri模块的驱动时钟(CLK_F

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值