FPGA学习系列:31. 基于FPGA的IIC的设计

描述

设计背景: 

    IIC简单来说,就是一种串行通信协议,IIC 的通信协议和通信接口在很多工程中有广泛的应用,如数据采集领域的串行 AD,图像处理领域的摄像头配置,工业控制领域的 X 射线管配置等等。除此之外,由于 IIC协议占用的 IO 资源特别少,连接方便,所以工程中也常选用 IIC 接口做为不同芯片间的通信协议。

 

设计原理: 

IIC电路原理图如下:

FPGA



24LC64各引脚定义:

    1、A0,A1,A2 为 24LC64 的片选信号,由于 IIC 总线可以挂载多个 IIC 接口器件,所以每个器件都应该有自己的“身份标识”,通过对 A0,A1,A2 输入不同的高低电平,就可以设置该 EEPROM 的片选信号。

    2、WP 为读写使能信号,当 WP 悬空或者接地,EEPROM 可读可写,当 WP 接电源,EEPROM 只能读不能写。

    3、SCL为 IIC 接口的时钟线。

    4、SDA为 IIC 接口的数据线。


IIC 接口的读写时序:

    IIC接口读写时序分为随机读写(单字节读写)和页面读写(多字节读写),先分析随机读写(Byte Write/Read)时序。


Byte Write 时序如下:

FPGA



时序解读:如果我们要向 EEPROM 写入一个字节,那么必须经过以下步骤:

1. 发送启动信号

2. 发送控制字

3. 接收并检测 EEPROM 发来的应答信号 ACK

4. 发送高字节地址位

5. 接收并检测 EEPROM 发来的应答信号 ACK

6. 发送低字节地址位

7. 接收并检测 EEPROM 发来的应答信号 ACK

8. 发送 8bit 有效数据

9. 接收并检测 EEPROM 发来的应答信号 ACK

10.发送停止信号


Byte Read 时序如下:

FPGA


时序解读:如果我们要从 EEPROM 读出一个字节,那么必须经过以下步骤:

1. 发送启动信号

2. 发送控制字 1010_A2A1A0_0

3. 接收并检测 EEPROM 发来的应答信号 ACK

4. 发送高字节地址位

5. 接收并检测 EEPROM 发来的应答信号 ACK

6. 发送低字节地址位

7. 接收并检测 EEPROM 发来的应答信号 ACK

8. 发送启动信号

9. 发送控制字 1010_A2A1A0_1

10. 接收并检测 EEPROM 发来的应答信号 ACK

11. 读取一个字节数据

12. 发送 NO ACK 信号

13. 发送停止信号


接下来则需要分析各步骤具体意义:

1.启动信号

FPGA


     SCL 保持高电平期间,如果 SDA 出现由高到低的跳变沿,代表启动信号。


2. 控制字

    我们的控制字为 1010_0000,其中 1010 为 EEPROM 的型号标识,为一组固定的序列,紧接着 A2,A1,A0 就是我们的片选信号,最后一位为读写控制位,低电平代表写,高电平代表读,我们这里首先需要对 EEPROM 写入地址位,所以我们最后一位为 0。


3. 高/低位地址

    由于 24LC64 有 64Kbit 的存储空间,所以我们需要 13 位的地址位宽才能寻址所有的存储空间,由于 IIC 协议规定只能以字节形式写入,所以必须将 13 位的地址扩展为 16 位的地址,分为高八位和低八位,多出来的前三位填充任意数据即可,对我们的寻址地址没有影响。


4. 停止信号

FPGA


5. 应答信号 ACK

    应答信号是由数据接收方发出的,当 SCL 为高电平期间,如果监测到 SDA为低电平,说明有应答信号。


