2.MPU6050模块¶

2.MPU6050模块¶

2.5.2. 硬件STM32-MPU6050例程介绍¶

初始化硬件I2C

本实验中的I2C驱动与MPU6050驱动分开主要是考虑到扩展其它传感器时的通用性,如使用磁场传感器、气压传感器都可以使用同样一个I2C驱动,

这个驱动只要给出针对不同传感器时的不同读写接口即可。关于STM32的I2C驱动原理请参考野火STM32教程《零死角玩转STM32》中读写EEPROM的章节,

本章讲解的I2C驱动主要针对接口封装讲解,细节不再赘述。本实验中的I2C硬件定义在 bsp_i2c.h 中,具体如下,这些宏根据传感器使用的I2C硬件封装起来了。

bsp_i2c.h(霸道开发板例程)¶

1

2

3

4

5

6

7

8

9

10 /**************************I2C参数定义,I2C1或I2C2********************************/

#define SENSORS_I2Cx I2C1

#define SENSORS_I2C_APBxClock_FUN RCC_APB1PeriphClockCmd

#define SENSORS_I2C_CLK RCC_APB1Periph_I2C1

#define SENSORS_I2C_GPIO_APBxClock_FUN RCC_APB2PeriphClockCmd

#define SENSORS_I2C_GPIO_CLK RCC_APB2Periph_GPIOB

#define SENSORS_I2C_SCL_PORT GPIOB

#define SENSORS_I2C_SCL_PIN GPIO_Pin_6

#define SENSORS_I2C_SDA_PORT GPIOB

#define SENSORS_I2C_SDA_PIN GPIO_Pin_7

接下来利用这些宏对I2C进行初始化,初始化过程与I2C读写EEPROM中的无异,见如下,

bsp_i2c.c¶

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53 static void I2C_GPIO_Config(void)

{

GPIO_InitTypeDef GPIO_InitStructure;

/* 使能与 I2C1 有关的时钟 */

SENSORS_I2C_APBxClock_FUN ( SENSORS_I2C_CLK, ENABLE );

SENSORS_I2C_GPIO_APBxClock_FUN ( SENSORS_I2C_GPIO_CLK, ENABLE );

/* PB6-I2C1_SCL、PB7-I2C1_SDA*/

GPIO_InitStructure.GPIO_Pin = SENSORS_I2C_SCL_PIN;

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; // 开漏输出

GPIO_Init(SENSORS_I2C_SCL_PORT, &GPIO_InitStructure);

GPIO_InitStructure.GPIO_Pin = SENSORS_I2C_SDA_PIN;

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; // 开漏输出

GPIO_Init(SENSORS_I2C_SDA_PORT, &GPIO_InitStructure);

}

/**

* @brief I2C 工作模式配置

* @param 无

* @retval 无

*/

static void I2C_Mode_Configu(void)

{

I2C_InitTypeDef I2C_InitStructure;

/* I2C 配置 */

I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;

/* 高电平数据稳定,低电平数据变化 SCL 时钟线的占空比 */

I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;

I2C_InitStructure.I2C_OwnAddress1 =I2Cx_OWN_ADDRESS7;

I2C_InitStructure.I2C_Ack = I2C_Ack_Enable ;

/* I2C的寻址模式 */

I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;

/* 通信速率 */

I2C_InitStructure.I2C_ClockSpeed = I2C_Speed;

/* I2C1 初始化 */

I2C_Init(SENSORS_I2Cx, &I2C_InitStructure);

/* 使能 I2C1 */

I2C_Cmd(SENSORS_I2Cx, ENABLE);

}

MPU6050的寄存器定义

MPU6050有各种各样的寄存器用于控制工作模式,我们把这些寄存器的地址、寄存器位使用宏定义到了 mpu6050.h 文件中了,代码如下。

mpu6050.h¶

1

2

3

4

5

6

7

8

9

10

11 //模块的A0引脚接GND,IIC的7位地址为0x68,若接到VCC,需要改为0x69

#define MPU6050_SLAVE_ADDRESS (0x68<<1) //MPU6050器件读地址

#define MPU6050_WHO_AM_I 0x75

#define MPU6050_SMPLRT_DIV 0 //8000Hz

#define MPU6050_DLPF_CFG 0

#define MPU6050_GYRO_OUT 0x43 //MPU6050陀螺仪数据寄存器地址

#define MPU6050_ACC_OUT 0x3B //MPU6050加速度数据寄存器地址

// ... 这里省略了很多MPU6050的寄存器宏定义

// ... ...

初始化MPU6050

根据MPU6050的寄存器功能定义,我们使用I2C往寄存器写入特定的控制参数,代码如下,

mpu6050.c¶

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43 /**

* @brief 写数据到MPU6050寄存器

* @param

* @retval

*/

