[i=s] 本帖最后由 流鱼 于 2026-7-3 13:53 编辑 [/i]
BNO080 移植指南
将 BNO080 9 轴 IMU 的官方ceva sh2 库移植到 Zephyr RTOS 平台。
https://github.com/ceva-dsp/sh2
1. BNO080 简介
BNO080 是 Bosch 的系统级封装 (SiP) 9 轴 IMU,


集成:
- 三轴加速度计
- 三轴陀螺仪
- 三轴磁力计
- 32-bit ARM Cortex-M0+ 微控制器(运行 SH-2 固件)
1.1 关键特性
| 特性 |
参数 |
| 通信接口 |
I2C / SPI / UART(通过 PS0/PS1 引脚选择) |
| 工作电压 |
1.7V~ 3.6V |
| 传感器融合 |
内置 SH-2 算法(四元数、欧拉角、线性加速度等) |
| 输出数据 |
加速度、角速度、磁场、旋转矢量、线性加速度、重力等 |
| 中断输出 |
INT 引脚(低电平有效) |
| 复位 |
RST 引脚(低电平有效) |
1.2 通信协议选择
通过 PS1/PS0 引脚电平选择(BOOTN=1 正常模式):
| PS1 |
PS0 |
协议 |
| 0 |
0 |
I2C |
| 0 |
1 |
UART-RVC |
| 1 |
0 |
UART |
| 1 |
1 |
SPI |
本项目使用 SPI 模式(PS1=1, PS0=1,均拉高到 3.3V)。
2. 官方库结构
BNO080 使用 SH2 传感器库:
drv/bno080/
├── inc/
│ ├── sh2.h # SH2 主 API 头文件
│ ├── sh2_SensorValue.h # 传感器数据解码
│ ├── sh2_hal.h # HAL 接口定义(需用户实现)
│ ├── sh2_err.h # 错误码定义
│ ├── sh2_util.h # 工具函数
│ ├── shtp.h # SHTP 协议层
│ ├── euler.h # 欧拉角转换
│ └── bno080_sensor.h # 位域传感器定义和统一数据结构
├── src/
│ ├── sh2.c # SH2 核心实现
│ ├── sh2_SensorValue.c # 传感器事件解码
│ ├── sh2_util.c # 工具函数实现
│ ├── shtp.c # SHTP 协议实现
│ └── euler.c # 欧拉角转换实现
└── platform/
├── sh2_spi_hal.c # SPI HAL 实现(Zephyr 适配)
├── sh2_spi_hal.h
├── sh2_i2c_hal.c # I2C HAL 实现(Zephyr 适配)
├── sh2_i2c_hal.h
├── sh2_uart_hal.c # UART HAL 实现(Zephyr 适配)
├── sh2_uart_hal.h
├── sh2_demo.c # 应用层封装
└── sh2_demo.h
2.1 官方 SH2 API 核心函数
| 函数 |
说明 |
sh2_open() |
打开传感器会话 |
sh2_close() |
关闭会话 |
sh2_service() |
服务设备(读取数据、分发回调) |
sh2_setSensorCallback() |
注册传感器数据回调 |
sh2_setSensorConfig() |
配置传感器(启用/禁用、报告间隔) |
sh2_getSensorConfig() |
获取传感器配置 |
sh2_getProdIds() |
获取产品 ID |
sh2_saveDcdNow() |
保存动态校准数据 |
2.2 HAL 接口(需自行实现)
struct sh2_Hal_s {
int (*open)(sh2_Hal_t *self);
void (*close)(sh2_Hal_t *self);
int (*read)(sh2_Hal_t *self, uint8_t *pBuffer, unsigned len, uint32_t *t_us);
int (*write)(sh2_Hal_t *self, uint8_t *pBuffer, unsigned len);
uint32_t (*getTimeUs)(sh2_Hal_t *self);
};
关键要求:
open: 初始化硬件(GPIO、SPI/I2C),执行复位序列
close: 关闭硬件,进入复位状态
read: 如果有完整 SHTP 传输,加载到 pBuffer 并返回长度;否则返回 0
write: 复制数据到发送缓冲区,返回接受字节数
getTimeUs: 返回微秒时间戳
3. 移植架构
3.1 分层设计
┌─────────────────────────────────────────┐
│ 应用层 (example_bno080.c) │
│ 调用 sh2_demo_* 接口读取传感器数据 │
├─────────────────────────────────────────┤
│ 应用封装层 (sh2_demo.c) │
│ 封装 SH2 初始化、回调、数据缓存 │
├─────────────────────────────────────────┤
│ SH2 核心库 (sh2.c, shtp.c) │
│ SHTP 协议、传感器配置、数据处理 │
├─────────────────────────────────────────┤
│ HAL 层 (sh2_spi_hal.c) │
│ 将 SH2 HAL 接口映射到 Zephyr SPI/GPIO │
├─────────────────────────────────────────┤
│ Zephyr RTOS (SPI/GPIO 驱动) │
│ 硬件抽象,设备树绑定 │
├─────────────────────────────────────────┤
│ 硬件 (MCU + BNO080 传感器) │
└─────────────────────────────────────────┘
3.2 关键设计决策
- 不修改 SH2 核心库 — 仅实现 HAL 层
- SPI 模式 — PS0=1, PS1=1(拉高到 3.3V)
- GPIO CS — 不使用硬件 SSL,避免引脚冲突
- 中断驱动 — INT 引脚触发数据读取
- 同步 SPI —
read()/write() 直接执行同步 SPI 传输
- 应用封装 —
sh2_demo.c 封装 SH2 初始化和数据缓存
4. HAL 层实现
4.1 同步 SPI 传输模型
BNO080 SPI 通信采用同步传输模型,HAL 层的 read() 和 write() 函数直接执行同步 SPI 操作(spi_transceive()),无需状态机或异步缓冲。
核心设计:
- 使用 Zephyr
spi_transceive() 进行全双工同步传输
data_ready 标志由 INT 中断设置,read() 检查该标志决定是否执行 SPI 读取
write() 先拉低 PS0(WAKEN)唤醒传感器,再执行 SPI 写入
- CS 引脚由 GPIO 手动控制,不使用硬件 SSL
4.2 GPIO 控制
/* 设备树节点 */
#define BNO080_NODE DT_NODELABEL(bno080)
/* SPI 设备 */
static const struct device *spi_dev = DEVICE_DT_GET(DT_BUS(BNO080_NODE));
/* GPIO 设备 */
static const struct device *cs_gpio_dev =
DEVICE_DT_GET(DT_GPIO_CTLR(BNO080_NODE, cs_gpios));
static const struct device *int_reset_gpio_dev =
DEVICE_DT_GET(DT_GPIO_CTLR(BNO080_NODE, int_gpios));
/* 引脚定义 */
#define CS_PIN DT_GPIO_PIN(BNO080_NODE, cs_gpios)
#define INT_PIN DT_GPIO_PIN(BNO080_NODE, int_gpios)
#define RESET_PIN DT_GPIO_PIN(BNO080_NODE, reset_gpios)
4.3 SPI 配置
/* SPI 配置 - 使用设备树和 Kconfig 配置 */
static struct spi_config spi_cfg = {
.frequency = DT_PROP(BNO080_NODE, spi_max_frequency),
.operation = (SPI_OP_MODE_MASTER | SPI_WORD_SET(8) | SPI_TRANSFER_MSB |
SPI_MODE_CPOL | SPI_MODE_CPHA | SPI_HOLD_ON_CS),
.slave = 0,
.cs = { .gpio = { .port = NULL, .pin = 0, .dt_flags = 0 }, .delay = 0 },
};
SPI 模式说明:
SPI_MODE_CPOL | SPI_MODE_CPHA = Mode 3(CPOL=1, CPHA=1)
- BNO080 支持 Mode 0 和 Mode 3
- 如果通信失败,可尝试移除
SPI_MODE_CPOL | SPI_MODE_CPHA 切换到 Mode 0
4.4 HAL open 函数
static int sh2_spi_hal_open(sh2_Hal_t *self)
{
/* 1. 检查设备就绪 */
if (!device_is_ready(spi_dev)) return SH2_ERR_IO;
/* 2. 配置 GPIO */
gpio_pin_configure(cs_gpio_dev, CS_PIN, GPIO_OUTPUT_HIGH); /* CS 初始高 */
gpio_pin_configure(int_reset_gpio_dev, RESET_PIN, GPIO_OUTPUT_LOW); /* RST 低 */
gpio_pin_configure(int_reset_gpio_dev, INT_PIN,
GPIO_INPUT | GPIO_INT_EDGE_FALLING); /* INT 下降沿 */
/* 3. 保存 HAL 实例 */
hal_instance = self;
/* 4. 初始化状态 */
in_reset = true;
is_open = true;
/* 5. 执行虚拟 SPI 操作(建立 SCLK 初始状态) */
spi_dummy_op();
/* 6. 延时确保复位生效 */
delay_us(RESET_DELAY_US);
/* 7. 设置启动模式为 SHTP-SPI (PS1=1, PS0=1) */
set_ps0(true);
set_ps1(true);
/* 8. 释放复位,非 DFU 模式 */
set_bootn(true);
set_reset(true);
/* 9. 注册 INT 中断回调 */
gpio_init_callback(&int_cb_data, int_callback, BIT(INT_PIN));
gpio_add_callback(int_reset_gpio_dev, &int_cb_data);
gpio_pin_interrupt_configure(int_reset_gpio_dev, INT_PIN, GPIO_INT_EDGE_FALLING);
/* 10. 等待 INT 被触发 */
delay_us(START_DELAY_US);
return SH2_OK;
}
4.5 HAL read/write 函数
static int sh2_spi_hal_read(sh2_Hal_t *self, uint8_t *pBuffer,
unsigned len, uint32_t *t)
{
static uint8_t dummy_tx[SH2_HAL_MAX_TRANSFER_IN] = {0};
if (len < READ_LEN) return SH2_ERR_BAD_PARAM;
/* 检查 data_ready 标志(由 INT 中断设置) */
if (!data_ready) return 0;
data_ready = false;
/* 拉低 CS */
set_cs(false);
k_usleep(50); /* CS setup time */
/* 读取 header (4 字节) */
if (spi_transfer_sync(dummy_tx, pBuffer, READ_LEN) != 0) {
set_cs(true);
return 0;
}
/* 解析数据长度 */
uint16_t rx_len = (pBuffer[0] + (pBuffer[1] << 8)) & ~0x8000;
if (rx_len > len || rx_len < READ_LEN) {
set_cs(true);
return 0;
}
/* 读取 payload */
if (rx_len > READ_LEN) {
if (spi_transfer_sync(dummy_tx, pBuffer + READ_LEN, rx_len - READ_LEN) != 0) {
set_cs(true);
return 0;
}
}
set_cs(true);
*t = rx_timestamp_us;
return rx_len;
}
static int sh2_spi_hal_write(sh2_Hal_t *self, uint8_t *pBuffer, unsigned len)
{
if (len == 0 || pBuffer == NULL) return SH2_ERR_BAD_PARAM;
/* 激活 WAKEN(拉低 PS0) */
set_ps0(false);
set_cs(false);
if (spi_transfer_sync(pBuffer, NULL, len) != 0) {
set_cs(true);
set_ps0(true);
return SH2_ERR_IO;
}
set_cs(true);
set_ps0(true);
return len;
}
关键设计:
data_ready 标志由 INT 中断回调设置,read() 检查该标志避免无数据时的 SPI 操作
read() 分两步:先读 4 字节 header 获取长度,再读 payload
write() 通过拉低 PS0(WAKEN)唤醒传感器,写入完成后恢复
- CS setup time (50us) 满足 BNO080 时序要求
4.6 中断处理
/* INT 引脚中断回调(中断上下文) */
static void int_callback(const struct device *dev,
struct gpio_callback *cb, gpio_port_pins_t pins)
{
/* 记录时间戳 */
rx_timestamp_us = time_now_us();
in_reset = false;
data_ready = true; /* 设置数据就绪标志 */
}
关键设计:
- 中断回调仅设置
data_ready 标志和记录时间戳,不执行 SPI 传输
read() 函数在下一次被 SH2 核心调用时检查 data_ready 标志并执行同步读取
- 无工作队列、无异步缓冲,实现简洁
5. 位域传感器管理
5.1 位域掩码结构
BNO080 支持大量传感器类型(0x01~0x2E),使用双 uint32_t 位域高效管理:
typedef struct {
uint32_t mask_lo; /* 传感器 ID 0x00-0x1F */
uint32_t mask_hi; /* 传感器 ID 0x20-0x3F */
} bno080_mask_t;
位操作宏: | 宏 | 说明 | |---|---| | BNO080_MASK_SET(mask, id) | 设置传感器位 | | BNO080_MASK_CLEAR(mask, id) | 清除传感器位 | | BNO080_MASK_TEST(mask, id) | 测试传感器位 | | BNO080_MASK_OR(dst, src) | 合并掩码 | | BNO080_MASK_IS_ZERO(mask) | 检查是否为空 |
5.2 预定义传感器组
/* 基础 9 轴 */
#define BNO080_GROUP_BASIC_9AXIS /* ACC + GYRO + MAG + RV + LINACC + GRAVITY */
/* 原始数据 */
#define BNO080_GROUP_ALL_RAW /* RAW_ACC + RAW_GYRO + RAW_MAG */
/* 未校准 */
#define BNO080_GROUP_ALL_UNCAL /* GYRO_UNCAL + MAG_UNCAL */
/* 旋转矢量变体 */
#define BNO080_GROUP_ALL_RV /* RV + GAME_RV + GEOMAG_RV + GYRO_RV */
/* 环境传感器 */
#define BNO080_GROUP_ALL_ENV /* PRESSURE + LIGHT + HUMIDITY + PROXIMITY + TEMP */
/* 活动检测器 */
#define BNO080_GROUP_ALL_DETECTORS /* STEP + TAP + SHAKE + FLIP + PICKUP + ... */
/* 全部传感器 */
#define BNO080_GROUP_ALL /* 所有支持的传感器类型 */
5.3 统一数据结构
typedef struct {
bno080_mask_t valid_mask; /* 标记哪些字段有效 */
/* 基础运动传感器 */
sh2_Accelerometer_t accel;
sh2_Gyroscope_t gyro;
sh2_MagneticField_t mag;
sh2_RotationVectorWAcc_t rv;
sh2_Accelerometer_t lin_accel;
sh2_Accelerometer_t gravity;
/* 原始数据、未校准数据、旋转矢量变体、环境传感器、活动检测器等 */
/* ... */
} bno080_data_t;
bno080_data_fill() 内联函数根据 sensorId 自动填充对应字段并设置 valid_mask 位。
5.4 sh2_demo API
| 函数 |
说明 |
sh2_demo_init() |
初始化 SH2 Demo(根据 Kconfig 选择默认传感器组) |
sh2_demo_service() |
服务循环,必须定期调用 |
sh2_demo_get_sensor_data(id, data) |
获取单个传感器数据 |
sh2_demo_read_all(data) |
读取所有启用传感器数据到 bno080_data_t |
sh2_demo_enable_sensors(mask) |
启用传感器(位域方式) |
sh2_demo_disable_sensors(mask) |
禁用传感器(位域方式) |
sh2_demo_enable_all() |
启用所有传感器 |
sh2_demo_get_enabled_mask() |
获取当前启用的传感器掩码 |
6. 设备树绑定
6.1 绑定文件 (bosch,bno080.yaml)
compatible: "bosch,bno080"
include: [spi-device.yaml, sensor-device.yaml]
properties:
int-gpios:
type: phandle-array
required: true
description: INT 引脚,中断输出(低电平有效)
reset-gpios:
type: phandle-array
required: false
description: RST 引脚,复位(低电平有效)
ps0-gpios:
type: phandle-array
required: false
description: PS0 引脚(可选,硬件固定时可省略)
ps1-gpios:
type: phandle-array
required: false
description: PS1 引脚(可选,硬件固定时可省略)
cs-gpios:
type: phandle-array
required: true
description: CS 引脚(低电平有效)
current-speed:
type: int
required: false
default: 115200
description: UART 波特率(仅 UART 模式)
reset-delay-us:
type: int
required: false
default: 10000
description: |
Reset delay in microseconds.
Time to wait after asserting reset before releasing.
Default is 10000 us (10 ms) as per BNO080 datasheet.
start-delay-us:
type: int
required: false
default: 2000000
description: |
Start delay in microseconds.
Time to wait after power-on before sensor is ready.
Default is 2000000 us (2 seconds) as per BNO080 datasheet.
6.2 设备树覆盖 (overlay)

