I2C软硬件实现详解

/**  
 * @brief I2C去初始化  
 *  
 * 将指定的I2C外设复位到默认状态。  
 *  
 * @param I2Cx I2C结构体指针,指定要操作的I2C实例  
 */  
void I2C_DeInit(I2C_TypeDef* I2Cx);  
  
/**  
 * @brief I2C初始化  
 *  
 * 根据指定的参数初始化I2C外设。  
 *  
 * @param I2Cx I2C结构体指针,指定要操作的I2C实例  
 * @param I2C_InitStruct 指向I2C初始化结构的指针,包含初始化参数  
 */  
void I2C_Init(I2C_TypeDef* I2Cx, I2C_InitTypeDef* I2C_InitStruct);  
  
/**  
 * @brief I2C结构初始化  
 *  
 * 将指定的I2C初始化结构复位到默认状态。  
 *  
 * @param I2C_InitStruct 指向I2C初始化结构的指针,用于存储初始化参数  
 */  
void I2C_StructInit(I2C_InitTypeDef* I2C_InitStruct);  
  
/**  
 * @brief I2C命令使能或失能  
 *  
 * 使能或失能指定的I2C外设。  
 *  
 * @param I2Cx I2C结构体指针,指定要操作的I2C实例  
 * @param NewState 新的状态,ENABLE表示使能,DISABLE表示失能  
 */  
void I2C_Cmd(I2C_TypeDef* I2Cx, FunctionalState NewState);  
  
/**  
 * @brief I2C DMA命令使能或失能  
 *  
 * 使能或失能指定的I2C外设的DMA功能。  
 *  
 * @param I2Cx I2C结构体指针,指定要操作的I2C实例  
 * @param NewState 新的状态,ENABLE表示使能,DISABLE表示失能  
 */  
void I2C_DMACmd(I2C_TypeDef* I2Cx, FunctionalState NewState);  
  
/**  
 * @brief I2C DMA最后传输命令使能或失能  
 *  
 * 使能或失能指定的I2C外设的DMA最后传输功能。  
 *  
 * @param I2Cx I2C结构体指针,指定要操作的I2C实例  
 * @param NewState 新的状态,ENABLE表示使能,DISABLE表示失能  
 */  
void I2C_DMALastTransferCmd(I2C_TypeDef* I2Cx, FunctionalState NewState);  
  
/**  
 * @brief I2C生成START条件  
 *  
 * 在指定的I2C外设上生成START条件。  
 *  
 * @param I2Cx I2C结构体指针,指定要操作的I2C实例  
 * @param NewState 新的状态,ENABLE表示生成START条件,DISABLE表示不生成  
 */  
void I2C_GenerateSTART(I2C_TypeDef* I2Cx, FunctionalState NewState);  
  
/**  
 * @brief I2C生成STOP条件  
 *  
 * 在指定的I2C外设上生成STOP条件。  
 *  
 * @param I2Cx I2C结构体指针,指定要操作的I2C实例  
 * @param NewState 新的状态,ENABLE表示生成STOP条件,DISABLE表示不生成  
 */  
void I2C_GenerateSTOP(I2C_TypeDef* I2Cx, FunctionalState NewState);  
  
/**  
 * @brief I2C应答配置  
 *  
 * 配置指定的I2C外设是否响应从设备地址匹配时的应答信号。  
 *  
 * @param I2Cx I2C结构体指针,指定要操作的I2C实例  
 * @param NewState 新的状态,ENABLE表示响应应答信号,DISABLE表示不响应  
 */  
void I2C_AcknowledgeConfig(I2C_TypeDef* I2Cx, FunctionalState NewState);  
  
/**  
 * @brief I2C自有地址2配置  
 *  
 * 配置指定的I2C外设的自有地址2。  
 *  
 * @param I2Cx I2C结构体指针,指定要操作的I2C实例  
 * @param Address 新的自有地址2值  
 */  
void I2C_OwnAddress2Config(I2C_TypeDef* I2Cx, uint8_t Address);  
/**  
 * @brief I2C双地址模式命令使能或失能  
 *  
 * 使能或失能指定的I2C外设的双地址模式。  
 *  
 * @param I2Cx I2C结构体指针,指定要操作的I2C实例  
 * @param NewState 新的状态,ENABLE表示使能双地址模式,DISABLE表示失能  
 */  
void I2C_DualAddressCmd(I2C_TypeDef* I2Cx, FunctionalState NewState);  
  
/**  
 * @brief I2C全局调用命令使能或失能  
 *  
 * 使能或失能指定的I2C外设的全局调用功能。  
 *  
 * @param I2Cx I2C结构体指针,指定要操作的I2C实例  
 * @param NewState 新的状态,ENABLE表示使能全局调用功能,DISABLE表示失能  
 */  
void I2C_GeneralCallCmd(I2C_TypeDef* I2Cx, FunctionalState NewState);  
  
/**  
 * @brief I2C中断配置  
 *  
 * 配置指定的I2C外设的中断。  
 *  
 * @param I2Cx I2C结构体指针,指定要操作的I2C实例  
 * @param I2C_IT I2C中断源,指定要配置的中断  
 * @param NewState 新的状态,ENABLE表示使能中断,DISABLE表示失能  
 */  
