一、前置知识

1.1 EEPROM

EEPROM (Electrically Erasable Progammable Read Only Memory,E2PROM)即电可擦除可编程只读存储器,是一种常用的非易失性存储器,掉电数据不丢失。开发板上使用的是ATMEL生产的AT24C64芯片。AT24C64 存储容量为 64Kbit,内部分成 256 页,每页 32 字节, 共有 8192 个字节,且其读写操作都是以字节为基本单位。
AT24C64 采用两线串行接口的双向数据传输协议——I2C 协议实现读写操作。I2C 即 Inter-Integrated Circuit(集成电路总线),是由 Philips 半导体公司(现在的 NXP)在八十年代初设计出来的一种简单、双向、二线制总线标准。
对于 AT24C64 而言,其器件地址为 1010 加 3 位的可编程地址,3 位可编程地址由器件上的 3 个管脚A2、 A1、A0的硬件连接决定。当硬件电路上分别将这三个管脚连接到 GND 或 VCC 时,就可以设置不同的可编程地址。

1.2 IIC

1.2.1 IIC概述

I2C 总线由数据线 SDA 和时钟线 SCL 构成通信线路,既可用于发送数据,也可接收数据。在主控与被控 IC 之间可进行双向数据传送,数据的传输速率在标准模式下可达100kbit/s,在快速模式下可达 400kbit/s,在高速模式下可达 3.4Mbit/s,各种被控器件均并联在总线上,通过器件地址(SLAVE ADDR)识别。
下图是开发板上使用IIC总线设备的物理拓扑结构:
image.png
由于 I2C 器件一般采用开漏结构与总线相连,所以 I2C_SCL 和 I2C_SDA 均需接上拉电阻。

1.2.2 IIC时序

  1. 空闲状态:在IIC开始通信传输数据之前,时钟线SCL和数据线SDA由于上拉电阻,均处于拉高状态,此时I2C总线空闲。
  2. 起始信号:若主机想要传输数据,需要在SCL为高电平时,将SDA数据线拉低,产生一个起始信号。
  3. 数据传输:主机检测到起始信号后,开始传输数据。
  4. 停止信号:数据传输完成后,主机需要产生一个停止信号。停止信号是在SCL为高电平时,SDA由低电平跳变到高电平。

image.png
具体传输时序图:
image.png

  1. 主机产生起始信号后,在第1个周期开始传输数据。
  2. SCL为高电平时,SDA状态保持以便采样数据;SCL为低电平时允许SDA电平改变。
  3. 在第8个周期结束后,主机释放SDA以使从机产生应答信号。
  4. 在第9个周期,SCL高电平时,如果SDA被从机拉低,说明从机产生应答,本次通信有效;如果SDA没有被从机拉低,说明从机未产生应答,本次通信无效。
  5. 若从机产生有效应答信号,从机会在第9个周期末释放SDA,以使主机继续传输数据。
  6. 如果主机发送停止信号,则通信过程停止;若主机发送起始信号,则继续进行下一轮通信。
  7. 数据以8bit即1字节为单位进行传输,最先传输的为数据最高位。

    1.2.3 传输过程

    1. 器件地址
    在进行数据传输时,主机首先向总线上发出开始信号,对应开始位 S,然后按照从高到低的位序发送器件地址,一般为 7bit,第 8bit 位为读写控制位 R/W,该位为 0 时表示主机对从机进行写操作,当该位为 1 时表示主机对从机进行读操作,然后接收从机响应。
    image.png
    发送完第一个字节(7位器件地址和1位读写控制位)并收到从机正确的应答后就开始发送字地址(Word Address)。
    2. 字地址
    字地址就是IC内部寄存器的地址,由于IC存储容量的大小不同,字地址的长度也不同。比如AT24C02的存储容量是2Kbit即256Byte,寻址是以字节为单位的,所以256字节需要8位寻址的字地址就够了,即字地址为8位(1个字节);但是AT24C64存储容量为64Kb即8KB,需要13位地址来做寻址,即字地址为13位(至少2个字节)。因此就有了单字节字地址和双字节字地址。
    单字节字地址:
    image.png
    双字节字地址:
    image.pngimage.png
    3. 写数据
    主机发送字地址,从机正确应答后,就可以根据器件地址的读写位R/W进行数据读写操作了。如果是写操作R/W=0,主机就发送写的数据。写数据分为单次写和连续写。
  • 单次写是指在主机写完1字节数据后,主机发送停止信号,结束输出传输。
  • 连续写是指在主机写完1字节数据后,主机继续发送下1个字节的数据,多次以1字节为单位发送,最后再发一个停止信号结束。

单次写数据:
image.png
连续写数据:
image.png

4. 读数据

主机发送字地址,从机正确应答后,就可以根据器件地址的读写位R/W进行数据读写操作了。如果是读操作R/W=1,从机就开始发送数据,主机读从机的数据。读数据分为当前地址读、随机读、连续读。

  • 当前地址读是指在一次读写操作后进行一次读操作。由于I2C在读写操作后其内部地址指针自动加一,所以当前地址读到的数据是下一个地址的数据,即若上次读写的地址是02,则当前读所读的地址是03地址。

