I2C通信协议及STM32代码解释

I2C通信协议

一:简介
IIC(Inter-Integrated Circuit)总线是一种由 PHILIPS 公司开发的两线式串行总线,用于连接控制器及其外围设备。它是由数据线 SDA 和时钟线SCL 构成的串行总线,可发送和接收数据。在 CPU 与被控 IC 之间、IC 与 IC 之间进行双向传送,高速 IIC 总线一般可达 400kbps 以上。
I2C 总线在物理连接上非常简单,分别由SDA(串行数据线)和SCL(串行时钟线)及上拉电阻组成。通信原理是通过对SCL和SDA线高低电平时序的控制,来产生I2C总线协议所需要的信号进行数据的传递。在总线空闲状态时,这两根线一般被上面所接的上拉电阻拉高,保持着默认高电平状态
I2C通信方式为半双工,只有一根SDA线,同一时间只可以单向通信,485也为半双工,SPI和uart为双工。
在这里插入图片描述二 I2C总线特征
1:I2C总线上的每一个设备都可以作为主设备或者从设备,而且每一个设备都会对应一个唯一的地址(地址通过物理接地或者拉高,可以从I2C器件的数据手册得知,如TVP5158芯片,7位地址依次bit6~bit0:x101 1xxx, 最低三位可配,如果全部物理接地,则该设备地址为0x58, 而之所以7bit因为1个bit要代表方向,主向从和从向主),主从设备之间就通过这个地址来确定与哪个器件进行通信,在通常的应用中,我们把CPU带I2C总线接口的模块作为主设备,把挂接在总线上的其他设备都作为从设备。
2: I2C总线上可挂接的设备数量受总线的最大电容400pF 限制,如果所挂接的是相同型号的器件,则还受器件地址位的限制。
3:I2C总线数据传输速率在标准模式下可达100kbit/s,快速模式下可达400kbit/s,高速模式下可达3.4Mbit/s。一般通过I2C总线接口可编程时钟来实现传输速率的调整,同时也跟所接的上拉电阻的阻值有关。
4: I2C总线上的主设备与从设备之间以字节(8位)为单位进行双向的数据传输

三:I2C总线协议
1:起始信号和停止信号
I2C协议规定,总线上数据的传输必须以一个起始信号作为开始条件,以一个结束信号作为传输的停止条件。起始和结束信号总是由主设备产生(意味着从设备不可以主动通信?所有的通信都是主设备发起的,主可以发出询问的command,然后等待从设备的通信)。
起始和结束信号产生条件:总线在空闲状态时,SCL和SDA都保持着高电平,当SCL为高电平而SDA由高到低的跳变,表示产生一个起始条件;当SCL为高而SDA由低到高的跳变,表示产生一个停止条件。
在起始条件产生后,总线处于忙状态,由本次数据传输的主从设备独占,其他I2C器件无法访问总线;而在停止条件产生后,本次数据传输的主从设备将释放总线,总线再次处于空闲状态。起始和结束如图所示:
在这里插入图片描述
2:数据传输
在了解起始条件和停止条件后,我们再来看看在这个过程中数据的传输是如何进行的。前面我们已经提到过,数据传输以字节为单位。主设备在SCL线上产生每个时钟脉冲的过程中将在SDA线上传输一个数据位,当一个字节按数据位从高位到低位的顺序传输完后,紧接着从设备将拉低SDA线,回传给主设备一个应答位, 此时才认为一个字节真正的被传输完成。当然,并不是所有的字节传输都必须有一个应答位,比如:当从设备不能再接收主设备发送的数据时,从设备将回传一个否定应答位。数据传输的过程如图所示:
正点原子解释:发送器每发送一个字节,就在时钟脉冲9期间释放数据线,由接收器反馈一个应答信号。应答信号为低电平时,规定为有效应答位(ACK简称应答位),表示接收器已经成功地接收了该字节;应答信号为高电平时,规定为非应答位(NACK),一般表示接收该字节没有成功。
对于反馈有效应答位ACK的要求是,接收器在第9个时钟脉冲之前的低电平期间将SDA线拉低,并且确保在该时钟的高电平期间为稳定的低电平。如果接收器是主控器,则在它收到最后一个字节后,发送一个NACK信号,以通知被控发送器结束数据发送,并释放SDA线,以便主控接收器发送一个停止信号P。
在这里插入图片描述3:数据有效性
IIC总线进行数据传输时,时钟信号为高电平期间,数据线上的数据必须保持稳定,只有在时钟信号上的信号为低电平期间,数据线上的高电平或者低电平状态才允许变化。
即:数据在SCL的上升沿到来之前就需要准备好,并在下降沿到来之前必须稳定
在这里插入图片描述4:地址
在前面我们还提到过,I2C总线上的每一个设备都对应一个唯一的地址,主从设备之间的数据传输是建立在地址的基础上,也就是说,主设备在传输有效数据之前要先指定从设备的地址,地址指定的过程和上面数据传输的过程一样,只不过大多数从设备的地址是7位的,然后协议规定再给地址添加一个最低位用来表示接下来数据传输的方向,0表示主设备向从设备写数据,1表示主设备向从设备读数据。向指定设备发送数据的格式如图所示:(每一最小包数据由9bit组成,8bit内容+1bit ACK, 如果是地址数据,则8bit包含1bit方向)

