STM32 I2C通讯演示:MPU6050传感器数据读取

一.I2C通讯

1.I2C通讯简介

I2C(Inter IC Bus)是由Philips公司开发的一种通用数据总线;

两根通信线:SCL(Serial Clock)、SDA(Serial Data);

同步,半双工,带数据应答;

支持总线挂载多设备(一主多从、多主多从)

2.硬件电路

所有I2C设备的SCL连在一起,SDA连在一起;

设备的SCL和SDA均要配置成开漏输出模式;

SCL和SDA各添加一个上拉电阻,阻值一般为4.7KΩ左右

3.I2C时序基本单元

I2C总线处于空闲状态时,默认情况下SCL和SDA上拉电阻拉高高电平,SCL和SDA先均处于高电平状态;

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

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

起始条件和终止条件都是由主机产生的;

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

主机如果发送0,就拉低SDA到低电平;

主机如果发送1,就有上拉电阻拉高到高电平;

SCL高电平期间(如下图B7位置),从机读取SDA;

SCL低电平期间,主机把数据放在SDA线上;数据放完之后,主机再次放手SCL拉高电电平,从机读取SDA线上的数据,一次运行八次,读到一个字节的数据;

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

接收与发送的区别:

发送:SCL低电平,主机将数据放在SDA线上,SCL高电平,从机读取SDA线上的数据;

接收:SCL低电平,从机将数据放在SDA线上,SCL高电平,主机读取SDA线上的数据;

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

4.I2C外设简介

STM32内部集成了硬件I2C收发电路,可以由硬件自动执行时钟生成、起始终止条件生成、应答位收发、数据收发等功能,减轻CPU的负担

支持多主机模型

支持7位/10位地址模式

支持不同的通讯速度,标准速度(高达100 kHz),快速(高达400 kHz)

支持DMA

STM32F103C8T6 硬件I2C资源:I2C1、I2C2

5.I2C框图

二.I2C程序标准库(此程序以MPU6065模块演示)

1.软件I2C程序

MyI2C.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"

void MyI2C_W_SCL(uint8_t BitValue)     //写SCL引脚反转     直接调用这个函数,传参给1或0就可释放或拉低SCL端口
{
	GPIO_WriteBit(GPIOB,GPIO_Pin_10,(BitAction)BitValue); //bitactiion是一个枚举类型的变量,表示这个位是高电平还是低电平,  将uint8_t 强转BitValue(0或1)
	Delay_us(10);      //防止一些高性能单片机端口反转速度快,丛机速度跟不上  ,引脚延时10us
}

void MyI2C_W_SDA(uint8_t BitValue)     //写SDA引脚反转     直接调用这个函数,传参给1或0就可释放或拉低SDA端口
{
	GPIO_WriteBit(GPIOB,GPIO_Pin_11,(BitAction)BitValue);
	Delay_us(10);      //防止一些高性能单片机端口反转速度快,丛机速度跟不上  ,引脚延时10us
}

uint8_t MyI2C_R_SDA(void)     //读SDA引脚
{
	uint8_t BitValue;
	BitValue = GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11);    //读PB11引脚的状态
	Delay_us(10);
	return BitValue;    //将读到的参数返回 SDA线的电平
}


void MyI2C_Init(void)     //软件I2C不需要库函数,
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;     //软件I2C需要将端口配置开漏输出模式,开漏输出模式仍可以输入,输入时,端口输出1,再读取数据寄存器
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
 	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	GPIO_SetBits(GPIOB,GPIO_Pin_10 | GPIO_Pin_11);  //将PA12和PA13置位高电平,SCL和SDA高电平空闲状态
	
}

//配置I2C 6个时序基本单元
//1.起始条件
void MyI2C_Start(void)   //起始条件函数:先拉低SDA,再拉低SCL,就能触发起始条件
{
	MyI2C_W_SDA(1);      //先释放SDA引脚,输出1  ,高电平空闲
	MyI2C_W_SCL(1);      //释放SCL引脚,输出1  ,高电平空闲
	MyI2C_W_SDA(0);      //先拉低SDA引脚,输出0  ,低电平有效 
	MyI2C_W_SCL(0);      //再拉低SCL引脚,输出0  ,低电平有效 
}

//2.终止条件
void MyI2C_Stop(void)
{
	MyI2C_W_SDA(0);       //先拉低SDA引脚,输出0  ,低电平有效 
	MyI2C_W_SCL(1);       //然后释放SCL引脚,输出1  ,高电平空闲 
	MyI2C_W_SDA(1);       //再释放SDA引脚,输出1  ,高电平空闲
}