image.png

  • 随机读是指首先主机在发送器件地址时,使用写操作,接着在发送完字地址之后再第二次发送起始信号,器件地址使用读操作,然后接着进行一次读数据。这样做是为了在第一次写操作之后,让I2C指针指向器件内部我们所需要的地址,然后在该地址进行数据读取。上面第一次的写操作成为需写操作(Dummy Write)。

无标题.png

  • 连续读是指连续读几个字节的数据。对于当前地址读和随机读,都是只读1字节数据,在读完1字节数据后,主机发送不应答信号1,主机从送停止信号结束通信。对于连续读,在每次读完1字节数据后,主机发送应答信号0,主机继续读,直到主机发送不应答的1,主机发送停止信号结束通信。

无标题.png

5. 起始和停止信号
  • 起始信号:SCL高电平时,SDA由高拉低
  • 停止信号:SCL高电平时,SDA由低拉高

    二、硬件设计

    开发板上使用的AT24C64引脚及功能描述如下:
    image.png

  • A0、A1、A2:器件地址选择位

  • GND、VCC:接地,电源
  • SDA、SCL:I2C数据线和地址线
  • WP:写保护,高电平有效。高电平时只能读数据不能写入,低电平时可写入数据。

AT24C64原理图如下:
image.png
开发板上只有一个AT24C64,其A0A1A2都接地,所以器件地址为:1010_000=0x50
管脚分配如下:
image.png
image.png

  1. inout sda;
  2. assign sda = sda_dir ? sda_out : 1'bz;
  3. assign sda_in = sda;

三、代码编写

image.png
image.png

3.1 各模块接口

3.1.1 I2C驱动模块