void MPU6050_WriteReg(u8 reg_add,u8 reg_dat)

{

I2C_ByteWrite(reg_dat,reg_add);

}

/**

* @brief 从MPU6050寄存器读取数据

* @param

* @retval

*/

void MPU6050_ReadData(u8 reg_add,unsigned char* Read,u8 num)

{

I2C_BufferRead(Read,reg_add,num);

}

/**

* @brief 初始化MPU6050芯片

* @param

* @retval

*/

void MPU6050_Init(void)

{

int i=0,j=0;

//在初始化之前要延时一段时间,若没有延时,则断电后再上电数据可能会出错

for(i=0;i<1000;i++)

{

for(j=0;j<1000;j++)

{

;

}

}

MPU6050_WriteReg(MPU6050_RA_PWR_MGMT_1, 0x00); //解除休眠状态

MPU6050_WriteReg(MPU6050_RA_SMPLRT_DIV , 0x07); //陀螺仪采样率

MPU6050_WriteReg(MPU6050_RA_CONFIG , 0x06);

MPU6050_WriteReg(MPU6050_RA_ACCEL_CONFIG , 0x01); //配置加速度传感器工作在2G模式

MPU6050_WriteReg(MPU6050_RA_GYRO_CONFIG, 0x18); //陀螺仪自检及测量范围,典型值:0x18(不自检,2000deg/s)

}

这段代码首先使用 MPU6050_ReadData 及MPU6050_WriteReg 函数封装了I2C的底层读写驱动,接下来用它们在 MPU6050_Init 函数中向MPU6050寄存器写入控制参数,设置了MPU6050的采样率、量程(分辨率)等。

读传感器ID

初始化后,可通过读取它的“WHO AM I”寄存器内容来检测硬件是否正常,该寄存器存储了ID号0x68,见代码如下。

mpu6050.c¶

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18 /**

* @brief 读取MPU6050的ID

* @param

* @retval 正常返回1,异常返回0

*/

uint8_t MPU6050ReadID(void)

{

unsigned char Re = 0;

MPU6050_ReadData(MPU6050_RA_WHO_AM_I,&Re,1); //读器件地址

if (Re != 0x68) {

MPU_ERROR("检测不到MPU6050模块,请检查模块与开发板的接线");

return 0;

} else {

MPU_INFO("MPU6050 ID = %d\r\n",Re);

return 1;

}

}

读取原始数据

若传感器检测正常,就可以读取它数据寄存器获取采样数据了,见代码如下。

mpu6050.c¶

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54 /**

* @brief 读取MPU6050的加速度数据

* @param

* @retval

*/

void MPU6050ReadAcc(short *accData)

{

u8 buf[6];

MPU6050_ReadData(MPU6050_ACC_OUT, buf, 6);

accData[0] = (buf[0] << 8) | buf[1];

accData[1] = (buf[2] << 8) | buf[3];

accData[2] = (buf[4] << 8) | buf[5];

}

/**

* @brief 读取MPU6050的角加速度数据

* @param

* @retval

*/

void MPU6050ReadGyro(short *gyroData)

{

u8 buf[6];

MPU6050_ReadData(MPU6050_GYRO_OUT,buf,6);

gyroData[0] = (buf[0] << 8) | buf[1];

gyroData[1] = (buf[2] << 8) | buf[3];

gyroData[2] = (buf[4] << 8) | buf[5];

}

/**

* @brief 读取MPU6050的原始温度数据

* @param

* @retval

*/

void MPU6050ReadTemp(short *tempData)

{

u8 buf[2];

MPU6050_ReadData(MPU6050_RA_TEMP_OUT_H,buf,2); //读取温度值

*tempData = (buf[0] << 8) | buf[1];

}

/**

* @brief 读取MPU6050的温度数据,转化成摄氏度

* @param

* @retval

*/

void MPU6050_ReturnTemp(float*Temperature)

{

short temp3;

u8 buf[2];

MPU6050_ReadData(MPU6050_RA_TEMP_OUT_H,buf,2); //读取温度值

temp3= (buf[0] << 8) | buf[1];

*Temperature=((double) (temp3 /340.0))+36.53;

}

其中以上前三个函数:MPU6050ReadAcc、MPU6050ReadGyro 和 MPU6050ReadTemp 分别用于读取三轴加速度、角速度及温度值,这些都是原始的ADC数值(16位长),

对于加速度和角速度,把读取得的ADC值除以分辨率,即可求得实际物理量数值。

最后一个函数 MPU6050_ReturnTemp 展示了温度ADC值与实际温度值间的转换,它是根据MPU6050的说明给出的转换公式进行换算的,

注意陀螺仪检测的温度会受自身芯片发热的影响,严格来说它测量的是自身芯片的温度,所以用它来测量气温是不太准确的。