#include <zephyr/dt-bindings/gpio/gpio.h>
#include <zephyr/dt-bindings/pinctrl/renesas/pinctrl-ra.h>
/ {
chosen {
zephyr,spi = &spi1;
};
};
/* 定义 SPI1 pinctrl( SSL 为CS 片选) */
&pinctrl {
spi1_no_ssl: spi1_no_ssl {
group1 {
psels = <RA_PSEL(RA_PSEL_SPI, 1, 0)>, /* MISO: P1.0 */
<RA_PSEL(RA_PSEL_SPI, 1, 1)>, /* MOSI: P1.1 */
<RA_PSEL(RA_PSEL_SPI, 1, 2)>; /* RSPCK: P1.2 */
};
};
};
&spi1 {
status = "okay";
pinctrl-0 = <&spi1_no_ssl>;
pinctrl-names = "default";
bno080: bno080@0 {
compatible = "bosch,bno080";
reg = <0>;
spi-max-frequency = <1000000>; /* 1MHz */
spi-cpol; /* SPI Mode 3 */
spi-cpha;
status = "okay";
cs-gpios = <&ioport1 3 GPIO_ACTIVE_LOW>; /* P1.3 */
int-gpios = <&ioport3 1 GPIO_ACTIVE_LOW>; /* P3.1 */
reset-gpios = <&ioport3 6 GPIO_ACTIVE_LOW>; /* P3.6 */
};
};
关键注意事项:
- CS 引脚必须使用
- PS0/PS1 硬件拉高 — 需要拉高 3.3V
- SPI 模式 —
spi-cpol + spi-cpha = Mode 3
7. Kconfig 配置
7.1 项目 Kconfig
menuconfig ENABLE_BNO080
bool "Enable BNO080 9-axis IMU"
help
Enable BNO080 9-axis IMU sensor driver and examples (SPI interface).
if ENABLE_BNO080
choice BNO080_DEFAULT_SENSORS
prompt "BNO080 default sensor group"
default BNO080_DEFAULT_ALL
config BNO080_DEFAULT_BASIC_9AXIS
bool "Basic 9-axis (accel/gyro/mag/rv/linacc/gravity)"
config BNO080_DEFAULT_ALL_RAW
bool "All raw sensors"
config BNO080_DEFAULT_ALL
bool "All sensors"
endchoice
menuconfig BNO080_TIMING
bool "BNO080 timing configuration"
default y
if BNO080_TIMING
config BNO080_RESET_DELAY_US
int "Reset delay (microseconds)"
default 10000
help
Delay after asserting reset before releasing.
config BNO080_START_DELAY_US
int "Start delay (microseconds)"
default 2000000
help
Delay after power-on before sensor is ready.
config BNO080_SPI_FREQ
int "SPI frequency (Hz)"
default 1000000
help
SPI clock frequency for BNO080 communication.
endif # BNO080_TIMING
endif # ENABLE_BNO080
7.2 板级 prj.conf
# SPI 驱动
CONFIG_SPI=y
CONFIG_SPI_ASYNC=y
CONFIG_SPI_RENESAS_RA=y
CONFIG_SPI_INTERRUPT=y
# GPIO 驱动
CONFIG_GPIO=y
CONFIG_GPIO_RA_IOPORT=y
# 外部中断
CONFIG_RENESAS_RA_EXTERNAL_INTERRUPT=y
# 启用 BNO080
CONFIG_ENABLE_BNO080=y
8. CMake 构建集成
# BNO080 驱动源文件
set(BNO080_DIR ${CMAKE_CURRENT_SOURCE_DIR}/drv/bno080)
# SH2 核心库
set(SH2_SOURCES
${BNO080_DIR}/src/sh2.c
${BNO080_DIR}/src/sh2_SensorValue.c
${BNO080_DIR}/src/sh2_util.c
${BNO080_DIR}/src/shtp.c
${BNO080_DIR}/src/euler.c
)
# SH2 平台适配层
set(SH2_PLATFORM_SOURCES
${BNO080_DIR}/platform/sh2_spi_hal.c
${BNO080_DIR}/platform/sh2_i2c_hal.c
${BNO080_DIR}/platform/sh2_uart_hal.c
${BNO080_DIR}/platform/sh2_demo.c
)
# BNO080 所有源文件汇总
set(BNO080_SOURCES
${SH2_SOURCES}
${SH2_PLATFORM_SOURCES}
)
set(BNO080_INCLUDE_DIRS
${BNO080_DIR}/inc
${BNO080_DIR}/platform
)
# 条件编译
if(CONFIG_ENABLE_BNO080)
target_sources(app PRIVATE example/example_bno080.c)
target_sources(app PRIVATE ${BNO080_SOURCES})
target_include_directories(app PRIVATE ${BNO080_INCLUDE_DIRS})
endif()
9. 应用层调用
9.1 初始化流程
/* 1. 初始化 SH2 Demo */
int ret = sh2_demo_init();
if (ret) {
LOG_ERR("SH2 Demo 初始化失败: %d", ret);
return ret;
}
/* sh2_demo_init() 内部执行: */
/* - 初始化 SPI HAL */
/* - 调用 sh2_open() */
/* - 设置传感器回调 */
/* - 配置并启用各传感器 */
9.2 传感器配置
/* 在 sh2_demo_init() 中配置传感器 */
sh2_SensorConfig_t config;
memset(&config, 0, sizeof(config));
config.reportInterval_us = 100000; /* 100ms 报告间隔 */
config.changeSensitivityEnabled = false;
config.wakeupEnabled = false;
config.alwaysOnEnabled = false;
/* 启用加速度计 */
sh2_setSensorConfig(SH2_ACCELEROMETER, &config);
/* 启用陀螺仪 */
sh2_setSensorConfig(SH2_GYROSCOPE_CALIBRATED, &config);
/* 启用磁力计 */
sh2_setSensorConfig(SH2_MAGNETIC_FIELD_CALIBRATED, &config);
/* 启用旋转矢量 */
sh2_setSensorConfig(SH2_ROTATION_VECTOR, &config);
/* 启用线性加速度 */
sh2_setSensorConfig(SH2_LINEAR_ACCELERATION, &config);
/* 启用重力 */
sh2_setSensorConfig(SH2_GRAVITY, &config);
9.3 数据读取
/* 主循环 */
while (1) {
/* 服务 SH2 设备(必须定期调用) */
sh2_demo_service(); /* 内部调用 sh2_service() */
/* 读取加速度计 */
sh2_SensorValue_t data;
if (sh2_demo_get_sensor_data(SH2_ACCELEROMETER, &data) == 0) {
LOG_INF("ACC (m/s^2): X=%.3f Y=%.3f Z=%.3f",
data.un.accelerometer.x,
data.un.accelerometer.y,
data.un.accelerometer.z);
}
/* 读取陀螺仪 */
if (sh2_demo_get_sensor_data(SH2_GYROSCOPE_CALIBRATED, &data) == 0) {
LOG_INF("GYR (rad/s): X=%.3f Y=%.3f Z=%.3f",
data.un.gyroscope.x,
data.un.gyroscope.y,
data.un.gyroscope.z);
}
/* 读取旋转矢量 */
if (sh2_demo_get_sensor_data(SH2_ROTATION_VECTOR, &data) == 0) {
LOG_INF("ARVR_GRV: i=%.3f j=%.3f k=%.3f real=%.3f",
data.un.rotationVector.i,
data.un.rotationVector.j,
data.un.rotationVector.k,
data.un.rotationVector.real);
}
k_sleep(K_MSEC(10));
}
9.4 支持的传感器类型
| 传感器 ID |
数据类型 |
单位 |
SH2_ACCELEROMETER |
sh2_Accelerometer_t |
m/s^2 |
SH2_LINEAR_ACCELERATION |
sh2_Accelerometer_t |
m/s^2 |
SH2_GRAVITY |
sh2_Accelerometer_t |
m/s^2 |
SH2_GYROSCOPE_CALIBRATED |
sh2_Gyroscope_t |
rad/s |
SH2_MAGNETIC_FIELD_CALIBRATED |
sh2_MagneticField_t |
uTesla |
SH2_ROTATION_VECTOR |
sh2_RotationVectorWAcc_t |
四元数 |
SH2_GAME_ROTATION_VECTOR |
sh2_RotationVector_t |
四元数 |
SH2_PRESSURE |
sh2_Pressure_t |
hPa |
SH2_AMBIENT_LIGHT |
sh2_AmbientLight_t |
lux |
SH2_HUMIDITY |
sh2_Humidity_t |
% |
SH2_PROXIMITY |
sh2_Proximity_t |
cm |
SH2_TEMPERATURE |
sh2_Temperature_t |
C |
SH2_STEP_COUNTER |
sh2_StepCounter_t |
steps |
SH2_STABILITY_CLASSIFIER |
sh2_StabilityClassifier_t |
enum |
10. 移植步骤清单
步骤 1:获取官方库
- 从 Hillcrest 获取 SH2 传感器库
- 将
src/ 和 inc/ 复制到 drv/bno080/
步骤 2:实现 HAL 层
- 创建
platform/sh2_spi_hal.c
- 实现
sh2_Hal_t 接口:
open: 初始化 SPI、GPIO,执行复位序列
close: 关闭 SPI,进入复位
read: 检查 data_ready 标志,执行同步 SPI 读取
write: 拉低 PS0 唤醒传感器,执行同步 SPI 写入
getTimeUs: 返回微秒时间戳
- 实现 INT 中断回调(仅设置
data_ready 标志)
步骤 3:实现应用封装层
- 创建
platform/sh2_demo.c
- 封装
sh2_demo_init():
- 初始化 HAL
- 调用
sh2_open()
- 设置传感器回调
- 配置传感器
- 封装
sh2_demo_service():调用 sh2_service()
- 封装
sh2_demo_get_sensor_data():从缓存读取数据
步骤 4:创建设备树绑定
- 创建
dts/bindings/sensor/bosch,bno080.yaml
- 定义
compatible = "bosch,bno080"
- 添加
int-gpios、reset-gpios、cs-gpios、ps0-gpios、ps1-gpios、reset-delay-us、start-delay-us 属性
步骤 5:编写设备树覆盖
- 在
boards/xxx.overlay 中添加 BNO080 节点
- 配置 SPI 控制器和引脚
- 确保 CS 使用 GPIO(不使用硬件 SSL)
步骤 6:配置 Kconfig 和 CMake
- 在
Kconfig 中添加 ENABLE_BNO080
- 在
CMakeLists.txt 中条件编译 BNO080 源文件
步骤 7:编写应用层代码
- 参考
sh2-demo 示例编写 example_bno080.c
- 调用
sh2_demo_init() 初始化
- 在主循环中调用
sh2_demo_service() 和 sh2_demo_get_sensor_data()
11. 成果展示