image.png

  • clk:系统时钟
  • rst_n:系统复位
  • i2c_exec:I2C触发执行信号,高电平开始执行I2C读写操作。
  • bit_ctr:字地址位控制(16b/8b),1为16bit字地址;0为8bit字地址
  • i2c_rh_wl:I2C读写控制信号,1读0写
  • i2c_addr:I2C器件内地址
  • i2c_data_w:I2C要写的数据
  • i2c_data_r:I2C读出的数据
  • i2c_done:I2C一次操作完成,高电平写入完成
  • i2c_ack:I2C应答标志 0:应答 1:未应答
  • scl:I2C的SCL时钟信号
  • sda:I2C的SDA信号
  • dri_clk:输出驱动I2C操作的驱动时钟

    3.1.2 读写测试模块

    image.png

  • clk:由I2C驱动模块产生的时钟信号

  • rst_n:系统复位信号
  • i2c_ack:I2C应答标志
  • i2c_done:I2C一次操作完成标志
  • i2c_exec:I2C触发执行信号
  • i2c_rh_wl:I2C读写控制信号
  • i2c_data_r[7:0]:I2C读出的数据
  • i2c_data_w[7:0]:I2C要写的数据
  • i2c_addr[15:0]:I2C器件内的地址
  • rw_done:E2PROM读写测试完成标志
  • rw_result:E2PROM读写测试结果

    3.1.3 LED灯模块

    image.png

  • clk:时钟信号(I2C驱动时钟)

  • rst_n:复位信号
  • rw_done:E2PROM读写测试完成
  • rw_result:E2PROM读写测试结果
  • led:控制LED状态

    3.2 模块代码编写

    3.2.1 读写测试模块

    I2C驱动代码较复杂,假设I2C驱动代码已经编写完成,先来编写I2C读写测试模块。示例代码使用了四状态的状态机来实现功能。状态0和1用来向E2PROM写入数据;状态2和3用来读取E2PROM的数据并验证数据正确性。

  • 状态0:写数据和判断所有写数据完成

  • 状态1:等待本次写数据完成,改变下一轮写数据和写地址
  • 状态2:执行读数据并条状到状态3
  • 状态3:等待本次读数据完成,验证数据正确性,向用户接口输出验证结果 ```verilog module eeprom_rw( input clk , // I2C驱动提供的时钟信号 input rst_n , // 系统复位

    input i2c_ack , // I2C应答标志 input i2c_done , // I2C一次操作完成标志 output reg i2c_exec , // I2C触发执行信号 output reg i2c_rh_wl , // I2C读写控制信号 input [ 7:0] i2c_data_r , // I2C读出的数据 output reg [ 7:0] i2c_data_w , // I2C要写的数据 output reg [15:0] i2c_addr , // I2C器件内的地址

    output reg rw_done , // E2PROM读写测试完成标志 output reg rw_result // E2PROM读写测试结果 );

parameter MAX_ADDR = 9’d256; // 测试地址0~255 parameter WRITE_WAIT_TIME = 13’d5000; // I2C模块输出频率4us,

reg [ 1:0] flow_cnt; // 状态流切换控制 reg [12:0] wait_cnt; // 写等待倒计时

always @(posedge clk or negedge rst_n) begin if(!rst_n) begin i2c_exec <= 1’b0; i2c_rh_wl <= 1’b0; i2c_data_w <= 8’d0; i2c_addr <= 16’d0; rw_done <= 1’b0; rw_result <= 1’b0; flow_cnt <= 2’d0; wait_cnt <= 13’d0; end else begin i2c_exec <= 1’b0; case(flow_cnt) 2’d0: begin // 写数据 wait_cnt <= wait_cnt + 1’b1; if(wait_cnt == WRITE_WAIT_TIME) begin wait_cnt <= 1’b0; if(i2c_addr == MAX_ADDR) begin flow_cnt <= 2’d2; // 跳转到读状态 i2c_addr <= 16’d0; i2c_rh_wl <= 1’b1; end else begin // 准备写入数据 i2c_exec <= 1’b1; flow_cnt <= 2’d1; end end end 2’d1: begin // 等待一轮写入数据完成,准备下一轮写入的数据 if(i2c_done) begin flow_cnt <= 2’d0; i2c_data_w <= i2c_data_w + 1’b1; i2c_addr <= i2c_addr + 1’b1; end end 2’d2: begin // 读数据 i2c_exec <= 1’b1; flow_cnt <= 2’d3; end 2’d3: begin // 等待读数据完成,验证数据正确性 if(i2c_done) begin if((i2c_data_r != i2c_addr[7:0]) || (i2c_ack == 1)) begin rw_done <= 1’b1; // 验证出错 rw_result <= 1’b0; end else if(i2c_addr == MAX_ADDR-1) begin // 全部验证完成 rw_done <= 1’b1; rw_result <= 1’b1; end else begin // 下一轮数据读取和验证 i2c_addr <= i2c_addr + 1’b1; flow_cnt <= 2’d2; end end end default :; endcase end end

endmodule

  1. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/25873077/1651824690105-a5b433bf-39ec-45ae-80eb-70431d065869.png#clientId=ud4d9d2c3-1449-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=600&id=u2486bafe&margin=%5Bobject%20Object%5D&name=image.png&originHeight=600&originWidth=771&originalType=binary&ratio=1&rotation=0&showTitle=false&size=35572&status=done&style=none&taskId=u95e81df1-f5b5-4947-b8f7-d7bbfdcf385&title=&width=771)
  2. <a name="iKUSJ"></a>
  3. #### 3.2.2 LED灯控制模块
  4. 上述编写的读写测试模块,当全部0~255地址中的数据全部读取并验证无误后,读写测试结束,读写测试模块在状态3不断循环,由于i2c_done是脉冲信号,而rx_done被清零,所以rx_done会输出脉冲1rx_result一直输出1。如果存在数据验证出错,则同样在状态3循环,rx_done会输出脉冲信号1rx_result则一直输出0
  5. ```verilog
  6. module led_alarm(
  7. input clk,
  8. input rst_n,
  9. input rx_done,
  10. input rx_result,
  11. output reg led
  12. );
  13. parameter BILNK_TIME = 25_000_000;
  14. reg rx_done_flag;
  15. reg [24:0] bilnk_cnt;
  16. always @(posedge clk or negedge rst_n) begin
  17. if(!rst_n)
  18. rx_done_flag <= 1'b0;
  19. else if(rx_done)
  20. rx_done_flag <= 1'b1;
  21. end
  22. always @(posedge clk or negedge rst_n) begin
  23. if(!rst_n) begin
  24. led <= 1'b0;
  25. bilnk_cnt <= 25'd0;
  26. end
  27. else begin
  28. if(rx_done_flag) begin
  29. if(rx_result == 1'b0) begin // 验证失败
  30. bilnk_cnt <= bilnk_cnt + 1'b1;
  31. if(bilnk_cnt == BILNK_TIME - 1) begin
  32. led <= ~led;
  33. bilnk_cnt <= 25'd0;
  34. end
  35. end
  36. else
  37. led <= 1'b1; // 验证成功
  38. end
  39. else // 还未验证完成
  40. led <= 1'b0;
  41. end
  42. end
  43. endmodule

3.2.3 I2C驱动模块

正点原子教程使用8状态的状态机实现了I2C驱动模块,暂时先使用正点原子提供的模板,以后再自己写。。

  1. //****************************************Copyright (c)***********************************//
  2. //原子哥在线教学平台:www.yuanzige.com
  3. //技术支持:www.openedv.com
  4. //淘宝店铺:http://openedv.taobao.com
  5. //关注微信公众平台微信号:"正点原子",免费获取ZYNQ & FPGA & STM32 & LINUX资料。
  6. //版权所有,盗版必究。
  7. //Copyright(C) 正点原子 2018-2028
  8. //All rights reserved
  9. //----------------------------------------------------------------------------------------
  10. // File name: i2c_dri
  11. // Last modified Date: 2019/05/04 9:19:08
  12. // Last Version: V1.0
  13. // Descriptions: IIC驱动
  14. //
  15. //----------------------------------------------------------------------------------------
  16. // Created by: 正点原子
  17. // Created date: 2019/05/04 9:19:08
  18. // Version: V1.0
  19. // Descriptions: The original version
  20. //
  21. //----------------------------------------------------------------------------------------
  22. //****************************************************************************************//
  23. module i2c_dri
  24. #(
  25. parameter SLAVE_ADDR = 7'b1010000 , //EEPROM从机地址
  26. parameter CLK_FREQ = 26'd50_000_000, //模块输入的时钟频率
  27. parameter I2C_FREQ = 18'd250_000 //IIC_SCL的时钟频率
  28. )
  29. (
  30. input clk ,
  31. input rst_n ,
  32. //i2c interface
  33. input i2c_exec , //I2C触发执行信号
  34. input bit_ctrl , //字地址位控制(16b/8b)
  35. input i2c_rh_wl , //I2C读写控制信号
  36. input [15:0] i2c_addr , //I2C器件内地址
  37. input [ 7:0] i2c_data_w , //I2C要写的数据
  38. output reg [ 7:0] i2c_data_r , //I2C读出的数据
  39. output reg i2c_done , //I2C一次操作完成
  40. output reg i2c_ack , //I2C应答标志 0:应答 1:未应答
  41. output reg scl , //I2C的SCL时钟信号
  42. inout sda , //I2C的SDA信号
  43. //user interface
  44. output reg dri_clk //驱动I2C操作的驱动时钟
  45. );
  46. //localparam define
  47. localparam st_idle = 8'b0000_0001; //空闲状态
  48. localparam st_sladdr = 8'b0000_0010; //发送器件地址(slave address)
  49. localparam st_addr16 = 8'b0000_0100; //发送16位字地址
  50. localparam st_addr8 = 8'b0000_1000; //发送8位字地址
  51. localparam st_data_wr = 8'b0001_0000; //写数据(8 bit)
  52. localparam st_addr_rd = 8'b0010_0000; //发送器件地址读
  53. localparam st_data_rd = 8'b0100_0000; //读数据(8 bit)
  54. localparam st_stop = 8'b1000_0000; //结束I2C操作
  55. //reg define
  56. reg sda_dir ; //I2C数据(SDA)方向控制
  57. reg sda_out ; //SDA输出信号
  58. reg st_done ; //状态结束
  59. reg wr_flag ; //写标志
  60. reg [ 6:0] cnt ; //计数
  61. reg [ 7:0] cur_state ; //状态机当前状态
  62. reg [ 7:0] next_state; //状态机下一状态
  63. reg [15:0] addr_t ; //地址
  64. reg [ 7:0] data_r ; //读取的数据
  65. reg [ 7:0] data_wr_t ; //I2C需写的数据的临时寄存
  66. reg [ 9:0] clk_cnt ; //分频时钟计数
  67. //wire define
  68. wire sda_in ; //SDA输入信号
  69. wire [8:0] clk_divide ; //模块驱动时钟的分频系数
  70. //*****************************************************
  71. //** main code
  72. //*****************************************************
  73. //SDA控制
  74. assign sda = sda_dir ? sda_out : 1'bz; //SDA数据输出或高阻
  75. assign sda_in = sda ; //SDA数据输入
  76. assign clk_divide = (CLK_FREQ/I2C_FREQ) >> 2'd2;//模块驱动时钟的分频系数
  77. //生成I2C的SCL的四倍频率的驱动时钟用于驱动i2c的操作
  78. always @(posedge clk or negedge rst_n) begin
  79. if(!rst_n) begin
  80. dri_clk <= 1'b0;
  81. clk_cnt <= 10'd0;
  82. end
  83. else if(clk_cnt == clk_divide[8:1] - 1'd1) begin
  84. clk_cnt <= 10'd0;
  85. dri_clk <= ~dri_clk;
  86. end
  87. else
  88. clk_cnt <= clk_cnt + 1'b1;
  89. end
  90. //(三段式状态机)同步时序描述状态转移
  91. always @(posedge dri_clk or negedge rst_n) begin
  92. if(!rst_n)
  93. cur_state <= st_idle;
  94. else
  95. cur_state <= next_state;
  96. end
  97. //组合逻辑判断状态转移条件
  98. always @(*) begin
  99. next_state = st_idle;
  100. case(cur_state)
  101. st_idle: begin //空闲状态
  102. if(i2c_exec) begin
  103. next_state = st_sladdr;
  104. end
  105. else
  106. next_state = st_idle;
  107. end
  108. st_sladdr: begin
  109. if(st_done) begin
  110. if(bit_ctrl) //判断是16位还是8位字地址
  111. next_state = st_addr16;
  112. else
  113. next_state = st_addr8 ;
  114. end
  115. else
  116. next_state = st_sladdr;
  117. end
  118. st_addr16: begin //写16位字地址
  119. if(st_done) begin
  120. next_state = st_addr8;
  121. end
  122. else begin
  123. next_state = st_addr16;
  124. end
  125. end
  126. st_addr8: begin //8位字地址
  127. if(st_done) begin
  128. if(wr_flag==1'b0) //读写判断
  129. next_state = st_data_wr;
  130. else
  131. next_state = st_addr_rd;
  132. end
  133. else begin
  134. next_state = st_addr8;
  135. end
  136. end
  137. st_data_wr: begin //写数据(8 bit)
  138. if(st_done)
  139. next_state = st_stop;
  140. else
  141. next_state = st_data_wr;
  142. end
  143. st_addr_rd: begin //写地址以进行读数据
  144. if(st_done) begin
  145. next_state = st_data_rd;
  146. end
  147. else begin
  148. next_state = st_addr_rd;
  149. end
  150. end
  151. st_data_rd: begin //读取数据(8 bit)
  152. if(st_done)
  153. next_state = st_stop;
  154. else
  155. next_state = st_data_rd;
  156. end
  157. st_stop: begin //结束I2C操作
  158. if(st_done)
  159. next_state = st_idle;
  160. else
  161. next_state = st_stop ;
  162. end
  163. default: next_state= st_idle;
  164. endcase
  165. end
  166. //时序电路描述状态输出
  167. always @(posedge dri_clk or negedge rst_n) begin
  168. //复位初始化
  169. if(!rst_n) begin
  170. scl <= 1'b1;
  171. sda_out <= 1'b1;
  172. sda_dir <= 1'b1;
  173. i2c_done <= 1'b0;
  174. i2c_ack <= 1'b0;
  175. cnt <= 1'b0;
  176. st_done <= 1'b0;
  177. data_r <= 1'b0;
  178. i2c_data_r<= 1'b0;
  179. wr_flag <= 1'b0;
  180. addr_t <= 1'b0;
  181. data_wr_t <= 1'b0;
  182. end
  183. else begin
  184. st_done <= 1'b0 ;
  185. cnt <= cnt +1'b1 ;
  186. case(cur_state)
  187. st_idle: begin //空闲状态
  188. scl <= 1'b1;
  189. sda_out <= 1'b1;
  190. sda_dir <= 1'b1;
  191. i2c_done<= 1'b0;
  192. cnt <= 7'b0;
  193. if(i2c_exec) begin
  194. wr_flag <= i2c_rh_wl ;
  195. addr_t <= i2c_addr ;
  196. data_wr_t <= i2c_data_w;
  197. i2c_ack <= 1'b0;
  198. end
  199. end
  200. st_sladdr: begin //写地址(器件地址和字地址)
  201. case(cnt)
  202. 7'd1 : sda_out <= 1'b0; //开始I2C
  203. 7'd3 : scl <= 1'b0;
  204. 7'd4 : sda_out <= SLAVE_ADDR[6]; //传送器件地址
  205. 7'd5 : scl <= 1'b1;
  206. 7'd7 : scl <= 1'b0;
  207. 7'd8 : sda_out <= SLAVE_ADDR[5];
  208. 7'd9 : scl <= 1'b1;
  209. 7'd11: scl <= 1'b0;
  210. 7'd12: sda_out <= SLAVE_ADDR[4];
  211. 7'd13: scl <= 1'b1;
  212. 7'd15: scl <= 1'b0;
  213. 7'd16: sda_out <= SLAVE_ADDR[3];
  214. 7'd17: scl <= 1'b1;
  215. 7'd19: scl <= 1'b0;
  216. 7'd20: sda_out <= SLAVE_ADDR[2];
  217. 7'd21: scl <= 1'b1;
  218. 7'd23: scl <= 1'b0;
  219. 7'd24: sda_out <= SLAVE_ADDR[1];
  220. 7'd25: scl <= 1'b1;
  221. 7'd27: scl <= 1'b0;
  222. 7'd28: sda_out <= SLAVE_ADDR[0];
  223. 7'd29: scl <= 1'b1;
  224. 7'd31: scl <= 1'b0;
  225. 7'd32: sda_out <= 1'b0; //0:写
  226. 7'd33: scl <= 1'b1;
  227. 7'd35: scl <= 1'b0;
  228. 7'd36: begin
  229. sda_dir <= 1'b0;
  230. sda_out <= 1'b1;
  231. end
  232. 7'd37: scl <= 1'b1;
  233. 7'd38: begin //从机应答
  234. st_done <= 1'b1;
  235. if(sda_in == 1'b1) //高电平表示未应答
  236. i2c_ack <= 1'b1; //拉高应答标志位
  237. end
  238. 7'd39: begin
  239. scl <= 1'b0;
  240. cnt <= 1'b0;
  241. end
  242. default : ;
  243. endcase
  244. end
  245. st_addr16: begin
  246. case(cnt)
  247. 7'd0 : begin
  248. sda_dir <= 1'b1 ;
  249. sda_out <= addr_t[15]; //传送字地址
  250. end
  251. 7'd1 : scl <= 1'b1;
  252. 7'd3 : scl <= 1'b0;
  253. 7'd4 : sda_out <= addr_t[14];
  254. 7'd5 : scl <= 1'b1;
  255. 7'd7 : scl <= 1'b0;
  256. 7'd8 : sda_out <= addr_t[13];
  257. 7'd9 : scl <= 1'b1;
  258. 7'd11: scl <= 1'b0;
  259. 7'd12: sda_out <= addr_t[12];
  260. 7'd13: scl <= 1'b1;
  261. 7'd15: scl <= 1'b0;
  262. 7'd16: sda_out <= addr_t[11];
  263. 7'd17: scl <= 1'b1;
  264. 7'd19: scl <= 1'b0;
  265. 7'd20: sda_out <= addr_t[10];
  266. 7'd21: scl <= 1'b1;
  267. 7'd23: scl <= 1'b0;
  268. 7'd24: sda_out <= addr_t[9];
  269. 7'd25: scl <= 1'b1;
  270. 7'd27: scl <= 1'b0;
  271. 7'd28: sda_out <= addr_t[8];
  272. 7'd29: scl <= 1'b1;
  273. 7'd31: scl <= 1'b0;
  274. 7'd32: begin
  275. sda_dir <= 1'b0;
  276. sda_out <= 1'b1;
  277. end
  278. 7'd33: scl <= 1'b1;
  279. 7'd34: begin //从机应答
  280. st_done <= 1'b1;
  281. if(sda_in == 1'b1) //高电平表示未应答
  282. i2c_ack <= 1'b1; //拉高应答标志位
  283. end
  284. 7'd35: begin
  285. scl <= 1'b0;
  286. cnt <= 1'b0;
  287. end
  288. default : ;
  289. endcase
  290. end
  291. st_addr8: begin
  292. case(cnt)
  293. 7'd0: begin
  294. sda_dir <= 1'b1 ;
  295. sda_out <= addr_t[7]; //字地址
  296. end
  297. 7'd1 : scl <= 1'b1;
  298. 7'd3 : scl <= 1'b0;
  299. 7'd4 : sda_out <= addr_t[6];
  300. 7'd5 : scl <= 1'b1;
  301. 7'd7 : scl <= 1'b0;
  302. 7'd8 : sda_out <= addr_t[5];
  303. 7'd9 : scl <= 1'b1;
  304. 7'd11: scl <= 1'b0;
  305. 7'd12: sda_out <= addr_t[4];
  306. 7'd13: scl <= 1'b1;
  307. 7'd15: scl <= 1'b0;
  308. 7'd16: sda_out <= addr_t[3];
  309. 7'd17: scl <= 1'b1;
  310. 7'd19: scl <= 1'b0;
  311. 7'd20: sda_out <= addr_t[2];
  312. 7'd21: scl <= 1'b1;
  313. 7'd23: scl <= 1'b0;
  314. 7'd24: sda_out <= addr_t[1];
  315. 7'd25: scl <= 1'b1;
  316. 7'd27: scl <= 1'b0;
  317. 7'd28: sda_out <= addr_t[0];
  318. 7'd29: scl <= 1'b1;
  319. 7'd31: scl <= 1'b0;
  320. 7'd32: begin
  321. sda_dir <= 1'b0;
  322. sda_out <= 1'b1;
  323. end
  324. 7'd33: scl <= 1'b1;
  325. 7'd34: begin //从机应答
  326. st_done <= 1'b1;
  327. if(sda_in == 1'b1) //高电平表示未应答
  328. i2c_ack <= 1'b1; //拉高应答标志位
  329. end
  330. 7'd35: begin
  331. scl <= 1'b0;
  332. cnt <= 1'b0;
  333. end
  334. default : ;
  335. endcase
  336. end
  337. st_data_wr: begin //写数据(8 bit)
  338. case(cnt)
  339. 7'd0: begin
  340. sda_out <= data_wr_t[7]; //I2C写8位数据
  341. sda_dir <= 1'b1;
  342. end
  343. 7'd1 : scl <= 1'b1;
  344. 7'd3 : scl <= 1'b0;
  345. 7'd4 : sda_out <= data_wr_t[6];
  346. 7'd5 : scl <= 1'b1;
  347. 7'd7 : scl <= 1'b0;
  348. 7'd8 : sda_out <= data_wr_t[5];
  349. 7'd9 : scl <= 1'b1;
  350. 7'd11: scl <= 1'b0;
  351. 7'd12: sda_out <= data_wr_t[4];
  352. 7'd13: scl <= 1'b1;
  353. 7'd15: scl <= 1'b0;
  354. 7'd16: sda_out <= data_wr_t[3];
  355. 7'd17: scl <= 1'b1;
  356. 7'd19: scl <= 1'b0;
  357. 7'd20: sda_out <= data_wr_t[2];
  358. 7'd21: scl <= 1'b1;
  359. 7'd23: scl <= 1'b0;
  360. 7'd24: sda_out <= data_wr_t[1];
  361. 7'd25: scl <= 1'b1;
  362. 7'd27: scl <= 1'b0;
  363. 7'd28: sda_out <= data_wr_t[0];
  364. 7'd29: scl <= 1'b1;
  365. 7'd31: scl <= 1'b0;
  366. 7'd32: begin
  367. sda_dir <= 1'b0;
  368. sda_out <= 1'b1;
  369. end
  370. 7'd33: scl <= 1'b1;
  371. 7'd34: begin //从机应答
  372. st_done <= 1'b1;
  373. if(sda_in == 1'b1) //高电平表示未应答
  374. i2c_ack <= 1'b1; //拉高应答标志位
  375. end
  376. 7'd35: begin
  377. scl <= 1'b0;
  378. cnt <= 1'b0;
  379. end
  380. default : ;
  381. endcase
  382. end
  383. st_addr_rd: begin //写地址以进行读数据
  384. case(cnt)
  385. 7'd0 : begin
  386. sda_dir <= 1'b1;
  387. sda_out <= 1'b1;
  388. end
  389. 7'd1 : scl <= 1'b1;
  390. 7'd2 : sda_out <= 1'b0; //重新开始
  391. 7'd3 : scl <= 1'b0;
  392. 7'd4 : sda_out <= SLAVE_ADDR[6]; //传送器件地址
  393. 7'd5 : scl <= 1'b1;
  394. 7'd7 : scl <= 1'b0;
  395. 7'd8 : sda_out <= SLAVE_ADDR[5];
  396. 7'd9 : scl <= 1'b1;
  397. 7'd11: scl <= 1'b0;
  398. 7'd12: sda_out <= SLAVE_ADDR[4];
  399. 7'd13: scl <= 1'b1;
  400. 7'd15: scl <= 1'b0;
  401. 7'd16: sda_out <= SLAVE_ADDR[3];
  402. 7'd17: scl <= 1'b1;
  403. 7'd19: scl <= 1'b0;
  404. 7'd20: sda_out <= SLAVE_ADDR[2];
  405. 7'd21: scl <= 1'b1;
  406. 7'd23: scl <= 1'b0;
  407. 7'd24: sda_out <= SLAVE_ADDR[1];
  408. 7'd25: scl <= 1'b1;
  409. 7'd27: scl <= 1'b0;
  410. 7'd28: sda_out <= SLAVE_ADDR[0];
  411. 7'd29: scl <= 1'b1;
  412. 7'd31: scl <= 1'b0;
  413. 7'd32: sda_out <= 1'b1; //1:读
  414. 7'd33: scl <= 1'b1;
  415. 7'd35: scl <= 1'b0;
  416. 7'd36: begin
  417. sda_dir <= 1'b0;
  418. sda_out <= 1'b1;
  419. end
  420. 7'd37: scl <= 1'b1;
  421. 7'd38: begin //从机应答
  422. st_done <= 1'b1;
  423. if(sda_in == 1'b1) //高电平表示未应答
  424. i2c_ack <= 1'b1; //拉高应答标志位
  425. end
  426. 7'd39: begin
  427. scl <= 1'b0;
  428. cnt <= 1'b0;
  429. end
  430. default : ;
  431. endcase
  432. end
  433. st_data_rd: begin //读取数据(8 bit)
  434. case(cnt)
  435. 7'd0: sda_dir <= 1'b0;
  436. 7'd1: begin
  437. data_r[7] <= sda_in;
  438. scl <= 1'b1;
  439. end
  440. 7'd3: scl <= 1'b0;
  441. 7'd5: begin
  442. data_r[6] <= sda_in ;
  443. scl <= 1'b1 ;
  444. end
  445. 7'd7: scl <= 1'b0;
  446. 7'd9: begin
  447. data_r[5] <= sda_in;
  448. scl <= 1'b1 ;
  449. end
  450. 7'd11: scl <= 1'b0;
  451. 7'd13: begin
  452. data_r[4] <= sda_in;
  453. scl <= 1'b1 ;
  454. end
  455. 7'd15: scl <= 1'b0;
  456. 7'd17: begin
  457. data_r[3] <= sda_in;
  458. scl <= 1'b1 ;
  459. end
  460. 7'd19: scl <= 1'b0;
  461. 7'd21: begin
  462. data_r[2] <= sda_in;
  463. scl <= 1'b1 ;
  464. end
  465. 7'd23: scl <= 1'b0;
  466. 7'd25: begin
  467. data_r[1] <= sda_in;
  468. scl <= 1'b1 ;
  469. end
  470. 7'd27: scl <= 1'b0;
  471. 7'd29: begin
  472. data_r[0] <= sda_in;
  473. scl <= 1'b1 ;
  474. end
  475. 7'd31: scl <= 1'b0;
  476. 7'd32: begin
  477. sda_dir <= 1'b1;
  478. sda_out <= 1'b1;
  479. end
  480. 7'd33: scl <= 1'b1;
  481. 7'd34: st_done <= 1'b1; //非应答
  482. 7'd35: begin
  483. scl <= 1'b0;
  484. cnt <= 1'b0;
  485. i2c_data_r <= data_r;
  486. end
  487. default : ;
  488. endcase
  489. end
  490. st_stop: begin //结束I2C操作
  491. case(cnt)
  492. 7'd0: begin
  493. sda_dir <= 1'b1; //结束I2C
  494. sda_out <= 1'b0;
  495. end
  496. 7'd1 : scl <= 1'b1;
  497. 7'd3 : sda_out <= 1'b1;
  498. 7'd15: st_done <= 1'b1;
  499. 7'd16: begin
  500. cnt <= 1'b0;
  501. i2c_done <= 1'b1; //向上层模块传递I2C结束信号
  502. end
  503. default : ;
  504. endcase
  505. end
  506. endcase
  507. end
  508. end
  509. endmodule

3.2.4 顶层模块

顶层模块将读写测试模块、I2C驱动模块和LED控制模块连接起来即可。

  1. module iic_eeprom(
  2. input sys_clk,
  3. input sys_rst_n,
  4. // IIC interface
  5. output iic_scl,
  6. output iic_sda,
  7. // User interface
  8. output led
  9. );
  10. parameter BIT_CTRL = 1'b1; // 字地址位控制参数
  11. parameter SLAVE_ADDR = 7'b1010000; // EEPROM从机地址
  12. parameter CLK_FREQ = 26'd50_000_000; // 模块输入的时钟频率
  13. parameter I2C_FREQ = 18'd250_000; // IIC_SCL的时钟频率
  14. parameter BILNK_TIME = 17'd125_000; // LED闪烁时间参数
  15. wire i2c_exec ;
  16. wire i2c_rh_wl ;
  17. wire [15:0] i2c_addr ;
  18. wire [ 7:0] i2c_data_w ;
  19. wire [ 7:0] i2c_data_r ;
  20. wire i2c_done ;
  21. wire i2c_ack ;
  22. wire dri_clk ;
  23. wire rw_done ;
  24. wire rw_result ;
  25. i2c_dri #(
  26. .SLAVE_ADDR (SLAVE_ADDR),
  27. .CLK_FREQ (CLK_FREQ ),
  28. .I2C_FREQ (I2C_FREQ )
  29. ) u_i2c_dri(
  30. .clk (sys_clk ),
  31. .rst_n (sys_rst_n ),
  32. .i2c_exec (i2c_exec ),
  33. .bit_ctrl (BIT_CTRL ),
  34. .i2c_rh_wl (i2c_rh_wl ),
  35. .i2c_addr (i2c_addr ),
  36. .i2c_data_w (i2c_data_w),
  37. .i2c_data_r (i2c_data_r),
  38. .i2c_done (i2c_done ),
  39. .i2c_ack (i2c_ack ),
  40. .scl (iic_scl ),
  41. .sda (iic_sda ),
  42. .dri_clk (dri_clk )
  43. );
  44. eeprom_rw u_eeprom_rw(
  45. .clk (dri_clk ),
  46. .rst_n (sys_rst_n ),
  47. .i2c_ack (i2c_ack ),
  48. .i2c_done (i2c_done ),
  49. .i2c_exec (i2c_exec ),
  50. .i2c_rh_wl (i2c_rh_wl ),
  51. .i2c_data_r (i2c_data_r),
  52. .i2c_data_w (i2c_data_w),
  53. .i2c_addr (i2c_addr ),
  54. .rw_done (rw_done ),
  55. .rw_result (rw_result )
  56. );
  57. led_alarm #(
  58. .BILNK_TIME (BILNK_TIME)
  59. )u_led_alarm(
  60. .clk (dri_clk ),
  61. .rst_n (sys_rst_n ),
  62. .rx_done (rw_done ),
  63. .rx_result (rw_result ),
  64. .led (led )
  65. );
  66. endmodule

3.2.5 TCL脚本

只需定义顶层模块中CLK、RST_N、SCL、SDA和LED的引脚即可。

  1. package require ::quartus::project
  2. #system clock:50Mhz
  3. set_location_assignment PIN_M2 -to sys_clk
  4. #system reset
  5. set_location_assignment PIN_M1 -to sys_rst_n
  6. #led
  7. set_location_assignment PIN_D11 -to led
  8. #I2C(EEPROM/ALS&PS/RTC/AUDIO)
  9. set_location_assignment PIN_D8 -to iic_scl
  10. set_location_assignment PIN_C8 -to iic_sda

程序烧录

12_iic_eeprom_error.mp4 (5.93MB)结果出现错误,数据验证出错,LED闪烁。
另外由于其余三个LED没有约束,一直为亮的状态。在AssignmentsDeviceDevice and Pin Optionsunused pin页面,把未使用的引脚设置为As input tri-stated。重新编译之后再烧录就好了。
image.png
数据验证失败的问题还在调试中。。。