对于加速度和角速度值我们没有进行转换,在下一小节中我们直接利用这些数据交给 DMP 单元,求解出姿态角。

main函数

最后我们来看看本实验的main函数:

main.c¶

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46 /**

* @brief 主函数

* @param 无

* @retval 无

*/

int main(void)

{

/* LED 端口初始化 */

LED_GPIO_Config();

/* 串口通信初始化 */

USART_Config();

//I2C初始化

I2C_Bus_Init();

//MPU6050初始化

MPU6050_Init();

//检测MPU6050

if( MPU6050ReadID() == 0 )

{

printf("\r\n没有检测到MPU6050传感器!\r\n");

LED_RED;

while(1); //检测不到MPU6050 会红灯亮然后卡死

}

/* 配置SysTick定时器和中断 */

SysTick_Init(); //配置 SysTick 为 1ms 中断一次,在中断里读取传感器数据

SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk; //启动定时器

while(1)

{

if( task_readdata_finish ) //task_readdata_finish = 1 表示读取MPU6050数据完成

{

printf("加速度:%8d%8d%8d",Acel[0],Acel[1],Acel[2]);

printf(" 陀螺仪%8d%8d%8d",Gyro[0],Gyro[1],Gyro[2]);

printf(" 温度%8.2f\r\n",Temp);

task_readdata_finish = 0; // 清零标志位

}

}

}

在main函数里,调用 I2C_Bus_Init、MPU6050_Init 及 MPU6050ReadID 函数后,在 while 主循环中使用串口打印加速度、角速度及温度值到电脑端。

本实验中控制MPU6050并没有使用中断引脚进行检测,我们是利用 SysTick 定时器进行计时,每隔一段时间就会读取 MPU6050 的数据寄存器获取采样数据,并且将 task_readdata_finish 标志位置 1,

该标志位置 1 后就可以在主循环中将MPU6050的加速度、角速度及温度值打印出来,再将 task_readdata_finish 重新置0,等待它再一次被置 1、然后再一次打印数据。

那么标志位 task_readdata_finish 是在哪里被置位的呢?答案是在 SysTick 定时器中断服务函数当中。

我们在进入主循环之前就已经配置SysTick定时器和中断、并且启动了它,使得它每 1ms 它都会产生一次中断,去执行中断服务函数。该中断服务函数如下。

stm32f10x_it.c¶

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51 #define TASK_DELAY_NUM 2 //总任务个数,可以自己根据实际情况修改

#define TASK_DELAY_0 1000 //任务0延时 1000*1 毫秒后执行:翻转LED

#define TASK_DELAY_1 500 //任务1延时 500*1 毫秒后执行:MPU6050任务

uint32_t Task_Delay_Group[TASK_DELAY_NUM]; //任务数组,用来计时、并判断是否执行对应任务

/* 执行任务标志:读取MPU6050数据 */

// - 标志置 1表示读取MPU6050数据完成,需要在主循环处理MPU6050数据

// - 标志置 0表示未完成读取MPU6050数据,需要在中断中读取MPU6050数据

int task_readdata_finish;

void SysTick_Handler(void)

{

int i;

for(i=0; i

{

Task_Delay_Group[i] ++; //任务计时,时间到后执行

}

/* 处理任务0 */

if(Task_Delay_Group[0] >= TASK_DELAY_0) //判断是否执行任务0

{

Task_Delay_Group[0] = 0; //置0重新计时

/* 任务0:翻转LED */

LED2_TOGGLE;

}

/* 处理任务1 */

if(Task_Delay_Group[1] >= TASK_DELAY_1) //判断是否执行任务1

{

Task_Delay_Group[1] = 0; //置0重新计时

/* 任务1:MPU6050任务 */

if( ! task_readdata_finish )

{

MPU6050ReadAcc(Acel);

MPU6050ReadGyro(Gyro);

MPU6050_ReturnTemp(&Temp);

task_readdata_finish = 1; //标志位置1,表示需要在主循环处理MPU6050数据

}

}

/* 处理任务2 */

//添加任务需要修改任务总数的宏定义 TASK_DELAY_NUM

//并且添加定义任务的执行周期宏定义 TASK_DELAY_x(x就是一个编号),比如 TASK_DELAY_2

}

SysTick中断服务函数的代码中,我们用for语句将变量 Task_Delay_Group[1] 自加1,使用 TASK_DELAY_1 变量来控制定时时间,

当 Task_Delay_Group[1] >= TASK_DELAY_1 时说明定时时间到了,就将 Task_Delay_Group[1] 重新置0,让它重新计时,

并且读取 MPU6050 的加速度、角速度及温度数据后将标志位 task_readdata_finish 置 1,表示数据读取完成,

然后在执行完中断服务函数之后,我们就回到主循环把读取到的数据打印出来。