摘要:总线空闲和均为高电平协议起始位为高电平时,出现下降沿协议终止位为高电平时,出现上升沿。主设备产生所有时钟脉冲,包括确认位第九个时钟脉冲。当在第个时钟脉冲期间保持高时,这被定义为非应答信号。
I2C总线是由Philips公司开发的一种简单、双向二线制同步串行总线。它只需要两根线即可在连接于总线上的器件之间传送信息。
主器件用于启动总线传送数据,并产生时钟以开放传送的器件,此时任何被寻址的器件均被认为是从器件.在总线上主和从、发和收的关系不是恒定的,而取决于此时数据传送方向。如果主机要发送数据给从器件,则主机首先寻址从器件,然后主动发送数据至从器件,最后由主机终止数据传送;如果主机要接收从器件的数据,首先由主器件寻址从器件.然后主机接收从器件发送的数据,最后由主机终止接收过程。在这种情况下.主机负责产生定时时钟和终止数据传送。
I2C总线是公认的世界标准,由50多家公司生产超过1000个不同的地方实施的集成电路。此外,通用的i2c总线用于各种控制体系结构,如系统管理总线(SMBus),电源管理总线(PMBus),智能平台管理接口(IPMI),显示器数据通道(DDC)和高级电信计算架构(ATCA)。
I2C总线的原理和细节参考这篇文章,感谢作者的整理,受益匪浅。
串行数据线(SDA)和串行时钟线(SCL)
SDA和SCL都是双向的线路,通过电流源或上拉电阻连接到一个正的供应电压。连接到总线的设备的输出级必须有一个开路漏极或开路集电极来执行线和功能。I2C-bus上的数据可以在标准模式下以高达100kbit /s的速率传输,在快速模式下以高达400kbit /s的速率传输,在快速模式+下为1 Mbit/s,在高速模式下最高为3.4 Mbit/s。总线电容限制连接到总线的接口数量。
数据有效性
SDA线上的数据必须在SCL时钟线时钟高电平期间保持稳定,在时钟低电平期间改变(见下图)。因此我们在设计信号时,最佳情况就是在时钟线SCL为低电平中间时SDA数据线上的数据改变,在时钟高电平中间时获取SDA数据线上的数据。
起始和终止位
所有数据的传输都以START ( S )开始,以STOP ( P )结束(见下图)。
I2C总线空闲:SDA和SCL均为高电平;
I2C协议起始位:SCL为高电平时,SDA出现下降沿;
I2C协议终止位:SCL为高电平时,SDA出现上升沿。
启动和停止条件总是由主设备生成。在启动条件后,总线被认为是忙碌的。该总线在停止条件后的某一段时间内再次空闲。如果生成了重复启动(Sr)而不是停止条件,则总线将保持忙碌状态。在这方面,启动(S)和重复启动(Sr)条件在功能上是相同的。
传输1字节格式
在SDA数据线上传输的每个字节长度必须是8位。每次传输可以传输的字节数是不受限制的。每个字节后面必须跟着一个应答位(ACK)。数据从字节最高位(MSB)开始传输(见下图)。如果一个从设备无法接收或发送另一个完整的字节的数据,直到执行一些其他功能,例如服务内部中断,它可以维持时钟线scl为低强迫主设备进入等待状态。当从设备准备好另一个字节的数据后继续传输数据并释放时钟线SCL。
应答(ACK)与非应答(NACK)
应答发生在每个字节之后。应答位响应,表明该字节已成功接收,并且可以发送另一个字节。主设备产生所有时钟脉冲,包括确认位第九个时钟脉冲。应答信号定义如下:在响应时钟脉冲期间,发射机释放SDA线,接收机可以把SDA线拉低,,并且在时钟高电平期间稳定保持低电平。
当SDA在第9个时钟脉冲期间保持高时,这被定义为非应答信号。然后,主设备可以生成终止传输 的停止条件,或者生成启动新传输的重复启动条件。NACK的产生有五个条件:
- 总线上没有带有所传输地址的接收者,因此没有设备以应答。
- 接收器无法接收或发送,因为它正在执行一些实时功能,并没有准备好开始与主控通信。
- 在传输过程中,接收方获取它不理解的数据或命令。
- 在传输过程中,接收器无法接收到更多的数据字节。
- 主控接收机必须将传输结束的信号发送给从发射机。
从设备器件地址通常是由固定为和可变位组合而成的。所谓固定位就是器件本就确定无法更改的,认为不能让控制的的,而可变位通常是器件的硬件,可供用户进行硬件连接,按照用户的硬件连接确定,例如下图中7位器件地址中,前四位1010是出厂时就已经固定了的,而后三位是器件硬件引脚可供用户改变的。
对不同的器件,I2C传输格式略有不同,对于存储设备,还具有存储器的地址号,在主设备发送器件地址,从设备存储器响应后,主设备要再发8或16位存储器地址数据,来选择存储器的地址,等待从设备响应后,主设备在发送数据到存储器地址或从存储器地址读取数据。
I2C接口具有严格的读写时序,包括写数据时序、读数据时序、连续写数据以及连续读时序。本次实验仅实现写数据时序和读数据时序。I2C数据传输都是高位优先。
根据I2C的读写时序图,我们可以发现,读写时序传输顺序:
所以可以据此画出状态机图:
废话不多说,上代码:
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接口实现...
摘要:使用库读写环境光照度传感器本文将教大家如何快速使用库读取光照度数据。五实验样机测试展示通过之前配置好的面板,通过涂鸦智能进行配网实时采集光照度传感器的数据。 使用STM32 HAL库读写环境光照度传感器(BH1750) 本文将教大家如何快速使用STM32HAL库读取光照度数据。 实现功能:通...
摘要:总线挂载的外设有等。外设地址映射片上外设区分为三条总线,根据外设速度的不同,不同总线挂载着不同的外设,挂载低速外设,和挂载高速外设。 第二章 STM32资源介绍 2...
摘要:背景开发板具有接口,本文对该接口的使用方法做一介绍。同时,与设备要共地连接。例如向号总线上,设备地址,寄存器地址开始,读取长度为个字节的数据。 0 背景 Jetson 开发板具有 I2C 接口,本文对该接口的使用方法做一介绍。以 Jetson TX2 为例,其它设备的方法类似,主要是硬件接口...
摘要:因为操作系统一直被看做是计算机软件的基石。本系列是我学习操作系统的笔记,操作系统是以为例子。其他的操作系统也是差不多。将设备驱动一共分为个级别,每个级别的驱动初始化声明宏定义及其在系统启动过程中的启动顺序如下表所示。 老板说我技术需要有长进,不能只做一个crud boy。 于是我选来选去,...
阅读 1016·2023-04-25 22:27
阅读 871·2021-11-22 14:56
阅读 983·2021-11-11 16:54
阅读 1677·2019-08-30 15:54
阅读 3498·2019-08-30 13:20
阅读 1212·2019-08-30 10:55
阅读 2079·2019-08-26 13:34
阅读 3280·2019-08-26 11:53