6. 非应答信号 NO ACK

    非应答信号也是由数据接收方发出的,当 SCL 为高电平期间,如果 SDA 为高电平,说明有非应答信号。

    说明:由于 IIC 总线协议启动和停止信号都是在 SCL 高电平期间发生跳变,这就决定了我们其他数据的改变只能发生在 SCL 低电平期间,在 SCL 为高电平期间,数据必须保持稳定。即在 SCL 低电平改变数据,在 SCL 高电平采集数据。

    相比于单字节读写,页面读写只是增加了几个状态,具体时序如下,这里和后面的设计代码不做详细论述。


Page Write 时序如下:

FPGA


Page Read 时序如下:

FPGA


设计架构图: 

    本设计用两个按键控制 EEPROM 读写,当写按键按下时,向 EEPROM 某一固定地址写入一个字节数据,当读按键按下时,将该地址数据读出,并显示到数码管,LED 灯是一个标志信号,LED 亮说明数据写入完毕。设计架构如下:

FPGA


设计代码: 

iic_wr模块代码:负责进行IIC数据的读写

0   module iic_wr (  

1                       clk_sys, //系统时钟

2                       rst_n,   //系统复位

3                       eeprom_scl,//eeprom 串行时钟信号

4                       eeprom_sda,//eeprom 串行数据信号

5                       key_wr,  //外部写控制按键

6                       key_rd,  //外部读控制按键

7                       result,  //数据采集结果寄存器

8                       led     //led指示灯

9                    );

10                   

11  input clk_sys;  

12  input rst_n;

13  input key_wr;

14  input key_rd;

15 

16  output reg eeprom_scl;

17  inout eeprom_sda;

18  output reg led;

19  reg clk;

20  reg [7:0]cnt;   //分频计数器

21  reg [7:0]state; //状态寄存器

22  reg [3:0]counter;//数据移位计数器

23  reg link_sda;   //总线开关

24  reg wr;         //写标志寄存器

25  reg rd;         //读标志寄存器

26  reg sda_buf;     //总线数据缓存器

27  output reg [7:0]result;

28  reg [7:0]data;   //待发送控制字、地址、数据寄存器

29  assign eeprom_sda=(link_sda)?sda_buf:1'hz;

30 

31  //-----------------system clk----------------

32  //系统时钟分频

33  always @(posedge clk_sys or negedge rst_n)

34      begin

35          if(!rst_n)

36              begin

37                  clk<=0;

38                  cnt<=0;

39              end

40          else

41              begin

42                  if(cnt<250)

43                      cnt<=cnt+1'b1;

44                  else

45                      begin

46                          clk<=~clk;

47                          cnt<=0;

48                      end

49              end

50      end

51 

52  //-----------------eeprom scl-----------------

53  //产生eeprom scl信号

54  always @(negedge clk or negedge rst_n)

55      begin

56          if(!rst_n)

57              begin

58                  eeprom_scl<=0;

59              end

60          else

61              eeprom_scl<=~eeprom_scl;

62      end

63 

64  //----------------- eeprom contral-----------

65  always @(posedge clk or negedge rst_n)

66      begin

67          if(!rst_n)     //所有寄存器复位

68              begin

69                  state<=0;

70                  link_sda<=0;

71                  sda_buf<=0;

72                  counter<=0;

73                  wr<=0;

74                  led<=1;

75                  rd<=0;

76                  result<=0;

77                  data<=0;

78              end

79          else

80              begin

81                  case(state)

82  //--------------send start singial-------------

83                      0:begin

84                          if(!key_wr) //检测外部写控制按键是否按下

85                              wr<=1;

86                          if(!key_rd) //检测外部读控制按键是否按下

87                              rd<=1;

88                              

89                          if(((rd==1)||(wr==1))&&(!eeprom_scl))

90                              begin

91                                  link_sda<=1;

92                                  sda_buf<=1;

93                                  state<=1;

94                              end

95                      end

96                      1:begin

97                          if(eeprom_scl)//高电平期间,使sda由高变低,启动串行传输

98                              begin

99                                  sda_buf<=0;

100                                 state<=2;

101                                 data<=8'b10100000;//写控制字准备

