串行通信接口-IIC(1)接口协议

串行通信接口-IIC(1)接口协议介绍传统串行通信接口 IIC 协议内容 消息类型 接口时序 读写过程 并提供仿真分析及源码 iic 接口

大家好,欢迎来到IT知识分享网。

在这里插入图片描述

1.IIC简介

1.1 IIC概述

IIC即 Inter-Integrated Circuit(集成电路总线),是由 Philips 半导体公司在八十年代初设计出来的一种简单、双向、二线制总线标准。多用于主机和从机在数据量不大且传输距离短的场合下的主从通信。主机启动总线,并产生时钟用于传送数据,此时任何接收数据的器件均被认为是从机。

传输速率标准模式下可以达到100kb/s,快速模式下可以达到 400kb/s,高速模式下可达 3.4Mbit/s,具体使用要看设备的支持情况。IIC接口只需要两条总线线路,即 SCL(串行时钟线)、SDA(串行数据线),IIC 总线是半双工通信。本篇解决的问题就是使用FPGA驱动这两根线实现标准IIC协议,但要清楚使用IIC接口于某设备通信要同样遵守设备的要求,例如IIC接口的EEPROM,会要求现发送读写地址,发送读写指示等,这个例程将在下一篇给出,下一篇连接在文末。

与UART不同的是,IIC总线上可以挂载若干个从机(一对多)通过主机下发的器件地址选择从机进行通信,并且需要有双向IO的支持,抗干扰能力较弱。每个连接在总线上的 IIC 器件都有一个唯一的器件地址,在通信的时候就是靠这个地址来握手的。而UART为异步串行通信,可称为点对点通信,较为复杂,需要有波特率,即数据传输的间隔要相等,以及接收和发送两根线可实现全双工。

IIC传输时,按照从高到低的位序进行传输,即采用大端模式。大端模式:就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。小端模式:是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。

1.2 总线结构

严格讲,主机不是直接向从机发送地址,而是主机往总线上发送地址,所有的从机都能接收到主机发出的地址,然后每个从机都将主机发出的地址与自己的地址比较,如果匹配上了,这个从机就会向总线发出一个响应信号。主机收到响应信号后,开始向总线上发送数据, 与这个从机的通讯就建立起来了。如果主机没有收到响应信号,则表示寻址失败。

需要注意的是,对于 i2c 总线,要求连接到总线上的输出端必须是开漏输出结构,给不了高电平,所以总线上所有的高电平应该是由上拉电阻上拉达到效果的,而不是由主机直接给总线赋值 1 就能实现, 本例中需要输出高电平时,输出高阻状态。

2.IIC通信协议

2.1消息种类

在这里插入图片描述
IIC 协议规定,在时钟(SCL)为高电平的时候,数据总线(SDA)必须保持稳定,数据总线(SDA)在时钟(SCL)为低电平的时候才能改变。因此,当SCL为高电平时,改变SDA的值,这些情况被赋予了特殊的含义,主机向从机发送的信息种类有启动信号、停止信号、7位地址码、读/写控制位、10位地址码(地址扩展)、数据字节、重启动信号、应答信号、时钟脉冲。而从机向主机发送的信息种类有应答信号、数据字节、时钟低电平(时钟拉伸)。
1.空闲状态:在空闲状态下SDA与SCL均为高电平;
2.起始信号:在时钟(SCL)为高电平的时候,数据总线(SDA)由高到低的跳变;
3.停止信号:在时钟(SCL)为高电平的时候,数据总线(SDA)由低到高的跳变;
在这里插入图片描述
4. 重新开始信号:在IIC总线上,由主机发送一个开始信号启动一次通信后,在首次发送停止信号之前,主机通过发送重新开始信号,可以转换与当前从机的通信模式,或是切换到与另一个从机通信。当SCL为高电平时,SDA由高电平向低电平跳变,产生重新开始信号,它的本质就是一个开始信号。
在这里插入图片描述
5.应答位IIC总线上的所有数据都是以8位字节传送的,发送器每发送一个字节,就在第9个时钟脉冲期间释放数据线,由接收器反馈一个应答信号。 应答信号为低电平时,规定为有效应答位(ACK简称应答位),表示接收器已经成功地接收了该字节;应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。因此一个完整的字节数据传输需要9个时钟脉冲。如果从机作为接收方向主机发送非应答信号,主机方就认为此次数据传输失败;如果是主机作为接收方,在从机发送器发送完一个字节数据后,向从机发送了非应答信号,从机就认为数据传输结束,并释放SDA线。不论是以上哪种情况都会终止数据传输,这时主机或是产生停止信号释放总线或是产生重新开始信号,开始一次新的通信。







2.2通信过程

写过程
在这里插入图片描述

3.开发思路

