stm32IIC学习笔记

IIC硬件电路

所有I2C设备的SCL连在一起,SDA连在一起
设备的SCL和SDA均要配置成开漏输出模式
SCL和SDA各添加一个上拉电阻,阻值一般为4.7KΩ左右

  • 在IIC通信过程中设备的SCL和SDA均要配置成开漏输出模式 作用:开漏输出只能输出低电平。

  • SCL和SDA各添加一个上拉电阻 作用:在开漏输出开关断开时,会处于浮空模式,上拉电阻会把浮空模式下的电平拉高,都是高电平对通信没有影响。
    如果设备想要相互通信,发送方就可输出低电平,接收方默认高电平就可以接收。

IIC时序基本单元

起始单元

起始条件:SCL高电平期间,SDA从高电平切换到低电平
终止条件:SCL高电平期间,SDA从低电平切换到高电平

  • SCL迟到早退
发送单元

发送一个字节:SCL低电平期间,主机将数据位依次放到SDA线上(高位先行),然后释放SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节

接收单元

接收一个字节:SCL低电平期间,从机将数据位依次放到SDA线上(高位先行),然后释放SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间从机在SDA上有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA)

应答单元

发送应答:主机在接收完从机发送的一个字节之后,在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答(如果发送的是0表示主机接收到数据,从机继续发送。如果发送数据1表示主机没有应答,可能是主机没有收到数据,或者是主机不想让从机继续发送数据)

接收应答:主机在发送完一个字节之后,在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA,接着从机接管SDA,如果从机在SDA发送的是0,表示从机接收数据成功,如果从机在SDA发送的是1,表示未接受成功)

指定地址写数据

  • 对于指定设备(Slave Address),在指定地址(Reg Address)下,写入指定数据(Data)。
  • 首字节第八位R/W位:0是主机向从机写数据,1是主机向从机读数据。

当前地址读数据

对于指定设备(Slave Address),在当前地址指针指示的地址下,读取从机数据(Data)

指定地址读数据

  • 首先写入一个寄存器地址,写入的该寄存器地址会存储在地址指针里,再进行起始开始读,就可以读取到上一步地址指针写入的地址,从而读取该设备寄存器上的地址。
  • 对于指定设备(Slave Address),在指定地址(Reg Address)下,读取从机数据(Data)

I2C初始化函数

/*封装函数SCL把引脚置为高低电平*/
void MyI2C_W_SCL(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOB,GPIO_Pin_10,(BitAction)BitValue);
	Delay_us(10);
}
/*封装函数SDA把引脚置为高低电平*/
void MyI2C_W_SDA(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOB,GPIO_Pin_11,(BitAction)BitValue);
	Delay_us(10);
}

/*读取SDA的高低电平*/
uint8_t MyI2C_R_SDA()
{
	uint8_t BitVlalue;
	BitVlalue = GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11);
	Delay_us(10);
	return BitVlalue;
}

void MyI2C_Init()
{
		RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;//PB10PB11都配置成开漏输出的模式
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	GPIO_SetBits(GPIOB, GPIO_Pin_10 | GPIO_Pin_11);

}

/*起始条件*/
void MyI2C_Start()
{
	MyI2C_W_SDA(1);
	MyI2C_W_SCL(1);
	MyI2C_W_SDA(0);
	MyI2C_W_SCL(0);
}
/*终止条件*/
void MyI2C_Stop()
{
	MyI2C_W_SDA(0);
	MyI2C_W_SCL(1);
	MyI2C_W_SDA(1);
	
}

/*写入一个字节数据*/

void MyI2C_SendByte(uint8_t Byte)
{
	uint16_t i;
	for(i = 0;i < 8;i++)
	{
		MyI2C_W_SDA(Byte &  (0x80 >> i));
		MyI2C_W_SCL(1);//拉高时钟线从机读取数据
		MyI2C_W_SCL(0);//再拉低数据
	}
}

/*接收一个字节数据*/
uint8_t MyI2C_ReceiveByte()
{
	uint16_t i;
	uint8_t Byte = 0x00;
	for(i = 0;i < 8;i ++)
	{
		MyI2C_W_SDA(1);//主机释放SDA,从机把数据写到SDA上
		MyI2C_W_SCL(1);//SCL高电平,主机读取上一步从机发送的数据
		if(MyI2C_R_SDA() == 1)
		{
			Byte |= (0x80>>i);
		}
		MyI2C_W_SCL(0);//SCL时钟拉低,让从机接着发数据
	}

	return Byte;
	
}


