STM32学习笔记14-I2C硬件控制

发布于:2025-08-19 ⋅ 阅读:(18) ⋅ 点赞:(0)

I2C外设简介


  • STM32内部集成了硬件I2C收发电路(硬件收发器:自动生产波形,自动翻转电平等),可以由硬件自动执行时钟生成、起始终止条件生成、应答位收发、数据收发等功能,减轻CPU的负担——软件只需要写入控制寄存器CR和DR,还有实时监控时序状态的状态寄存器SR
  • 支持多主机模型
  • 支持7位/10位地址模式
  • 支持不同的通讯速度,标准速度(高达100 kHz),快速(高达400 kHz)
  • 支持DMA
  • 兼容SMBus协议
  • STM32F103C8T6 硬件I2C资源:I2C1、I2C2

补:多主机模型:固定多主机(有固定的主机数和固定的从机数)和可变多主机(任何设备,都可以从空闲的状态跳出来作为主机,然后指定通信,之后又跳回来),采用10位地址的时序:起始条件后的两个字节都是寻址,其中前一个字节,是帧头:内容是5位的标志位11110+2位地址+1位读写位,后一个字节:纯粹的8位地址。

I2C框图

引脚对应关系

I2C基本结构(一主多从)

硬件I2C的操作流程:

主机发送

主机接收

软件/硬件波形对比

手册——对应24章

接线图

10-2 硬件I2C读写MPU6050


  1. 开启I2C外设和对应GPIO口的时钟
  2. 把I2C外设对应的GPIO口初始化为复用开漏模式
  3. 使用结构体,对整个I2C进行配置
  4. I2C_Cmd,使能I2C

相关函数:

void I2C_DeInit(I2C_TypeDef* I2Cx);

void I2C_Init(I2C_TypeDef* I2Cx, I2C_InitTypeDef* I2C_InitStruct);

void I2C_StructInit(I2C_InitTypeDef* I2C_InitStruct);

void I2C_Cmd(I2C_TypeDef* I2Cx, FunctionalState NewState);

void I2C_GenerateSTART(I2C_TypeDef* I2Cx, FunctionalState NewState);  //生产起始条件

void I2C_GenerateSTOP(I2C_TypeDef* I2Cx, FunctionalState NewState);  //生产结束条件

void I2C_AcknowledgeConfig(I2C_TypeDef* I2Cx, FunctionalState NewState);  //配置应答ACK

void I2C_SendData(I2C_TypeDef* I2Cx, uint8_t Data);  //数据写入DR寄存器

uint8_t I2C_ReceiveData(I2C_TypeDef* I2Cx);  //读取DR的数值

void I2C_Send7bitAddress(I2C_TypeDef* I2Cx, uint8_t Address, uint8_t I2C_Direction);  //发送7位地址的专用函数

//EV——多种监控

I2C_CheckEvent()  //基本

I2C_GetLastEvent()  //高级

I2C_GetFlagStatus()   //基于状态位的标准监控

void I2C_ClearFlag(I2C_TypeDef* I2Cx, uint32_t I2C_FLAG);

ITStatus I2C_GetITStatus(I2C_TypeDef* I2Cx, uint32_t I2C_IT);

void I2C_ClearITPendingBit(I2C_TypeDef* I2Cx, uint32_t I2C_IT);

MPU6050.c

//27硬件I2C读写MPU6050
//与软件的区别就是MyI2C.c这个文件,硬件是不需要的
//意思是:底层的逻辑会有不同,其他是一样的
#include "stm32f10x.h"                  // Device header


#define MPU_ADD   0xD0
#include "MPU_Reg.h"
//封装指定地址写和指定地址读的时序
//优化:在代码中,存在很多死循环的地方——超时退出

void CheckEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT){
	uint32_t TimeOut;
	TimeOut=100;	
	while(I2C_CheckEvent(I2Cx, I2C_EVENT)!=SUCCESS){
			TimeOut--;
			if(TimeOut==0){
				break;//错误处理
			}
		}
}

void MPU_WriteReg(uint8_t RegAddress,uint8_t Data){
	//用此函数,则会一直传输数据,所以我们需要用标志位去确定它是否操作成功了,这里就要用EV5事件来确定
	I2C_GenerateSTART(I2C2, ENABLE);
	
	//while(I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT)!=SUCCESS);
	
	//封装
//		while(I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT)!=SUCCESS){
//			TimeOut--;
//			if(TimeOut==0){
//				return;
//			}
//		}
	
	CheckEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);
	
	
	//发送函数会自带应答位,所以我们不需要考虑应答,只需要考虑发送后的事件EV6即可
	I2C_Send7bitAddress(I2C2,MPU_ADD, I2C_Direction_Transmitter); 
	CheckEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);
	//EV8_1的事件是提醒应该写入DR数据,不需要等待
	
	I2C_SendData(I2C2, RegAddress); //同理,等待对应的事件EV8
	CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTING); 
	
	I2C_SendData(I2C2, Data);//当发送为最后一个数据时,就需要等待EV8_2事件
	CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED);
	
	I2C_GenerateSTOP(I2C2, ENABLE); 

}

