IIC储存器是笔者用来练习精密控时的经典例子。《整合篇》之际,IIC储存器的解释,笔者也自认变态。如今笔者回头望去,笔者也不知道自己当初到底发什么神经,既然将IIC的时序都解释一番。由于开发上板也嵌着IIC储存器(24LC04),笔者还得循例地介绍一下。

IIC储存器是应用IIC总线的储存器,时序本身并不是很复杂不过缺有一大堆时序参数,而且官方提供的时序也不利于描述,所以许多时序都必须自行绘制,真是麻烦死人。麻烦归麻烦,笔者终究还要吃饭,为了肚子,再麻烦的事情也要硬着头皮捱过去 ... 这也是白骆驼的恶作剧!

图16.1 IIC总线与IIC设备。

图16.1是IIC总线与IIC设备常见的示意图。理想上,一条IIC总线允许千万IIC设备占据在上 ... 物理下,一条IIC总监究竟允许多少IIC设备占据其中必须根据设备地址的长度。默认下,设备地址为八位宽,因此设备地址也称为设备字节。设备地址的高四位,即[7..4]记录硬件ID,接续三位即 [3..1] 则记录硬件地址,最后一位则是设备的访问方向。结果如表16.1所示:

表16.1 设备地址的位分配。

[7]

[6]

[5]

[4]

[3]

[2]

[1]

[0]

硬件ID

硬件地址

访问方向

所谓硬件ID就是IIC设备的辨识ID,硬件ID会随着厂商还有设备的种类而有所改变。开发板上的IIC设备是某厂商的IIC储存器,即24LC04,硬件ID为 4’b1010。至于硬件地址就是IIC设备在总线上辨识地址,默认下为3位,即同类的IIC设备在同一条IIC总线上仅允许占据8个而已。然而,开发板上的 24LC04 为3’b000。最后的访问方向位则是主机用来通知从机,此刻的访问目的是读还是写。

总结来说,设备地址除了访问方向以外,前七位一般都是固定的,例如开发板的IIC储存器24LC04,设备地址就是 8’b1010_000_×。

图16.2 24LC04的写操作(主机视角)。

IIC总线的时序,感觉上一组完成的操作宛如是一堆拼图。如图16.2所示,那是24LC04的写操作,时序先填上为起始位,再来是设备地址,余下是应答位,随之是数据地址,然后又是应答位,接着是写如数据,再一次应答位,最后挂上结束位以示一次性的写操作已经完成。那么,写操作的经过如下所示:

(一)主机发送起始位;

(二)主机发送设备地址(写);

(三)等待从机应答;

(四)主机发送数据地址;

(五)等待从机应答;

(六)主机发送数据;

(七)等待从机应答;

(八)主机发送结束位。

读者稍微注意一下设备地址的最低位,笔者稍微用蓝色将其高亮。由于此刻是写操作,所以设备地址的访问方向是“写”,所以访问方向位设置为0。

图16.3 24LC04的读操作(主机视角)。

图16.3是24LC04的读时序,同样它也是由一堆“拼图”组合而成。相较写操作,读操作不仅多了许多“拼图”,而且途中也改变访问方向。那么,读操作的经过如下所示:

(一)主机发送起始位;

(二)主机发送设备地址(写);

(三)等待从机应答;

(四)主机发送数据地址;

(五)主机发送起始位;

(六)主机发送设备地址(读);

(七)等待从机应答;

(八)主机读取数据;

(九)从机没有应答(主机无视应答);

(十)主机发送结束位。

未进入正题之前,请允许笔者加入一些小插曲。IIC总线是一种低速的总线,不过IIC总线有 100Khz 还有 400Khz 两种速率提供我们选择,要么100Khz,要么400Khz,要么两者兼施,不管哪一种《整合篇》都曾实验过。在此,实验十六会以400Khz的速率作为标准。

笔者曾在前面说过,IIC总线之所以麻烦,因为IIC总线有大小不同的时序参数(时间参数)。一般而言,时间参数都都被顺序语言一笑而过,那是因为顺序语言无法实现精密控时。虽然描述语言也可以一笑而过,但是语言的本质却不允我们这么作,如果我们选择无视时序参数 ... 那么,打从一开始我们还是不学为好。

此外,描述IIC的总线时序有各种各样的方法,但是笔者会选择表达能力更高,控制能力更细的描述手段。我们知道IIC的总线时序是由一块又一块的拼图拼凑而成,当我们在建模的时候,我们会针对各个拼图作出局部性的描述。期间,我们也必须考虑各种时序参数,如表16.2所示:

表16.2 各种时序参数(50Mhz量化)。

相关参数

标示

最小时间

最小时钟

最大时间

最大时钟

Clock Frequency

FCLK

---

---

400Khz

125

Clock High Time

THIGH

600ns

30

---

---

Clock Low Time

TLOW

1300ns

65

