串口通信系列(二)、I2C通信方式

2020-06-30 11:15:08 浏览数 (2)

一、I2C简介

IIC全称为Inter Integrated Circuit:两根通信线:一根时钟线SCL一根数据线SDA,只有一根数据线,所以是半双工通信。数据的传输速率在标准模式下可达100kbit/s,在快速模式下可达400kbit/s,高速模式下可达3.4Mbit/s。

IIC总线的器件输出基本为开漏输出,所以需要接总线需要接上拉电阻,连接示意图:

起始信号:

由于总线上接有上拉电阻,在空闲状态时SCL与SDA都是高电平。所以起始位为:

当SCL为高电平时,SDA总线拉低。

数据传输:

IIC总线在进行数据传输时,时钟信号为高电平期间,数据线上的数据必须保持稳定只有在SCL为低电平期间,数据线上的电平状态才允许有变化。

停止信号:

上面提到了在SCL为高电平时数据线不允许有变化,这时为了防止触发停止位,停止位为:

在SCL为高电平时,SDA从低电平跳变到高电平。

IIC传输时序图:

分析IIC时序图可以看到:在SCL为高时,SDA拉低表示起始位;之后开始数据传输,SCL拉低,在SCL为低期间给SDA赋值,SCL的0 1作为一个完整时钟,传输8bit数据需要8个完整SCL时钟;在第8个时钟末,主机释放SDA等待从机应答,如果第9个周期从机将SDA拉低,表示应答成功。在第九个时钟周期末,从机释放SDA使主机继续传输数据,如果主机发送停止信号,此次传输结束。

在数据发送期间,最先发送的是最高位!

二、 器件地址

每个I2C器件都有一个器件,器件地址由固定与部分可编程两种。对于E2PROM器件地址为1010 3bit可硬件设置地址,当硬件电路上分别将这三个管脚连接到GND或VCC时候,就可以设置不同的可编程地址。

当AT24C64的可编程管脚全部接地,传输格式为:

进行数据传输时,主机首先向总线上发送起始信号,然后按照从高到低的位序发送器件地址,第8bit为读写控制位R/W_N,N表示低电平有效。1:主机对从机读取操作,然后接收从机响应。

三、读写操作

对 器件中的存储单元进行读写操作时,首先要指定存储单元的地址,然后向该地址读写内容。该地址的长度为1个或2个字节。当一个存储单元数量不超过8bit,用一个字节表示。超过一个字节所能表示的最大容量时,使用2个字节表示。

Example:

AT24C02存储单元容量为2Kb,每个存储单元存储8bit数据,一共2^8的存储单元,。需要1个字节的传输地址。在成功启动AT24C02后,未发送停止位,而是发送要进行操作的地址,单字节地址分布

AT24C64的存储单元容量为64Kb,每个存储单元存储一个字节,则一个8K个字节,需要2^13个地址,地址宽度为13bit,需要两个字节传输地址。在传输地址时。低8位在第二次地址传输,5位在第一次地址传输的低5位,双字节地址传输

主机发送完字地址,从机正确应答正确后就把内部存储单元地址指针指向该存储单元。写数据:分为单次写(对于EEPROM称为字节写)和连续写(对于EEPROM称为页写),两者的区别在于发送完一字节数据后,是发送结束信号还是继续发送下一字节数据。如果发送的是结束信号,就是单次写。如果继续发送下一字节数据就称为连续写。

读数据:如果读写控制位为1即为读命令,主机就处于接收数据的状态,从机从该地址单元输出数据。数据读取有三种方式:当前地址读、随机读、连续读。

(1)、当前地址读:

在一次读或写操作之后发起读操作,由于I2C器件在读写操作之后,内部的地址指针自动加一,所以当前地址读读取的是下一字地址的数据。

(2)、随机读:

第一次发送器件地址时后面的控制位为0(写指令),第二次发送器件地址时候后面的读写控制位为1(读指令)。因为需要使从机内存储单元地址指针指向想要读取的存储单元,所以先发送一次Dummy Write(虚写操作), 并不是真的写入数据,而是通过虚写操作使地址指针指向虚写操作中字地址的位置,等从机应答后就可以以当前地址读的方式读取数据。

(3)、连续读:

当前地址读和随机读都是一次读取一个字节,将当前地址读或随机读的主机应答改为主机非应答,表示继续读取数据。

四、 分频时钟的计算

这是最近看到的一个很骚的代码,想起来就想笑,绝对是大神写的,骚的不行哈哈哈哈,真心佩服。

首先,CLK_FREQ是系统的输入时钟频率,I2C_FREQ是设定的IIC通信时钟频率。要生成IIC_SCL这样一个时钟的话肯定要分频,分多少?

CLK_FREQ/I2C_FREQ是一个SCL周期包含的系统时钟个数,一个SCL包含一个高电平一个低电平,那么就需要再除以2,(CLK_FREQ/I2C_FREQ)>> 2'd1;表示半个SCL包含的系统时钟个数。

在每半个SCL时钟周期翻转一个SCL,那么就还需要一个这样的时钟信号,在这个时钟的上升沿使得SCL翻转,该时钟是SCL频率的二倍,那么这个时钟包含的系统时钟个数就是:(CLK_FREQ/I2C_FREQ) >> 2'd2。

本来这样就可以了,可是顾虑到SCL为低电平时候才允许数据发送变化,索性再分一次,将SCL拉低的时间与SDA变化的时间分隔开,这样操作更加稳定。

所以最后就是生成一个4倍SCL的时钟。

代码:

//产生二倍速率的驱动时钟,模块驱动时钟的分频系数assign clk_divide = (CLK_FREQ/I2C_FREQ) >> 2'd2;//生成I2C的SCL的四倍频率的驱动时钟用于驱动i2c的操作always @(posedge clk or negedge rst_n) begin if(!rst_n) begin dri_clk <= 1'b0; clk_cnt <= 10'd0; end else if(clk_cnt == clk_divide[8:1] - 1'd1) begin clk_cnt <= 10'd0; dri_clk <= ~dri_clk; end else clk_cnt <= clk_cnt 1'b1;end

五、状态机设计

上面已经设置好IIC_SCL的驱动时钟,接下来就是设计IIC的状态机。分析系统的控制信号输入:

(1)、IIC_EXEC:IIC通信执行的触发信号

(2)、bit_ctrl:地址控制信号,是8bit还是16bit的地址

(3)、i2c_rh_wl:读写类型控制信号输入

(4)、i2c_addr:I2C器件地址

(5)、i2c_data_w:IIC要写的数据

(6)、i2c_data_r:IIC读取的数据

(7)、i2c_done: IIC一次操作完成

(8)、i2c_ack: IIC的应答信号 0:应答 1:非应答

分析工作状态:

平时肯定是处于空闲状态——之后接收到IIC执行信号——要看是读取还是写入操作——与从机建立通信即发送从机地址——然后传输要读取或写入的地址——之后进行数据读取或写入——结束

0 人点赞