文章还没有完结……
1. 废话
五一稍微空一点了,做了一下MPU9250移植。这是去年8月就想做的事情,但因为种种原因,一直拖到了现在。一方面是比较忙,另一方面是上次6050DMP移植花了整整三天时间,有点阴影了。但是机器人省赛将近,不能再拖了。
简单浏览了一下,在网上没有找到特别合适的教程。大部分资料都是正点原子的,但是因为我的代码都不是他们框架的,所以之前的移植都被狠狠地折磨了。又是sys.h,又是iic里面一堆冗余的函数,又是好多个delay函数,最后还有莫名其妙的结算缓慢的现象。故这次决定不再使用他们家的代码。
2. 资源
在github上简单搜索了一下,找到下面这个资源,看了一下,代码还是比较简洁的。虽然是硬件IIC,但是简单改一下就可以转换为软件IIC。
3. 开始移植
(1)首先是最基本的下载文件,解压缩,创建文件夹,keil包含路径,添加文件。
(2)阐述一下几个文件的功能:
stm32_mpu9250_i2c
: 撰写底层的IIC函数,如收发等。需要移植硬件IIC或者标准库的话,主要修改的就是这里的内容。MPU9250-DMP
: 用户接口,最后执行角度读取的任务就是调用这里的函数。其他
:几乎不用修改的部分。我们只需要管硬件层和应用层即可,中间的主要为算法以及寄存器部分。
(3)stm32_mpu9250_i2c.c
和 stm32_mpu9250_i2c.c
内容填充
因为原来作者使用的是Hal的硬件IIC,因此,我把这部分全部换成自己的东西。函数命名很随意,大家自己改一下吧。
stm32_mpu9250_i2c.c
#include "stm32_mpu9250_i2c.h"
void MYIIC_Delayus(uint32_t u_sec)
{
uint16_t cnt = 0;
while(u_sec--)
{
for(cnt=0; cnt>0; cnt--);
}
}
void MYIIC_W_SDA(uint8_t i)
{
if(i==0)
{
IIC_SDA(GPIO_PIN_RESET);
// MYIIC_Delayus(1);
}
else if(i==1)
{
IIC_SDA(GPIO_PIN_SET);
// MYIIC_Delayus(1);
}
}
void MYIIC_W_SCL(uint8_t i)
{
if(i==0)
{
IIC_SCL(GPIO_PIN_RESET);
// MYIIC_Delayus(1);
}
else if(i==1)
{
IIC_SCL(GPIO_PIN_SET);
// MYIIC_Delayus(1);
}
}
uint8_t MYIIC_R_SDA(void)
{
uint8_t BitVal;
if(IIC_SDA_R()==GPIO_PIN_SET)
BitVal=1;
else if(IIC_SDA_R()==GPIO_PIN_RESET)
BitVal=0;
// MYIIC_Delayus(1);
return BitVal;
}
void MYIIC_Start(void)
{
MYIIC_W_SDA(1);
MYIIC_W_SCL(1);
MYIIC_W_SDA(0);
MYIIC_W_SCL(0);
}
void MYIIC_Stop(void)
{
MYIIC_W_SDA(0);
MYIIC_W_SCL(1);
MYIIC_W_SDA(1);
}
/* following function are needed in transplant of DMP */
void MYIIC_SendByte(uint8_t Byte)
{
uint8_t i;
for (i = 0; i < 8; i ++)
{
if(Byte & (0x80 >> i))
MYIIC_W_SDA(1);
else
MYIIC_W_SDA(0);
MYIIC_W_SCL(1);
MYIIC_W_SCL(0);
}
}
//return 1:success 0:failed
uint8_t IIC_Wait_Ack(void)
{
uint8_t ucErrTime=0;
MYIIC_W_SDA(1);
MYIIC_W_SCL(1);
while(MYIIC_R_SDA())
{
ucErrTime++;
if(ucErrTime>100)
{
MYIIC_Stop();
return 0;
}
}
MYIIC_W_SCL(0);
return 1;
}
void MYIIC_SendAck(uint8_t AckBit) //0——ack 1——nack
{
MYIIC_W_SDA(AckBit);
MYIIC_W_SCL(1);
MYIIC_W_SCL(0);
}
uint8_t IIC_Read_Byte(unsigned char ack)
{
unsigned char i,receive=0;
for(i=0;i<8;i++ )
{
MYIIC_W_SCL(0);
MYIIC_W_SCL(1);
receive<<=1;
if(MYIIC_R_SDA())
receive++;
}
if (ack)
MYIIC_SendAck(0); //send ACK
else
MYIIC_SendAck(1); //send nACK
return receive;
}
int stm32_i2c_write(uint8_t dev, uint8_t reg, uint8_t length, uint8_t* data)
{
int count = 0;
MYIIC_Start();
MYIIC_SendByte(dev); //发送写命令
IIC_Wait_Ack();
MYIIC_SendByte(reg); //发送地址
IIC_Wait_Ack();
for(count=0;count<length;count++){
MYIIC_SendByte(data[count]);
IIC_Wait_Ack();
}
MYIIC_Stop();//产生一个停止条件
return 1; //status == 0;
}
int stm32_i2c_read(uint8_t dev, uint8_t reg, uint8_t length, uint8_t* data)
{
int count = 0;
MYIIC_Start();
MYIIC_SendByte(dev); //发送写命令
IIC_Wait_Ack();
MYIIC_SendByte(reg); //发送地址
IIC_Wait_Ack();
MYIIC_Start();
MYIIC_SendByte(dev+1); //进入接收模式
IIC_Wait_Ack();
for(count=0;count<length;count++)
{
if(count!=length-1)data[count]=IIC_Read_Byte(1); //带ACK的读数据
else data[count]=IIC_Read_Byte(0); //最后一个字节NACK
}
MYIIC_Stop(); //产生一个停止条件
return count;
}
stm32_mpu9250_i2c.h(记得把宏改成自己的引脚)
#include "main.h"
#ifndef _STM32_MPU9250_I2C_H_
#define _STM32_MPU9250_I2C_H_
#define I2C_SCL_Pin GPIO_PIN_9
#define I2C_SCL_GPIO_Port GPIOB
#define I2C_SDA_Pin GPIO_PIN_10
#define I2C_SDA_GPIO_Port GPIOB
#define IIC_SCL(a) HAL_GPIO_WritePin(IIC_GPIO_PORT, IIC_SCL_PIN, a)
#define IIC_SDA(a) HAL_GPIO_WritePin(IIC_GPIO_PORT, IIC_SDA_PIN, a)
#define IIC_SDA_R() HAL_GPIO_ReadPin(IIC_GPIO_PORT, IIC_SDA_PIN)
int stm32_i2c_write(uint8_t dev, uint8_t reg,
uint8_t length, uint8_t * data);
int stm32_i2c_read(uint8_t dev, uint8_t reg,
uint8_t length, uint8_t * data);
#endif // _STM32_MPU9250_I2C_H_
(4)为系统添加宏
第一次编译后的报错来自inv_mpu.h
此处:
#elif defined EMPL_TARGET_STM32F4
void (*cb)(void);
#endif
在工程设置中添加如下宏(EMPL_TARGET_STM32F4根据自己设备调整):
MPL_LOG_NDEBUG=1,EMPL,MPU9250,EMPL_TARGET_STM32F4
(5)dmpmap.h
小改
第二次编译后这么报错:
…\MPU9250\dmpmap.h(6): error: #37: the #endif for this directive is missing
就是字面意思,不要想复杂了,第一话 #ifndef DMPMAP_H
,没有对应的 #endif
。总共需要两个,那么我们再补上一个。
(6)MPU9250-DMP.c
替换
第四次编译,如是报错:
Project\Project.axf: Error: L6218E: Undefined symbol constrain (referred from mpu9250-dmp.o).
Project\Project.axf: Error: L6218E: Undefined symbol constrainU16 (referred from mpu9250-dmp.o).
Project\Project.axf: Error: L6218E: Undefined symbol dmpEnableFeatures (referred from mpu9250-dmp.o).
Project\Project.axf: Error: L6218E: Undefined symbol dmpGetEnabledFeatures (referred from mpu9250-dmp.o).
看了好久才发现,h文件里的相关内容都是MPU9250_constrain
这种,而c文件里面却没有头衔constrain
。
请使用快捷键Ctrl + F,如上搜索MPU9250-DMP.c
文件(尽量搜整个工程,不过这步错误都出现在这个c文件里面),做以下替换:
constrain 替换为 MPU9250_constrain
constrainU16 替换为 MPU9250_constrainU16
dmpEnableFeatures 替换为 MPU9250_dmpEnableFeatures
dmpGetEnabledFeatures 替换为 MPU9250_dmpGetEnabledFeatures
github的分享者到底是怎么把代码跑起来的……
(7)MPU9250-DMP.h
变量搬家
第四次编译,就没什么问题了。
我很想调用了,所以做了一些准备工作,在main.h
里面声明了外部变量:
extern float pitch_inside, roll_inside, yaw_inside;
之后进行第五次编译,出现462个报错😅,发现都是这种:
Project\Project.axf: Error: L6200E: Symbol yaw_inside multiply defined (by stm32_mpu9250_i2c.o and main.o).
观察这些变量发现,它们都被定义在MPU9250-DMP.h
里面,然后会被MPU9250-DMP.c
调用。现在我还要在main.h
里面声明,然后在main.c
里面调用,可能由此产生了重复定义。
我选择把下面这坨变量从MPU9250-DMP.h
移动到MPU9250-DMP.c
里面,我是一直这么做的,但不知道这是不是最优雅的做法。
unsigned short _aSense;
float _gSense, _mSense;
int ax, ay, az;
int gx, gy, gz;
int mx, my, mz;
long qw, qx, qy, qz;
long temperature;
unsigned long time_inside;
float pitch_inside, roll_inside, yaw_inside;
float heading;
(8)在main中调用
作者没有教怎么调用,但是通过各h文件的包含关系,我们可以知道MPU9250_DMP
就是一切的终点。所以我把两个文件发给GPT,让它教我使用。给出了如下代码,添加到main函数中去。第六次编译没有任何报错。
// 初始化MPU9250
if (MPU9250_begin() != INV_SUCCESS) {
printf("Unable to initialize MPU9250\n");
return -1;
}
// 启用DMP特性和设置FIFO采样率
unsigned short dmpFeatures = DMP_FEATURE_6X_LP_QUAT | DMP_FEATURE_SEND_RAW_ACCEL | DMP_FEATURE_SEND_CAL_GYRO | DMP_FEATURE_GYRO_CAL;
if (MPU9250_dmpBegin(dmpFeatures, 50) != INV_SUCCESS) { // 50Hz FIFO rate
printf("Failed to initialize DMP\n");
return -1;
}
// 启用中断,这样每当有新数据可读时,MPU9250会通知MCU
if (MPU9250_enableInterrupt(1) != INV_SUCCESS) {
printf("Failed to enable interrupts\n");
return -1;
}
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
// 检查是否有新的数据
if (MPU9250_dataReady()) {
// 读取所有传感器数据
if (MPU9250_update(UPDATE_ACCEL | UPDATE_GYRO | UPDATE_COMPASS) != INV_SUCCESS) {
printf("Failed to read sensor data\n");
}
// 计算欧拉角
MPU9250_computeEulerAngles(true);
// 输出计算得到的欧拉角
printf("Pitch: %f degrees\n", pitch_inside);
printf("Roll: %f degrees\n", roll_inside);
printf("Yaw: %f degrees\n", yaw_inside);
}
HAL_Delay(200);
}