/*发送ACK*/
void MyI2C_SendACK(uint8_t ACK)
{
	uint16_t i;

		MyI2C_W_SDA(ACK);
		MyI2C_W_SCL(1);//拉高时钟线从机读取数据
		MyI2C_W_SCL(0);//再拉低数据
}

/*接收ACK*/
uint8_t MyI2C_ReceiveACK()
{
	  uint8_t ACK;
	  MyI2C_W_SDA(1);//主机释放SDA,从机把数据写到SDA上
		MyI2C_W_SCL(1);//SCL高电平,主机读取上一步从机发送的数据
		ACK = MyI2C_R_SDA();//接收的应答位赋值给ACK
		MyI2C_W_SCL(0);//SCL时钟拉低,进入下个时序单元
	  
	  return ACK;
	
}



/*主函数*/
/*可在MPU6050 AD0接高电平用于更改地址名(将原来的0xD0改为0XD2)*/
int main(void)
{
	MyI2C_Init();
	MyI2C_Start();
	MyI2C_SendByte(0xD0);//发送一个地址
	uint16_t ACK = MyI2C_ReceiveACK();//查看该从机是否接收到了该地址,如果接收到了应答位为0,没接收到应答位为1
	MyI2C_Stop();
	Serial_Init();
	Serial_SendByte(ACK);
	while (1)
	{
		
	}
}

MPU6050+I2C通信

#define MPU5050_ADDRESS    0XD2


/*指定地址写寄存器         寄存器地址   写入的数据*/
void MPU6050_Write(uint8_t ReAddress, uint8_t Data)
{
	MyI2C_Start();
	MyI2C_SendByte(MPU5050_ADDRESS);//写入从机地址
	MyI2C_ReceiveACK();
	MyI2C_SendByte(ReAddress);	//写入寄存器地址
	MyI2C_ReceiveACK();
	MyI2C_SendByte(Data);//写入指定寄存器下的数据
	MyI2C_ReceiveACK();
	MyI2C_Stop();
	
}

/*读取指定地址寄存器的数据*/
uint8_t MPU6050_Read(uint8_t ReAddress)
{
	uint8_t Data;
	MyI2C_Start();
	MyI2C_SendByte(MPU5050_ADDRESS );//写入从机地址
	MyI2C_ReceiveACK();
	MyI2C_SendByte(ReAddress);	//写入寄存器地址
	MyI2C_ReceiveACK();
	
	MyI2C_Start();
	MyI2C_SendByte(MPU5050_ADDRESS | 0x01);//写入从机地址并把第八位置1,改为读数据
	MyI2C_ReceiveACK();
	Data = MyI2C_ReceiveByte();//读取一个字节数据
	MyI2C_SendACK(1);//应答位为1停止读数据
	MyI2C_Stop();
	
	return Data;
}