//3.发送一个字节
void MyI2C_SendByte(uint8_t Byte)
{
	uint8_t i;
	for(i = 0; i < 8; i++)      //循环八次, (0x80 >> i)),没循环一次,右移一次,从字节的最高位一直到最后一位
	{
		MyI2C_W_SDA(Byte & (0x80 >> i)); //用发送的字节Byte &(与)0x80(1000 0000),看字节高位,如果是1,SDA高电平,如果是0 SDA低电平
		MyI2C_W_SCL(1);        //然后释放SCL引脚,输出1  ,高电平空闲     释放SCL后,丛机立刻把放在SDA的数据读走
		MyI2C_W_SCL(0);        //再拉低SCL引脚,输出0  ,低电平有效      可以继续放下一个数据

	}
}

//4.接收一个字节
uint8_t MyI2C_ReceiveByte(void)
{
	uint8_t  Byte = 0x00;
	uint8_t  i ;
	MyI2C_W_SDA(1);       //主机释放SDA,丛机把数据放在SDA上; 
	for(i = 0; i < 8; i++)    //循环8次,读八次
	{
		MyI2C_W_SCL(1);       //主机释放SCL,主机读取数据;
		if (MyI2C_R_SDA() == 1)
		{
			Byte |= (0x80 >> i);     //如果读到SDA为1,把Byte的最高位 置1   右移八次
		}
		MyI2C_W_SCL(0);       
	}	
	return Byte;
}

//5.发送应答
void MyI2C_SendAck(uint8_t AckByte)
{
	    MyI2C_W_SDA(AckByte ); //主机把 AckByte放到SDA上  
		MyI2C_W_SCL(1);        //然后释放SCL引脚,输出1  ,高电平空闲     释放SCL后,丛机立刻把放在SDA的数据读
		MyI2C_W_SCL(0);        //再拉低SCL引脚,输出0  ,低电平有效      可以继续放下一个数据
}

//6.接收应答
uint8_t MyI2C_ReceiveAck(void)
{
	uint8_t  AckByte ;
	
	MyI2C_W_SDA(1);       //主机释放SDA,丛机把数据放在SDA上; 
	MyI2C_W_SCL(1);       //主机释放SCL,主机读取数据;
	AckByte = MyI2C_R_SDA();
	MyI2C_W_SCL(0);
	
	return AckByte;
}

MPU6050.C

#include "stm32f10x.h"                  // Device header
#include "MyI2C.h"
#include "MPU6050_Reg.h"

#define  MPU6050_ADDRESS     0XD0     //替换丛机地址0XD0 

void MPU6050_WriteReg(uint8_t RegAddress , uint8_t Data)	//指定地址写寄存器  参数1:8位寄存器地址   参数2:8位数据 
{
	MyI2C_Start();          //起始时序
	MyI2C_SendByte(MPU6050_ADDRESS);   //发送字节:丛机的地址+读写位
	MyI2C_ReceiveAck() ;    //接收应答  :返回一个应答位
	MyI2C_SendByte(RegAddress);       //发送字节 :指定寄存器地址
	MyI2C_ReceiveAck() ;    //接收应答  :返回一个应答位
	MyI2C_SendByte(Data);       //发送字节 :指定寄存器下的数据
	MyI2C_ReceiveAck() ;    //接收应答  :返回一个应答位
	MyI2C_Stop();
}

uint8_t MPU6050_ReadReg(uint8_t RegAddress)    //读丛机寄存器;参数1;指定读的地址
{
	uint8_t Data;
	
	MyI2C_Start();          //起始时序
	MyI2C_SendByte(MPU6050_ADDRESS);   //发送字节:丛机的地址+读写位
	MyI2C_ReceiveAck() ;    //接收应答  :返回一个应答位
	MyI2C_SendByte(RegAddress);       //发送字节 :指定寄存器地址
	MyI2C_ReceiveAck() ;    //接收应答  :返回一个应答位
	
	MyI2C_Start();          //起始时序
	MyI2C_SendByte(MPU6050_ADDRESS | 0X01);   //发送字节:丛机的地址+读写位
	MyI2C_ReceiveAck() ;    //接收应答  :返回一个应答位
	Data = MyI2C_ReceiveByte();    //接收一个字节 存放在Data
	MyI2C_SendAck(1);        //0给丛机应答,1不给应答
	MyI2C_Stop();
	
	return Data;
}



