【STM32】MPU9250移植记录(软件IIC,STM32F4,Hal)

文章还没有完结……

1. 废话

五一稍微空一点了,做了一下MPU9250移植。这是去年8月就想做的事情,但因为种种原因,一直拖到了现在。一方面是比较忙,另一方面是上次6050DMP移植花了整整三天时间,有点阴影了。但是机器人省赛将近,不能再拖了。

简单浏览了一下,在网上没有找到特别合适的教程。大部分资料都是正点原子的,但是因为我的代码都不是他们框架的,所以之前的移植都被狠狠地折磨了。又是sys.h,又是iic里面一堆冗余的函数,又是好多个delay函数,最后还有莫名其妙的结算缓慢的现象。故这次决定不再使用他们家的代码。

2. 资源

在github上简单搜索了一下,找到下面这个资源,看了一下,代码还是比较简洁的。虽然是硬件IIC,但是简单改一下就可以转换为软件IIC

MPU9250-DMP-STM32

在这里插入图片描述

3. 开始移植

(1)首先是最基本的下载文件,解压缩,创建文件夹,keil包含路径,添加文件。


(2)阐述一下几个文件的功能:

在这里插入图片描述

  • stm32_mpu9250_i2c: 撰写底层的IIC函数,如收发等。需要移植硬件IIC或者标准库的话,主要修改的就是这里的内容。
  • MPU9250-DMP: 用户接口,最后执行角度读取的任务就是调用这里的函数。
  • 其他:几乎不用修改的部分。我们只需要管硬件层和应用层即可,中间的主要为算法以及寄存器部分。

(3)stm32_mpu9250_i2c.cstm32_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);
  }

(9) F4板子不在身边,所以后续内容等我测试完并且空闲下来再更新吧,估计要好久之后了……


4. 视频

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值