2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
本内容基于江协科技STM32视频学习之后整理而得。
低电平主机放数据,高电平从机读数据
起始条件之后,第一个字节也必须是主机发送的。SCL低电平,主机想发送0,就拉低SDA到低电平;如果想发送1,就放手,SDA回弹到高电平。在SCL低电平期间,允许改变SDA的电平,当放好数据之后,主机就松手时钟线,SCL回弹到高电平。在高电平期间,是从机读取SDA的时候,所以在高电平期间,SDA不允许变化。SCL处于高电平之后,从机需要尽快地读取SDA,一般都是在SCL上升沿这个时刻,从机就已经读取完成了。因为时钟是主机控制的,从机并不知道什么时候产生下降沿,因此在SCL上升沿时,从机就会把数据读走。当主机在放手SCL一段时间后,就可以继续拉低SCL,传输下一位了。主机也需要在SCL下降沿之后尽快把数据放到SDA上。但主机有时钟的主导权,所以只需要在低电平的任意时刻把数据放在SDA上就可以了。数据放完之后,主机再松手SCL,SCL高电平,从机读取这一位。循环该流程:主机拉低SCL,把数据放到SDA上,主机松开SCL,从机读取SDA数据。在SCL的同步下,依次进行主机发送和从机接收,循环8次,就发送了8位数据,也就是一个字节。
由于是高位先行,所以第一位是一个字节的最高位B7,最后发送最低位B0,
低电平从机放数据,高电平主机读数据
SDA线:主机在接收之前要释放SDA,这时从机获得SDA的控制权,从机需要发送0,就把SDA拉低,从机需要发送1,就放手,SDA回弹高电平。低电平变换数据,高电平读取数据。实线表示主机控制的电平,虚线表示从机控制的电平。SCL全程由主机控制,SDA主机在接收前要释放,交由从机控制。因为SCL时钟是由主机控制的,所以从机的数据变换基本上都是贴着SCL下降沿进行的,而主机可以在SCL高电平的任意时刻读取。
就是在调用发送一个字节的时序之后,就要紧跟着调用接收应答的时序,用来判断从机有没有收到刚才给它的数据。如果从机收到了,那在应答位这里,主机释放SDA的时候,从机就应该立刻把SDA拉下来,然后在SCL高电平期间,主机读取应答位。如果应答位为0,就说明从机确实收到了。
在接收一个字节时候,需要调用发送应答。发送应答的目的是告诉从机,你是不是要继续发。如果从机发送一个数据后,得到了主机的应答,那从机就还会继续发送,如果从机没有得到主机的应答,那从机就会认为发送了一个数据,但主机不理我,可能主机不想要吧,这时从机就是乖乖地释放SDA,交出SDA的控制权,防止干扰主机之后的操作。
流程:
(1)起始条件
(2)发送一个字节时序—0xD0(从机地址(7bit) +写(1bit)-0)(1101 0000)
(3)接收应答:RA = 0(接收从机的应答)
(4)指定地址:0x19(0001 1001)
(5)接收应答:RA = 0(接收从机的应答)
(6)写入指定数据:0xAA(1010 1010)
(7)接收应答:RA = 0
(8)停止位P(终止条件)
该数据帧的目的是:对于指定从机地址为1101000的设备,在其内部0x19地址的寄存器中,写入0xAA这个数据。
0表示:之后的时序主机要进行写入操作;
1表示:之后的时序主机要进行读出操作;
流程:
(1)起始条件
(2)发送一个字节时序—0xD1(从机地址(7bit) +读(1bit)-1)(1101 0001)
(3)接收应答:RA = 0(接收从机的应答)
(4)读从机数据:0x0F(0000 1111)
(7)发送应答:SA = 0
(8)停止位P(终止条件)
先起始、再重复起始、再停止
流程:
(1)起始条件
(2)发送一个字节时序—0xD0(从机地址(7bit) +写(1bit)-0)(1101 0000)
(3)接收应答:RA = 0(接收从机的应答)
(4)指定地址:0x19(0001 1001)
(5)接收应答:RA = 0(接收从机的应答)
(6)重复起始条件
(7)发送一个字节时序—0xD1(从机地址(7bit) +读(1bit)-1)(1101 0001)
(8)接收应答:RA = 0
(9)读取从机数据:0xAA(1010 1010)
(10)发送应答:SA = 0
(11)停止位P(终止条件)
引脚 | 功能 |
---|---|
VCC、GND | 电源 |
SCL、SDA | I2C通信引脚 |
XCL、XDA | 主机I2C通信引脚 |
AD0 | 从机地址最低位 |
INT | 中断信号输出 |
通过软件I2C通信,对MPU6050芯片内部的寄存器进行读写,写入到配置寄存器,就可以对外挂的这个模块进行配置,读出数据寄存器,就可以获取外挂模块的数据,读出的数据会显示在OLED上,最上面的数据是设备的ID号,这个MPU6050的ID号固定为0x68。下面的,左边3个是加速度传感器的输出数据,分别是X轴、Y轴、Z轴的加速度,右边3个是陀螺仪传感器的输出数据,分别是X轴、Y轴、Z轴的角速度。
SCL接到STM32的PB10引脚,SDA接到PB11引脚。这里由于是软件翻转电平实现,所以可以任意连接两个GPIO口即可。
STM32是主机,MPU6050是从机,是一主一从模式。
#include "stm32f10x.h" // Device header
#include "Delay.h"
void MyI2C_W_SCL(uint8_t BitValue)
{
GPIO_WriteBit(GPIOB, GPIO_Pin_10,(BitAction)BitValue);
Delay_us(10);
}
void MyI2C_W_SDA(uint8_t BitValue)
{
GPIO_WriteBit(GPIOB, GPIO_Pin_11,(BitAction)BitValue);
Delay_us(10);
}
uint8_t MyI2C_R_SDA(void)
{
uint8_t BitValue;
BitValue = GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11);
Delay_us(10);
return BitValue;
}
void MyI2C_Init(void)
{
/*
软件I2C初始化:
1. 把SCL和SDA都初始化为开漏输出模式;
2. 把SCL和SDA置高电平;
输入时,先输出1,再直接读取输入数据寄存器就行了;
初始化结束后,调用SetBits,把GPIOB的Pin_10和Pin_11都置高电平,
此时I2C总线处于空闲状态
*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
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);
GPIO_SetBits(GPIOB, GPIO_Pin_10 | GPIO_Pin_11);
}
/*
起始条件:SCL高电平期间,SDA从高电平切换到低电平。
如果起始条件之前,SDA和SCL都已经是高电平了,那先释放哪一个是一样的效果。
但是这个Start还要兼容重复起始条件Sr,Sr最开始,SCL是低电平,SDA电平不敢确定,
所以为保险起见,在SCL低电平时,先确保释放SDA,再释放SCL。
这时SDA和SCL都是高电平,然后再拉低SDA、拉低SCL。
这样这个Start就可以兼容起始条件和重复起始条件了。
*/
void MyI2C_Start(void)
{
MyI2C_W_SDA(1);
MyI2C_W_SCL(1);
MyI2C_W_SDA(0);
MyI2C_W_SCL(0);
}
/*
终止条件:SCL高电平期间,SDA从低电平切换到高电平
如果Stop开始时,SCL和SDA都已经是低电平了,那就先释放SCL,再释放SDA。
但在这个时序单元开始时,SDA并不一定是低电平,所以为了确保之后释放
SDA能产生上升沿,要在时序单元开始时,先拉低SDA,然后再释放SCL、释放SDA。
*/
void MyI2C_Stop(void)// 终止条件
{
MyI2C_W_SDA(0);
MyI2C_W_SCL(1);
MyI2C_W_SDA(1);
}
/*
发送一个字节:发送一个字节时序开始时,SCL是低电平。
除了终止条件SCL以高电平结束,所有的单元都会保证SCL以低电平结束。
SCL低电平变换数据;高电平保持数据稳定。由于是高位先行,所以变换数据的时候,
按照先放最高位,再放次高位,...,最后最低位的顺序,依次把每一个字节的每一位放在SDA线上,
每放完一位后,执行释放SCL,拉低SCL的操作,驱动时钟运转。
程序:趁SCL低电平,先把Byte的最高位放在SDA线上,
*/
void MyI2C_SendByte(uint8_t Byte) // 发送一个字节
{
uint8_t i;
for (i = 0; i < 8; i ++)
{
MyI2C_W_SDA(Byte & (0x80 >> i));// 右移i位
MyI2C_W_SCL(1);
MyI2C_W_SCL(0);
}
}
/*
接收一个字节:时序开始时,SCL低电平,此时从机需要把数据放到SDA上,
为了防止主机干扰从机写入数据,主机需要先释放SDA,释放SDA相当于切换为输入模式,
那在SCL低电平时,从机会把数据放到SDA上,如果从机想发1,就释放SDA,想发0,就拉低SDA,
主机释放SCL,在SCL高电平期间,读取SDA,再拉低SCL,低电平期间,从机就会把下一位数据放到SDA上,重复8次,
主机就能读到一个字节了。
SCL低电平变换数据,高电平读取数据,实际上是一种读写分离的操作,低电平时间定义为写的时间,高电平时间定义为读的时间,
*/
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) // 如果if成立,接收的这一位为1,
{
Byte |= (0x80 >> i); // 最高位置1
}
MyI2C_W_SCL(0);
}
return Byte;
}
/*
问题:反复读取SDA,for循环中又没写过SDA,那SDA读出来应该始终是一个值啊?
回答:I2C是在进行通信,通信是有从机的,当主机不断驱动SCL时钟时,
从机就有义务去改变SDA的电平,所以主机每次循环读取SDA的时候,
这个读取到的数据是从机控制的,这个数据也正是从机想要给我们发送的数据,
所以这个时序叫做接收一个字节。
*/
void MyI2C_SendAck(uint8_t AckBit) // 发送应答
{
// 函数进来,SCL低电平,主机把AckBit放到SDA上,
MyI2C_W_SDA(AckBit);
MyI2C_W_SCL(1); // 从机读取应答
MyI2C_W_SCL(0); // 进入下一个时序单元
}
uint8_t MyI2C_ReceiveAck(void) // 接收应答
{
// 函数进来,SCL低电平,主机释放SDA,防止从机干扰
uint8_t AckBit;
MyI2C_W_SDA(1); // 主机释放SDA
MyI2C_W_SCL(1); // SCL高电平,主机读取应答位
AckBit = MyI2C_R_SDA();
MyI2C_W_SCL(0); // SCL低电平,进入下一个时序单元
return AckBit;
}
/*问题:在程序里,主机先把SDA置1了,然后再读取SDA,
这应答位肯定是1啊,
回答:第一,I2C的引脚是开漏输出+弱上拉的配置,主机输出1,
并不是强制SDA为高电平,而是释放SDA,
第二,I2C是在通信,主机释放了SDA,从机是有义务在此时把SDA再拉低的,
所以,即使主机把SDA置1了,之后再读取SDA,读到的值也可能是0,
读到0,代表从机给了应答,读到1,代表从机没给应答,这就是接收应答的流程。
*/
#ifndef __MPU6050_REG_H
#define __MPU6050_REG_H
// 宏定义: 寄存器的名称 对应的地址
#define MPU6050_SMPLRT_DIV 0x19 // 采样率分频
#define MPU6050_CONFIG 0x1A // 配置外部帧同步(FSYNC)引脚采样和数字低通滤波器(DLPF)设置
#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 // 电源管理寄存器1
#define MPU6050_PWR_MGMT_2 0x6C // 电源管理寄存器2
#define MPU6050_WHO_AM_I 0x75 // 用于验证设备身份
#endif
#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); // 指定寄存器地址,存在MPU6050的当前地址指针里,用于指定具体读写哪个寄存器
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); // 指定地址:就是设置了MPU6050的当前地址指针
MyI2C_ReceiveAck();
// 转入读的时序,重新指定读写位,就必须重新起始
MyI2C_Start();// 重复起始条件
MyI2C_SendByte(MPU6050_ADDRESS | 0x01);// 指定从机地址和读写位,0xD0是写地址,或上0x01变为0xD1,读写位为1,接下来要读从机的数据
MyI2C_ReceiveAck(); // 接收应答后,总线控制权就正式交给从机了,从机开始发送一个字节
Data = MyI2C_ReceiveByte();// 主机接收一个字节,该函数返回值就是接收到的数据
// 主机接收一个字节后,要给从机发送一个应答
MyI2C_SendAck(1);// 参数为0,就是给从机应答,参数给1,就是不给从机应答
// 如果想继续读多个字节,就要给应答,从机收到应答之后,就会继续发送数据,如果不想继续读了,就不能给从机应答了。
// 主机收回总线的控制权,防止之后进入从机以为你还想要,但你实际不想要的冲突状态,
// 这里,只需要读取1个字节,所以就给1,不给从机应答,
MyI2C_Stop();
return Data;
}
void MPU6050_Init(void)
{
MyI2C_Init();
// 写入一些寄存器对MPU6050硬件电路进行初始化配置
// 电源管理寄存器1:设备复位:0,不复位;睡眠模式:0,解除睡眠:循环模式:0,不循环;无关位i:0;温度传感器失能:0,不失能;最后三位选择时钟:000,选择内部时钟,001,选择x轴的陀螺仪时钟,
MPU6050_WriteReg(MPU6050_PWR_MGMT_1,0x01);// 解除睡眠,选择陀螺仪时钟
// 电源管理寄存器2:前两位,循环模式唤醒频率:00,不需要;后6位,每一个轴的待机位:全为0,不需要待机;
MPU6050_WriteReg(MPU6050_PWR_MGMT_2,0x00); // 均不待机
// 采样率分频:该8位决定了数据输出的快慢,值越小越快
MPU6050_WriteReg(MPU6050_SMPLRT_DIV,0x09);// 采样分频:10分频
// 配置寄存器:外部同步:全为0,不需要;数字低通滤波器:110,最平滑的滤波
MPU6050_WriteReg(MPU6050_CONFIG,0x06);// 滤波参数给最大
// 陀螺仪配置寄存器:前三位,自测使能:全为0,不自测;满量程选择:11,最大量程;后三位无关位:为0
MPU6050_WriteReg(MPU6050_GYRO_CONFIG,0x18);// 陀螺仪和加速度计都选最大量程
// 加速度计配置寄存器:前三位,自测使能:全为0,不自测;满量程选择:11,最大量程;后三位高通滤波器:用不到,为000
MPU6050_WriteReg(MPU6050_ACCEL_CONFIG,0x18);
}
// 获取芯片的ID号
uint8_t MPU6050_GetID(void)
{
return MPU6050_ReadReg(MPU6050_WHO_AM_I);
}
// 获取寄存器数据的函数,返回6个int16_t的数据,分别表示XYZ的加速度值和陀螺仪值
// 指针地址传递的方法,返回多值
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;
// 加速度计X
DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);
DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);
*AccX = (DataH << 8) | DataL; // 高8位左移8位,再或上低8位,得到加速度计X轴的16位数据
// 加速度计Y
DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);
DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);
*AccY = (DataH << 8) | DataL;
// 加速度计Z
DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);
DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);
*AccZ = (DataH << 8) | DataL;
// 陀螺仪X
DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);
DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);
*GyroX = (DataH << 8) | DataL;
// 陀螺仪Y
DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);
DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);
*GyroY = (DataH << 8) | DataL;
// 陀螺仪Z
DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);
DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);
*GyroZ = (DataH << 8) | DataL;
}
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "MyI2C.h"
#include "MPU6050.h"
uint8_t ID;
int16_t AX, AY, AZ, GX, GY, GZ;// 接收XYZ轴的加速度值和陀螺仪值
int main(void)
{
OLED_Init();
// MyI2C_Init();
MPU6050_Init();
//
OLED_ShowString(1,1,"ID:");
ID = MPU6050_GetID();
OLED_ShowHexNum(1, 4, ID, 2);
// // 指定地址写
// MyI2C_Start(); // 产生起始条件,开始一次传输
// // 主机首先发送一个字节,内容是从机地址+读写位,进行寻址
// MyI2C_SendByte(0xD0); // 1101 000 0,0代表即将进行写入操作
// // 发送一个字节后,要接收一下应答位,看看从机有没有收到刚才的数据
// uint8_t Ack = MyI2C_ReceiveAck();
// // 接收应答之后,要继续发送一个字节,写入寄存器地址
// MyI2C_Stop();
//
// OLED_ShowNum(1, 1, Ack, 3);
// // 指定地址读
// uint8_t ID = MPU6050_ReadReg(0X75);// 返回值是0x68
// OLED_ShowHexNum(1, 1, ID, 2);
// // 指定地址写,需要先解除睡眠模式,否则写入无效
// // 睡眠模式是电源管理寄存器1的这一位SLEEP控制的,把该寄存器写入0x00,解除睡眠模式
// // 该寄存器地址是0x6B
// MPU6050_WriteReg(0x6B, 0x00);
// // 采样率分频寄存器,地址是0x19,值的内容是采样分频
// MPU6050_WriteReg(0x19, 0xAA);
//
// uint8_t ID = MPU6050_ReadReg(0X19);
// OLED_ShowHexNum(1, 1, ID, 2);//显示0x19地址下的内容,应该是0xAA
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);
}
}
当STM32想要执行指定地址写的时候,需要按照着发送器传送序列图进行。
7位主接收:起始、从机地址+读、接收应答、接收数据、发送应答 ··· 接收数据、非应答、终止
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);
// 配置CR1的ACK这一位,0:无应答,1:应答
void I2C_AcknowledgeConfig(I2C_TypeDef* I2Cx, FunctionalState NewState);
// 发送数据,把Data数据直接写入到DR寄存器
void I2C_SendData(I2C_TypeDef* I2Cx, uint8_t Data);
// 读取DR,接收数据
uint8_t I2C_ReceiveData(I2C_TypeDef* I2Cx);
// Address参数也是通过DR发送的,但在发送之前,设置了Address最低位的读写位,
// I2C_Direction不是发送,是把Address的最低位置1(读),否则最低位清0(写)
void I2C_Send7bitAddress(I2C_TypeDef* I2Cx, uint8_t Address, uint8_t I2C_Direction);
SCL接到STM32的PB10引脚,SDA接到PB11引脚。这里由于是软件翻转电平实现,所以可以任意连接两个GPIO口即可。
OLED最上面的数据是设备的ID号,这个MPU6050的ID号固定为0x68。下面的,左边3个是加速度传感器的输出数据,分别是X轴、Y轴、Z轴的加速度,右边3个是陀螺仪传感器的输出数据,分别是X轴、Y轴、Z轴的角速度。
#include "stm32f10x.h" // Device header
#include "MPU6050_Reg.h"
/*
1. 配置I2C外设,对I2C外设进行初始化,替换MyI2C_Init
(1)开启I2C外设和对应GPIO口的时钟,
(2)把I2C外设对应的GPIO口初始化为复用开漏模式
(3)使用结构体,对整个I2C进行配置
(4)I2C_Cmd,使能I2C
2. 控制外设电路,实现指定地址写的时序,替换WriteReg
3. 控制外设电路,实现指定地址读的时序,替换ReadReg
*/
// 宏定义:从机地址
#define MPU6050_ADDRESS 0xD0
// 超时退出
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)
{
// MyI2C_Start();
// MyI2C_SendByte(MPU6050_ADDRESS);// 发送从机地址后,接收应答
// MyI2C_ReceiveAck();// 寻址找到从机之后,继续发送下一个字节
// MyI2C_SendByte(RegAddress); // 指定寄存器地址,存在MPU6050的当前地址指针里,用于指定具体读写哪个寄存器
// MyI2C_ReceiveAck();
// MyI2C_SendByte(Data);// 指定写入指定寄存器地址下的数据
// MyI2C_ReceiveAck();
// MyI2C_Stop();
I2C_GenerateSTART(I2C2, ENABLE); // 起始条件
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT); //EV5事件
// 发送从机地址,接收应答。该函数自带了接收应答,如果应答错误,硬件会通过标志位和中断来提示我们
I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter);
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED); //EV6事件
// 直接写入DR,发送数据
I2C_SendData(I2C2, RegAddress);
// 写入了DR,DR立刻转移到移位寄存器进行发送,EV8事件出现的非常快,基本不用等。因为有两级缓存,
// 第一个数据写进DR了,会立刻跑到移位寄存器,这时不用等第一个数据发完,第二个数据就可以写进去等着了。
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTING); //EV8事件
I2C_SendData(I2C2, Data);
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED); //EV8_2事件
I2C_GenerateSTOP(I2C2, ENABLE);
}
// 指定地址读:接收器传送序列
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
uint8_t Data;
// MyI2C_Start();
// MyI2C_SendByte(MPU6050_ADDRESS);
// MyI2C_ReceiveAck();
// MyI2C_SendByte(RegAddress); // 指定地址:就是设置了MPU6050的当前地址指针
// MyI2C_ReceiveAck();
// // 转入读的时序,重新指定读写位,就必须重新起始
// MyI2C_Start();// 重复起始条件
// MyI2C_SendByte(MPU6050_ADDRESS | 0x01);// 指定从机地址和读写位,0xD0是写地址,或上0x01变为0xD1,读写位为1,接下来要读从机的数据
// MyI2C_ReceiveAck(); // 接收应答后,总线控制权就正式交给从机了,从机开始发送一个字节
// Data = MyI2C_ReceiveByte();// 主机接收一个字节,该函数返回值就是接收到的数据
// // 主机接收一个字节后,要给发送从机一个应答
// MyI2C_SendAck(1);// 参数为0,就是给从机应答,参数给1,就是不给从机应答
// MyI2C_Stop();
I2C_GenerateSTART(I2C2, ENABLE); // 起始条件
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT); //EV5事件
// 发送从机地址,接收应答。该函数自带了接收应答,如果应答错误,硬件会通过标志位和中断来提示我们
I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter);
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED); //EV6事件
// 直接写入DR,发送数据
I2C_SendData(I2C2, RegAddress);
// 写入了DR,DR立刻转移到移位寄存器进行发送,EV8事件出现的非常快,基本不用等。因为有两级缓存,
// 第一个数据写进DR了,会立刻跑到移位寄存器,这时不用等第一个数据发完,第二个数据就可以写进去等着了。
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED); //EV8_2事件
I2C_GenerateSTART(I2C2, ENABLE);// 重复起始条件
// 主机接收
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT); //EV5事件
// 接收地址
I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Receiver); // 函数内部就自动将该地址MPU6050_ADDRESS的最低位置1
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED); //EV6事件
// 在最后一个数据之前就要把应答位ACK置0,同时把停止条件生成位STOP置1
I2C_AcknowledgeConfig(I2C2, DISABLE);
I2C_GenerateSTOP(I2C2, ENABLE);
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_RECEIVED); //EV7事件
// 等EV7事件产生后,一个字节的数据就已经在DR里面了。
// 读取DR就可拿出该字节
Data = I2C_ReceiveData(I2C2); // 返回值就是DR的数据
// 在接收函数的最后,要恢复默认的ACK = 1。
// 默认状态下ACK就是1,给从机应答,在收最后一个字节之前,临时把ACK置0,给非应答,
// 所以在接收函数的最后,要恢复默认的ACK = 1,这个流程是为了方便指定地址收多个字节。
I2C_AcknowledgeConfig(I2C2, ENABLE);
return Data;
}
void MPU6050_Init(void)
{
// MyI2C_Init();
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_InitStructure.I2C_ClockSpeed = 50000; // 时钟速度,最大400kHz的时钟频率
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2; // 时钟占空比,只有在时钟频率大于100kHz,也就是进入到快速状态时才有用,小于100kHz,占空比是固定的1:1,
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable; //
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; // STM32作为从机,可以响应几位的地址
I2C_InitStructure.I2C_OwnAddress1 = 0x00; // 自身地址1,也是作为从机使用,
I2C_Init(I2C2, &I2C_InitStructure);
I2C_Cmd(I2C2,ENABLE);
// 写入一些寄存器对MPU6050硬件电路进行初始化配置
// 电源管理寄存器1:设备复位:0,不复位;睡眠模式:0,解除睡眠:循环模式:0,不循环;无关位i:0;温度传感器失能:0,不失能;最后三位选择时钟:000,选择内部时钟,001,选择x轴的陀螺仪时钟,
MPU6050_WriteReg(MPU6050_PWR_MGMT_1,0x01);// 解除睡眠,选择陀螺仪时钟
// 电源管理寄存器2:前两位,循环模式唤醒频率:00,不需要;后6位,每一个轴的待机位:全为0,不需要待机;
MPU6050_WriteReg(MPU6050_PWR_MGMT_2,0x00); // 均不待机
// 采样率分频:该8位决定了数据输出的快慢,值越小越快
MPU6050_WriteReg(MPU6050_SMPLRT_DIV,0x09);// 采样分频:10分频
// 配置寄存器:外部同步:全为0,不需要;数字低通滤波器:110,最平滑的滤波
MPU6050_WriteReg(MPU6050_CONFIG,0x06);// 滤波参数给最大
// 陀螺仪配置寄存器:前三位,自测使能:全为0,不自测;满量程选择:11,最大量程;后三位无关位:为0
MPU6050_WriteReg(MPU6050_GYRO_CONFIG,0x18);// 陀螺仪和加速度计都选最大量程
// 加速度计配置寄存器:前三位,自测使能:全为0,不自测;满量程选择:11,最大量程;后三位高通滤波器:用不到,为000
MPU6050_WriteReg(MPU6050_ACCEL_CONFIG,0x18);
}
// 获取芯片的ID号
uint8_t MPU6050_GetID(void)
{
return MPU6050_ReadReg(MPU6050_WHO_AM_I);
}
// 获取寄存器数据的函数,返回6个int16_t的数据,分别表示XYZ的加速度值和陀螺仪值
// 指针地址传递的方法,返回多值
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;
// 加速度计X
DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);
DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);
*AccX = (DataH << 8) | DataL; // 高8位左移8位,再或上低8位,得到加速度计X轴的16位数据
// 加速度计Y
DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);
DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);
*AccY = (DataH << 8) | DataL;
// 加速度计Z
DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);
DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);
*AccZ = (DataH << 8) | DataL;
// 陀螺仪X
DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);
DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);
*GyroX = (DataH << 8) | DataL;
// 陀螺仪Y
DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);
DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);
*GyroY = (DataH << 8) | DataL;
// 陀螺仪Z
DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);
DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);
*GyroZ = (DataH << 8) | DataL;
}
#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;// 接收XYZ轴的加速度值和陀螺仪值
int main(void)
{
OLED_Init();
MPU6050_Init();
OLED_ShowString(1,1,"ID:");
ID = MPU6050_GetID();
OLED_ShowHexNum(1, 4, ID, 2);
// // 指定地址写
// MyI2C_Start(); // 产生起始条件,开始一次传输
// // 主机首先发送一个字节,内容时从机地址+读写位,进行寻址
// MyI2C_SendByte(0xD0); // 1101 000 0,0代表即将进行写入操作
// // 发送一个字节后,要接收一下应答位,看看从机有没有收到刚才的数据
// uint8_t Ack = MyI2C_ReceiveAck();
// // 接收应答之后,要继续发送一个字节,写入寄存器地址
// MyI2C_Stop();
//
// OLED_ShowNum(1, 1, Ack, 3);
// // 指定地址读
// uint8_t ID = MPU6050_ReadReg(0X75);// 返回值是0x68
// OLED_ShowHexNum(1, 1, ID, 2);
// // 指定地址写,需要先解除睡眠模式,否则写入无效
// // 睡眠模式是电源管理寄存器1的这一位SLEEP控制的,把该寄存器写入0x00,解除睡眠模式
// // 该寄存器地址是0x6B
// MPU6050_WriteReg(0x6B, 0x00);
// // 采样率分频寄存器,地址是0x19,值的内容是采样分频
// MPU6050_WriteReg(0x19, 0xAA);
//
// uint8_t ID = MPU6050_ReadReg(0X19);
// OLED_ShowHexNum(1, 1, ID, 2);//显示0x19地址下的内容,应该是0xAA
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);
}
}