void MPU6050_Init()
{
	MyI2C_Init();
	MPU6050_Write(MPU6050_PWR_MGMT_1,0x01);//接触睡眠,选择陀螺仪时钟
	MPU6050_Write(MPU6050_PWR_MGMT_2,0x00);//六个轴不待机
	MPU6050_Write(MPU6050_SMPLRT_DIV,0x09);//采样分频为10
	MPU6050_Write(MPU6050_CONFIG,0x06);//滤波参数给最大
	MPU6050_Write(MPU6050_GYRO_CONFIG,0x18);//陀螺仪选择最大量程
	MPU6050_Write(MPU6050_ACCEL_CONFIG,0x18);	//加速度计选择最大量程
}
/*通过指针把主函数变量的地址传递到子函数内,从而把子函数想要的值传递进来,调用该函数就把子函数的值输出返回*/
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;
	DataH = MPU6050_Read(MPU6050_ACCEL_XOUT_H);//读取加速度寄存器X轴高八位的值
	DataL = MPU6050_Read(MPU6050_ACCEL_XOUT_L);//读取加速度寄存器X轴低八位的值
	*Accx = (DataH<<8) | DataL;  //将读取加速度寄存器X轴16位的值赋值给输出参数*Accx
	
	DataH = MPU6050_Read(MPU6050_ACCEL_YOUT_H);
	DataL = MPU6050_Read(MPU6050_ACCEL_YOUT_L);
	*Accy = (DataH<<8) | DataL;
	
	DataH = MPU6050_Read(MPU6050_ACCEL_ZOUT_H);
	DataL = MPU6050_Read(MPU6050_ACCEL_ZOUT_L);
	*Accz = (DataH<<8) | DataL;
	
	
	DataH = MPU6050_Read(MPU6050_GYRO_XOUT_H);//读取陀螺仪寄存器X轴高八位的值
	DataL = MPU6050_Read(MPU6050_GYRO_XOUT_L);//读取陀螺仪寄存器X轴低八位的值
	*Gyrox = (DataH<<8) | DataL; //将读取陀螺仪寄存器X轴16位的值赋值给输出参数*Gyrox
	
	DataH = MPU6050_Read(MPU6050_GYRO_YOUT_H);
	DataL = MPU6050_Read(MPU6050_GYRO_YOUT_L);
	*Gyroy = (DataH<<8) | DataL;
	
	DataH = MPU6050_Read(MPU6050_GYRO_ZOUT_H);
	DataL = MPU6050_Read(MPU6050_GYRO_ZOUT_L);
	*Gyroz = (DataH<<8) | DataL;
	
	
}

/*主函数功能实现*/
int16_t AX,AY,AZ,GX,GY,GZ;//这六个值用来接收XYZ轴的加速度值和陀螺仪值
int main(void)
{
	
	MPU6050_Init();

	while (1)
	{
		/*把6个子函数变量的值地址*Accx,*Accy,*Accz,*Gyrox,*Gyroy,*Gyroz传递给AX,AY,AZ,GX,GY,GZ */
		MPU6050_GetData(&AX,&AY,&AZ,&GX,&GY,&GZ);
		Serial_Init();
		Serial_SendNumber(AX,5);
		Serial_SendString("\r\n");
		Serial_SendNumber(AY,5);
		Serial_SendString("\r\n");
		Serial_SendNumber(AZ,5);
		Serial_SendString("\r\n");
		Serial_SendNumber(GX,5);
		Serial_SendString("\r\n");
		Serial_SendNumber(GY,5);
		Serial_SendString("\r\n");
		Serial_SendNumber(GZ,5);
		Serial_SendString("\r\n");
		Serial_SendString("\r\n");
		Serial_SendString("\r\n");
		Delay_ms(1000);
	}
}

硬件实现I2C协议

由STM32的IIC片上外设专门负责实现IIC通讯协议,只要配置好该外设,它就会自动根据协议要求产生通讯信号,收发数据并缓存起来,CPU只要检测该外设的状态和访问数据寄存器,就能完成数据收发。这种由硬件外设处理IIC协议的方式减轻了CPU的工作,且使软件设计更加简单。

STM32的IIC外设可用作通讯的主机及从机,支持100Kbit/s和400Kbits/s的速率,支持7位、10位设备地址,支持DMA数据传输,并具有数据校验功能。

I2C基本结构

主机发数据从GPIO引脚写入到数据寄存器DR,从而转移到移位寄存器,移位寄存器空闲时数据寄存器才能把数据转移到移位寄存器。

主机接收数据时,从机把数据写入到数据寄存器,然后转移到移位寄存器,接着主机接受移位寄存器的值,每接收一位数据,移位寄存器就空闲,紧接着从机就发送到移位寄存器上,依次循环,直到数据接收完成。

硬件I2C读写MPU6050函数

#define MPU6050_ADDRESS		0xD2

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)
		{
			break;
		}
	}
}

void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{
	I2C_GenerateSTART(I2C2, ENABLE);//生成起始条件
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);//监测EV5事件:EV5事件是主机模式已选择事件
	
	I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter);//发送一个地址,并把最低位设置成写模式
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);//监测EV6事件:EV6事件是发送模式已选择事件
	
	I2C_SendData(I2C2, RegAddress);//发送寄存器地址
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTING);//EV8事件:往IIC的数据寄存器DR写入要发送的数据
	
	I2C_SendData(I2C2, Data);//发送字节数据
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED);//监测EV8_2事件:EV8_2事件是数据发送完毕
	
	I2C_GenerateSTOP(I2C2, ENABLE);//终止时序
}
/*读取指定地址寄存器的数据*/
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
	uint8_t Data;
	
