[STM32]BMI270驱动开发(一)
BMI270 Driver Development
这篇文档描述了BMI270驱动的开发笔记
初始化
基本过程
首先参考了用户手册的Digital Interface一章,我们需要知道这个IMU的通信方法和时序:
- I2C:地址是0x68(SD0接地)/0x69(SD0接VDDIO),支持100kHz,40kHz,1MHz,7-bit地址模式
- SPI: 支持10MHz,三线模式,读模式在发送地址后需要一个dummy bit,才能接data
接着参考了用户手册的Quick Start Guide一章,我们需要知道关于配置的几个基本方法:
- 通过读取0x00地址的CHIP_ID(0x24)来测试通信是否正常
- 写配置时,先关闭PWR_CONF(0x7C第0位置0),然后等待450us,之后开启INIT_CTRL(0x59置0x00),连接写入配置列表(0x5E)后,关闭INIT_CTRL(0x59置0x01)(
bmi270.c
开头给出了配置列表,非常长) - 读取INTERNAL_STATUS(0x21)(3,0)是否为0x01,表示init_ok,其它则为出错
4.4节对此进行了更详细的描述,当完成这个操作后,IMU进入"Configuration mode"。
Configuration mode和其它模式
4.5节对Configuration mode和其它模式进行了对比。可以看到,Configuration mode是一个没有开启任何传感器的模式。
-
需要注意,重新上电和soft reset都会使其进入Suspend mode,需要从这个模式切换到其它模式才能正常工作。
-
不同的power mode不影响传感数据质量,只影响其唤起时间
-
从节能模式(PWR_CONFI.adv_power_save=0b1)切换时,需要至少450us的延迟。由于每次都需要从Suspend mode切换,所以这450us是必须的。
建立通信
static int8_t write_config_file(struct bmi2_dev *dev)
in bmi2.c
:
这个方法实现了上节描述的写配置过程,而官方提供的例子对这个方法进行了层层包装,又臭又长,本文打算基于这个方法对配置过程进行简化。不过在此之前,需要先做到基本的通信。
在写入配置文件之前,需要先将通信接口接入BMI270中,需要进行下面的几个工作:
- 传递读和写:这里使用了函数指针,将spi的读和写函数在实例化类的时候传入
- 传递延迟:同上,这里使用sys/delay.c中的delay_us()
- 设定SPI/I2C:这里我没有通过类属性成员来定义,而是放在了
user_interface_header.h
这个头文件中用constexpr
定义模式 - 其它设定:
Hook_Log
指向打印函数,这里是printf
的定义,Hook_Error_Handler
指向一个错误处理函数,用于打印错误代码。
,核心代码部分如下:
/* user_interface_header.h */
// rename to selet mode, optional names: I2C, I3C
constexpr bool SPI = true;
using Hook_Read = void (*)(uint8_t reg_addr, uint8_t *reg_data, uint32_t len);
using Hook_Write = void (*)(uint8_t reg_addr, uint8_t *reg_data, uint32_t len);
using Hook_Delay = void (*)(uint32_t period);
using Hook_Log = int (*)(const char *format, ...);
//args:
// error_source: is passsed through user definition, refer to the @ref Debug_Device_Code
// error_code: is the debug code, referto the @ref BMI270_NS::Debug_Code in imu_defs.h
using Hook_Error_Handler = void (*)(uint8_t error_source, uint8_t error_code);
/* imu.cpp */
BMI2700::BMI2700(
User_Interface::Hook_Read read_fun,
User_Interface::Hook_Write write_fun,
User_Interface::Hook_Delay delay_us_fun,
User_Interface::Hook_Log log,
User_Interface::Hook_Error_Handler error_hdl,
uint16_t read_write_len)
: _read(read_fun), _write(write_fun), _delay_us(delay_us_fun), _log(log),
_error_hdl(error_hdl),_read_write_len(read_write_len)
{
// Any private variable init here,
// Please do not call any methods here, put them in Init()
_config_file = Configration_Mode::bmi270_config_file;
_config_file_size = sizeof(Configration_Mode::bmi270_config_file);
}
/* main.cpp */
#include "user_interface.h"
#define msb_rx 0x80
#define msb_tx 0x00
#define read_byte_cmd(adr) spi1_read_write_byte(msb_rx | adr);
#define write_byte_cmd(adr) spi1_read_write_byte(msb_tx | adr);
void spi_read(uint8_t reg_addr, uint8_t *reg_data, uint32_t len)
{
uint8_t temp_buf;
SPI1_CS_SAFE({
read_byte_cmd(reg_addr);
HAL_SPI_Receive(&hspi1, &temp_buf, 1, 1000);
HAL_SPI_Receive(&hspi1, reg_data, len, 1000);
});
}
void spi_write(uint8_t reg_addr, uint8_t *reg_data, uint32_t len)
{
SPI1_CS_SAFE({
write_byte_cmd(reg_addr);
HAL_SPI_Transmit(&hspi1, reg_data, len, 1000);
});
}
void spi_delay_us(uint32_t period)
{
delay_us(period);
}
int print_log(char *ptr, int len)
{
HAL_UART_Transmit(&g_uart1_handle, (uint8_t *)ptr, len, HAL_MAX_DELAY);
return len;
}
BMI2700 BMI2700_instance{
spi_read,
spi_write,
spi_delay_us,
printf,
Error_Handler,
46};
void bmi270_init()
{
spi_init(SPI1);
if (BMI2700_instance.Init())
{
printf("BMI2700 init failed, check the hw connection\r\n");
}
BMI2700_instance.Soft_Reset();
}
写入配置文件
配置文件在bmi270.c
的开头,相当于是一个官方提供的校准数据文件,大小为8kb。这里用了三个方法:
Load_Config_File
: 参考官方文档的4.4节,按照save power mode off -> INIT_CTRL reset ->burst write -> INIT_CTRL set -> check status -> save power mode on。其中的延迟不可省略_Init_Reg_Burst_Write
: 关于这里的写入方法在文档没有详细说明。INIT文件写入操作涉及INIT_CTRL
,INIT_ADDR_0
,INIT_ADDR_1
和INIT_DATA
几个地址,INIT_ADDR_0
和INIT_ADDR_1
组成了一个“映射地址的地址”,写入INIT_DATA
中的数据会自动转移到一个16位的大寄存器,INIT_DATA
相当于是一个出入餐口,INIT_ADDR_0
和INIT_ADDR_1
是一个指针的低位和高位,每写/读2byte,这对地址的值自增1 。无论对该地址还是INIT_DATA
进行读还是写,都需要将INIT_CTRL
置0 。
写入INIT_ADDR_0
和INIT_ADDR_1
时,可直接对INIT_ADDR_0
写入2byte,自动将后一位放入INIT_ADDR_1
。
450延迟不可省略_Init_Reg_Check
:检查写入数据是否正确,这里只检查前后16位。请将该操作放在INIT_CTRL
置0的域内。
uint8_t BMI2700::Load_Config_File()
{
Write_Bits(Reg_Adr::PWR_CONF_ADDR, 0x01, 0x00);
_delay_us(100);
Read_Byte(Reg_Adr::PWR_CONF_ADDR);
_delay_us(450);
// set INIT_CTRL to 0x00,
Write_Bit(Reg_Adr::INIT_CTRL_ADDR, 0, false);
_delay_us(450);
// burst write config file using proper BMI270 method
_Init_Reg_Burst_Write();
_delay_us(100);
_Init_Reg_Check();
// set INIT_CTRL to 0x01
Write_Bit(Reg_Adr::INIT_CTRL_ADDR, 0, true);
_delay_us(450);
// check INTERNAL_STATUS
_delay_us(20000); // wait until INTERNAL_STATUS_ADDR set to 0x01
if (Read_Byte(Reg_Adr::INTERNAL_STATUS_ADDR)!=0x01)
{
BMI2_DEBUG(Debug_Code::INIT_CONFIG_FILE_NOT_COMPLETE);
}
/* Enable advanced power save mode */
Write_Bits(Reg_Adr::PWR_CONF_ADDR, 0x01, 0x01);
return 0;
}
uint8_t BMI2700::_Init_Reg_Burst_Write()
{
if (_config_file != NULL)
{
/* Write the 2 bytes of address in consecutive locations */
Write_Bytes(Reg_Adr::INIT_ADDR_0, (uint8_t *)0x00, 2);
_delay_us(450);
Write_Bytes(Reg_Adr::INIT_DATA_ADDR, (uint8_t *)(_config_file), _config_file_size);
}
else
{
BMI2_DEBUG(Debug_Code::NULL_PTR);
}
return 0;
}
constexpr uint32_t NUM_OF_CHECK = 16;
// Worning!! This method should implement in enviroment whose INIT_CTRL is 0x00
uint8_t BMI2700::_Init_Reg_Check()
{
uint8_t tmp_data1[NUM_OF_CHECK];
uint8_t tmp_data2[NUM_OF_CHECK];
uint8_t addr[2]{0};
// Check the first 16 bytes
Write_Byte(Reg_Adr::INIT_ADDR_0, 0x00);
_delay_us(450);
Read_Bytes(Reg_Adr::INIT_DATA_ADDR, tmp_data1, NUM_OF_CHECK);
// Check the last 16 bytes
addr[0] = (_config_file_size - NUM_OF_CHECK) / 2 & 0x0f;
addr[1] = (_config_file_size - NUM_OF_CHECK) / 2 >> 4;
Write_Bytes(Reg_Adr::INIT_ADDR_0, addr, 2);
_delay_us(450);
Read_Bytes(Reg_Adr::INIT_DATA_ADDR, tmp_data2, NUM_OF_CHECK);
for (size_t i = 0; i < NUM_OF_CHECK; i++)
{
if ((tmp_data1[i] != _config_file[i]) || (tmp_data2[i] != _config_file[_config_file_size - NUM_OF_CHECK + i]))
{
BMI2_DEBUG(Debug_Code::INIT_CONFIG_FILE_NOT_COMPLETE);
}
}
return 0;
}