当前位置:   article > 正文

FPGA常见接口及逻辑实现(二)—— I2C_fpga管脚i2c

fpga管脚i2c

一、I2C协议简介

虽然上期说了接下来要更新SPI,但是由于我的板子没有SPI外设,只仿真也太不像话了,所以还是先写I2C了,等买个小SPI模块再更新SPI。

I2C(Inter-Integrated Circuit)是一种通用的总线协议。它是由Philips(飞利浦)公司,现NXP(恩智浦)半导体开发的一种简单的双向两线制总线协议标准。

对于硬件设计人员来说,只需要2个管脚,极少的连接线和面积,就可以实现芯片间的通讯,对于软件开发者来说,可以使用同一个I2C驱动库,来实现实现不同器件的驱动,大大减少了软件的开发时间。极低的工作电流,降低了系统的功耗,完善的应答机制大大增强通讯的可靠性。

I2C也是一种低速接口,常见工作速率主要有以下五种:

1.标准模式(Standard):不超过100Kbps

2.快速模式(Fast):不超过400Kbps

3.快速模式+(Fast-Plus):不超过1Mbps

4.高速模式(High-Speed):不超过3.4Mbps

5.超快模式(Ultra-Fast):不超过5Mbps(单向传输)

这里的速度单位bps是比特每秒,指数据传输的速率,但是由于I2C总线每个时钟周期只传输一比特数据,所以这个速率也等于I2C总线时钟的频率,即Hz。

由于I2C总线的时序比起串口来说较为复杂,本人对于I2C的时序有一些个人的理解,本期就粗略的介绍一下I2C的时序,在讲解时序之前先明确一些定义:

MASTER:主机,读写操作的发起方。

SLAVE:从机,读写操作的接收方。

SCL:I2C总线的时钟信号,由主机产生,接上拉电阻,空闲为高电平。

SDA:I2C总线的数据信号,接上拉电阻,空闲为高电平。

START:SCL为高时SDA的下降沿。

STOP:SCL为高时SDA的上升沿。

ACK:SDA低电平。

NO_ACK:SDA高电平。

I2C总线一次写入的时序如下:

START为起始信号,SDA下降沿以后,在SCL的低电平发送数据,图中第一个字节叫做CONTROL BYTE,是由七位从机器件ID和一位读写控制位构成的,至于图中为什么是1010xxB,主要是一般从机地址都是1010xxx;最后一位0是读写控制位,0代表写,1代表读。最后有一位响应位(ACK),代表从机接收到主机发送的内容并示意主机继续。

下一个字节开始就是数据操作了,图中把这个字节叫做字地址,但是地址归根结底也是一个八位数据,发送完一个字节的数据又是一个响应位,从机成功响应后接着发下一个数据。只要从机一直响应,主机可以一直发下去,直到从机响应失败,或者主机不想继续发送了,主动产生一个停止位。

I2C总线读时序:

读时序和写时序类似,也是先发器件ID和读写标志,然后进行数据操作,但是请注意,可以看到第一个起始信号后的读写信号是0,代表本次操作是写操作,然后下一个START之后的读写控制位才是1,代表本次操作才是读操作,因为第一次操作要先发送读取的地址,第二次操作再从刚刚发送的地址处读出数据。

很多文章将这一整个时序视为I2C的一次读操作,第二次的START叫做RESTART即重启信号,但是这其实就是一次写操作和一次读操作,事实证明,写完地址过一段时间再进行读操作,也能正确的读出该地址的数据,所以也没有什么RESTART,就是短时间内的第二次START,但是为了操作方便,还是按照这种方式来编写代码。

所以I2C的时序其实很简单,启动后第一个字节发送器件ID和读写控制位,等待响应,响应后根据读写控制位来进入写操作或读操作,每操作一个字节就等待响应,响应成功后进行下一个字节的操作,直到响应失败或主机停止。

二、I2C总线驱动verilog实现思路

上期说过verilog的编写思路主要就是计数器和状态机,根据上一部分的讲解,可以看到I2C的时序还是稍微复杂一点,只用计数器实现还是很不方便的,于是这次我将用状态机架构来实现I2C的主机和从机。

根据第一部分的时序总结,可以将一次读写操作分为以下几个状态:

平时为空闲状态(IDLE),开始信号(START/RESTART),器件ID和读写标志(DEVICE_ID),响应(ACK),写数据(WRITE),读数据(READ),结束信号(DONE)。

根据这个状态机做出波形图如下:

其中还有一个比特计数器,来计数当前操作的比特。

这个状态机已经涵盖了I2C所有可能的状态,但是响应(ACK)是双向的,当主机写的时候,是从机响应,当主机读的时候,是主机响应,所以还要将响应(ACK)分为主机响应(M_ACK)和从机响应(S_ACK),总共就是九种状态。

编写状态机的代码,从绘制状态转移图开始:

然后根据上图编写状态机即可。

编写状态机时一般都采用三段式状态机,具体什么是三段式状态机本文就不多说了,状态机编码除非状态多的离谱不然一般都采用独热码。

SDA的时序基本已解决,接下来看SCL的时序,I2C协议规定在SCL的低电平更新数据,在SCL的高电平采样数据,而且一般都是在低电平的最中心更新数据,所以很多教程都是用计数器生成SCL的过程中在中心位置产生脉冲来作为更新数据和采样数据的信号,但是实际器件很多时候并不需要那么准确的在中心位置更新和采样,比如我读写过的EEPROM都是直接取SCL下降沿作为ACK的开始输出低电平,下一个下降沿再释放总线,所以最好让数据更新点可调,可以满足各种情况,复用性高。

对此我们可以直接在SCL的变化沿进行数据操作,然后将输出的SCL进行一定周期的延时,这样我们可以自由的控制输出SCL的相位,还能简化代码编写的过程。

如上图,可以看出来只要在SCL每个周期的上升沿更新数据,下降沿采样数据即可,而起始位和结束位同样在下降沿。

上述内容主要是编写I2C主机的思路,为了将协议完整掌握,我们还要编写一个从机,从机一般都是用来和寄存器空间对接的,所以用户接口可以留ram接口,方便操作内存,时序方面类比主机,只不过要更精简一点,因为大部分时间都是主机在操作总线,所以像START和DONE这种状态从机就不需要了。

附上状态转移图:

其中中间第三行的状态是WAIT,没注意被遮住了,我的我的。

旁边是整理思路的时候写的,有些是错的,建议别看,以上就是编写思路,接下来介绍根据以上思路编写的代码。

三、I2C主机的verilog实现

我编写接口的理念是结构简单,复用性高,对于I2C主机,首先想到需要兼容的就是总线速度,所以要将I2C总线的速率参数化,除了速度,I2C还有个7位地址和10位地址的区别,不过这个问题不是问题,不管是7位地址还是10位地址,I2C总线的操作时序都是没有变化的,只是前两个字节都需要发送器件ID,这种事交给发送控制端去考虑就好,接口只需要负责兼容。

