I2C总线通讯协议中主机模块的FPGA实现

 

本人FPGA小白,对FPGA比较感兴趣,前段时间跟某位同学讨论I2C总线通讯协议,我以前写过关于串口和SPI的通讯协议,还没有接触过I2C总线通讯协议。这次就抱着试试看心态,去了解了下I2C总线通讯协议。
结果就是,I2C通讯的复杂程度远超串口和SPI,我查找了一些关于I2C总线通讯的资料内容,比较不开心的是,各类资料对于I2C总线通讯协议的描述不尽相同,我选了其中的两篇我能接受的资料作为这次博客内容的基础,一篇是对I2C总线通讯的基本描述,一篇是其中关于协议通讯中ACK过程的描述,在接下来的内容中,会用到两篇资料中的内容,以下是两篇资料的链接:

 1.I2C总线通讯协议及其原理

 2.对I2C总线时序的一点理解以及ACK和NACK(NAK)

I2C总线通讯协议概述:
1.信号线描述
通讯协议为串行通讯协议,总共用到两根双向信号线,为SDA与SCL,其中SDA为数据线,SCL为时钟线;总线上通过上拉电阻接正电源,当总线空闲时,两根信号线均为高电平,连接到总线上的任一设备输出低电平时都会使总线拉低,表面两根信号线均为线“与”逻辑。
SCL为时钟线,为OD门,当上升沿时将数据输入到EEPROM中,下降沿时驱动EEPROM输出数据。
SDA也为OD门,输出与其他OD门或者OC门构成线与逻辑。
2.主从设备区分
协议区分主设备与从设备,每个从设备拥有对应于自己的7位地址码,前4位为器件类型,由厂家决定,而后三位则由用户自己定义。同一时间,主设备只能与一个从设备通讯,从设备挂载在总线上的数量由地址码位数以及总线最大电容400pf限制。
主设备在通讯中主要承担提供SCL时钟,控制信息读写流向,决定通讯的开始与结束的任务。
从设备则是提供和接收信息并于主机进行交互。
3.协议的简要内容
3.1 开始和结束信号
协议有自己的通讯帧,每帧都有开始信号与结束信号
开始信号:当SCL为高电平期间,将SDA信号从高电平拉低,则构成开始信号,此时总线将由空闲状态转为被占用状态,各从机将准备好从主机接收数据。
结束信号:当SCL为高电平期间,将SDA信号从低电平拉高,则构成结束信号,此时总线将被释放,从占用状态变为空闲状态,通讯结束。
在通讯过程中,SCL为高电平时,SDA均不允许发生变化,否则将会被视作开始或者结束信号,导致通讯出错。

 

3.2 ACK状态
ACK状态是I2C区别串口和SPI的一个很大的地方,当发送方发送8位数据后,在第9个SCL时钟上升沿之前需要释放总线(在我写的程序中,我选择在第9个时钟的下降沿),从接收方接收一个来自SDA的信号,该信号在第9个SCL时钟的高电平期间应该保持不变,对于接收方而言,若成功接收这8位数据,在总线释放期间,将SDA线拉低,表示ACK;若无法接收这8位数据,在总线释放期间,需要将SDA线拉高,表示NACK,通知发送方结束本次发送。
如果接收方是主设备,则在接收到从设备发来的最后一位数据后,在结束信号前,需要发送一个NACK状态,以通知从设备发送方释放总线,使主设备可以发送结束信号结束本次通讯。

3.3 I2C总线读写部分

I2C总线的读写过程有相同的部分也有各自区别,对于单个8位数据读写过程而言,从主机角度看,写过程需要写入3个8位数据,分别为从设备地址与写标志(7位从地址,写标志),从设备子寄存器地址(有的设备可能不需要),写入数据;而读过程需要写3个8位数据,读一个8位数据,分别为写从设备地址与写标志,写从设备子寄存器地址,写从设备地址与读标志,读出子寄存器数据。
3.3.1主设备向从设备写
如前述,主设备发送开始信号,接下来发送7位从设备地址和写标志信号(低电平),与之匹配的从机继续通讯过程,之后主机向匹配到的从机发送要写入的子寄存器地址,随后传送写入的数据,最后发送结束信号结束本次通讯。
更加具体的过程可以参看我给的链接一中的资料,这里直接上图:

 

3.3.2 主设备从从设备读

读过程第一次接触的时候感觉非常的怪异,就我个人的习惯看,我觉得应该只需要改变写过程中的读写标志信号和最后一个字节的信号流向就可以改变读写过程,可事实却不是这样,读过程远比写过程复杂的多。

读过程大致过程如下:

1)发送开始信号

2)发送7位从机地址和写标志信号,接收ACK信号

3)发送8位从机子寄存器地址,接收ACK信号

4)重启开始信号

5)发送7位从机地址和读标志信号,接收ACK信号

6)读出SDA上传过来的从机子寄存器数据,发送NACK信号

7)发送结束信号

 

 

4.主机模块FPGA实现与仿真
I2C通讯协议可以有很多种实现方法,可以对硬件的I2C电路控制编程实现,也可以用模拟GPIO的时序方法实现,在这里我用的就是后者,毕竟FPGA直接写接口就是模拟GPIO时序的方式。

软件版本是ISE14.7,仿真工具用的Moesim_10.1c,主要内容包括:

I2C_Master.v-----------I2C通信协议的通用主机读写模块

SI5338_Init.v------------通过I2C通信的方式初始化时钟芯片SI5338,完成将输入25MHz时钟倍频为50MHz的任务

I2C_Master_tb.v-------I2C_Master.v的testbench

 

4.1. I2C通信协议通用主机读写模块

模块实例:

        

 

管脚说明:

I_Clk_in---------------输入时钟,默认为50MHz,其他值需要修改内部参数

I_Rst_n---------------复位端,低电平有效

I_Start-----------------使能端,置高电平则模块正常运行

I_R_W_SET---------读写控制端信号,当为1时为写过程,为0时为读过程

I_Slave_Addr[6:0]--7位从机地址

I_R_W_Data[15:0]--读写控制字,其中高八位为从机子寄存器地址,低八位为写入寄存器的数据;读过程时,低八位置入何数据,不影响运行

