精华内容
下载资源
问答
  • I2C通信协议详细讲解

    千次阅读 多人点赞 2020-12-13 23:08:59
    I2C协议讲解讲解流程我们为什么要学习I2C通信I2C协议简介:I2C物理层特点I2C协议层写数据读数据读和写数据通讯的起始和停止信号地址及数据方向 讲解流程 我们为什么要学习I2C通信 Stm32的最常用的板间通信有很多,有...

    实验准备

    一块STM32最小系统板,BH1750模块,一块PCB转换板模块、串口TTL转USB模块
    图片:
    在这里插入图片描述
    在这里插入图片描述

    PCB转换板原理图及PCB电路图

    在这里插入图片描述
    在这里插入图片描述

    讲解流程

    我们为什么要学习I2C通信

    Stm32的最常用的板间通信有很多,有I2C、SPI、CAN;I2C通信协议是我们stm32板间通信比较常用的、也是比较简单的。

    I2C协议简介:

    I2C 通讯协议(Inter-Integrated Circuit)是由 Phiilps 公司开发的,由于它引脚少,硬件实现简单,可扩展性强,不需要 USART、 CAN 等通讯协议的外部收发设备,所以被广泛使用。

    I2C物理层特点

    (1)它是一个支持设备的总线。“总线”指多个设备共用的信号线。在一个 I2C 通讯总线中,可连接多个 I2C 通讯设备,支持多个通讯主机及多个通讯从机。
    (2)一个 I2C 总线只使用两条总线线路,一条双向串行数据线(SDA) ,一条串行时钟线(SCL)。数据线即用来表示数据,时钟线用于数据收发同步。
    (3)每一个连接总线的设备都有一个独立的地址,主机可以通过这个地址进行选择连接总线的设备与之通信。
    (4)总线通过上拉电阻接到电源。当 I2C 设备空闲时,会输出高阻态,而当所有设备都空闲,都输出高阻态时,由上拉电阻把总线拉成高电平。
    (5)多个主机同时使用总线时,为了防止多个设备发送数据冲突,会利用仲裁方式决定由哪个设备占用总线。
    (6) 具有三种传输模式:标准模式传输速率为 100kbit/s ,快速模式为 400kbit/s ,高速模式下可达 3.4Mbit/s,但目前大多 I2C 设备尚不支持高速模式。
    (7) 连接到相同总线的 IC 数量受到总线的最大电容 400pF 限制 。
    备注:
    仲裁:SDA线的仲裁也是建立在总线具有线“与”逻辑功能(线与逻辑,即两个以上的输出端直接互连就可以实现“AND”的逻辑功能。两个一出一,一个一出零、没有一出零)的原理上的。节点在发送1位数据后,比较总线上所呈现的数据与自己发送的是否一致。是,继续发送;否则,进行比较,输出低电平进行发送,输出高电平退出。SDA线的仲裁可以保证I2C总线系统在多个主节点同时企图控制总线时通信正常进行并且数据不丢失。总线系统通过仲裁只允许一个主节点可以继续占据总线。

    在这里插入图片描述

    I2C协议层

    在这里插入图片描述

    S:由主机的 I2C 接口产生的传输起始信号(S),这时连接到 I2C 总线上的所有从机都会接收到这个信号。
    SLAVE ADDRESS:从机地址信号。 在 I2C 总线上,每个设备的地址都是唯一的, 当主机广播的地址与某个设备地址相同时,这个设备就被选中了,没被选中的设备将会忽略之后的数据信号。从机地址一般是 7 位或 10 位。
    R/W:是传输方向的选择位,该位为 0 时,表示后面的数据传输方向是由主机传输至从机,即主机向从机写数据。该位为 1 时,则相反,即主机由从机读数据。
    A | A/:一个应答(ACK)或非应答(NACK)信号。
    P:停止传输信。
    图中背景有填充的矩形表示 数据由主机传输至从机

    写数据

    讲得简单一点,即主机发送信息、从机阅读信息。配置为方向为“写数据”方向,接受的数据包大小为8位(一个byte),主机每发送完一个字节数据,都要等待从机的应答信号(ACK),重复这个过程,数据的多少可以理论是可以随便的。当数据传输结束时,主机向从机发送一个停止传输信号§,表示不再传输数据。

    读数据

    讲得简单一点,即主机阅读信息、从机发送信息。若配置的方向传输位为“读数据”方向,发送的数据包大小也为 8 位,从机每发送完一个数据,都会等待主机的应答信号(ACK),重复这个过程,数据的多少可以理论也是可以随便的。当主机希望停止接收数据时,就向从机返回一个非应答信号(NACK),则从机自动停止数据传输。

    读和写数据

    除了基本的读写,I2C 通讯更常用的是复合格式,该传输过程有两次起始信号(S)。一般在第一次传输中,主机通过 SLAVE_ADDRESS 寻找到从设备后,发送一段“数据”,这段数据通常用于表示从设备内部的寄存器或存储器地址(注意区分它与 SLAVE_ADDRESS 的区别);在第二次的传输中,对该地址的内容进行读或写。也就是说,第一次通讯是告诉从机读写地址,第二次则是读写的实际内容。

    通讯的起始和停止信号

    在这里插入图片描述

    当 SCL 线是高电平时 SDA 线从高电平向低电平切换,这个情况表示通讯的起始。当 SCL 是高电平时 SDA线由低电平向高电平切换,表示通讯的停止。起始和停止信号一般由主机产生。
    在这里插入图片描述

    I2C 使用 SDA 信号线来传输数据,使用 SCL 信号线进行数据同步。见图 24-6。 SDA数据线在 SCL 的每个时钟周期传输一位数据。传输时, SCL 为高电平的时候 SDA 表示的数据有效,即此时的 SDA 为高电平时表示数据“1”,为低电平时表示数据“0”。当 SCL为低电平时, SDA 的数据无效,一般在这个时候 SDA 进行电平切换,为下一次表示数据做好准备。
    每次数据传输都以字节为单位,每次传输的字节数不受限制。

    地址及数据方向

    I2C 总线上的每个设备都有自己的独立地址,主机发起通讯时,通过 SDA 信号线发送设备地址(SLAVE_ADDRESS)来查找从机。 I2C 协议规定设备地址可以是 7 位或 10 位,实际中 7 位的地址应用比较广泛。紧跟设备地址的一个数据位用来表示数据传输方向,它是数据方向位(R/),第 8 位或第 11 位。数据方向位为“1”时表示主机由从机读数据,该位为“0”时表示主机向从机写数据。
    在这里插入图片描述

    再提一嘴,读数据方向时,主机会释放对 SDA 信号线的控制,由从机控制 SDA 信号线,主机接收信号,写数据方向时, SDA 由主机控制,从机接收信号。切记,不要混淆。

    时钟控制逻辑

    SCL 线的时钟信号,由 I2C 接口根据时钟控制寄存器(CCR)控制,控制的参数主要为时
    钟频率。配置 I2C 的 CCR 寄存器可修改通讯速率相关的参数:

    • 可选择 I2C 通讯的“标准/快速”模式,这两个模式分别 I2C 对应 100/400Kbit/s 的
      通讯速率。

    • 在快速模式下可选择 SCL 时钟的占空比,可选 Tlow/Thigh=2 或 Tlow/Thigh=16/9
      模式,我们知道 I2C 协议在 SCL 高电平时对 SDA 信号采样, SCL 低电平时 SDA
      准备下一个数据,修改 SCL 的高低电平比会影响数据采样,但其实这两个模式的
      比例差别并不大,若不是要求非常严格,这里随便选就可以了。

    • CCR 寄存器中还有一个 12 位的配置因子 CCR,它与 I2C 外设的输入时钟源共同
      作用,产生 SCL 时钟, STM32 的 I2C 外设都挂载在 APB1 总线上,使用 APB1 的
      时钟源 PCLK1, SCL 信号线的输出时钟公式如下:

       	标准模式:
       	Thigh=CCR*TPCKL1
       	Tlow=CCR*TPCLK1
       	 
       	快速模式中 Tlow/Thigh=2 时:
       	Thigh = CCR*TPCKL1 
       	Tlow = 2*CCR*TPCKL1
       	
       	快速模式中 Tlow/Thigh=16/9 时:
       	Thigh = 9*CCR*TPCKL1 
       	Tlow = 16*CCR*TPCKL1
      

    例如,我们的 PCLK1=36MHz,想要配置 400Kbit/s 的速率,计算方式如下:
    PCLK 时钟周期: TPCLK1 = 1/36000000
    目标 SCL 时钟周期: TSCL = 1/400000
    SCL 时钟周期内的高电平时间: THIGH = TSCL/3
    SCL 时钟周期内的低电平时间: TLOW = 2*TSCL/3
    计算 CCR 的值:CCR=THIGHTPCLK1=TSCL3136000000=14000003136000000=360000001200000=30CCR ={THIGH\over TPCLK1} ={{TSCL\over 3}\over {1\over 36000000} }={{{1\over 400000}\over 3}\over {1\over 36000000} }={36000000\over 1200000}=30

    注:计算结果得出 CCR 为 30,向该寄存器位写入此值则可以控制 IIC 的通讯速率为400KHz,其实即使配置出来的 SCL 时钟不完全等于标准的 400KHz,IIC 通讯的正确性也不会受到影响,因为所有数据通讯都是由 SCL 协调的,只要它的时钟频率不远高于标准即可。

    通讯过程

    使用 I2C 外设通讯时,在通讯的不同阶段它会对“状态寄存器(SR1 及 SR2)”的不同数
    据位写入参数,我们通过读取这些寄存器标志来了解通讯状态。

    主发射器

    在这里插入图片描述

    主发送器发送流程及事件说明如下:

    1. 控制产生起始信号(S),当发生起始信号后,它产生事件“EV5”,并会对 SR1 寄
      存器的“SB”位置 1,表示起始信号已经发送;
    2. 紧接着发送设备地址并等待应答信号,若有从机应答,则产生事件“ EV6”及
      “EV8”,这时 SR1 寄存器的“ADDR”位及“TXE”位被置 1, ADDR 为 1 表
      示地址已经发送, TXE 为 1 表示数据寄存器为空;
    3. 以上步骤正常执行并对 ADDR 位清零后,我们往 I2C 的“数据寄存器 DR”写入
      要发送的数据,这时 TXE 位会被重置 0,表示数据寄存器非空, I2C 外设通过
      SDA 信号线一位位把数据发送出去后,又会产生“EV8”事件,即 TXE 位被置 1,
      重复这个过程,就可以发送多个字节数据了;
    4. 当我们发送数据完成后,控制 I2C 设备产生一个停止信号§,这个时候会产生
      EV8_2 事件, SR1 的 TXE 位及 BTF 位都被置 1,表示通讯结束。

    假如我们使能了 I2C 中断,以上所有事件产生时,都会产生 I2C 中断信号,进入同一
    个中断服务函数,到 I2C 中断服务程序后,再通过检查寄存器位来判断是哪一个事件。

    主接收器

    在这里插入图片描述

    主接收器接收流程及事件说明如下:

    1. 同主发送流程,起始信号(S)是由主机端产生的,控制发生起始信号后,它产生事
      件“EV5”,并会对 SR1 寄存器的“SB”位置 1,表示起始信号已经发送;
    2. 紧接着发送设备地址并等待应答信号,若有从机应答,则产生事件“ EV6”这时
      SR1 寄存器的“ADDR”位被置 1,表示地址已经发送。
    3. 从机端接收到地址后,开始向主机端发送数据。当主机接收到这些数据后,会产
      生“EV7”事件, SR1 寄存器的 RXNE 被置 1,表示接收数据寄存器非空,我们
      读取该寄存器后,可对数据寄存器清空,以便接收下一次数据。此时我们可以控
      制 I2C 发送应答信号(ACK)或非应答信号(NACK),若应答,则重复以上步骤接收
      数据,若非应答,则停止传输;
    4. 发送非应答信号后,产生停止信号§,结束传输。

    在发送和接收过程中,有的事件不只是标志了我们上面提到的状态位,还可能同时标
    志主机状态之类的状态位,而且读了之后还需要清除标志位,比较复杂。我们可使用
    STM32 标准库函数来直接检测这些事件的复合标志,降低编程难度。

    通讯引脚

    引脚 I2C1 I2C2
    SCL PB5/PB8(重映射) PB10
    SDA PB6/PB9(重映射) PB11

    I2C程序讲解

    讲了怎么多的理论知识,相信大部分人都困了,对I2C通信还有诸多疑问,但没关系,看不懂可以多看几次就可以看懂了,或者通过一个程序理解。那么我们来写一个使用I2C通信的传感器设备BH1750。BH1750光强度传感器的介绍此传感器可以直接输出环境光强的数值(单位为lx),其内部有16位AD转换,即可表示1lx-65535lx,通过IIC输出其数值。此传感器有3种分辨率模式,他们的分辨率分别为:4lx,1lx和0.5lx。他们的测量时间分别为:16ms,120ms,120ms。如果对测量时间要求不高的话,建议使用0.5lx分辨率的。

    初始化BH1750光照度传感器的大致流程为:

    GPIO初始化
    发送打开模块等待测量指令0x01
    发送重置数据寄存器值0x07但仅在PowerOn模式下有效
    发送选择连续L分辨率模式指令0x13
    获取光照度并转发串口

    器件安装

    在这里插入图片描述

    程序源码

    看过了流程,那就将流程图转代码吧。

    main函数

    int main(void)
    {	
    	delay_init();	    	 //延时函数初始化
    	uart_init(115200);		 //串口函数初始化
    	Light_Init();		 	 //GPIO初始化
    	bh_data_send(BHPowOn);	 //发送打开模块等待测量指令0x01
    	bh_data_send(BHReset);	 //发送重置数据寄存器值0x07但仅在PowerOn模式下有效
    	bh_data_send(BHModeL);	 //发送选择连续L分辨率模式指令0x13
    	delay_ms(180);
    	
    	while(1)
    	{
    		printf("%d\r\n",bh_data_read()); //获取光照度并通过串口转发
    		delay_ms(100);
    	}
    }
    

    Light_Init函数:

    void Light_Init(void)
    {
    	GPIO_InitTypeDef  GPIO_InitStructure;
     	
    
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);	 	//使能A端口时钟
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;	 	//设置PB6和PB7
     	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		 	//推挽输出
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;			//速度50MHz
     	GPIO_Init(GPIOB, &GPIO_InitStructure);	 				    //初始化GPIOB6,7
     	GPIO_SetBits(GPIOB,GPIO_Pin_6|GPIO_Pin_7);	 				//设置PB6和PB7输出高电平
    
    	Single_Write_BH1750(0x01); 									//发送设备地址
    }
    
    //发送地址值函数
    void Single_Write_BH1750(uchar REG_Address)
    {
       IIC_Start();                  //起始信号
       IIC_Send_Byte(BHAddWrite);   //发送设备地址+写信号
       IIC_Send_Byte(REG_Address);    //内部寄存器地址,
      //  BH1750_SendByte(REG_data);       //内部寄存器数据,
       IIC_Stop();                   //发送停止信号
    }
    

    bh_data_send函数:

    void bh_data_send(u8 command)
    {
        do{
    		IIC_Start();                   //iic起始信号
    		IIC_Send_Byte(BHAddWrite);     //发送器件地址
        }while(IIC_Wait_Ack());            //等待从机应答
        IIC_Send_Byte(command);            //发送指令
        IIC_Wait_Ack();                    //等待从机应答
        IIC_Stop();                        //iic停止信号
    }
    

    看不懂这个函数为什么这么做,可以对照主发送器通讯过程
    在这里插入图片描述

    首先发送iic起始信号和器件地址,等待从机返回应答信号,然后发送指令,继续等待从机返回应答信号,如果没有指令要继续发送的话,那么可以发送iic停止信号而不发送指令。那么一个发送函数就算完成了。关于iic函数我会下面进行讲解的。

    bh_data_read函数:

    //读取传感器发送的数据,从而获取光照度函数
    u16 bh_data_read(void)
    {
    	u16 buf;
    	IIC_Start();                       //iic起始信号
    	IIC_Send_Byte(BHAddRead);         //发送器件地址+读标志位
    	IIC_Wait_Ack();                     //等待从机应答
    	buf=IIC_Read_Byte(1);              //读取数据
    	buf=buf<<8;                        //读取并保存高八位数据
    	buf+=0x00ff&IIC_Read_Byte(0);      //读取并保存低八位数据
    	IIC_Stop();                        //发送停止信号 
    	return buf; 
    }
    

    看不懂这个读取函数,同样可以对照主接受器接收过程
    在这里插入图片描述

    先声明一个无符号16位的buf变量准备存储数据,然后发送iic起始信号和器件地址,等待从机返回应答信号,然后将读取的高八位数据赋予给buf,因为是高八位数据,但buf里的数据并没有在高八位而在低八位,那么我们就要就数据左移八位将数据放在高八位,有了高八位数据但没有低八位数据怎么行,那么我们就在获取一次放在buf里就可以了,获取数据后,就没有其他事情干了,发送IIC停止信号就可以结束了。

    前面的发送函数和接收函数里发送和接收的原理还没有说呢,现在新建一个iic.c文件 和 iic.h文件,对照着下面的两张时序图去写代码。

    在这里插入图片描述
    在这里插入图片描述

    代码大致如下:
    iic.c文件

    #include "IIC.h"
    #include "sys.h"
    
    typedef   unsigned char BYTE;
    //BYTE    BUF[8];                         //接收数据缓存区   
    
    //产生IIC起始信号
    void IIC_Start(void)
    {
    	SDA_OUT();     //sda线输出
    	IIC_SDA=1;	  	  
    	IIC_SCL=1;
    	delay_us(4);
     	IIC_SDA=0;//START:when CLK is high,DATA change form high to low 
    	delay_us(4);
    	IIC_SCL=0;//钳住I2C总线,准备发送或接收数据 
    }	  
    //产生IIC停止信号
    void IIC_Stop(void)
    {
    	SDA_OUT();//sda线输出
    	IIC_SCL=0;
    	IIC_SDA=0;//STOP:when CLK is high DATA change form low to high
     	delay_us(4);
    	IIC_SCL=1; 
    	IIC_SDA=1;//发送I2C总线结束信号
    	delay_us(4);							   	
    }
    //等待应答信号到来
    //返回值:1,接收应答失败
    //        0,接收应答成功
    u8 IIC_Wait_Ack(void)
    {
    	u8 ucErrTime=0;
    	SDA_IN();      //SDA设置为输入  
    	IIC_SDA=1;delay_us(1);	   
    	IIC_SCL=1;delay_us(1);	 
    	while(READ_SDA)
    	{
    		ucErrTime++;
    		if(ucErrTime>250)
    		{
    			IIC_Stop();
    			return 1;
    		}
    	}
    	IIC_SCL=0;//时钟输出0 	   
    	return 0;  
    } 
    //产生ACK应答
    void IIC_Ack(void)
    {
    	IIC_SCL=0;
    	SDA_OUT();
    	IIC_SDA=0;
    	delay_us(2);
    	IIC_SCL=1;
    	delay_us(2);
    	IIC_SCL=0;
    }
    //不产生ACK应答		    
    void IIC_NAck(void)
    {
    	IIC_SCL=0;
    	SDA_OUT();
    	IIC_SDA=1;
    	delay_us(2);
    	IIC_SCL=1;
    	delay_us(2);
    	IIC_SCL=0;
    }					 				     
    //IIC发送一个字节
    //返回从机有无应答
    //1,有应答
    //0,无应答			  
    void IIC_Send_Byte(u8 txd)
    {                        
        u8 t;   
    	SDA_OUT(); 	    
        IIC_SCL=0;//拉低时钟开始数据传输
        for(t=0;t<8;t++)
        {              
            //IIC_SDA=(txd&0x80)>>7;
    		if((txd&0x80)>>7)
    			IIC_SDA=1;
    		else
    			IIC_SDA=0;
    		txd<<=1; 	  
    		delay_us(2);   //对TEA5767这三个延时都是必须的
    		IIC_SCL=1;
    		delay_us(2); 
    		IIC_SCL=0;	
    		delay_us(2);
        }	 
    } 	    
    //读1个字节,ack=1时,发送ACK,ack=0,发送nACK   
    u8 IIC_Read_Byte(unsigned char ack)
    {
    	unsigned char i,receive=0;
    	SDA_IN();//SDA设置为输入
        for(i=0;i<8;i++ )
    	{
            IIC_SCL=0; 
            delay_us(2);
    		IIC_SCL=1;
            receive<<=1;
            if(READ_SDA)receive++;   
    		delay_us(1); 
        }					 
        if (!ack)
            IIC_NAck();//发送nACK
        else
            IIC_Ack(); //发送ACK   
        return receive;
    }
    

    iic.h文件

    #ifndef __IIC_H
    #define __IIC_H
    
    //库名
    #include "stm32f10x.h"
    #include "delay.h"
    
    #define SDA_IN()  {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)8<<28;}
    #define SDA_OUT() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)3<<28;}
    
    
    
    #define IIC_SCL    PBout(6) //SCL
    #define IIC_SDA    PBout(7) //SDA	 
    #define READ_SDA   PBin(7)  //输入SDA 
    #define ADDR 0x23//0100011
    #define uchar unsigned char 
    
    #define BHAddWrite     0x46      //从机地址+最后写方向位
    #define BHAddRead      0x47      //从机地址+最后读方向位
    #define BHPowDown      0x00      //关闭模块
    #define BHPowOn        0x01      //打开模块等待测量指令
    #define BHReset        0x07      //重置数据寄存器值在PowerOn模式下有效
    #define BHModeH1       0x10      //高分辨率 单位1lx 测量时间120ms
    #define BHModeH2       0x11      //高分辨率模式2 单位0.5lx 测量时间120ms
    #define BHModeL        0x13      //低分辨率 单位4lx 测量时间16ms
    #define BHSigModeH     0x20      //一次高分辨率 测量 测量后模块转到 PowerDown模式
    #define BHSigModeH2    0x21      //同上类似
    #define BHSigModeL     0x23      // 上类似
    
    //IIC所有操作函数
    void IIC_Init(void);                //初始化IIC的IO口				 
    void IIC_Start(void);				//发送IIC开始信号
    void IIC_Stop(void);	  			//发送IIC停止信号
    void IIC_Send_Byte(u8 txd);			//IIC发送一个字节
    u8 IIC_Read_Byte(unsigned char ack);//IIC读取一个字节
    u8 IIC_Wait_Ack(void); 				//IIC等待ACK信号
    void IIC_Ack(void);					//IIC发送ACK信号
    void IIC_NAck(void);				//IIC不发送ACK信号	
    
    #endif 
    

    看了怎么多代码,相信还是懵懵懂懂,但只要多看几遍就会理解了的。

    展开全文
  • STM32的I2C通信

    万次阅读 多人点赞 2017-08-07 12:49:35
    STM32的两个GPIO引脚,分别用于SCL和SDA,按照I2C规约的时序,像控制LED灯那样控制引脚输出,若是接收数据时则读取SDA线上的电平,那就可以实现I2C通信了,这也是我们在51单片机上的“软件模拟协议”做法。...

      STM32的两个GPIO引脚,分别用于SCL和SDA,按照I2C规约的时序,像控制LED灯那样控制引脚输出,若是接收数据时则读取SDA线上的电平,那就可以实现I2C通信了,这也是我们在51单片机上的“软件模拟协议”做法。但是STM32上还配有I2C控制器片上外设,只要配置好该外设,它就可以依据规约产生通讯信号。收/发数据置于缓存寄存器中,cpu只要检测该外设的状态和数据寄存器就能完成数据收发。但是利用该外设来实现I2C通讯,存在许多硬件Bug,下来还是简单讲解这种做法。

    1. I2C通讯时序

      通俗来讲,IIC的时序就是主机向从设备发出一个数据后,要间隔多少时间从机才会回复,或者说SCL上的信号要维持多久的高电平、低电平才使得SDA的信号正确传输到从机等,这些是在I2C协议明确规约的,在51单片机软件模拟IIC时序时,编程中我们需要自己实现函数去延时。在STM32的I2C控制器下的IIC时序将不需要这么繁琐。如何实现?看下面摘自《STM32中文参考手册_V10.pdf》I2C章节的两图(STM32系统既可以当IIC主机也可以当IIC从机,以I2C主机模式为例)。

    主机发送:
    这里写图片描述
    (1) 主机端代码控制IIC控制器产生起始信号(S),当发生起始信号后,控制器产生事件“EV5”,并会对SR1寄存器的“SB”位置1,表示起始信号已经发送。
    (2) 紧接着发送设备地址,然后等待从机的应答信号,若从机有回答,控制器将产生“EV6”及“EV8”
    (3) I2C外设通过SDA信号线将数据一位一位发送出去,一字节数据发送完毕后,控制器将会产生“EV8”,重复此过程可以发送多个字节数据
    (4) 发送完所有数据后,代码控制IIC控制器产生结束信号(P),控制器将会产生“EV8_2”事件,表示通讯结束。

    假设我们使能了I2C中断,以上所有事件都可以产生I2C中断信号,进入同一个中断处理函数,在该函数中通过访问对应寄存器判断是哪一事件。

    主机接收:
    这里写图片描述
    (1) 主机端代码控制IIC控制器产生起始信号(S),当发生起始信号后,控制器产生事件“EV5”,并会对SR1寄存器的“SB”位置1,表示起始信号已经发送。
    (2) 紧接着发送设备地址,然后等待从机的应答信号,若从机有回答,控制器将产生“EV6”
    (3) 从机向主机端发送数据,当主机接收到数据后,会产生“EV7”事件。接下来若要继续接收数据则发出应答信号ACK,若发出非应答信号(NACK)则停止传输
    (4) 主机端发送非应答信号,产生停止信号,传输结束

    利用标准库函数I2C_CheckEvent()可以检测EV5事件的发生与否,如:

    I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT); //I2C_EVENT_MASTER_MODE_SELECT表EV5

    2.I2C初始化描述结构体

    I2C初始化描述结构体:
    typedef struct
    {
      uint32_t I2C_ClockSpeed;         /* 设置SCL线上的时钟频率,此值不可高于400000 */
      uint16_t I2C_Mode;               /* 指定工作模式,I2C模式或者SMBUS模式可选 */
      uint16_t I2C_DutyCycle;          /* 时钟占空比,即高电平时间/低电平时间 */
      uint16_t I2C_OwnAddress1;        /* 指定I2C自身设备的地址 */
      uint16_t I2C_Ack;                /* 应答使能或关闭,一般为使能 */
      uint16_t I2C_AcknowledgedAddress;/* 指定地址长度,7位/10位可选 */
    }I2C_InitTypeDef;

    (1) I2C_ClockSpeed:设置I2C的数据传输速率,此数值不得高于400000
    (2) I2C_Mode:STM32的I2C外设可用于I2C通讯的主机/从机,还可用于SMBus协议通讯使用。此值取值可为:

    I2C_Mode_I2C:I2C模式,I2C模式不需要指定主从机。
    I2C_Mode_SMBusHost:SMBus主模式
    I2C_Mode_SMBusDevice:SMBus从模式

    (3) I2C_DutyCycle:指定SCL的占空比,即高电平时间/低电平时间,取值可为I2C_DutyCycle_2或者I2C_DutyCycle_16_9。对此值要求并不严格,可任意选择。
    (4) I2C_OwnAddress1:配置I2C设备自身的地址,挂接在I2C总线上的设备,包括主机,都有自己的地址。STM32的I2C外设可同时使用两个地址,即对两个地址都作出反应。这里设置的是第一个地址,第二个地址通过函数I2C_OwnAddress2Config()设置。
    (5) I2C_Ack:是否发送响应信号,可设置为使能2C_Ack_Enable或者非使能2C_Ack_Enable,一般I2C通讯都使用使能。
    (6) I2C_AcknowledgedAddress:设置I2C的寻址模式,可设置为7位(I2C_AcknowledgedAddress_7bit)或者10位(I2C_AcknowledgedAddress_10bit),若要使用10位寻址模式,需要将此值设置为I2C_AcknowledgedAddress,I2C_OwnAddress1才是有效的10位地址。

    配置好这个结构体,通过函数I2C_Init()设置到实际控制器中。

    3. I2C通讯引脚

    STM32F10x中是有两个I2C外设的,分别为I2C1和I2C2。但是我在手上的《STM32中文参考手册_V10.pdf》中看到的只是I2C1的SCL和SDA引脚设置:
    这里写图片描述
    在正点原子MiniSTM32开发板的原理图看到,确实存在I2C2,I2C2的SCL和SDA分别是PB10和PB11。
    这里写图片描述

      以上只是了解一下标准库对STM32的硬件I2C的封装。因为硬件I2C这种方式的不稳定,所以详细的编码就不再记录。在STM32上实现I2C通讯,个人觉得还是采用在学习51单片机的时候估计大部分人都折腾过,用软件模拟I2C时序的方法,虽然貌似复杂度增加,但是比较稳定,下来有时间再折腾并记录。

    展开全文
  • I2C通信全面解析

    万次阅读 多人点赞 2020-02-19 18:03:25
    1. 物理接口: SCL + SDA (1)SCL(serial clock):时钟线,传输CLK信号,一般是I2C主...(1)I2C属于串行通信,所有的数据以位为单位在SDA线上串行传输。 (2)同步通信就是通信双方工作在同一个时钟下,一般...

    1.  物理接口: SCL + SDA

    (1)SCL(serial clock):时钟线,传输CLK信号,一般是I2C主设备向从设备提供时钟的通道。

    (2)SDA(serial data):数据线,通信数据都通过SDA线传输

    2. 通信特征:串行、同步、非差分、低速率

    (1)I2C属于串行通信,所有的数据以位为单位在SDA线上串行传输。

    (2)同步通信就是通信双方工作在同一个时钟下,一般 通信的A方通过一根CLK信号线传输A自己的时钟给B,B工作在A传输的时钟下。所以同步通信的显著特征就是:通信线中有CLK。

    (3)非差分。因为I2C通信速率不高,而且通信双方距离很近,对干扰不敏感,所以使用电平信号通信。

    (4)低速率。I2C一般是用在同一个板子上的2个IC之间的通信,而且用来传输的数据量不大,所以本身通信速率很低(一般几百KHz,不同的I2C芯片的通信速率可能不同,具体在编程的时候要看自己所使用的设备允许的I2C通信最高速率,不能超过这个速率)

    3. 突出特征1:主设备+从设备

    (1)I2C通信的时候,通信双方地位是不对等的,而是分主设备和从设备。通信由主设备发起,由主设备主导,从设备只是按照I2C协议被动的接受主设备的通信,并及时响应。

    (2)谁是主设备、谁是从设备是由通信双方来定的(I2C协议并无规定),一般来说一个芯片可以只能做主设备、也可以只能做从设备、也可以既能当主设备又能当从设备。(像一些传感器芯片设计的时候只能做从设备,有的芯片本身既可以当主设备又可以当从设备,通过软件来配置是主设备还是从设备)

    4. 突出特征2:

    (1)I2C通信可以一对一(一个主设备对1个从设备),也可以一对多(一个主设备对多个从设备)

    (2)主设备负责调度总线,决定某一时间和哪个从设备通信。注意:同一时间内,I2C的总线上只能传输一对设备的通信信息,所以同一时间只能有一个从设备和主设备通信,其他从设备处于“冬眠”。不能出来捣乱,否则通信就乱套了。

    (3)每一个I2C从设备在通信中都有一个I2C从设备地址,这个设备地址是从设备本身固有的属性,然后通信时主设备需要知道自己将要通信的那个从设备的地址,然后在通信中通过地址来甄别是不是自己要找的那个从设备。

    5. I2C的通信时序

    (1)什么是时序?

    字面意思,时序就是时间顺序,实际上在通信中时序就是通信线上按照时间顺序发生的电平变化,以及这些变化对通信的意义就叫时序。

    (2)I2C的总线空心状态、起始位、结束位

    总线的意思就是SCL与SDA加起来就叫做总线。

    I2C总线上有1个主设备,n(n>=1)个从设备。I2C总线上有2种状态:空闲态(所有从设备都未和主设备通信,此时总线空闲)和忙态(其中一个从设备在和主设备通信,此时总线被这一对占用,其他从设备必须歇着)。

    怎么区分总线处于空闲态,SDA、SCL在通信的过程中也可能同时为高电平,但是,如果SDA、SCL如果连续多个周期同时处于高电平,则总线一定处于空闲态。

    整个通信分为一个周期一个周期的,两个相邻的通信周期是空闲态。每一个通信周期由一个起始位开始,一个结束位结束,中间是本周期的通信数据。

    起始位:起始位说的不是一个时间点,而是一个时间段,如上图的t1到t2这个时间段,在t1到t2的这个时间段内,SCL一直保持了高电平,SDA由高电平到低电平跳变(下降沿)。接收方在接收到这样一个电平变化之后就知道发送方要开始发送数据了,紧接着下一个周期就是数据,

    停止位:与起始位相似,结束位也是一个时间段。在这段时间内总线状态变化情况是:SCL先维持高电平,同时SDA线发生一个从低电平到高的上升沿。

    起始位和停止位中间的就是通信位。

    CLK上升沿锁存数据,CLK上升沿到来前SDA要提前准备好数据。

    A是应答信号,从设备的应答是为了让主设备知道自己已经接收到了主设备的信息。相应的主设备的应答也是如此。

    6. I2C数据传输格式(数据位&ACK)

    (1)每一个通信周期的发起和结束都是由主设备来做的,从设备只有被动的响应主设备,没法自己自发的去做任何事情。

    (2)主设备在通信周期会先发8位的从设备地址(其实8位中只有7位是从设备地址,还有1位表示主设备下面要写入还是读出)到总线(主设备是以广播的形式发送的,只要是总线上的所有从设备其实都能收到这个信息)。然后总线上的每个从设备都能收到这个地址,并且收到地址后和自己的设备地址比较看是否相等。如果相等说明主设备本次通信 就和给我说话,如果不相等说明这次通信与我无关,不用听了不管了。

    (3)发送方发送一段数据后,接收方需要回应一个ACK。这个响应本身只有1个bit位,不能携带有效信息,只能表示2个意思(要么表示收到数据,即有效响应;要么表示未收到数据,无效响应),需要注意的是,到第九个周期的时候,主设备会释放SDA,即让其变为高电平,从设备会主动来拉低SDA,主设备会读取此时的SDA,若读取此时的SDA为则表示从设备已经收到刚刚发送的信息,若读取此时的SDA为则表示从设备没有收到刚刚发送的信息。

    (4)在某一个通信时刻,主设备和从设备只能有一个在发(占用总线,也就是向总线写),另一个在收(从总线读)。如果在某个时间主设备和从设备都试图向总线写那就完蛋了,通信就乱套了。

    7. 数据在总线上的传输协议

    (1)I2C通信时的基本数据单位也是以字节为单位的,每次传输的有效数据都是1个字节(8位)。

     

    (2)起始位及其后的8个clk中都是主设备在发送(主设备掌控总线),此时从设备只能读取总线,通过读总线来得知主设备发给从设备的信息;然后到了第9个周期,按照协议规定从设备需要发送ACK给主设备,所以此时主设备必须释放总线(主设备把总线置为高电平然后不要动,其实就类似于总线空闲状态),同时从设备试图拉低总线发出ACK。如果从设备拉低总线失败,或者从设备根本就没有拉低总线,则主设备看到的现象就是总线在第9周期仍然一直保持高,这对主设备来说,意味着我没有收到ACK,主设备就认为刚才给从设备发送的8字节不对(接收失败)。

    (3)由于I2C的速率很低,完全可以通gpio来模拟。

    8. I2C控制器

    通信双方本质上是通过时序在工作,但是时序会比较复杂不利于Soc软件完成,于是乎解决方案是soc内部内置了硬件的控制器来产生通信时序。这样我们写软件时只需要向控制器的寄存器中写入配置值即可,控制器会产生适当的时序在通信线上和对方通信。

    (1)时钟部分,时钟来源是PCLK_PSYS,经过内部分频最终得到I2C控制器的CLK,通信中这个CLK会通过SCL线传给从设备。

    (2)I2C总线控制逻辑(前台代表是I2CCON、I2CSTAT这两个寄存器),主要负责产生I2C通信时序。实际编程中要发送起始位、停止位、接收ACK等都是通过这两个寄存器(背后所代表的电路模块)实现的。

    (3)移位寄存器(shift register),将代码中要发送的字节数据,通过移位寄存器变成1个位一个位的丢给SDA线上去发送/接收。

    (4)地址寄存器+比较器   本I2C控制器做从设备的时候用。

    注1:为什么SDA=就是释放总线,是因为当单片机把引脚拉高时,根据gpio为OC门外置上拉的原理,从设备可以选择再把这边引脚拉高或者拉低:但是当单片机把这个引脚拉低(接地)后,从设备再也没有办法把这个引脚拉高了。

    注2:写入和读取都是高位在前。

    展开全文
  • 本文重点还是想教你真正的理解了I2C通信的原理与编程,I2C通信一要掌握原理,二要自己真正的去编程实践,如果你看完本篇文章,你能自己编写一个软件模拟I2C驱动程序,你就真正的掌握了I2C通信原理。 2、I2C通...

    1、导读

            如果你想深入了解和学习I2C通信,请阅读全篇文章,如果你只是要临时快速的完成I2C通信外设的驱动,可以直接看代码,复制到你的工程中去,编译,调试很快就解决问题。本文重点还是想教你真正的理解了I2C通信的原理与编程,I2C通信一要掌握原理,二要自己真正的去编程实践,如果你看完本篇文章,你能自己编写一个软件模拟I2C驱动程序,你就真正的掌握了I2C通信原理。

    2、I2C通信协议简介

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

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

         

    2.1 工作原理 

     

          SDA(串行数据线)和SCL(串行时钟线)都是双向I/O线,接口电路为开漏输出,需通过上拉电阻接电源VCC。当总线空闲时.两根线都是高电平,连接总线的外同器件都是CMOS器件,输出级也是开漏电路.在总线上消耗的电流很小,因此,总线上扩展的器件数量主要由电容负载来决定,因为每个器件的总线接口都有一定的等效电容.而线路中电容会影响总线传输速度.当电容过大时,有可能造成传输错误.所以,其负载能力为400pF,因此可以估算出总线允许长度和所接器件数量。

    2.2  I2C总线特点

    (1)在硬件上,I2C总线只需要一根数据线和一根时钟线两根线,总线接口已经集成在芯片内部,不需要特殊的接口电路,而且片上接口电路的滤波器可以滤去总线数据上的毛刺.因此I2C总线简化了硬件电路PCB布线,降低了系统成本,提高了系统可靠性。因为I2C芯片除了这两根线和少量中断线,与系统再没有连接的线,常用的IC外设可以很容易形成标准化和模块化,便于重复利用。

    (2)I2C总线是一个真正的多主机总线,如果两个或多个主机同时初始化数据传输,可以通过冲突检测和仲裁防止数据破坏,每个连接到总线上的器件都有唯一的地址,任何器件既可以作为主机也可以作为从机,但同一时刻只允许有一个主机。数据传输和地址设定由软件设定,非常灵活。总线上的器件增加和删除不影响其他器件正常工作。

    (3)I2C总线可以通过外部连线进行在线检测,便于系统故障诊断和调试,故障可以立即被寻址,软件也利于标准化和模块化,缩短开发时间。

    (4)连接到相同总线上的IC数量只受总线最大电容的限制,串行的8位双向数据传输位速率在标准模式下可达100Kbit/s,快速模式下可达400Kbit/s,高速模式下可达3.4Mbit/s。

    (5)总线具有极低的电流消耗.抗高噪声干扰,增加总线驱动器可以使总线电容扩大10倍,传输距离达到15m;兼容不同电压等级的器件,工作温度范围宽

        

    2.3  字节格式

         I2C总线上传输数据前要先发送一个起始位(起始条件 START),之后SDA 线上发送数据,数据的每个字节必须为8 位,每次传输可以发送的字节数量不受限制。每个字节后必须跟一个应答位(ACK)。首先传输的是数据的最高位(MSB),如果从机要完成一些其他功能后(例如一个内部中断服务程序)才能接收或发送下一个完整的数据字节,可以使时钟线SCL 保持低电平,迫使主机进入等待状态,当从机准备好接收下一个数据字节并释放时钟线SCL 后数据传输继续, 数据传输完成后再发送一个停止位(STOP)表示一次通信完成。

     

     

    2.4   应答响应

       数据传输必须带响应位,相关的响应时钟脉冲由主机产生。在响应的时钟脉冲期间发送器释放SDA 线(高)。

       在响应的时钟脉冲期间,接收器必须将SDA 线拉低,使它在这个时钟脉冲的高电平期间保持稳定的低电平。

       通常被寻址的接收器在接收到的每个字节后,

     

    必须产生一个响应。当从机不能响应从机地址时(例如它正在执行一些实时函数不能接收或发送),从机必须使数据线保持高电平,主机然后产生一个停止条件终止传输或者产生重复起始条件开始新的传输。

          如果从机接收器响应了从机地址,但是在传输了一段时间后不能接收更多数据字节,主机必须再一次终止传输。这个情况用从机在第一个字节后没有产生响应来表示。从机使数据线保持高电平,主机产生一个停止或重复起始条件。

         如果传输中有主机接收器,它必须通过在从机发出的最后一个字节时产生一个响应,向从机发送器通知数据结束。从机发送器必须释放数据线,允许主机产生一个停止或重复起始条件。

    2.5 时钟同步

           所有主机在SCL线上产生它们自己的时钟来传输I2C总线上的报文。数据只在时钟的高电平周期有效,因此需要一个确定的时钟进行逐位仲裁。

           时钟同步通过线与连接I2C 接口到SCL 线来执行。这就是说SCL 线的高到低切换会使器件开始数它们的低电平周期,而且一旦器件的时钟变低电平,它会使SCL 线保持这种状态直到到达时钟的高电平。但是如果另一个时钟仍处于低电平周期,这个时钟的低到高切换不会改变SCL 线的状态。因此SCL 线被有最长低电平周期的器件保持低电平。此时低电平周期短的器件会进入高电平的等待状态。

         当所有有关的器件数完了它们的低电平周期后,时钟线被释放并变成高电平。之后器件时钟SCL线的状态没有差别,而且所有器件会开始数它们的高电平周期。首先完成高电平周期的器件会再次将SCL线拉低。

    这样产生的同步SCL 时钟的低电平周期由低电平时钟周期最长的器件决定,而高电平周期由高电平时钟周期最短的器件决定。

    3、软件模拟I2C通信驱动程序编写

            根据上面I2C通信协议的介绍,一个段I2C的通信时序分成“起始位”,“数据位”,“停止位”,“应答位”,软件模拟I2C通信就是实现这几个数据位的函数。

             本文的开源的软件I2C驱动以STM32F103为硬件,底层的IO调用使用了STM32F103的驱动,如果你使用的CPU与此不同,需要进行底层IO驱动部分的修改。

    3.1  初始化函数

            软件模拟I2C通信驱动程序要编写的第一个函数i2c_init, 就是I2C通信接口使用的SCL, SDA两个IO的初始化,根据I2C协议,这两个IO要初始化成开漏模式。i2c_init函数首先打开SCL, SDA  IO口的时钟,设置SCL, SDA为输出开漏模式,输出为高电平。

    I2C_NUM是一个宏定义,用于定义软件驱动支持的软件模拟的I2C的通道的数量,可以设置为1或2。后面的i2c_start, i2c_stop,i2c_ack等函数均有一个输入参数取值为0或1分别表示第1路,第2路I2C驱动。

            使用软件模拟I2C驱动程序,需要首先调用这个函数进行一次初始化。

    /****************************************************************************************
    ** Function name:      i2c_init()
    ** Descriptions:        I2C使用的IO管脚初始化函数
    ** input parameters:   无
    ** output parameters:  无
    ** Returned value:      无
    ****************************************************************************************/
    OTP_VOID i2c_init(void)
    {
    
    #if(2 == I2C_NUM)
        /*打开I2C0使用的端口时钟*/
        RCC_APB2PeriphClockCmd(I2C0_SOFT_SCL_PORT_PERIPH |  I2C0_SOFT_SDA_PORT_PERIPH, Enable);
        RCC_APB2PeriphClockCmd(I2C1_SOFT_SCL_PORT_PERIPH |  I2C1_SOFT_SDA_PORT_PERIPH , Enable);
        
        SDA_OUT(0);
        SDA_OUT(1);
        SDAH(0);
        SDAH(1);
        
        SCL_OUT(0);
        SCL_OUT(1);
        SCLH(0);
        SCLH(1);
         
    #else
        
        RCC_APB2PeriphClockCmd(I2C0_SOFT_SCL_PORT_PERIPH |  I2C0_SOFT_SDA_PORT_PERIPH , Enable);
        
        SDA_OUT(0);
        SDAH(0);
        
        SCL_OUT(0);
        SCLH(0);
    #endif    
    
    }

         

    3.2  起始位,停止位函数

               i2c_start函数用于产生起始位条件,即保持SCL高电平时, SDA产生一个下降沿。

               i2c_stop函数用于产生停止条件,即保持SCL高电平时,SDA产生一个上升沿。

    /*********************************************************************************************************
    ** Function name:      i2c_delay()
    ** Descriptions:        I2C延时函数
    ** input parameters:   延时值
    ** output parameters:  无
    ** Returned value:      无
    *********************************************************************************************************/
    OTP_VOID i2c_delay(OTP_UINT32 time)
    {
        OTP_UINT32 i;
        for( i = 0; i < time; i++)
        {
            /*do nothing*/
        }
    }
    
    
    /*********************************************************************************************************
    ** Function name:      i2c_start()
    ** Descriptions:        产生I2C起始条件
    ** input parameters:   操作的I2C端口
    ** output parameters:  无
    ** Returned value:      无
    *********************************************************************************************************/
    LOCAL OTP_VOID i2c_start(OTP_UINT8 id)
    {   
        SDA_OUT(id);  
        SDAH(id);
        SCLH(id);
        i2c_delay(I2C_DELAY_TIME);
        SDAL(id); 
        i2c_delay(I2C_DELAY_TIME);
        SCLL(id);
        SDAH(id);
        
    }
    
    /*********************************************************************************************************
    ** Function name:      i2c_stop()
    ** Descriptions:        产生I2C停止条件
    ** input parameters:   操作的I2C端口
    ** output parameters:  无
    ** Returned value:      无
    *********************************************************************************************************/
    LOCAL OTP_VOID i2c_stop(OTP_UINT8 id)
    {
        SDA_OUT(id);
        
        SCLL(id);
        SDAL(id);
        i2c_delay(I2C_DELAY_TIME);
        
        SCLH(id);
        i2c_delay(I2C_DELAY_TIME);
    
        SDAH(id);
        i2c_delay(I2C_DELAY_TIME);
    }

    3.3 应答

          应答分成应答位ACK, 非应答NACK两个种。当主机进行写数据给从机时,需要调用i2c_wait_ack等待从机应答获取从机已经接收到写入的数据。当主机进行读取数据时,需要调用i2c_send_ack给从机进行应答,告诉从机传输下一下字节,读取到最后一个字节后调用i2c_sens_no_ack给从机产生非应答,告诉从机读取完成,不要再向总线上输出数据了。

    /*********************************************************************************************************
    ** Function name:      i2c_send_ack()
    ** Descriptions:        产生I2C应答条件
    ** input parameters:   操作的I2C端口
    ** output parameters:  无
    ** Returned value:      无
    *********************************************************************************************************/
    LOCAL OTP_VOID i2c_send_ack(OTP_UINT8 id)   
    {
        /*发送0,在scl为高电平时使sda信号为低*/
        SDA_OUT(id);
        
        SCLL(id);                  /*数据线可以输出 */
    
        SDAL(id);                  /*在此发出应答或非应答信号 */    
        i2c_delay(I2C_DELAY_TIME); 
    
        SCLH(id);
        i2c_delay(I2C_DELAY_TIME);  /*时钟低电平周期大于4.7μs*/
    
        SCLL(id);
        SDAH(id);
        i2c_delay(I2C_DELAY_TIME);
    }
    
    /*********************************************************************************************************
    ** Function name:      i2c_send_no_ack()
    ** Descriptions:        产生I2C非应答条件
    ** input parameters:   操作的I2C端口
    ** output parameters:  无
    ** Returned value:      无
    *********************************************************************************************************/
    LOCAL OTP_VOID i2c_send_no_ack(OTP_UINT8 id)
    {
        /*发送1,在scl为高电平时使sda信号为高*/
        SDA_OUT(id);
        SCLL(id);           /*数据线可以输出 */
        
        SDAH(id);
        i2c_delay(I2C_DELAY_TIME);
    
        SCLH(id);
        i2c_delay(I2C_DELAY_TIME);  
    
        SCLL(id);
        SDAH(id);
    }
    
    /*********************************************************************************************************
    ** Function name:      i2c_wait_ack()
    ** Descriptions:        等待I2C应答信号
    ** input parameters:   操作的I2C端口
    ** output parameters:  无
    ** Returned value:      无
    *********************************************************************************************************/
    LOCAL STATUS i2c_wait_ack(OTP_UINT8 id)
    {
        OTP_UINT32 flag = 0; 
        SCLL(id);   
        SDA_IN(id);
                            
        i2c_delay(I2C_DELAY_TIME);
        
        SCLH(id);       /*读取应答位 */
        i2c_delay(I2C_DELAY_TIME);
        
        flag = SDA_R(id);
        SCLL(id);
        i2c_delay(I2C_DELAY_TIME);
        
        if(flag)
        {
            return ERROR;
        }
        return OK;
    }

    3.4 写字节和读字节

             I2C传输数据是一个字节为单位来进行传输,读写数据首先要实现读写一个字节的函数,即读字节i2c_read_byte和写字节i2c_write_byte两个。

    /*********************************************************************************************************
    ** Function name:      i2c_write_byte()
    ** Descriptions:        向I2C写一个字节
    ** input parameters:   操作的I2C端口
    ** output parameters:  无
    ** Returned value:      无
    *********************************************************************************************************/
    LOCAL STATUS i2c_write_byte(OTP_UINT8 id, OTP_UINT8 data)
    {
        /*向I2C总线写一个字节*/
        OTP_UINT8 i = 0;
    
        SDA_OUT(id);
            
        for (i = 0; i < 8; i++ )    
        {   
            SCLL(id);
            i2c_delay(I2C_DELAY_TIME / 2);
            if (data & 0x80 )   
            {
                SDAH(id);
            }
            else
            {   
                SDAL(id);
            }
            data <<= 1;
            i2c_delay(I2C_DELAY_TIME / 2);
    
            SCLH(id);
            i2c_delay(I2C_DELAY_TIME);      
        }
        return i2c_wait_ack(id );
    }
    
    /*********************************************************************************************************
    ** Function name:      i2c_read_byte()
    ** Descriptions:        从I2C读取一个字节
    ** input parameters:   操作的I2C端口
    ** output parameters:  无
    ** Returned value:      无
    *********************************************************************************************************/
    OTP_UINT8 i2c_read_byte(OTP_UINT8 id)
    {
        OTP_UINT8 i = 0, data=0;
        
        SDA_IN(id);
        i2c_delay(I2C_DELAY_TIME);
    
        for(i = 0; i < 8; i++)
        {   
            SCLL(id);                       /*置时钟线为低,准备接收数据位*/
            i2c_delay(I2C_DELAY_TIME);  
    
            SCLH(id);                       /*置时钟线为高使数据线上数据有效*/
            i2c_delay(I2C_DELAY_TIME / 2);
    
            if(SDA_R(id))
            {
                data |= 1 << (7 - i) ;        /*读数据位,接收的数据位放入retc中 */
            }
               
            i2c_delay(I2C_DELAY_TIME / 2);
        }
        SCLL(id);
        return(data);
        
    }

    3.5  总线数据读写

        I2C总线一般通信都是读写多个字节,I2C从器件如EEPROM,  IO扩展芯片,传感器芯片(这芯片在I2C总线上做从器件)等,芯片内部设置有寄存器,对于这些芯片的访问就是读写寄存器。

         当写从器件寄存器时,I2C总线上需要先写入器件的从机地址,再写入寄存器的地址,最后写入寄存器的数据,具体的写入时序如下图所示。根据时序图,编写一个向I2C从器件写寄存器的函数i2c_send_data,这个函数调用上面介绍的函数,来实现一次对从器件的数据写入。

    /*********************************************************************************************************
    ** Function name:      i2c_send_data()
    ** Descriptions:        向i2c总线发送数据
    ** input parameters:   id--操作的I2C端口
    **                      i2cAddr--器件地址
    **                      addr--器件内部地址
    **                      addr_len--内部地址长度
    **                      pSendData--缓冲区地址
    **                      sendLen--数据长度
    ** output parameters:  无
    ** Returned value:      无
    *********************************************************************************************************/
    STATUS i2c_send_data(OTP_UINT8 id,       OTP_UINT8 i2cAddr,    OTP_UINT16 addr,  
                         OTP_UINT8 addr_len, OTP_UINT8 *pSendData, OTP_UINT8 sendLen)
    {
        OTP_UINT8 i =0;
        OTP_UINT8 temp[3];
        
        temp[0] = i2cAddr & I2C_WRITE;
        if(2 == addr_len)
        {    
            temp[1] = addr >> 8;
        }
        else
        {
            temp[1] = addr;
        }    
        temp[2] = addr;
        
        i2c_stop(id);
        i2c_start(id);
        
        for(i = 0; i < addr_len + 1; i++)
        {
            if( OK != i2c_write_byte(id, temp[i]))
            {
                ASSERT(0);
                i2c_stop(id);  
                return ERROR;
            }    
        }    
        
            
        while (sendLen--)
        {   
            if( OK != i2c_write_byte(id, *pSendData++) )
            {
                ASSERT(0);
                i2c_stop(id); 
                return ERROR;
            }
        }
        
        i2c_stop(id);
        
        return(OK);
    
    }

         当读从器件寄存器时,I2C总线上需要先发乘客器件的从机写地址,再写入寄存器的地址,产生重复超始条件,发送器件从机读地址,最后读取寄存器的数据,根据这种应用,编写一个向I2C从器件写寄存器的函数i2c_recv_data,这个函数调用上面介绍的函数来实现,时序图如下图所示,注意这个函数实现的I2C操作时序要复杂一些,当读取到最后一个字节后,还要额外再读取一个字节来给从机产生非应答位来通信从机读取完成, 不用再向总线上传输数据。

    
    /*********************************************************************************************************
    ** Function name:      i2c_rcv_data()
    ** Descriptions:        从i2c总线读取数据
    ** input parameters:   id--操作的I2C端口
    **                      Addr--地址
    **                      pSendData--缓冲区地址
    **                      sendLen--数据长度
    ** output parameters:  无
    ** Returned value:      无
    *********************************************************************************************************/
    STATUS i2c_rcv_data(OTP_UINT8 id,       OTP_UINT8 i2cAddr,   OTP_UINT16 addr,  
                        OTP_UINT8 addr_len, OTP_UINT8 *pRcvData, OTP_UINT8 rcvLen)
    {
        OTP_UINT8 buf[3];
        OTP_UINT8 i = 0;
        
        buf[0] = i2cAddr & I2C_WRITE;
        if(2 == addr_len)
        {    
            buf[1] = addr >> 8;
        }
        else
        {
            buf[1] = addr;
        } 
        buf[2] = addr;
        
         
        i2c_start(id);
        
        /*发送读地址*/
        for(i = 0; i < addr_len + 1; i++)
        {
            if( OK != i2c_write_byte(id, buf[i]))
            {
                ASSERT(0);
                i2c_stop(id);  
                return ERROR;
            }      
        
        }    
        
        
        i2c_start(id);
        /*发送读取命令*/
        if(OK != i2c_write_byte (id, i2cAddr | I2C_READ))
        {
            /*ASSERT(0);*/
            i2c_stop(id); 
            return ERROR;
        }
    
        while (rcvLen--)
        {
            *pRcvData++ = i2c_read_byte(id);
            i2c_send_ack(id);
        }
        /*多读取一个数据,用于产生非应答信号*/
        (OTP_VOID)i2c_read_byte(id);
        i2c_send_no_ack(id);
        
        i2c_stop(id);
        
        return OK;
    }
    

    3.6  头文件及功能配置

            到此,已经讲解完了I2C软件模拟的全部驱动程序了,下面贴出驱动程序中所使用的相关宏定义的头文件。

    #ifndef __INCLUDE_I2C_H
    #define __INCLUDE_I2C_H
    #include "stm32f10x.h"
    
    /*********************************************************************************************************
    *                定义软件I2C接口数目
    *********************************************************************************************************/
    #define     I2C_NUM  2 
    
    /*********************************************************************************************************
    *                定义I2C驱动管脚
    *********************************************************************************************************/
    #define I2C0_SOFT_SCL_PORT_PERIPH  RCC_APB2Periph_GPIOA
    #define I2C0_SOFT_SCL_PORT         GPIOA
    #define I2C0_SOFT_SCL              GPIO_Pin_11 
    
    #define I2C0_SOFT_SDA_PORT_PERIPH  RCC_APB2Periph_GPIOA
    #define I2C0_SOFT_SDA_PORT         GPIOA
    #define I2C0_SOFT_SDA              GPIO_Pin_12
    
    #define I2C1_SOFT_SCL_PORT_PERIPH  RCC_APB2Periph_GPIOB
    #define I2C1_SOFT_SCL_PORT         GPIOB
    #define I2C1_SOFT_SCL              GPIO_Pin_6 
    
    #define I2C1_SOFT_SDA_PORT_PERIPH  RCC_APB2Periph_GPIOB
    #define I2C1_SOFT_SDA_PORT         GPIOB
    #define I2C1_SOFT_SDA              GPIO_Pin_7
    
    
    
    /*********************************************************************************************************
    *                定义软件I2C接口速率
    *********************************************************************************************************/
    #define    I2C_SPEED         50000                                   /*定义I2C速率为100K                */
    #define    I2C_DELAY_TIME    (CPU_FREQ/I2C_SPEED/20)                 /*定义I2C时钟延时时间,半个时钟周期 */
    
    /*********************************************************************************************************
    *                定义软件I2C接口读写命令
    *********************************************************************************************************/
    #define    I2C_WRITE    0xFE
    #define    I2C_READ     0x01
    
    
    
    typedef struct S_I2C_IO_STRUCT
    {
        GPIO_TypeDef   *sclPort;        /*SCL的port口*/
        OTP_UINT16      sclPin;         /*SCL的pin*/
        GPIO_TypeDef   *sdaPort;        /*SDA的port口*/
        OTP_UINT16      sdaPin;        /*SDA的pin*/
    }S_I2C_IO;
    
    
    /*********************************************************************************************************
    ** Function name:      i2c_init()
    ** Descriptions:        I2C使用的IO管脚初始化函数
    ** input parameters:   无
    ** output parameters:  无
    ** Returned value:      无
    *********************************************************************************************************/
    OTP_VOID i2c_init(void);
    
    
    /*********************************************************************************************************
    ** Function name:      i2c_rcv_data()
    ** Descriptions:        从i2c总线读取数据
    ** input parameters:   id--操作的I2C端口
    **                      Addr--地址
    **                      pSendData--缓冲区地址
    **                      sendLen--数据长度
    ** output parameters:  无
    ** Returned value:      无
    *********************************************************************************************************/
    STATUS i2c_rcv_data(OTP_UINT8 id,       OTP_UINT8 i2cAddr,    OTP_UINT16 addr,  
                        OTP_UINT8 addr_len, OTP_UINT8 *pRcvData, OTP_UINT8 rcvLen);
    
    /*********************************************************************************************************
    ** Function name:      i2c_send_data()
    ** Descriptions:        向i2c总线发送数据
    ** input parameters:   id--操作的I2C端口
    **                      i2cAddr--器件地址
    **                      addr--器件内部地址
    **                      addr_len--内部地址长度
    **                      pSendData--缓冲区地址
    **                      sendLen--数据长度
    ** output parameters:  无
    ** Returned value:      无
    *********************************************************************************************************/
    STATUS i2c_send_data(OTP_UINT8 id, OTP_UINT8 i2cAddr, OTP_UINT16 addr,  
                         OTP_UINT8 addr_len, OTP_UINT8 *pSendData, OTP_UINT8 sendLen);

              上面的头文件还有一些宏定义是引用软件模拟I2C驱动程序之外的头文件,在这里做一个说明。

    /*定义CPU的工作频率, STM32主频工作频率设置为72MHz*/
    #define    CPU_FREQ                  72000000
    
    
    /*数据结构重定义*/
    
    typedef char            OTP_CHAR;   /* 不保证是否有符号*/
    typedef void            OTP_VOID;                                       
    typedef signed char     OTP_INT8;   /* 有符号8位 */
    typedef unsigned char   OTP_UINT8;  /* 无符号8位 */
    typedef signed short    OTP_INT16;  /* 有符号16位 */
    typedef unsigned short  OTP_UINT16; /* 无符号16位 */
    typedef signed int      OTP_INT32;  /* 有符号32位 */
    typedef unsigned int    OTP_UINT32; /* 无符号32位 */
    
    /*与定义成了long的移植代码对接,显式说明长度为32位*/
    typedef signed long     OTP_LONG32; /* 有符号32位 */
    typedef unsigned long   OTP_ULONG32;    /* 无符号32位 */
    typedef signed long long    OTP_INT64;  /* 有符号64位 */
    typedef unsigned long long OTP_UINT64;  /* 无符号64位 */
    
    /* 8位寄存器类型 */
    typedef volatile OTP_UINT8 OTP_REG8;
    
    /* 指令 */
    typedef unsigned int    OTP_INSTRUCT;
    
    
    /* 局部变量或局部函数修饰符 */
    #ifndef LOCAL
    #define LOCAL           static
    #endif
    
    #ifndef IMPORT
    #define IMPORT          extern
    #endif
    
    
    /* OK ERROR 与 STATUS 一起使用*/
    #ifndef STATUS
    #define STATUS int 
    #endif
    #ifndef OK
    #define OK              0
    #endif
    #ifndef ERROR
    #define ERROR           (-1)
    #endif
    
    

    4、使用软件模拟I2C驱动程序读写EEPROM

             上面我们已经自己编写了一个软件模拟I2C驱动程序,下面就来实现一个EEPROM  AT24C64的读写函数。

    
    
    /*AT24Cxx 使用第几个I2C id*/
    #define AT24CXX_I2C_ID		0
    #define AT24C08_I2C_ADDR	0xA0
    #define AT24C64_I2C_ADDR	0xA0
    
    
    /*定义页大小*/
    #define AT24C08_PAGE_SIZE   16
    #define AT24C64_PAGE_SIZE   32
    
    /*********************************************************************************************************
    ** Function name:      at24cxx_delay()
    ** Descriptions:        AT24Cxx操作延时函数
    ** input parameters:   无
    ** output parameters:  无
    ** Returned value:      无
    *********************************************************************************************************/
    LOCAL OTP_VOID at24cxx_delay(OTP_UINT8 time)
    {
        OTP_UINT16 i = 0;
        OTP_UINT16 j = 0;
    
        for( i = 0; i < time; i++ )
        {
            /*主频33M,延时1ms要执行4800次循环*/
            for(j = 0; j < 4800; j++)
            {
            }
        }
    }
    
    
    /*********************************************************************************************************
    ** Function name:      at24c64_read()
    ** Descriptions:        AT24C64读操作函数
    ** input parameters:   无
    ** output parameters:  无
    ** Returned value:      无
    *********************************************************************************************************/
    STATUS  at24c64_read(OTP_UINT16 addr, OTP_UINT8 *pBuf, OTP_UINT16 len)
    {
        OTP_UINT16  pageleft = 0;   
    
        /*ASSERT(NULL != pBuf);*/
        if(NULL==pBuf)
        {
            ASSERT(0);
            return ERROR;
        }
       
        while(len)
        {
            /*  当前页还有多少空间可以读   */
            pageleft = AT24C64_PAGE_SIZE - (addr & (AT24C64_PAGE_SIZE - 1));
    
            /*  当前页可以读出多少字节的数据*/
            pageleft = (pageleft >= len) ? len : pageleft;
    
           
            /*读出数据*/
            if( OK != i2c_rcv_data(AT24CXX_I2C_ID,
                                   AT24C64_I2C_ADDR, addr, sizeof(addr),
                                   pBuf, pageleft)) 
    
            
            {
                ASSERT(0);
                return ERROR;
            }
    
            len = len - pageleft;
            addr = addr + pageleft;
            pBuf = pBuf + pageleft;
            
         }
    
         return OK;
         
    }
    
    /*********************************************************************************************************
    ** Function name:      at24c64_write()
    ** Descriptions:        AT24C64读操作函数
    ** input parameters:   无
    ** output parameters:  无
    ** Returned value:      无
    *********************************************************************************************************/
    STATUS at24c64_write(OTP_UINT16 addr, OTP_UINT8 *pBuf, OTP_UINT16 len)
    {
        OTP_UINT16   pageleft = 0;  
    
    
        if(NULL == pBuf)
        {
            return ERROR;
        }
        
        
        while(len)
        {
            /*  当前页还有多少空间可以读   */
            pageleft = AT24C64_PAGE_SIZE - (addr & (AT24C64_PAGE_SIZE - 1));
    
            /*  当前页可以读出多少字节的数据*/
            pageleft = (pageleft >= len) ? len : pageleft;
    
    
            /*写入数据*/
            if(OK != i2c_send_data(AT24CXX_I2C_ID, AT24C64_I2C_ADDR, addr, sizeof(addr), pBuf, pageleft))
            {
                return ERROR;
            }
    
    
    
            len = len - pageleft;
            addr = addr + pageleft;
            pBuf = pBuf + pageleft;
            /*写完一页数据延时10ms,等待其写入*/
            at24cxx_delay(10);
         }
    
         
         return OK;
    
    }

     

     

    展开全文
  • I2C总线是PHLIPS公司推出的一种串行总线,I2C总线只有两根双向信号线。一根是数据线SDA,另一根是时钟线SCL。 每个接到I2C总线上的器件都有唯一的地址。主机与其它器件间的数据传送可以是由主机发送数据到其它器件,...
  • i2c时序图的详细讲解

    万次阅读 多人点赞 2018-04-25 21:25:22
    i2c简易时序图 启动信号: SCL为高电平的时候,SDA由高电平向低电平跳变。结束信号:SCL为高电平的时候,SDA由低电平向高电平跳变。 应答信号: I2C总线上的所有数据都是以8位字节传送的,发送器每发送一个字节...
  • I2C通信详解

    千次阅读 2018-10-04 15:28:31
    什么事I2C通信 物理接口:SCL+SDA SCL(serial clock ):时钟线,传输CLK信号,一般是I2C主设备向从设备提供时钟的通道。 SDA(serial data):数据线,通信数据都通过SDA线传输 通信特征:串行、同步、...
  • 第三部分,测试方法以及详细测试结果,i2c从设备的7bit器件地址可以在设备的datasheet查找。文章的最后会给大家分享本文的所有源码。 二、开发背景和环境 在做嵌入式相关工作时,需要配置i2c从设备的寄存器是常有...
  • 前后向通信构成双向的控制通道,从而有了本文中将要讨论的FPDLINK中I2C的巧妙设计问题。 FPDLINK在使用中都是一个serializer和deserializer配对使用,CPU可以连接到serializer,也可以连接到deserializer,取决于...
  • I2C通信详解-1.12.ARM裸机第十二部分

    千人学习 2015-09-17 17:03:08
    首先简单介绍I2C通信的基本特征,然后详细讲解I2C通信的时序图,并且通过I2C的时序图讲解了通信物理层的时序概念。之后介绍了S5PV210的I2C控制器、gsensor芯片的I2C通信流程图,后分析了相关的代码。本课程的目标...
  • GPIO模拟I2C通信协议(二)

    千次阅读 2018-11-08 14:48:58
    本博客是GPIO模拟I2C通信协议系列的第2篇,承接上一篇的内容,总结单片机通过用GPIO模拟的I2C和从设备E2PROM进行数据交换功能的实现。主要内容包括E2PROM简介、AT24C28的读写逻辑、实现代码和效果展示。其中AT24C28...
  • I2C通信 读写数据过程

    万次阅读 2017-03-10 12:04:44
    通信之初,主从机必须根据自己的要求约定好通信规则:command的定义和位置、address的位数和位置。 以读写从机寄存器数据为例: 假设从机寄存器地址为8位、从机寄存器也位8位(被读取数据为8位); ...
  • 十五.ARM裸机学习之I2C通信详解

    千次阅读 2017-12-29 22:55:26
    其中I2C总线以同步串行2线方式进行通信(一条时钟线,一条数据线)。 SPI总线则以同步串行3线方式进行通信(一条时钟线,一条数据输入线,一条数据输出线)。 SCI总线是以异步方式进行通信(一条数
  • I2C通信时序图解析

    千次阅读 2019-10-06 02:32:06
    一、I2C协议简介   I2C 通讯协议(Inter-Integrated Circuit)是由 Phiilps 公司开发的,由于它引脚少,...  关于I2C协议的更多内容,可阅读《I2C总线协议》,本博文主要分析I2C波形图,对于I2C的基础知识不在做...
  • I2C通信之EEPROM-第1季第15部分

    千人学习 2016-12-29 21:44:12
    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第15个课程,主要讲解了EEPROM的编程和使用,其中重点是I2C接口,I2C是物联网系统中主芯片和传感器芯片的通信接口,本课程的目标是对I2C的时序彻底掌握。
  • PIC单片机之I2C通信(主模式)。

    万次阅读 2013-05-17 11:29:44
    我们今天来讲I2C通信。那I2C通信的特点是什么能。我们一般使用的串口 (半双工异步串行通信)与I2C 有什么区别呢。  串口(半双工异步串行通信):就是好像朋友在对话。我可以主动和你讲话,你也可以主动和我讲话...
  • 本文使用STM32FI03RCT6型号的单片机, 基于正点原子的函数库进行总结讲解 想来单片机这块儿除了USART串口通信外,常见的便是I2C...STM32单片机是可以进行硬件I2C通信或者软件模拟进行I2C通信的,硬件I2C通信只需...
  • EFM8单片机与I2C外设通信

    千次阅读 2015-07-12 12:10:58
    最近帮同学做一个项目,开发板是EFM8单片机,支持SPI和I2C协议(SMBus)。很久没搞过单片机了,而且条件限制,为了使单片机和外设成功通信,花了一个星期时间。...本文重点解释I2C,废话少说了。 1、简介 I2C(In
  • I2C通信实验

    千次阅读 2019-01-06 11:50:35
    C语言小知识: 如何定义字符串: 可以通过字符数组或字符指针来定义字符串,也可以用宏定义对常量字符串进行定义。...char names2[ ] = "jack"; 字符串数组 char * names3 = "jack"; ...
  • I2C通信 初学笔记

    万次阅读 多人点赞 2017-09-14 16:03:49
     I2C总线是由Philips公司开发的一种简单、双向二线制同步串行总线。它只需要两根线即可在连接于总线上的器件之间传送信息。  一般是用于连接微控制器及其外围设备,由两根线组成,分别是:时钟线SDA和数据线SCL ,...
  • 普通IO口模拟实现I2C通信及应用解析

    万次阅读 2014-08-20 14:54:19
    根据I2C通信规范(具体可以参考“浅谈I2C总线”),通过普通IO端口模拟可以实现单片机(主设备)与从设备的I2C通信,其中SCL通过IO口延时高低电平变化实现,SDA根据SCL状态变化产生开始信号,结束信号,以及实现发送...
  • ESP8266开发之旅 基础篇⑤ ESP8266 SPI通信和I2C通信

    千次阅读 多人点赞 2019-06-19 13:49:35
        设备与设备之间的通信往往都伴随着总线的使用,而用得比较多的就当属于SPI总线和I2C总线,而恰巧NodeMcu也支持这两种总线通信,所以本章的主要内容就是讲解ESP8266 SPI和I2C总线的使用。 1. SPI总线——SPI...
  • I2C通信基本原理及其实现

    千次阅读 2017-09-09 10:21:16
    I2C是一种总线式结构,它只需要SCL时钟信号线与SDA数据线,两根线就能将连接与总线上的设备实现数据通信,由于它的简便的构造设计,于是成为一种较为常用的通信方式。 由于I2C采用的是主从式通信方式,所以,通信的...
  • 关于I2C通信BUG小积累之通信频率

    千次阅读 2019-01-16 14:12:23
    I2C的协议网上有很多讲解的非常好,可以参考其他人的总结复习一下协议内容 如:https://blog.csdn.net/lingfeng5/article/details/73361833 。 前些日子遇到了一个i2c通讯的问题,耗了不少精力和时间才解决,特地...
  • PIC单片机之I2C通信(主模式)

    千次阅读 2016-06-16 11:34:10
    我们今天来讲I2C通信。那I2C通信的特点是什么能。我们一般使用的串口 (半双工异步串行通信)与I2C 有什么区别呢。  串口(半双工异步串行通信):就是好像朋友在对话。我可以主动和你讲话,你也可以主动和我讲话...
  • 第十三天: I2C通信详解

    千次阅读 2016-08-13 22:32:51
    什么事I2C通信 物理接口:SCL+SDA SCL(serial clock ):时钟线,传输CLK信号,一般是I2C主设备向从设备提供时钟的通道。 SDA(serial data):数据线,通信数据都通过SDA线传输 通信特征:串行、...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 124,301
精华内容 49,720
关键字:

i2c通信的详细讲解