這個也是上學期一直困擾我的一個問題,上學期想要鞏固一下自己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採集到的三個角通過串口發送的數據畫成圖象表示出來了。
我均勻轉動三個角之後得到了上面的圖,紅綠藍三個顏色分別代表三個不同角的角度,由圖可以發現三個顏色分別明顯的轉動了兩個週期,可知實驗成功。