在这里插入图片描述下图是完整的一帧I2C数据:

在这里插入图片描述
四:I2C总线操作

对I2C总线的操作实际就是主从设备之间的读写操作。大致可分为以下三种操作情况:

  • 主设备往从设备中写数据。数据传输格式如下:

在这里插入图片描述

  • 主设备从从设备中读数据。数据传输格式如下:

    在这里插入图片描述

  • 主设备往从设备中写数据,然后重启起始条件,紧接着从从设备中读取数据;或者是主设备从从设备中读数据,然后重启起始条件,紧接着主设备往从设备中写数据。数据传输格式如下:

    在这里插入图片描述
    第三种操作在单个主设备系统中,重复的开启起始条件机制要比用STOP终止传输后又再次开启总线更有效率。

五:STM32代码详解

#ifndef __MYIIC_H
#define __MYIIC_H
#include "sys.h"

//IO输入输出方向设置,操作CRL寄存器
#define SDA_IN()  {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)8<<28;}
#define SDA_OUT() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)3<<28;}

//设置IIC数据线和时钟线的引脚
#define IIC_SCL    PBout(6) 		//SCL
#define IIC_SDA    PBout(7) 		//SDA	 
#define READ_SDA   PBin(7) 	 		//ÊäÈëSDA 

//IIC操作函数
void IIC_Init(void);                //初始化IIC的IO口 
void IIC_Start(void);				//发送IIC开始信号
void IIC_Stop(void);	  			//发送IIC停止信号
void IIC_Send_Byte(u8 txd);	//发送一个字节数据		
u8 IIC_Read_Byte(unsigned char ack);//IIC读取一个字节
u8 IIC_Wait_Ack(void); 	//IIC等待应答信号			
void IIC_Ack(void);					//IIC产生应答信号
void IIC_NAck(void);			//IIC产生非应答信号 
#endif
#include "myiic.h"
#include "delay.h"
//IIC初始化
void IIC_Init(void)
{					     
 	RCC->APB2ENR|=1<<3;		//使能外设IO
	GPIOB->CRL&=0X00FFFFFF;	//PB6/7推挽输出
	GPIOB->CRL|=0X33000000;	   
	GPIOB->ODR|=3<<6;     	//PB6,7 输出高
}
//产生IIC起始信号
//IIC起始信号产生的条件为:SCL为高电平时,SDA变为低电平
void IIC_Start(void)
{
	SDA_OUT();     //设置SDA为输出模式
	IIC_SDA=1;	  	//设置初始状态都为高电平  
	IIC_SCL=1;
	delay_us(4);
 	IIC_SDA=0;//起始信号,SDA由高变低
	delay_us(4);
	IIC_SCL=0; //钳住I2C总线,准备发送或接收数据
}	  
//产生IIC停止信号
//产生停止信号的条件为:SCL为高电平时,SDA由低变高
void IIC_Stop(void)
{
	SDA_OUT();//SDA设置为输出
	IIC_SCL=0;
	IIC_SDA=0;//起始都是低电平
 	delay_us(4);
	IIC_SCL=1; //SCL变为高电平
	IIC_SDA=1;//SDA由低电平转变为高电平产生停止信号
	delay_us(4);							   	
}

