基于stm32f030的模拟I2C与博世BMI160六轴传感器通信

博世BMI160简介

​

​ Bosch Sensortec公司推出的最新BMI160惯性测量单元将最顶尖的16位3轴重力加速度计和超低功耗3轴陀螺仪集成于单一封装。采用14管脚LGA封装,尺寸为2.5×3.0×0.8mm3。当加速度计和陀螺仪在全速模式下运行时,耗电典型值低至950µA,仅为市场上同类产品耗电量的50%或者更低。

BMI160是一款高度集成的低功耗惯性测量单元(IMU),可提供精确的加速度和角速率(陀螺 仪)测量。


主要特点

 高性能加速度计和陀螺仪(硬件同步)
 功耗极低:典型值。 920μA(全功能加速度计和陀螺仪)
 符合Android Kitkat:重要的运动和步进检测器/步进计数器(每个5μA)
 占地面积2.5 x 3.0 mm2,高度0.83 mm
 内置电源管理单元(PMU),用于高级电源管理
 具有快速启动陀螺仪模式的省电功能
 宽电源范围:1.71V …… 3.6V
 可分配的1024字节FIFO缓冲区(能够处理外部传感器数据)
 硬件传感器时间戳,用于精确的传感器数据融合
 集成中断,用于增强自主运动检测
 灵活的数字主接口,通过I2C或SPI连接主机
 扩展I2C模式,时钟频率高达1 MHz
 用于OIS应用的附加二级高速接口
 能够处理外部传感器数据 (例如Bosch Sensortec的地磁或气压传感器)


下载地址

BMI160的中文Datasheet的下载连接如下: [点击此处进入下载网页]

官方英文Datasheet下载地址

官方参考驱动代码下载地址

主接口I2C / SPI协议选择

bmi160flow1

​ 从上面的框图中,我们可以看到,BMI160与外部进行双向数据传输的方式有两种:SPI和I2C。下面,我们来看下通过I2C与外部进行通信的方式。当BMI160通过I2C与外部进行通信的时候,BMI160将作为I2C从设备挂到主控芯片(主设备)的I2C总线上,所以,主控芯片在配置其对应的I2C驱动时就需要知道BMI160的从设备地址。

​ 上电后,根据芯片选择CSB引脚行为自动选择协议复位/上电时,BMI160处于I2C模式。 如果CSB在上电期间连接到VDDIO且未更改,则传感器接口 在I2C模式下工作。 对于使用I2C,建议将CSB线硬连线到VDDIO。 由于上电复位仅在VDD和 VDDIO都建立时执行,因此不存在由于上电顺序而导致协议检测错误的风险。

​ 如果CSB在上电后看到上升沿,则BMI160接口切换到SPI直到复位或下一次上电。 因此,在启 动SPI之前需要CSB上升沿通讯。

​ 因此,建议在实际通信之前对ADDRESS 0x7F执行SPI单读访问,以便使用SPI接口。 如果没有数据,则无法切换CSB位通信时,寄存器(0x70)NV_CONF中还有spi_en位,可用于将 主接口永久设置为SPI,而无需在每次上电或复位时切换CSB引脚。

本文采用的是I2C接口

I2C接口

如果SDO引脚被拉至’GND‘,器件的I²C地址为0b1101000(0x68)。

如果SDO引脚被拉至VDDIO‘,器件的I²C地址为0b1101001(0x69)。

I2C时序图

下图给出的I²C时序的定义

I²C协议的工作原理如下:

START: 总线上的数据传输从SDA线上的高电平到低电平转换开始,而SCL保持高电平(I²C总 线主机指示的启动条件(S))。 一旦主机传输START信号,总线就会被认为是忙碌的。

STOP: 每个数据传输应由主机产生的停止信号(P)终止。 STOP条件是SDA线上的低电平到高 电平转换,而SCL保持高电平。

ACKS: 必须确认传输的每个数据字节。 它由接收器发送的确认位指示。 发送器必须在应答脉冲 期间释放SDA线(无下拉),而接收器必须将SDA线拉低,以便在应答时钟周期的高电平期间保 持稳定的低电平。

I²C写数据