void I2C_ITConfig(I2C_TypeDef* I2Cx, uint16_t I2C_IT, FunctionalState NewState);  
  
/**  
 * @brief I2C发送数据  
 *  
 * 通过指定的I2C外设发送一个字节的数据。  
 *  
 * @param I2Cx I2C结构体指针,指定要操作的I2C实例  
 * @param Data 要发送的数据  
 */  
void I2C_SendData(I2C_TypeDef* I2Cx, uint8_t Data);  
  
/**  
 * @brief I2C接收数据  
 *  
 * 从指定的I2C外设读取一个字节的数据。  
 *  
 * @param I2Cx I2C结构体指针,指定要操作的I2C实例  
 * @return 接收到的数据  
 */  
uint8_t I2C_ReceiveData(I2C_TypeDef* I2Cx);  
  
/**  
 * @brief I2C发送7位地址  
 *  
 * 通过指定的I2C外设发送一个7位的设备地址,并指定传输方向。  
 *  
 * @param I2Cx I2C结构体指针,指定要操作的I2C实例  
 * @param Address 目标设备的7位地址  
 * @param I2C_Direction 传输方向,通常指定为I2C_Direction_Transmitter或I2C_Direction_Receiver  
 */  
void I2C_Send7bitAddress(I2C_TypeDef* I2Cx, uint8_t Address, uint8_t I2C_Direction);  
  
/**  
 * @brief I2C读取寄存器  
 *  
 * 从指定的I2C外设的寄存器中读取数据。  
 *  
 * 注意:这个函数通常用于特定设备或库扩展,标准的I2C库中可能不包含这个函数。  
 *  
 * @param I2Cx I2C结构体指针,指定要操作的I2C实例  
 * @param I2C_Register 要读取的寄存器地址  
 * @return 读取到的寄存器数据  
 */  
uint16_t I2C_ReadRegister(I2C_TypeDef* I2Cx, uint8_t I2C_Register);  
  
/**  
 * @brief I2C软件复位命令使能或失能  
 *  
 * 使能或失能指定的I2C外设的软件复位功能。  
 *  
 * @param I2Cx I2C结构体指针,指定要操作的I2C实例  
 * @param NewState 新的状态,ENABLE表示使能软件复位,DISABLE表示失能  
 */  
void I2C_SoftwareResetCmd(I2C_TypeDef* I2Cx, FunctionalState NewState);  
  
/**  
 * @brief I2C NACK位置配置  
 *  
 * 配置指定的I2C外设的NACK(Not Acknowledge)响应的位置。  
 *  
 * @param I2Cx I2C结构体指针,指定要操作的I2C实例  
 * @param I2C_NACKPosition NACK位置,通常指定为I2C_NACKPosition_Next或I2C_NACKPosition_Current  
 */  
void I2C_NACKPositionConfig(I2C_TypeDef* I2Cx, uint16_t I2C_NACKPosition);  
/**  
 * @brief I2C NACK位置配置  
 *  
 * 配置指定的I2C外设的NACK(Not Acknowledge)响应的位置。  
 *  
 * @param I2Cx I2C结构体指针,指定要操作的I2C实例  
 * @param I2C_NACKPosition NACK位置配置,例如I2C_NACKPosition_Next或I2C_NACKPosition_Current  
 */  
void I2C_NACKPositionConfig(I2C_TypeDef* I2Cx, uint16_t I2C_NACKPosition);  
  
/**  
 * @brief I2C SMBus警报配置  
 *  
 * 配置指定的I2C外设的SMBus警报模式。  
 *  
 * @param I2Cx I2C结构体指针,指定要操作的I2C实例  
 * @param I2C_SMBusAlert SMBus警报配置  
 */  
void I2C_SMBusAlertConfig(I2C_TypeDef* I2Cx, uint16_t I2C_SMBusAlert);  
  
/**  
 * @brief I2C 传输PEC  
 *  
 * 使能或失能指定的I2C外设的PEC(Packet Error Checking)传输。  
 *  
 * @param I2Cx I2C结构体指针,指定要操作的I2C实例  
 * @param NewState 新的状态,ENABLE表示使能PEC传输,DISABLE表示失能  
 */  
void I2C_TransmitPEC(I2C_TypeDef* I2Cx, FunctionalState NewState);  
  
/**  
 * @brief I2C PEC位置配置  
 *  
 * 配置指定的I2C外设的PEC(Packet Error Checking)位置。  
 *  
 * @param I2Cx I2C结构体指针,指定要操作的I2C实例  
 * @param I2C_PECPosition PEC位置配置  
 */  
void I2C_PECPositionConfig(I2C_TypeDef* I2Cx, uint16_t I2C_PECPosition);  
  
/**  
 * @brief I2C 计算PEC  
 *  
 * 使能或失能指定的I2C外设的PEC(Packet Error Checking)计算功能。  
 *  
 * @param I2Cx I2C结构体指针,指定要操作的I2C实例  
 * @param NewState 新的状态,ENABLE表示使能PEC计算,DISABLE表示失能  
 */  