---

---

Rise Time

TR

---

---

300ns

15

Fall Time

TF

---

---

300ns

15

Start Hold Time

THD_STA

600ns

30

---

---

Start Setup Time

TSU_STA

600ns

30

---

---

Data Input Hold Time

THD_DAT

0ns

0

---

---

Data Input Setup Time

TSU_DAT

100ns

5

---

---

Stop Setup Time

TSU_STO

600ns

30

---

---

Output Valid From Clock

TAA

---

---

900ns

45

Bus Free Time

TBUF

1300ns

65

---

---

相比许多同学遇见表16.2便会立即憋着蛋蛋,因为它会吓坏一群小朋友。话虽如此,表16.2只有外表可怕的纸老虎而已,任何有时序基础的同学,随便擦擦两下就搞定。笔者虽然也想一笑打过,不过笔者还要循例介绍一下:

l Clock Frequency,既是频率也是速率,在此是400Khz。

l Clock High Time,既SCL信号保持高电平所需的最小时间。

l Clock Low Time,既SCL信号保持低电平所需的最小时间。

l Rise Time,既信号由底变高所需最大的时间。

l Fall Time,既信号又高变低所需最小的时间。

l Start Hold Time,既起始位所需最小的保持时间。

l Start Setup Time,既起始位所需最小的建立时间。

l Data Input Hold Time,既数据位所需最小的保持时间。

l Data Input Setup Time,既数据位所需最小的建立时间。

l Stop Setup Time,既结束位所需的最小保持时间。

l Ouput Valid From Clock,既数据位经时钟沿触发以后的有效时间。

l Bus Free Time,既释放总线的最小时间。

IIC总线是一种串行传输协议,既有时钟信号SCL,还有数据信号SDA。Clock Frequency 表示SCL信号的频率,Clock High Time 表示 SCL信号保持高电平所需的最小时间,Clock Low Time则表示 SCL信号保持低电平所需的最小的时间。

至于 Rise Time 与 Fall Time 表示,SCL信号还有 SDA信号由高变低或者由低变高时所需的最小时间,即上山与下山时间。Hold Time 与 Setup Time 是用来评估数据是否成功打入寄存器的时序参数,算是典型中的典型。Setup Time 表示建立时间,即数据写入寄存器之前所需的稳定时间;反之,Hold Time则是保持时间,即数据打入寄存器之后所需的稳定时间。只要两者得到满足,那么数据的寄存活动就得到确保。

Start是IIC总线的起始位,Stop是IIC总线的结束位,Data 是IIC总线的数据位,为了确保三者成功写入从机,Setup Time 与 Hold Time 必须得到满足。Ouput Valid From Clock是关系数据位的时序参数,还有 Bus Free Time 是关系结束位的时序参数,在此先丢胃口一下。此外,为了简化时序,笔者将各种参数的实际时间转换为50Mhz量化以后的结果。对此,Verilog 可以这样表示,结果如代码16.1所示:

1. parameter FCLK = 10'd125, FHALF = 10'd62, FQUARTER = 10'd31;

2. parameter THIGH = 10'd30, TLOW = 10'd65, TR = 10'd15, TF = 10'd15;

3. parameter THD_STA = 10'd30, TSU_STA = 10'd30, TSU_STO = 10'd30;

代码16.1

如代码16.1所示,FCLK表示400Khz的周期,FHALF表示1/2周期,FQUARTER表示1/4周期。至于为什么代码16.1不见,Data Input Hold Time 与 Bus Free Time 的时序参数,请读者暂时忍耐,往后会解释。

(话题继续之前,请读者确保自己对“整合时序”有一定的理解,不然的话 ... 接下来的内容,读者一定会看到泪流满面。)

图 16.4 起始位。

首先让我们先瞧瞧起始位这枚拼图。如图16.4所示,左图是起始位的理想时序,右图是起始位的物理时序。IIC总线的起始位也就类似串口或者PS/2等传输协议的起始位,然而不同的是,IIC总线的起始位是 SCL 拉高 TR + TSU_STA + THD_STA + TF 之久,换之 SDA 则是拉高 TR + THIGH 然后拉低 TF + TLOW。起始位总和所用掉的时间,恰恰好有一个速率的周期。对此,Verilog则可以这样描述,结果如代码16.2所示:

1. begin

2. isQ = 1;

3. rSCL <= 1'b1;

4. if( C1 == 0 ) rSDA <= 1'b1;

5. else if( C1 == (TR + THIGH) ) rSDA <= 1'b0;

6. if( C1 == (FCLK) -1) begin C1 <= 10'd0; i <= i + 1'b1; end

7. else C1 <= C1 + 1'b1;

8. end

代码16.2

如代码16.2所示,第2行的isQ = 1 表示设置 SDA 为输出状态(即时结果),第3行则表示 SCL一直持续拉高状态,第4~5行表示C1为0的时候SDA拉高,直到C1为TR+THIGH才拉低SDA。第6~7行表示一个步骤所逗留的时间。

