STM32 HAL 硬件IIC驱动BNO085(附上源码)

做大创买的BNO085,可后来换了IMU,我就打算拿这个来做飞控得了,反正这么好的IMU不用白不用。

之前问过tb商家要资料,没有stm32的;也找过很多文章,没找到一篇很详细说怎么移植这个IMU的(详细的都要VIP,我就算了,就当作学习分享吧)。可能是这个IMU太贵了吧,不过贵也有它贵的原因,精度高嘛(用mpu6050实在绷不住了,自己用卡尔曼滤波做姿态解算但是缺个磁力计,计算不了Yaw)。

我试过用软件IIC,后来失败了。现在放假,还是不甘心,又想尝试用硬件IIC来做了。没想到这次一晚上就搞定了,果然还是HAL库的硬件IIC强。

本来也想用esp32来驱动的,但我的esp32一用串口打印就会报错(我也不知道为什么,希望看到的大佬可以帮我解释一下),干脆不用esp32了,就用stm32(我也经常用stm32,比较熟悉)。

源码在后面。

代码移植这里的,改一改就可以用了。开源BNO085陀螺仪读取数据 – 全国大学生智能汽车竞赛,清华卓老师 国芯技术交流网站 – AI32位8051交流社区

1. RCC,选外部晶振,时钟频率100MHz(我的是STM32F411CEU6)。

2. SYS里面打开Serial Wire,用于stlink烧录程序(不用管TIM11,我这是打开了FreeRTOS,所以要选择其它时钟)。

3. IIC配置,使用的是IIC1,PB6是SCL,PB7是SDA,选择Fast Mode(手册说支持100k和400k,都用硬件IIC了,用就用最快的)。

4. 打开串口1(调试看数据的)

5. 项目管理,选好存放项目的路径

(这里的IDE要注意一下,因为我用的是vscode而不是Keil,所以选的是Makefile,用Keil的要选MDK-ARM。也想用vscode的话,可以看这个视频,这里的重点不是这个。抛弃keil?VScode开发stm32完整教程_哔哩哔哩_bilibili)

勾上这些

然后生成项目。

6. 下载好开头的文件,将这2个文件复制到自己的项目里面

这个是我的项目。

7. 在main.h里面include一下”bno08x.h“,然后编译。

报错1

改为”#include "main.h"“,再编译。

报错2

删掉它,再编译。

报错3,一大堆,这个不慌。

删掉这个delay。

再划到IIC部分,这些typedef删掉。

保留void I2C_Init(void),把下面每个IIC的时序删掉,void WriteNbyte(u8 *p, u8 number)和void ReadNbyte(u8 *p, u8 number)里面的时序也删掉,再编译。

这次没报错,但是有个问题,就是delay被我删掉了,所以要把delay改成HAL_Delay()。ctrl+F查找delay,选上替换(replace),填入HAL_Delay。只有4个,全部替换,再编译。

换完之后再编译,没问题,继续改代码。

8. include一下“i2c.h”

9. 修改void I2C_Init(void) 、void WriteNbyte(u8 *p, u8 number)和void ReadNbyte(u8 *p, u8 number)

在头文件先添加这2个地址,这个是BNO085的读和写地址。

再回到c文件,将3个函数改成下面这样。

编译,没毛病。

10. 点开头文件,注释掉这2个有虚线的函数。

11.  回到main里面, 初始化一下IIC

12. 复位一下,然后这些enable全都用上一下,全部填100(这个是IMU发数据的时间间隔,以ms为单位),开一下校准

可以只选自己想要的数据,这里我懒得选了,全都用上。可以参考手册说的频率来设置。

13. 读一下加速度的数据,大循环写下面的代码,编译烧录。

(这里要先重定向一下串口

这个是Keil的

【STM32】HAL库三步实现串口重定向(代码复制可用)_stm32串口重定向hal库-CSDN博客

这个是vscode的

基于 VsCode + GCC + STM32 环境下的串口输入输出重定向_gcc libc 重定向-CSDN博客)

14. 打开VOFA+看一下数据

没有数据,这里也别慌,这个是用vscode打印浮点数的毛病,

打开”构建配置:GCC“的“构建器选型”,在“编译器附加选项”添加“ -u_printf_float”

修改后(一定要点一下“全部保存”,不然不会自动保存)

之后再编译烧录。

可以打印了,但是数据好像有点问题。

有255的,这说明数据类型转换出了问题,跳到getAccelX()的定义

再跳到qToFloat的定义

这里看起来好像是qPoint的类型有问题,我改过之后,还是不行。后来我想到,int类型在不同的单片机或者不同的系统代表的数据类型不一样,可能是int16_t也可能是int32_t,所以我打印了一下fixedPointValue。先注释掉大循环里面的打印,在这里面打印一下fixedPointValue看看。

发现有65531这种接近65535的数据,说明确实是我的单片机的int代表的是int32_t类型,将fixedPointValue的类型改为int16_t,头文件的声明也要改一下,编译烧录。

这个看起来正常多了。去大循环打印一下加速度看看。

现在就正常了。

15. 读一下四元数,解算一下姿态角。第1个旋转向量就是四元数,只需要打开第1个enable,四元数转欧拉角的公式问一下GPT就可以了,挺简单的。这里要注意的是要单位转换一下,因为atan2和asin返回的数据是以rad为单位的,要乘一下57.3(就是180/pi)。编译烧录。

可以看到解算到的姿态角,这个姿态角的yaw可能是静止参考系的,每次复位都是有一个角度。

下面是源码(我一开始开了FreeRTOS,我把调度器给注释了才能到大循环的,这里要注意一下):

mian.c

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2024 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "cmsis_os.h"
#include "dma.h"
#include "i2c.h"
#include "spi.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

