title: STM32移植BNO055(各种单片机都适用)
date: 2020-07-26
tags:
categories: STM32学习记录
背景
听说BNO055很牛逼,买一个来摸一摸。集成了磁力计,不需要自己再去配置外围电路,并且9轴陀螺仪能比6轴获取更多的信息,数据的精度也会好得多,最最重要的,这个模块除了有详细的官方文档,博世还写了快速使用手册,这极大的方便了用户的使用,缩短了产品的设计周期,无疑是它在消费电子领域兴起的重要原因之一,总之50块的价格绝对不吃亏。
准备
首先BNO055肯定要有一片,虽然相对于传统的MPU6XXX要贵上不少,但是上面的特性我也说过了,用过的人都说好。
另外,STM32开发板,仿真器,串口模块这些就不多说了,并且我是默认你有基本的STM32开发能力的,
前导认识
这里我使用HAL库+CubeMX编程,标准库或者寄存器编程的原理都一样,甚至使用德州仪器和NXP的单片机,移植方法都是类似的,走一个流程就能正常调用官方的API进行快速的开发和应用。
这里主要参考两个BNO055的官方文档,一个是Data Sheet数据手册,另一个是User Guide用户快速上手,相当于极简版的数据手册,最重要的是附加了手把手教你用API的步骤,可谓非常贴心。
下面粘贴官方的介绍,以及这款传感器支持的模式:
BNO055 是一款封装系统(SiP)芯片,包括一个 3 轴 14 位加速计,一个 3 轴 16 位陀螺仪,一个 3 轴磁 力计,和一个运行 BSX3.0 FusionLib 软件的 32 位 Cortex M0 +微控制器。 除了可以访问单个传感器信号(如加速度,旋转和磁场强度)之外,传感器还总共提供了五种不同的传感 器融合模式。具体模式下表:
非融合就不多说了,都看得懂,由于这款芯片中集成了一个CortexM0+内核,这赋予了它低功耗的情况下还能快速处理融合并输出融合结果的能力,所以我们通常使用的都是它自带的融合模式,减轻了外部处理单元的处理压力,使它的应用领域更加广泛。
融合模式:
-
IMU 或惯性测量单元是加速度计和陀螺仪的融合
-
COMPASS 是加速度计和磁力计的融合,也被称为倾斜补偿指南针
-
M4G(陀螺仪磁体)是加速度计和磁力计的融合体,但输出数据类似于 IMU 模式,因此陀螺仪的限制在 这种模式下得到了补偿。
-
NDOF_FMC_OFF 是所有三种传感器(加速度计,陀螺仪和磁力计)的融合,从而提供了九自由度 (NDOF)。在此模式下,“FMC(快速磁性校准)”功能被禁用,因此传感器需要类似于“图 8 模式”的移动来 校准磁力计。
-
NDOF 也是所有三种传感器的融合,但启用了“FMC”功能。通过 启用此功能,快速移动(即使不完整的“数字 8 模式”)将完全校准磁力计。
上图中也可以看到这些融合模式会输出什么样的数据供我们使用。具体的数据融合方式可以参考数据手册,里面有很详细的说明。这里我只是演示一遍具体的应用,关于应用主要的参考是快速应用手册中的一遍步骤。
Go
首先当然是创建或者自己准备一份可以正常运行的单片机源代码,需要注意的是,这个程序应该至少可以运行I2C模块,不管是软件模拟的还是硬件外设,例如我的代码就是这样,当然不可能放全部,这是该有的部分:
#include "main.h"
#include "i2c.h"
#include "usart.h"
#include "gpio.h"
MX_GPIO_Init();
MX_I2C1_Init();
MX_USART1_UART_Init();
下一步是下载官方的API源文件,地址:https://github.com/BoschSensortec/BNO055_driver,其实主要的东西就是两个头文件,那个C文件是演示如何使用的,我们用不到。下载后复制这两个头文件到自己的工程中,直接把官方文件夹copy到你的工程文件夹,便于管理就像下面这样:
然后我们就按照官方的指导走一遍:
-
第1步不用多说,启动BNO055电源,之后如果不进行任何操作,就处于Config模式,不会输出数据,详见数据手册。
-
第2步:创建BNO的句柄结构体,这个句柄将装载BNO055的各种配置数据。但是这个结构体类型的声明在头文件里,所以首先我们需要添加头文件,再按照说明声明句柄:
#include "bno055.h" struct bno055_t myBNO; //全局变量
-
第3步:给这个控制块的结构体成员赋值,或者说传参更准确。这是最最关键的一步,这一步有问题的话一定会出玄学问题。我们进入结构体定义的地方看看:
struct bno055_t { u8 chip_id; /**< chip_id of bno055 */ u16 sw_rev_id; /**< software revision id of bno055 */ u8 page_id; /**< page_id of bno055 */ u8 accel_rev_id; /**< accel revision id of bno055 */ u8 mag_rev_id; /**< mag revision id of bno055 */ u8 gyro_rev_id; /**< gyro revision id of bno055 */ u8 bl_rev_id; /**< boot loader revision id of bno055 */ u8 dev_addr; /**< i2c device address of bno055 */ BNO055_WR_FUNC_PTR; /**< bus write function pointer */ BNO055_RD_FUNC_PTR; /**<bus read function pointer */ void (*delay_msec)(BNO055_MDELAY_DATA_TYPE); /**< delay function pointer */ };
其实我们这一步是给最后三个成员进行配置,可以看到是三个宏,我们再转到宏定义的地方:
/***************************************************************/ /**\name BUS READ AND WRITE FUNCTIONS */ /***************************************************************/ #define BNO055_WR_FUNC_PTR s8 (*bus_write) \ (u8, u8, u8 *, u8) #define BNO055_BUS_WRITE_FUNC(dev_addr, reg_addr, reg_data, wr_len) \ bus_write(dev_addr, reg_addr, reg_data, wr_len) #define BNO055_RD_FUNC_PTR s8 \ (*bus_read)(u8, u8, u8 *, u8) #define BNO055_BUS_READ_FUNC(dev_addr, reg_addr, reg_data, r_len) \ bus_read(dev_addr, reg_addr, reg_data, r_len) #define BNO055_DELAY_RETURN_TYPE void #define BNO055_DELAY_PARAM_TYPES u32 #define BNO055_DELAY_FUNC(delay_in_msec) \ delay_func(delay_in_msec)
从变量的简写就已经很清晰了吧,两个结构体成员是为了和用户的IIC读写函数进行对接的,应该传入用户IIC读写函数的地址,也就是函数名,我们已经有读写函数了,那就是HAL库的读写函数,但是这个读写函数是不符合要求的,参数的数量和意义与API需要的函数都有些出入,所以我们需要定义一个用户函数,用来对接用户函数和模块API。
s8 BNO_read(u8 dev_addr,u8 reg_addr,u8 *reg_data,u8 wr_len) { HAL_StatusTypeDef ret; ret = HAL_I2C_Mem_Read(&hi2c1, dev_addr << 1, reg_addr, sizeof(reg_addr), reg_data, wr_len, 0xFF); return 0; } s8 BNO_write(u8 dev_addr,u8 reg_addr,u8 *reg_data,u8 wr_len) { HAL_StatusTypeDef ret; ret = HAL_I2C_Mem_Write(&hi2c1, dev_addr << 1, reg_addr, sizeof(reg_addr), reg_data, wr_len, 0xFF); return 0; }
这个也很好理解,函数指针的类型必须和API指定的类型一致,剩下的就是把对应的参数填入HAL库的IIC读写函数,这里使用STM32平台进行移植的读者需要注意两点,第一点就是,写函数必须使用HAL_I2C_Mem_Write(),函数的作用是在阻塞状态下向器件写入大量数据,同理,读函数也需要HAL_I2C_Mem_Read(),而不是HAL库的主机读写模式:HAL_I2C_Master_Transmit和HAL_I2C_Master_Receive;第二点就是,STM32的HAL库的读写函数并不会自动移位,所以我们需要在器件地址上手动左移2位。然后就可以将函数指针传入API的初始化句柄了:
myBNO.bus_read = BNO_read; //再main函数中进行 myBNO.bus_write = BNO_write; myBNO.delay_msec = HAL_Delay; myBNO.dev_addr = BNO055_I2C_ADDR1;
这里我直接把第4步一起做了。
-
第4步,关于地址的头文件在bno055.h头文件中定义
/* bno055 I2C Address */ #define BNO055_I2C_ADDR1 (0x28) #define BNO055_I2C_ADDR2 (0x29)
这里主要看模块的IIC_Arr引脚,如果拉低就是0x28的器件地址,拉高就是0x29。
-
第5步,初始化配置
bno055_init(&myBNO);
-
第6步,自检并设置模式
bno055_set_sys_rst(BNO055_BIT_ENABLE); HAL_Delay(700); bno055_set_operation_mode(BNO055_OPERATION_MODE_IMUPLUS);
模式的宏定义也在头文件
-
如果要读融合数据的角度,定义储存三轴角度的结构体,然后调用官方函数:
while(1) { bno055_get_gyro_calib_stat(&gyro_calib_status); if (gyro_calib_status == 3) { break; } HAL_Delay(500); HAL_UART_Transmit(&huart1, (uint8_t *)waiting, sizeof(waiting), 0xFF); }
注意这个在main函数中,但是不在while(1)的死循环中,目的是判断获取是否成功。比如这里我要读取Gyro陀螺仪,那么就要等到设置完毕才可以继续。所以就有了这个判断过程。
-
可以开始读取数据了:
while (1) { HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13); s8 stat = bno055_convert_float_euler_hpr_deg(&buff); if (stat == BNO055_SUCCESS) //接收成功 { sprintf(temp, "%.1f, %.1f, %.1f\r\n", buff.h, buff.r, buff.p); HAL_UART_Transmit(&huart1, (uint8_t *)temp, sizeof(temp), 0xFF); } else //接收失败 { HAL_UART_Transmit(&huart1, (uint8_t *)error, sizeof(error), 0xFF); } //HAL_Delay(50); /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ }
总结
对于应用BNO055来说还是很简单的,主要是没有MPU6XXX那样的漂移,并且数据稳定,传输速度较快,自动融合,自带滤波,这些特性使它成为了现在比较优秀的民用陀螺仪传感器。