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

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

 

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

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

clip_image002

图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_×。

clip_image004

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

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

(一)主机发送起始位;

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

(三)等待从机应答;

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

(五)等待从机应答;

(六)主机发送数据;

(七)等待从机应答;

(八)主机发送结束位。

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

clip_image006

图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 的时序参数,请读者暂时忍耐,往后会解释。

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

clip_image008

图 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行表示一个步骤所逗留的时间。

clip_image010

图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行表示该步骤所逗留的时间。

clip_image012

图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。

clip_image014

图16.7 数据位。

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

clip_image016

图16.8 数据位更新有效。

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

clip_image018

图16.9 写一字节。

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

1. 0,1,2,3,4,5,6,7:

2. begin

3. isQ = 1'b1;

4. rSDA <= D1[7-i];

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

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

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

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

9. end

代码16.5

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

clip_image020

图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个周期的时间。

clip_image022

图16.11 读一字节。

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

1. 0,1,2,3,4,5,6,72. begin

3. isQ = 1'b0;

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

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

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

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

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

9. end

代码16.7

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

clip_image024

图16.12 第二次起始位。

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

1. begin

2. isQ = 1'b1;

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

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

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

6.

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

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

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

10.

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

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

13. 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行表示该步骤停留一个周期的时间。

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

clip_image026

图16.13 实验十六的建模图。

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

iic_savemod.v

clip_image028

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

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

1. module iic_savemod

2. (

3. input CLOCK, RESET,

4. output SCL,

5. inout SDA,

6. input [1:0]iCall,

7. output oDone,

8. input [7:0]iAddr,

9. input [7:0]iData,

10. output [7:0]oData

11. );

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

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

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

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

15. parameter FF_Write1 = 5'd7;

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

17.

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

18. reg [4:0]i;

19. reg [4:0]Go;

20. reg [9:0]C1;

21. reg [7:0]D1;

22. reg rSCL,rSDA;

23. reg isAck, isDone, isQ;

24.

25. always @ ( posedge CLOCK or negedge RESET )

26. if( !RESET )

27. begin

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

29. C1 <= 10'd0;

30. D1 <= 8'd0;

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

32. end

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

33. else if( iCall[1] )

34. case( i )

35.

36. 0: // Call

37. begin

38. isQ = 1;

39. rSCL <= 1'b1;

40.

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

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

43.

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

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

46. end

47.

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

48. 1: // Write Device Addr

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

50.

51. 2: // Wirte Word Addr

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

53.

54. 3: // Write Data

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

56.

57. /*************************/

58.

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

59. 4: // Stop

60. begin

61. isQ = 1'b1;

62.

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

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

65.

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

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

68.

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

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

71. end

72.

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

73. 5:

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

75.

76. 6: 

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

78.

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

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

80.

81. 7,8,9,10,11,12,13,14:

82. begin

83. isQ = 1'b1;

84. rSDA <= D1[14-i];

85.

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

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

88.

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

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

91. end

92.

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

93. 15: // waiting for acknowledge

94. begin

95. isQ = 1'b0;

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

97.

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

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

100.

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

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

103. end

104.

105. 16:

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

107. else i <= Go; 

108.

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

110.

111. endcase

112.

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

113. else if( iCall[0] ) 

114. case( i )

115.

116. 0: // Start

117. begin

118. isQ = 1; 

119. rSCL <= 1'b1;

120.

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

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

123.

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

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

126. end

127.

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

128. 1: // Write Device Addr

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

130.

131. 2: // Wirte Word Addr

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

133.

134. 3: // Start again

135. begin

136. isQ = 1'b1;

137.

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

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

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

141.

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

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

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

145.

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

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

148. end

149.

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

150. 4: // Write Device Addr ( Read )

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

152.

153. 5: // Read Data

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

155.

156. 6: // Stop

157. begin

158. isQ = 1'b1;

159.

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

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

162.

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

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

165.

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

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

168. end

169.

170. 7:

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

172.

173. 8: 

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

175.

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

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

177.

178. 9,10,11,12,13,14,15,16:

179. begin

180. isQ = 1'b1;

181.

182. rSDA <= D1[16-i];

183.

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

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

186.

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

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

189. end

190.

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

191. 17: // waiting for acknowledge

192. begin

193. isQ = 1'b0;

194.

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

196.

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

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

199.

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

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

202. end

203.

204. 18:

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

206. else i <= Go;

207.

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

208. /*****************************/

209.

210. 19,20,21,22,23,24,25,26: // Read

211. begin

212. isQ = 1'b0;

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

214.

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

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

217.

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

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

220. end

221.

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

222. 27: // no acknowledge

223. begin

224. isQ = 1'b1;

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

226.

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

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

229.

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

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

232. end

233.

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

235.

236. endcase

237.

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

238. /***************************************/

239.

240. assign SCL = rSCL;