/* 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 */
__IO uint8_t InitOK = 0;

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
void MX_FREERTOS_Init(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 */

  /* 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_DMA_Init();
  MX_TIM4_Init();
  MX_SPI1_Init();
  MX_USART2_UART_Init();
  MX_TIM10_Init();
  MX_TIM2_Init();
  MX_TIM3_Init();
  MX_USART1_UART_Init();
  MX_USART6_UART_Init();
  MX_I2C1_Init();
  /* USER CODE BEGIN 2 */
  OLED_Init( OLED_ADDR_B );

  I2C_Init();  // BNO085 IIC 初始化

  uint8_t data;
  HAL_UART_Receive_IT(&huart2, &data, 1);
  printf("Init OK!!!\r\n");
  /* USER CODE END 2 */

  /* Call init function for freertos objects (in cmsis_os2.c) */
  // MX_FREERTOS_Init();

  /* Start scheduler */
  // osKernelStart();

  /* We should never get here as control is now taken by the scheduler */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */

  float q0, q1, q2, q3;
  float roll, pitch, yaw;
  // 重置传感器
  softReset();

  enableRotationVector(1);
  // enableGameRotationVector(100);
  // enableAccelerometer(100);
  // enableLinearAccelerometer(100);
  // enableGyro(100);
  // enableMagnetometer(100);
  // enableStepCounter(100);
  // enableStabilityClassifier(100);

  // 开始校准(可选)
  calibrateAll();

  while (1)
  {
  
    if ( dataAvailable() )
    {
      q0 = getQuatReal();
      q1 = getQuatI();
      q2 = getQuatJ();
      q3 = getQuatK();

      roll = atan2( 2 * ( q0 * q1 + q2 * q3 ) ,  1- 2 * ( q1 * q1 + q2 * q2 ) ) * 57.3;
      pitch = asin( 2 * ( q0 * q2 - q3 * q1 ) ) * 57.3;
      yaw = atan2( 2 * ( q0 * q3 + q1 * q2 ) ,  1 - 2 * ( q2 * q2 + q3 * q3 ) ) * 57.3;
      
      printf("%f,%f,%f\n", roll, pitch, yaw);
    }

    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }





  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Configure the main internal regulator output voltage
  */
  __HAL_RCC_PWR_CLK_ENABLE();
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  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 = 12;
  RCC_OscInitStruct.PLL.PLLN = 96;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = 4;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses 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_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_3) != HAL_OK)
  {
    Error_Handler();
  }
}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
  * @brief  Period elapsed callback in non blocking mode
  * @note   This function is called  when TIM11 interrupt took place, inside
  * HAL_TIM_IRQHandler(). It makes a direct call to HAL_IncTick() to increment
  * a global variable "uwTick" used as application time base.
  * @param  htim : TIM handle
  * @retval None
  */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  /* USER CODE BEGIN Callback 0 */

  /* USER CODE END Callback 0 */
  if (htim->Instance == TIM11) {
    HAL_IncTick();
  }
  /* USER CODE BEGIN Callback 1 */
  if (htim->Instance == TIM4) {
    ulFreeRTOSRunTimeTicks++;
  }

  /* USER CODE END Callback 1 */
}