void I2C_CalculatePEC(I2C_TypeDef* I2Cx, FunctionalState NewState);  
  
/**  
 * @brief I2C 获取PEC  
 *  
 * 读取指定的I2C外设的PEC(Packet Error Checking)计算结果。  
 *  
 * @param I2Cx I2C结构体指针,指定要操作的I2C实例  
 * @return PEC计算结果  
 */  
uint8_t I2C_GetPEC(I2C_TypeDef* I2Cx);  
  
/**  
 * @brief I2C 自动重发命令  
 *  
 * 使能或失能指定的I2C外设的自动重发功能。  
 *  
 * @param I2Cx I2C结构体指针,指定要操作的I2C实例  
 * @param NewState 新的状态,ENABLE表示使能自动重发,DISABLE表示失能  
 */  
void I2C_ARPCmd(I2C_TypeDef* I2Cx, FunctionalState NewState);  
  
/**  
 * @brief I2C 时钟拉伸命令  
 *  
 * 使能或失能指定的I2C外设的时钟拉伸功能。  
 *  
 * @param I2Cx I2C结构体指针,指定要操作的I2C实例  
 * @param NewState 新的状态,ENABLE表示使能时钟拉伸,DISABLE表示失能  
 */  
void I2C_StretchClockCmd(I2C_TypeDef* I2Cx, FunctionalState NewState);  
  
/**  
 * @brief I2C 快速模式占空比配置  
 *  
 * 配置指定的I2C外设在快速模式下的占空比。  
 *  
 * @param I2Cx I2C结构体指针,指定要操作的I2C实例  
 * @param I2C_DutyCycle 占空比配置  
 */  
void I2C_FastModeDutyCycleConfig(I2C_TypeDef* I2Cx, uint16_t I2C_DutyCycle);

I²C(Inter-Integrated Circuit)是一种串行通信协议,通常用于连接微控制器和外部设备,例如传感器、存储器、显示器等。以下是I²C通信的基本原理:

  1. 总线结构
    • I²C通信使用两根线,即串行数据线(SDA)和串行时钟线(SCL)。
    • SDA线用于传输数据,而SCL线用于传输时钟信号。
    • 多个设备可以连接到同一条I²C总线上。
  2. 主从结构
    • I²C通信中的设备分为主设备(Master)和从设备(Slave)。
    • 主设备负责发起通信并控制总线上的数据传输,而从设备则被动地响应主设备的指令。
  3. 起始和停止条件
    • 通信开始时,主设备发送起始条件(Start Condition),它包括将SDA线从高电平切换到低电平后,再将SCL线从高电平切换到低电平。
    • 通信结束时,主设备发送停止条件(Stop Condition),它包括将SDA线从低电平切换到高电平后,再将SCL线从低电平切换到高电平。
    • 在通信过程中,起始和停止条件的发送标志着数据传输的开始和结束。
  4. 数据传输
    • 每个数据传输包含8位数据字节,以及一个读/写位,用于指示数据的方向。
    • 数据传输的开始时,主设备发送一个7位的设备地址,用于指示通信的目标设备。
    • 如果目标设备存在并且可以响应,它会发送应答位(Acknowledge)以示确认。
    • 主设备继续发送或接收数据字节,每个字节后都会有一个应答位。
    • 数据传输完成后,主设备发送停止条件。
  5. 时钟同步
    • 数据传输的时钟由主设备生成和控制,从设备在SCL线上同步主设备的时钟信号以接收或发送数据。
  6. 速率和频率
    • I²C通信的速率可以根据需要进行调整,通常有标准速率和快速速率两种模式,分别为100 kHz和400 kHz。
    • 除此之外,还有更高速率的扩展模式,如快速模式加速(Fast Mode Plus,最高达1 MHz)和超快速模式(High-Speed Mode,最高达3.4 MHz)。
  7. 通讯流程

I²C(Inter-Integrated Circuit)通信流程遵循特定的序列,包括启动、设备地址、数据传输和停止等步骤。以下是典型的I²C通信流程:

  1. 启动条件(Start Condition)
    • 通信开始时,主设备发出启动条件,即在时钟信号(SCL)为高电平时,数据信号(SDA)从高电平切换到低电平。
    • 这个信号告诉所有从设备一个新的通信周期即将开始。
  2. 发送设备地址
    • 主设备发送一个7位的目标设备地址,同时附带读/写位。目标设备地址用于指示要进行通信的从设备。
    • 如果通信是要向设备写入数据,则读/写位为0;如果是要从设备读取数据,则读/写位为1。
    • 在发送地址后,主设备等待从设备的应答。
  3. 应答位(Acknowledge)
    • 从设备在接收到其地址后会发送一个应答位。如果从设备存在且准备好接收数据,则应答位为低电平(0);如果从设备不可用或出现错误,则应答位为高电平(1)。
    • 应答位的发送允许主设备确定通信是否可以继续。
  4. 数据传输
    • 主设备根据通信的类型(读或写)发送或接收数据字节。
    • 每个数据字节都由8位组成,先发送最高位,然后是次高位,依此类推,直到发送最低位。
    • 每发送一个字节后,主设备等待从设备的应答。
  5. 重复启动(Repeated Start)(可选):
    • 如果主设备需要与另一个设备进行通信而不断开总线,则可以发送重复启动条件,而不是发送停止条件。
    • 重复启动条件与启动条件相同,但在通信过程中不会释放总线。
  6. 停止条件(Stop Condition)
    • 通信结束时,主设备发送停止条件。停止条件是在时钟信号为高电平时,数据信号从低电平切换到高电平。
    • 发送停止条件后,所有设备释放总线,并将其状态重置为等待下一个通信周期。