除此以外还有前文提到的SCL延时功能,也可以通过参数配置。

端口部分留下和控制器交互的操作启动输入,操作结束输出,操作长度,I2C必须的读写标志,器件ID,还有和fifo交互的数据接口即可。

综上所述,I2C主机的端口如下:

  1. module i2c_master #(
  2. parameter SYS_CLK = 50_000_000, // 输入时钟周期,单位为 Hz
  3. parameter IIC_FREQ = 100_000, // 总线速度,单位为 Hz
  4. parameter SCL_DELAY_OW = 0, // 时钟线延延时重载使能
  5. parameter SCL_DELAY_USR = 0 // 时钟线用户输入延时,单位为 周期(输入时钟)
  6. )(
  7. input clk, // 输入时钟
  8. input rst_n, // 同步复位
  9. input op_start, // 启动信号
  10. output op_done, // 结束信号
  11. input [3:0] wr_len, // 写操作长度
  12. input [3:0] rd_len, // 读操作长度
  13. input [1:0] rw_flag, // 读写标志 1 : read, 0 : write.
  14. input [6:0] device_id, // 器件ID
  15. output dreq, // 数据请求
  16. input [7:0] din, // 数据输入
  17. output dvld, // 数据有效
  18. output [7:0] dout, // 数据输出
  19. output wire scl_out, // IIC时钟输出
  20. output wire scl_ctrl, // IIC时钟线控制
  21. input wire sda_in, // IIC数据输入
  22. output wire sda_out, // IIC数据输出
  23. output wire sda_ctrl // IIC数据线控制
  24. );

可以看到端口声明中I2C的接口并不是scl和sda,而是分成了输入输出和控制,这是因为I2C总线是多主多从的协议,总线在不用的时候就要释放掉以便其他器件操作总线,所以scl和sda都是以inout的形式接入FPGA的,对于inout port,规范的操作是在top module把它分成in,out和tri三个信号接入其他模块,具体操作的代码是:

  1. assign scl = scl_ctrl ? scl_out : 1'bz;
  2. assign sda = sda_ctrl ? sda_out : 1'bz;
  3. assign scl_in = scl;
  4. assign sda_in = sda;

然后是变量声明和组合逻辑,代码如下:

  1. // -------------------- Declaration --------------------
  2. // SCL相对于输入时钟的计数
  3. localparam SCL_CYCLE = SYS_CLK / IIC_FREQ;
  4. // 实际SCL延迟,当重载参数为1时选用用户定义的延迟,否则使用默认的1/4计数延迟
  5. localparam SCL_DELAY = SCL_DELAY_OW ? SCL_DELAY_USR : SCL_CYCLE/4 - 1;
  6. // 状态机编码
  7. localparam IDLE = 8'b0000_0000;
  8. localparam START = 8'b0000_0001;
  9. localparam RE_ST = 8'b0000_0010;
  10. localparam DEVID = 8'b0000_0100;
  11. localparam WRITE = 8'b0000_1000;
  12. localparam READ = 8'b0001_0000;
  13. localparam M_ACK = 8'b0010_0000;
  14. localparam S_ACK = 8'b0100_0000;
  15. localparam DONE = 8'b1000_0000;
  16. // 寄存器声明
  17. reg start_ff1;
  18. reg start_ff2;
  19. reg start_reg;
  20. reg read_flag;
  21. reg op_type;
  22. reg iic_clk;
  23. reg [9:0] clk_cnt;
  24. reg [3:0] bit_cnt;
  25. reg [3:0] wr_byte_cnt;
  26. reg [3:0] rd_byte_cnt;
  27. reg iic_busy;
  28. reg [7:0] iic_dout;
  29. reg [7:0] iic_din;
  30. reg [7:0] cur_state;
  31. reg [7:0] nxt_state;
  32. reg [127:0] state_ascii;
  33. reg slave_ack;
  34. reg delay_array [0:SCL_DELAY-1];
  35. // 线网声明
  36. wire scl = delay_array[SCL_DELAY-1];
  37. wire update_edge = clk_cnt == SCL_CYCLE/2 - 1;
  38. wire latch_edge = clk_cnt == SCL_CYCLE - 1;
  39. wire one_byte = bit_cnt == 7;
  40. // 输出信号连接
  41. assign scl_out = scl;
  42. assign scl_ctrl = iic_busy;
  43. assign sda_out = iic_dout[7];
  44. assign sda_ctrl = iic_busy & (cur_state != S_ACK) & (cur_state != READ);
  45. assign dout = iic_din;
  46. assign dreq = cur_state == WRITE & one_byte & update_edge;
  47. assign dvld = cur_state == READ & one_byte & latch_edge;
  48. assign op_done = ((wr_byte_cnt == wr_len & op_type == 0)|(rd_byte_cnt == rd_len & op_type == 1)) & iic_busy;