void MPU6050_Init(void)
{
	MyI2C_Init();
	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);
	
}

uint8_t MPU6050_GetID(void)    //读取设备ID号码
{
	return MPU6050_ReadReg(0x75);
}

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_ReadReg(MPU6050_ACCEL_XOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);
	*AccX = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);
	*AccY = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);
	*AccZ = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);
	*GyroX = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);
	*GyroY = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);
	*GyroZ = (DataH << 8) | DataL;
}

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "MPU6050.h"

int16_t AX,AY,AZ,GX,GY,GZ;
uint8_t ID ;
int main()
{
	OLED_Init();
	
	MPU6050_Init();
	uint8_t ID  = MPU6050_GetID();
	OLED_ShowString(1,1,"ID:");
	
	while(1)
	{
		MPU6050_GetData(&AX, &AY, &AZ, &GX, &GY, &GZ);
		OLED_ShowSignedNum(2,1,AX,5);
		OLED_ShowSignedNum(3,1,AY,5);
		OLED_ShowSignedNum(4,1,AZ,5);
		OLED_ShowSignedNum(2,8,GX,5);
		OLED_ShowSignedNum(3,8,GY,5);
		OLED_ShowSignedNum(4,8,GZ,5);
		OLED_ShowHexNum(1,4,ID,2);
	}
}
2.硬件I2C代码

MPU6050.c

#include "stm32f10x.h"                  // Device header
#include "MPU6050_Reg.h"

#define  MPU6050_ADDRESS     0XD0     //替换丛机地址0XD0 

void MPU6050_WaitEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)
{
	uint32_t Timeout;
	Timeout = 10000;
	while (I2C_CheckEvent(I2C2, I2C_EVENT) != SUCCESS)//起始条件发送完成后,会产生一个标志位:  EV5S事件  
	{
		Timeout--;
		if(Timeout == 0)
		{
			break;
		}
	}							
}

void MPU6050_WriteReg(uint8_t RegAddress , uint8_t Data)	//指定地址写寄存器  
{
	
	
	 //1.起始条件
	I2C_GenerateSTART(I2C2,ENABLE);    //开启起始条件
	MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT);//起始条件发送完成后,会产生一个标志位:  EV5S事件  
																		// 返回值SUCCESS: Last event is equal to the I2C_EVENT
													                     //表示最后一次事件等于我们指定的事件
																		 //while循环,等待事件发生,完成后跳出循环
	//2.发送从机地址
	I2C_Send7bitAddress(I2C2,MPU6050_ADDRESS,I2C_Direction_Transmitter); //向DR寄存器写一个七位字节:即从机地址
	MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED );//当地址发送出去后,接收到应答位后,会产生  EV6事件:
	
	//3.发送数据
	I2C_SendData(I2C2,RegAddress);//直接写入DR,发送一个字节的数据(函数传参)
	MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTING );//等待事件完成, EV8 :
	
	//EV8时间完成后,继续写入下一个数据
	I2C_SendData(I2C2,Data);//直接写入DR,发送一个字节的数据(函数传参)
	MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED );//等待事件完成, EV8-2:结束发送 :最后一个字节
	
	//4.终止条件
	I2C_GenerateSTOP(I2C2,ENABLE);     //开启终止时序
	
}