以上是典型的I²C通信流程,通过这些步骤,主设备可以与一个或多个从设备进行可靠的数据交换。
I²C通信协议灵活且简单,适用于连接多种类型的设备,并且由于其使用的线路较少,可以节省系统的硬件成本。
image.png
image.png
image.png
S:起始条件
2:设备地址 + 读写位 1表示读 0表示写
RA:接收从机的应答位,主机释放SDA,SDA应该为高电平,只是从机会拉低SDA,此时SDA如果为高电平代表没有从应答
4:一般为寄存器地址
RA:接收从机的应答位,主机释放SDA,SDA应该为高电平,只是从机会拉低SDA,此时SDA如果为高电平代表没有从应答
6:写入数据
RA:接收从机的应答位,主机释放SDA,SDA应该为高电平,只是从机会拉低SDA,此时SDA如果为高电平代表没有从应答
P:终止条件
image.png
S:起始条件
2:主机发送一个字节:设备地址 + 读写位 1表示读 0表示写
RA:接收从机的应答位,主机释放SDA,SDA应该为高电平,只是从机会拉低SDA,此时SDA如果为高电平代表没有从应答
S:起始条件
4:主机交出SDA控制权,依照当前地址指示的地址读取SDA数据,从机将数据存入SDA,
RA:接收从机的应答位,主机释放SDA,SDA应该为高电平,只是从机会拉低SDA,此时SDA如果为高电平代表没有从应答
P:终止条件
image.png
S:起始条件
2:设备地址 + 读写位 1表示读 0表示写
RA:接收从机的应答位,主机释放SDA,SDA应该为高电平,只是从机会拉低SDA,此时SDA如果为高电平代表没有从应答
4:指定寄存器地址
RA:接收从机的应答位,主机释放SDA,SDA应该为高电平,只是从机会拉低SDA,此时SDA如果为高电平代表没有从应答
SR:另起一个时序,起始条件
6:主机发送一个字节:设备地址 + 读写位 1表示读 0表示写
RA:接收从机的应答位,主机释放SDA,SDA应该为高电平,只是从机会拉低SDA,此时SDA如果为高电平代表没有从应答
S:起始条件
8:主机交出SDA控制权,依照当前地址指示的地址读取SDA数据,从机将数据存入SDA,
RA:接收从机的应答位,主机释放SDA,SDA应该为高电平,只是从机会拉低SDA,此时SDA如果为高电平代表没有从应答
P:终止条件
image.png
一. 软实现

  1. I2C写SCL引脚电平
/**
  * 函    数:I2C写SCL引脚电平
  * 参    数:BitValue 协议层传入的当前需要写入SCL的电平,范围0~1
  * 返 回 值:无
  * 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置SCL为低电平,当BitValue为1时,需要置SCL为高电平
  */
void MyI2C_W_SCL(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOB, GPIO_Pin_10, (BitAction)BitValue);		//根据BitValue,设置SCL引脚的电平
	Delay_us(10);												//延时10us,防止时序频率超过要求
}
  1. I2C写SDA引脚电平

/**
  * 函    数:I2C写SDA引脚电平
  * 参    数:BitValue 协议层传入的当前需要写入SDA的电平,范围0~0xFF
  * 返 回 值:无
  * 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置SDA为低电平,当BitValue非0时,需要置SDA为高电平
  */
void MyI2C_W_SDA(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOB, GPIO_Pin_11, (BitAction)BitValue);		//根据BitValue,设置SDA引脚的电平,BitValue要实现非0即1的特性
	Delay_us(10);												//延时10us,防止时序频率超过要求
}
  1. I2C读SDA引脚电平
/**
  * 函    数:I2C读SDA引脚电平
  * 参    数:无
  * 返 回 值:协议层需要得到的当前SDA的电平,范围0~1
  * 注意事项:此函数需要用户实现内容,当前SDA为低电平时,返回0,当前SDA为高电平时,返回1
  */
uint8_t MyI2C_R_SDA(void)
{
	uint8_t BitValue;
	BitValue = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11);		//读取SDA电平
	Delay_us(10);												//延时10us,防止时序频率超过要求
	return BitValue;											//返回SDA电平
}
  1. I2C初始化
/**
  * 函    数:I2C初始化
  * 参    数:无
  * 返 回 值:无
  * 注意事项:此函数需要用户实现内容,实现SCL和SDA引脚的初始化
  */