//IIC主设备传输一个数据完成后,从设备产生应答信号,主设备等待应答信号到来
//产生条件:SCL为高电平期间,SDA时钟保持低电平。
//返回值: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)//读取数据线SDA的电平状态,如果持续低电平,则不会产生IIC_Stop信号,返回0
	{
		ucErrTime++;
		if(ucErrTime>250)
		{
			IIC_Stop();//如果在SCL高电平期间,SDA信号线产生了一定时间的高电平则认为应答失败
			return 1;
		}
	}
	IIC_SCL=0;//应答结束,时钟输出0
	return 0;  
} 
//产生ACK应答信号
//产生条件为:SCL为高电平期间,SDA始终保持低电平
void IIC_Ack(void)
{
	IIC_SCL=0;
	SDA_OUT();
	IIC_SDA=0;
	delay_us(2);
	IIC_SCL=1;
	delay_us(2);
	IIC_SCL=0;
}
//产生非应答信号
//产生条件为:SCL为高电平期间,SDA也出现了高电平  
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发送一个字节  
//发送条件为:SCL为低电平期间准备好数据,SCL为高电平期间保持数据
void IIC_Send_Byte(u8 txd)
{                        
    u8 t;   
	SDA_OUT(); 	  //SDA设置为输出
    IIC_SCL=0;//拉低时钟准备数据
    for(t=0;t<8;t++)
    {              
      if((txd&0x80)>>7) //从数据的最高位开始传输
      		IIC_SDA=1;	//如果为1,则数据位为1
      	else IIC_SDA=0; //不为1,数据位为0
        txd<<=1; 	  //逐个传输
		delay_us(2);   
		IIC_SCL=1;
		delay_us(2); 
		IIC_SCL=0;	
		delay_us(2);
    }	 
} 	    
//读一个字节,ack=1时,发送ACK,ack=0,发送nACK
//读取条件为:SCL为高电平期间,读取SDA的电平状态
u8 IIC_Read_Byte(unsigned char ack)
{
	unsigned char i,receive=0;
	SDA_IN();//设置SDA为输入
    for(i=0;i<8;i++ ) //逐个读8位
	{
        IIC_SCL=0; 
        delay_us(2);
		IIC_SCL=1; //SCL为高电平
        receive<<=1; //逐个移动数据位
        if(READ_SDA)receive++;   //如果SDA为高,则相应的数据为+1,反之为0
		delay_us(1); 
    }					 
    if (!ack)
        IIC_NAck();//不产生ACK应答
    else
        IIC_Ack(); //产生ACK应答
    return receive;
}

六:通过IIC与EEPROM(24C02)通信
1:硬件连接
在这里插入图片描述
在这里插入图片描述EEPROM的A0-A2默认接地,SDA数据线与SCL时钟线默认拉高,与PC11和PC12连接(不同单片机引脚可能不同),WP为写保护

在这里插入图片描述当A0\A1\A2接地时,设备地址为10100000时表示写入数据,即0xA0,10100001为读出数据,即0xA1

2:写过程
24C02其实可以看做一个存储器,每个地址存储一个字节,写字节时过程如下图:首先是起始信号,然后发送设备地址(写时为0xA0),等待应答,发送准备写入字节的地址,等待应答,写入数据,等待应答,发送停止信号。
在这里插入图片描述

//在AT24CXX指定地址写入一个数据
//WriteAddr  :写入数据的目的地址,对于24C02为0-255
//DataToWrite:要写入的数据
void AT24CXX_WriteOneByte(u16 WriteAddr,u8 DataToWrite)
{				   	  	    																 
    IIC_Start();  
	if(EE_TYPE>AT24C16)//防止更大容量的AT24CXX
	{
		IIC_Send_Byte(0XA0);	    //发送写命令
		IIC_Wait_Ack();
		IIC_Send_Byte(WriteAddr>>8);//发送高地址
	}else IIC_Send_Byte(0XA0+((WriteAddr/256)<<1)); //发送器件地址,写数据
	IIC_Wait_Ack();	   //等待应答
    IIC_Send_Byte(WriteAddr%256);   //发送低地址
	IIC_Wait_Ack(); 	 										  		   
	IIC_Send_Byte(DataToWrite);     //发送字节						   
	IIC_Wait_Ack();  		    	   
    IIC_Stop();						//产生停止条件 
	delay_ms(10);	 				//EEPROM的写入速度较慢,加入延时
} 