/**
  * @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 */
  __disable_irq();
  while (1)
  {
  }
  /* 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,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

bno08x.c

#include "bno08x.h"
#include "math.h"
#include "i2c.h"

// Global Variables
unsigned char shtpHeader[4]; // Each packet has a header of 4 chars
unsigned char shtpData[MAX_PACKET_SIZE];
unsigned char sequenceNumber[6] = {0, 0, 0, 0, 0, 0}; // There are 6 com channels. Each channel has its own seqnum
unsigned char commandSequenceNumber = 0;              // Commands have a seqNum as well. These are inside command packet, the header uses its own seqNum per channel
unsigned long metaData[MAX_METADATA_SIZE];                 // There is more than 10 words in a metadata record but we'll stop at Q point 3
// Variables
unsigned char _deviceAddress;//跟踪 I2C 地址。setI2CAddress 更改了这一点。


// These are the raw sensor values pulled from the user requested Input Report
unsigned int rawAccelX, rawAccelY, rawAccelZ, accelAccuracy;
unsigned int rawLinAccelX, rawLinAccelY, rawLinAccelZ, accelLinAccuracy;
unsigned int rawGyroX, rawGyroY, rawGyroZ, gyroAccuracy;
unsigned int rawMagX, rawMagY, rawMagZ, magAccuracy;
unsigned int rawQuatI, rawQuatJ, rawQuatK, rawQuatReal, rawQuatRadianAccuracy, quatAccuracy;
unsigned int stepCount;
unsigned char stabilityClassifier;
unsigned char activityClassifier;
unsigned char *_activityConfidences; // Array that store the confidences of the 9 possible activities

// These Q values are defined in the datasheet but can also be obtained by querying the meta data records
// See the read metadata example for more info
int rotationVector_Q1 = 14;
int accelerometer_Q1 = 8;
int linear_accelerometer_Q1 = 8;
int gyro_Q1 = 9;
int magnetometer_Q1 = 4;

char dataAvailable(void)
{
    if (receivePacket() == 1)
    {
        // Check to see if this packet is a sensor reporting its data to us
        if (shtpHeader[2] == CHANNEL_REPORTS && shtpData[0] == SHTP_REPORT_BASE_TIMESTAMP)
        {
            parseInputReport(); // This will update the rawAccelX, etc variables depending on which feature report is found
            return (1);
        }
    }
    return (0);
}

// This function pulls the data from the input report
// The input reports vary in length so this function stores the various 16-bit values as globals

// Unit responds with packet that contains the following:
// shtpHeader[0:3]: First, a 4 byte header
// shtpData[0:4]: Then a 5 byte timestamp of microsecond clicks since reading was taken
// shtpData[5 + 0]: Then a feature report ID (0x01 for Accel, 0x05 for Rotation Vector)
// shtpData[5 + 1]: Sequence number (See 6.5.18.2)
// shtpData[5 + 2]: Status
// shtpData[3]: HAL_Delay
// shtpData[4:5]: i/accel x/gyro x/etc
// shtpData[6:7]: j/accel y/gyro y/etc
// shtpData[8:9]: k/accel z/gyro z/etc
// shtpData[10:11]: real/gyro temp/etc
// shtpData[12:13]: Accuracy estimate
void parseInputReport(void)
{
    // Calculate the number of data bytes in this packet
    int dataLength = ((unsigned int)shtpHeader[1] << 8 | shtpHeader[0]);

    unsigned char status = shtpData[5 + 2] & 0x03; // Get status bits
    unsigned int data1 = (unsigned int)shtpData[5 + 5] << 8 | shtpData[5 + 4];
    unsigned int data2 = (unsigned int)shtpData[5 + 7] << 8 | shtpData[5 + 6];
    unsigned int data3 = (unsigned int)shtpData[5 + 9] << 8 | shtpData[5 + 8];
    unsigned int data4 = 0;
    unsigned int data5 = 0;
    unsigned char x = 0;
    dataLength &= ~(1 << 15); // Clear the MSbit. This bit indicates if this package is a continuation of the last.
    // Ignore it for now. TODO catch this as an error and exit

    dataLength -= 4; // Remove the header bytes from the data count

    if (dataLength - 5 > 9)
    {
        data4 = (unsigned int)shtpData[5 + 11] << 8 | shtpData[5 + 10];
    }
    if (dataLength - 5 > 11)
    {
        data5 = (unsigned int)shtpData[5 + 13] << 8 | shtpData[5 + 12];
    }

    // Store these generic values to their proper global variable
    if (shtpData[5] == SENSOR_REPORTID_ACCELEROMETER)
    {
        accelAccuracy = status;
        rawAccelX = data1;
        rawAccelY = data2;
        rawAccelZ = data3;
    }
    else if (shtpData[5] == SENSOR_REPORTID_LINEAR_ACCELERATION)
    {
        accelLinAccuracy = status;
        rawLinAccelX = data1;
        rawLinAccelY = data2;
        rawLinAccelZ = data3;
    }
    else if (shtpData[5] == SENSOR_REPORTID_GYROSCOPE)
    {
        gyroAccuracy = status;
        rawGyroX = data1;
        rawGyroY = data2;
        rawGyroZ = data3;
    }
    else if (shtpData[5] == SENSOR_REPORTID_MAGNETIC_FIELD)
    {
        magAccuracy = status;
        rawMagX = data1;
        rawMagY = data2;
        rawMagZ = data3;
    }
    else if (shtpData[5] == SENSOR_REPORTID_ROTATION_VECTOR || shtpData[5] == SENSOR_REPORTID_GAME_ROTATION_VECTOR)
    {
        quatAccuracy = status;
        rawQuatI = data1;
        rawQuatJ = data2;
        rawQuatK = data3;
        rawQuatReal = data4;
        rawQuatRadianAccuracy = data5; // Only available on rotation vector, not game rot vector
    }
    else if (shtpData[5] == SENSOR_REPORTID_STEP_COUNTER)
    {
        stepCount = data3; // Bytes 8/9
    }
    else if (shtpData[5] == SENSOR_REPORTID_STABILITY_CLASSIFIER)
    {
        stabilityClassifier = shtpData[5 + 4]; // Byte 4 only
    }
    else if (shtpData[5] == SENSOR_REPORTID_PERSONAL_ACTIVITY_CLASSIFIER)
    {
        activityClassifier = shtpData[5 + 5]; // Most likely state

        // Load activity classification confidences into the array
        for (x = 0; x < 9; x++)                            // Hardcoded to max of 9. TODO - bring in array size
            _activityConfidences[x] = shtpData[5 + 6 + x]; // 5 bytes of timestamp, byte 6 is first confidence byte
    }
    else
    {
        // This sensor report ID is unhandled.
        // See reference manual to add additional feature reports as needed
    }

    // TODO additional feature reports may be strung together. Parse them all.
}

// Return the rotation vector quaternion I
float getQuatI()
{
    float quat = qToFloat(rawQuatI, (unsigned char)rotationVector_Q1);
    return (quat);
}

// Return the rotation vector quaternion J
float getQuatJ()
{
    float quat = qToFloat(rawQuatJ, (unsigned char)rotationVector_Q1);
    return (quat);
}

// Return the rotation vector quaternion K
float getQuatK()
{
    float quat = qToFloat(rawQuatK, (unsigned char)rotationVector_Q1);
    return (quat);
}

// Return the rotation vector quaternion Real
float getQuatReal()
{
    float quat = qToFloat(rawQuatReal, (unsigned char)rotationVector_Q1);
    return (quat);
}

// Return the rotation vector accuracy
float getQuatRadianAccuracy()
{
    float quat = qToFloat(rawQuatRadianAccuracy, (unsigned char)rotationVector_Q1);
    return (quat);
}

// Return the acceleration component
unsigned char getQuatAccuracy()
{
    return (quatAccuracy);
}

// Return the acceleration component
float getAccelX()
{
    float accel = qToFloat(rawAccelX, (unsigned char)accelerometer_Q1);
    return (accel);
}

// Return the acceleration component
float getAccelY()
{
    float accel = qToFloat(rawAccelY, (unsigned char)accelerometer_Q1);
    return (accel);
}

// Return the acceleration component
float getAccelZ()
{
    float accel = qToFloat(rawAccelZ, (unsigned char)accelerometer_Q1);
    return (accel);
}

// Return the acceleration component
unsigned char getAccelAccuracy()
{
    return (accelAccuracy);
}

// linear acceleration, i.e. minus gravity

// Return the acceleration component
float getLinAccelX()
{
    float accel = qToFloat(rawLinAccelX, (unsigned char)linear_accelerometer_Q1);
    return (accel);
}

// Return the acceleration component
float getLinAccelY()
{
    float accel = qToFloat(rawLinAccelY, (unsigned char)linear_accelerometer_Q1);
    return (accel);
}

// Return the acceleration component
float getLinAccelZ()
{
    float accel = qToFloat(rawLinAccelZ, (unsigned char)linear_accelerometer_Q1);
    return (accel);
}

// Return the acceleration component
unsigned char getLinAccelAccuracy()
{
    return (accelLinAccuracy);
}

// Return the gyro component
float getGyroX()
{
    float gyro = qToFloat(rawGyroX, (unsigned char)gyro_Q1);
    return (gyro);
}

// Return the gyro component
float getGyroY()
{
    float gyro = qToFloat(rawGyroY, (unsigned char)gyro_Q1);
    return (gyro);
}

// Return the gyro component
float getGyroZ()
{
    float gyro = qToFloat(rawGyroZ, (unsigned char)gyro_Q1);
    return (gyro);
}

// Return the gyro component
unsigned char getGyroAccuracy()
{
    return (gyroAccuracy);
}

// Return the magnetometer component
float getMagX()
{
    float mag = qToFloat(rawMagX, (unsigned char)magnetometer_Q1);
    return (mag);
}

// Return the magnetometer component
float getMagY()
{
    float mag = qToFloat(rawMagY, (unsigned char)magnetometer_Q1);
    return (mag);
}

// Return the magnetometer component
float getMagZ()
{
    float mag = qToFloat(rawMagZ, (unsigned char)magnetometer_Q1);
    return (mag);
}

// Return the mag component
unsigned char getMagAccuracy()
{
    return (magAccuracy);
}

// Return the step count
unsigned int getStepCount()
{
    return (stepCount);
}

// Return the stability classifier
unsigned char getStabilityClassifier()
{
    return (stabilityClassifier);
}

// Return the activity classifier
unsigned char getActivityClassifier()
{
    return (activityClassifier);
}

// Given a record ID, read the Q1 value from the metaData record in the FRS (ya, it's complicated)
// Q1 is used for all sensor data calculations
int getQ1(unsigned int recordID)
{
    // Q1 is always the lower 16 bits of word 7
    unsigned int q = readFRSword(recordID, 7) & 0xFFFF; // Get word 7, lower 16 bits
    return (q);
}

// Given a record ID, read the Q2 value from the metaData record in the FRS
// Q2 is used in sensor bias
int getQ2(unsigned int recordID)
{
    // Q2 is always the upper 16 bits of word 7
    unsigned int q = readFRSword(recordID, 7) >> 16; // Get word 7, upper 16 bits
    return (q);
}

// Given a record ID, read the Q3 value from the metaData record in the FRS
// Q3 is used in sensor change sensitivity
int getQ3(unsigned int recordID)
{
    // Q3 is always the upper 16 bits of word 8
    unsigned int q = readFRSword(recordID, 8) >> 16; // Get word 8, upper 16 bits
    return (q);
}

// Given a record ID, read the resolution value from the metaData record in the FRS for a given sensor
float getResolution(unsigned int recordID)
{
    // The resolution Q value are 'the same as those used in the sensor's input report'
    // This should be Q1.
    int Q = getQ1(recordID);

    // Resolution is always word 2
    unsigned long value = readFRSword(recordID, 2); // Get word 2

    float resolution = qToFloat((unsigned int)value, (unsigned char)Q);

    return (resolution);
}

// Given a record ID, read the range value from the metaData record in the FRS for a given sensor
float getRange(unsigned int recordID)
{
    // The resolution Q value are 'the same as those used in the sensor's input report'
    // This should be Q1.
    int Q = getQ1(recordID);

    // Range is always word 1
    unsigned long value = readFRSword(recordID, 1); // Get word 1

    float range = qToFloat((unsigned int)value, (unsigned char)Q);

    return (range);
}

// Given a record ID and a word number, look up the word data
// Helpful for pulling out a Q value, range, etc.
// Use readFRSdata for pulling out multi-word objects for a sensor (Vendor data for example)
unsigned long readFRSword(unsigned int recordID, unsigned char wordNumber)
{
    if (readFRSdata(recordID, wordNumber, 1) == 1) // Get word number, just one word in length from FRS
        return (metaData[0]);                      // Return this one word

    return (0); // Error
}

// Ask the sensor for data from the Flash Record System
// See 6.3.6 page 40, FRS Read Request
void frsReadRequest(unsigned int recordID, unsigned int readOffset, unsigned int blockSize)
{
    shtpData[0] = SHTP_REPORT_FRS_READ_REQUEST; // FRS Read Request
    shtpData[1] = 0;                            // Reserved
    shtpData[2] = (readOffset >> 0) & 0xFF;     // Read Offset LSB
    shtpData[3] = (readOffset >> 8) & 0xFF;     // Read Offset MSB
    shtpData[4] = (recordID >> 0) & 0xFF;       // FRS Type LSB
    shtpData[5] = (recordID >> 8) & 0xFF;       // FRS Type MSB
    shtpData[6] = (blockSize >> 0) & 0xFF;      // Block size LSB
    shtpData[7] = (blockSize >> 8) & 0xFF;      // Block size MSB

    // Transmit packet on channel 2, 8 bytes
    sendPacket(CHANNEL_CONTROL, 8);
}

// Given a sensor or record ID, and a given start/stop bytes, read the data from the Flash Record System (FRS) for this sensor
// Returns 1 if metaData array is loaded successfully
// Returns 0 if failure
char readFRSdata(unsigned int recordID, unsigned char startLocation, unsigned char wordsToRead)
{
    unsigned char spot = 0;
    unsigned char dataLength = 0;
    unsigned char frsStatus = 0;
    unsigned long data0 = 0;
    unsigned long data1 = 0;
    // First we send a Flash Record System (FRS) request
    frsReadRequest(recordID, startLocation, wordsToRead); // From startLocation of record, read a # of words

    // Read bytes until FRS reports that the read is complete
    while (1)
    {
        // Now we wait for response
        while (1)
        {
            unsigned char counter = 0;
            while (receivePacket() == 0)
            {
                if (counter++ > 100)
                    return (0); // Give up
                HAL_Delay(1);
            }

            // We have the packet, inspect it for the right contents
            // See page 40. Report ID should be 0xF3 and the FRS types should match the thing we requested
            if (shtpData[0] == SHTP_REPORT_FRS_READ_RESPONSE)
                if (((unsigned int)shtpData[13] << 8 | shtpData[12]) == recordID)
                    break; // This packet is one we are looking for
        }

        dataLength = shtpData[1] >> 4;
        frsStatus = shtpData[1] & 0x0F;

        data0 = (unsigned long)shtpData[7] << 24 | (unsigned long)shtpData[6] << 16 | (unsigned long)shtpData[5] << 8 | (unsigned long)shtpData[4];
        data1 = (unsigned long)shtpData[11] << 24 | (unsigned long)shtpData[10] << 16 | (unsigned long)shtpData[9] << 8 | (unsigned long)shtpData[8];

        // Record these words to the metaData array
        if (dataLength > 0)
        {
            metaData[spot++] = data0;
        }
        if (dataLength > 1)
        {
            metaData[spot++] = data1;
        }

        if (frsStatus == 3 || frsStatus == 6 || frsStatus == 7)
        {
            return (1); // FRS status is read completed! We're done!
        }
    }
}

// Send command to reset IC
// Read all advertisement packets from sensor
// The sensor has been seen to reset twice if we attempt too much too quickly.
// This seems to work reliably.
void softReset(void)
{
    shtpData[0] = 1; // Reset

    // Attempt to start communication with sensor
    sendPacket(CHANNEL_EXECUTABLE, 1); // Transmit packet on channel 1, 1 byte

    // Read all incoming data and flush it
    HAL_Delay(50);
    while (receivePacket() == 1)
        ;
    HAL_Delay(50);
    while (receivePacket() == 1)
        ;
}

// Get the reason for the last reset
// 1 = POR, 2 = Internal reset, 3 = Watchdog, 4 = External reset, 5 = Other
unsigned char resetReason()
{
    shtpData[0] = SHTP_REPORT_PRODUCT_ID_REQUEST; // Request the product ID and reset info
    shtpData[1] = 0;                              // Reserved

    // Transmit packet on channel 2, 2 bytes
    sendPacket(CHANNEL_CONTROL, 2);

    // Now we wait for response
    if (receivePacket() == 1)
    {
        if (shtpData[0] == SHTP_REPORT_PRODUCT_ID_RESPONSE)
        {
            return (shtpData[1]);
        }
    }

    return (0);
}

// Given a register value and a Q point, convert to float
float qToFloat(int16_t fixedPointValue, unsigned char qPoint)
{
    float qFloat = fixedPointValue;
    qFloat *= pow(2, qPoint * -1);
    return (qFloat);
}

// Sends the packet to enable the rotation vector
void enableRotationVector(unsigned int timeBetweenReports)
{
    setFeatureCommand(SENSOR_REPORTID_ROTATION_VECTOR, timeBetweenReports, 0);
}

// Sends the packet to enable the rotation vector
void enableGameRotationVector(unsigned int timeBetweenReports)
{
    setFeatureCommand(SENSOR_REPORTID_GAME_ROTATION_VECTOR, timeBetweenReports, 0);
}

// Sends the packet to enable the accelerometer
void enableAccelerometer(unsigned int timeBetweenReports)
{
    setFeatureCommand(SENSOR_REPORTID_ACCELEROMETER, timeBetweenReports, 0);
}

// Sends the packet to enable the accelerometer
void enableLinearAccelerometer(unsigned int timeBetweenReports)
{
    setFeatureCommand(SENSOR_REPORTID_LINEAR_ACCELERATION, timeBetweenReports, 0);
}

// Sends the packet to enable the gyro
void enableGyro(unsigned int timeBetweenReports)
{
    setFeatureCommand(SENSOR_REPORTID_GYROSCOPE, timeBetweenReports, 0);
}

// Sends the packet to enable the magnetometer
void enableMagnetometer(unsigned int timeBetweenReports)
{
    setFeatureCommand(SENSOR_REPORTID_MAGNETIC_FIELD, timeBetweenReports, 0);
}

// Sends the packet to enable the step counter
void enableStepCounter(unsigned int timeBetweenReports)
{
    setFeatureCommand(SENSOR_REPORTID_STEP_COUNTER, timeBetweenReports, 0);
}

// Sends the packet to enable the Stability Classifier
void enableStabilityClassifier(unsigned int timeBetweenReports)
{
    setFeatureCommand(SENSOR_REPORTID_STABILITY_CLASSIFIER, timeBetweenReports, 0);
}

// Sends the commands to begin calibration of the accelerometer
void calibrateAccelerometer()
{
    sendCalibrateCommand(CALIBRATE_ACCEL);
}

// Sends the commands to begin calibration of the gyro
void calibrateGyro()
{
    sendCalibrateCommand(CALIBRATE_GYRO);
}

// Sends the commands to begin calibration of the magnetometer
void calibrateMagnetometer()
{
    sendCalibrateCommand(CALIBRATE_MAG);
}

// Sends the commands to begin calibration of the planar accelerometer
void calibratePlanarAccelerometer()
{
    sendCalibrateCommand(CALIBRATE_PLANAR_ACCEL);
}

// See 2.2 of the Calibration Procedure document 1000-4044
void calibrateAll()
{
    sendCalibrateCommand(CALIBRATE_ACCEL_GYRO_MAG);
}

void endCalibration()
{
    sendCalibrateCommand(CALIBRATE_STOP); // Disables all calibrations
}

// Given a sensor's report ID, this tells the BNO080 to begin reporting the values
// Also sets the specific config word. Useful for personal activity classifier
void setFeatureCommand(unsigned char reportID, unsigned int timeBetweenReports, unsigned long specificConfig)
{
    long microsBetweenReports = (long)timeBetweenReports * 1000L;

    shtpData[0] = SHTP_REPORT_SET_FEATURE_COMMAND;     // Set feature command. Reference page 55
    shtpData[1] = reportID;                            // Feature Report ID. 0x01 = Accelerometer, 0x05 = Rotation vector
    shtpData[2] = 0;                                   // Feature flags
    shtpData[3] = 0;                                   // Change sensitivity (LSB)
    shtpData[4] = 0;                                   // Change sensitivity (MSB)
    shtpData[5] = (microsBetweenReports >> 0) & 0xFF;  // Report interval (LSB) in microseconds. 0x7A120 = 500ms
    shtpData[6] = (microsBetweenReports >> 8) & 0xFF;  // Report interval
    shtpData[7] = (microsBetweenReports >> 16) & 0xFF; // Report interval
    shtpData[8] = (microsBetweenReports >> 24) & 0xFF; // Report interval (MSB)
    shtpData[9] = 0;                                   // Batch Interval (LSB)
    shtpData[10] = 0;                                  // Batch Interval
    shtpData[11] = 0;                                  // Batch Interval
    shtpData[12] = 0;                                  // Batch Interval (MSB)
    shtpData[13] = (specificConfig >> 0) & 0xFF;       // Sensor-specific config (LSB)
    shtpData[14] = (specificConfig >> 8) & 0xFF;       // Sensor-specific config
    shtpData[15] = (specificConfig >> 16) & 0xFF;      // Sensor-specific config
    shtpData[16] = (specificConfig >> 24) & 0xFF;      // Sensor-specific config (MSB)

    // Transmit packet on channel 2, 17 bytes
    sendPacket(CHANNEL_CONTROL, 17);
}

// Tell the sensor to do a command
// See 6.3.8 page 41, Command request
// The caller is expected to set P0 through P8 prior to calling
void sendCommand(unsigned char command)
{
    shtpData[0] = SHTP_REPORT_COMMAND_REQUEST; // Command Request
    shtpData[1] = commandSequenceNumber++;     // Increments automatically each function call
    shtpData[2] = command;                     // Command

    // Caller must set these
    /*shtpData[3] = 0; //P0
    shtpData[4] = 0; //P1
    shtpData[5] = 0; //P2
    shtpData[6] = 0;
    shtpData[7] = 0;
    shtpData[8] = 0;
    shtpData[9] = 0;
    shtpData[10] = 0;
    shtpData[11] = 0;*/

    // Transmit packet on channel 2, 12 bytes
    sendPacket(CHANNEL_CONTROL, 12);
}