void MyI2C_Init(void)
{
	/*开启时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);	//开启GPIOB的时钟
	
	/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);					//将PB10和PB11引脚初始化为开漏输出
	
	/*设置默认电平*/
	GPIO_SetBits(GPIOB, GPIO_Pin_10 | GPIO_Pin_11);			//设置PB10和PB11引脚初始化后默认为高电平(释放总线状态)
}

  1. I2C起始
/**
  * 函    数:I2C起始
  * 参    数:无
  * 返 回 值:无
  */
void MyI2C_Start(void)
{
	MyI2C_W_SDA(1);							//释放SDA,确保SDA为高电平
	MyI2C_W_SCL(1);							//释放SCL,确保SCL为高电平
	MyI2C_W_SDA(0);							//在SCL高电平期间,拉低SDA,产生起始信号
	MyI2C_W_SCL(0);							//起始后把SCL也拉低,即为了占用总线,也为了方便总线时序的拼接
}

  1. I2C终止
/**
  * 函    数:I2C终止
  * 参    数:无
  * 返 回 值:无
  */
void MyI2C_Stop(void)
{
	MyI2C_W_SDA(0);							//拉低SDA,确保SDA为低电平
	MyI2C_W_SCL(1);							//释放SCL,使SCL呈现高电平
	MyI2C_W_SDA(1);							//在SCL高电平期间,释放SDA,产生终止信号
}
  1. I2C发送一个字节
/**
  * 函    数:I2C发送一个字节
  * 参    数:Byte 要发送的一个字节数据,范围:0x00~0xFF
  * 返 回 值:无
  */
void MyI2C_SendByte(uint8_t Byte)
{
	uint8_t i;
	for (i = 0; i < 8; i ++)				//循环8次,主机依次发送数据的每一位
	{
		MyI2C_W_SDA(Byte & (0x80 >> i));	//使用掩码的方式取出Byte的指定一位数据并写入到SDA线
		MyI2C_W_SCL(1);						//释放SCL,从机在SCL高电平期间读取SDA
		MyI2C_W_SCL(0);						//拉低SCL,主机开始发送下一位数据
	}
}
  1. I2C接收一个字节
/**
  * 函    数:I2C接收一个字节
  * 参    数:无
  * 返 回 值:接收到的一个字节数据,范围:0x00~0xFF
  */
uint8_t MyI2C_ReceiveByte(void)
{
	uint8_t i, Byte = 0x00;					//定义接收的数据,并赋初值0x00,此处必须赋初值0x00,后面会用到
	MyI2C_W_SDA(1);							//接收前,主机先确保释放SDA,避免干扰从机的数据发送
	for (i = 0; i < 8; i ++)				//循环8次,主机依次接收数据的每一位
	{
		MyI2C_W_SCL(1);						//释放SCL,主机机在SCL高电平期间读取SDA
		if (MyI2C_R_SDA() == 1){Byte |= (0x80 >> i);}	//读取SDA数据,并存储到Byte变量
														//当SDA为1时,置变量指定位为1,当SDA为0时,不做处理,指定位为默认的初值0
		MyI2C_W_SCL(0);						//拉低SCL,从机在SCL低电平期间写入SDA
	}
	return Byte;							//返回接收到的一个字节数据
}
  1. I2C发送应答位
/**
  * 函    数:I2C发送应答位
  * 参    数:Byte 要发送的应答位,范围:0~1,0表示应答,1表示非应答
  * 返 回 值:无
  */
void MyI2C_SendAck(uint8_t AckBit)
{
	MyI2C_W_SDA(AckBit);					//主机把应答位数据放到SDA线
	MyI2C_W_SCL(1);							//释放SCL,从机在SCL高电平期间,读取应答位
	MyI2C_W_SCL(0);							//拉低SCL,开始下一个时序模块
}

  1. I2C接收应答位
/**
  * 函    数:I2C接收应答位
  * 参    数:无
  * 返 回 值:接收到的应答位,范围:0~1,0表示应答,1表示非应答
  */
uint8_t MyI2C_ReceiveAck(void)
{
	uint8_t AckBit;							//定义应答位变量
	MyI2C_W_SDA(1);							//接收前,主机先确保释放SDA,避免干扰从机的数据发送
	MyI2C_W_SCL(1);							//释放SCL,主机机在SCL高电平期间读取SDA
	AckBit = MyI2C_R_SDA();					//将应答位存储到变量里
	MyI2C_W_SCL(0);							//拉低SCL,开始下一个时序模块
	return AckBit;							//返回定义应答位变量
}
  1. MPU6050写寄存器
/**
  * 函    数:MPU6050写寄存器
  * 参    数:RegAddress 寄存器地址,范围:参考MPU6050手册的寄存器描述
  * 参    数:Data 要写入寄存器的数据,范围:0x00~0xFF
  * 返 回 值:无
  */
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{
	MyI2C_Start();						//I2C起始
	MyI2C_SendByte(MPU6050_ADDRESS);	//发送从机地址,读写位为0,表示即将写入
	MyI2C_ReceiveAck();					//接收应答
	MyI2C_SendByte(RegAddress);			//发送寄存器地址
	MyI2C_ReceiveAck();					//接收应答
	MyI2C_SendByte(Data);				//发送要写入寄存器的数据
	MyI2C_ReceiveAck();					//接收应答
	MyI2C_Stop();						//I2C终止
}
  1. MPU6050读寄存器
