STM32入门——软件I2C读写MPU6050(15)

STM32入门——软件I2C读写MPU6050(15)

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

uint8_t ID;						// 定义用于存放ID号的变量
int16_t AX, AY, AZ, GX, GY, GZ; // 定义用于存放各个数据的变量

int main(void)
{

	/*模块初始化*/
	OLED_Init();	// OLED初始化
	MPU6050_Init(); // MPU6050初始化

	/*显示ID号*/
	OLED_ShowString(1, 1, "ID:"); // 显示静态字符串
	ID = MPU6050_GetID();		  // 获取MPU6050的ID号
	OLED_ShowHexNum(1, 4, ID, 2); // OLED显示ID号

	while (1)
	{
		MPU6050_GetData(&AX, &AY, &AZ, &GX, &GY, &GZ); // 获取MPU6050的数据
		OLED_ShowSignedNum(2, 1, AX, 5);			   // OLED显示数据
		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);
	}
}

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

/* 引脚配置层 */

/**
 * 函    数: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);
}

/**
 * 函    数:I2C写SDA引脚电平
 * 参    数:BitValue 协议层传入的当前需要写入SDA的电平,范围0~1
 * 返 回 值:无
 * 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置SDA为低电平,当BitValue为1时,需要置SDA为高电平
 */
void MyI2C_W_SDA(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOB, GPIO_Pin_11, (BitAction)BitValue); // 根据BitValue,设置SDA引脚的电平,BitValue要实现非0即1的特性
	Delay_us(10);
}

/**
 * 函    数: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电平
}

/**
 * 函    数: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引脚初始化后默认为高电平(释放总线状态)
}

/*协议层*/

/**
 * 函    数: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也拉低,即为了占用总线,也为了方便总线时序的拼接
}

/**
 * 函    数:I2C终止
 * 参    数:无
 * 返 回 值:无
 */
void MyI2C_Stop(void)
{
	MyI2C_W_SDA(0); // 拉低SDA,确保SDA为低电平
	MyI2C_W_SCL(1); // 释放SCL,使SCL呈现高电平
	MyI2C_W_SDA(1); // 在SCL高电平期间,释放SDA,产生终止信号
}

/**
 * 函    数:I2C发送一个字节
 * 参    数:Byte 要发送的一个字节数据,范围:0x00~0xFF
 * 返 回 值:无
 */
void MyI2C_SendByte(uint8_t Byte)
{
	uint8_t i;
	for (i = 0; i < 8; i++) // 循环8次,主机依次发送数据的每一位
	{
		/*两个!可以对数据进行两次逻辑取反,作用是把非0值统一转换为1,即:!!(0) = 0,!!(非0) = 1*/
		MyI2C_W_SDA(!!(Byte & (0x80 >> i))); // 使用掩码的方式取出Byte的指定一位数据并写入到SDA线
		MyI2C_W_SCL(1);						 // 释放SCL,从机在SCL高电平期间读取SDA
		MyI2C_W_SCL(0);						 // 拉低SCL,主机开始发送下一位数据
	}
}

/**
 * 函    数: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++)
	{
		MyI2C_W_SCL(1); // 释放SCL,主机在SCL高电平期间读取SDA
		if (MyI2C_R_SDA())
		{
			Byte |= (0x80 >> i);
		}				// 读取SDA数据,并存储到Byte变量
						// 当SDA为1时,置变量置顶位为1,当SDA为0时,不做处理,指定位为默认的初值0
		MyI2C_W_SCL(0); // 拉低SCL,从机在SCL低电平期间写入SDA
	}
	return Byte; // 返回接收到的一个字节数据
}

/**
 * 函    数: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,开始下一个时序模块
}

/**
 * 函    数: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;			// 返回定义应答位变量
}

#ifndef __MYI2C_H
#define __MYI2C_H

// #include <stdint.h>                                                  // 第一种方法:避免 uint8_t 未定义报错
//"forcedInclude": ["C:\\Keil_v5\\ARM\\ARMCC\\include\\stdint.h"]       // 第二种方法:避免 uint8_t 未定义报错

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

#include "stm32f10x.h"
#include "MyI2C.h"
#include "MPU6050_Reg.h"

#define MPU6050_ADDRESS 0xD0 // MPU6050的I2C从机地址

/**
 * 函    数: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终止
}

/**
 * 函    数: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;
}

/**
 * 函    数: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_GYRO_CONFIG, 0x18);  // 陀螺仪配置寄存器,选择满量程为±2000°/s
    MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18); // 加速度计配置寄存器,选择满量程为±16g
}

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

/**
 * 函    数: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;                // 数据拼接,通过输出参数返回
}

#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

#ifndef __MPU6050_REG_H
#define __MPU6050_REG_H

#define	MPU6050_SMPLRT_DIV		0x19
#define	MPU6050_CONFIG			0x1A
#define	MPU6050_GYRO_CONFIG		0x1B
#define	MPU6050_ACCEL_CONFIG	0x1C

#define	MPU6050_ACCEL_XOUT_H	0x3B
#define	MPU6050_ACCEL_XOUT_L	0x3C
#define	MPU6050_ACCEL_YOUT_H	0x3D
#define	MPU6050_ACCEL_YOUT_L	0x3E
#define	MPU6050_ACCEL_ZOUT_H	0x3F
#define	MPU6050_ACCEL_ZOUT_L	0x40
#define	MPU6050_TEMP_OUT_H		0x41
#define	MPU6050_TEMP_OUT_L		0x42
#define	MPU6050_GYRO_XOUT_H		0x43
#define	MPU6050_GYRO_XOUT_L		0x44
#define	MPU6050_GYRO_YOUT_H		0x45
#define	MPU6050_GYRO_YOUT_L		0x46
#define	MPU6050_GYRO_ZOUT_H		0x47
#define	MPU6050_GYRO_ZOUT_L		0x48

#define	MPU6050_PWR_MGMT_1		0x6B
#define	MPU6050_PWR_MGMT_2		0x6C
#define	MPU6050_WHO_AM_I		0x75

#endif

posted @ 2026-04-03 21:00  Q&25  阅读(2)  评论(1)    收藏  举报