// This tells the BNO080 to begin calibrating
// See page 50 of reference manual and the 1000-4044 calibration doc
void sendCalibrateCommand(unsigned char thingToCalibrate)
{
    unsigned char x = 0;
    /*shtpData[3] = 0; //P0 - Accel Cal Enable
    shtpData[4] = 0; //P1 - Gyro Cal Enable
    shtpData[5] = 0; //P2 - Mag Cal Enable
    shtpData[6] = 0; //P3 - Subcommand 0x00
    shtpData[7] = 0; //P4 - Planar Accel Cal Enable
    shtpData[8] = 0; //P5 - Reserved
    shtpData[9] = 0; //P6 - Reserved
    shtpData[10] = 0; //P7 - Reserved
    shtpData[11] = 0; //P8 - Reserved*/

    for (x = 3; x < 12; x++) // Clear this section of the shtpData array
        shtpData[x] = 0;

    if (thingToCalibrate == CALIBRATE_ACCEL)
        shtpData[3] = 1;
    else if (thingToCalibrate == CALIBRATE_GYRO)
        shtpData[4] = 1;
    else if (thingToCalibrate == CALIBRATE_MAG)
        shtpData[5] = 1;
    else if (thingToCalibrate == CALIBRATE_PLANAR_ACCEL)
        shtpData[7] = 1;
    else if (thingToCalibrate == CALIBRATE_ACCEL_GYRO_MAG)
    {
        shtpData[3] = 1;
        shtpData[4] = 1;
        shtpData[5] = 1;
    }
    else if (thingToCalibrate == CALIBRATE_STOP)
        ; // Do nothing, bytes are set to zero

    // Using this shtpData packet, send a command
    sendCommand(COMMAND_ME_CALIBRATE);
}