/**
  * 函    数:MPU6050读寄存器
  * 参    数:RegAddress 寄存器地址,范围:参考MPU6050手册的寄存器描述
  * 返 回 值:读取寄存器的数据,范围:0x00~0xFF
  */
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
	uint8_t Data;
	
	MyI2C_Start();						//I2C起始
	MyI2C_SendByte(MPU6050_ADDRESS);	//发送从机地址,读写位为0,表示即将写入
	MyI2C_ReceiveAck();					//接收应答
	MyI2C_SendByte(RegAddress);			//发送寄存器地址
	MyI2C_ReceiveAck();					//接收应答
	
	MyI2C_Start();						//I2C重复起始
	MyI2C_SendByte(MPU6050_ADDRESS | 0x01);	//发送从机地址,读写位为1,表示即将读取
	MyI2C_ReceiveAck();					//接收应答
	Data = MyI2C_ReceiveByte();			//接收指定寄存器的数据
	MyI2C_SendAck(1);					//发送应答,给从机非应答,终止从机的数据输出
	MyI2C_Stop();						//I2C终止
	
	return Data;
}
  1. MPU6050初始化
/**
  * 函    数:MPU6050初始化
  * 参    数:无
  * 返 回 值:无
  */
void MPU6050_Init(void)
{
	MyI2C_Init();									//先初始化底层的I2C
	
	/*MPU6050寄存器初始化,需要对照MPU6050手册的寄存器描述配置,此处仅配置了部分重要的寄存器*/
	MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01);		//电源管理寄存器1,取消睡眠模式,选择时钟源为X轴陀螺仪
	MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00);		//电源管理寄存器2,保持默认值0,所有轴均不待机
	MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09);		//采样率分频寄存器,配置采样率
	MPU6050_WriteReg(MPU6050_CONFIG, 0x06);			//配置寄存器,配置DLPF
	MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18);	//陀螺仪配置寄存器,选择满量程为±2000°/s
	MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18);	//加速度计配置寄存器,选择满量程为±16g
}

  1. MPU6050获取ID号
/**
  * 函    数:MPU6050获取ID号
  * 参    数:无
  * 返 回 值:MPU6050的ID号
  */
uint8_t MPU6050_GetID(void)
{
	return MPU6050_ReadReg(MPU6050_WHO_AM_I);		//返回WHO_AM_I寄存器的值
}

  1. MPU6050获取数据
/**
  * 函    数:MPU6050获取数据
  * 参    数:AccX AccY AccZ 加速度计X、Y、Z轴的数据,使用输出参数的形式返回,范围:-32768~32767
  * 参    数:GyroX GyroY GyroZ 陀螺仪X、Y、Z轴的数据,使用输出参数的形式返回,范围:-32768~32767
  * 返 回 值:无
  */
void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ, 
						int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ)
{
	uint8_t DataH, DataL;								//定义数据高8位和低8位的变量
	
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);		//读取加速度计X轴的高8位数据
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);		//读取加速度计X轴的低8位数据
	*AccX = (DataH << 8) | DataL;						//数据拼接,通过输出参数返回
	
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);		//读取加速度计Y轴的高8位数据
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);		//读取加速度计Y轴的低8位数据
	*AccY = (DataH << 8) | DataL;						//数据拼接,通过输出参数返回
	
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);		//读取加速度计Z轴的高8位数据
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);		//读取加速度计Z轴的低8位数据
	*AccZ = (DataH << 8) | DataL;						//数据拼接,通过输出参数返回
	
	DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);		//读取陀螺仪X轴的高8位数据
	DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);		//读取陀螺仪X轴的低8位数据
	*GyroX = (DataH << 8) | DataL;						//数据拼接,通过输出参数返回
	
	DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);		//读取陀螺仪Y轴的高8位数据
	DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);		//读取陀螺仪Y轴的低8位数据
	*GyroY = (DataH << 8) | DataL;						//数据拼接,通过输出参数返回
	
	DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);		//读取陀螺仪Z轴的高8位数据
	DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);		//读取陀螺仪Z轴的低8位数据
	*GyroZ = (DataH << 8) | DataL;						//数据拼接,通过输出参数返回
}

二. 硬件I2C

  1. 开启时钟
/*开启时钟*/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE);		//开启I2C2的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);		//开启GPIOB的时钟
	
  1. GPIO初始化
/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;
 
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);					//将PB10和PB11引脚初始化为复用开漏输出
	
  1. I2C初始化