图16.5 结束位。

图16.5是结束位的时序图,IIC设备的操作好坏一般都取决结束位。保险起见,SCL与SDA都事先拉低1/4周期,紧接着 SCL会拉高 TR+TSU_STO(或者1/2周期),最后又保持高电平1/2周期。反之,SDA会拉低1/2周期,随之拉高 TR+THIGH(或者1/2周期)。对此,Verilog可以这样表示,结果如代码16.3所示:

1. begin

2. isQ = 1'b1;

3. if( C1 == 0 ) rSCL <= 1'b0;

4. else if( C1 == FQUARTER ) rSCL <= 1'b1;

5. if( C1 == 0 ) rSDA <= 1'b0;

6. else if( C1 == (FQUARTER + TR + TSU_STO) ) rSDA <= 1'b1;

7. if( C1 == ( FQUARTER + FCLK ) -1 ) begin C1 <= 10'd0; i <= i + 1'b1; end

8. else C1 <= C1 + 1'b1;

9. end

代码16.3

如代码16.3所示,第2行表示 SDA为输出状态(即时),第3~4行表示C1为0拉高SCL,C1为1/4周期就拉高。第5~6行表示,C1为0拉低SDA,C1为 1/4周期 + TR + TSU_STO就拉高 SDA。第7~8行表示该步骤所逗留的时间。

图16.6 释放总线。

此外,结束位还有 Bus Free Tme 这个时序参数,IIC总线在闲置的状态下 SCL 与 SDA 等信号都持续高电平。主机发送结束位以示结束操作,然而主机持续拉高SCL信号与SDA信号 TBUF以示总线释放。TBUF的有效时间从SCL信号与SDA信号拉高那一刻开始算起

根据表16.2所示,TBUF是65个时钟,结果如图16.6所示,SDA信号拉高之后,SCL与SDA信号只要持续保持 1/2周期(即62个时),基本上就能满足TBUF。如果笔者是一位紧密控时狂人,可能无法接受这样的结果,因为满足 TBUF 少了3个时钟,为此代码16.3需要更动一下:

7. if( C1 == ( FQUARTER + FCLK + 3) -1 ) begin C1 <= 10'd0; i <= i + 1'b1; end

8. else C1 <= C1 + 1'b1;

9. end

代码16.4

如代码16.4所示,笔者为第7行写下 +3 表示该步骤多逗留3个时钟,以致满足TBUF。

图16.7 数据位。

不管对象是设备地址,数据地址,写入数据,读出数据,还是应答位,大伙都视为数据位。IIC总线类似其他传输协议,它有时钟信号也有上升沿与下降沿。如图16.7所示,SCL信号的下降沿导致设备设置(更新)数据,上升沿则是锁存(读取)数据。期间,TF+TLOW 表示时钟信号的前半周期,TR+THIGH则表示后半周期。此外,为了确保数据成功打入寄存器,数据被上升沿锁存哪一刻起,TSU_DAT 还有 THD_DAT 必须得到满足。

图16.8 数据位更新有效。

除此之外,为了确保数据有效被更新,我们也必须确保TAA得到满足,结果如图16.8所示。理解完毕以后,我们就可以开始学习,写一字节数据与读一字节数据,还有应答位。

图16.9 写一字节。

IIC总线一般都是一个字节一个字节读写数据,如图16.9所示,那是写一字节的理想时序图,一字节数据是从最高位开始写起。对此,Verilog可以这样描述,结果如代码16.5所示:

. ,,,,,,,:

. begin

. isQ = 'b1;

. rSDA <= D1[-i];

. if( C1 ==  ) rSCL <= 'b0;

. else if( C1 == (TF + TLOW) ) rSCL <= 'b1; 

. if( C1 == FCLK - ) begin C1 <= 'd0; i <= i + 1'b1; end

. else C1 <= C1 + 'b1;

. end

代码16.5

如代码16.5所示,第1行有8个步骤,表示写一个字节。第3行isQ为1表示SDA为输出状态。第4行表示从最高位开始更新SDA的数据位。第5~6行表示,C1为0拉低SCL,C1为TF+TLOW则拉高SCL。第7~8行表示该步骤逗留一个周期的时间。

图16.10 应答位。

应答位是从机给予主机的回答,0为是1为否。然而,从旁观看,读取应答位也是读取一位数据位。当主机完成写入一个字节或者读取一个字节数据的时候,从机都会产生应答位。主机拉低SCL那刻,从机便会发送应答位,然后主机会借由上升沿读取应答位。如图16.10所示,上升沿会产生在 TF + TLOW 之后,也是1/2周期。对此,Verilog可以这样表示,结果如代码16.6所示:

代码16.6