12. 常见问题与解决方案
12.1 SPI 通信失败(MISO 返回 0xFF)
可能原因:
- CS 引脚冲突 — 硬件 SSL 与 GPIO CS 配置冲突
- PS0/PS1 未拉高 — BNO080 未进入 SPI 模式
- SPI 模式错误 — CPOL/CPHA 配置不匹配
- 引脚接线错误 — MISO/MOSI 接反
解决方案:
- 检查 pinctrl 配置,确保使用
spi1_default
- 确认 PS0 和 PS1 引脚已拉高到 3.3V
- 尝试不同的 SPI 模式(Mode 0 或 Mode 3)
- 使用逻辑分析仪检查 CS、CLK、MISO 信号
12.2 初始化超时
可能原因:
- 硬件接线错误 — 电源、地、复位引脚未正确连接
- PS0/PS1 配置错误 — BNO080 未进入预期模式
- 复位序列不正确 — 复位时间不足
解决方案:
- 检查硬件接线,特别是 VCC、GND、RST
- 确认 PS0/PS1 电平符合预期模式(SPI: PS1=1, PS0=1)
- 确保复位延时足够(RESET_DELAY_US = 10ms, START_DELAY_US = 2s)
12.3 INT 中断不触发
可能原因:
- GPIO 中断配置错误 — 引脚不支持外部中断
- 中断优先级问题 — 优先级设置不当
data_ready 标志未正确设置 — 中断回调未注册
解决方案:
- 查阅 MCU 数据手册,确认引脚支持外部中断
- 检查 overlay 中
port_irq 配置
- 确保 INT 中断回调中正确设置
data_ready = true
12.4 传感器数据不更新
可能原因:
- sh2_service() 未定期调用 — 数据未从 HAL 读取
- 传感器未启用 — 未调用
sh2_setSensorConfig()
- 回调未注册 — 未调用
sh2_setSensorCallback()
解决方案:
- 确保主循环中调用
sh2_demo_service()(建议间隔 10ms)
- 检查
sh2_demo_init() 中是否启用了目标传感器
- 检查回调注册是否成功
12.5 校准数据丢失
可能原因:
解决方案:
/* 保存校准数据到 flash */
sh2_saveDcdNow();
/* 或启用自动保存 */
sh2_setDcdAutoSave(true);
12.6 只有加速度计输出数据
现象:
- 只有加速度计 (ACC) 数据正常输出
- 陀螺仪、磁力计、旋转矢量等传感器无数据
可能原因:
- SH2 库 bug —
sh2.c 中 setSensorConfigStart() 函数使用了错误的字段名
- 传感器配置未正确发送 — 配置命令发送到错误的内存地址
解决方案: 检查并修复 drv/bno080/src/sh2.c 第 977 行:
// 错误代码(原始 SH2 库)
sh2_SensorConfig_t *pConfig = pSh2->opData.getSensorConfig.pConfig;
// 正确代码(修复后)
sh2_SensorConfig_t *pConfig = pSh2->opData.setSensorConfig.pConfig;
根本原因分析:
opData 是一个联合体,包含 getSensorConfig 和 setSensorConfig 两个结构
- 原始代码在
setSensorConfigStart() 中错误地使用了 getSensorConfig.pConfig
- 导致配置指针指向错误位置,传感器配置命令未正确发送
- 加速度计是 BNO080 的默认传感器,不需要配置就能输出数据
- 其他传感器需要正确的配置命令才能启用
12.7 传感器初始化时间过长
现象:
- 初始化后立即读取数据,部分传感器无输出
- 需要等待一段时间后才能看到所有传感器数据
可能原因:
- BNO080 传感器初始化需要时间
- 加速度计最先就绪,陀螺仪、磁力计需要更长时间
- 融合传感器(旋转矢量、线性加速度、重力)依赖基础传感器
解决方案: 使用 sh2_demo_wait_for_sensors_ready() 等待传感器就绪:
/* 初始化 SH2 Demo */
int ret = sh2_demo_init();
if (ret) {
LOG_ERR("SH2 Demo 初始化失败: %d", ret);
return ret;
}
/* 等待传感器就绪(默认超时 5 秒) */
ret = sh2_demo_wait_for_sensors_ready(0);
if (ret < 0) {
LOG_ERR("等待传感器就绪失败: %d", ret);
return ret;
}
LOG_INF("传感器就绪,开始数据采集");
/* 主循环 */
while (1) {
sh2_demo_service();
/* 读取传感器数据 */
}
函数说明:
sh2_demo_wait_for_sensors_ready(timeout_ms):等待所有启用的传感器就绪
- 参数
timeout_ms:超时时间(毫秒),0 表示使用默认超时(5000ms)
- 返回值:就绪的传感器数量,负值表示错误
- 函数会输出日志,显示哪些传感器已就绪,哪些未就绪
提供移植驱动库,后续实现完整项目整体提交
附件:bno080.zip