【stm32】软件I2C读写MPU6050

软件I2C读写MPU6050(文章最后附上源码)

编码

概况

  1. 首先建立通信层的.c和.h模块

  2. 在通信层里写好I2C底层的GPIO初始化

  3. 以及6个时序基本单元

    1. 起始、终值、发送一个字节、接收一个字节、发送应答、接收应答
  4. 写好I2C通信层之后,再建立MPU6050的.c和.h模块

    1. 基于I2C通信的模块,来实现指定地址读、指定地址写

    2. 再实现写寄存器对芯片进行配置

    3. 都寄存器得到传感器数据

  5. 最终在main.c里调用MPU6050的模块

    1. 初始化

    2. 拿到数据

    3. 显示数据

  6. 这就是程序的基本架构

步骤

  1. 初始化GPIO

    1. 引脚都要配置成开漏输出的模式

    2. 开漏输出模式仍然可以输入

    3. 输入时先输出1,再直接读取数据寄存器就行了

  2. 调用Setbits,把pin10和pin11都置高电平

  3. 这也初始化就完成了

  4. 调用MyI2C_Init函数

    1. pb10和pb11两个端口就被初始化为开漏输出模式

    2. 然后释放总线

    3. SCL和SDA处于高电平

    4. 此时I2C总线处于空闲状态

  5. 接下来就根据ppt时序波形来完成6个时序单元

  6. 初始化函数之前,定义函数,对操作端口的函数进行封装

    1. void MyI2C_w_SCL(uint8_t BitValue)

    2. 函数里面调用WriteBit函数

    3. 后面再调用MyI2C_w_SCL函数,参数给1或0

    4. 就可以释放或拉低SCL

  7. 复制函数,定义SDA函数

  8. 再写一个读SDA函数uint8_t MyI2C_R_SDA(void)

  9. 写六个时序单元

    1. 开始的函数

      1. 在前面最好先释放SDA,这样保险一些

      2. 如果起始条件之前,SCL和SDA已经是高电平了,先释放哪个都无所谓

      3. 但是在图示在这里插入图片描述
        还要兼容这里的重复起始条件Sr

      4. Sr开始,SCl是低电平,SDA电平不敢确定

      5. 所以保险起见,我们趁SCL是低电平,先确保释放SDA,再释放SCL,这是SDA和SCL都是高电平

      6. 然后再拉低SDA拉低SCl

      7. 这样这个Start可以兼容起始条件和重复起始条件

    2. 结束的函数

      1. 为了确保再SCL高电平期间,SDA产生上升沿,先把SDA拉低
    3. 发送一个字节数据

    4. 接收一个字节数据

      1. 防止主机干扰从机写入数据

      2. 主机需要先释放SDA,释放SDA也相当于切换为输入模式

      3. 再释放SCL

      4. 在SCL低电平时,从机会把数据已经放到SDA上

      5. 如果从机想发1,就释放SDA,如果从机想发0,就拉低SDA

      6. 主机释放SCL,在SCL高电平期间,读取SDA

      7. 再拉低SCL,从机把下一位数据放在SDA上

    5. 发送应答

    6. 接收应答

编写MPU6050模块

  1. 调用MyI2C.h函数

  2. 初始化MPU6050,调用I2C_Init

  3. 之后在上面 先封装指定地址写和指定地址读 的时序

    1. MPU6050_WriteReg指定地址写寄存器 参数是8位的指定地址(指定读写哪个寄存器,就是要读写寄存器的地址),另一个参数是要写入的数据

    2. 为了方便修改MyI2C_SendByte()的参数,并且突出它是从机地址,可以用宏定义替换一下这个数据

  4. MyI2C_ReceiveAck应答位是可以不处理的

  5. 在接收一个字节函数里uint8_t MPU6050_ReadReg(uint8_t RegAdress)

    1. 如果只接受一个字节,应答位给1(非应答)

    2. 如果想继续接收数据,就要给0(应答)

    3. 如果想进阶为指定地址读多个字节,可以用for循环套起来,重读读取多次,最后一个应答给非应答1

写寄存器注意事项
  • 首先解除芯片的睡眠模式

    • 睡眠模式是电源管理寄存器1的SLEEP位在这里插入图片描述

    • 直接写入0x00 这样就可以解除睡眠模式了