// This tells the BNO080 to save the Dynamic Calibration Data (DCD) to flash
// See page 49 of reference manual and the 1000-4044 calibration doc
void saveCalibration()
{
    unsigned char x = 0;
    /*shtpData[3] = 0; //P0 - Reserved
    shtpData[4] = 0; //P1 - Reserved
    shtpData[5] = 0; //P2 - Reserved
    shtpData[6] = 0; //P3 - Reserved
    shtpData[7] = 0; //P4 - Reserved
    shtpData[8] = 0; //P5 - Reserved
    shtpData[9] = 0; //P6 - Reserved
    shtpData[10] = 0; //P7 - Reserved
    shtpData[11] = 0; //P8 - Reserved*/

    for (x = 3; x < 12; x++) // Clear this section of the shtpData array
        shtpData[x] = 0;

    // Using this shtpData packet, send a command
    sendCommand(COMMAND_DCD); // Save DCD command
}





//  IIC 硬件底层

void I2C_Init(void)
{
    MX_I2C1_Init();
}

void WriteNbyte(u8 *p, u8 number) /*  WordAddress,First Data Address,Byte lenth   */
{
    HAL_I2C_Master_Transmit( &hi2c1, BNO080_W_ADDRESS, p, number, 0xffff );
}

void ReadNbyte(u8 *p, u8 number) /*  WordAddress,First Data Address,Byte lenth   */
{
    HAL_I2C_Master_Receive( &hi2c1, BNO080_R_ADDRESS, p, number, 0xffff );
}