102                             end

103                     end

104 //--------------send countral word--------------

105                     2:begin

106                         if((counter<8)&&(!eeprom_scl))//scl低电平期间,完成并串转换,发出写控制字

107                             begin

108                                 counter<=counter+1'b1;

109                                 data<={data[6:0],data[7]};

110                                 sda_buf<=data[7];

111                             end

112                         else if((counter==8)&&(!eeprom_scl))

113                             begin

114                                 counter<=0;

115                                 state<=3;

116                                 link_sda<=0;//FPGA释放总线控制权

117                             end

118                     end

119 //--------------receive ack singial--------------

120                     3:begin

121                         if(eeprom_scl)//scl高电平期间,检测是否有应答信号

122                             begin

123                                 if(!eeprom_sda)

124                                     begin

125                                         state<=4;//有应答则状态继续跳转

126                                         data<=8'b00000000;//高字节地址准备

127                                     end

128                             end

129                     end

130 //--------------send high Byte address--------------

131                     4:begin

132                         link_sda<=1;//FPGA控制总线

133                         if((counter<8)&&(!eeprom_scl))//scl低电平期间,完成并串转换,发出高字节地址

134                             begin

135                                 counter<=counter+1'b1;

136                                 data<={data[6:0],data[7]};

137                                 sda_buf<=data[7];

138                             end

139                         else if((counter==8)&&(!eeprom_scl))

140                             begin

141                                 counter<=0;

142                                 state<=5;

143                                 link_sda<=0;//FPGA释放总线控制权

144                             end

145                     end

146 //--------------receive ack singial--------------

147                     5:begin

148                         if(eeprom_scl)//scl高电平期间,检测是否有应答信号

149                             begin

150                                 if(!eeprom_sda)

151                                     begin

152                                         state<=6;//有应答则状态继续跳转

153                                         data<=8'b00000011;//低字节地址准备

154                                     end

155                             end

156                     end

157 //--------------send low Byte address--------------

158                     6:begin

159                         link_sda<=1;//FPGA控制总线

160                         if((counter<8)&&(!eeprom_scl))//scl低电平期间,完成并串转换,发出低字节地址

161                             begin

162                                 counter<=counter+1'b1;

163                                 data<={data[6:0],data[7]};

164                                 sda_buf<=data[7];

165                             end

166                         else if((counter==8)&&(!eeprom_scl))

167                             begin

168                                 counter<=0;

169                                 state<=7;

170                                 sda_buf<=1;

171                                 link_sda<=0;//FPGA释放总线控制权

172                             end

173                     end

174 //--------------receive ack singial--------------

175                     7:begin

176                         if(eeprom_scl)//scl高电平期间,检测是否有应答信号

177                             begin

178                                 if(!eeprom_sda)

179                                     begin

180                                         if(wr==1)//如果是写的话,跳到状态8,遵循随机写时序

181                                             state<=8;

182                                         if(rd==1)//如果是读的话,跳到状态11,遵循随机读时序

183                                             begin

184                                                 state<=11;

185                                                 sda_buf<=1;//准备再次发启动信号

186                                             end

187                                         data<=8'b00001111;//准备想要写入的数据

188                                     end

189                             end

190                     end

191 //--------------send active data-------------

192                     8:begin

193                         link_sda<=1;//FPGA控制总线

194                         if((counter<8)&&(!eeprom_scl))//scl低电平期间,完成并串转换,发出有效数据

195                             begin

196                                 counter<=counter+1'b1;

197                                 data<={data[6:0],data[7]};

198                                 sda_buf<=data[7];

199                             end

200                         else if((counter==8)&&(!eeprom_scl))

201                                 begin

202                                     counter<=0;

203                                     state<=9;

204                                     link_sda<=0;//FPGA释放总线控制权

205                                 end

206                     end

207 //--------------receive ack singial--------------                   

208                     9:begin

209                         if(eeprom_scl)//scl高电平期间,检测是否有应答信号