uint8_t MPU_ReadingReg(uint8_t RegAddress){
	uint8_t Data;
	I2C_GenerateSTART(I2C2, ENABLE);
	CheckEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT); 
	
	//发送函数会自带应答位,所以我们不需要考虑应答,只需要考虑发送后的事件EV6即可
	I2C_Send7bitAddress(I2C2,MPU_ADD, I2C_Direction_Transmitter); 
	CheckEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED); 
	//EV8_1的事件是提醒应该写入DR数据,不需要等待
	
	I2C_SendData(I2C2, RegAddress); //在最后一个数据,用EV8_2
	while(I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED)!=SUCCESS); 
	
	I2C_GenerateSTART(I2C2, ENABLE);
	while(I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT)!=SUCCESS); 
	//主机接收
	I2C_Send7bitAddress(I2C2,MPU_ADD, I2C_Direction_Receiver); 
	while(I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)!=SUCCESS); 
	//接收从机的数据:规定:在接收数据之前,需要把ACK置0,同时设置停止位STOP
	//如果读取多个字节,那直接等待EV7事件,读取DR,就能收到数据,在接收最后一个字节之前EV7_1事件,需要把ACK置0,同时设置停止位STOP
	//如果读取一个字节,那在EV6事件之后,需要把ACK置0,同时设置停止位STOP,在等待EV7事件,不然会多一个字节
	I2C_AcknowledgeConfig(I2C2, DISABLE);
	I2C_GenerateSTOP(I2C2,ENABLE);
	while(I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_RECEIVED)!=SUCCESS); 
	
	//读取DR
	Data=I2C_ReceiveData(I2C2); 
	I2C_AcknowledgeConfig(I2C2, ENABLE);  //应答值设为1,给从机应答,这样可以使指定地址收多个字节
	
	return Data;
	
}



void MPU6050_Init(void){
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10|GPIO_Pin_11;
 	GPIO_Init(GPIOB, &GPIO_InitStructure);
	I2C_InitTypeDef I2C_InitStructure;
	I2C_InitStructure.I2C_Mode =I2C_Mode_I2C;  //I2C的模式
	I2C_InitStructure.I2C_ClockSpeed=50000;  //时钟频率,要低于400KHz
	I2C_InitStructure.I2C_DutyCycle=I2C_DutyCycle_2;//时钟占空比,只有在时钟频率大于100KHz才有用,小于则固定的1:1;——能更快的传输
	I2C_InitStructure.I2C_Ack=I2C_Ack_Enable;  //应答配置
	I2C_InitStructure.I2C_AcknowledgedAddress=I2C_AcknowledgedAddress_7bit;  //STM做从机,可以被响应几位地址
	I2C_InitStructure.I2C_OwnAddress1=0x00;  //自身寄存器,当STM32做从机时,指定STM32的自身地址,方便主机呼叫
	I2C_Init(I2C2,&I2C_InitStructure);
	
	I2C_Cmd(I2C2,ENABLE);
	
	MPU_WriteReg(MPU6050_PWR_MGMT_1,0x01);  //解除睡眠,选择陀螺仪时钟
	MPU_WriteReg(MPU6050_PWR_MGMT_2,0x00);	//6个轴均不待机
	MPU_WriteReg(MPU6050_SMPLRT_DIV,0x09);	//采样分频为10
	MPU_WriteReg(MPU6050_CONFIG,0x06);	//滤波参数最大
	MPU_WriteReg(MPU6050_GYRO_CONFIG,0x18);	//陀螺仪和加速度选择最大
	MPU_WriteReg(MPU6050_ACCEL_CONFIG,0x18);
//	//此时的MPU就在进行大量的数据转换,数据存放在其他的寄存器里
}

void MPU_Getdata(int16_t *AccX, int16_t *AccY, int16_t *AccZ, 
      						int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ)  
{
	//读取加速度寄存器XYZ轴的高8位和低8位
	uint8_t DataH, DataL;								//定义数据高8位和低8位的变量
	
	DataH = MPU_ReadingReg(MPU6050_ACCEL_XOUT_H);		//读取加速度计X轴的高8位数据
	DataL = MPU_ReadingReg(MPU6050_ACCEL_XOUT_L);		//读取加速度计X轴的低8位数据
	*AccX = (DataH << 8) | DataL;						//数据拼接,通过输出参数返回
	
	DataH = MPU_ReadingReg(MPU6050_ACCEL_YOUT_H);		//读取加速度计Y轴的高8位数据
	DataL = MPU_ReadingReg(MPU6050_ACCEL_YOUT_L);		//读取加速度计Y轴的低8位数据
	*AccY = (DataH << 8) | DataL;						//数据拼接,通过输出参数返回
	
	DataH = MPU_ReadingReg(MPU6050_ACCEL_ZOUT_H);		//读取加速度计Z轴的高8位数据
	DataL = MPU_ReadingReg(MPU6050_ACCEL_ZOUT_L);		//读取加速度计Z轴的低8位数据
	*AccZ = (DataH << 8) | DataL;						//数据拼接,通过输出参数返回
	
	DataH = MPU_ReadingReg(MPU6050_GYRO_XOUT_H);		//读取陀螺仪X轴的高8位数据
	DataL = MPU_ReadingReg(MPU6050_GYRO_XOUT_L);		//读取陀螺仪X轴的低8位数据
	*GyroX = (DataH << 8) | DataL;						//数据拼接,通过输出参数返回
	
	DataH = MPU_ReadingReg(MPU6050_GYRO_YOUT_H);		//读取陀螺仪Y轴的高8位数据
	DataL = MPU_ReadingReg(MPU6050_GYRO_YOUT_L);		//读取陀螺仪Y轴的低8位数据
	*GyroY = (DataH << 8) | DataL;						//数据拼接,通过输出参数返回
	
	DataH = MPU_ReadingReg(MPU6050_GYRO_ZOUT_H);		//读取陀螺仪Z轴的高8位数据
	DataL = MPU_ReadingReg(MPU6050_GYRO_ZOUT_L);		//读取陀螺仪Z轴的低8位数据
	*GyroZ = (DataH << 8) | DataL;						//数据拼接,通过输出参数返回

	
}