项目之前研究了I2C通信协议的实现,完成FPGA对视频解码芯片SAA7111A的初始化配置,设计实现了I2C主机对从机(SAA7111A)32个寄存器的写操作,因此只简单实现了I2C的写时序。

这次重新梳理学习了I2C协议,借助黑金开发板设计I2C主机控制器完成对EEPROM(24LC02)的读写操作,设计单字节的写时序和随机读时序。通过按键将数据先入EEPROM,再通过按键选择将数据显示在数码管上进行验证。

1. 时序介绍

主要的时序如下所示:

数据线SDA在空闲状态时为高电平,在SCL高电平时拉低SDA表示开始,在SCL低电平时拉高SDA表示结束。数据在SCL低电平时变化,8位数据,高位在前,低位在后。一个数据字节后,接收器需要产生一个低电平,即拉低SDA,表示接收正确。

写时序:

读时序:

其中,应答位一般由接收器产生,在读时序时主机接收数据一般不产生应答位(NO ACK),除了在连续读模式下,一个数据读完需要拉低SDA产生应答位。

2. 串行时钟线(SCL)

首先要确定SCL时钟,根据系统时钟利用计数器完成SCL的100KHz的设置,这里SCL作为输出信号,因此为输出单向口。

         //分频部分
reg[:] cnt; // cnt=0:scl上升沿,cnt=1:scl高电平中间,cnt=2:scl下降沿,cnt=3:scl低电平中间
reg[:] cnt_delay; //500循环计数,产生iic所需要的时钟
reg scl_r; //时钟脉冲寄存器 always @ (posedge clk or negedge rst_n)
if(!rst_n) cnt_delay <= 'd0;
else if(cnt_delay == 'd499) cnt_delay <= 9'd0; //计数到10us为scl的周期,即100KHz
else cnt_delay <= cnt_delay+'b1; //时钟计数 always @ (posedge clk or negedge rst_n) begin
if(!rst_n) cnt <= 'd5;
else begin
case (cnt_delay)
'd124: cnt <= 3'd1; //cnt=1:scl高电平中间,用于数据采样
'd249: cnt <= 3'd2; //cnt=2:scl下降沿
'd374: cnt <= 3'd3; //cnt=3:scl低电平中间,用于数据变化
'd499: cnt <= 3'd0; //cnt=0:scl上升沿
default: cnt <= 'd5;
endcase
end
end `define SCL_POS (cnt=='d0) //cnt=0:scl上升沿
`define SCL_HIG (cnt=='d1) //cnt=1:scl高电平中间,用于数据采样
`define SCL_NEG (cnt=='d2) //cnt=2:scl下降沿
`define SCL_LOW (cnt=='d3) //cnt=3:scl低电平中间,用于数据变化 always @ (posedge clk or negedge rst_n)
if(!rst_n) scl_r <= 'b0;
else if(cnt=='d0) scl_r <= 1'b1; //scl信号上升沿
else if(cnt=='d2) scl_r <= 1'b0; //scl信号下降沿 assign scl = scl_r; //产生iic所需要的时钟

分频产生SCL

根据计数器的计数结果获得SCL的上升沿、高电平中间时刻、下降沿和低电平中间时刻。四个信号作为系统时钟的使能信号,保持信号的同步,完成发送和接收。

3. 串行数据线(SDA)

串行数据线是双向口,作为输出口时,完成开始信号、结束信号、从机地址、字节地址和写数据的输出;作为输入口时,完成从机应答位和读数据的输入。因此需要实现一个三态口控制:

assign sda = sda_link ? sda_r:1'bz;

本实验设计了一段式的状态机控制串行数据口的输入和输出,涉及单字节写时序和随机读时序。

由时序可知,前两次数据字节操作一样,可共享代码。在第3个数据字节处理时,写时序进行之前同样的操作即可,最后产生停止位;读时序时先发送从机地址读操作命令字节(最后一位为1),然后SDA口设置为输入口读取数据,最后FPGA无需产生应答位而产生停止位即可。返回IDLE状态前,产生清零标志以清零上次按键结果。

写一个字节:

             IDLE:    begin
sda_link <= 'b1; //数据线sda为output
sda_r <= 'b1;
if(!sw1_r || !sw2_r) begin //SW1,SW2键有一个被按下
db_r <= `DEVICE_WRITE; //送器件地址(写操作) //写读控制字节
cstate <= START1;
end
else cstate <= IDLE; //没有任何键被按下
end
START1:if(`SCL_HIG) begin //scl为高电平期间
sda_link <= 'b1; //数据线sda为output
sda_r <= 'b0; //拉低数据线sda,产生起始位信号
cstate <= ADD1;
num <= 'd0; //num计数清零
end
else cstate <= START1; //等待scl高电平中间位置到来 ADD1: if(`SCL_LOW) begin
if(num == 'd8) begin
num <= 'd0; //num计数清零
// sda_r <= 1'b1;
sda_link <= 'b0; //sda置为高阻态(input)
cstate <= ACK1;
end
else begin
cstate <= ADD1;
num <= num+'b1;
case (num)
'd0: sda_r <= db_r[7];
'd1: sda_r <= db_r[6];
'd2: sda_r <= db_r[5];
'd3: sda_r <= db_r[4];
'd4: sda_r <= db_r[3];
'd5: sda_r <= db_r[2];
'd6: sda_r <= db_r[1];
'd7: sda_r <= db_r[0];
default: ;
endcase
// sda_r <= db_r[4'd7-num]; //送器件地址,从高位开始
end
end
// else if(`SCL_POS) db_r <= {db_r[6:0],1'b0}; //器件地址左移1bit
else cstate <= ADD1; ACK1:
// if(/*!sda*/`SCL_NEG) begin //注:24C01/02/04/08/16器件可以不考虑应答位
if(`SCL_HIG && !sda) begin // SCL_HIG高电平时sda稳定,可以考虑`SCL_HIG && !sda和!sda效果一样
cstate <= ADD2; //从机响应信号
db_r <= `BYTE_ADDR; // 存储器读写地址
end
else cstate <= ACK1; //等待从机响应

读一个字节:

 //*********读操作起始位,先拉高SDA,再拉低SDA    ******************************//
START2:if(`SCL_LOW) begin //等待应答位高电平过去,检测一下个SCL的低电平!!!!
sda_link <= 'b1; //sda作为output
sda_r <= 'b1; //拉高数据线sda
cstate <= READ;
end
else cstate <= START2; READ: if(`SCL_HIG) begin //scl为高电平中间
sda_r <= 'b0; //拉低数据线sda,产生起始位信号
cstate <= ADD3;
end ADD3: //送读控制字节
if(`SCL_LOW) begin
if(num=='d8) begin
num <= 'd0; //num计数清零
// sda_r <= 1'b1;
sda_link <= 'b0; //sda置为高阻态(input)
cstate <= ACK3;
end
else begin
num <= num+'b1;
case (num)
'd0: sda_r <= db_r[7];
'd1: sda_r <= db_r[6];
'd2: sda_r <= db_r[5];
'd3: sda_r <= db_r[4];
'd4: sda_r <= db_r[3];
'd5: sda_r <= db_r[2];
'd6: sda_r <= db_r[1];
'd7: sda_r <= db_r[0];
default: ;
endcase
// sda_r <= db_r[4'd7-num]; //送EEPROM地址(高bit开始)
cstate <= ADD3;
end
end
// else if(`SCL_POS) db_r <= {db_r[6:0],1'b0}; //器件地址左移1bit
else cstate <= ADD3; ACK3: begin
// if(/*!sda*/`SCL_NEG) begin
if(`SCL_HIG && !sda) begin
cstate <= wait_L; //从机响应信号
// sda_link <= 1'b0;
end
else cstate <= ACK3; //等待从机响应
end wait_L: if(/*`SCL_NEG*/`SCL_LOW) cstate <= DATA; //等待应答位高电平过去,检测一下个SCL的低电平!!!! DATA: if(!sw2_r) begin //读操作
if((`SCL_LOW) && (num=='d8)) begin
num <= 'd0; //num计数清零
cstate <= NO_ACK;
end
else if(`SCL_HIG && (num<='d7) ) begin
num <= num+'b1;
case (num)
'd0: read_data[7] <= sda;
'd1: read_data[6] <= sda;
'd2: read_data[5] <= sda;
'd3: read_data[4] <= sda;
'd4: read_data[3] <= sda;
'd5: read_data[2] <= sda;
'd6: read_data[1] <= sda;
'd7: read_data[0] <= sda;
default: ;
endcase
// read_data[4'd7-num] <= sda; //读数据(高bit开始)
cstate <= DATA;
// else if(`SCL_NEG) read_data <= {read_data[6:0],read_data[7]}; //数据循环右移
end
else cstate <= DATA;
end

I2C通信的更多相关文章

  1. MCU开发之I2C通信

    程序状态字PSW是8位寄存器,用于存放程序运行的状态信息,PSW中各位状态通常是在指令执行的过程中自动形成的,但也可以由用户根据需要采用传送指令加以改变.各个标志位的意义如下: PSW.7(Cy):进 ...

  2. 【转载】GPIO模拟i2c通信

    I2C总线的通信过程(见图4-8)主要包含三个主要阶段:起始阶段.数据传输阶段和终止阶段. 1. 起始阶段 在I2C总线不工作的情况下,SDA(数据线)和SCL(时钟线)上的信号均为高电平.如果此时主 ...

  3. 由于用mpu6050模块,所以要用上i2c通信原理。

    i2c通信原理 i2c总线只有两根双向信号线,一根是数据线SDA,一根是时钟线SCL. 每个接到i2c总线上的器件都有唯一的地址,主机与其他器件之间的数据传送可以是由主机发送给其他器件.主机为发送器, ...

  4. I2C通信基本原理及其实现

    I2C是一种总线式结构,它只需要SCL时钟信号线与SDA数据线,两根线就能将连接与总线上的设备实现数据通信,由于它的简便的构造设计,于是成为一种较为常用的通信方式. 由于I2C采用的是主从式通信方式, ...

  5. AT24C0X I2C通信原理

    /********************************************************************** * AT24C0X I2C通信原理 * 说明: * 之前 ...

  6. 【转】三种方法让你在I2C通信中同时和多个从机通信

    ref:http://tieba.baidu.com/p/3769008030 对于不同地址的模块就不用多说了,直接分别对其地址进行通信即可.那么若拿到相同地址的模块,或者直接是相同的多个模块怎么办呢 ...

  7. ESP8266开发之旅 基础篇⑤ ESP8266 SPI通信和I2C通信

        设备与设备之间的通信往往都伴随着总线的使用,而用得比较多的就当属于SPI总线和I2C总线,而恰巧NodeMcu也支持这两种总线通信,所以本章的主要内容就是讲解ESP8266 SPI和I2C总线 ...

  8. STM32的I2C通信

    I2C总线是由NXP(原PHILIPS)公司设计,有十分简洁的物理层定义,其特性如下: 只要求两条总线线路:一条串行数据线SDA,一条串行时钟线SCL: 每个连接到总线的器件都可以通过唯一的地址和一直 ...

  9. i2c 通信

    时间长了记忆就会模糊, 保存下逻辑分析抓到的图像, 什么时候需要可以看一眼. 当clk处于高电平时, data线有下降,说明开始传输, 有上升说明结束传输. 发送地址无回应: 发送地址有回应 正常数据 ...

  10. ov5640 i2c通信异常问题

    1 异常场景如下描述 之前的测试场景: 将插在排针上的杜邦线向上拔一点,留出空间挂示波器的探针. 这种方式会导致i2c只发送一组8bit的数据,而另外两组没有发送成功.如上图所示. 因此,之前出现没有 ...

随机推荐

  1. Action方法调用

    一.Action访问路径 Action的访问路径是由struts.xml文件中配置的Action所在包的命名空间,Action的名字和常struts.action.extension共同决定的 例如: ...

  2. STL源代码剖析——STL算法stl_algo.h

    前言 在前面的博文中剖析了STL的数值算法.基本算法和set集合算法.本文剖析STL其它的算法,比如排序算法.合并算法.查找算法等等.在剖析的时候.会针对函数给出一些样例说明函数的使用.源代码出自SG ...

  3. Failed to resolve: com.android.support:appcompat-v7:26.0.0wenti

    在安装Android Studio 3.0的时候出现了这个问题.查阅了许多资料都没有找到原因.到最后才发现,Android Studio默认https是不走代理的,只要勾选上https的代理就顺利的安 ...

  4. 用docker搭建测试环境--docker的基本操作

    上一篇文章中最后执行了docker pull centos的指令,经过一段时间的等待,会从hub.docker.com上下载docker官方最新的centos的images,接下来熟悉一下docker ...

  5. shell 颜色

    PS1='\[\e[33;1m\][\u@\h \W]\\$ \[\e[m\]' echo -e "\033[30m 黑色字oldboy trainning \033[0m" ec ...

  6. Tomcat nginx log日志按天分割切割

    利用 Linux 自带的 logrotate 工具来实现按天切割日志.下方已 centos 7 系统为例来实践讲解. 原理 Logrotate是基于CRON来运行的,其脚本是/etc/cron.dai ...

  7. page coloring小结

    页着色是一种通过选择性物理页分配来实现把虚存映射到特定cache位置的软件方法. 最早引入页着色概念是在计算机体系结构,是为了解决地址别名问题引入的. 首先对cache是使用虚拟地址还是物理地址的问题 ...

  8. kafka 安装步骤

    kafka安装文档 1.解压缩(官网下载:http://kafka.apache.org/downloads.html) tar -xzf kafka_2.10-0.8.2.0.tgz cd kafk ...

  9. WPF数据验证(4)——响应与获取验证错误

    1780 前面的示例中,有关用户接受到错误的唯一指示是在违反规则的文本框周围的红色轮廓.为了提供更多信息,可以处理 Error 事件,但存储或清除错误时会引发该事件,但前提是必须确保已将 Bindin ...

  10. MathType与Origin是怎么兼容的

    MathType作为一款常用的公式编辑器,可以与很多的软件兼容使用.Origin虽然是一款专业绘图与数据分析软件,但是在使用过程中也是可以用到MathType.它可以帮助Origin给图表加上标签,或 ...