最近遇到一个BUG,跟IIC通信有关,所以借这个机会总结一下IIC总线协议

1.引脚接口介绍

1.A0,A1,A2为24LC64的片选信号,IIC总线最多可以挂载8个IIC接口器件,通过对A0,A1,A2寻址,可以实现对不同的EEPROM操作

2.WP为读写使能信号,当WP悬空或者接地,EEPROM可读可写,当WP接电源,EEPROM只能读不能写。因为我们要对EEPROM写,所以这里WP信号悬空

3.SCL为时钟信号线,最高频率400Khz

4.SDA为数据线,双向线(inout),当为in时,数据通过SDA写到EEPROM。为out时,eeprom读出来的数据通过SDA传到外面

2.接口时序

IIC读写时序分为随机读写和页读写,这里只研究随机读写

2.1 写时序

写操作步骤

1.发送启动信号

2.发送控制字写(1010_A0A1A2_0 )

3.EEPROM发送应答信号ACK

4.发送高字节写地址

5.EEPROM发送应答信号ACK

6.发送低字节写地址

7.EEPROM发送应答信号ACK

8.发送8bit写数据

9.EEPROM发送应答信号ACK

10.发送停止信号

2.2 读时序

读操作信号

1.发送启动信号

2.发送控制字写(1010_A0A1A2_0)

3.EEPROM发送应答信号ACK

4.发送高字节读地址

5.EEPROM发送应答信号ACK

6.发送低字节读地址

7.EEPROM发送应答信号ACK

8.发送启动信号

9.发送控制字读(1010_A0A1A2_1)

10.EEPROM发送应答信号ACK

11.读取一个8bit数据

12..EEPROM发送NO ACK信号

13.发送停止信号

3.操作步骤解析

3.1启动信号

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

3.2控制字

1010_A0A1A2X,

1.1010为EEPROM信号标识,为一组固定的序列

2.A0A1A2为片选信号,由于只有一个flash,所以A0A1A2在这里全为0

3.最后一个bit X,为0时代表写,为1时代表读。

3.3地址

24LC64表示有64Kbit的存储空间,需要13位地址线寻址。但是IIC是以字节的实行操作的,所以需要13位地址线扩展成16位,高3位随意填0或者1,习惯填0

3.4应答信号与非应答信号

应答信号和非应答信号都是由数据接收方(EEPROM)发出的,当SCL为高电平时候,如果检测到SDA为低电平,说明有应答信号。如果检测到SDA为高电平,说明有非应答信号。所以在应答时钟周期的时候,我们要释放SDA信号线,让EEPROM通过SDA发送一个低电平或者高电平过来。

3.5停止信号

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

3.6 数据传输

由于IIC总线协议的启动和停止信号都是在SCL高电平期间发生跳变,这就决定了其数据只能在SCL低电平期间发生改变,不然会被当做启动或者停止信号处理。在SCL为高电平期间,数据必须保持稳定。即在SCL低电平的时候改变数据,高电平的时候采集数据

4关键代码解析

4.1状态机设置

4.2 sda信号线控制

由于sda是inout型,读写都是有这根线控制。所以我们要有一个信号,来指示sda信号线什么时候写,什么时候是读。

当link_sda信号为1的时候,指示sda信号写。这时候我们把需要写的数据一个bit一个bit的赋给中间变量sda_buf信号,该信号经过sda信号线把数据写进flash

当link_sda信号为0的时候,指示sda信号读。

完整代码如下