210                             begin

211                                 if(!eeprom_sda)//有应答则状态继续跳转

212                                     state<=10;

213                             end 

214                     end

215 //--------------send stop singial-------------  

216                     10:begin

217                         link_sda<=1;//FPGA控制总线

218                         sda_buf<=0;//拉低sda,准备发出停止信号

219                         if(eeprom_scl)//scl高电平期间,拉高sda,终止串行传输

220                             begin

221                                 led<=0;//点亮led,说明写操作完毕

222                                 sda_buf<=1;

223                                 if(key_wr && key_rd)//在按键放开以后才跳转回空闲状态,避免不断循环写入

224                                     state<=0;//状态跳回

225                                 wr<=0;//清除写控制标志

226                             end

227                     end

228 //------------send start  singial-----------

229                     11:begin

230                         link_sda<=1;//FPGA控制总线

231                         if(eeprom_scl)//scl高电平期间拉低sda,发送启动信号

232                             begin

233                                 sda_buf<=0;

234                                 state<=12;

235                                 data<=8'b10100001;//读控制字准备

236                             end

237                     end

238 //------------send countral word------------

239                     12:begin

240                         if((counter<8)&&(!eeprom_scl))//scl低电平期间,完成并串转换,发出读控制字

241                             begin

242                                 counter<=counter+1'b1;

243                                 data<={data[6:0],data[7]};

244                                 sda_buf<=data[7];

245                             end

246                         else if((counter==8)&&(!eeprom_scl))

247                             begin

248                                 counter<=0;

249                                 state<=13;

250                                 link_sda<=0;//FPGA释放总线控制权

251                             end

252                     end

253 //--------------receive ack singial--------------

254                     13:begin

255                         if(eeprom_scl)//scl高电平期间,检测是否有应答信号

256                             begin

257                                 if(!eeprom_sda)//有应答则状态继续跳转

258                                     state<=14;

259                             end 

260                     end

261 //--------------receive input active data--------------

262                     14:begin

263                         if((counter<8)&&(eeprom_scl))//scl高电平期间,完成串并转换,存储接收数据

264                             begin

265                                 counter<=counter+1'b1;

266                                 result[7-counter]<=eeprom_sda;

267                             end

268                         else if(counter==8)

269                             begin

270                                 counter<=0;

271                                 state<=15;

272                                 sda_buf<=1;

273                                 link_sda<=1;//接收完毕以后FPGA继续控制总线

274                             end

275                     end

276 //--------------send NO ACK singial--------------

277                     15:begin

278                         if(eeprom_scl)//scl高电平期间,将sda总线拉高,发出非应答信号

279                             begin

280                                 sda_buf<=1;

281                                 state<=16;

282                             end

283                     end 

284 //--------------send stop singial-------------

285                     16:begin

286                         if(!eeprom_scl)//scl低电平期间,将sda总线拉低,准备发送停止信号

287                             sda_buf<=0;

288                         if(eeprom_scl)//scl高电平期间,将sda总线拉高,发出停止信号

289                             begin

290                                 sda_buf<=1;//拉高sda

291                                 state<=0;//状态回转

292                                 rd<=0;//清除读标志信号

293                             end

294                     end

295                 default:state<=0;

296                 endcase

297             end

298     end

299 endmodule


seg7_lut模块代码,负责数码管显示

0   module seg7_lut (

1                   oseg,   //数码管段选

2                   idig,   //驱动数据

3                   clk_sys,//系统时钟

4                   sel,    //数码管位选

5                   rst_n   //系统复位

6                  );

7   //--------输入信号--------               

8   input [7:0] idig;

9   input clk_sys;

10  input rst_n;

11  //--------输出信号--------

12  output reg[7:0] oseg;  

13  output reg[2:0] sel;

14  //--------中间变量--------

15  reg [3:0]cnt;

16  //-----------------系统时钟分频-----------------

17  always @(posedge clk_sys or negedge rst_n)