3:读过程
首先是起始信号,然后发送设备地址(此时先设置为写状态0xA0),等待应答,写入准备读出字节的地址,等待应答,重新启动,发送设备地址更改为读状态,等待应答,读出数据,等待应答,发送停止信号。
在这里插入图片描述

//在AT24CXX指定地址读取一个数据
//ReadAddr:开始读数的地址
//返回值:读到的数据
u8 AT24CXX_ReadOneByte(u16 ReadAddr)
{				  
	u8 temp=0;		  	    																 
    IIC_Start();  
	if(EE_TYPE>AT24C16)
	{
		IIC_Send_Byte(0XA0);	   //发送写命令
		IIC_Wait_Ack();
		IIC_Send_Byte(ReadAddr>>8);//发送高地址 
	}else IIC_Send_Byte(0XA0+((ReadAddr/256)<<1));   //发送器件地址0xA0,写数据
	IIC_Wait_Ack(); 
    IIC_Send_Byte(ReadAddr%256);   //发送低地址
	IIC_Wait_Ack();	    
	IIC_Start();  	 	   
	IIC_Send_Byte(0XA1);           //进入接收模式	   
	IIC_Wait_Ack();	 
    temp=IIC_Read_Byte(0);		   
    IIC_Stop();//产生一个停止条件 
	return temp;
}

参考:http://blog.csdn.net/w89436838/article/details/38660631
《STM32不完全手册_寄存器版本_V3.1》

  • 10
    点赞
  • 80
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
STM32可以通过I2C总线与其他设备进行通信。I2C总线是一种串行通信协议,允许多个设备在同一个总线上通信。下面是STM32 I2C通信的基本步骤: 1. 配置I2C总线的GPIO管脚和时钟。 2. 初始化I2C外设,并设置传输速率和数据格式。 3. 发送起始信号和从设备地址。 4. 发送数据或读取数据。 5. 发送停止信号。 以下是一个简单的STM32 I2C通信示例代码,用于向从设备发送数据: ``` #include "stm32f4xx.h" #include "stm32f4xx_i2c.h" #define I2C_SPEED 100000 // I2C传输速率 #define I2C_TIMEOUT 1000 // I2C超时时间 void I2C_Configuration(void) { GPIO_InitTypeDef GPIO_InitStruct; I2C_InitTypeDef I2C_InitStruct; // 使能I2C和GPIO时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE); RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE); // 配置GPIO管脚 GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStruct.GPIO_OType = GPIO_OType_OD; GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP; GPIO_Init(GPIOB, &GPIO_InitStruct); // 配置GPIO复用功能 GPIO_PinAFConfig(GPIOB, GPIO_PinSource6, GPIO_AF_I2C1); GPIO_PinAFConfig(GPIOB, GPIO_PinSource7, GPIO_AF_I2C1); // 配置I2C参数 I2C_InitStruct.I2C_Mode = I2C_Mode_I2C; I2C_InitStruct.I2C_OwnAddress1 = 0x00; I2C_InitStruct.I2C_Ack = I2C_Ack_Enable; I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; I2C_InitStruct.I2C_ClockSpeed = I2C_SPEED; I2C_Init(I2C1, &I2C_InitStruct); // 使能I2C外设 I2C_Cmd(I2C1, ENABLE); } uint8_t I2C_SendData(uint8_t slaveAddr, uint8_t* buffer, uint8_t length) { uint32_t timeout = I2C_TIMEOUT; uint8_t status; // 发送起始信号 I2C_GenerateSTART(I2C1, ENABLE); while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)) { if (--timeout == 0) return I2C_ERROR_TIMEOUT; } // 发送从设备地址 I2C_Send7bitAddress(I2C1, slaveAddr, I2C_Direction_Transmitter); timeout = I2C_TIMEOUT; while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) { if (--timeout == 0) return I2C_ERROR_TIMEOUT; } // 发送数据 for (int i = 0; i < length; i++) { I2C_SendData(I2C1, buffer[i]); timeout = I2C_TIMEOUT; while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)) { if (--timeout == 0) return I2C_ERROR_TIMEOUT; } } // 发送停止信号 I2C_GenerateSTOP(I2C1, ENABLE); return I2C_ERROR_NONE; } int main(void) { uint8_t data[] = {0x01, 0x02, 0x03}; uint8_t result = I2C_SendData(0x50, data, sizeof(data)); while (1); } ``` 需要注意的是,I2C通信中的从设备地址需要根据实际情况修改。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值