u8 i2c_tmp[32] = {0};

// 检查是否有任何可用的新数据
// 将传入数据包的内容读入 shtpData 数组
char receivePacket(void)
{
    int dataLength = 0;
    unsigned char packetLSB = 0;
    unsigned char packetMSB = 0;
    unsigned char channelNumber = 0;
    unsigned char sequenceNumber = 0;
    ReadNbyte(i2c_tmp, 4);

    // Get the first four bytes, aka the packet header
    packetLSB = i2c_tmp[0];
    packetMSB = i2c_tmp[1];
    channelNumber = i2c_tmp[2];
    sequenceNumber = i2c_tmp[3]; // Not sure if we need to store this or not

    // Store the header info.
    shtpHeader[0] = packetLSB;
    shtpHeader[1] = packetMSB;
    shtpHeader[2] = channelNumber;
    shtpHeader[3] = sequenceNumber;

    // Calculate the number of data bytes in this packet
    dataLength = ((unsigned int)packetMSB << 8 | packetLSB);
    dataLength &= ~(1 << 15);//清除 MSbit。
//此位指示此包是否是上一个包的延续。暂时忽略它。
//TODO 将此捕获为错误并退出
    if (dataLength == 0)
    {
        // Packet is empty
        return (0); // All done
    }
    dataLength -= 4; // Remove the header bytes from the data count

    getData(dataLength);

    return (1); // We're done!
}