module iic_control(
input wire sclk,
input wire reset,
input wire key_wr,
input wire key_rd, output reg scl,
inout wire sda,
output wire[:] dataout,
output reg led
); parameter IDLE = 'b00_0000_0000_0000,
start1 = 'b00_0000_0000_0001,
control_byte1 = 'b00_0000_0000_0010,
ack1 = 'b00_0000_0000_0100,
high_addr_byte = 'b00_0000_0000_1000,
ack2 = 'b00_0000_0001_0000,
low_addr_byte = 'b00_0000_0010_0000,
ack3 = 'b00_0000_0100_0000,
start2 = 'b00_0000_1000_0000,
control_byte2 = 'b00_0001_0000_0000,
ack4 = 'b00_0010_0000_0000,
transfer_data = 'b00_0100_0000_0000,
ack5 = 'b00_1000_0000_0000,
no_ack = 'b01_0000_0000_0000,
stop = 'b10_0000_0000_0000; reg[:] state; reg[:] cnt; //分频计数
reg link_sda; //总线开关
reg sda_buf; //总线数据缓存器
reg wr; //写使能
reg rd; //读使能 reg[:] data;
reg[:] cnt_num;
reg[:] result; assign sda=(link_sda)?sda_buf:'hz;
assign dataout = result; always@(posedge sclk or negedge reset)
if(!reset)
cnt <= 'd0;
else if(cnt=='d124)
cnt <= 'd0;
else
cnt <= cnt + 'b1; always@(posedge sclk or negedge reset)
if(!reset)
scl <= 'b0;
else if(cnt=='d30)
scl <= 'b1;
else if(cnt=='d93)
scl <= 'b0; always@(posedge sclk or negedge reset)
if(!reset==)
begin
state <= IDLE;
link_sda <= ;
sda_buf <= ;
data <= ;
cnt_num <= 'd0;
result <= 'd0;
led <= ;
end
else case(state)
IDLE:
begin
if(!key_wr)
wr <= ;
if(!key_rd)
rd <= ;
if((wr==)||(rd==)&&(!scl==))
begin
state <= start1;
link_sda <= ;
sda_buf <= ;
data <= 'b10100000; //写控制字准备
end
end
start1:
begin
if((scl==)&&(cnt=='d61))
begin
state <= control_byte1;
link_sda <= ;
sda_buf <= ;
end
end
control_byte1:
begin
if((cnt_num<'d8)&&(cnt==7'd124))
begin
cnt_num <= cnt_num + ;
sda_buf <= data[];
data <= {data[:],data[]};
end
else if((cnt_num=='d8)&&(cnt==7'd124))
begin
state <= ack1;
link_sda <= ;
cnt_num <= ;
end
end
ack1:
begin
// if((scl==1)&&(cnt==7'd61)&&(sda==1'b0))
// begin
// state <= high_addr_byte;
// data <= 8'b00000000; //高字节地址准备
// link_sda <= 1;
// end
if((scl==)&&(cnt=='d61))
begin
state <= high_addr_byte;
data <= 'b00000000; //高字节地址准备
// link_sda <= 1;
end
end
high_addr_byte:
begin
if(cnt=='d124)
begin
link_sda <= ;
end
if((cnt_num<'d8)&&(cnt==7'd124))
begin
cnt_num <= cnt_num + ;
sda_buf <= data[];
data <= {data[:],data[]};
end
else if((cnt_num=='d8)&&(cnt==7'd124))
begin
state <= ack2;
link_sda <= ;
cnt_num <= ;
end
end
ack2:
begin
// if((scl==1)&&(cnt==7'd61)&&(sda==1'b0))
// begin
// state <= low_addr_byte;
// data <= 8'b00000000; //低字节地址准备
// link_sda <= 1;
// end
if((scl==)&&(cnt=='d61))
begin
state <= low_addr_byte;
data <= 'b00000000; //低字节地址准备
// link_sda <= 1;
end
end
low_addr_byte:
begin
if(cnt=='d124)
begin
link_sda <= ;
end
if((cnt_num<'d8)&&(cnt==7'd124))
begin
cnt_num <= cnt_num + ;
sda_buf <= data[];
data <= {data[:],data[]};
end
else if((cnt_num=='d8)&&(cnt==7'd124))
begin
state <= ack3;
link_sda <= ;
cnt_num <= ;
end
end
ack3:
begin
// if((scl==1)&&(cnt==7'd61)&&(sda==1'b0))
// begin
// link_sda <= 1;
// if(wr==1)
// begin
// state <= transfer_data;
// data <= 8'b10101010;//准备想要写入的数据
// end
// if(rd==1)
// begin
// state <= start2;
// sda_buf <= 1;//准备再次发启动信号
// end
// end
if((scl==)&&(cnt=='d61))
begin
// link_sda <= 1;
if(wr==)
begin
state <= transfer_data;
data <= 'b10101010;//准备想要写入的数据
end
if(rd==)
begin
state <= start2;
sda_buf <= ;//准备再次发启动信号
end
end
end
start2:
begin
if(cnt=='d124)
begin
link_sda <= ;
end
if((scl==)&&(cnt=='d61))
begin
state <= control_byte2;
sda_buf <= ;
data <= 'b10100001; //读控制字准备
end
end
control_byte2:
begin
if((cnt_num<'d8)&&(cnt==7'd124))
begin
cnt_num <= cnt_num + ;
sda_buf <= data[];
data <= {data[:],data[]};
end
else if((cnt_num=='d8)&&(cnt==7'd124))
begin
state <= ack4;
link_sda <= ;
cnt_num <= ;
end
end
ack4:
begin
// if((scl==1)&&(cnt==7'd61)&&(sda==1'b0))
// begin
// state <= transfer_data;
// data <= 8'b00000000; //低字节地址准备
// link_sda <= 0;
// end
if((scl==)&&(cnt=='d61))
begin
state <= transfer_data;
data <= 'b00000000; //低字节地址准备
// link_sda <= 0;
end
end
transfer_data:
begin
if(wr==)
begin
if(cnt=='d124)
begin
link_sda <= ;
end
if((cnt_num<'d8)&&(cnt==7'd124))
begin
cnt_num <= cnt_num + ;
sda_buf <= data[];
data <= {data[:],data[]};
end
else if((cnt_num=='d8)&&(cnt==7'd124))
begin
state <= ack5;
link_sda <= ;
cnt_num <= ;
wr <= ;
led <= ;
end
end
if(rd==)
begin
if(cnt=='d124)
begin
link_sda <= ;
end
if((cnt_num<'d8)&&(cnt==7'd124))
begin
cnt_num <= cnt_num + ;
result <= {result[:],sda};
end
else if((cnt_num=='d8)&&(cnt==7'd124))
begin
state <= no_ack;
link_sda <= ;
cnt_num <= ;
sda_buf <= ;
rd <= ;
end
end
end
ack5:
begin
// if((scl==1)&&(cnt==7'd61)&&(sda==1'b0))
// begin
// state <= stop;
// link_sda <= 1;
// end
if((scl==)&&(cnt=='d61))
begin
state <= stop;
// link_sda <= 1;
end
end
no_ack:
begin
if(cnt=='d124)
begin
state <= stop;
link_sda <= ;
sda_buf <= ;
end
end
stop:
begin
if(cnt=='d124)
begin
link_sda <= ;
end
if((scl==)&&(cnt=='d61))
begin
sda_buf <= ;
state <= IDLE;
end
end
default:state<=;
endcase endmodule

说明:由于仿真中没有嵌入EEPROM仿真模型,因此,无法给出ACK应答信号,没有应答信号,状态机就没办法继续向下跳转。所以为了完成仿真,就在代码中屏蔽了所有的ACK检测。在仿真中,只要看本该出现ACK信号的时候,sda信号是不是蓝色高组态。如果是高组态就表示仿真没有问题。在实际工程中,只需要把代码中屏蔽掉的if语句放开,把对应的if语句(仿真用的)屏蔽掉就可以直接用了

iic接口介绍的更多相关文章

  1. 八、IIC 接口

    8.1 IIC接口介绍 8.1.1 IIC 总线的概念 I2C总线是由Philips公司开发的一种简单.双向二线制同步串行总线.它只需要两根线即可在连接于总线上的器件之间传送信息. 主器件用于启动总线 ...

  2. IIC接口下的24C02 驱动分析

    本节来学习IIC接口下的24C02 驱动分析,本节学完后,再来学习Linux下如何使用IIC操作24C02 1.I2C通信介绍 它是由数据线SDA和时钟SCL构成的串行总线,可发送和接收数据,是一个多 ...

  3. Hive 接口介绍(Web UI/JDBC)

    Hive 接口介绍(Web UI/JDBC) 实验简介 本次实验学习 Hive 的两种接口:Web UI 以及 JDBC. 一.实验环境说明 1. 环境登录 无需密码自动登录,系统用户名shiyanl ...

  4. SSH动态查询封装接口介绍

    SSH动态查询封装接口介绍 1.查询记录总条数 public int count(Class c,Object[][] eq,Object[][] like,String[] group,String ...

  5. 【百度地图API】如何在地图上添加标注?——另有:坐标拾取工具+打车费用接口介绍

    原文:[百度地图API]如何在地图上添加标注?--另有:坐标拾取工具+打车费用接口介绍 摘要: 在这篇文章中,你将学会,如何利用百度地图API进行标注.如何使用API新增的打车费用接口. ------ ...

  6. 如何删除要素类 IFeatureWorkspace 接口介绍(1)

    如何删除要素类 要想删除一个要素类,那么必须先得到这个,在得到这个要素类的时候,我们要学习一个新的接口IFeatureWorkspace. IFeatureWorkspace  接口介绍 这个接口主要 ...

  7. Redis --> Redis的接口介绍及使用

    Redis的接口介绍及使用 Redis是一个远程内存数据库,它不仅性能强劲,而且还具有复制特性以及为解决问题而生的独一无二的数据模型.Redis提供了5种不同类型的数据结构,各式各样的问题都可以很自然 ...

  8. Spring之InstantiationAwareBeanPostProcessor接口介绍

      InstantiationAwareBeanPostProcessor接口是BeanPostProcessor的子接口,通过接口字面意思翻译该接口的作用是感知Bean实例话的处理器.实际上该接口的 ...

  9. I2S接口介绍

    I2S接口介绍一.I2S协议介绍 I2S协议作为音频数据传输协议,由Philips制定.该协议由三条数据线组成:1.SCLK:串行时钟,频率= 2 * 采样频率 * 采样位数.2.WS:字段(声道)选 ...

随机推荐

  1. PERL学习笔记---正则表达式

    要匹配某个模式(正则表达式)和$_的关系,可以将模式放在正斜线(//)之间,如下:$_ =“yabba dabba doo”;if(/abba/){print “It matched!\n”;} 表达 ...

  2. Linux+.Net Core+Nginx(在Linux上使用Nginx反向代理.Net Core 项目)

    Linux+.Net Core+Nginx 之前的文章中有提到关于使用Nginx在linux来实现反向代理,今天我们继续加点料.在Centos7中部署.NetCore,然后使用Nginx进行反向代理! ...

  3. JVM回收器与调优

    定义: 使用编程语言将GC算法实现出来,产生的程序就是垃圾搜集器了 JVM给了三种选择:串行收集器.并行收集器.并发收集器 串行搜集器(serial collector):它只有一条GC线程,且就像前 ...

  4. 浅谈Promise

    学习过JavaScript的人都知道,JavaScript是单线程作业,这样会有一个很大的缺陷,所有的Ajax,浏览器事件等,都是通过异步去完成.所谓的同步和异步最大的区别无非就是在于同步会阻塞后续代 ...

  5. 并发系列(3)之 CLH、MCS 队列锁简介

    这篇博客主要是作为 AbstractQueuedSynchronizer 的背景知识介绍:平时接触也非常的少,如果你不感兴趣可以跳过:但是了解一下能更加的清楚 AQS 的设计思路: 一.自旋锁简介 通 ...

  6. SLAM+语音机器人DIY系列:(三)感知与大脑——2.带自校准九轴数据融合IMU惯性传感器

    摘要 在我的想象中机器人首先应该能自由的走来走去,然后应该能流利的与主人对话.朝着这个理想,我准备设计一个能自由行走,并且可以与人语音对话的机器人.实现的关键是让机器人能通过传感器感知周围环境,并通过 ...

  7. Notepad++替换SQL Server Select窗口列名的中括号的小技巧

    条件:“查找模式”那选中“扩展” 一.简单替换 查找目标(包括空格,各个SSMS版本可能不同): ]\r\n ,[ 替换为:, 二.替换为@ 查找目标(包括空格,各个SSMS版本可能不同): ]\r\ ...

  8. C#知识拾遗

    参数验证方式 1.    一般方法 1.1 手动验证 最为普遍常见,略. 1.2 使用扩展方法验证 在C#3.0 中,引入了扩展方法,可以以一种更优雅的方式来进行参数验证,如: //参数辅助类 pub ...

  9. javaweb + websocket实现客户端

    最近需要完成一个javaweb项目,但是没有和数据库连接而是通过websocket通讯实现和服务器端数据交互.我搜了好多,网上大部分都是通过页面websocket连接本地服务器或连接异地服务器,但是这 ...

  10. RSA签名和验签Util

    目录 1.DigitalSign类 2.CryptException异常类 3.加签示例 1.DigitalSign类 import org.apache.commons.codec.binary.B ...