STM32F4软件IIC移植MPU9250记录【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. 视频

    作者:埋土人

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32F4软件IIC移植MPU9250记录【Hal库】

    发表回复