// 向传感器发送多个请求,直到从传感器接收到所有数据字节
// shtpData 缓冲区的最大容量为 MAX_PACKET_SIZE。超过此量的任何字节都将丢失。
// Arduino I2C 读取限制为 32 字节。标头是 4 个字节,因此我们每个交互可以读取的最大数据为 28 字节
char getData(unsigned int bytesRemaining)
{
    unsigned int dataSpot = 0; // Start at the beginning of shtpData array
    unsigned char x = 0;
    unsigned char incoming = 0;
    // Setup a series of chunked 32 byte reads
    while (bytesRemaining > 0)
    {
        unsigned int numberOfBytesToRead = bytesRemaining;
        if (numberOfBytesToRead > (I2C_BUFFER_LENGTH - 4))
            numberOfBytesToRead = (I2C_BUFFER_LENGTH - 4);
        ReadNbyte(i2c_tmp, (unsigned char)(numberOfBytesToRead + 4));

        // 前四个字节是标头字节,被丢弃

        for (x = 0; x < numberOfBytesToRead; x++)
        {
            incoming = i2c_tmp[x + 4];
            if (dataSpot < MAX_PACKET_SIZE)
            {
                shtpData[dataSpot++] = incoming; // Store data into the shtpData array
            }
            else
            {
                // Do nothing with the data
            }
        }

        bytesRemaining -= numberOfBytesToRead;
    }
    return (1); // Done!
}

// 给定数据包,发送标头,然后发送数据
// 如果传感器不 ACK,则返回 0
// TODO - Arduino 的最大发送量为 32 字节。如果需要,将发送分解为多个数据包。
char sendPacket(unsigned char channelNumber, unsigned char dataLength)
{
    unsigned char packetLength = dataLength + 4; // Add four bytes for the header
    // if(packetLength > I2C_BUFFER_LENGTH) return(0); //You are trying to send too much. Break into smaller packets.
    unsigned char i = 0;
    // Send the 4 byte packet header
    i2c_tmp[0] = (packetLength & 0xFF);             // Packet length LSB
    i2c_tmp[1] = (0);                               // Packet length MSB
    i2c_tmp[2] = (channelNumber);                   // Channel number
    i2c_tmp[3] = (sequenceNumber[channelNumber]++); // Send the sequence number, increments with each packet sent, different counter for each channel

    // Send the user's data packet
    for (i = 0; i < dataLength; i++)
    {
        i2c_tmp[4 + i] = (shtpData[i]);
    }
    WriteNbyte(i2c_tmp, (u8)(dataLength + 4));

    return (1);
}

bno080.h

#ifndef __BNO08X_H_
#define __BNO08X_H_

#include "main.h"

// The default I2C address for the BNO080 on the SparkX breakout is 0x4B. 0x4A is also possible.
#define BNO080_DEFAULT_ADDRESS 0x4B
#define BNO080_W_ADDRESS     ( BNO080_DEFAULT_ADDRESS << 1 | 0x00 )
#define BNO080_R_ADDRESS     ( BNO080_DEFAULT_ADDRESS << 1 | 0x01 )

// The catch-all default is 32
#define I2C_BUFFER_LENGTH 32
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

// Registers
#define CHANNEL_COMMAND  0
#define CHANNEL_EXECUTABLE  1
#define CHANNEL_CONTROL  2
#define CHANNEL_REPORTS  3
#define CHANNEL_WAKE_REPORTS  4
#define CHANNEL_GYRO  5

// All the ways we can configure or talk to the BNO080, figure 34, page 36 reference manual
// These are used for low level communication with the sensor, on channel 2
#define SHTP_REPORT_COMMAND_RESPONSE 0xF1
#define SHTP_REPORT_COMMAND_REQUEST 0xF2
#define SHTP_REPORT_FRS_READ_RESPONSE 0xF3
#define SHTP_REPORT_FRS_READ_REQUEST 0xF4
#define SHTP_REPORT_PRODUCT_ID_RESPONSE 0xF8
#define SHTP_REPORT_PRODUCT_ID_REQUEST 0xF9
#define SHTP_REPORT_BASE_TIMESTAMP 0xFB
#define SHTP_REPORT_SET_FEATURE_COMMAND 0xFD