I²C写访问可用于在一个序列中写入数据字节。 该序列以主机产生的启动条件开始,后跟7位从机地址和写入位(RW = 0)。 从器件发送一个应 答位(ACKS = 0)并释放总线。 然后主机发送一个字节的寄存器地址。 从机再次确认传输并等 待8位数据,这些数据应写入指定的寄存器地址。 从机确认数据字节后,主机产生停止信号并终 止写入协议。I²C写访问的示例:

I²C读数据

I²C读访问也可用于在一个序列中读取一个或多个数据字节。
I²C读访问也可用于在一个序列中读取一个或多个数据字节。
读序列由一个字节的I²C写阶段和I C读阶段组成。变速器的两个部分必须通过重复启动条件(S)分开。 I²C写入阶段寻址从器件并发送要读取的寄存器地址。从机确认发送后,主机再次产生一个起始条件,并将从机地址与读取位(RW = 1)一起发送。然后主机释放总线并等待从从机读出数据字节。在每个数据字节之后,主机必须生成应答位(ACKS = 0)以启用进一步的数据传输。
来自主设备的NACKM(ACKS = 1)停止从从设备传输的数据。从器件释放总线,以便主器件可以生成STOP条件并终止传输。
寄存器地址自动递增,因此可以顺序读出多个字节。一旦新的数据读取传输开始,起始地址将被设置为自最新的I²C写入命令以来指定的寄存器地址。默认情况下,起始地址设置为0x00。以这种方式,可以从相同的起始地址重复多字节读取。

I²C读访问示例(读取陀螺仪数据):

I²C读写初始化程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
/**************************实现函数********************************************
*函数原型: void IIC_Start(void)
*功  能: 产生IIC起始信号
*******************************************************************************/
void IIC_Start(void)
{
SDA_OUT(); //sda线输出
IIC_SDA_1;
IIC_SCL_1;

delay_us(5);
IIC_SDA_0;//START:when CLK is high,DATA change form high to low

delay_us(5);
IIC_SCL_0;//钳住I2C总线,准备发送或接收数据
}

/**************************实现函数********************************************
*函数原型: void IIC_Stop(void)
*功  能: //产生IIC停止信号
*******************************************************************************/
void IIC_Stop(void)
{
SDA_OUT();//sda线输出
IIC_SCL_0;
IIC_SDA_0;//STOP:when CLK is high DATA change form low to high

delay_us(5);
IIC_SCL_1;
IIC_SDA_1;//发送I2C总线结束信号

delay_us(5);
}

/**************************实现函数********************************************
*函数原型: u8 IIC_Wait_Ack(void)
*功  能: 等待应答信号到来
//返回值:1,接收应答失败
// 0,接收应答成功
*******************************************************************************/
u8 IIC_Wait_Ack(void)
{
u8 ucErrTime=0;
SDA_IN(); //SDA设置为输入
IIC_SDA_1;
delay_us(5);
while(READ_SDA)
{
ucErrTime++;
if(ucErrTime>50)
{
IIC_Stop();
return 1;
}
delay_us(5);
}
IIC_SCL_1;
delay_us(5);
IIC_SCL_0;//时钟输出0
return 0;
}

/**************************实现函数********************************************
*函数原型: void IIC_Ack(void)
*功  能: 产生ACK应答
*******************************************************************************/
void IIC_Ack(void)
{
IIC_SCL_0;
SDA_OUT();
IIC_SDA_0;
delay_us(5);
IIC_SCL_1;
delay_us(5);
IIC_SCL_0;
}

/**************************实现函数********************************************
*函数原型: void IIC_NAck(void)
*功  能: 产生NACK应答
*******************************************************************************/
void IIC_NAck(void)
{
IIC_SCL_0;
SDA_OUT();
IIC_SDA_1;
delay_us(5);
IIC_SCL_1;
delay_us(5);
IIC_SCL_0;
}

/**************************实现函数********************************************
*函数原型: void IIC_Send_Byte(u8 txd)
*功  能: IIC发送一个字节
*******************************************************************************/
void IIC_Send_Byte(u8 txd)
{
u8 t;
SDA_OUT();
IIC_SCL_0;//拉低时钟开始数据传输
for(t=0;t<8;t++)
{
if(txd&0x80)
IIC_SDA_1; //IIC_SDA=1;
else
IIC_SDA_0; //IIC_SDA=0;
txd<<=1;
delay_us(2);
IIC_SCL_1;
delay_us(5);
IIC_SCL_0;
delay_us(3);
}
}