/*I2C初始化*/
	I2C_InitTypeDef I2C_InitStructure;						//定义结构体变量
	I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;				//模式,选择为I2C模式
	I2C_InitStructure.I2C_ClockSpeed = 50000;				//时钟速度,选择为50KHz
	I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;		//时钟占空比,选择Tlow/Thigh = 2
	I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;				//应答,选择使能
	I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;	//应答地址,选择7位,从机模式下才有效
	I2C_InitStructure.I2C_OwnAddress1 = 0x00;				//自身地址,从机模式下才有效
	I2C_Init(I2C2, &I2C_InitStructure);						//将结构体变量交给I2C_Init,配置I2C2
	
typedef struct  
{  
  // 指定I2C的时钟频率  
  // 此参数必须设置为低于400kHz的值  
  uint32_t I2C_ClockSpeed;          /*!< Specifies the clock frequency.  
                                         This parameter must be set to a value lower than 400kHz */  
  
  // 指定I2C的工作模式  
  // 此参数可以是@ref I2C_mode中定义的值  
  uint16_t I2C_Mode;                /*!< Specifies the I2C mode.  
                                         This parameter can be a value of @ref I2C_mode */  
  
  // 指定I2C快速模式的占空比  
  // 此参数可以是@ref I2C_duty_cycle_in_fast_mode中定义的值  
  uint16_t I2C_DutyCycle;           /*!< Specifies the I2C fast mode duty cycle.  
                                         This parameter can be a value of @ref I2C_duty_cycle_in_fast_mode */  
  
  // 指定第一个设备的自有地址  
  // 此参数可以是7位或10位地址  
  uint16_t I2C_OwnAddress1;         /*!< Specifies the first device own address.  
                                         This parameter can be a 7-bit or 10-bit address. */  
  
  // 启用或禁用应答  
  // 此参数可以是@ref I2C_acknowledgement中定义的值  
  uint16_t I2C_Ack;                 /*!< Enables or disables the acknowledgement.  
                                         This parameter can be a value of @ref I2C_acknowledgement */  
  
  // 指定是否应答7位或10位地址  
  // 此参数可以是@ref I2C_acknowledged_address中定义的值  
  uint16_t I2C_AcknowledgedAddress; /*!< Specifies if 7-bit or 10-bit address is acknowledged.  
                                         This parameter can be a value of @ref I2C_acknowledged_address */  
} I2C_InitTypeDef;
/* 定义I2C的标准模式,即普通的I2C通信模式 */  
#define I2C_Mode_I2C                    ((uint16_t)0x0000)  
  
/* 定义SMBus(System Management Bus,系统管理总线)设备模式  
 * SMBus 是一种基于I2C的简化总线,通常用于系统内部的电源管理和设备监控  
 * 这里的值可能是特定硬件或库实现所定义的  
 */  
#define I2C_Mode_SMBusDevice            ((uint16_t)0x0002)    
  
/* 定义SMBus主机模式  
 * SMBus主机可以发起与SMBus设备之间的通信  
 * 这里的值也是特定硬件或库实现所定义的  
 */  
#define I2C_Mode_SMBusHost              ((uint16_t)0x000A)
/* 定义I2C快速模式的占空比,Tlow/Thigh = 16/9  
 * 这意味着在低电平(SCL = 0)时的时间长度与在高电平(SCL = 1)时的时间长度的比率为16:9  
 */  
#define I2C_DutyCycle_16_9              ((uint16_t)0x4000)   
  
/* 定义I2C快速模式的占空比,Tlow/Thigh = 2  
 * 这意味着在低电平(SCL = 0)时的时间长度与在高电平(SCL = 1)时的时间长度的比率为2:1  
 * 注意:这里的值0xBFFF可能是一个位掩码或特定于某个硬件或库的表示,因为通常的比率值不会直接用16位整数的形式来表示  
 * 在实际应用中,这个值可能会被用来配置SCL线的时钟占空比  
 */  
#define I2C_DutyCycle_2                 ((uint16_t)0xBFFF)
/* 定义I2C通信中确认的7位设备地址的位掩码  
 * 在I2C通信中,设备地址通常通过起始条件(START condition)后跟一个或多个字节来发送  
 * 对于7位地址,最高位(MSB)是读/写位,接下来的7位是设备地址  
 * 这个宏通常用于检查或设置I2C通信的某个状态或寄存器,以表示接收到了一个有效的7位设备地址的响应(ACK)  
 */  
#define I2C_AcknowledgedAddress_7bit    ((uint16_t)0x4000)  
  
/* 定义I2C通信中确认的10位设备地址的位掩码  
 * 10位地址模式在I2C通信中不太常见,但在某些应用中可能需要支持  
 * 10位地址模式使用两个字节来发送设备地址:第一个字节的高7位是地址的高位部分,最低位是地址模式位(设置为1表示使用10位地址)  
 * 第二个字节包含地址的低10位中的剩余部分  
 * 这个宏通常用于检查或设置I2C通信的某个状态或寄存器,以表示接收到了一个有效的10位设备地址的响应(ACK)  
 */  
#define I2C_AcknowledgedAddress_10bit   ((uint16_t)0xC000)
  1. I2C使能
/*I2C使能*/
	I2C_Cmd(I2C2, ENABLE);									//使能I2C2,开始运行
	
  1. MPU6050等待事件