// All the different sensors and features we can get reports from
// These are used when enabling a given sensor
#define SENSOR_REPORTID_ACCELEROMETER 0x01
#define SENSOR_REPORTID_GYROSCOPE 0x02
#define SENSOR_REPORTID_MAGNETIC_FIELD 0x03
#define SENSOR_REPORTID_LINEAR_ACCELERATION 0x04
#define SENSOR_REPORTID_ROTATION_VECTOR 0x05
#define SENSOR_REPORTID_GRAVITY 0x06
#define SENSOR_REPORTID_GAME_ROTATION_VECTOR 0x08
#define SENSOR_REPORTID_GEOMAGNETIC_ROTATION_VECTOR 0x09
#define SENSOR_REPORTID_TAP_DETECTOR 0x10
#define SENSOR_REPORTID_STEP_COUNTER 0x11
#define SENSOR_REPORTID_STABILITY_CLASSIFIER 0x13
#define SENSOR_REPORTID_PERSONAL_ACTIVITY_CLASSIFIER 0x1E

// Record IDs from figure 29, page 29 reference manual
// These are used to read the metadata for each sensor type
#define FRS_RECORDID_ACCELEROMETER 0xE302
#define FRS_RECORDID_GYROSCOPE_CALIBRATED 0xE306
#define FRS_RECORDID_MAGNETIC_FIELD_CALIBRATED 0xE309
#define FRS_RECORDID_ROTATION_VECTOR 0xE30B

// Command IDs from section 6.4, page 42
// These are used to calibrate, initialize, set orientation, tare etc the sensor
#define COMMAND_ERRORS 1
#define COMMAND_COUNTER 2
#define COMMAND_TARE 3
#define COMMAND_INITIALIZE 4
#define COMMAND_DCD 6
#define COMMAND_ME_CALIBRATE 7
#define COMMAND_DCD_PERIOD_SAVE 9
#define COMMAND_OSCILLATOR 10
#define COMMAND_CLEAR_DCD 11

#define CALIBRATE_ACCEL 0
#define CALIBRATE_GYRO 1
#define CALIBRATE_MAG 2
#define CALIBRATE_PLANAR_ACCEL 3
#define CALIBRATE_ACCEL_GYRO_MAG 4
#define CALIBRATE_STOP 5

#define MAX_PACKET_SIZE 128  // Packets can be up to 32k but we don't have that much RAM.
#define MAX_METADATA_SIZE 9  // This is in words. There can be many but we mostly only care about the first 9 (Qs, range, etc)
void softReset();            // Try to reset the IMU via software
unsigned char resetReason(); // Query the IMU for the reason it last reset

float qToFloat(int16_t fixedPointValue, unsigned char qPoint); // Given a Q value, converts fixed point floating to regular floating point number

// char waitForI2C(); // Delay based polling for I2C traffic
char receivePacket(void);
char getData(unsigned int charsRemaining); // Given a number of chars, send the requests in I2C_BUFFER_LENGTH chunks
char sendPacket(unsigned char channelNumber, unsigned char dataLength);
// void printPacket(void); // Prints the current shtp header and data packets

void enableRotationVector(unsigned int timeBetweenReports);
void enableGameRotationVector(unsigned int timeBetweenReports);
void enableAccelerometer(unsigned int timeBetweenReports);
void enableLinearAccelerometer(unsigned int timeBetweenReports);
void enableGyro(unsigned int timeBetweenReports);
void enableMagnetometer(unsigned int timeBetweenReports);
void enableStepCounter(unsigned int timeBetweenReports);
void enableStabilityClassifier(unsigned int timeBetweenReports);

char dataAvailable(void);
void parseInputReport(void);

float getQuatI();
float getQuatJ();
float getQuatK();
float getQuatReal();
float getQuatRadianAccuracy();
unsigned char getQuatAccuracy();

float getAccelX();
float getAccelY();
float getAccelZ();
unsigned char getAccelAccuracy();

float getLinAccelX();
float getLinAccelY();
float getLinAccelZ();
unsigned char getLinAccelAccuracy();

float getGyroX();
float getGyroY();
float getGyroZ();
unsigned char getGyroAccuracy();

float getMagX();
float getMagY();
float getMagZ();
unsigned char getMagAccuracy();

void calibrateAccelerometer();
void calibrateGyro();
void calibrateMagnetometer();
void calibratePlanarAccelerometer();
void calibrateAll();
void endCalibration();
void saveCalibration();

unsigned int getStepCount();
unsigned char getStabilityClassifier();
unsigned char getActivityClassifier();

void setFeatureCommand(unsigned char reportID, unsigned int timeBetweenReports, unsigned long specificConfig);
void sendCommand(unsigned char command);
void sendCalibrateCommand(unsigned char thingToCalibrate);

// Metadata functions
int getQ1(unsigned int recordID);
int getQ2(unsigned int recordID);
int getQ3(unsigned int recordID);
float getResolution(unsigned int recordID);
float getRange(unsigned int recordID);
unsigned long readFRSword(unsigned int recordID, unsigned char wordNumber);
void frsReadRequest(unsigned int recordID, unsigned int readOffset, unsigned int blockSize);
char readFRSdata(unsigned int recordID, unsigned char startLocation, unsigned char wordsToRead);

void I2C_Init(void);

#endif

作者:lrj depression

物联沃分享整理
物联沃-IOTWORD物联网 » STM32 HAL 硬件IIC驱动BNO085(附上源码)

发表回复