/**************************实现函数********************************************
*函数原型: u8 IIC_Read_Byte(unsigned char ack)
*功  能: //读1个字节,ack=1时,发送ACK,ack=0,发送nACK
*******************************************************************************/
u8 IIC_Read_Byte(unsigned char ack)
{
unsigned char i,receive=0;
SDA_IN();//SDA设置为输入
for(i=0;i<8;i++ )
{
IIC_SCL_0;

delay_us(5);
IIC_SCL_1;
receive<<=1;
if(READ_SDA)receive++;
delay_us(5);
}
if (ack)
IIC_Ack(); //发送ACK
else
IIC_NAck();//发送nACK
return receive;
}

/**************************实现函数********************************************
*功  能: 读取指定设备 指定寄存器的 length个值
输入 dev 目标设备地址
reg 寄存器地址
length 要读的字节数
*data 读出的数据将要存放的指针
返回 读出来的字节数量
*******************************************************************************/

u8 IICreadBytes(u8 dev, u8 reg, u8 *data , uint16_t length)
{
u8 count = 0;

IIC_Start();
IIC_Send_Byte(dev<<1); //发送写命令
IIC_Wait_Ack();
IIC_Send_Byte(reg); //发送地址
IIC_Wait_Ack();
IIC_Start();
IIC_Send_Byte((dev<<1)+1); //进入接收模式
IIC_Wait_Ack();

for(count=0;count<length;count++){

if(count!=length-1)data[count]=IIC_Read_Byte(1); //带ACK的读数据
else data[count]=IIC_Read_Byte(0); //最后一个字节NACK
}
IIC_Stop();//产生一个停止条件
return 0;
}

/**************************实现函数********************************************
*功  能: 将多个字节写入指定设备 指定寄存器
输入 dev 目标设备地址
reg 寄存器地址
length 要写的字节数
*data 将要写的数据的首地址
返回 返回是否成功
*******************************************************************************/
u8 IICwriteBytes(u8 dev, u8 reg, u8 *data , uint16_t length)
{
u8 count = 0;
IIC_Start();
IIC_Send_Byte(dev<<1); //发送写命令
IIC_Wait_Ack();
IIC_Send_Byte(reg); //发送地址
IIC_Wait_Ack();
for(count=0;count<length;count++){
IIC_Send_Byte(data[count]);
IIC_Wait_Ack();
}
IIC_Stop();//产生一个停止条件

return 0; //status == 0;

}

初始化完成之后,就可以结合官方库区读取数据了。

首先对BMI160初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
struct bmi160_dev bmi160sensor ;
void mybmi160_init(void)//BMI160初始化
{
// struct bmi160_dev bmi160sensor;
int8_t rslt = BMI160_OK;
bmi160sensor.id = BMI160_I2C_ADDR;
bmi160sensor.interface = BMI160_I2C_INTF;
bmi160sensor.read = IICreadBytes;
bmi160sensor.write = IICwriteBytes;
bmi160sensor.delay_ms = delay_ms;
bmi160sensor.chip_id=0;
IICreadBytes(BMI160_I2C_ADDR,BMI160_CHIP_ID_ADDR, &bmi160sensor.chip_id, 1);
printf("bmi_id=%d\n",bmi160sensor.chip_id);
// bmi160_get_regs2(BMI160_CHIP_ID_ADDR, &bmi160sensor.chip_id, 1, dev);
rslt = bmi160_init(&bmi160sensor);
/* Select the Output data rate, range of accelerometer bmi160sensor */
bmi160sensor.accel_cfg.odr = BMI160_ACCEL_ODR_800HZ;
bmi160sensor.accel_cfg.range = BMI160_ACCEL_RANGE_4G;
bmi160sensor.accel_cfg.bw = BMI160_ACCEL_BW_NORMAL_AVG4;

/* Select the power mode of accelerometer bmi160sensor */
bmi160sensor.accel_cfg.power = BMI160_ACCEL_NORMAL_MODE;

/* Select the Output data rate, range of Gyroscope bmi160sensor */
bmi160sensor.gyro_cfg.odr = BMI160_GYRO_ODR_800HZ;
bmi160sensor.gyro_cfg.range = BMI160_GYRO_RANGE_2000_DPS;
bmi160sensor.gyro_cfg.bw = BMI160_GYRO_BW_NORMAL_MODE;

/* Select the power mode of Gyroscope bmi160sensor */
bmi160sensor.gyro_cfg.power = BMI160_GYRO_NORMAL_MODE;

/* Set the bmi160sensor configuration */
rslt = bmi160_set_sens_conf(&bmi160sensor);
/* After the above function call, accel and gyro parameters in the device structure
are set with default values, found in the datasheet of the bmi160sensor */
}