在MPU初始化函数里配置电源管理寄存器
  1. 先用宏定义把寄存器的地址用一个字符串来表示

  2. 寄存器比较少的话可以直接在上面进行宏定义

    1. 如果比较多的话,可以再新建一个单独的头文件进行存放

    2. 再添加一个.h文件 MPU6050_Reg 存放宏定义

  3. 配置电源管理寄存器1 0x01

  4. 配置电源管理寄存器2 0x00

  5. 配置头文件里上面四个寄存器

  6. 配置完之后陀螺仪内部就在连续不断的进行数据转换了

  7. 输出的数据就存放在数据寄存器里

    1. 接下来想获取数据的话

    2. 只需要再写一个获取数据寄存器的函数

  8. 在初始化下面编写一个获取数据寄存器数据的函数

    1. 根据任务要求,函数需要返回6个int16_t数据

    2. 分别表示xyz的加速度值和陀螺仪值

    3. 但是c语言中,函数的返回值只能有一个

      1. 使用指针,进行变量的地址传递来实现多返回值

      2. 高8位左移8位或上低8位

MyI2C.c程序

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

void MyI2C_W_SCL(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOA, GPIO_Pin_2, (BitAction)BitValue);
	Delay_us(10);
}

void MyI2C_W_SDA(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOA, GPIO_Pin_3, (BitAction)BitValue);
	Delay_us(10);
}

uint8_t MyI2C_R_SDA(void)
{
	uint8_t BitValue;
	BitValue = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_3);
	Delay_us(10);
	return BitValue;
}

void MyI2C_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
//	GPIO_SetBits(GPIOA, GPIO_Pin_2 | GPIO_Pin_3);
}

void MyI2C_Start(void)
{
	MyI2C_W_SDA(1);
	MyI2C_W_SCL(1);
	MyI2C_W_SDA(0);
	MyI2C_W_SCL(0);
}

void MyI2C_Stop(void)
{
	MyI2C_W_SDA(0);
	MyI2C_W_SCL(1);
	MyI2C_W_SDA(1);
}

void MyI2C_SendByte(uint8_t Byte)
{
	uint8_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(void)
{
	uint8_t i, Byte = 0x00;
	MyI2C_W_SDA(1);
	for (i = 0; i < 8; i ++)
	{
		MyI2C_W_SCL(1);
		if (MyI2C_R_SDA() == 1){Byte |= (0x80 >> i);}
		MyI2C_W_SCL(0);
	}
	return Byte;
}

void MyI2C_SendAck(uint8_t AckBit)
{
	MyI2C_W_SDA(AckBit);
	MyI2C_W_SCL(1);
	MyI2C_W_SCL(0);
}

uint8_t MyI2C_ReceiveAck(void)
{
	uint8_t AckBit;
	MyI2C_W_SDA(1);
	MyI2C_W_SCL(1);
	AckBit = MyI2C_R_SDA();
	MyI2C_W_SCL(0);
	return AckBit;
}

MyI2C.h程序

#ifndef __MYI2C_H
#define __MYI2C_H

void MyI2C_Init(void);
void MyI2C_Start(void);
void MyI2C_Stop(void);
void MyI2C_SendByte(uint8_t Byte);
uint8_t MyI2C_ReceiveByte(void);
void MyI2C_SendAck(uint8_t AckBit);
uint8_t MyI2C_ReceiveAck(void);

#endif

MPU6050.c程序

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

#define MPU6050_ADDRESS		0xD0

void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{
	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)
{
	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();
	MyI2C_SendAck(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)
{
	return MPU6050_ReadReg(MPU6050_WHO_AM_I);
}

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);
uint8_t MPU6050_GetID(void);
void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ, 
						int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ);

#endif

main.c程序

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

uint8_t ID;
int16_t AX, AY, AZ, GX, GY, GZ;
uint32_t light;

int main(void)
{
	OLED_Init();
	
	MPU6050_Init();
	
	OLED_ShowString(1, 1, "ID:");
	ID = MPU6050_GetID();
	OLED_ShowHexNum(1, 4, ID, 2);
	
	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);
	}
}

如果发现错误或者需要改进的地方请私信或者评论

上一篇:c++的学习之路:14、list(1)


下一篇:为硬刚小米SU7,华为智界S7整出了「梅开二度」操作