O_SCL-----------------协议中的SCL端,由于不考虑从机无法接收数据而主动拉低SCL的情况,这里端口定义为输出,正常考虑全部可能情况时,定义为双向端口

IO_SDA----------------协议中的SDA端,定义为双向端口,不考虑从机ACK时,可以定义为输出

O_Done----------------通讯过程结束指示端,当一次读写完成时,会在端口输出一个Clk的高脉冲

O_Error----------------通讯错误指示信号,当主从机通讯出错时,该端口电平会拉高,直到下一次通讯开始。

O_Data[7:0]-----------读过程完成后,可以从该端口得到读出的8位数据

 

具体的实现代码如下:

 
  1. `timescale 1ns / 1ps

  2. //

  3. // Company:

  4. // Engineer:

  5. //

  6. // Create Date: 22:40:45 11/20/2017

  7. // Design Name:

  8. // Module Name: I2C_Master

  9. // Project Name:

  10. // Target Devices:

  11. // Tool versions:

  12. // Description:

  13. /*

  14. I2C总线通信协议通用模块:SCL SDA

  15. 开始信号:SCL高时,SDA拉低

  16. 结束信号:SCL高时,SDA拉高

  17. SDA数据在SCL低电平时置位

  18. 模块中实际默认开始信号与结束信号在SCL高电平中间产生

  19. SDA数据位改变在SCL低电平的中间产生

  20. SCL时钟频率为200kHz

  21. 从机地址可调,模块既支持读也支持写,通过输入管脚控制

  22. */

  23. //

  24. // Dependencies:

  25. //

  26. // Revision:

  27. // Revision 0.01 - File Created

  28. // Additional Comments:

  29. //

  30. //

  31. module I2C_Master(

		//I2C
		I_Clk_in,
 
  1. I_Rst_n,

  2. O_SCL,

  3. IO_SDA,

  4. //control_sig

  5. I_Start, //一次读/写操作开始信号

  6. O_Done, //一次读/写操作结束信号

  7. I_R_W_SET, //读写控制信号,写为1,读为0

  8. I_Slave_Addr,//从机地址

  9. I_R_W_Data,//读写控制字16位I_R_W_Data[15:8]->reg_addr,I_R_W_Data[7:0]->W_data,读状态则可默认为7'b0

  10. O_Data, //读到的数据,当O_Done拉高时数据有效

  11. O_Error //检测传输错误信号,当出现从机未响应,从机不能接收数据等情况时,拉高电平

  12. );

  13.  
  14. //I/O

  15. input I_Clk_in;

  16. input I_Rst_n;

  17. output O_SCL;

  18. inout IO_SDA;

  19.  
  20. input I_Start;

  21. output O_Done;

  22. input [6:0] I_Slave_Addr;

  23. input I_R_W_SET;

  24. input [15:0] I_R_W_Data;

  25. output [7:0] O_Data;

  26. output O_Error;

  27. /******时钟定位模块(测试时时钟为50MHz),定位SCL的高电平中心,与SCL的低电平中心,产生100kHz的SCL*******/

  28. parameter Start_Delay=9'd60;//开始时SDA拉低电平持续的时间,共用计数器下应小于SCL_HIGH2LOW-1

  29. parameter Stop_Delay=9'd150;//一次读/写结束后SDA拉高电平的时间,共用计数器下应小于SCL_HIGH2LOW-1

  30. parameter SCL_Period=9'd499;//测试板时钟为50MHz,100KHz为500个Clk

  31. parameter SCL_LOW_Dest=9'd374;//时钟判定高电平在前,低电平在后,低电平中央为3/4个周期,375个Clk

  32. parameter SCL_HIGH2LOW=9'd249;//电平翻转位置,1/2个SCL周期,250个Clk

  33. parameter ACK_Dect=9'd124; //SCL高电平中间位置,用于检测ACK信号

  34. reg [8:0] R_SCL_Cnt;

  35. reg R_SCL_En;

  36.  
  37. assign O_SCL=(R_SCL_Cnt<=SCL_HIGH2LOW)?1'b1:1'b0;//SCL 时钟输出

  38.  
  39. always @ (posedge I_Clk_in or negedge I_Rst_n)

  40. begin

  41. if (~I_Rst_n)

  42. begin

  43. R_SCL_Cnt<=9'b0;

  44. end

  45. else

  46. begin

  47. if (R_SCL_En)

  48. if (R_SCL_Cnt==SCL_Period)

  49. R_SCL_Cnt<=9'b0;

  50. else

  51. R_SCL_Cnt<=R_SCL_Cnt+9'b1;

  52. else

  53. R_SCL_Cnt<=9'b0;

  54. end

  55. end

  56.  
  57. /******SDA读写控制模块******/

  58. reg [5:0] R_State;

  59. reg R_SDA_I_O_SET;//SDA双向选择I/O口 1为输出,0为输入

  60. reg R_SDA_t; //SDA的输出端口

  61. reg O_Done; //结束信号

  62. reg [7:0] O_Data; //读到的数据

  63. reg O_Error; //传输错误指示信号

  64.  
  65. /****状态定义*****/

  66. parameter Start=6'd0; //一次读写开始的状态

  67. parameter ReStart=6'd34; //读操作入口状态

  68. parameter Stop=6'd56; //发送停止位状态

  69.  
  70. always @ (posedge I_Clk_in or negedge I_Rst_n)

  71. begin

  72. if (~I_Rst_n)

  73. begin

  74. R_SCL_En<=1'b0; //计数时钟停止

  75. R_State<=6'd0;

  76. R_SDA_I_O_SET<=1'b1;//默认设置为输出管脚

  77. R_SDA_t<=1'b1; //SDA输出默认拉高

  78. O_Data<=8'b0;

  79. O_Done<=1'b0;

  80. O_Error<=1'b0;

  81. end

  82. else

  83. begin

  84. if (I_Start) //当开始信号置高时表示I2C通信开始

  85. begin

  86. case(R_State)

  87. Start: //启动位

  88. begin

  89. R_SCL_En<=1'b1;

  90. O_Error<=1'b0;//每次重新下一次传输时,清除错误标志位

  91. if (R_SCL_Cnt==Start_Delay)

  92. begin

  93. R_SDA_t<=1'b0; //SCL高电平时拉低

  94. R_State<=R_State+6'd1;

  95. end

  96. else

  97. begin

  98. R_SDA_t<=1'b1;

  99. R_State<=R_State;

  100. end

  101. end

  102. 6'd1,6'd2,6'd3,6'd4,6'd5,6'd6,6'd7: //写入7位从机地址

  103. begin

  104. if (R_SCL_Cnt==SCL_LOW_Dest)

  105. begin

  106. R_SDA_t<=I_Slave_Addr[6'd7-R_State];//从MSB-LSB写入输入端从机地址

  107. R_State<=R_State+6'd1;

  108. end

  109. else

  110. R_State<=R_State;

  111. end

  112. 6'd8: //写入写标志(0)

  113. begin

  114. if (R_SCL_Cnt==SCL_LOW_Dest)

  115. begin

  116. R_SDA_t<=1'b0;

  117. R_State<=R_State+6'd1;

  118. end

  119. else

  120. R_State<=R_State;

  121. end

  122. 6'd9: //ACK状态

  123. begin

  124. if (R_SCL_Cnt==SCL_HIGH2LOW) //在第8个时钟的下降沿释放SDA

  125. begin

  126. R_SDA_I_O_SET<=1'b0;

  127. R_State<=R_State+6'd1;

  128. end

  129. else

  130. R_State<=R_State;

  131. end

  132. 6'd10: //在第9个时钟高电平中心检测ACK信号是否为0,如果为1,则表示从机未应答,进入结束位

  133. begin

  134. if (R_SCL_Cnt==ACK_Dect)

  135. begin

  136. O_Error<=IO_SDA; //检测从机是否响应

  137. R_State<=R_State+6'd1;

  138. end

  139. else

  140. R_State<=R_State;

  141. end

  142. 6'd11:

  143. begin

  144. if (R_SCL_Cnt==SCL_HIGH2LOW) //在第9个时钟的下降沿重新占用SDA,准备发送从机子寄存器地址

  145. begin

  146. R_SDA_I_O_SET<=1'b1;

  147. R_State<=(O_Error)?Stop:(R_State+6'd1);

  148. R_SDA_t<=1'b0;

  149. end

  150. else

  151. R_State<=R_State;

  152. end

  153. 6'd12,6'd13,6'd14,6'd15,6'd16,6'd17,6'd18,6'd19: //写入8位寄存器地址

  154. begin

  155. if (R_SCL_Cnt==SCL_LOW_Dest)

  156. begin

  157. R_SDA_t<=I_R_W_Data[6'd27-R_State];//从MSB-LSB写入寄存器地址 I_R_W_Data[15:8]

  158. R_State<=R_State+6'd1;

  159. end

  160. else

  161. R_State<=R_State;

  162. end

  163. 6'd20: //ACK状态

  164. begin

  165. if (R_SCL_Cnt==SCL_HIGH2LOW)//在第8个时钟的下降沿释放SDA

  166. begin

  167. R_SDA_I_O_SET<=1'b0;

  168. R_State<=R_State+6'd1;

  169. end

  170. else

  171. R_State<=R_State;

  172. end

  173. 6'd21: //检测ACK

  174. begin

  175. if (R_SCL_Cnt==ACK_Dect)

  176. begin

  177. O_Error<=IO_SDA;//检测从机是否响应

  178. R_State<=R_State+6'd1;

  179. end

  180. else

  181. R_State<=R_State;

  182. end

  183. 6'd22:

  184. begin

  185. if (R_SCL_Cnt==SCL_HIGH2LOW) //在第9个时钟的下降沿重新占用SDA,区分接下来该发送数据还是读数据

  186. begin

  187. R_SDA_I_O_SET<=1'b1;

  188. R_State<=(O_Error)?Stop:((I_R_W_SET)?(R_State+6'd1):ReStart); //从机状态

  189. R_SDA_t<=(O_Error|I_R_W_SET)?1'b0:1'b1; //此处拉高SDA信号是为读状态重启开始信号做准备

  190. end

  191. else

  192. R_State<=R_State;

  193. end

  194. 6'd23,6'd24,6'd25,6'd26,6'd27,6'd28,6'd29,6'd30://写入8位数据地址

  195. begin

  196. if (R_SCL_Cnt==SCL_LOW_Dest)

  197. begin

  198. R_SDA_t<=I_R_W_Data[6'd30-R_State];//从MSB-LSB写入8位数据地址

  199. R_State<=R_State+6'd1;

  200. end

  201. else

  202. R_State<=R_State; end

  203. 6'd31: //ACK状态

  204. begin

  205. if (R_SCL_Cnt==SCL_HIGH2LOW)//在第8个时钟的下降沿释放SDA

  206. begin

  207. R_SDA_I_O_SET<=1'b0;

  208. R_State<=R_State+6'd1;

  209. end

  210. else

  211. R_State<=R_State;

  212. end

  213. 6'd32://检测ACK

  214. begin

  215. if (R_SCL_Cnt==ACK_Dect)

  216. begin

  217. O_Error<=IO_SDA;//检测从机是否响应

  218. R_State<=R_State+6'd1;

  219. end

  220. else

  221. R_State<=R_State;

  222. end

  223. 6'd33:

  224. begin

  225. if (R_SCL_Cnt==SCL_HIGH2LOW)//在第9个时钟的下降沿重新占用SDA,准备发送停止位

  226. begin

  227. R_SDA_I_O_SET<=1'b1;

  228. R_SDA_t<=1'b0;//先拉低SDA信号

  229. R_State<=Stop;//跳转到结束位发送状态

  230. end

  231. else

  232. R_State<=R_State;

  233. end

  234. ReStart://主机读状态入口 初始时需要重启开始状态

  235. begin

  236. if (R_SCL_Cnt==Start_Delay)

  237. begin

  238. R_SDA_t<=1'b0; //SCL高电平时拉低

  239. R_State<=R_State+6'd1;

  240. end

  241. else

  242. begin

  243. R_SDA_t<=1'b1;

  244. R_State<=R_State;

  245. end

  246. end

  247. 6'd35,6'd36,6'd37,6'd38,6'd39,6'd40,6'd41://发送从机7位地址

  248. begin

  249. if (R_SCL_Cnt==SCL_LOW_Dest)

  250. begin

  251. R_SDA_t<=I_Slave_Addr[6'd41-R_State];//从MSB-LSB写入输入端从机地址

  252. R_State<=R_State+6'd1;

  253. end

  254. else

  255. R_State<=R_State;

  256. end

  257. 6'd42://写入读标志(1)

  258. begin

  259. if (R_SCL_Cnt==SCL_LOW_Dest)

  260. begin

  261. R_SDA_t<=1'b1;//写入读地址标志

  262. R_State<=R_State+6'd1;

  263. end

  264. else

  265. R_State<=R_State;

  266. end

  267. 6'd43: //ACK状态

  268. begin

  269. if (R_SCL_Cnt==SCL_HIGH2LOW)//在第8个时钟的下降沿释放SDA

  270. begin

  271. R_SDA_I_O_SET<=1'b0;

  272. R_State<=R_State+6'd1;

  273. end

  274. else

  275. R_State<=R_State; end

  276. 6'd44://ACK检测

  277. begin

  278. if (R_SCL_Cnt==ACK_Dect)

  279. begin

  280. O_Error<=IO_SDA;

  281. R_State<=R_State+6'd1;

  282. end

  283. else

  284. R_State<=R_State;

  285. end

  286. 6'd45://之后需要一直读取数据,所以SDA总线这里需要保持输入状态

  287. begin

  288. if (R_SCL_Cnt==SCL_HIGH2LOW)//在第9个时钟下降沿保持SDA总线的释放状态

  289. begin

  290. R_SDA_I_O_SET<=(O_Error)?1'b1:1'b0;//若前次ACK检测通过,则保持SDA总线释放状态,不 通过则占用SDA总线用来发送停止位

  291. R_State<=(O_Error)?Stop:(R_State+6'd1);

  292. R_SDA_t<=1'b0;

  293. end

  294. else

  295. R_State<=R_State;

  296. end

  297. 6'd46,6'd47,6'd48,6'd49,6'd50,6'd51,6'd52,6'd53://8个时钟信号高电平中间依次从SDA上读取数据

  298. begin

  299. if (R_SCL_Cnt==ACK_Dect)

  300. begin

  301. O_Data<={O_Data[6:0],IO_SDA};//从MSB开始读入数据

  302. R_State<=R_State+6'd1;

  303. end

  304. else

  305. R_State<=R_State;

  306. end

  307. 6'd54://读入8位数据后,主机需要向外发送一个NACK信号

  308. begin

  309. if (R_SCL_Cnt==SCL_HIGH2LOW)

  310. begin

  311. R_SDA_I_O_SET<=1'b1;//主机重新占用SDA

  312. R_SDA_t<=1'b1;

  313. R_State<=R_State+6'd1;

  314. end

  315. else

  316. R_State<=R_State;

  317. end

  318. 6'd55://在第9个时钟下降沿持续占用总线,拉低SDA,开始发送结束位

  319. begin

  320. if (R_SCL_Cnt==SCL_HIGH2LOW)

  321. begin

  322. R_SDA_t<=1'b0;

  323. R_State<=R_State+6'd1;

  324. end

  325. else

  326. R_State<=R_State;

  327. end

  328. Stop: //发送停止位

  329. begin

  330. if (R_SCL_Cnt==Stop_Delay)

  331. begin

  332. R_SDA_t<=1'b1;

  333. R_State<=R_State+6'd1;

  334. end

  335. else

  336. R_State<=R_State;

  337. end

  338. 6'd57: //停止时钟,同时输出Done信号,表示一次读写操作完成

  339. begin

  340. R_SCL_En<=1'b0;

  341. O_Done<=1'b1;//拉高Done信号

  342. R_State<=R_State+6'd1;

  343. end

  344. 6'd58:

  345. begin

  346. O_Done<=1'b0;//拉低Done信号

  347. R_State<=Start;

  348. end

  349. default:

  350. begin

  351. R_SCL_En<=1'b0;//计数时钟停止

  352. R_State<=6'd0;

  353. R_SDA_I_O_SET<=1'b1;//默认设置为输出管脚

  354. R_SDA_t<=1'b1;//SDA输出默认拉高

  355. O_Done<=1'b0;

  356. end

  357. endcase

  358. end

  359. else //开始信号无效时,回到初始设置

  360. begin

  361. R_SCL_En<=1'b0; //计数时钟停止

  362. R_State<=6'd0;

  363. R_SDA_I_O_SET<=1'b1;//默认设置为输出管脚

  364. R_SDA_t<=1'b1; //SDA输出默认拉高

  365. O_Done<=1'b0;

  366. end

  367. end

  368. end

  369.  
  370. /*******配置三态门信号******/

  371. assign IO_SDA=(R_SDA_I_O_SET)?R_SDA_t:1'bz;

  372.  
  373.  
  374. endmodule

 

采用了一个仿顺序操作的写法来完成了本次的主机模块的编写,代码段注释比较详细,就不在过多解读代码,至于什么是仿顺序写法与本文无关,感兴趣的可以去看《FPGA那些事--建模篇》,本人受这本书“荼毒甚深”。

为了能对该模块进行测试,编写了相应的TestBench程序,具体的代码段如下:

 
  1. `timescale 1ns / 1ps

  2.  
  3. // Company:

  4. // Engineer:

  5. //

  6. // Create Date: 19:10:07 11/21/2017

  7. // Design Name: I2C_Master

  8. // Module Name: F:/verilog_demo/I2C_Bus/I2C_Master_tb.v

  9. // Project Name: I2C_Bus

  10. // Target Device:

  11. // Tool versions:

  12. // Description:

  13. //

  14. // Verilog Test Fixture created by ISE for module: I2C_Master

  15. //

  16. // Dependencies:

  17. //

  18. // Revision:

  19. // Revision 0.01 - File Created

  20. // Additional Comments:

  21. //

  22.  
  23. module I2C_Master_tb;

  24.  
  25. // Inputs

  26. reg I_Clk_in;

  27. reg I_Rst_n;

  28. reg I_Start;

  29. reg I_R_W_SET;

  30. reg [6:0] I_Slave_Addr;

  31. reg [15:0] I_R_W_Data;

  32.  
  33. // Outputs

  34. wire O_SCL;

  35. wire O_Done;

  36. wire [7:0] O_Data;

  37. wire O_Error;

  38. // Bidirs

  39. wire IO_SDA;

  40. // Instantiate the Unit Under Test (UUT)

  41. I2C_Master uut (

  42. .I_Clk_in(I_Clk_in),

  43. .I_Rst_n(I_Rst_n),

  44. .O_SCL(O_SCL),

  45. .IO_SDA(IO_SDA),

  46. .I_Start(I_Start),

  47. .O_Done(O_Done),

  48. .I_R_W_SET(I_R_W_SET),

  49. .I_Slave_Addr(I_Slave_Addr),

  50. .I_R_W_Data(I_R_W_Data),

  51. .O_Data(O_Data),

  52. .O_Error(O_Error)

  53. );

  54. //重定义参数大小,减少仿真时间

  55. defparam uut.Start_Delay=8'd4;

  56. defparam uut.Stop_Delay=8'd4;

  57. defparam uut.SCL_Period=8'd19;

  58. defparam uut.SCL_LOW_Dest=8'd14;

  59. defparam uut.SCL_HIGH2LOW=8'd9;

  60. defparam uut.ACK_Dect=8'd5;

  61. initial begin

  62. // Initialize Inputs

  63. I_Clk_in = 0;

  64. I_Rst_n = 0;

  65. I_Start = 0;

  66. I_R_W_SET=1'b1;

  67. I_Slave_Addr = 0;

  68. #3 I_Rst_n=1'b1;

  69. I_Start=1'b1;

  70. I_Slave_Addr=7'b010_1100;//从机地址

  71. I_R_W_Data=16'b0010_0001_0010_0011;//I_R_W_Data[15:8]->reg addr I_R_W_Data[7:0]-> data

  72. end

  73.  
  74. /***CLK***/

  75. always #1 I_Clk_in=~I_Clk_in;

  76.  
  77. /****Test_Vector****/

  78. reg [6:0] R_State;

  79. reg [7:0] WR_Data;

  80. reg [7:0] RD_Data;

  81. //建立新的三态门

  82. reg SDA_t;

  83. reg SDA_SET;

  84. wire IO_SDA_S;

  85. assign IO_SDA_S=(SDA_SET)?SDA_t:1'bz;

  86. //两个三态门相连

  87. assign IO_SDA=IO_SDA_S;

  88.  
  89. always @ (posedge O_SCL or negedge I_Rst_n)

  90. begin

  91. if (~I_Rst_n)

  92. begin

  93. R_State<=7'd0;

  94. WR_Data<=8'b0;

  95. RD_Data<=8'b0101_0110;

  96. SDA_SET<=1'b0; //默认为输入

  97. SDA_t<=1'b0;

  98. end

  99. else

  100. begin

  101. case(R_State)

  102. 7'b0,7'd1,7'd2,7'd3,7'd4,7'd5,7'd6: //接收从机地址和读写标志

  103. R_State<=R_State+7'd1;

  104. 7'd7: //第8个脉冲周期

  105. begin

  106. #20 SDA_SET<=1'b1;//三态转输出

  107. SDA_t<=1'b0;

  108. R_State<=R_State+7'd1;

  109. end

  110. 7'd8://返回ACK信号

  111. begin

  112. #20 SDA_SET<=1'b0; //三态门转输入

  113. R_State<=R_State+7'd1;

  114. end

  115. 7'd9,7'd10,7'd11,7'd12,7'd13,7'd14,7'd15://接收寄存器地址

  116. R_State<=R_State+7'd1;

  117. 7'd16:

  118. begin

  119. #20 SDA_SET<=1'b1;//三态转输出

  120. SDA_t<=1'b0;

  121. R_State<=R_State+7'd1;

  122. end

  123. 7'd17: //返回ACK信号

  124. begin

  125. #20 SDA_SET<=1'b0; //三态门转输入

  126. R_State<=R_State+7'd1;

  127. end

  128. 7'd18,7'd19,7'd20,7'd21,7'd22,7'd23,7'd24://接收数据

  129. begin

  130. WR_Data<={WR_Data[6:0],IO_SDA};

  131. R_State<=R_State+7'd1;

  132. end

  133. 7'd25:

  134. begin

  135. WR_Data<={WR_Data[6:0],IO_SDA};

  136. #20 SDA_SET<=1'b1;//三态转输出

  137. SDA_t<=1'b0; //返回ACK信号

  138. R_State<=R_State+7'd1;

  139. end

  140. 7'd26: //ACK

  141. begin

  142. #20 SDA_SET<=1'b0; //三态门转输入

  143. R_State<=R_State+7'd1;

  144. end

  145. 7'd27: //终止位

  146. begin

  147. #18 I_R_W_SET<=1'b0; //运行模式转为读,同时Start关掉

  148. I_Start=1'b0; //关闭模块

  149. #100 I_Start=1'b1; //Start开启 进行一次完整读过程

  150. R_State<=R_State+7'd1;

  151. end

  152. 7'd28,7'd29,7'd30,7'd31,7'd32,7'd33,7'd34://接收从机地址

  153. R_State<=R_State+7'd1;

  154. 7'd35:

  155. begin

  156. #20 SDA_SET<=1'b1;//三态转输出

  157. SDA_t<=1'b0;

  158. R_State<=R_State+7'd1;

  159. end

  160. 7'd36: //返回ACK信号

  161. begin

  162. #20 SDA_SET<=1'b0; //三态门转输入

  163. R_State<=R_State+7'd1;

  164. end

  165. 7'd37,7'd38,7'd39,7'd40,7'd41,7'd42,7'd43: //接收读寄存器地址

  166. R_State<=R_State+6'd1;

  167. 7'd44: //第8个脉冲

  168. begin

  169. #20 SDA_SET<=1'b1;//三态转输出

  170. SDA_t<=1'b0;

  171. R_State<=R_State+7'd1;

  172. end

  173. 7'd45://返回ACK信号

  174. begin

  175. #20 SDA_SET<=1'b0;//三态转输入

  176. R_State<=R_State+7'd1;

  177. end

  178. 7'd46:

  179. R_State<=R_State+7'd1;

  180. 7'd47,7'd48,7'd49,7'd50,7'd51,7'd52,7'd53://接收从机地址

  181. R_State<=R_State+7'd1;

  182. 7'd54://第8个脉冲

  183. begin

  184. #20 SDA_SET<=1'b1;//三态转输出

  185. SDA_t<=1'b0;//返回ACK信号

  186. R_State<=R_State+7'd1;

  187. end

  188. 7'd55://第9个脉冲

  189. begin

  190. #30 SDA_t<=RD_Data[7];

  191. R_State<=R_State+7'd1;

  192. end

  193. 7'd56,7'd57,7'd58,7'd59,7'd60,7'd61,7'd62://输出寄存器内数据

  194. begin

  195. #30 SDA_t<=RD_Data[7'd62-R_State];

  196. R_State<=R_State+7'd1;

  197. end

  198. 7'd63://第8个脉冲

  199. begin

  200. #20 SDA_SET<=1'b0;//三态转输入

  201. R_State<=R_State+7'd1;

  202. end

  203. 7'd64: //第9个脉冲

  204. begin

  205. #20 R_State<=R_State+7'd1;

  206. end

  207. 7'd65://STOP

  208. begin

  209. #18 I_R_W_SET<=1'b1; //运行模式转为写,同时Start关掉

  210. I_Start=1'b0;

  211. #100 I_Start=1'b1; //Start开启 进行一次NACK的写过程

  212. R_State<=R_State+7'd1;

  213. end

  214. 7'd66,7'd67,7'd68,7'd69,7'd70,7'd71,7'd72://写入7位从机地址

  215. R_State<=R_State+7'd1;

  216. 7'd73://第8个脉冲

  217. begin

  218. #20 SDA_SET<=1'b1;

  219. SDA_t<=1'b1;//发送NACK信号

  220. R_State<=R_State+7'd1;

  221. end

  222. 7'd74://第9个脉冲

  223. begin

  224. #20 SDA_SET<=1'b0;

  225. R_State<=R_State+7'd1;

  226. end

  227. 7'd75://Stop

  228. begin

  229. #10 I_R_W_SET<=1'b1; //运行模式为写,同时Start关掉

  230. I_Start=1'b0;

  231. #100 R_State<=R_State;//结束仿真

  232. end

  233. default:

  234. R_State<=6'b0;

  235. endcase

  236.  
  237. end

  238. end

  239. endmodule

  240.  

