草庐IT

IIC通信过程详解以及底层代码实现

鸵鸟小姐啊 2023-05-25 原文

1、概述

I2C总线是PHLIPS公司推出的一种串行总线,是具备多主机系统所需的包括总线裁决和高低速器件同步功能的高性能串行总线。此通信方式为半双工。
I2C总线只有两根双向信号线。一根是数据线SDA,另一根是时钟线SCL。
I2C总线通过上拉电阻接正电源。当总线空闲时,两根线均为高电平。连到总线上的任一器件输出的低电平,都将使总线的信号变低,即各器件的SDA及SCL都是线“与”关系。

2、IIC总线寻址方式

主机在发送起始信号后必须发送一个字节的数据,该数据的高7位为从机地址,最低位表示后续字节的传送方向,‘0’表示主机发送数据,‘1’表示主机接收数据;总线上所有的从机接收到该字节数据后都将这7位地址与自己的地址进行比较,如果相同,则认为自己被主机寻址,然后再根据第8位将自己定位发送器或接收器。

3、起始信号

当SCL为高期间,SDA由高到低的跳变;启动信号是一种电平跳变时序信号,而不是一个电平信号。
起始信号由主机发起,当主机发起起始信号后,总线就处于占领状态;当主机发出停止信号时,总线恢复空闲状态。

以下为起始信号的代码段:

void IIC_Start( void)
{
	IIC_SCL_H();         //拉高SCL
	BSP_IIC_Delay( );
	
	IIC_SDA_H( );    //SDA拉高
	BSP_IIC_Delay( );
	BSP_IIC_Delay( );
	
	IIC_SDA_L( );  //SDA拉低
	BSP_IIC_Delay( );	
	IIC_SCL_L( );    //SCL复位
}

4、停止信号

当SCL为高期间,SDA由低到高的跳变;停止信号也是一种电平跳变时序信号,而不是一个电平信号。

以下为停止信号的代码段:

void IIC_Stop(void)
{
	IIC_SCL_H( );	
	BSP_IIC_Delay( );
	IIC_SDA_L( );
	
	BSP_IIC_Delay( );
	BSP_IIC_Delay( );
	IIC_SDA_H();
	
	BSP_IIC_Delay( );	
	IIC_SCL_L( );    //SCL复位
}

5、有效数据

当主机发起起始信号后,需要向从机进行寻址,发送相应的从机地址给从机进行响应。
SCL为时钟线,SDA为数据线。IIC总线进行数据传送时,SCL信号为高电平时,SDA电平必须保持稳定,即此时作为接收器接收数据。当SCL为低电平时,SDA的高低电平状态才允许变化,此时即为发送器发送数据。
SCL作为连接在收发双方上时钟线就解决了不同步的问题。

IIC总线通信时每个字节为8位长度,数据传送时,先传送最高位,后传送低位,当8位发送完后,从机将SDA拉低,表示应答。如还是高电平,表示非应答,即可能没有接收到。所以发送一个完整的字节,SCL会产生9个时钟信号。
下面为发送0xA0的时序图:

以下为发送字节的代码段:

uint8_t IIC_Send(  uint8_t uSendByte ) 
{ 
	uint8_t uIndex = 0;
	uint8_t uAskState = BSP_OK;
	uint16_t count = 0;

	for( uIndex = 0; uIndex < 8; uIndex ++ )
	{
		if( uSendByte & 0x80 )   
        {
            IIC_SDA_H();     
        }
        else
        {
            IIC_SDA_L();
        }
		BSP_IIC_Delay( );	//延时
		IIC_SCL_H( );	    //  SCL高电平 SDA要求稳定 接收器接收数据	  先将SDA的电平拉高,再拉高SCL的                         
		
		BSP_IIC_Delay(  );	//延时
		BSP_IIC_Delay( );	//延时
		IIC_SCL_L( );	    //  SCL低电平 SDA允许变化 发送器发送数据
		uSendByte <<= 1;
	}
	IIC_SDA_H( );					//拉高SDA和SCL 释放总线 等待从机将SDA拉低进行应答
    IIC_SCL_H(  );					
	BSP_IIC_Delay(  );
    if( 0 == IIC_SDA_READ( ) )                                                   
    { 
        uAskState = BSP_OK; 
    }
    else
    {
        uAskState = BSP_ERR;
    }
	BSP_IIC_Delay( );
	IIC_SCL_L();
	BSP_IIC_Delay( );
    return uAskState;
} 

6、应答信号和非应答信号

当主机作为接收器接收从机返回的数据时,接收完一个字节主机需要发起一个应答信号告诉从机,该字节接收完成。当主机接收完所有的数据后需要发起一个非应答信号给从机,方便从机准备接收停止信号,停止此次的通信。
以下为应答信号和非应答信号的时序图,SCL信号一致,都是维持在高电平,应答信号SDA为低电平,非应答信号SDA为高电平。