uint8_t MPU6050_ReadReg(uint8_t RegAddress)    //读丛机寄存器;参数1;指定读的地址
{
	uint8_t Data;
	 //1.起始条件
	I2C_GenerateSTART(I2C2,ENABLE);    //开启起始条件
	 MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT) ;//起始条件发送完成后,会产生一个标志位:  EV5S事件  
																		 // 返回值SUCCESS: Last event is equal to the I2C_EVENT
													                     //表示最后一次事件等于我们指定的事件
																		 //while循环,等待事件发生,完成后跳出循环
	//2.发送从机地址
	I2C_Send7bitAddress(I2C2,MPU6050_ADDRESS,I2C_Direction_Transmitter); //向DR寄存器写一个七位字节:即从机地址
	 MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) ;//当地址发送出去后,接收到应答位后,会产生  EV6事件:
	
	//3.发送数据
	I2C_SendData(I2C2,RegAddress);//直接写入DR,发送一个字节的数据(函数传参)
	 MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED ) ;//等待事件完成, EV8 :
	
	//4.生成重复起始条件
	I2C_GenerateSTART(I2C2,ENABLE);    //重复开启起始条件
	 MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT);//重复起始条件发送完成后,会产生一个标志位:  EV5S事件 
	
	//5.发送从机地址
	I2C_Send7bitAddress(I2C2,MPU6050_ADDRESS,I2C_Direction_Receiver); //向DR寄存器读一个七位字节:即从机地址
	 MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED  ) ;//当地址接收到后,接收到应答位后,会产生  EV6事件
	
	//需要接收多个字节将下列代码用FOR循环
		//6.配置ACK位
		I2C_AcknowledgeConfig(I2C2,DISABLE);    //不给应答
	
		//7.配置停止位
		I2C_GenerateSTOP(I2C2,ENABLE);
	
		MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_RECEIVED);//EV7时间产生后,一个字节的数据就在DR中
	
		//8.读取DR寄存器
		Data = I2C_ReceiveData(I2C2);  //将返回的DR内的值存放Data
	
	//9.把ACK再置一
	I2C_AcknowledgeConfig(I2C2,ENABLE);  //默认恢复ACK应答
	
	return Data;
}



void MPU6050_Init(void)
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2,ENABLE);   //开启RCC  I2C时钟;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);  //开启RCC  GPIOB时钟;
	
	GPIO_InitTypeDef GPIO_InitStructure;         	   //定义结构体,变量名: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;  //选择速度50MHZ;
	GPIO_Init(GPIOB,&GPIO_InitStructure);   //初始化结构体
	
	//初始化I2C外设
	I2C_InitTypeDef I2C_InitStruct;      //定义I2C结构体
	I2C_InitStruct.I2C_Ack = I2C_Ack_Enable;   //应答位配置,是否应答
	I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;  //STM32做从机相应几位地址
	I2C_InitStruct.I2C_ClockSpeed = 50000;   //配置SCL的时钟频率   最大400Khz;
	I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2; //时钟占空比,只有在时钟频率大于100khz,此参数才有效
	I2C_InitStruct.I2C_Mode = I2C_Mode_I2C ;    //模式:I2C
	I2C_InitStruct.I2C_OwnAddress1 = 0x00;  //STM32做从机的地址
	I2C_Init(I2C2, &I2C_InitStruct);     //初始化结构体
	
	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);
}

uint8_t MPU6050_GetID(void)    //读取设备ID号码
{
	return MPU6050_ReadReg(0x75);
}

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_ReadReg(MPU6050_ACCEL_XOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);
	*AccX = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);
	*AccY = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);
	*AccZ = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);
	*GyroX = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);
	*GyroY = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);
	*GyroZ = (DataH << 8) | DataL;
}

MPU6050.h

#ifndef __MPU6050_H
#define __MPU6050_H

void MPU6050_WriteReg(uint8_t RegAddress , uint8_t Data)	;
uint8_t MPU6050_ReadReg(uint8_t RegAddress) ;
void MPU6050_Init(void);
void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ, int16_t *GyroX, int16_t *GyroY,int16_t *GyroZ);
uint8_t MPU6050_GetID(void) ;

#endif

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "MPU6050.h"

int16_t AX,AY,AZ,GX,GY,GZ;
uint8_t ID ;
int main()
{
	OLED_Init();
	
	MPU6050_Init();
	uint8_t ID  = MPU6050_GetID();
	OLED_ShowString(1,1,"ID:");
	
	while(1)
	{
		MPU6050_GetData(&AX, &AY, &AZ, &GX, &GY, &GZ);
		OLED_ShowSignedNum(2,1,AX,5);
		OLED_ShowSignedNum(3,1,AY,5);
		OLED_ShowSignedNum(4,1,AZ,5);
		OLED_ShowSignedNum(2,8,GX,5);
		OLED_ShowSignedNum(3,8,GY,5);
		OLED_ShowSignedNum(4,8,GZ,5);
		OLED_ShowHexNum(1,4,ID,2);
	}
}

三.HAL库+CubeMX代码

1.设置RCC时钟

2.设置Debug

3.设置时钟频率

4.配置I2C(此工程中,仅使用主模式)

 Master  features  主模式特性

I2C Speed Mode: IIC模式设置 快速模式和标准模式。实际上也就是速率的选择。

I2C Clock Speed:I2C传输速率,默认为100KHz

Slave  features  从模式特性

Clock No Stretch Mode: 时钟没有扩展模式

