资讯专栏INFORMATION COLUMN

FPAG学习笔记——I2C接口实现

DevTalking / 940人阅读

摘要:总线空闲和均为高电平协议起始位为高电平时,出现下降沿协议终止位为高电平时,出现上升沿。主设备产生所有时钟脉冲,包括确认位第九个时钟脉冲。当在第个时钟脉冲期间保持高时,这被定义为非应答信号。

一、I2C总线介绍

  I2C总线是由Philips公司开发的一种简单、双向二线制同步串行总线。它只需要两根线即可在连接于总线上的器件之间传送信息。

  主器件用于启动总线传送数据,并产生时钟以开放传送的器件,此时任何被寻址的器件均被认为是从器件.在总线上主和从、发和收的关系不是恒定的,而取决于此时数据传送方向。如果主机要发送数据给从器件,则主机首先寻址从器件,然后主动发送数据至从器件,最后由主机终止数据传送;如果主机要接收从器件的数据,首先由主器件寻址从器件.然后主机接收从器件发送的数据,最后由主机终止接收过程。在这种情况下.主机负责产生定时时钟和终止数据传送。

  I2C总线是公认的世界标准,由50多家公司生产超过1000个不同的地方实施的集成电路。此外,通用的i2c总线用于各种控制体系结构,如系统管理总线(SMBus),电源管理总线(PMBus),智能平台管理接口(IPMI),显示器数据通道(DDC)和高级电信计算架构(ATCA)。

  I2C总线的原理和细节参考这篇文章,感谢作者的整理,受益匪浅。

二、I2C总线协议

  1. 串行数据线(SDA)和串行时钟线(SCL)
      SDA和SCL都是双向的线路,通过电流源或上拉电阻连接到一个正的供应电压。连接到总线的设备的输出级必须有一个开路漏极或开路集电极来执行线和功能。I2C-bus上的数据可以在标准模式下以高达100kbit /s的速率传输,在快速模式下以高达400kbit /s的速率传输,在快速模式+下为1 Mbit/s,在高速模式下最高为3.4 Mbit/s。总线电容限制连接到总线的接口数量。

  2. 数据有效性
      SDA线上的数据必须在SCL时钟线时钟高电平期间保持稳定,在时钟低电平期间改变(见下图)。因此我们在设计信号时,最佳情况就是在时钟线SCL为低电平中间时SDA数据线上的数据改变,在时钟高电平中间时获取SDA数据线上的数据。

  3. 起始和终止位
      所有数据的传输都以START ( S )开始,以STOP ( P )结束(见下图)。
      I2C总线空闲:SDA和SCL均为高电平;
      I2C协议起始位:SCL为高电平时,SDA出现下降沿;
      I2C协议终止位:SCL为高电平时,SDA出现上升沿。
      启动和停止条件总是由主设备生成。在启动条件后,总线被认为是忙碌的。该总线在停止条件后的某一段时间内再次空闲。如果生成了重复启动(Sr)而不是停止条件,则总线将保持忙碌状态。在这方面,启动(S)和重复启动(Sr)条件在功能上是相同的。

  4. 传输1字节格式
      在SDA数据线上传输的每个字节长度必须是8位。每次传输可以传输的字节数是不受限制的。每个字节后面必须跟着一个应答位(ACK)。数据从字节最高位(MSB)开始传输(见下图)。如果一个从设备无法接收或发送另一个完整的字节的数据,直到执行一些其他功能,例如服务内部中断,它可以维持时钟线scl为低强迫主设备进入等待状态。当从设备准备好另一个字节的数据后继续传输数据并释放时钟线SCL。

  5. 应答(ACK)与非应答(NACK)
      应答发生在每个字节之后。应答位响应,表明该字节已成功接收,并且可以发送另一个字节。主设备产生所有时钟脉冲,包括确认位第九个时钟脉冲。应答信号定义如下:在响应时钟脉冲期间,发射机释放SDA线,接收机可以把SDA线拉低,,并且在时钟高电平期间稳定保持低电平。
      当SDA在第9个时钟脉冲期间保持高时,这被定义为非应答信号。然后,主设备可以生成终止传输 的停止条件,或者生成启动新传输的重复启动条件。NACK的产生有五个条件:

  - 总线上没有带有所传输地址的接收者,因此没有设备以应答。
  - 接收器无法接收或发送,因为它正在执行一些实时功能,并没有准备好开始与主控通信。
  - 在传输过程中,接收方获取它不理解的数据或命令。
  - 在传输过程中,接收器无法接收到更多的数据字节。
  - 主控接收机必须将传输结束的信号发送给从发射机。

  1. 从设备器件地址和读写位
      从设备地址是I2C协议在传输数据时对总线上设备寻址的依据。数据传输遵循下图所示的格式。在启动条件之后,发送一个从设备器件地址。这个地址有7位长,后面跟着一个数据方向位(R/W)——“0”表示传输(写),“1”表示对数据的请求(读)(参见下2图)。数据传输总是以主设备产生的停止条件§结束。然而,如果一个主人仍然希望在总线上通信,它可以产生一个重复的开始条件(Sr)和地址而不用再产生一个停止条件。

  从设备器件地址通常是由固定为和可变位组合而成的。所谓固定位就是器件本就确定无法更改的,认为不能让控制的的,而可变位通常是器件的硬件,可供用户进行硬件连接,按照用户的硬件连接确定,例如下图中7位器件地址中,前四位1010是出厂时就已经固定了的,而后三位是器件硬件引脚可供用户改变的。

  对不同的器件,I2C传输格式略有不同,对于存储设备,还具有存储器的地址号,在主设备发送器件地址,从设备存储器响应后,主设备要再发8或16位存储器地址数据,来选择存储器的地址,等待从设备响应后,主设备在发送数据到存储器地址或从存储器地址读取数据。

