嵌入式开发--STM32用HAL库硬件I2C读取MPU6050的数据
MPU6050简介
MPU6050是一款6轴传感器,可检测3轴位移和3轴加速度,MCU可通过I2C接口操作。注意接口线需要上拉电阻,一般为10K,在距离远或干扰强的场合,可降低通讯速率,和适当减小电阻值,比如4.7K。
STM32的I2C接口
一般通过2种方式来操作I2C接口
软件I2C
控制GPIO引脚来模拟I2C的时序,这就是常说的软件I2C,也是应用得比较多的方式。正点原子的例程均以软件I2C方式。
这种方式可控性强,也便于理解I2C时序。但是增加了MCU的负担,操作周期会大于硬件方式,效率不高。
硬件I2C
用STM32自带的I2C硬件控制器来操作,也就是硬件I2C。
正点原子不推荐这种方式,理由是STM32的I2C不好用。网上也普遍流传说STM32的I2C容易死机等等,但这属于人云亦云的说法,我做过测试,证明STM32的I2C是完全可用的,具体测试和问题排查可以看我另一篇文章,《嵌入式开发-STM32硬件I2C驱动OLED屏》。
本文就以硬件I2C的方式来进行操作。
MPU6050电路
我是将MPU6050做在一块扩展板上,再通过数据线与MCU的主控板相连,原理图如下:
第9脚是用来设置MPU6050的设备地址的,接地后,设备地址是0x68,但是在I2C协议中是用高7位表示设备地址,所以0x68需要左移一位,就变成了0xD0,最低位为1表示读操作,最低位为0表示写操作。
也就是最后的地址是0xD1(读)和0xD0(写)
CubeMX设置
用的是PC8和PC9引脚
开启I2C3,默认的基本设置
在GPIO标签,需要将引脚速度设置为高或非常高
用到的函数
是用正点原子的例程改的,其实改动也就是读和写的相关函数,总共就4个。
读,写,连续读,连续写。而且只改写这4个函数的内容,接口保持不变。
HAL库提供可以用的函数有4个,分为mem和master,具体如下:
HAL_I2C_Master_Transmit()
HAL_I2C_Master_Receive()
HAL_I2C_Mem_Read()
HAL_I2C_Mem_Write()
可以只用前2个函数,也可以只用后2个,最后的效果基本等价,这里讲一下他们的区别。
以读操作为例,代码如下:
u8 MPU_Read_Byte(u8 reg)
{
u8 data;
HAL_I2C_Mem_Read(&hi2c3, MPU6050_WRITE_ADDR, MPU_DEVICE_ID_REG, 1, &data, 1, 100);
HAL_Delay(1); //上下代码二选一,同样功能,在此只是为了演示方便
HAL_I2C_Master_Transmit(&hi2c3, MPU6050_WRITE_ADDR, ®, 1, 100);
HAL_I2C_Master_Receive(&hi2c3, MPU6050_READ_ADDR, &data, 1, 100);
return data;
}
接下来演示一下读ID号的波形差别。
MPU_Read_Byte(MPU_DEVICE_ID_REG);
可以看到mem函数一行就可以了,而master函数需要2行,mem函数要简洁一些,用逻辑分析仪看二者的波形如下:先是mem后master
可以看到二者时序略有不同,但功能是一样的。最终的运行时间也只相差了半个I2C时钟周期而已,可以忽略。
全部代码
修改的代码如下,只改了这4个函数,其他参照正点原子。
#define MPU6050_I2C hi2c3
//AD0引脚(9脚)接低,表示0x68为地址,再左移1位为0xd0,最低位表示读写选择
#define MPU_ADDR 0X68
#define MPU6050_READ_ADDR (MPU_ADDR<<1)|1
#define MPU6050_WRITE_ADDR (MPU_ADDR<<1)|0
//I2C写一个字节
//reg:寄存器地址
//data:数据
//返回值:0,正常
// 其他,错误代码
u8 MPU_Write_Byte(u8 reg,u8 data)
{
return HAL_I2C_Mem_Write(&MPU6050_I2C, MPU6050_WRITE_ADDR, reg, 1, &data, 1, 100);
}
//I2C读一个字节
//reg:寄存器地址
//返回值:读到的数据
u8 MPU_Read_Byte(u8 reg)
{
u8 data;
HAL_I2C_Mem_Read(&MPU6050_I2C, MPU6050_WRITE_ADDR, MPU_DEVICE_ID_REG, 1, &data, 1, 100);
// HAL_Delay(1);
//
// HAL_I2C_Master_Transmit(MPU6050_I2C, MPU6050_WRITE_ADDR, ®, 1, 100);
// HAL_I2C_Master_Receive(MPU6050_I2C, MPU6050_READ_ADDR, &data, 1, 100);
return data;
}
//IIC连续写
//addr:器件地址
//reg:寄存器地址
//len:写入长度
//buf:数据区
//返回值:0,正常
// 其他,错误代码
u8 MPU_Write_Len(u8 addr,u8 reg,u8 len,u8 *buf)
{
return HAL_I2C_Mem_Write(&MPU6050_I2C, MPU6050_WRITE_ADDR, reg, 1, buf, len, 100);
}
//IIC连续读
//addr:器件地址
//reg:要读取的寄存器地址
//len:要读取的长度
//buf:读取到的数据存储区
//返回值:0,正常
// 其他,错误代码
u8 MPU_Read_Len(u8 addr,u8 reg,u8 len,u8 *buf)
{
HAL_I2C_Mem_Read(&MPU6050_I2C, MPU6050_WRITE_ADDR, reg, 1, buf, len, 100);
return 0;
}