IIC时钟拉伸(Clock stretching) – -clock stretching通过将SCL线拉低来暂停一个传输.直到释放SCL线为高电平,传输才继续进行.clock stretching是可选的,实际上大多数从设备不包括SCL驱动,所以它们不能stretch时钟.

Primary Address Length selection: 从设备地址长度 设置从设备的地址是7bit还是10bit 大部分为7bit

Dual Address Acknowledged: 双地址确认

Primary slave address:  从设备初始地址

5.配置USART串口,将读到数据打印出来

串口使用默认配置

6.生成文件

设置好路径后直接生成文件

7.代码部分

OLED显示驱动部分和Printf部分代码不在展示,有需要可留言联系我;

MPU6050.h

#ifndef __MPU6050SD_H
#define __MPU6050SD_H

#include "main.h"

//============= MPU6050 内部寄存器地址 =============//

#define  SlaveAddress		0x68		//从机设备地址,AD0接地

#define  SMPLRT_DIV		0x19		//陀螺仪采样频率,典型值:0x07(125HZ) 
#define  CONFIG			0x1A		//低通滤波器,典型值0x06(5HZ)
#define  GYRO_CONFIG		0x1B		//陀螺仪自检及测量范围,典型值 0x18 (不自检,2000 deg/s)
#define  ACCEL_CONFIG		0x1C		//加速计自检,测量范围及高通滤波频率,典型值 0x01(不自检 2g 5HZ)
#define  FIFO_EN		0x23		//各功能的 FIFO 使能或失能
#define  INT_PIN_CFG		0x37		//中断,旁路设置
#define  INT_ENABLE		0x38		//中断使能寄存器

#define  ACCEL_XOUT_H		0x3B
#define  ACCEL_XOUT_L		0x3C
#define  ACCEL_YOUT_H		0x3D
#define  ACCEL_YOUT_L		0x3E
#define  ACCEL_ZOUT_H		0x3F
#define  ACCEL_ZOUT_L		0x40

#define  TEMP_OUT_H		0x41
#define  TEMP_OUT_L		0x42

#define  GYRO_XOUT_H		0x43
#define  GYRO_XOUT_L		0x44
#define  GYRO_YOUT_H		0x45
#define  GYRO_YOUT_L		0x46
#define  GYRO_ZOUT_H		0x47
#define  GYRO_ZOUT_L		0x48

#define  USER_CTRL		0x6A		//用户控制寄存器
#define  PWR_MGMT1		0x6B		//电源管理1,典型值:0x01(正常启动)
#define  PWR_MGMT2		0x6C		//电源管理2,设置是正常工作模式还是唤醒模式

#define  WHO_AM_I		0x75		//MPU6050 的 IIC 地址寄存器,只读

extern short Gyrox,Gyroy,Gyroz;
extern short Accelx,Accely,Accelz;

void MPU6050_Config(void);			
uint8_t Get_WHOAMI(void);	
float MPU6050_Temperature(void);
void MPU6050_Gyro(void);				
void MPU6050_Accel(void);
void I2C_WriteByteToSlave(uint8_t mpu_add,uint8_t reg_add,uint8_t reg_data);
#endif


MPU6050.c

#include "main.h"
#include "MPU6050.h"
#include "i2c.h"

short Gyrox,Gyroy,Gyroz;
short Accelx,Accely,Accelz;


void I2C_WriteByteToSlave(uint8_t mpu_add,uint8_t reg_add,uint8_t reg_data)
{
	uint8_t sendbuf[2];
	sendbuf[0]=reg_add;		
	sendbuf[1]=reg_data;
	HAL_I2C_Master_Transmit(&hi2c2,mpu_add,sendbuf,2,10);
}
//=============初始化 MPU6050,解除睡眠模式 NO1=============//
void MPU6050_Config(void)
{
	I2C_WriteByteToSlave(SlaveAddress,PWR_MGMT1,0x80);	//启动 MPU6050
	HAL_Delay(100);																				//等待 MPU6050复位完毕
	I2C_WriteByteToSlave(SlaveAddress,PWR_MGMT1,0x00);	//唤醒 MPU6050
	I2C_WriteByteToSlave(SlaveAddress,GYRO_CONFIG,0x18);	//设置陀螺仪量程为 2000 deg/s
	I2C_WriteByteToSlave(SlaveAddress,ACCEL_CONFIG,0x00);	//设置加速度传感器量程为 2g
	I2C_WriteByteToSlave(SlaveAddress,SMPLRT_DIV,0x13);	//设置采样率的分频值为19,此时采样率为15HZ
	I2C_WriteByteToSlave(SlaveAddress,CONFIG,0x04);		//设置数字低通滤波器,滤波带宽为20HZ
	I2C_WriteByteToSlave(SlaveAddress,USER_CTRL,0x00);	//关闭所有FIFO,并且关闭 I2C 主模式
	I2C_WriteByteToSlave(SlaveAddress,FIFO_EN,0x00);	//关闭所有FIFO
	I2C_WriteByteToSlave(SlaveAddress,INT_PIN_CFG,0x80);	//设置INT引脚(低电平有效,推挽输出)
	I2C_WriteByteToSlave(SlaveAddress,INT_ENABLE,0x00);	//关闭所有中断
	I2C_WriteByteToSlave(SlaveAddress,PWR_MGMT1,0x01);	//使用X轴的PLL时钟作为工作时钟
	I2C_WriteByteToSlave(SlaveAddress,PWR_MGMT2,0x00);	//设置陀螺仪和加速度计处于正常工作模式
}