如代码16.6所示,第2行表示SDA为输入状态。第4~5行表示,C1为0拉低SCL,C1为1/2周期则拉高SCL。第3行表示,C1为1/2周期的时候读取应答位。第6~7行表示该步骤逗留1个周期的时间。

图16.11 读一字节。

所谓读一字节数据就是重复读取8次应答位。如图16.11所示,SCL的下降沿导致从机更新数据,然后主机在SCL的上升沿读取数据。此外,从机也会由高至低更新数据位。至于Verilog 则可以这样表示,结果如代码16.7所示:

. ,,,,,,,:

. begin

. isQ = 'b0;

. if( C1 == FHALF ) D1[-i] <= SDA;

. if( C1 ==  ) rSCL <= 'b0;

. else if( C1 == FHALF ) rSCL <= 'b1; 

. if( C1 == FCLK - ) begin C1 <= 'd0; i <= i + 1'b1; end

. else C1 <= C1 + 'b1;

. end

代码16.7

如代码16.7所示,第1行表示读取一字节。第3行表示SDA为输入状态,第5~6行表示,C1为0拉低SCL,C1为1/2周期则拉高SCL。第4行表示,C1为1/2周期的时候读取数据,而且数据位由高至低存入D1。第7~8行表示该步骤逗留一个周期的时间。

图16.12 第二次起始位。

我们知道主机向从机读取数据的时候,它必须改变设备地址的方向,因此读操作又第二次起始位。如图16.12所示,感觉上第二次起始位也是第一次起始位,不过为了促使改变方向成功,第二次起始位相较第一次起始位的前后都拉低1/4周期。对此,Verilog 可以这样表示,结果如代码16.8所示:

. begin

. isQ = 'b1;

. if( C1 ==  ) rSCL <= 'b0;

. else if( C1 == FQUARTER ) rSCL <= 'b1;

. else if( C1 == (FQUARTER + TR + TSU_STA + THD_STA + TF) ) rSCL <= 'b0;

.

. if( C1 ==  ) rSDA <= 'b0; 

. else if( C1 == FQUARTER ) rSDA <= 'b1;

. else if( C1 == ( FQUARTER + TR + THIGH) ) rSDA <= 'b0;

.

. if( C1 == (FQUARTER + FCLK + FQUARTER) - ) begin C1 <= 'd0; i <= i + 1'b1; end

. else C1 <= C1 + 'b1;

. end

代码16.8

如代码16.8所示,第2行表示SDA为输出状态。第3~5行表示,C1为0拉低SCL,C1为1/4周期拉高 SCL,C1为 1/4周期 + TR + TSU_STA + THD_STA + TF 便拉低SCL。第7~9行表示,C1为0拉低SDA,C1为1/4周期拉高SDA,C1为1/4周期 + TR + THIGH 便拉低SDA。第11~12行表示该步骤停留一个周期的时间。

理解完毕以后,我们便可以开始建模了。

图16.13 实验十六的建模图。

图16.13是实验十六的建模图,组合模块iic_demo 内容包含 IIC储存模块,核心操作还有SMG基础模块。首先核心操作会将数据纯如IIC储存模块,然后又从中读取,完后再将读出的数据驱动SMG基础模块。

iic_savemod.v

图16.14 IIC储存模块的建模图。

图16.14是IIC储存模块的建模图,左边是顶层信号,右边则是沟通用的问答信号,写入地址iAddr,写入数据 iData,还有读出数据oData。Call/Done有两位,即表示该模块有读功能还有些功能。具体内容,我们还是来看代码吧:

. module iic_savemod

. (

. input CLOCK, RESET,

. output SCL,

. inout SDA,

. input [:]iCall,

. output oDone,

. input [:]iAddr,

. input [:]iData,

. output [:]oData

. );

以上内容为相关的出入端声明。

. parameter FCLK = 'd125, FHALF = 10'd62, FQUARTER = 'd31; //(1/400E+3)/(1/50E+6)

. parameter THIGH = 'd30, TLOW = 10'd65, TR = 'd15, TF = 10'd15;

. parameter THD_STA = 'd30, TSU_STA = 10'd30, TSU_STO = 'd30;

. parameter FF_Write1 = 'd7;

. parameter FF_Write2 = 'd9, RDFUNC = 5'd19;

.

以上内容为相关的速率还有时序参数声明。第15~16行则是相关的伪函数声明。

. reg [:]i;

. reg [:]Go;

. reg [:]C1;

. reg [:]D1;

. reg rSCL,rSDA;

. reg isAck, isDone, isQ;

.

. always @ ( posedge CLOCK or negedge RESET )

. if( !RESET )

. begin

. { i,Go } <= { 'd0,5'd0 };

. C1 <= 'd0;

. D1 <= 'd0;

. { rSCL,rSDA,isAck,isDone,isQ } <= 'b11101;

. end

以上内容为相关的寄存器声明以及复位操作。