以下为相关的代码段

E_Bool IIC_AskOrNo( uint8_t ask_or_no )
{
	/* 每发送完一个数据字节后,发送器释放SDA,接收器拉高SDA(接收时BMS为接收器),
	在SCL处于高电平期间保持稳定的高电平接收器产生一个有效的否应答信号 */ 	
	//应答和非应答的时候SCL时序一样,只是SDA维持的电平不一样,低电平是应答信号,高电平是非应答信号
	
	IIC_SCL_L( );
	BSP_IIC_Delay( );
	//if( ask_or_no)   //应答
	( ask_or_no==1) ?(IIC_SDA_L( )):(IIC_SDA_H( ));
	BSP_IIC_Delay();
	
	IIC_SCL_H( );
	BSP_IIC_Delay( );
	
	IIC_SCL_L();
	BSP_IIC_Delay();
	return True; 
}

7、接收数据

当主机向从机发送读地址后,从机开始向主机回复相关数据 。相关代码段如下:

uint8_t IIC_Recv_Byte( )  
{ 
    /* 接收数据,高位在前 */
	uint16_t count = 0;
    uint8_t uBitIndex    = 0; 
    uint8_t uReceiveByte = 0; 
	
	IIC_SDA_H(  );
	IIC_SCL_L();
	BSP_IIC_Delay();
	for( uBitIndex = 0; uBitIndex < 8; uBitIndex ++)
	{ 
		uReceiveByte <<= 1; 
		IIC_SCL_H();
		/*count = 0;    等待SCL电平变化  可以屏蔽
		do{
			__nop();
			if( count ++ > WAIT_CLK_TIMES )
			{
				IIC_Stop( uIIC_ID );
				break;
			}
		}while( 0 == IIC_SCL_READ( uIIC_ID ) );*/
		BSP_IIC_Delay();
		if( IIC_SDA_READ() ) 
		{ 
			uReceiveByte |= 0X01; 
		} 
		BSP_IIC_Delay( );
		IIC_SCL_L();
		BSP_IIC_Delay();				
	}
    return uReceiveByte;		
}

8、IIC通信流程

8.1读数据

1、主机发送一个起始信号
2、主机发送从机写地址
3、主机发送要写寄存器的地址
4、主机发送从机读地址
5、主机开始收取数据,每收取完一个字节,主机发送一个应答信号给从机。当全部的字节都收取完了以后,主机发送一个非应答信号给从机。
6、主机发送一个停止信号给从机
相关代码段如下:
从机写地址为:0xA0 读地址为:0xA1

uint8_t ReadReg( uint8_t uRegAdr, uint8_t uRegNum, uint8_t *pData )
{
	uint8_t  uIndex  = 0;
	uint8_t uState  = BSP_ERR;
	E_Bool eAsk = False;
	IIC_Start();
	uState = IIC_Send( 0xA0 );
	if(BSP_OK==uState)
	{
	  ///写寄存器地址
		uState = IIC_Send( uRegAdr );
	    if(BSP_OK==uState)
		{
			uState = IIC_Send( 0xA1 );
			if(BSP_OK==uState)
			{
				for( uIndex = 0 ; uIndex < uRegNum; uIndex ++ )
				{
					pData[uIndex] = Recv_Byte(); //读寄存器数据
					if(uIndex < (uRegNum - 1) )
					{
						eAsk =IIC_AskOrNo(1); 
						if(False==eAsk)
						{
							uState = BSP_ERR;
							break;
						}
					}
				}
				if(uIndex==uRegNum)
				{
					IIC_AskOrNo(0);    //收完最后一个字节发送一个非应答位
				}
			}
		}
	}	
	IIC_Stop();
	return uState;	 
}

8.2写数据

写数据相对于读数据来说较为简单。前三个步骤和读数据一样。
1、主机发送一个起始信号
2、主机发送从机写地址
3、主机发送要写寄存器的地址
4、主机发送需要写的数据,发送完毕后主机发送一个停止信号给从机。
相关代码段如下:

uint8_t WriteReg( uint8_t uRegAdr, uint8_t uRegNum, uint8_t* pData )
{
	uint8_t uIndex  = 0;
	uint8_t uState  = BSP_ERR;

	IIC_Start();
	uState = IIC_Send( 0xA0  );
	if(BSP_OK==uState)
	{
		uState = IIC_Send( uRegAdr );
		if(BSP_OK==uState)
		{
			for(uIndex=0; uIndex<uRegNum; uIndex++)
			{
				uState = IIC_Send( pData[uIndex] );
				if(uState != BSP_OK)
				{
					break;
				}
			}
		}
	}
	IIC_Stop();
	return uState;	  
}