//=============== 获取IIC地址 ================//
uint8_t Get_WHOAMI(void)
{
	uint8_t buf[1];
	HAL_I2C_Mem_Read(&hi2c2,SlaveAddress,WHO_AM_I,I2C_MEMADD_SIZE_8BIT,buf,1,50);
	return *buf;
}


//=========获取 MPU6050 芯片温度 =========//
float MPU6050_Temperature(void)
{
	uint8_t temp[2];
	short value;	
	HAL_I2C_Mem_Read(&hi2c2,SlaveAddress,TEMP_OUT_H,I2C_MEMADD_SIZE_8BIT,temp,2,50);
	value = (short) ((temp[0]<<8)|temp[1]);
	return	(36.53 + (value/340.0));
}

//=========获取 MPU6050 陀螺仪数据 =========//
void MPU6050_Gyro(void)
{
	uint8_t temp[6];
	HAL_I2C_Mem_Read(&hi2c2,SlaveAddress,GYRO_XOUT_H,I2C_MEMADD_SIZE_8BIT,temp,6,50);
	Gyrox = (short) ((temp[0]<<8)|temp[1]);
	Gyroy = (short) ((temp[2]<<8)|temp[3]);
	Gyroz = (short) ((temp[4]<<8)|temp[5]);
}
//=========获取 MPU6050 加速计数据 =========//
void MPU6050_Accel(void)
{
	uint8_t temp[6];
	HAL_I2C_Mem_Read(&hi2c2,SlaveAddress,ACCEL_XOUT_H,I2C_MEMADD_SIZE_8BIT,temp,6,50);
	Accelx = (short) ((temp[0]<<8)|temp[1]);
	Accely = (short) ((temp[2]<<8)|temp[3]);
	Accelz = (short) ((temp[4]<<8)|temp[5]);
}

main.c

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2024 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "i2c.h"
#include "usart.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "OLED.h"
#include "MPU6050.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */
  float temp;
  uint8_t ID;
/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_I2C2_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
  MPU6050_Config();
  OLED_Init();
  OLED_ShowChinese(0,0,"你好,世界。");
  OLED_ShowChinese(0,17,"你好,世界。");
  OLED_Update();
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */
      temp = MPU6050_Temperature();
      MPU6050_Gyro();
      MPU6050_Accel();
      ID =  Get_WHOAMI();
      printf("MPU6050实时数据\r\n");
      //printf("\r\n");
      //HAL_Delay(100);
      printf("MPU6050's temp is %f \r\n",temp);
      //printf("\r\n");
      //HAL_Delay(100);
      printf("MPU6050's ID is %x \r\n",ID);
     // printf("\r\n");
      printf("MPU6050's Gyrox is %d \r\n",Gyrox);
      printf("MPU6050's Gyroy is %d \r\n",Gyroy);
      printf("MPU6050's Gyroz is %d \r\n",Gyroz);
      printf("MPU6050's Accelx is %d \r\n",Accelx);
      printf("MPU6050's Accely is %d \r\n",Accely);
      printf("MPU6050's Accelz is %d \r\n",Accelz);
      printf("--------------------------------\r\n");
      HAL_Delay(1000);

        
    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

演示效果

物联沃分享整理
物联沃-IOTWORD物联网 » STM32 I2C通讯演示:MPU6050传感器数据读取

发表评论