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,表示数据读取完成, 然后在执行完中断服务函数之后,我们就回到主循环把读取到的数据打印出来。