IIC协议虽然只有两根线,协议规定也简明清晰,但是要使用HDL实现,并不是一件“愉快”的事情,因为思路很难理清楚,可能会导致代码臃肿,资源浪费的情况出现。如下图所示为iic_driver模块,IIC协议规定每8bit数据为一个传输单位,需要有一个ACK信号,基于此,将所有的IIC消息分为带起始位写8bit,不带起始位写8bit,带停止位写8bit,不带起始位和停止位读8bit,带停止位的读五种操作类型,默认都带有应答位。因此创建IIC驱动模块的主要思路就是,保持读或写8bit的主体结构,区分五种命令以区分是否带起始位、是否带停止位、是否有ACK、是读还是写。当针对不同的设备器件写入不同的器件地址,或者是不同器件有不同的操作要求时,可在此驱动之上写一个模块用来满足器件要求,称此模块为iic_ctrl模块。例如,某型eeprom的一条写入数据命令依次为,带起始信号写入器件地址和写请求,不带起始位写入EEPROM内部地址,带停止位写入要写入的8bit数据,完成写入操作,iic_ctrl的任务就是将此写入命令拆分成了3个8bit主体,然后“调用”驱动模块完成写入操作。本节仅介绍驱动模块的编写,如下图所示。clk和rst分别为该模块的时钟和脉冲信号;iic_en为输入脉冲信号,拉高一次代表执行一次“8bit操作”,cmd表征了具体执行哪一个“8bit”操作,cmd[0] 1为读操作,0为写操作。cmd[1] 1为带起始位,0为不带起始位。cmd[2] 1为带停止位,0为不带停止位。cmd[3] 1为ACK,0为NOACK(在带停止位的读或者写操作中可以不要ACK信号)。Trans_data为要传入的8bit数据,不同的操作可能包含了器件地址、读写指示等。矩形框右侧为输出信号,iic_busy代表驱动模块正在忙,无法相应当前请求。Ack_fail标识是否有相应失败的情况,reci分别是读取到的数据和该数据的同步信号。
在这里插入图片描述

4.仿真分析

iic_sim文件如下:

module iic_sim(); reg clk_50; reg rst_n; reg iic_en; reg [3:0]cmd; reg [7:0]trans_data; wire iic_busy; wire ack_fail; wire [7:0]reci_data;//接收数据 wire reci_dvalid;//接收数据同步信号 wire iic_clk; wire iic_data;//使用force initial begin clk_50 = 0; rst_n = 0; iic_en = 0; cmd = 0; trans_data = 0; #1000 rst_n = 1; #100 //仿真写过程:向某器件地址中写入data1和data2 //写起始位+器件地址+读写指示+ACK do_iic(1'b1,4'b1010,8'b); #20 iic_en = 0; @(negedge iic_busy) #10 //写8bit数据1+ACK do_iic(1'b1,4'b1000,8'b00); #20 iic_en = 0; @(negedge iic_busy) #10 //写8bit数据2+ACK+停止位 do_iic(1'b1,4'b1100,8'b0); #20 iic_en = 0; @(negedge iic_busy) #110 //仿真 8bit读 过程 do_iic(1'b1,4'b1001,8'b0); #20 iic_en = 0; end always #10 begin clk_50 = ~clk_50; end task do_iic; input user_en; input [3:0]user_cmd; input [7:0]user_data; begin iic_en <= user_en; cmd <= user_cmd; trans_data <= user_data; end endtask //将一次eeprom动作拆解成多个iic动作 iic_driver sub_for_one_iic( .clk_50 (clk_50), .rst_n (rst_n), .iic_en (iic_en), .cmd (cmd), //3:0 .trans_data (trans_data), .iic_busy (iic_busy), .ack_fail (ack_fail), .reci_data (reci_data), .reci_dvalid(reci_dvalid), .iic_clk (iic_clk), .iic_data (iic_data) ); endmodule 

iic_driver文件如下:

module iic_driver( input clk_50, input rst_n, input iic_en, input [3:0]cmd, input [7:0]trans_data, output reg iic_busy, output reg ack_fail, output reg [7:0]reci_data,//接收数据 output reg reci_dvalid,//接收数据同步信号 output reg iic_clk, inout iic_data ); //系统时钟采用50MHz parameter SYS_CLOCK = 50_000_000; //SCL总线时钟采用400kHz parameter SCL_CLOCK = 400_000; //产生时钟SCL计数器最大值 localparam SCL_CNT_M = SYS_CLOCK/SCL_CLOCK/4 - 1; reg[3:0]state; reg[3:0]sub_cmd; reg[7:0]sub_data; reg sub_ack; always@(posedge clk_50 or negedge rst_n) if(!rst_n)begin sub_cmd <= 4'd0; sub_data<= 8'd0; end else if(iic_en)begin sub_cmd <= cmd; sub_data<= trans_data; end reg en_div; reg [19:0]div_cnt; always@(posedge clk_50 or negedge rst_n) if(!rst_n) div_cnt <= 20'd0; else if(en_div)begin if(div_cnt < SCL_CNT_M) div_cnt <= div_cnt + 1'b1; else div_cnt <= 0; end else div_cnt <= 0; wire imp_node; reg out_oe; //1代表写 , 0代表读. reg out_10;//代表输出高地电平 reg [5:0]rd_cnt; assign imp_node = div_cnt == SCL_CNT_M; reg [1:0]cnt; //时钟生成 iic_clk信号 always@(posedge clk_50 or negedge rst_n) if(!rst_n)begin iic_clk <= 0; end else if(en_div)begin if(imp_node)begin cnt <= cnt + 1; case (cnt) 0:iic_clk <= 0; 1:iic_clk <= 1; 2:iic_clk <= 1; 3:iic_clk <= 0; default: iic_clk <= 0; endcase end end assign iic_data = !out_10 && out_oe ? 1'b0:1'bz; //IIC上拉电阻,高电平无需输出 always@(posedge clk_50 or negedge rst_n) if(!rst_n)begin state <= 0; en_div<= 0; cnt <= 0; out_10 <= 1; out_oe <= 0; rd_cnt <= 0; reci_dvalid <= 0; sub_ack <= 0; ack_fail <= 0; iic_busy<=0; reci_data <=0; end else begin case (state) 0:begin //接收指令,进入选择选项 ack_fail <= 0; if(iic_en)begin en_div<=1; iic_busy<=1; state <= state + 1; end else begin en_div<=0; end end 1:begin//根据不同指令分发 //cmd[0] 1为读操作,0为写操作 //cmd[1] 1为带起始位,0为不带起始位 //cmd[2] 1为带停止位,0为不带停止位 //cmd[3] 1为ACK,0为NOACK if(sub_cmd[1]) state <= 2; else if(sub_cmd[0]) state <= 3; else state <= 4; end 2:begin //生成起始位 START if(imp_node)begin case(cnt) 0:begin out_10 <= 1; out_oe <= 1'd1;end //keep high(clk=0) 1:;//data high and clk=1 2:begin out_10 <= 0; end//data high clk=0 产生起始位 3:; default:begin out_10 <= 1;end endcase if(cnt==3)begin if(sub_cmd[0]) //读操作 state <= 3; else state <= 4; //写操作 end end end 3:begin //读操作  if(imp_node)begin case(cnt) 0:begin out_oe <= 1'd0;end 1:; 2:begin reci_data <= { 
   reci_data[6:0],iic_data}; end 3:begin if(rd_cnt<7) rd_cnt <= rd_cnt + 1; else begin rd_cnt <= 0; state <= 6; reci_dvalid <= 1; end end default:begin out_10 <= 1;end endcase end end 4:begin //写操作 if(imp_node)begin case(cnt) 0:begin out_10 <= sub_data[7-rd_cnt];out_oe<=1'b1;end 1:; 2:; 3:begin if(rd_cnt<7) rd_cnt <= rd_cnt + 1; else begin rd_cnt <= 0; state <= 5; end end default:begin out_10 <= 1;end endcase end end 5:begin //获取应答 if(imp_node)begin case(cnt) 0:begin out_oe<=1'b0;end 1:; 2:sub_ack <= iic_data; 3:begin //状态跳转 if(sub_cmd[2]) state <= 7; //产生终止位 else begin state <= 0; iic_busy <= 0; end //收到错误应答 if(sub_ack) ack_fail <= 1; end default:begin out_10 <= 1;end endcase end end 6:begin //生成应答 reci_dvalid <= 0; if(imp_node)begin case(cnt) 0:begin out_oe<=1'b1; if(sub_cmd[3])//应答 out_10 <=0; else out_10 <=1; end 1:; 2:; 3:begin if(sub_cmd[2]) state <= 7; else begin iic_busy <= 0; state <= 0; end end default:begin out_10 <= 1;end endcase end end 7:begin //产生终止信号 ack_fail <= 0; if(imp_node)begin case(cnt) 0:begin out_10 <= 0;out_oe<=1'b1;end 1:; 2:begin out_10 <= 1; end 3:begin iic_busy <= 0; state <= 0; end default:begin out_10 <= 1;end endcase end end default: state <= 0; endcase end endmodule 

5.传送门

  • 我的主页
  • FPGA通信接口专栏汇总导航
  • 『FPGA通信接口』串行通信接口-IIC(2)IIC实战
END

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/121924.html

(0)
上一篇 2025-10-20 17:10
下一篇 2025-10-20 17:20

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信