241. assign SDA = isQ ? rSDA : 1'bz;

242. assign oDone = isDone;

243. assign oData = D1;

244.

245. /***************************************/

246.

247. endmodule

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

iic_demo.v
该组合模块的连线部署请参考图16.13,具体内容让我们来看代码吧。

1. module iic_demo

2. (

3. input CLOCK, RESET,

4. output SCL,

5. inout SDA,

6. output [7:0]DIG,

7. output [5:0]SEL

8. );

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

9. wire [7:0]DataU1;

10. wire DoneU1;

11.

12. iic_savemod U1

13. (

14. .CLOCK( CLOCK ),

15. .RESET( RESET ),

16. .SCL( SCL ), // > top

17. .SDA( SDA ), // <> top

18. .iCall( isCall ), // < core

19. .oDone( DoneU1 ), // > core

20. .iAddr( D1 ), // < core

21. .iData( D2 ), // < core

22. .oData( DataU1 ) // > core

23. );

24.

以上内容为IIC储存模块的实例化 。

25. smg_basemod U2

26. (

27. .CLOCK( CLOCK ),

28. .RESET( RESET ),

29. .DIG( DIG ), // > top

30. .SEL( SEL ), // > top

31. .iData( D3 ) // < core

32. );

33.

以上内容为数码管基础模块的实例化。

34. /***************************/

35.

36. reg [3:0]i;

37. reg [7:0]D1,D2;

38. reg [23:0]D3;

39. reg [1:0]isCall;

40.

41. always @ ( posedge CLOCK or negedge RESET ) // core

42. if( !RESET )

43. begin

44. i <= 4'd0;

45. { D1,D2 } <= { 8'd0,8'd0 };

46. D3 <= 24'd0;

47. isCall <= 2'b00;

48. end

49. else

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

50. case( i )

51.

52. 0:

53. if( DoneU1 ) begin isCall <= 2'b00; i <= i + 1'b1; end

54. else begin isCall <= 2'b10; D1 <= 8'd0; D2 <= 8'hAB; end

55.

56. 1:

57. if( DoneU1 ) begin isCall <= 2'b00; i <= i + 1'b1; end

58. else begin isCall <= 2'b10; D1 <= 8'd1; D2 <= 8'hCD; end

59.

60. 2:

61. if( DoneU1 ) begin isCall <= 2'b00; i <= i + 1'b1; end

62. else begin isCall <= 2'b10; D1 <= 8'd2; D2 <= 8'hEF; end

63.

64. 3:

65. if( DoneU1 ) begin D3[23:16] <= DataU1; isCall <= 2'b00; i <= i + 1'b1; end

66. else begin isCall <= 2'b01; D1 <= 8'd0; end

67.

68. 4:

69. if( DoneU1 ) begin D3[15:8] <= DataU1; isCall <= 2'b00; i <= i + 1'b1; end

70. else begin isCall <= 2'b01; D1 <= 8'd1; end

71.

72. 5:

73. if( DoneU1 ) begin D3[7:0] <= DataU1; isCall <= 2'b00; i <= i + 1'b1; end

74. else begin isCall <= 2'b01; D1 <= 8'd2; end

75.

76. 6:

77. i <= i;

78.

79. endcase

80.

81. 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储存模块已经是完整的个体模块,随之可以调用。