时序逻辑代码如下:

  1. // -------------------- I2C start --------------------
  2. // 接收开始信号并寄存
  3. always @(negedge clk) begin
  4. if(!rst_n) begin
  5. start_ff1 <= 0;
  6. start_ff2 <= 0;
  7. end else begin
  8. start_ff1 <= op_start;
  9. start_ff2 <= start_ff1;
  10. end
  11. end
  12. always @(negedge clk) begin
  13. if(!rst_n)
  14. start_reg <= 1'b0;
  15. else if(start_ff1 & ~start_ff2)
  16. start_reg <= 1'b1;
  17. else if(cur_state == START)
  18. start_reg <= 1'b0;
  19. end
  20. always @(negedge clk) begin
  21. if(!rst_n)
  22. read_flag <= 1'b0;
  23. else if(cur_state == RE_ST)
  24. read_flag <= 1'b0;
  25. else if(cur_state == START)
  26. read_flag <= rw_flag[1];
  27. end
  28. // -------------------- I2C busy status --------------------
  29. // 根据状态产生总线忙碌信号
  30. always @(negedge clk) begin
  31. if(!rst_n)
  32. iic_busy <= 0;
  33. else if((cur_state == START | cur_state == RE_ST) & latch_edge)
  34. iic_busy <= 1;
  35. else if(cur_state == DONE & latch_edge)
  36. iic_busy <= 0;
  37. end
  38. // -------------------- SCL generator --------------------
  39. // 产生SCL
  40. always @(posedge clk) begin
  41. if (!rst_n)
  42. iic_clk <= 1'b0;
  43. else if (update_edge)
  44. iic_clk <= 1'b1;
  45. else if (latch_edge)
  46. iic_clk <= 1'b0;
  47. end
  48. // SCL计数器
  49. always @(posedge clk) begin
  50. if (!rst_n) begin
  51. clk_cnt <= 10'd0;
  52. end
  53. else begin
  54. if (latch_edge)
  55. clk_cnt <= 10'd0;
  56. else
  57. clk_cnt <= clk_cnt + 1'd1;
  58. end
  59. end
  60. // SCL输出延时
  61. always @(posedge clk)
  62. delay_array[0] <= iic_clk;
  63. genvar i;
  64. generate
  65. for (i = 0;i < SCL_DELAY-1;i = i + 1) begin
  66. always @(posedge clk)
  67. delay_array[i+1] <= delay_array[i];
  68. end
  69. endgenerate
  70. // -------------------- FSM --------------------
  71. // 状态机第一段,时序逻辑切换状态
  72. always @(posedge clk) begin
  73. if(!rst_n)
  74. cur_state <= IDLE;
  75. else if(update_edge)
  76. cur_state <= nxt_state;
  77. end
  78. // 状态机第二段,组合逻辑产生次态
  79. always @(*) begin
  80. case (cur_state)
  81. IDLE:begin
  82. if(start_reg) // 开始信号为高进入开始状态
  83. nxt_state <= START;
  84. else if(read_flag)
  85. nxt_state <= RE_ST;
  86. else
  87. nxt_state <= IDLE;
  88. end
  89. START:begin
  90. if(iic_busy) // 总线忙碌进入发送ID状态
  91. nxt_state <= DEVID;
  92. else
  93. nxt_state <= START;
  94. end
  95. RE_ST:begin
  96. if(iic_busy) // 总线忙碌进入发送ID状态
  97. nxt_state <= DEVID;
  98. else
  99. nxt_state <= RE_ST;
  100. end
  101. DEVID:begin
  102. if(one_byte) // 发送一个字节进入等待从机响应状态
  103. nxt_state <= S_ACK;
  104. else
  105. nxt_state <= DEVID;
  106. end
  107. WRITE:begin
  108. if(one_byte) // 写一个字节进入等待从机响应状态
  109. nxt_state <= S_ACK;
  110. else
  111. nxt_state <= WRITE;
  112. end
  113. READ:begin
  114. if(one_byte) // 读一个字节进入主机响应状态
  115. nxt_state <= M_ACK;
  116. else
  117. nxt_state <= READ;
  118. end
  119. M_ACK:begin
  120. if(op_done) // 主机不响应进入结束状态
  121. nxt_state <= DONE;
  122. else // 主机响应进入读状态
  123. nxt_state <= READ;
  124. end
  125. S_ACK:begin
  126. if(!slave_ack) // 从机不响应进入结束状态
  127. nxt_state <= DONE;
  128. else if(op_done) // 主机操作完成进入结束状态
  129. nxt_state <= DONE;
  130. else if(op_type) // 从机响应且为读操作进入读状态
  131. nxt_state <= READ;
  132. else // 从机响应且为写操作进入写状态
  133. nxt_state <= WRITE;
  134. end
  135. DONE:begin
  136. if(!iic_busy) // 总线空闲则进入空闲状态
  137. nxt_state <= IDLE;
  138. else
  139. nxt_state <= DONE;
  140. end
  141. default: nxt_state <= IDLE;
  142. endcase
  143. end
  144. // 状态机第三段,各个状态输出信号
  145. // SCL上升沿更新数据
  146. always @(posedge clk) begin
  147. if(!rst_n)
  148. bit_cnt <= 0;
  149. else if(update_edge) begin
  150. if(cur_state == DEVID | cur_state == WRITE | cur_state == READ)
  151. bit_cnt <= bit_cnt + 1;
  152. else
  153. bit_cnt <= 0;
  154. end
  155. end
  156. always @(posedge clk) begin
  157. if(!rst_n)
  158. iic_dout <= 0;
  159. else if(update_edge) begin
  160. if(cur_state == START)
  161. iic_dout <= {device_id,rw_flag[0]};
  162. else if(cur_state == RE_ST)
  163. iic_dout <= {device_id,rw_flag[1]};
  164. else if(cur_state == WRITE | cur_state == DEVID)
  165. iic_dout <= {iic_dout[6:0],1'b0};
  166. else if(cur_state == READ)
  167. iic_dout <= (rd_byte_cnt == rd_len - 1) ? 8'h80 : 8'h00;
  168. else if(cur_state == S_ACK)
  169. iic_dout <= slave_ack ? din : 8'h00;
  170. else if(cur_state == M_ACK)
  171. iic_dout <= 8'h00;
  172. else if(cur_state == IDLE)
  173. iic_dout <= 8'h00;
  174. end
  175. end
  176. always @(posedge clk) begin
  177. if(!rst_n)
  178. wr_byte_cnt <= 0;
  179. else if(cur_state == WRITE & one_byte & update_edge)
  180. wr_byte_cnt <= wr_byte_cnt + 1;
  181. else if(cur_state == DONE)
  182. wr_byte_cnt <= 0;
  183. end
  184. always @(posedge clk) begin
  185. if(!rst_n)
  186. rd_byte_cnt <= 0;
  187. else if(cur_state == READ & one_byte & update_edge)
  188. rd_byte_cnt <= rd_byte_cnt + 1;
  189. else if(cur_state == DONE)
  190. rd_byte_cnt <= 0;
  191. end
  192. // SCL下降沿采样数据
  193. always @(posedge clk) begin
  194. if(!rst_n)
  195. iic_din <= 0;
  196. else if(latch_edge) begin
  197. if(cur_state == READ)
  198. iic_din[7 - bit_cnt] <= sda_in;
  199. else
  200. iic_din <= 0;
  201. end
  202. end
  203. always @(posedge clk) begin
  204. if(!rst_n)
  205. op_type <= 0;
  206. else if(cur_state == DEVID & one_byte & latch_edge)
  207. op_type <= sda_in;
  208. else if(cur_state == IDLE)
  209. op_type <= 0;
  210. end
  211. always @(posedge clk) begin
  212. if(!rst_n)
  213. slave_ack <= 0;
  214. else if(latch_edge) begin
  215. if(cur_state == S_ACK)
  216. slave_ack <= ~sda_in;
  217. else
  218. slave_ack <= 0;
  219. end
  220. end
  221. // simulation only
  222. always @(*) begin
  223. case(cur_state)
  224. IDLE:state_ascii <= "IDLE";
  225. START:state_ascii <= "START";
  226. RE_ST:state_ascii <= "RESTART";
  227. DEVID:state_ascii <= "DEVID";
  228. WRITE:state_ascii <= "WRITE";
  229. READ:state_ascii <= "READ";
  230. M_ACK:state_ascii <= "M_ACK";
  231. S_ACK:state_ascii <= "S_ACK";
  232. DONE:state_ascii <= "DONE";
  233. default:state_ascii <= "UNKNOWN";
  234. endcase
  235. end
  236. endmodule

本次编写的I2C模块和串口模块的发送方式不同,选用了移位输出。

此次I2C的代码中没有sda和scl的同步链模块,这是因为sda和scl都是inout类型,由于inout是在顶层操作的,所以把同步工作也留给顶层去处理,而且I2C读写过程中由于总线不停的被占用和释放,经常产生毛刺,还需要对SDA做一个简单的滤波工作再接入I2C模块。

虽然代码看上去还是有点多,但是这已经是我能想到的兼顾复用性和稳定性的最简单的I2C主机了,代码大部分都在编写状态机,组合逻辑的部分也很简单,都是基于状态机和SCL变化沿的输出,很容易理解。

读写标志信号rw_flag有三种情况,为0时,写操作,写wr_len个字节的数据后停止;为1时是读操作,直接开始读rd_len个字节的数据后停止;为2时是先写后读的操作,也就是大部分器件的读指定寄存器的操作,先写wr_len个字节的数据后停止,再重新开始然后读rd_len个字节的数据后停止。

还有最后的代码,备注了simulation only的那部分,是一个仿真小技巧,因为vivado在仿真过程中,状态机的值没有办法显示为定义的状态名,仿真的时候就会很不方便,状态机全是0001,0010这种,很难分的清这个值是哪个状态,因此我们声明一个变量,每当状态切换时,同步把变量名作为字符串赋给这个变量,然后在仿真界面拖入这个变量,选择Radix -> ASCII,这样就能在仿真过程中显示状态名啦,而且由于这个变量完全没有使用,所以在综合的过程中就会优化掉,没有任何影响。

最后,附上顶层sda滤波模块的代码:

滤波模块:

  1. module filter(
  2. input wire clk,
  3. input wire sin,
  4. output wire sout
  5. );
  6. (* ASYNC_REG = "true" *)reg in_ff1;
  7. (* ASYNC_REG = "true" *)reg in_ff2;
  8. (* ASYNC_REG = "true" *)reg in_ff3;
  9. (* ASYNC_REG = "true" *)reg out_ff;
  10. always @(posedge clk) begin
  11. in_ff1 <= ~sin;
  12. in_ff2 <= in_ff1;
  13. in_ff3 <= in_ff2;
  14. end
  15. always @(posedge clk) begin
  16. if(in_ff2 == in_ff1)
  17. out_ff <= in_ff2;
  18. end
  19. assign sout = ~out_ff;
  20. endmodule

由于I2C没有从机无法仿真,没有响应直接就停止操作了,所以本文的仿真环节留到从机编写完成之后再进行。

四、I2C从机的verilog实现

I2C从机比起主机要简单很多,只用给出响应和返回数据即可,数据接收和串口类似,不过I2C是MSB在前LSB在后。从机可以直接在SCL的变化沿进行数据操作,所以不需要延时。

I2C从机不需要什么参数,只需要设定从机ID即可,和其他模块的接口就按照上文所说的,用ram接口,ram接口一般包含en,we,addr,din,dout。en使能时读取addr处的数据到dout,we和en同时使能时将din写入addr处,为了减少接口数量,就不要en信号了,让en总是处于使能状态即可,综上所述,I2C从机模块的代码如下:

  1. `timescale 1ns / 1ps
  2. module i2c_slave #(
  3. parameter DEVICE_ID = 7'b1010010 // 7'h52 从机ID
  4. )(
  5. input clk, // 输入时钟
  6. input rst_n, // 同步复位
  7. input SCL_in, // I2C时钟输入
  8. input SDA_in, // I2C数据输入
  9. output SDA_out, // I2C数据输出
  10. output sda_ctrl, // I2C数据控制
  11. input [7:0] rd_data, // 读ram数据
  12. output [7:0] wr_data, // 写ram数据
  13. output [7:0] op_addr, // 操作ram地址
  14. output reg wr_en // 写ram使能
  15. );
  16. //变量声明
  17. localparam IDLE = 6'b000001;
  18. localparam DEVID = 6'b000010;
  19. localparam WAIT = 6'b000100;
  20. localparam WBACK = 6'b001000;
  21. localparam M_ACK = 6'b010000;
  22. localparam S_ACK = 6'b100000;
  23. reg scl_ff1;
  24. reg scl_ff2;
  25. reg scl_ff3;
  26. reg scl_sync;
  27. reg sda_ff1;
  28. reg sda_ff2;
  29. reg sda_ff3;
  30. reg sda_sync;
  31. reg [5:0] cur_state;
  32. reg [5:0] nxt_state;
  33. reg [63:0] state_ascii;
  34. reg rw_flag;
  35. reg ad_flag;
  36. reg start_flag;
  37. reg done_flag;
  38. reg slave_ack;
  39. reg master_ack;
  40. reg [6:0] deviceid;
  41. reg [7:0] iic_dout;
  42. reg [7:0] iic_din;
  43. reg [3:0] bit_cnt;
  44. reg [7:0] r_addr;
  45. reg [7:0] r_data;
  46. wire scl_rise = scl_ff3 & ~scl_sync;
  47. wire scl_fall = ~scl_ff3 & scl_sync;
  48. wire sda_rise = sda_ff3 & ~sda_sync;
  49. wire sda_fall = ~sda_ff3 & sda_sync;
  50. wire one_byte = bit_cnt == 7;
  51. assign SDA_out = iic_dout[7];
  52. assign sda_ctrl = cur_state == WBACK | cur_state == S_ACK;
  53. assign op_addr = r_addr;
  54. assign wr_data = r_data;
  55. // 输入信号同步链滤波
  56. always @(posedge clk) begin
  57. scl_ff1 <= SCL_in;
  58. scl_ff2 <= scl_ff1;
  59. scl_ff3 <= (scl_ff2 == scl_ff1) ? scl_ff2 : scl_ff3;
  60. scl_sync <= scl_ff2;
  61. end
  62. always @(posedge clk) begin
  63. sda_ff1 <= SDA_in;
  64. sda_ff2 <= sda_ff1;
  65. sda_ff3 <= (sda_ff2 == sda_ff1) ? sda_ff2 : sda_ff3;
  66. sda_sync <= sda_ff2;
  67. end
  68. // 状态机
  69. always @(posedge clk) begin
  70. if(!rst_n)
  71. cur_state <= IDLE;
  72. else if(scl_fall)
  73. cur_state <= nxt_state;
  74. end
  75. always @(*) begin
  76. case (cur_state)
  77. IDLE:begin
  78. if(start_flag)
  79. nxt_state <= DEVID;
  80. else
  81. nxt_state <= IDLE;
  82. end
  83. DEVID:begin
  84. if(one_byte)
  85. nxt_state <= S_ACK;
  86. else
  87. nxt_state <= DEVID;
  88. end
  89. WAIT:begin
  90. if(done_flag)
  91. nxt_state <= IDLE;
  92. else if(one_byte)
  93. nxt_state <= S_ACK;
  94. else
  95. nxt_state <= WAIT;
  96. end
  97. WBACK:begin
  98. if(one_byte)
  99. nxt_state <= M_ACK;
  100. else
  101. nxt_state <= WBACK;
  102. end
  103. M_ACK:begin
  104. if(master_ack)
  105. nxt_state <= WBACK;
  106. if(!master_ack)
  107. nxt_state <= IDLE;
  108. else
  109. nxt_state <= M_ACK;
  110. end
  111. S_ACK:begin
  112. if(rw_flag & slave_ack)
  113. nxt_state <= WBACK;
  114. else
  115. nxt_state <= WAIT;
  116. end
  117. default:nxt_state <= IDLE;
  118. endcase
  119. end
  120. always @(posedge clk) begin
  121. if(!rst_n) begin
  122. iic_dout <= 0;
  123. bit_cnt <= 0;
  124. end else if(scl_fall) begin
  125. case(cur_state)
  126. DEVID:begin
  127. bit_cnt <= bit_cnt + 1;
  128. iic_dout <= (deviceid == DEVICE_ID) ? 8'h00 : 8'hff;
  129. end
  130. WAIT:begin
  131. bit_cnt <= bit_cnt + 1;
  132. iic_dout <= (deviceid == DEVICE_ID) ? 8'h00 : 8'hff;
  133. end
  134. WBACK:begin
  135. bit_cnt <= bit_cnt + 1;
  136. iic_dout <= {iic_dout[6:0],1'b0};
  137. end
  138. M_ACK:begin
  139. bit_cnt <= 0;
  140. end
  141. S_ACK:begin
  142. bit_cnt <= 0;
  143. iic_dout <= rd_data;
  144. end
  145. default:begin
  146. iic_dout <= 0;
  147. bit_cnt <= 0;
  148. end
  149. endcase
  150. end
  151. end
  152. always @(posedge clk) begin
  153. if(!rst_n) begin
  154. r_addr <= 0;
  155. r_data <= 0;
  156. end else if(cur_state == WAIT & scl_fall & one_byte) begin
  157. if(ad_flag) begin
  158. r_addr <= iic_din;
  159. end else begin
  160. r_data <= iic_din;
  161. end
  162. end else if(wr_en)
  163. r_addr <= r_addr + 1;
  164. end
  165. always @(posedge clk) begin
  166. if(!rst_n) begin
  167. deviceid <= 0;
  168. iic_din <= 0;
  169. master_ack <= 0;
  170. slave_ack <= 0;
  171. end else if(scl_rise) begin
  172. case(cur_state)
  173. DEVID:deviceid[6 - bit_cnt] <= sda_sync;
  174. WAIT:iic_din[7 - bit_cnt] <= sda_sync;
  175. M_ACK:master_ack <= ~sda_sync;
  176. S_ACK:slave_ack <= (deviceid == DEVICE_ID);
  177. IDLE:deviceid <= 0;
  178. default:begin
  179. master_ack <= 0;
  180. slave_ack <= 0;
  181. end
  182. endcase
  183. end
  184. end
  185. always @(posedge clk) begin
  186. if(!rst_n)
  187. wr_en <= 0;
  188. else if(cur_state == WAIT & scl_fall & one_byte & !ad_flag)
  189. wr_en <= 1;
  190. else
  191. wr_en <= 0;
  192. end
  193. always @(posedge clk) begin
  194. if(!rst_n)
  195. ad_flag <= 1;
  196. else if(cur_state == WAIT & scl_fall & one_byte & ad_flag)
  197. ad_flag <= 0;
  198. else if(cur_state == IDLE)
  199. ad_flag <= 1;
  200. end
  201. always @(posedge clk) begin
  202. if(!rst_n)
  203. rw_flag <= 0;
  204. else if(cur_state == DEVID & scl_rise & one_byte)
  205. rw_flag <= sda_sync;
  206. else if(cur_state == IDLE)
  207. rw_flag <= 0;
  208. end
  209. always @(posedge clk) begin
  210. if(!rst_n)
  211. start_flag <= 0;
  212. else if(scl_sync & sda_fall)
  213. start_flag <= 1;
  214. else if(cur_state == DEVID)
  215. start_flag <= 0;
  216. end
  217. always @(posedge clk) begin
  218. if(!rst_n)
  219. done_flag <= 0;
  220. else if(scl_sync & sda_rise)
  221. done_flag <= 1;
  222. else if(cur_state == IDLE)
  223. done_flag <= 0;
  224. end
  225. // simulation only
  226. always @(*) begin
  227. case (cur_state)
  228. IDLE:state_ascii <= "IDLE";
  229. DEVID:state_ascii <= "DEVID";
  230. WAIT:state_ascii <= "WAIT";
  231. WBACK:state_ascii <= "WBACK";
  232. M_ACK:state_ascii <= "M_ACK";
  233. S_ACK:state_ascii <= "S_ACK";
  234. default:state_ascii <= "IDLE";
  235. endcase
  236. end
  237. endmodule

一般来说,Verilog代码中应该尽量减少信号的耦合,不同信号应该尽量分开单独编写一个always块,我这里就是懒了,感觉不冲突的信号都写到一起去了,这样并不好,除非是高度关联的信号,不然一般都分开写比较好。

五、I2C主从仿真

现在I2C的主机从机都已经编写完成,可以对其进行仿真了。

I2C的仿真需要一点平时不怎么用的小技巧,众所周知,I2C协议规定时钟线和数据线需要上拉,在空闲时都为高电平,而在仿真里,一条wire不被占用的时候,就成了高阻态z,这样就没法进行仿真了。所以我们需要用pullup语句模拟上拉电阻,这样这条wire在空闲的时候值就是1了。

  1. wire scl;
  2. wire sda;
  3. pullup(sda);
  4. assign sda =
  5. sda_ctrl ? sda_out :
  6. sda_slv ? SDA_out : 1'bz;
  7. assign sda_in = sda;
  8. assign SDA_in = sda;

例化主机和从机,主机使用标准模式速度,使用默认延时,设定从机地址,主机操作地址设定为相同地址,开始仿真:

  1. i2c_master #(
  2. .SYS_CLK (50_000_000), // unit "hz"
  3. .IIC_FREQ (100_000), // unit "hz"
  4. .SCL_DELAY_OW (0), // delay overwrite enable
  5. .SCL_DELAY_USR (20) // delay input by user
  6. ) i2c_master_inst(
  7. .clk (clk),
  8. .rst_n (rst_n),
  9. .op_start (op_start),
  10. .op_done (op_done),
  11. .wr_len (wr_len),
  12. .rd_len (rd_len),
  13. .rw_flag (rw_flag), // 1 : read, 0 : write.
  14. .device_id (device_id),
  15. .dreq (txq),
  16. .din (txd),
  17. .dvld (rxv),
  18. .dout (rxd),
  19. .scl_out (scl),
  20. .sda_in (sda_in),
  21. .sda_out (sda_out),
  22. .sda_ctrl (sda_ctrl));
  23. i2c_slave #(
  24. .DEVICE_ID (7'b1010010) // 'h52
  25. ) i2c_slave_inst(
  26. .clk(clk),
  27. .rst_n(rst_n),
  28. .SCL_in(scl),
  29. .SDA_in(SDA_in),
  30. .SDA_out(SDA_out),
  31. .sda_ctrl(sda_slv),
  32. .rd_data(8'h9a),
  33. .wr_data(),
  34. .op_addr(),
  35. .wr_en());

先看主机时序,START状态SDA产生下降沿,进入写器件ID状态,写完等待从机响应,从机响应后根据读写标志进行读写操作,移位计数器循环计数移位输出。

可以看到在响应位后会有毛刺,这是因为主机是延时了四分之一周期的,而从机是直接在SCL变化沿操作的,所以在下降沿后从机已经完成响应操作并释放了总线,但是主机还没有进行下一步操作,所以总线空闲,呈现高电平,这种情况在实际应用中也不会影响读写结果的,只要在SCL高电平期间,数据保持稳定即可。

读写完成后在一个SCL的高电平器件拉高SDA作为一次操作的结束信号。

然后是从机时序,接收到主机发出的开始信号后,直接进入接收ID的状态,接收到的ID与自身ID相同时做出响应,否则不响应。

因为在响应之后,从机不知道主机是要继续写还是要结束操作,所以称该状态为等待状态,假如主机继续发送数据,从机就继续接收缓存并响应,假如在等待状态下收到主机发出的结束信号,就结束接收进入空闲状态。

六、I2C主机板级验证

终于来到了上板的环节,无论仿真的时序有多完美,没有上板就都是虚的。因为I2C是主从结构,没法环回验证,只能主机从机分开验证,先通过读写板上的EEPROM来验证主机的正确性,再通过主机读写从机来验证从机的正确性。

首先遇到的第一个问题就是如何与I2C主机交互,看代码可以知道我们I2C主机的用户接口比较多,没有匹配的通用接口,即使可以通过VIO控制所有的用户接口,但是这样的模块终究是复用性很差的,那么为了提高模块的复用性,对于这种用户接口很多的模块,可以用寄存器去控制,把所有用户接口和寄存器连接,然后用配置接口去控制寄存器,这样就可以通过配置接口去控制该模块了,常用的配置接口有native ram,axi lite还有Xilinx自己的drp等,为了简便这里就用native ram接口去编写一个寄存器控制模块。

  1. `timescale 1ns / 1ps
  2. module i2c_ctrl(
  3. input clk,
  4. input en,
  5. input we,
  6. input [7:0] din,
  7. output reg [7:0] dout,
  8. input [7:0] addr,
  9. output op_start,
  10. input op_done,
  11. output [3:0] wr_len,
  12. output [3:0] rd_len,
  13. output [1:0] rw_flag,
  14. output [6:0] device_id,
  15. input txq,
  16. output [7:0] txd,
  17. input rxv,
  18. input [7:0] rxd
  19. );
  20. wire [3:0] tx_ptr = reg_tx_buffer_ctrl[3:0];
  21. wire [3:0] rx_ptr = reg_rx_buffer_ctrl[3:0];
  22. // write only
  23. reg [7:0] reg_tx_buffer_0 = 0; // 0x00
  24. reg [7:0] reg_tx_buffer_1 = 0; // 0x01
  25. reg [7:0] reg_tx_buffer_2 = 0; // 0x02
  26. reg [7:0] reg_tx_buffer_3 = 0; // 0x03
  27. reg [7:0] reg_tx_buffer_4 = 0; // 0x04
  28. reg [7:0] reg_tx_buffer_5 = 0; // 0x05
  29. reg [7:0] reg_tx_buffer_6 = 0; // 0x06
  30. reg [7:0] reg_tx_buffer_7 = 0; // 0x07
  31. // read only
  32. reg [7:0] reg_rx_buffer_0 = 0; // 0x08
  33. reg [7:0] reg_rx_buffer_1 = 0; // 0x09
  34. reg [7:0] reg_rx_buffer_2 = 0; // 0x0a
  35. reg [7:0] reg_rx_buffer_3 = 0; // 0x0b
  36. reg [7:0] reg_rx_buffer_4 = 0; // 0x0c
  37. reg [7:0] reg_rx_buffer_5 = 0; // 0x0d
  38. reg [7:0] reg_rx_buffer_6 = 0; // 0x0e
  39. reg [7:0] reg_rx_buffer_7 = 0; // 0x0f
  40. // read - write
  41. reg [7:0] reg_tx_buffer_ctrl = 0; // 0x10
  42. // bit 7: clear buffer, self reset
  43. // bit 3-0: tx_ptr
  44. reg [7:0] reg_rx_buffer_ctrl = 0; // 0x11
  45. // bit 7: clear buffer, self reset
  46. // bit 3-0: rx_ptr
  47. reg [7:0] reg_op_status = 0; // 0x12
  48. // bit 0: op start, self reset
  49. // bit 7: op done
  50. reg [7:0] reg_op_deviceid = 0; // 0x13 8'b01010000 = 8'h50
  51. // bit 6-0: device id
  52. reg [7:0] reg_op_length = 0; // 0x14
  53. // bit 7-0: operate length
  54. reg [7:0] reg_op_rwctrl = 0; // 0x15
  55. // bit 0: read-write flag
  56. reg [7:0] reg_reserve_0 = 0; // 0x16
  57. reg [7:0] reg_reserve_1 = 0; // 0x17
  58. reg [7:0] reg_reserve_2 = 0; // 0x18
  59. reg [7:0] reg_reserve_3 = 0; // 0x19
  60. reg [7:0] reg_reserve_4 = 0; // 0x1a
  61. reg [7:0] reg_reserve_5 = 0; // 0x1b
  62. reg [7:0] reg_reserve_6 = 0; // 0x1c
  63. reg [7:0] reg_reserve_7 = 0; // 0x1d
  64. reg [7:0] reg_reserve_8 = 0; // 0x1e
  65. reg [7:0] reg_reserve_9 = 0; // 0x1f
  66. assign op_start = reg_op_status[0];
  67. assign wr_len = reg_op_length[3:0];
  68. assign rd_len = reg_op_length[7:4];
  69. assign rw_flag = reg_op_rwctrl[1:0];
  70. assign device_id = reg_op_deviceid[6:0];
  71. assign txd =
  72. tx_ptr == 0 ? reg_tx_buffer_0 :
  73. tx_ptr == 1 ? reg_tx_buffer_1 :
  74. tx_ptr == 2 ? reg_tx_buffer_2 :
  75. tx_ptr == 3 ? reg_tx_buffer_3 :
  76. tx_ptr == 4 ? reg_tx_buffer_4 :
  77. tx_ptr == 5 ? reg_tx_buffer_5 :
  78. tx_ptr == 6 ? reg_tx_buffer_6 :
  79. tx_ptr == 7 ? reg_tx_buffer_7 : 8'h00;
  80. always @(posedge clk) begin
  81. if(en) begin
  82. case (addr)
  83. 8'h08:dout <= reg_rx_buffer_0;
  84. 8'h09:dout <= reg_rx_buffer_1;
  85. 8'h0a:dout <= reg_rx_buffer_2;
  86. 8'h0b:dout <= reg_rx_buffer_3;
  87. 8'h0c:dout <= reg_rx_buffer_4;
  88. 8'h0d:dout <= reg_rx_buffer_5;
  89. 8'h0e:dout <= reg_rx_buffer_6;
  90. 8'h0f:dout <= reg_rx_buffer_7;
  91. 8'h10:dout <= reg_tx_buffer_ctrl;
  92. 8'h11:dout <= reg_rx_buffer_ctrl;
  93. 8'h12:dout <= reg_op_status;
  94. 8'h13:dout <= reg_op_deviceid;
  95. 8'h14:dout <= reg_op_length;
  96. 8'h15:dout <= reg_op_rwctrl;
  97. 8'h16:dout <= reg_reserve_0;
  98. 8'h17:dout <= reg_reserve_1;
  99. 8'h18:dout <= reg_reserve_2;
  100. 8'h19:dout <= reg_reserve_3;
  101. 8'h1a:dout <= reg_reserve_4;
  102. 8'h1b:dout <= reg_reserve_5;
  103. 8'h1c:dout <= reg_reserve_6;
  104. 8'h1d:dout <= reg_reserve_7;
  105. 8'h1e:dout <= reg_reserve_8;
  106. 8'h1f:dout <= reg_reserve_9;
  107. default:dout <= dout;
  108. endcase
  109. if(we) begin
  110. case (addr)
  111. 8'h13:reg_op_deviceid <= din;
  112. 8'h14:reg_op_length <= din;
  113. 8'h15:reg_op_rwctrl <= din;
  114. 8'h16:reg_reserve_0 <= din;
  115. 8'h17:reg_reserve_1 <= din;
  116. 8'h18:reg_reserve_2 <= din;
  117. 8'h19:reg_reserve_3 <= din;
  118. 8'h1a:reg_reserve_4 <= din;
  119. 8'h1b:reg_reserve_5 <= din;
  120. 8'h1c:reg_reserve_6 <= din;
  121. default:;
  122. endcase
  123. end
  124. end
  125. end
  126. // -------------------- Tx buffer ctrl --------------------
  127. always @(posedge clk) begin
  128. if(reg_tx_buffer_ctrl[7]) begin
  129. reg_tx_buffer_0 <= 0;
  130. reg_tx_buffer_1 <= 0;
  131. reg_tx_buffer_2 <= 0;
  132. reg_tx_buffer_3 <= 0;
  133. reg_tx_buffer_4 <= 0;
  134. reg_tx_buffer_5 <= 0;
  135. reg_tx_buffer_6 <= 0;
  136. reg_tx_buffer_7 <= 0;
  137. end else if(we) begin
  138. case (addr)
  139. 8'h00:reg_tx_buffer_0 <= din;
  140. 8'h01:reg_tx_buffer_1 <= din;
  141. 8'h02:reg_tx_buffer_2 <= din;
  142. 8'h03:reg_tx_buffer_3 <= din;
  143. 8'h04:reg_tx_buffer_4 <= din;
  144. 8'h05:reg_tx_buffer_5 <= din;
  145. 8'h06:reg_tx_buffer_6 <= din;
  146. 8'h07:reg_tx_buffer_7 <= din;
  147. default:;
  148. endcase
  149. end
  150. end
  151. always @(posedge clk) begin
  152. if(we) begin
  153. if(addr == 8'h10)
  154. reg_tx_buffer_ctrl <= din;
  155. end else if(&reg_reserve_7) begin
  156. reg_tx_buffer_ctrl <= reg_tx_buffer_ctrl & 8'b0111_1111;
  157. end else if(txq) begin
  158. reg_tx_buffer_ctrl[3:0] <= reg_tx_buffer_ctrl[3:0] + 1;
  159. end
  160. end
  161. // -------------------- Tx buffer ctrl --------------------
  162. always @(posedge clk) begin
  163. if(reg_tx_buffer_ctrl[7]) begin
  164. reg_rx_buffer_0 <= 0;
  165. reg_rx_buffer_1 <= 0;
  166. reg_rx_buffer_2 <= 0;
  167. reg_rx_buffer_3 <= 0;
  168. reg_rx_buffer_4 <= 0;
  169. reg_rx_buffer_5 <= 0;
  170. reg_rx_buffer_6 <= 0;
  171. reg_rx_buffer_7 <= 0;
  172. end else if(rxv) begin
  173. case (rx_ptr)
  174. 8'h00:reg_rx_buffer_0 <= rxd;
  175. 8'h01:reg_rx_buffer_1 <= rxd;
  176. 8'h02:reg_rx_buffer_2 <= rxd;
  177. 8'h03:reg_rx_buffer_3 <= rxd;
  178. 8'h04:reg_rx_buffer_4 <= rxd;
  179. 8'h05:reg_rx_buffer_5 <= rxd;
  180. 8'h06:reg_rx_buffer_6 <= rxd;
  181. 8'h07:reg_rx_buffer_7 <= rxd;
  182. default:;
  183. endcase
  184. end
  185. end
  186. always @(posedge clk) begin
  187. if(we) begin
  188. if(addr == 8'h11)
  189. reg_rx_buffer_ctrl <= din;
  190. end else if(&reg_reserve_8) begin
  191. reg_rx_buffer_ctrl <= reg_rx_buffer_ctrl & 8'b0111_1111;
  192. end else if(rxv) begin
  193. reg_rx_buffer_ctrl[3:0] <= reg_rx_buffer_ctrl[3:0] + 1;
  194. end
  195. end
  196. always @(posedge clk) begin
  197. if(we) begin
  198. if(addr == 8'h12)
  199. reg_op_status <= din;
  200. end else if(&reg_reserve_9) begin
  201. reg_op_status <= reg_op_status & 8'b1111_1110;
  202. end else begin
  203. reg_op_status <= reg_op_status | {op_done,7'b0000_000};
  204. end
  205. end
  206. always @(posedge clk) begin
  207. if(we) begin
  208. if(addr == 8'h1d)
  209. reg_reserve_7 <= din;
  210. end else if(!reg_tx_buffer_ctrl[7]) begin
  211. reg_reserve_7 <= 0;
  212. end else begin
  213. reg_reserve_7 <= {reg_reserve_7[6:0],1'b1};
  214. end
  215. end
  216. always @(posedge clk) begin
  217. if(we) begin
  218. if(addr == 8'h1e)
  219. reg_reserve_8 <= din;
  220. end else if(!reg_rx_buffer_ctrl[7]) begin
  221. reg_reserve_8 <= 0;
  222. end else begin
  223. reg_reserve_8 <= {reg_reserve_8[6:0],1'b1};
  224. end
  225. end
  226. always @(posedge clk) begin
  227. if(we) begin
  228. if(addr == 8'h1e)
  229. reg_reserve_9 <= din;
  230. end else if(!reg_op_status[0]) begin
  231. reg_reserve_9 <= 0;
  232. end else begin
  233. reg_reserve_9 <= reg_reserve_9 + 1;
  234. end
  235. end
  236. endmodule

最后用VIO去控制这个ram接口,对寄存器进行读写,就可以轻松控制I2C主机了。再用ILA抓取I2C模块内部的SCL和SDA,方便观察总线波形。编写好的顶层模块如下:

  1. `timescale 1ns / 1ps
  2. module top(
  3. input wire sys_clk,
  4. input wire sys_rst_n,
  5. inout wire scl,
  6. inout wire sda
  7. );
  8. wire clk_50M;
  9. wire sync_rst_n;
  10. wire write;
  11. wire read;
  12. wire [7:0] wr_data;
  13. wire [7:0] rd_data;
  14. wire [7:0] rw_addr;
  15. wire en;
  16. wire we;
  17. wire [7:0] din;
  18. wire [7:0] dout;
  19. wire [7:0] addr;
  20. wire scl_in;
  21. wire scl_out;
  22. wire scl_ctrl;
  23. wire sda_in;
  24. wire sda_out;
  25. wire sda_ctrl;
  26. assign scl = scl_ctrl ? scl_out : 1'bz;
  27. assign sda = sda_ctrl ? sda_out : 1'bz;
  28. assign scl_in = scl;
  29. assign sda_in = sda;
  30. sys_pll pll_inst(
  31. .clk_in (sys_clk),
  32. .clk_out (clk_50M)
  33. );
  34. filter filter_rst(
  35. .clk (clk_50M),
  36. .sin (sys_rst_n),
  37. .sout (sync_rst_n));
  38. ram_ctrl ram_ctrl_inst(
  39. .clk (clk_50M),
  40. .write (write),
  41. .read (read),
  42. .wr_data (wr_data),
  43. .rd_data (rd_data),
  44. .rw_addr (rw_addr),
  45. .en (en),
  46. .we (we),
  47. .din (din),
  48. .dout (dout),
  49. .addr (addr));
  50. i2c_master_top i2c_master_top_inst(
  51. .clk (clk_50M),
  52. .rst_n (sync_rst_n),
  53. .scl_out (scl_out),
  54. .scl_ctrl (scl_ctrl),
  55. .sda_in (sda_in),
  56. .sda_out (sda_out),
  57. .sda_ctrl (sda_ctrl),
  58. .en (en),
  59. .we (we),
  60. .din (din),
  61. .dout (dout),
  62. .addr (addr));
  63. vio_0 vio_inst(
  64. .clk (clk_50M),
  65. .probe_out0 (write),
  66. .probe_out1 (read),
  67. .probe_out2 (wr_data),
  68. .probe_out3 (rw_addr),
  69. .probe_in0 (rd_data),
  70. .probe_in1 (rw_flag),
  71. .probe_in2 (device_id),
  72. .probe_in3 ({wr_len,rd_len})
  73. );
  74. ila_i2c ila_inst(
  75. .clk (clk_50M),
  76. .probe0 (scl_in),
  77. .probe1 (sda_in)
  78. );
  79. endmodule

程序上板后,先对寄存器进行配置,向对应地址写入值:

读写标志设置为0,写操作,写操作长度3,写入三字节数据,器件ID设为EEPROM的ID,在TX buffer的前两字节里写入了00和10,第三个字节处写入8D,即往0x0010地址处写入0x8D。往启动寄存器中写入01开始发送,然后抓取总线的波形:

可以看到和预期的波形一致,EEPROM也正确地产生了响应位。

再次对寄存器进行配置:

读写标志设置为10,先写后读操作,写操作长度为2,读操作长度为1,写两字节数据,读一字节数据,器件ID不变。对于我写的寄存器模块,还需要清除发送buffer的指针。因为要读同一地址,发送buffer中的内容也不用变,即读出EEPROM在0x0010处的数据。往启动寄存器中写入01开始发送:

成功地读出了0x8D!证明该主机模块是可以使用的。

七、I2C从机板级验证

有了验证通过的主机,就可以使用主机来验证从机了,本次验证在两块FPGA开发板上进行,在另一块板上例化从机后,把I2C接口约束在任意两个引出的IO,修改此开发板上主机的引脚约束,也约束在两个引出的IO上,这样通过杜邦线连接就可以实现I2C通信了。

  1. module top(
  2. input sys_clk,
  3. input sys_rst_n,
  4. input scl,
  5. inout sda,
  6. output [3:0] led_out
  7. );
  8. wire clk_100M;
  9. wire pll_locked;
  10. wire [7:0] wr_data;
  11. wire [7:0] rd_data;
  12. wire [7:0] op_addr;
  13. wire wr_en;
  14. wire scl_in;
  15. wire sda_in;
  16. wire sda_out;
  17. wire sda_ctrl;
  18. wire [3:0] flow;
  19. assign led_out = flow;
  20. assign scl_in = scl;
  21. assign sda_in = sda;
  22. assign sda = sda_ctrl ? sda_out : 1'bz;
  23. sys_pll pll_inst(
  24. .CLK (sys_clk),
  25. .CLKOP (clk_100M),
  26. .LOCK (pll_locked));
  27. timer #(
  28. .SYS_CLK (100_000_000)
  29. ) timer_inst(
  30. .clk(clk_100M),
  31. .rst_n(sys_rst_n),
  32. .intr_a(intr_a),
  33. .intr_b(intr_b),
  34. .flow(flow));
  35. spram #(
  36. .DATA_WIDTH(8),
  37. .RAM_DEPTH(256)
  38. ) spram(
  39. .clk(clk_100M),
  40. .en(1'b1),
  41. .we(wr_en),
  42. .addr(op_addr),
  43. .din(wr_data),
  44. .dout(rd_data));
  45. i2c_slave #(
  46. .DEVICE_ID (7'b1010010) // 7'h52 从机ID
  47. ) i2c_slave_inst(
  48. .clk(clk_100M), // 输入时钟
  49. .rst_n(sys_rst_n), // 同步复位
  50. .SCL_in(scl_in), // I2C时钟输入
  51. .SDA_in(sda_in), // I2C数据输入
  52. .SDA_out(sda_out), // I2C数据输出
  53. .sda_ctrl(sda_ctrl), // I2C数据控制
  54. .rd_data(rd_data), // 读ram数据
  55. .wr_data(wr_data), // 写ram数据
  56. .op_addr(op_addr), // 操作ram地址
  57. .wr_en(wr_en) // 写ram使能
  58. );
  59. endmodule

用杜邦线连接的I2C需要注意一个问题,I2C协议中SCL和SDA都是上拉的,一般开发板上的电路都是在外部做了上拉的,所以不需要进行额外的处理,但是杜邦线肯定是没有上拉的,所以需要在引脚配置界面将SCL和SDA设置为上拉模式,主机和从机的四个引脚都需要上拉。

因为从机是一字节地址,所以写长度设为2,写入一字节地址一字节数据,寄存器配置为在0x00处写入0x53。

可以看出从机是正常响应的,接下来再读0x00处的数据,读出0x53,从机验证完成。

这一篇内容拖了时间太长,中间还经历了换不同板子,换不同厂家的芯片,刚开始写的一些内容到后来已经不一样了,毕竟用公司的板子,还是限制比较多,有些接口还得自己买外界模块才能做,SPI flash模块已经到了,下一篇内容就是SPI接口了,欢迎持续关注!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/599750
推荐阅读
相关标签
  

闽ICP备14008679号