TestBench使主机模块先写后读一组数据,两次过程间存在一定延迟,最后模拟了从机地址未成功写入下的主机应答,本次写TestBench让我收获最大的是完成了三态门仿真,以前如何对三态门进行仿真是我很头疼的问题,这次得到了解决,具体的仿真方法,在TestBench里有实例可供参考。

以下为仿真结果:

主机进行一次写时序:

主机进行一次读时序:

主机写入从机地址时从机未响应:

 

4.2 SI5338时钟芯片初始化程序

5338时钟芯片是一类可实现时钟分频与倍频的芯片,通过配置其寄存器参数,可以完成对输入时钟的分频与倍频操作,配置寄存器参数则需要用到I2C通讯协议。芯片资料上关于5338上I2C接口的说明如下:

从上述说明中得到如下重要的信息:其一,5338从机地址为70H,当然这是在不使用I2C_LSB_PIN的情况下。其二,5338支持连续写功能,主机在第三个字节写入后不发送结束信号,之后写入的一字节数据将会进入比当前子寄存器地址高一位的子寄存器中;其三,可以发现5338的读过程比先前资料给出的多了一个P过程,也就是结束信号过程,这再次充分证明了,这个协议具体怎么使还得看资料手册怎么标注的,不过还好,本次配置5338只需要用写过程就可以。