void MPU6050_WaitEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)
{
	uint32_t Timeout;
	Timeout = 10000;									//给定超时计数时间
	while (I2C_CheckEvent(I2Cx, I2C_EVENT) != SUCCESS)	//循环等待指定事件
	{
		Timeout --;										//等待时,计数值自减
		if (Timeout == 0)								//自减到0后,等待超时
		{
			/*超时的错误处理代码,可以添加到此处*/
			break;										//跳出等待,不等了
		}
	}
}
  1. MPU6050写寄存器
/**
  * 函    数:MPU6050写寄存器
  * 参    数:RegAddress 寄存器地址,范围:参考MPU6050手册的寄存器描述
  * 参    数:Data 要写入寄存器的数据,范围:0x00~0xFF
  * 返 回 值:无
  */
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{
	I2C_GenerateSTART(I2C2, ENABLE);										//硬件I2C生成起始条件
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);					//等待EV5
	
	I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter);	//硬件I2C发送从机地址,方向为发送
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);	//等待EV6
	
	I2C_SendData(I2C2, RegAddress);											//硬件I2C发送寄存器地址
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTING);			//等待EV8
	
	I2C_SendData(I2C2, Data);												//硬件I2C发送数据
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED);				//等待EV8_2
	
	I2C_GenerateSTOP(I2C2, ENABLE);											//硬件I2C生成终止条件
}

  1. MPU6050读寄存器
/**
  * 函    数:MPU6050读寄存器
  * 参    数:RegAddress 寄存器地址,范围:参考MPU6050手册的寄存器描述
  * 返 回 值:读取寄存器的数据,范围:0x00~0xFF
  */
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
	uint8_t Data;
	
	I2C_GenerateSTART(I2C2, ENABLE);										//硬件I2C生成起始条件
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);					//等待EV5
	
	I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter);	//硬件I2C发送从机地址,方向为发送
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);	//等待EV6
	
	I2C_SendData(I2C2, RegAddress);											//硬件I2C发送寄存器地址
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED);				//等待EV8_2
	
	I2C_GenerateSTART(I2C2, ENABLE);										//硬件I2C生成重复起始条件
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);					//等待EV5
	
	I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Receiver);		//硬件I2C发送从机地址,方向为接收
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED);		//等待EV6
	
	I2C_AcknowledgeConfig(I2C2, DISABLE);									//在接收最后一个字节之前提前将应答失能
	I2C_GenerateSTOP(I2C2, ENABLE);											//在接收最后一个字节之前提前申请停止条件
	
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_RECEIVED);				//等待EV7
	Data = I2C_ReceiveData(I2C2);											//接收数据寄存器
	
	I2C_AcknowledgeConfig(I2C2, ENABLE);									//将应答恢复为使能,为了不影响后续可能产生的读取多字节操作
	
	return Data;
}

/* 定义了主模式被选中的事件,包括BUSY、MSL和SB标志 */  
#define  I2C_EVENT_MASTER_MODE_SELECT                      ((uint32_t)0x00030001)  /* BUSY, MSL and SB flag */  
  
/* --EV6 事件定义 --*/  
  
/* 定义了主发送模式被选中的事件,包括BUSY、MSL、ADDR、TXE和TRA标志 */  
#define  I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED        ((uint32_t)0x00070082)  /* BUSY, MSL, ADDR, TXE and TRA flags */  
  
/* 定义了主接收模式被选中的事件,包括BUSY、MSL和ADDR标志 */  
#define  I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED           ((uint32_t)0x00030002)  /* BUSY, MSL and ADDR flags */  
  
/* --EV9 事件定义 --*/  
  
/* 定义了使用10位地址的主模式事件,包括BUSY、MSL和ADD10标志 */  
#define  I2C_EVENT_MASTER_MODE_ADDRESS10                   ((uint32_t)0x00030008)  /* BUSY, MSL and ADD10 flags */  
  
  
/* 主接收模式相关的事件定义 -----------------------------*/   
  
/* --EV7 事件定义 --*/  
  
/* 定义了主模式接收到一个字节的事件,包括BUSY、MSL和RXNE标志 */  
#define  I2C_EVENT_MASTER_BYTE_RECEIVED                    ((uint32_t)0x00030040)  /* BUSY, MSL and RXNE flags */  
  
  
/* 主发送模式相关的事件定义 --------------------------*/  
  
/* --EV8 事件定义 --*/  
  
/* 定义了主模式正在发送一个字节的事件,包括TRA、BUSY、MSL和TXE标志 */  
#define I2C_EVENT_MASTER_BYTE_TRANSMITTING                 ((uint32_t)0x00070080) /* TRA, BUSY, MSL, TXE flags */  
  
/* --EV8_2 事件定义 --*/  
  
/* 定义了主模式发送一个字节完成的事件,包括TRA、BUSY、MSL、TXE和BTF标志 */  
#define  I2C_EVENT_MASTER_BYTE_TRANSMITTED                 ((uint32_t)0x00070084)  /* TRA, BUSY, MSL, TXE and BTF flags */
  • 16
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值