三、代码实现

  I2C接口具有严格的读写时序,包括写数据时序读数据时序连续写数据以及连续读时序。本次实验仅实现写数据时序读数据时序。I2C数据传输都是高位优先

图1.写数据时序

图2.读数据时序

  根据I2C的读写时序图,我们可以发现,读写时序传输顺序:

  1. 首先,主设备在SCL为高电平时拉低SDA,产生一个起始信号
  2. 主设备发送从设备地址位(R/W位为0,写入),在之后的一个时钟周期,主设备释放SDA总线的控制,从设备将SDA拉低,产生一个应答位(ACK)
  3. 主设备发送要写入或读取的寄存器地址位,在之后的一个时钟周期,主设备释放SDA总线的控制,从设备将SDA拉低,产生一个应答位(ACK)
  4. (写入数据时) 主设备发送要写入的数据,在之后的一个时钟周期,主设备释放SDA总线的控制,从设备将SDA拉低,产生一个应答位(ACK)
  5. (读取数据时) 主设备再次产生一个起始信号(这里非常重要!),然后再次发送从设备地址位(R/W位为1,读取),主设备释放SDA总线控制,从设备产生应答位,然后从设备发送数据,主设备不产生应答位
  6. 最后,主设备在SCL高电平期间,拉高SDA,产生一个结束信号

  所以可以据此画出状态机图

图3.状态机

废话不多说,上代码:

module IICModule(    input  CLK,         // 50MHz时钟频率    input reset,    input start,        // 启动信号,注意!!!启动信号高电平持续时间最好在一个scl高/低周期左右,不可以很长	input WR_OR_RE,		// 读/写控制,0=写,1=读    input [6:0]DeviecAddr,   // 从设备器件地址,7bit	input [7:0] RegisterAddr,  	// 寄存器地址    input [7:0] WriteData,     // 要写入的数据    	output reg scl,    inout  sda,    output reg done,	output reg [7:0] readData	// 从从设备读取的8bit数据);parameter CLK_FREQ = 50_000_000 , SCL_FREQ = 100_000;   // SCL的频率设置为100kHzlocalparam SCL_CNT = CLK_FREQ/(2*SCL_FREQ);     // 为使signalTap能够观察足够长的窗口时间,我提高了频率,实际不需要除2// 状态机的状态标识parameter IDLE =4"d0 ,START =4"d1 ,WR_ADDR =4"d2 ,WR_REGISTER =4"d3 , WR_DATA =4"d4 ,RE_START =4"d5 ,RE_ADDR =4"d6 ,RE_DATA =4"d7 ,STOP =4"d8 ;reg [7:0] state = IDLE , next_state = IDLE;// 设备地址// parameter DeviecAddr = 7"b0011101;reg [7:0] DeviecAddr_wr;reg [7:0] DeviecAddr_re;always@(*)begin	DeviecAddr_wr = {DeviecAddr , 1"b0};	DeviecAddr_re = {DeviecAddr , 1"b1};end// scl & scl_cnt ---- 50MHz时钟生成100kHzscl时钟reg [7:0] scl_cnt;always@(posedge CLK)begin    if(reset)begin        scl <= 1"b1;        scl_cnt <= 8"d0;    end    else begin        if(scl_cnt == (SCL_CNT/2-1))begin            scl <= ~scl;            scl_cnt <= 8"d0;        end        else begin            scl_cnt <= scl_cnt + 1"b1;        end    endend// scl_midHigh ---- 当处于scl高电平段的中间时,置1reg scl_midHigh;always@(posedge CLK)begin    if(reset)begin        scl_midHigh <= 1"b0;    end    else begin        if((scl_cnt == (SCL_CNT/4-1)) & (scl == 1"b1))begin            scl_midHigh <= 1"b1;        end        else begin            scl_midHigh <= 1"b0;        end    endend// scl_midLow ---- 当处于scl低电平段的中间时,置1reg scl_midLow;always@(posedge CLK)begin    if(reset)begin        scl_midLow <= 1"b0;    end    else begin        if((scl_cnt == (SCL_CNT/4-1)) & (scl == 1"b0))begin            scl_midLow <= 1"b1;        end        else begin            scl_midLow <= 1"b0;        end    endend// bit_cnt ---- 写器件地址、寄存器、数据等都是一字节(8bit)// 在scl_midLow和scl_midHigh时均计数(8bit数据位 + 1bit应答位,所以计数范围为0~17)reg [4:0] bit_cnt;always@(posedge CLK)begin    if(reset)begin        bit_cnt <= 5"d0;    end    else begin        case(state)        IDLE:               bit_cnt <= 5"d0;        START:              bit_cnt <= 5"d0;        WR_ADDR:            begin                                if(scl_midHigh | scl_midLow)begin                                    bit_cnt <= (bit_cnt == 5"d17 & scl_midLow)?5"d0:(bit_cnt + 1"b1);                                end                                else begin                                    bit_cnt <= bit_cnt + 1"b0;                                end                            end        WR_REGISTER:        begin                                if(scl_midHigh | scl_midLow)begin                                    bit_cnt <= (bit_cnt == 5"d17 & scl_midLow)?5"d0:(bit_cnt + 1"b1);                                end                                else begin                                    bit_cnt <= bit_cnt + 1"b0;                                end                            end        WR_DATA:            begin                                if(scl_midHigh | scl_midLow)begin                                    bit_cnt <= (bit_cnt == 5"d17 & scl_midLow)?5"d0:(bit_cnt + 1"b1);                                end                                else begin                                    bit_cnt <= bit_cnt + 1"b0;                                end                            end        RE_START:           bit_cnt <= 5"d0;        RE_ADDR:            begin                                if(scl_midHigh | scl_midLow)begin                                    bit_cnt <= (bit_cnt == 5"d17 & scl_midLow)?5"d0:(bit_cnt + 1"b1);                                end                                else begin                                    bit_cnt <= bit_cnt + 1"b0;                                end                            end            RE_DATA:            begin                                if(scl_midHigh | scl_midLow)begin                                    bit_cnt <= (bit_cnt == 5"d17 & scl_midLow)?5"d0:(bit_cnt + 1"b1);                                end                                else begin                                    bit_cnt <= bit_cnt + 1"b0;                                end                            end        STOP:               bit_cnt <= 5"d0;          default:            bit_cnt <= 5"d0;        endcase    endend// link ---- 控制sda(inout信号)的控制权,link == 0 时从设备控制, link == 1 时主设备控制reg link;assign sda = link?sda_reg:1"bz;         //  1"bz表示由从设备控制数据总线always@(posedge CLK)begin    if(reset)begin        link <= 1"b1;    end    else begin        case(state)        IDLE:               link <= 1"b1;                  START:              link <= 1"b1;        WR_ADDR:            begin                                if(bit_cnt < 5"d16)begin                                    link <= 1"b1;                                end                                else begin                                    link <= 1"b0;                                end                            end        WR_REGISTER:        begin                                if(bit_cnt < 5"d16)begin                                    link <= 1"b1;                                end                                else begin                                    link <= 1"b0;                                end                            end        WR_DATA:            begin                                if(bit_cnt < 5"d16)begin                                    link <= 1"b1;                                end                                else begin                                    link <= 1"b0;                                end                            end        RE_START:           link <= 1"b1;        RE_ADDR:            begin                                if(bit_cnt < 5"d16)begin                                    link <= 1"b1;                                end                                else begin                                    link <= 1"b0;                                end                            end        RE_DATA:            begin                                if(bit_cnt < 5"d16)begin                                    link <= 1"b0;                                end                                else begin                                    link <= 1"b1;       // 此时主设备不会产生应答信号,但是也要将控制权交给主设备                                end                            end        STOP:               link <= 1"b1;         default:            link <= 1"b1;         endcase    endend// ack ---- 当主设备发送8bit数据后,将sda控制权释放,从设备将sda拉低产生应答位reg ack;always@(posedge CLK)begin    if(reset)begin        ack <= 1"d0;    end    else begin        case(state)        IDLE:               ack <= 1"d0;                  START:              ack <= 1"d0;        WR_ADDR:            begin                                if(bit_cnt < 5"d16)begin                                    ack <= 1"b0;                                end                                else if(bit_cnt == 5"d16 & scl_midHigh & ~sda)begin                                    ack <= 1"b1;                                end										  else 												ack <= ack + 1"b0;                            end        WR_REGISTER:        begin                                if(bit_cnt < 5"d16)begin                                    ack <= 1"b0;                                end                                else if(bit_cnt == 5"d16 & scl_midHigh & ~sda)begin                                    ack <= 1"b1;                                end										  else 												ack <= ack + 1"b0;                            end        WR_DATA:            begin                                if(bit_cnt < 5"d16)begin                                    ack <= 1"b0;                                end                                else if(bit_cnt == 5"d16 & scl_midHigh & ~sda)begin                                    ack <= 1"b1;                                end										  else 												ack <= ack + 1"b0;                            end        RE_START:           ack <= 1"b0;        RE_ADDR:            begin                                if(bit_cnt < 5"d16)begin                                    ack <= 1"b0;                                end                                else if(bit_cnt == 5"d16 & scl_midHigh & ~sda)begin                                    ack <= 1"b1;                                end										  else 												ack <= ack + 1"b0;                            end        RE_DATA:            begin                                if(bit_cnt < 5"d16)begin                                    ack <= 1"b0;                                end                                else if(bit_cnt == 5"d16 & scl_midHigh & ~sda)begin                                    ack <= 1"b1;                                end										  else 												ack <= ack + 1"b0;                            end        STOP:               ack <= 1"b0;        default:            ack <= 1"b0;        endcase    endend// sda_reg_cnt ----  8bit数据计数,用作数据数组索引reg [3:0] sda_reg_cnt;always@(posedge CLK)begin    if(reset)begin        sda_reg_cnt <= 4"d7;    end    else begin        case(state)        IDLE:               sda_reg_cnt <= 4"d7;        START:              sda_reg_cnt <= 4"d7;        WR_ADDR:            begin                                if(bit_cnt < 5"d16 & scl_midLow )begin                                    sda_reg_cnt <= (sda_reg_cnt == 4"d0)?4"d7:(sda_reg_cnt - 1"b1);                                end                                else begin                                    sda_reg_cnt <= sda_reg_cnt + 1"b0;                                end                            end        WR_REGISTER:        begin                                if(bit_cnt < 5"d16 & scl_midLow )begin                                    sda_reg_cnt <= (sda_reg_cnt == 4"d0)?4"d7:(sda_reg_cnt - 1"b1);                                end                                else begin                                    sda_reg_cnt <= sda_reg_cnt + 1"b0;                                end                            end        WR_DATA:            begin                                if(bit_cnt < 5"d16 & scl_midLow )begin                                    sda_reg_cnt <= (sda_reg_cnt == 4"d0)?4"d7:(sda_reg_cnt - 1"b1);                                end                                else begin                                    sda_reg_cnt <= sda_reg_cnt + 1"b0;                                end                            end        RE_START:           sda_reg_cnt <= 4"d7;        RE_ADDR:            begin                                if(bit_cnt < 5"d16 & scl_midLow )begin                                    sda_reg_cnt <= (sda_reg_cnt == 4"d0)?4"d7:(sda_reg_cnt - 1"b1);                                end                                else begin                                    sda_reg_cnt <= sda_reg_cnt + 1"b0;                                end                            end        RE_DATA:            sda_reg_cnt <= 4"d7;        STOP:               sda_reg_cnt <= 4"d7;        default:            sda_reg_cnt <= 4"d7;        endcase    endend// sda_reg ---- 在不同state时sda输出不同的值,使用sda_reg来控制reg sda_reg;always@(posedge CLK)begin    if(reset)begin        sda_reg <= 1"b1;    end    else begin        case(state)        IDLE:               sda_reg <= 1"b1;        START:              sda_reg <= 1"b0;        WR_ADDR:            begin                                if(bit_cnt < 5"d16)begin                                    sda_reg <= DeviecAddr_wr[sda_reg_cnt];                                end                                else begin                                    sda_reg <= 1"b1;                                        end                            end        WR_REGISTER:        begin                                if(bit_cnt < 5"d16)begin                                    sda_reg <= RegisterAddr[sda_reg_cnt];                                end                                else begin                                    sda_reg <= 1"b1;        //  这里sda_reg <= 1"b1是为了state==RE_START时方便产生start信号                                end                            end									         WR_DATA:            begin                                if(bit_cnt < 5"d16)begin                                    sda_reg <= WriteData[sda_reg_cnt];                                end                                else begin                                    sda_reg <= 1"b0;        //  这里sda_reg <= 1"b0是为了state==STOP时方便产生stop信号                                end                            end        RE_START:           begin                                if(scl_midHigh & scl)begin                                    sda_reg <= 1"b0;                                end                                else begin                                    sda_reg <= sda_reg;                                end                            end        RE_ADDR:            sda_reg <= DeviecAddr_re[sda_reg_cnt];        RE_DATA:            sda_reg <= 1"b0;        //  这里sda_reg <= 1"b0是为了state==STOP时方便产生stop信号        STOP:               begin                   //  产生stop信号                                if(scl_midHigh & scl)                                    sda_reg <= 1"b1;                                else                                    sda_reg <= sda_reg + 1"b0;                            end        default:            sda_reg <= 1"b1;        endcase    endend// readData ---- 存储从设备发回的数据always@(posedge CLK)begin    if(reset)begin        readData <= 8"d0;    end    else begin        case(state)            RE_DATA:            begin                                    if(bit_cnt < 5"d16 & scl_midHigh)begin                                        readData <= {readData[6:0],sda}; 													 //readData[sda_reg_cnt] <= sda;                                    end                                    else begin                                        readData <= readData + 1"b0;                                    end                                end            default:            readData <= readData + 1"b0;        endcase    endend// done ---- 一次读/写数据完成后置1always@(posedge CLK)begin    if(reset)begin        done <= 1"b0;    end    else begin        if(state == STOP)            done <= 1"b1;        else            done <= 1"b0;    endend// 状态机第一段always@(posedge CLK)begin    if(reset)begin        state <= IDLE;    end    else begin        state <= next_state;    endend// 状态机第二段always@(*)begin    case(state)        IDLE:               next_state = (start&scl)?START:IDLE;        START:              next_state = (scl_midLow&~scl)?WR_ADDR:START;        WR_ADDR:            begin                                if(bit_cnt == 5"d17 && scl_midLow && ack)                                    next_state = WR_REGISTER
            
                     
             
               

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/123012.html

相关文章

  • stm32通过I2C接口实现温湿度(AHT20)的采集

    摘要:如图所示在了解起始条件和停止条件后,我们再来看看在这个过程中数据的传输是如何进行的。四参考资料通过接口实现温湿度的采集硬件和软件区别 stm32通过I2C接口实现...

    chengtao1633 评论0 收藏0
  • STM32CubeMX学习教程之硬件I2C读取光照度

    摘要:使用库读写环境光照度传感器本文将教大家如何快速使用库读取光照度数据。五实验样机测试展示通过之前配置好的面板,通过涂鸦智能进行配网实时采集光照度传感器的数据。 使用STM32 HAL库读写环境光照度传感器(BH1750) 本文将教大家如何快速使用STM32HAL库读取光照度数据。 实现功能:通...

    tinylcy 评论0 收藏0
  • STM32学习笔记 第二章 STM32资源介绍

    摘要:总线挂载的外设有等。外设地址映射片上外设区分为三条总线,根据外设速度的不同,不同总线挂载着不同的外设,挂载低速外设,和挂载高速外设。 第二章 STM32资源介绍 2...

    G9YH 评论0 收藏0
  • NVIDIA Jetson之I2C测试

    摘要:背景开发板具有接口,本文对该接口的使用方法做一介绍。同时,与设备要共地连接。例如向号总线上,设备地址,寄存器地址开始,读取长度为个字节的数据。 0 背景 Jetson 开发板具有 I2C 接口,本文对该接口的使用方法做一介绍。以 Jetson TX2 为例,其它设备的方法类似,主要是硬件接口...

    OpenDigg 评论0 收藏0
  • 国庆假期学门新技术,拒绝只做crud boy, 就从操作系统开始(三)

    摘要:因为操作系统一直被看做是计算机软件的基石。本系列是我学习操作系统的笔记,操作系统是以为例子。其他的操作系统也是差不多。将设备驱动一共分为个级别,每个级别的驱动初始化声明宏定义及其在系统启动过程中的启动顺序如下表所示。 老板说我技术需要有长进,不能只做一个crud boy。 于是我选来选去,...

    Hegel_Gu 评论0 收藏0

发表评论

0条评论

DevTalking

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<