有关IIC通信过程详解以及底层代码实现的更多相关文章

  1. ruby - 如何在 buildr 项目中使用 Ruby 代码? - 2

    如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby​​

  2. ruby - 什么是填充的 Base64 编码字符串以及如何在 ruby​​ 中生成它们? - 2

    我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%

  3. ruby-on-rails - Rails 源代码 : initialize hash in a weird way? - 2

    在rails源中:https://github.com/rails/rails/blob/master/activesupport/lib/active_support/lazy_load_hooks.rb可以看到以下内容@load_hooks=Hash.new{|h,k|h[k]=[]}在IRB中,它只是初始化一个空哈希。和做有什么区别@load_hooks=Hash.new 最佳答案 查看rubydocumentationforHashnew→new_hashclicktotogglesourcenew(obj)→new_has

  4. ruby-on-rails - Rails 应用程序之间的通信 - 2

    我构建了两个需要相互通信和发送文件的Rails应用程序。例如,一个Rails应用程序会发送请求以查看其他应用程序数据库中的表。然后另一个应用程序将呈现该表的json并将其发回。我还希望一个应用程序将存储在其公共(public)目录中的文本文件发送到另一个应用程序的公共(public)目录。我从来没有做过这样的事情,所以我什至不知道从哪里开始。任何帮助,将不胜感激。谢谢! 最佳答案 无论Rails是什么,几乎所有Web应用程序都有您的要求,大多数现代Web应用程序都需要相互通信。但是有一个小小的理解需要你坚持下去,网站不应直接访问彼此

  5. ruby - 如何根据特征实现 FactoryGirl 的条件行为 - 2

    我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden

  6. ruby-on-rails - 浏览 Ruby 源代码 - 2

    我的主要目标是能够完全理解我正在使用的库/gem。我尝试在Github上从头到尾阅读源代码,但这真的很难。我认为更有趣、更温和的踏脚石就是在使用时阅读每个库/gem方法的源代码。例如,我想知道RubyonRails中的redirect_to方法是如何工作的:如何查找redirect_to方法的源代码?我知道在pry中我可以执行类似show-methodmethod的操作,但我如何才能对Rails框架中的方法执行此操作?您对我如何更好地理解Gem及其API有什么建议吗?仅仅阅读源代码似乎真的很难,尤其是对于框架。谢谢! 最佳答案 Ru

  7. ruby - 模块嵌套代码风格偏好 - 2

    我的假设是moduleAmoduleBendend和moduleA::Bend是一样的。我能够从thisblog找到解决方案,thisSOthread和andthisSOthread.为什么以及什么时候应该更喜欢紧凑语法A::B而不是另一个,因为它显然有一个缺点?我有一种直觉,它可能与性能有关,因为在更多命名空间中查找常量需要更多计算。但是我无法通过对普通类进行基准测试来验证这一点。 最佳答案 这两种写作方法经常被混淆。首先要说的是,据我所知,没有可衡量的性能差异。(在下面的书面示例中不断查找)最明显的区别,可能也是最著名的,是你的

  8. ruby - 寻找通过阅读代码确定编程语言的ruby gem? - 2

    几个月前,我读了一篇关于ruby​​gem的博客文章,它可以通过阅读代码本身来确定编程语言。对于我的生活,我不记得博客或gem的名称。谷歌搜索“ruby编程语言猜测”及其变体也无济于事。有人碰巧知道相关gem的名称吗? 最佳答案 是这个吗:http://github.com/chrislo/sourceclassifier/tree/master 关于ruby-寻找通过阅读代码确定编程语言的rubygem?,我们在StackOverflow上找到一个类似的问题:

  9. ruby - Net::HTTP 获取源代码和状态 - 2

    我目前正在使用以下方法获取页面的源代码:Net::HTTP.get(URI.parse(page.url))我还想获取HTTP状态,而无需发出第二个请求。有没有办法用另一种方法做到这一点?我一直在查看文档,但似乎找不到我要找的东西。 最佳答案 在我看来,除非您需要一些真正的低级访问或控制,否则最好使用Ruby的内置Open::URI模块:require'open-uri'io=open('http://www.example.org/')#=>#body=io.read[0,50]#=>"["200","OK"]io.base_ur

  10. 程序员如何提高代码能力? - 2

    前言作为一名程序员,自己的本质工作就是做程序开发,那么程序开发的时候最直接的体现就是代码,检验一个程序员技术水平的一个核心环节就是开发时候的代码能力。众所周知,程序开发的水平提升是一个循序渐进的过程,每一位程序员都是从“菜鸟”变成“大神”的,所以程序员在程序开发过程中的代码能力也是根据平时开发中的业务实践来积累和提升的。提高代码能力核心要素程序员要想提高自身代码能力,尤其是新晋程序员的代码能力有很大的提升空间的时候,需要针对性的去提高自己的代码能力。提高代码能力其实有几个比较关键的点,只要把握住这些方面,就能很好的、快速的提高自己的一部分代码能力。1、多去阅读开源项目,如有机会可以亲自参与开源

随机推荐