I2C_GenerateSTART(I2C2, ENABLE);//生成起始条件
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);//监测EV5事件:EV5事件是主机模式已选择事件
	
	I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter);//发送一个地址,并把最低位设置成写模式
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);//监测EV6事件:EV6事件是发送模式已选择事件
	
	I2C_SendData(I2C2, RegAddress);//发送寄存器地址
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED);//监测EV8_2事件:EV8_2事件是数据发送完毕

	I2C_GenerateSTART(I2C2, ENABLE);//接着产生起始时序
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);
	
	I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Receiver);//发送一个地址,并把最低位设置成读模式
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED);//监测EV6事件:这个EV6事件是接收模式已选择事件
	
	I2C_AcknowledgeConfig(I2C2, DISABLE);//应答位非应答:在接受最后一位数据之前就要提前设置ACK位和STOP位
	I2C_GenerateSTOP(I2C2, ENABLE);//申请产生终止条件
	
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_RECEIVED);//监测EV7事件:EV7事件产生后一个字节的数据就已经存到DR寄存器里了,此刻使用DR读取函数就能把这个数据读取出来了
	Data = I2C_ReceiveData(I2C2);//接收的数据
	
	I2C_AcknowledgeConfig(I2C2, ENABLE);//ACK位置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_Pin = GPIO_Pin_10 | GPIO_Pin_11;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	I2C_InitTypeDef I2C_InitStructure;
	I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;//模式选择I2C模式
	I2C_InitStructure.I2C_ClockSpeed = 50000;//标准速度(高达100 kHz),快速(高达400 kHz)
	I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;//标准速度下默认占空比是1:1
  I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;//默认应答位为0
	I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;//这个是指定STM32作为从机,可以响应几位的地址
	I2C_InitStructure.I2C_OwnAddress1 = 0x00;//用于指定从机STM32的自身地址,方便主机呼叫它

	I2C_Init(I2C2, &I2C_InitStructure);
	
	I2C_Cmd(I2C2, ENABLE);
	
	MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01);
	MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00);
	MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09);
	MPU6050_WriteReg(MPU6050_CONFIG, 0x06);
	MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18);
	MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18);
}
/*通过指针把主函数变量的地址传递到子函数内,从而把子函数想要的值传递进来,调用该函数就把子函数的值输出返回*/
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;
	DataH = MPU6050_Read(MPU6050_ACCEL_XOUT_H);//读取加速度寄存器X轴高八位的值
	DataL = MPU6050_Read(MPU6050_ACCEL_XOUT_L);//读取加速度寄存器X轴低八位的值
	*Accx = (DataH<<8) | DataL;  //将读取加速度寄存器X轴16位的值赋值给输出参数*Accx
	
	DataH = MPU6050_Read(MPU6050_ACCEL_YOUT_H);
	DataL = MPU6050_Read(MPU6050_ACCEL_YOUT_L);
	*Accy = (DataH<<8) | DataL;
	
	DataH = MPU6050_Read(MPU6050_ACCEL_ZOUT_H);
	DataL = MPU6050_Read(MPU6050_ACCEL_ZOUT_L);
	*Accz = (DataH<<8) | DataL;
	
	
	DataH = MPU6050_Read(MPU6050_GYRO_XOUT_H);//读取陀螺仪寄存器X轴高八位的值
	DataL = MPU6050_Read(MPU6050_GYRO_XOUT_L);//读取陀螺仪寄存器X轴低八位的值
	*Gyrox = (DataH<<8) | DataL; //将读取陀螺仪寄存器X轴16位的值赋值给输出参数*Gyrox
	
	DataH = MPU6050_Read(MPU6050_GYRO_YOUT_H);
	DataL = MPU6050_Read(MPU6050_GYRO_YOUT_L);
	*Gyroy = (DataH<<8) | DataL;
	
	DataH = MPU6050_Read(MPU6050_GYRO_ZOUT_H);
	DataL = MPU6050_Read(MPU6050_GYRO_ZOUT_L);
	*Gyroz = (DataH<<8) | DataL;
	
	
}
  • 28
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值