这个也是上学期一直困扰我的一个问题,上学期想要巩固一下自己PWM和PID算法的基础,就搞了一个STM32控制的平衡车来玩,算法数学模型啥的都弄好之后,就出现了一个大问题,本人用的是stm32cubeMX来建立的工程项目,所以在IIC的部分就出现了一些问题,我看了写资料,总结了一下经验,下面就先讲讲我的主要经验:
1.MPU6050模块是什么?
MPU6050模块是一个常用的六轴传感器模块,主要目的是获取以传感器为基点的欧拉角(偏航角、俯仰角、滚转角),可以理解为以传感器为中点,初始X正半轴与当前传感器前方所指的一个向量在XY平面的夹角,初始X正半轴与当前传感器在XZ平面的夹角,初始Y正半轴与当前传感器在YZ平面的夹角。这个想仔细理解的玩家朋友可以看看MPU6050的手册。该模块用过IIC总线和STM32进行通信。
2.MPU6050用来干嘛?
在文章开头我说我买了个stm32的平衡车,可以理解为硬件已经完全搭建好了,说到平衡车那就是普通的两个轮子那种,两个轮子不转,车肯定就站不稳,要车站稳的话就肯定要知道车的当前状态和什么情况是稳,什么情况是不稳,所以就用到了MPU6050的俯仰角。
3.MPU6050怎么用?
东西怎么用,还是要看芯片文档,现在就稍微总结一下MPU6050怎么用的,首先是通信方式,芯片把当前数据测量出来之后,就需要通过IIC总线发送给MCU进行处理,这里我用的是STM32F767作为处理芯片,在之前的使用中,我通过使能stm32cube的IIC来移植github上某大神已经写好的MPU6050驱动,发现卡在fifo的频率设置上,频率设置不能超过40,但是这个满足不了小车的稳定,使用上还存在各种问题,所以干脆就用了模拟IIC,使用下来效果不错,所以在MPU6050的问题上我就直接使用模拟IIC了,如果有大神可以用硬件IIC实现的话请不吝赐教。
通信方式解决之后就要知道怎么获取三个角了,首先是MPU6050模块自带了DMP姿态解算,通过MPU官方库的程序读取fifo中的四元数组quat后,将格式转为浮点型,通过除以官方给出的q30格式long转化为float的除数后进行对应运算来得到当前的三个角。
主要使用方法就是在模拟IIC总线调整到可以使用之后,移植官方的DMP库
,官方的MPU6050驱动之后在主函数进行MPU6050以及DMP初始化之后即可通过mpu_dmp_get_data来读取当前三个角的度数。
下面我把我使用的模拟iic的代码贴出来,使用的时候只需要更改c文件和h文件的gpio引脚即可
iicb.c
#include "iicb.h"
static void delay_us(int s)//微秒延时函数,试出来的
{
volatile int i = 7*s;
while (i)
i--;
}
//初始化IIC
void IIC_InitB(void)
{
GPIO_InitTypeDef GPIO_Initure;
__HAL_RCC_GPIOB_CLK_ENABLE();
GPIO_Initure.Pin=GPIO_PIN_6|GPIO_PIN_7;
GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP;
GPIO_Initure.Pull=GPIO_PULLUP;
GPIO_Initure.Speed=GPIO_SPEED_FAST;
HAL_GPIO_Init(GPIOB,&GPIO_Initure);
IIC_SDAB(1);
IIC_SCLB(1);
}
//IIC起始信号
void IIC_StartB(void)
{
SDA_OUTB();
IIC_SDAB(1);
IIC_SCLB(1);
delay_us(4);
IIC_SDAB(0);
delay_us(4);
IIC_SCLB(0);
}
//IIC停止信号
void IIC_StopB(void)
{
SDA_OUTB();//sda线输出
IIC_SCLB(0);
IIC_SDAB(0);//STOP:when CLK is high DATA change form low to high
delay_us(4);
IIC_SCLB(1);
IIC_SDAB(1);//发送I2C总线结束信号
delay_us(4);
}
//等待应答信号到来 1.接收失败,2.成功
uint8_t IIC_Wait_AckB(void)
{
uint8_t ErrTime=0;
SDA_INB(); //SDA设置为输入
IIC_SDAB(1);delay_us(1);
IIC_SCLB(1);delay_us(1);
while(READ_SDAB)
{
ErrTime++;
if(ErrTime>250)
{
IIC_StopB();
return 1;
}
}
IIC_SCLB(0);
return 0;
}
//Ack应答信号
void IIC_AckB(void)
{
IIC_SCLB(0);
SDA_OUTB();
IIC_SDAB(0);
delay_us(2);
IIC_SCLB(1);
delay_us(2);
IIC_SCLB(0);
}
//Ack拒绝应答
void IIC_NAckB(void)
{
IIC_SCLB(0);
SDA_OUTB();
IIC_SDAB(1);
delay_us(2);
IIC_SCLB(1);
delay_us(2);
IIC_SCLB(0);
}
//IIC发送一个字节,从机有应答返回1,无应答返回0
void IIC_Send_ByteB(uint8_t txb)
{
uint8_t t;
SDA_OUTB();
IIC_SCLB(0);//拉低时钟开始数据传输
for(t=0;t<8;t++)
{
IIC_SDAB((txb&0x80)>>7);
txb<<=1;
delay_us(2);
IIC_SCLB(1);
delay_us(2);
IIC_SCLB(0);
delay_us(2);
}
}
//读1个字节 ack=1时,发送Ack,ack=0,发送NAck
uint8_t IIC_Read_ByteB(unsigned char ack)
{
unsigned char i,receive=0;
SDA_INB();//SDA设置为输入
for(i=0;i<8;i++ )
{
IIC_SCLB(0);
delay_us(2);
IIC_SCLB(1);
receive<<=1;
if(READ_SDAB)receive++;
delay_us(1);
}
if (!ack)
IIC_NAckB();//发送NAck
else
IIC_AckB(); //发送Ack
return receive;
}
iicb.h
#ifndef __IICB_H
#define __IICB_H
#include "main.h"
//IO方向设置
#define SDA_INB() {GPIOB->MODER&=~(3<<(7*2));GPIOB->MODER|=0<<(7*2);} //PB7输入模式
#define SDA_OUTB() {GPIOB->MODER&=~(3<<(7*2));GPIOB->MODER|=1<<(7*2);} //PB7输出模式
//IO操作
#define IIC_SCLB(n) (n?HAL_GPIO_WritePin(GPIOB,GPIO_PIN_6,GPIO_PIN_SET):HAL_GPIO_WritePin(GPIOB,GPIO_PIN_6,GPIO_PIN_RESET)) //SCL
#define IIC_SDAB(n) (n?HAL_GPIO_WritePin(GPIOB,GPIO_PIN_7,GPIO_PIN_SET):HAL_GPIO_WritePin(GPIOB,GPIO_PIN_7,GPIO_PIN_RESET)) //SDA
#define READ_SDAB HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_7) //输入SDA
//IIC所有操作函数
void IIC_InitB(void); //初始化IIC的IO口
void IIC_StartB(void); //IIC开始信号
void IIC_StopB(void); //IIC停止信号
void IIC_Send_ByteB(uint8_t txb); //IIC发送一个字节
uint8_t IIC_Read_ByteB(unsigned char ack); //IIC读取一个字节
uint8_t IIC_Wait_AckB(void); //IIC等待ACK信号
void IIC_AckB(void); //IIC发送ACK信号
void IIC_NAckB(void); //IIC不发送ACK信号
void IIC_Write_One_ByteB(uint8_t daddr,uint8_t addr,uint8_t data);
uint8_t IIC_Read_One_ByteB(uint8_t daddr,uint8_t addr);
#endif
移植完iic之后记得测试一下再进行下面的操作,具体测试可以用逻辑分析仪搞一下。
IIC测试完成之后就可以将MPU6050的驱动代码拿来用了,用的时候记得把MPU_Write_Byte类似的需要IIC驱动的函数处理一下,使用之前测试好的IIC发送对应的MPU处理函数。
使用模拟IIC的MPU驱动在网上也可以找到,大家就可以自己找找看。
之后在main函数完成MPU6050初始化和DMP的初始化之后就能直接使用mpu_dmp_get_data函数来获取三个角了,下面是我的主函数,写的很简单,仅供参考(代码是使用stm32cube生成的,没有使能硬件iic);
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* <h2><center>© Copyright (c) 2020 STMicroelectronics.
* All rights reserved.</center></h2>
*
* This software component is licensed by ST under BSD 3-Clause license,
* the "License"; You may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
* opensource.org/licenses/BSD-3-Clause
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "usart.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "stdio.h"
#include "mpu6050B.h"
#include "inv_mpuB.h"
#include "inv_mpu_dmp_motion_driverB.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
float Pitch=0;
float Roll=0;
float Yaw=0;
int mpu_work_flag=0;
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
int t=0;
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
printf("Gas\r\n");
printf("%d\r\n",MPU6050_InitB());
printf("%d\r\n",mpu_dmp_initB());
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
if(!mpu_dmp_get_dataB(&Pitch,&Roll,&Yaw))
printf("%d,%f,%f,%f\r\n",t,Pitch,Roll,Yaw);
t++;
HAL_Delay(200);
HAL_GPIO_TogglePin(GPIOB, LED0_Pin);
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};
/** Configure the main internal regulator output voltage
*/
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE3);
/** Initializes the CPU, AHB and APB busses clocks
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLM = 25;
RCC_OscInitStruct.PLL.PLLN = 288;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
RCC_OscInitStruct.PLL.PLLQ = 2;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB busses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV2;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_USART1;
PeriphClkInitStruct.Usart1ClockSelection = RCC_USART1CLKSOURCE_PCLK2;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK)
{
Error_Handler();
}
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
tex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
接下来是结果演示,我用了serialchart来把MPU6050采集到的三个角通过串口发送的数据画成图象表示出来了。
我均匀转动三个角之后得到了上面的图,红绿蓝三个颜色分别代表三个不同角的角度,由图可以发现三个颜色分别明显的转动了两个周期,可知实验成功。
来源:CSDN
作者:ASWaterbenben
链接:https://blog.csdn.net/ASWaterbenben/article/details/104663080