. else if( iCall[] )

. case( i )

.

. : // Call

. begin

. isQ = ;

. rSCL <= 'b1;

.

. if( C1 ==  ) rSDA <= 'b1; 

. else if( C1 == (TR + THIGH) ) rSDA <= 'b0;

.

. if( C1 == (FCLK) -) begin C1 <= 'd0; i <= i + 1'b1; end

. else C1 <= C1 + 'b1;

. end

.

以上内容为部分核心操作。第33行的 iCall[] 为使能写操作。步骤0用来产生起始位。

. : // Write Device Addr

. begin D1 <= {'b1010, 3'b000, 'b0}; i <= 5'd7; Go <= i + 'b1; end

.

. : // Wirte Word Addr

. begin D1 <= iAddr; i <= FF_Write1; Go <= i + 'b1; end

.

. : // Write Data

. begin D1 <= iData; i <= FF_Write1; Go <= i + 'b1; end

.

. /*************************/

.

以上内容为部分核心操作。步骤1用来写入设备地址,并且调用伪函数。步骤2用来写入数据地址,并且调用伪函数。步骤3用来写入数据,并且调用伪函数。

. : // Stop

. begin

. isQ = 'b1;

.

. if( C1 ==  ) rSCL <= 'b0;

. else if( C1 == FQUARTER ) rSCL <= 'b1; 

.

. if( C1 ==  ) rSDA <= 'b0;

. else if( C1 == (FQUARTER + TR + TSU_STO ) ) rSDA <= 'b1;

.

. if( C1 == (FQUARTER + FCLK) - ) begin C1 <= 'd0; i <= i + 1'b1; end

. else C1 <= C1 + 'b1; 

. end

.

以上内容为部分核心操作。步骤4用来产生结束位。

. :

. begin isDone <= 'b1; i <= i + 1'b1; end

.

. : 

. begin isDone <= 'b0; i <= 5'd0; end

.

以上内容为部分核心操作。步骤5~6用来产生完成信号。

. /*******************************/ //function

.

. ,,,,,,,:

. begin

. isQ = 'b1;

. rSDA <= D1[-i];

.

. if( C1 ==  ) rSCL <= 'b0;

. else if( C1 == (TF + TLOW) ) rSCL <= 'b1; 

.

. if( C1 == FCLK - ) begin C1 <= 'd0; i <= i + 1'b1; end

. else C1 <= C1 + 'b1;

. end

.

以上内容为部分核心操作。步骤7~14是写一个字节的伪函数。

. : // waiting for acknowledge

. begin

. isQ = 'b0;

. if( C1 == FHALF ) isAck <= SDA;

.

. if( C1 ==  ) rSCL <= 'b0;

. else if( C1 == FHALF ) rSCL <= 'b1;

.

. if( C1 == FCLK - ) begin C1 <= 'd0; i <= i + 1'b1; end

. else C1 <= C1 + 'b1; 

. end

.

. :

. if( isAck !=  ) i <= 'd0;

. else i <= Go; 

.

. /*******************************/ // end function

.

. endcase

.

以上内容为部分核心操作。步骤15则用来读取应答位,步骤16则用来判断应答位,应答成功返回步骤,失败则重新来过。

. else if( iCall[] ) 

. case( i )

.

. : // Start

. begin

. isQ = ; 

. rSCL <= 'b1;

.

. if( C1 ==  ) rSDA <= 'b1; 

. else if( C1 == (TR + THIGH) ) rSDA <= 'b0;

.

. if( C1 == FCLK - ) begin C1 <= 'd0; i <= i + 1'b1; end

. else C1 <= C1 + 'b1;

. end

.

以上内容为部分核心操作。第113行表示 iCall[] 使能读操作。步骤0用来产生起始位。

. : // Write Device Addr

. begin D1 <= {'b1010, 3'b000, 'b0}; i <= 5'd9; Go <= i + 'b1; end

.

. : // Wirte Word Addr

. begin D1 <= iAddr; i <= FF_Write2; Go <= i + 'b1; end

.

. : // Start again

. begin

. isQ = 'b1;

.

. if( C1 ==  ) rSCL <= 'b0;

. else if( C1 == FQUARTER ) rSCL <= 'b1;

. else if( C1 == (FQUARTER + TR + TSU_STA + THD_STA + TF) ) rSCL <= 'b0;

.

. if( C1 ==  ) rSDA <= 'b0; 

. else if( C1 == FQUARTER ) rSDA <= 'b1;

. else if( C1 == ( FQUARTER + TR + THIGH) ) rSDA <= 'b0;

.

. if( C1 == (FQUARTER + FCLK + FQUARTER) - ) begin C1 <= 'd0; i <= i + 1'b1; end

. else C1 <= C1 + 'b1;

. end

.

以上内容为部分核心操作。步骤1用来写入设备地址,并且调用伪函数。步骤2用来写入数据地址,并且调用伪函数。步骤3用来产生第二次起始位。

. : // Write Device Addr ( Read )

. begin D1 <= {'b1010, 3'b000, 'b1}; i <= 5'd9; Go <= i + 'b1; end

.

. : // Read Data

. begin D1 <= 'd0; i <= RDFUNC; Go <= i + 1'b1; end

.

. : // Stop

. begin

. isQ = 'b1;

.

. if( C1 ==  ) rSCL <= 'b0;

. else if( C1 == FQUARTER ) rSCL <= 'b1; 

.

. if( C1 ==  ) rSDA <= 'b0;

. else if( C1 == (FQUARTER + TR + TSU_STO) ) rSDA <= 'b1;

.

. if( C1 == (FCLK + FQUARTER) - ) begin C1 <= 'd0; i <= i + 1'b1; end

. else C1 <= C1 + 'b1; 

. end

.

. :

. begin isDone <= 'b1; i <= i + 1'b1; end

.

. : 

. begin isDone <= 'b0; i <= 5'd0; end

.

以上内容为部分核心操作。步骤4用来写入设备地址(读),并且调用伪函数。步骤5用来读取一个字节,并且调用伪函数。步骤6用来产生结束位。步骤7~8则用来产生完成信号。

. /*******************************/ //function

.

. ,,,,,,,:

. begin

. isQ = 'b1;

.

. rSDA <= D1[-i];

.

. if( C1 ==  ) rSCL <= 'b0;

. else if( C1 == (TF + TLOW) ) rSCL <= 'b1; 

.

. if( C1 == FCLK - ) begin C1 <= 'd0; i <= i + 1'b1; end

. else C1 <= C1 + 'b1;

. end

.

以上内容为部分核心操作。步骤9~16是用来写一字节的伪函数。

. : // waiting for acknowledge

. begin

. isQ = 'b0;

.

. if( C1 == FHALF ) isAck <= SDA;

.

. if( C1 ==  ) rSCL <= 'b0;

. else if( C1 == FHALF ) rSCL <= 'b1;

.

. if( C1 == FCLK - ) begin C1 <= 'd0; i <= i + 1'b1; end

. else C1 <= C1 + 'b1; 

. end

.

. :

. if( isAck !=  ) i <= 'd0;

. else i <= Go;

.

以上内容为部分核心操作。步骤17用来读取应答位,步骤18则用来判断应答位。

. /*****************************/

.

. ,,,,,,,: // Read

. begin

. isQ = 'b0;

. if( C1 == FHALF ) D1[-i] <= SDA;

.

. if( C1 ==  ) rSCL <= 'b0;

. else if( C1 == FHALF ) rSCL <= 'b1; 

.

. if( C1 == FCLK - ) begin C1 <= 'd0; i <= i + 1'b1; end

. else C1 <= C1 + 'b1;

. end

.

以上内容为部分核心操作。步骤19~26是读取一字节的伪函数。

. : // no acknowledge

. begin

. isQ = 'b1;

. //if( C1 == 100 ) isAck <= SDA;

.

. if( C1 ==  ) rSCL <= 'b0;

. else if( C1 == FHALF ) rSCL <= 'b1;

.

. if( C1 == FCLK - ) begin C1 <= 'd0; i <= Go; end

. else C1 <= C1 + 'b1; 

. end

.

. /*************************************/ // end fucntion

.

. endcase

.

以上内容为部分核心操作。步骤27用来无视应答位。

. /***************************************/

.

. assign SCL = rSCL;

. assign SDA = isQ ? rSDA : 'bz;

. assign oDone = isDone;

. assign oData = D1;

.

. /***************************************/

.

. endmodule

以上内容为相关的驱动声明。

iic_demo.v
该组合模块的连线部署请参考图16.,具体内容让我们来看代码吧。 . module iic_demo . ( . input CLOCK, RESET, . output SCL, . inout SDA, . output [:]DIG, . output [:]SEL . ); 以上内容为相关的出入端声明。 . wire [:]DataU1; . wire DoneU1; . . iic_savemod U1 . ( . .CLOCK( CLOCK ), . .RESET( RESET ), . .SCL( SCL ), // > top . .SDA( SDA ), // <> top . .iCall( isCall ), // < core . .oDone( DoneU1 ), // > core . .iAddr( D1 ), // < core . .iData( D2 ), // < core . .oData( DataU1 ) // > core . ); . 以上内容为IIC储存模块的实例化 。 . smg_basemod U2 . ( . .CLOCK( CLOCK ), . .RESET( RESET ), . .DIG( DIG ), // > top . .SEL( SEL ), // > top . .iData( D3 ) // < core . ); . 以上内容为数码管基础模块的实例化。 . /***************************/ . . reg [:]i; . reg [:]D1,D2; . reg [:]D3; . reg [:]isCall; . . always @ ( posedge CLOCK or negedge RESET ) // core . if( !RESET ) . begin . i <= 'd0; . { D1,D2 } <= { 'd0,8'd0 }; . D3 <= 'd0; . isCall <= 'b00; . end . else 以上内容为相关的寄存器声明以及复位操作。 . case( i ) . . : . if( DoneU1 ) begin isCall <= 'b00; i <= i + 1'b1; end . else begin isCall <= 'b10; D1 <= 8'd0; D2 <= 'hAB; end . . : . if( DoneU1 ) begin isCall <= 'b00; i <= i + 1'b1; end . else begin isCall <= 'b10; D1 <= 8'd1; D2 <= 'hCD; end . . : . if( DoneU1 ) begin isCall <= 'b00; i <= i + 1'b1; end . else begin isCall <= 'b10; D1 <= 8'd2; D2 <= 'hEF; end . . : . if( DoneU1 ) begin D3[:] <= DataU1; isCall <= 'b00; i <= i + 1'b1; end . else begin isCall <= 'b01; D1 <= 8'd0; end . . : . if( DoneU1 ) begin D3[:] <= DataU1; isCall <= 'b00; i <= i + 1'b1; end . else begin isCall <= 'b01; D1 <= 8'd1; end . . : . if( DoneU1 ) begin D3[:] <= DataU1; isCall <= 'b00; i <= i + 1'b1; end . else begin isCall <= 'b01; D1 <= 8'd2; end . . : . i <= i; . . endcase . . endmodule

以上内容为核心操作。步骤0~2将数据8’hAB 写入地址0,8’hCD写入地址1,8’hEF写入地址2。步骤3~5则是从地址0读出数据 8’hAB并且暂存至 D3[23:16], 从地址1读出数据 8’hCD 并且暂存至 D3[15:8],从地址2读出数据 8’hEF 并且暂存至 D3[7:0]。编辑完毕便下载程序,如果数码管从左至右显示 “ABCDEF” ,那么表示实验成功。

细节一: IIC储存模块,还是IIC功能模块?

有关IIC储存器的实验曾在《整合篇》出现过,不过是作为功能类来对待。换之,本实验的IIC储存器则作为储存类来看待,然而它究竟是功能类还是储存类呢?其实这是见仁见智的问题。如果读者认为功能类有助理解,那么它就是功能类 ... 相反的,笔者认为储存类有助理解,所以承认它就是储存类。

细节二: 100Khz 与 400Khz 速率

IIC储存器——24LC04 有两种速率供我们选择,100Khz是比较规格的速率,因为SCL有50%的占空比,反之400Khz则是比较不规格的速率,因为SCL的前半周期为36%,后半周期为64%。审美而言,100Khz比400Khz美丽 ... 速度而言,400Khz比100Khz快4倍。100Khz的时序参数还有50Mhz量化结果如表16.3所示:

表16.3 相关的时序参数(50Mhz量化)

相关参数

标示

最小时间

最小时钟

最大时间

最大时钟

Clock Frequency

FCLK

---

---

100Khz

500

Clock High Time

THIGH

4000ns

200

---

---

Clock Low Time

TLOW

4700ns

235

---

---

Rise Time

TR

---

---

1000ns

50

Fall Time

TF

---

---

300ns

15

Start Hold Time

THD_STA

4000ns

200

---

---

Start Setup Time

TSU_STA

4700ns

235

---

---

Data Input Hold Time

THD_DAT

0ns

0

---

---

Data Input Setup Time

TSU_DAT

250ns

12

---

---

Stop Setup Time

TSU_STO

4000ns

200

---

---

Output Valid From Clock

TAA

---

---

3500ns

175

Bus Free Time

TBUF

4700ns

235

---

---

Verilog 的常量声明如代码16.9所示:

1. parameter FCLK = 10'd500, FHALF = 10'd250, FQUARTER = 10'd125;

2. parameter THIGH = 10'd200, TLOW = 10'd235, TR = 10'd50, TF = 10'd15;

3. parameter THD_STA = 10'd200, TSU_STA = 10'd235, TSU_STO = 10'd200;

代码16.9

细节三:完整的个体模块

实验十六的IIC储存模块已经是完整的个体模块,随之可以调用。

【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验十六:IIC储存模块的更多相关文章

  1. [黑金原创教程] FPGA那些事儿《设计篇 III》- 图像处理前夕·再续

    简介 一本为入门图像处理的入门书,另外还教你徒手搭建平台(片上系统),内容请看目录. 注意 为了达到最好的实验的结果,请准备以下硬件. AX301开发板, OV7670摄像模块, VGA接口显示器, ...

  2. [黑金原创教程] FPGA那些事儿《设计篇 II》- 图像处理前夕·续

    简介 一本为入门图像处理的入门书,另外还教你徒手搭建平台(片上系统),内容请看目录. 注意 为了达到最好的实验的结果,请准备以下硬件. AX301开发板, OV7670摄像模块, VGA接口显示器, ...

  3. [黑金原创教程] FPGA那些事儿《设计篇 I》- 图像处理前夕

    简介 一本为入门图像处理的入门书,另外还教你徒手搭建平台(片上系统),内容请看目录. 注意 为了达到最好的实验的结果,请准备以下硬件. AX301开发板, OV7670摄像模块, VGA接口显示器, ...

  4. [黑金原创教程] FPGA那些事儿《数学篇》- CORDIC 算法

    简介 一本为完善<设计篇>的书,教你CORDIC算法以及定点数等,内容请看目录. 贴士 这本教程难度略高,请先用<时序篇>垫底. 目录 Experiment 01:认识CORD ...

  5. 【黑金原创教程】【FPGA那些事儿-驱动篇I 】原创教程连载导读【连载完成,共二十九章】

    前言: 无数昼夜的来回轮替以后,这本<驱动篇I>终于编辑完毕了,笔者真的感动到连鼻涕也流下来.所谓驱动就是认识硬件,还有前期建模.虽然<驱动篇I>的硬件都是我们熟悉的老友记,例 ...

  6. 【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验十八:SDRAM模块① — 单字读写

    实验十八:SDRAM模块① — 单字读写 笔者与SDRAM有段不短的孽缘,它作为冤魂日夜不断纠缠笔者.笔者尝试过许多方法将其退散,不过屡试屡败的笔者,最终心情像橘子一样橙.<整合篇>之际, ...

  7. 【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验十七:IIC储存模块 - FIFO读写

    . int main() . { . int A: . A = : . } 代码17.1 话题为进入之前,首先让我们来聊聊一些题外话.那些学过软核NIOS的朋友可曾记得,软核NIOS可利用片上内存作为 ...

  8. 【黑金原创教程】【FPGA那些事儿-驱动篇I 】连载导读

    前言: 无数昼夜的来回轮替以后,这本<驱动篇I>终于编辑完毕了,笔者真的感动到连鼻涕也流下来.所谓驱动就是认识硬件,还有前期建模.虽然<驱动篇I>的硬件都是我们熟悉的老友记,例 ...

  9. 【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验十:PS/2模块④ — 普通鼠标

    实验十:PS/2模块④ - 普通鼠标 学习PS/2键盘以后,接下来就要学习 PS/2 鼠标.PS/2鼠标相较PS/2键盘,驱动难度稍微高了一点点,因为FPGA(从机)不仅仅是从PS/2鼠标哪里读取数据 ...

随机推荐

  1. spring + springMVC + spring Data + jpa + maven 项目框架搭建

    首先看一下项目结构: 所用到的jar(pom.xml): <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:x ...

  2. electron demo项目npm install安装失败解决办法

    electron官网提供的demo项目,在npm install 的时候总是报错显示安装失败, 解决办法:FQ即可成功安装.

  3. python中字符串的几种表达方式(用什么方式表示字符串)

    说明: 今天在学习python的基础的内容,学习在python中如何操作字符串,在此记录下. 主要是python中字符串的几种表达,表示方式. python的几种表达方式 1 使用单引号扩起来字符串 ...

  4. 使用editorconfig配置你的编辑器

    摘要: 在团队开发中,统一的代码格式是必要的.但是不同开发人员使用的编辑工具可能不同,这样就造成代码的differ.今天给大家分享一个很好的方法来使不同的编辑器保持一样的风格. 不同的编辑器也有设置代 ...

  5. Ubuntu14.04下Mongodb(离线安装方式|非apt-get)安装部署步骤(图文详解)(博主推荐)

    不多说,直接上干货! 说在前面的话  首先,查看下你的操作系统的版本. root@zhouls-virtual-machine:~# cat /etc/issue Ubuntu LTS \n \l r ...

  6. UpLoader------实现上传大文件

    代码: <div id="selectFile">选择文件1</div> <script> var da = newGuid(); var kk ...

  7. scala spray 概念性内容的总结

    spray 是基于 akka 的轻量级 scala 库,可用于编写 REST API 服务.了解 spray 的 DSL 后可以在很短的时间内写出一个 REST API 服务,它的部署并不需要 tom ...

  8. Import VMware ESXi from VirtualBox

    VirtualBox can export appliance VMs to OVF format. And you can import the ovf format to VMware ESXi, ...

  9. 转载 IMP时数据库的IO性能监控,并提供IOPS的计算方法

     IMP时数据库的IO性能监控,并提供IOPS的计算方法 2011-07-15 17:36:10 分类: Linux [root@ntkdb oradata]# iostat -x 1 10     ...

  10. Selenium 管理 Cookies

    使用 Selenium ,还可以方便地对 Cookies 进行操作,例如获取.添加 .删除 Cookies 等 from selenium import webdriver browser = web ...