获取陀螺仪和加速度数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
//获取数据
void get_accel_gyro_data2(void)
{
uint8_t data_array[15] = {0};
uint8_t lsb;
uint8_t msb;
int16_t msblsb;
int16_t gx,gy,gz,ax,ay,az;

IICreadBytes(BMI160_I2C_ADDR,BMI160_GYRO_DATA_ADDR,data_array,15);//获取陀螺仪的数据

lsb = data_array[0];
msb = data_array[1];
msblsb = (int16_t)((msb << 8) | lsb);
gx = msblsb; /* gyro X axis data */

lsb = data_array[2];
msb = data_array[3];
msblsb = (int16_t)((msb << 8) | lsb);
gy = msblsb; /* gyro Y axis data */

lsb = data_array[4];
msb = data_array[5];
msblsb = (int16_t)((msb << 8) | lsb);
gz = msblsb; /* gyro Z axis data */

/* Accel Data */
lsb = data_array[6];
msb = data_array[7];
msblsb = (int16_t)((msb << 8) | lsb);
ax = (int16_t)msblsb; /* accel X axis data */

lsb = data_array[8];
msb = data_array[9];
msblsb = (int16_t)((msb << 8) | lsb);
ay = (int16_t)msblsb; /* accel Y axis data */

lsb = data_array[10];
msb = data_array[11];
msblsb = (int16_t)((msb << 8) | lsb);
az = (int16_t)msblsb; /* accel Z axis data */
printf("gyro->x=%d,y=%d,z=%d,accel->x=%d,y=%d,z=%d \n",gx,gy,gz,ax,ay,az);
}

设置为任意方向中断,这样可以通过示波器查看,当摇晃BMI160时,观察中断通道1是否有高电平产生了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
void bmi160_Interrupt(void)//设置任意方向中断
{
struct bmi160_int_settg int_config;
int8_t rslt = BMI160_OK;
/* Select the Interrupt channel/pin */
int_config.int_channel = BMI160_INT_CHANNEL_1;// Interrupt channel/pin 1

/* Select the Interrupt type */
int_config.int_type = BMI160_ACC_ANY_MOTION_INT;// Choosing Any motion interrupt
/* Select the interrupt channel/pin settings */
int_config.int_pin_settg.output_en = BMI160_ENABLE;// Enabling interrupt pins to act as output pin
int_config.int_pin_settg.output_mode = BMI160_DISABLE;// Choosing push-pull mode for interrupt pin
int_config.int_pin_settg.output_type = BMI160_ENABLE;// Choosing active low output
int_config.int_pin_settg.edge_ctrl = BMI160_ENABLE;// Choosing edge triggered output
int_config.int_pin_settg.input_en = BMI160_DISABLE;// Disabling interrupt pin to act as input
int_config.int_pin_settg.latch_dur = BMI160_LATCH_DUR_40_MILLI_SEC;// non-latched output

/* Select the Any-motion interrupt parameters */
int_config.int_type_cfg.acc_any_motion_int.anymotion_en = BMI160_ENABLE;// 1- Enable the any-motion, 0- disable any-motion
int_config.int_type_cfg.acc_any_motion_int.anymotion_x = BMI160_ENABLE;// Enabling x-axis for any motion interrupt
int_config.int_type_cfg.acc_any_motion_int.anymotion_y = BMI160_ENABLE;// Enabling y-axis for any motion interrupt
int_config.int_type_cfg.acc_any_motion_int.anymotion_z = BMI160_ENABLE;// Enabling z-axis for any motion interrupt
int_config.int_type_cfg.acc_any_motion_int.anymotion_dur = 0;// any-motion duration
int_config.int_type_cfg.acc_any_motion_int.anymotion_thr = 100;
/* Set the Any-motion interrupt */
rslt=bmi160_set_int_config(&int_config, &bmi160sensor); /* bmi160sensor is an instance of the structure bmi160_dev */
}

更多其他详细程序请到我的公众号或者github查找。

Daniel wechat
如果有帮助就支持一下我吧,或者关注我一下吧!