5338的子寄存器不是多而是很多。。。,完成一次赋值需要使用252次的子寄存器赋值,为了方便赋值和模块移植,把需要赋值的子寄存器的地址保存在一个深度为252,位宽为8的ROM内,而相应的数据则存放在同样大小ROM内,此后要改变参数只要修改ROM值就可以了。

在5338完成时钟分频后,把输入信号和输出时钟分频成1Hz分别驱动两个LED灯,可以观察到两个LED闪烁的频率一致,从而验证赋值的准确,在这里只给出配置寄存器的模块代码,顶层代码由于只具备验证性,不再给出。

 

 
  1. `timescale 1ns / 1ps

  2. //

  3. // Company:

  4. // Engineer:

  5. //

  6. // Create Date: 22:28:47 11/21/2017

  7. // Design Name:

  8. // Module Name: SI5338_Init

  9. // Project Name:

  10. // Target Devices:

  11. // Tool versions:

  12. // Description:

  13. // 使用I2C总线协议控制SI5338时钟芯片的寄存器初始化,由于只需要写,不使用I2C_Master通用读写模块,太笨重了。

  14. // Dependencies:

  15. //

  16. // Revision:

  17. // Revision 0.01 - File Created

  18. // Additional Comments:

  19. //

  20. //

  21. module SI5338_Init(

  22. //I2C

  23. I_Clk_in,

  24. I_Rst_n,

  25. O_SCL, //SCL时钟线

  26. IO_SDA, //SDA双向数据总线

  27. //

  28. O_Init_Done //初始化完成指示位.初始化后输出一个高电平

  29. );

  30.  
  31. //I/O

  32. input I_Clk_in;

  33. input I_Rst_n;

  34. output O_SCL;

  35. inout IO_SDA;

  36.  
  37. output O_Init_Done;

  38.  
  39. /******时钟定位模块(测试时钟25MHz),定位SCL的高电平中心,与SCL的低电平中心,产生100kHz的SCL*******/

  40. parameter Start_Delay=8'd24;//开始时SDA拉低电平持续的时间,共用计数器下应小于SCL_HIGH2LOW-1

  41. parameter Stop_Delay=8'd99;//一次读/写结束后SDA拉高电平的时间,共用计数器下应小于SCL_HIGH2LOW-1

  42. parameter SCL_Period=8'd249;//测试板时钟为25MHz,100KHz为250个Clk

  43. parameter SCL_LOW_Dest=8'd187;//时钟判定高电平在前,低电平在后,低电平中央为3/4个周期,187.5个Clk

  44. parameter SCL_HIGH2LOW=8'd124;//电平翻转位置,1/2个SCL周期,125个Clk

  45. reg [7:0] R_SCL_Cnt;

  46. reg R_SCL_En;

  47.  
  48. assign O_SCL=(R_SCL_Cnt<=SCL_HIGH2LOW)?1'b1:1'b0;//SCL 时钟输出

  49.  
  50. always @ (posedge I_Clk_in or negedge I_Rst_n)

  51. begin

  52. if (~I_Rst_n)

  53. begin

  54. R_SCL_Cnt<=8'b0;

  55. end

  56. else

  57. begin

  58. if (R_SCL_En)

  59. if (R_SCL_Cnt==SCL_Period)

  60. R_SCL_Cnt<=8'b0;

  61. else

  62. R_SCL_Cnt<=R_SCL_Cnt+8'b1;

  63. else

  64. R_SCL_Cnt<=8'b0;

  65. end

  66. end

  67.  
  68.  
  69. /***写入延迟计数器***/

  70. parameter T_Delay=8'd249;

  71. reg R_Delay_En;

  72. reg [7:0] R_Delay_Cnt;

  73.  
  74. always @ (posedge I_Clk_in or negedge I_Rst_n)

  75. begin

  76. if (~I_Rst_n)

  77. begin

  78. R_Delay_Cnt<=8'd0;

  79. end

  80. else

  81. begin

  82. if (R_Delay_En)

  83. R_Delay_Cnt<=R_Delay_Cnt+8'd1;

  84. else

  85. R_Delay_Cnt<=8'd0;

  86. end

  87. end

  88.  
  89. /***I2C写模块***/

  90. parameter Slave_Addr=7'b111_0000; //从机地址

  91. reg [8:0] R_Word_Cnt; //保存写入的子寄存器个数

  92. reg [5:0] R_State;

  93. reg [7:0] R_Rom_Addr_Addr; //写入寄存器地址的地址

  94. reg [7:0] R_Rom_Data_Addr; //写入数据的地址

  95. reg R_I_O_SET; //SDA管脚输入输出选择线

  96. reg R_SDA_t; //三态门的输出管教

  97. reg O_Init_Done; //指示芯片内部寄存器是否初始化完成

  98.  
  99. wire[6:0] W_Slave_Addr; //从机地址

  100. wire[7:0] W_Reg_Addr; //从机子寄存器地址

  101. wire[7:0] W_Reg_Data; //子寄存器数据

  102. assign W_Slave_Addr=Slave_Addr;

  103.  
  104. always @ (posedge I_Clk_in or negedge I_Rst_n)

  105. begin

  106. if (~I_Rst_n)

  107. begin

  108. R_State<=6'b0;

  109. R_SCL_En<=1'b0;

  110. R_Delay_En<=1'b0;

  111. R_Word_Cnt<=5'b0;

  112. R_Rom_Addr_Addr<=8'b0;

  113. R_Rom_Data_Addr<=8'b0;

  114. R_SDA_t<=1'b1; //SDA默认拉高

  115. R_I_O_SET<=1'b1; //默认SDA为输出

  116. O_Init_Done<=1'b0;

  117. end

  118. else

  119. begin

  120. case (R_State)

  121. 6'd0: //启动位

  122. begin

  123. R_SCL_En<=1'b1; //SCL时钟启动

  124. R_Delay_En<=1'b0; //延迟时钟关闭

  125. if (R_SCL_Cnt==Start_Delay)

  126. begin

  127. R_SDA_t<=1'b0; //启动位时,在SCL高电平拉低SDA

  128. R_State<=R_State+6'd1;

  129. end

  130. else

  131. R_State<=R_State;

  132. end

  133. 6'd1,6'd2,6'd3,6'd4,6'd5,6'd6,6'd7: //写入从机7位地址

  134. begin

  135. if (R_SCL_Cnt==SCL_LOW_Dest)

  136. begin

  137. R_SDA_t<=W_Slave_Addr[6'd7-R_State]; //从MSB-LSB顺序写入

  138. R_State<=R_State+6'd1;

  139. end

  140. else

  141. R_State<=R_State;

  142. end

  143. 6'd8: //写入写标志0

  144. begin

  145. if (R_SCL_Cnt==SCL_LOW_Dest)

  146. begin

  147. R_SDA_t<=1'b0; //从MSB-LSB顺序写入

 
  1. R_State<=R_State+6'd1;

  2. end

  3. else

  4. R_State<=R_State;

  5. end

  6. 6'd9: //第8个时钟下降沿处开始(跳过)检测ACK

  7. begin

  8. if (R_SCL_Cnt==SCL_HIGH2LOW)

  9. begin

  10. R_I_O_SET<=1'b0; //SDA输出转输入

  11. R_State<=R_State+6'd1;

  12. end

  13. else

  14. R_State<=R_State;

  15. end

  16. 6'd10: //第9个时钟下降沿处停止检测ACK

  17. begin

  18. if (R_SCL_Cnt==SCL_HIGH2LOW)

  19. begin

  20. R_I_O_SET<=1'b1; //占用总线用于下次发送

  21. R_SDA_t<=1'b0;

  22. R_State<=R_State+6'd1;

  23. end

  24. else

  25. R_State<=R_State;

  26. end

  27. 6'd11,6'd12,6'd13,6'd14,6'd15,6'd16,6'd17,6'd18://写入从机子寄存器8位地址

  28. begin

  29. if (R_SCL_Cnt==SCL_LOW_Dest)

  30. begin

  31. R_SDA_t<=W_Reg_Addr[6'd18-R_State]; //从MSB-LSB写入子寄存器地址

  32. R_State<=R_State+6'd1;

  33. end

  34. else

  35. R_State<=R_State;

  36. end

  37. 6'd19: //第8个时钟下降沿开始(跳过)检测ACK

  38. begin

  39. if (R_SCL_Cnt==SCL_HIGH2LOW)

  40. begin

  41. R_I_O_SET<=1'b0; //SDA输出转输入

  42. R_State<=R_State+6'd1;

  43. end

  44. else

  45. R_State<=R_State;

  46. end

  47. 6'd20:

  48. begin

  49. if (R_SCL_Cnt==SCL_HIGH2LOW)

  50. begin

  51. R_I_O_SET<=1'b1; //占用总线用于下次发送

  52. R_SDA_t<=1'b0;

  53. R_State<=R_State+6'd1;

  54. end

  55. else

  56. R_State<=R_State;

  57. end

  58. 6'd21,6'd22,6'd23,6'd24,6'd25,6'd26,6'd27,6'd28: //写入子寄存器数据

  59. begin

  60. if (R_SCL_Cnt==SCL_LOW_Dest)

  61. begin

  62. R_SDA_t<=W_Reg_Data[6'd28-R_State]; //从MSB-LSB顺序写入子寄存器中数值

  63. R_State<=R_State+6'd1;

  64. end

  65. else

  66. R_State<=R_State;

  67. end

  68. 6'd29://第8个时钟下降沿开始(跳过)检测ACK

  69. begin

  70. if (R_SCL_Cnt==SCL_HIGH2LOW)

  71. begin

  72. R_I_O_SET<=1'b0; //SDA输出转输入

  73. R_State<=R_State+6'd1;

  74. end

  75. else

  76. R_State<=R_State;

  77. end

  78. 6'd30:

  79. begin

  80. if (R_SCL_Cnt==SCL_HIGH2LOW)

  81. begin

  82. R_I_O_SET<=1'b1; //占用总线用于下次发送

  83. R_SDA_t<=1'b0;

  84. R_State<=R_State+6'd1;

  85. end

  86. else

  87. R_State<=R_State;

  88. end

  89. 6'd31: //停止位

  90. begin

  91. if (R_SCL_Cnt==Stop_Delay)

  92. begin

  93. R_SDA_t<=1'b1; //SCL高电平期间拉高SDA总线表示停止信号

  94. R_SCL_En<=1'b0;//SCL时钟使能关闭

  95. R_Delay_En<=1'b1;//由于数据写入从机寄存器需要时间,在结束位发送后,延迟一段时间再次开始下一次传输

  96. R_State<=R_State+6'd1;//开始进入等待数据写入寄存器状态

  97. end

  98. else

  99. R_State<=R_State;

  100. end

  101. 6'd32:

  102. begin

  103. if (R_Delay_Cnt==T_Delay)

  104. begin

  105. R_Word_Cnt<=R_Word_Cnt+8'd1;

  106. R_Delay_En<=1'b0; //定时时间到,则定时时钟停止

  107. if (R_Word_Cnt==8'd252) //当全部寄存器写入时,不在返回初始状态,初始化完成标志拉高

  108. begin

  109. R_State<=R_State;

  110. O_Init_Done<=1'b1;

  111. end

  112. else

  113. begin

  114. R_State<=6'd0;

  115. R_Rom_Addr_Addr<=R_Rom_Addr_Addr+8'd1;

  116. R_Rom_Data_Addr<=R_Rom_Data_Addr+8'd1;

  117. end

  118. end

  119. else

  120. R_State<=R_State;

  121. end

  122. default:

  123. begin

  124. R_State<=6'b0;

  125. R_SCL_En<=1'b0;

  126. R_Delay_En<=1'b0;

  127. R_Word_Cnt<=5'b0;

  128. R_SDA_t<=1'b1; //SDA默认拉高

  129. R_I_O_SET<=1'b1; //默认SDA为输出

  130. O_Init_Done<=1'b0;

  131. end

  132. endcase

  133. end

  134. end

  135.  
  136. /***三态门定义***/

  137. assign IO_SDA=(R_I_O_SET)?R_SDA_t:1'bz;

  138.  
  139.  
  140.  
  141. /***SI5338数据模块***/

  142. ROM_Si5338_addr_252x8bit ROM_Reg_Addr (

  143. .clka(I_Clk_in), // input clka

  144. .addra(R_Rom_Addr_Addr), // input [7 : 0] addra

  145. .douta(W_Reg_Addr) // output [7 : 0] douta

  146. );

  147.  
  148. ROM_Si5338_data_252x8bit ROM_Reg_Data (

  149. .clka(I_Clk_in), // input clka

  150. .addra(R_Rom_Data_Addr), // input [7 : 0] addra

  151. .douta(W_Reg_Data) // output [7 : 0] douta

  152. );

  153.  
  154. endmodule

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
FPGA i2c主机是指在FPGA芯片上实现I2C主机模块。根据引用的介绍,可以有多种实现方式,其一种是通过对硬件的I2C电路进行编程控制实现,另一种是使用模拟GPIO的时序方法实现。在FPGA,常常使用后者的方式来实现I2C主机。这种方式可以通过模拟时序来模拟I2C通讯协议的工作过程。 引用提到,编写FPGAI2C主从机模块的时候,可以参考EEPROM的手册。尽管引用的示例是一个回环测试项目,并没有实现读写EEPROM的程序,但它提供了一个方便的基础框架,可以在以后的开发直接使用。 引用提到,FPGAI2C主机模块可以采用三段式状态机的方式进行编写。这种方法可以更容易理解和维护,并且可以根据自己的理解进行状态机的划分。当然,这并不是唯一的实现方式,可以根据具体需求和理解来选择合适的方法。 综上所述,FPGA i2c主机是指在FPGA芯片上实现I2C主机模块,可以通过模拟GPIO的时序方法实现,并可以参考EEPROM的手册进行开发,常常使用三段式状态机的方式进行编写。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [I2C总线通讯协议主机模块FPGA实现](https://blog.csdn.net/shen_you/article/details/78628839)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [【FPGA】十一、I2C通信回环](https://blog.csdn.net/weixin_62912626/article/details/128518338)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值