目录
相关文章
|
4天前
|
算法 数据安全/隐私保护 异构计算
基于FPGA的16QAM调制+软解调系统,包含testbench,高斯信道模块,误码率统计模块,可以设置不同SNR
本项目基于FPGA实现了16QAM基带通信系统,包括调制、信道仿真、解调及误码率统计模块。通过Vivado2019.2仿真,设置不同SNR(如8dB、12dB),验证了软解调相较于传统16QAM系统的优越性,误码率显著降低。系统采用Verilog语言编写,详细介绍了16QAM软解调的原理及实现步骤,适用于高性能数据传输场景。
102 69
|
9天前
|
移动开发 算法 数据安全/隐私保护
基于FPGA的QPSK调制+软解调系统,包含testbench,高斯信道模块,误码率统计模块,可以设置不同SNR
本文介绍了基于FPGA的QPSK调制解调系统,通过Vivado 2019.2进行仿真,展示了在不同信噪比(SNR=1dB, 5dB, 10dB)下的仿真效果。与普通QPSK系统相比,该系统的软解调技术显著降低了误码率。文章还详细阐述了QPSK调制的基本原理、信号采样、判决、解调及软解调的实现过程,并提供了Verilog核心程序代码。
46 26
|
15天前
|
算法 异构计算
基于FPGA的4ASK调制解调系统,包含testbench,高斯信道模块,误码率统计模块,可以设置不同SNR
本文介绍了基于FPGA的4-ASK调制解调系统的算法仿真效果、理论基础及Verilog核心程序。仿真在Vivado2019.2环境下进行,分别测试了SNR为20dB、15dB、10dB时的性能。理论部分概述了4-ASK的工作原理,包括调制、解调过程及其数学模型。Verilog代码实现了4-ASK调制器、加性高斯白噪声(AWGN)信道模拟、解调器及误码率计算模块。
39 8
|
22天前
|
算法 物联网 异构计算
基于FPGA的4FSK调制解调系统,包含testbench,高斯信道模块,误码率统计模块,可以设置不同SNR
本文介绍了基于FPGA的4FSK调制解调系统的Verilog实现,包括高斯信道模块和误码率统计模块,支持不同SNR设置。系统在Vivado 2019.2上开发,展示了在不同SNR条件下的仿真结果。4FSK调制通过将输入数据转换为四个不同频率的信号来提高频带利用率和抗干扰能力,适用于无线通信和数据传输领域。文中还提供了核心Verilog代码,详细描述了调制、加噪声、解调及误码率计算的过程。
46 11
|
1月前
|
算法 数据安全/隐私保护 异构计算
基于FPGA的1024QAM基带通信系统,包含testbench,高斯信道模块,误码率统计模块,可以设置不同SNR
本文介绍了基于FPGA的1024QAM调制解调系统的仿真与实现。通过Vivado 2019.2进行仿真,分别在SNR=40dB和35dB下验证了算法效果,并将数据导入Matlab生成星座图。1024QAM调制将10比特映射到复数平面上的1024个星座点之一,适用于高数据传输速率的应用。系统包含数据接口、串并转换、星座映射、调制器、解调器等模块。Verilog核心程序实现了调制、加噪声信道和解调过程,并统计误码率。
42 1
|
2月前
|
算法 数据安全/隐私保护 异构计算
基于FPGA的64QAM基带通信系统,包含testbench,高斯信道模块,误码率统计模块,可以设置不同SNR
本文介绍了基于FPGA的64QAM调制解调通信系统的设计与实现,包括信号生成、调制、解调和误码率测试。系统在Vivado 2019.2中进行了仿真,通过设置不同SNR值(15、20、25)验证了系统的性能,并展示了相应的星座图。核心程序使用Verilog语言编写,加入了信道噪声模块和误码率统计功能,提升了仿真效率。
52 4
|
2月前
|
存储 算法 数据处理
基于FPGA的8PSK调制解调系统,包含testbench,高斯信道模块,误码率统计模块,可以设置不同SNR
本系统在原有的8PSK调制解调基础上,新增了高斯信道与误码率统计模块,验证了不同SNR条件下的8PSK性能。VIVADO2019.2仿真结果显示,在SNR分别为30dB、15dB和10dB时,系统表现出不同的误码率和星座图分布。8PSK作为一种高效的相位调制技术,广泛应用于无线通信中。FPGA凭借其高度灵活性和并行处理能力,成为实现此类复杂算法的理想平台。系统RTL结构展示了各模块间的连接与协同工作。
55 16
|
2月前
|
算法 数据安全/隐私保护 异构计算
基于FPGA的16QAM基带通信系统,包含testbench,高斯信道模块,误码率统计模块,可以设置不同SNR
本项目基于FPGA实现16QAM调制解调通信系统,使用Verilog语言编写,包括信道模块、误码率统计模块。通过设置不同SNR值(如8dB、12dB、16dB),仿真测试系统的误码性能。项目提供了完整的RTL结构图及操作视频,便于理解和操作。核心程序实现了信号的生成、调制、信道传输、解调及误码统计等功能。
49 3
|
1月前
|
算法 数据安全/隐私保护 异构计算
基于FPGA的256QAM基带通信系统,包含testbench,高斯信道模块,误码率统计模块,可以设置不同SNR
本文介绍了256QAM调制解调算法的仿真效果及理论基础。使用Vivado 2019.2进行仿真,分别在SNR为40dB、32dB和24dB下生成星座图,并导入Matlab进行分析。256QAM通过将8比特数据映射到复平面上的256个点,实现高效的数据传输。Verilog核心程序包括调制、信道噪声添加和解调模块,最终统计误码率。
34 0
|
2月前
|
算法 数据安全/隐私保护 异构计算
基于FPGA的16PSK调制解调系统,包含testbench,高斯信道模块,误码率统计模块,可以设置不同SNR
### 简介 本项目采用VIVADO 2019.2进行了十六进制相位移键控(16PSK)算法仿真,结果显示,在SNR=30dB时效果为Tttttttttttttt12,在SNR=20dB时效果为Tttttttttttttt34。系统RTL结构如Tttttttttttttt555555所示。16PSK是一种高效的相位调制技术,能在每个符号时间内传输4比特信息,适用于高速数据传输。其工作原理包括将比特流映射到16个相位状态之一(Tttttttttttttt777777),并通过匹配滤波和决策进行解调。具体Verilog核心程序见完整代码。
39 1

热门文章

最新文章