18      begin

19          if(!rst_n)

20              begin

21                  sel<=3'd0;//数码管段选定位

22              end

23      end

24

25  //--------数码管显示译码--------

26  always@(*)

27         case(idig)

28              8'h0:oseg<=8'hC0;

29              8'h1:oseg<=8'hF9;

30              8'h2:oseg<=8'hA4;

31              8'h3:oseg<=8'hB0;

32              8'h4:oseg<=8'h99;

33              8'h5:oseg<=8'h92;

34              8'h6:oseg<=8'h82;

35              8'h7:oseg<=8'hF8;

36              8'h8:oseg<=8'h80;

37              8'h9:oseg<=8'h90;

38              8'hA:oseg<=8'h88;

39              8'hB:oseg<=8'h83;

40              8'hC:oseg<=8'hC6;

41              8'hD:oseg<=8'hA1;

42              8'hE:oseg<=8'h86;

43              8'hF:oseg<=8'h8E;

44           endcase   

45           

46  endmodule                    


IIC顶层模块代码

0   module IIC (

1                   clk_sys,

2                   rst_n,

3                   eeprom_scl,

4                   eeprom_sda,

5                   key_wr,

6                   key_rd,

7                   oseg,

8                   sel,     

9                   led

10                );  

11  //--------输入端口--------    

12  input clk_sys;

13  input rst_n;

14  input key_wr;

15  input key_rd;

16  //--------输出端口--------

17  output eeprom_scl;

18  output [7:0]oseg;

19  output [2:0]sel;

20  output led;

21  //--------双向总线--------

22  inout  eeprom_sda;

23  //--------内部连线--------

24  wire [7:0]result;

25  //--------实例化eeprom控制模块--------

26  iic_wr iic_wr    (

27                      .clk_sys(clk_sys),

28                      .rst_n(rst_n),

29                      .eeprom_scl(eeprom_scl),

30                      .eeprom_sda(eeprom_sda),

31                      .key_wr(key_wr),

32                      .key_rd(key_rd),

33                      .result(result),

34                      .led(led)

35                   );

36  //--------实例化数码管显示驱动模块--------

37  seg7_lut seg7_lut   (

38                          .oseg(oseg),

39                          .idig(result),

40                          .clk_sys(clk_sys),

41                          .sel(sel),

42    .rst_n(rst_n)

43                      );

44                      

45  endmodule  


tb顶层测试模块代码

0   `timescale 1ns/1ns

1 

2   module tb;   

3 

4   reg clk_sys;

5   reg rst_n;

6   reg key_wr;

7   reg key_rd;

8 

9   wire eeprom_scl;

10

11  wire eeprom_sda;

12  wire [7:0]oseg;  

13  wire [2:0]sel;

14  wire led;

15

16  initial

17      begin

18          clk_sys=0;

19          key_rd=1;

20          rst_n=0;

21          #1000 rst_n=1;

22          #500 key_wr=0;

23          #3000 key_wr=1;

24          # 400000 key_rd=0;

25          #300 key_rd=1;

26      end

27

28  always #10 clk_sys=~clk_sys;

29

30  IIC IIC(

31          .clk_sys(clk_sys),

32          .rst_n(rst_n),

33          .eeprom_scl(eeprom_scl),

34          .eeprom_sda(eeprom_sda),

35          .key_wr(key_wr),

36          .key_rd(key_rd),

37          .oseg(oseg),

38          .sel(sel),

39          .led(led)

40        );

41                

42  endmodule


仿真图:

随机读写,仿真写时序:



随机读写,仿真读时序:


    在仿真时,需要将检测应答的状态跳过,直接向下一状态跳转,观察读写时序,当读写按键按下时,都会产生对应的动作。




打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
评论(0)
发评论
复位键 2019-04-11
0 回复 举报
这个”检测应答的状态跳过“,仿真怎么操作应答跳过,楼主能讲一下吗 收起回复

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分