I2C协议
本节介绍
📝本节您将学习I2C(Inter-Integrated Circuit)总线协议的核心原理与工程实践,掌握TMS320F28P550的双I2C外设模块特性,并通过SysConfig图形化工具实现板载姿态传感器数据采集与实时输出。
🏆本章目标
1️⃣理解I2C协议时序(起始/停止条件、ACK/NACK、时钟同步等)及主从架构。
2️⃣熟悉F28P550的I2C硬件特性(超快模式1MHz SCL、仲裁机制、FIFO缓冲)。
3️⃣掌握软件I2C(GPIO模拟)与硬件I2C(专用外设)的差异及适用场景。
4️⃣实战应用:配置I2C读取板载姿态传感器(LSM6DS3),在CCS的CIO窗口输出欧拉角/加速度数据。
I2C协议介绍
IIC(Inter-Integrated Circuit)协议也称为I2C总线,是一种串行通信协议,通常用于连接低速外设。它由Philips(现在的NXP Semiconductors)公司于1980年代初开发,现在已经成为一个标准。IIC总线只需要两条数据线,分别是串行数据线(SDA)和串行时钟线(SCL),这使得它成为一种非常简单的接口。它适用基于芯片的通信,例如连接传感器、存储器或数字信号处理器等。
在IIC协议中,总线上有一个主设备和多个从设备。主设备掌控着总线上的通信过程,负责发起、控制、停止通信。而从设备则需要等待主设备的请求,接收或发送数据。主设备和从设备之间的数据交换采用帧格式,每个帧通常包含地址、数据和控制信息。主设备根据从设备的地址来选中要通信的设备,从设备则根据控制信息进行相应的操作。IIC协议可以支持多个从设备连接到同一个主设备,为系统设计提供了更大的灵活性。
I2C的硬件实现
I2C总线通常使用两种电压电平,即高电平(VH)和低电平(VL)。高电平为2.5V至5.5V,低电平为0V至0.3V;这些电压电平范围是根据I2C规范确定的。I2C总线有不同的传输速率可选,包括标准模式(100 kbps)、快速模式(400 kbps)以及高速模式。传输速率的选择取决于应用的需求和设备的支持能力。为避免信号冲突,微处理器(MCU)必须只能驱动SDA和 SCL在低电平,即开漏输出。设置为开漏模式主要是为了保护器件和防止干扰。
- 防止干扰:多个器件共享同一条数据线(SDA)和同一条时钟线(SCL),如果采用推挽输出模式,多个器件的输出将会叠加在数据线上,造成信号干扰,严重时会损坏器件或导致通信错误。而采用开漏输出模式,则各个器件的输出只有拉低数据线的部分,不会干扰彼此,从而提高了总线的可靠性和抗干扰能力。
- 防止短路:在开漏输出模式下,由于器件的输出只有拉低数据线的部分,如果两个或多个器件同时输出,也不会造成短路。而如果采用推挽输出模式,两个或多个器件同时输出时,可能会形成短路。比如主设备输出高电平,从设备输出低电平。
- 因设置为开漏模式,需要连接一个外部的上拉电阻(例如:10k)将信号提拉至高电平。故I2C总线中的SDA(数据线)和SCL(时钟线)通常都连接了上拉电阻,以确保逻辑高电平的稳定性。上拉电阻的阻值通常在2.2kΩ至10kΩ之间,具体取决于总线的电容负载和通信距离。
I2C总线的最大线缆长度和传输容量受到一定限制。在标准模式下,最大线缆长度大约在1米左右,而在快速模式下,最大线缆长度约为0.3米。此外,线缆上的总线容量也会对传输速率产生影响。
I2C数据传输
IIC只有两根通信线,因此它数据传输是基于时钟信号的。各个设备在时钟信号的控制下进行数据的收发操作。下面是IIC总线的几个重要的时序:
起始信号:SCL在高电平的状态下,SDA的电平由高转低,表示开始一次通信。
void IIC_Start(void)
{
SDA_OUT();//设置SDA为输出模式
SDA(1);
SCL(1);
delay_us(5);
SDA(0);
delay_us(5);
SCL(0);
delay_us(5);
}
2
3
4
5
6
7
8
9
10
11
停止信号:SCL在高电平的状态下,SDA的电平由低转高,表示结束这次通信。主设备在发送停止信号后不能再向从设备发送任何数据,除非再次发送起始信号。
void IIC_Stop(void)
{
SDA_OUT();
SCL(0);
SDA(0);
SCL(1);
delay_us(5);
SDA(1);
delay_us(5);
}
2
3
4
5
6
7
8
9
10
数据传输:主设备和从设备进行数据的传输,可以是一个或多个字节的数据,发送和接收都是基于地址选择的。
//发送一个字节
void IIC_Send_Byte(uint8_t dat)
{
int i = 0;
SDA_OUT();
SCL(0);
for( i = 0; i < 8; i++ )
{
SDA( (dat & 0x80) >> 7 );
delay_us(1);
SCL(1);
delay_us(5);
SCL(0);
delay_us(5);
dat<<=1;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//接收一个字节
unsigned char IIC_Read_Byte(void)
{
unsigned char i,receive=0;
SDA_IN();//SDA设置为输入
for(i=0;i<8;i++ )
{
SCL(0);
delay_us(5);
SCL(1);
delay_us(5);
receive<<=1;
if( SDA_GET() )
{
receive |= 1;
}
}
SCL(0);
return receive;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
I2C还提供了一种称为“ACK/NACK”(应答/非应答)的确认机制。如果一个设备接收到数据,它将通过在SDA线上拉低电平来发送一个应答信号以通知发送方数据已被接收。相反,如果数据被损坏或未接收,接收设备将发送非应答信号。(在SDA上保持高电平)。
//主机发送应答信号
void IIC_Send_Ack(void)
{
SDA_OUT();
SCL(0);
SDA(1);
SDA(0);
SCL(1);
delay_us(5);
SCL(0);
SDA(1);
}
2
3
4
5
6
7
8
9
10
11
12
//主机发送非应答信号
void IIC_Send_Nack(void)
{
SDA_OUT();
SCL(0);
SDA(0);
SDA(1);
SCL(1);
delay_us(5);
SCL(0);
SDA(0);
}
2
3
4
5
6
7
8
9
10
11
12
//主机等待从机的应答信号
//函 数 返 回:1=无应答 0=有应答
uint8_t IIC_Wait_Ack(void)
{
char ack = 0;
unsigned char ack_flag = 10;
SDA_IN();
SCL(0);
delay_us(5);
SCL(1);
delay_us(5);
while( (SDA_GET()==1) && ( ack_flag ) )
{
ack_flag--;
delay_us(5);
}
if( ack_flag <= 0 )
{
IIC_Stop();
return 1;
}
else
{
SCL(0);
SDA_OUT();
}
return ack;
}
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
在IIC总线中,时钟线由主设备控制,每个数据位在时钟边沿更新,传输的最高速率取决于总线上最慢的设备。一般来讲,IIC总线的通信速率比较慢,通常在几百kbps的范围内。如果需要更高的传输速率,可以采用其他通信协议,如SPI协议、CAN协议等。
通信流程
I2C通信流程按照以下步骤进行:
- 主控向总线发送开始信号。
- 主控将要通信的设备地址和读写位(R/W)发送到总线上。
- 设备接收到地址后发送应答信号,主控接收到应答信号后发送数据或继续发送地址。
- 设备接收到数据后发送应答信号,主控接收到应答信号后可以继续发送数据或者停止通信。
- 主控向总线发送停止信号。
IIC基本参数
速率: I2C总线有标准模式(100 kbit/s)和快速模式(400 kbit/s)两种传输模式,还有更快的扩展模式和高速模式可供选择。
器件地址: 每个设备都有唯一的7位或10位地址,可以通过地址选择来确定与谁进行通信。
总线状态: I2C总线有五种状态,分别是空闲状态、起始信号、结束信号、响应信号、数据传输。
数据格式: I2C总线有两种数据格式,标准格式和快速格式。标准格式是8位数据字节加上1位ack/nack(应答/非应答)位,快速格式允许两个字节同时传输。
由于SCL和SDA线是双向的,它们也可能会由于外部原因(比如线路中的电容等)出现电平误差,而从而导致通信出错。因此,在IIC总线中,通常使用上拉电阻来保证信号线在空闲状态下的电平为高电平。
硬件I2C
TMS320F28P550的 I2C 支持主从模式,有7位和10位地址位可以设置,数据传输速率从 10kbps 到高达 400Kbps(快速模式)。 无论是主机或者从机,发送和接收都有独立的16个字节的接收与发送FIFO。
![]() |
---|
![]() | ![]() |
---|---|
软件I2C与硬件I2C
I2C协议可以通过软件实现或者硬件实现。这两种方式的区别在于实现的方法和所需的硬件资源。
软件I2C
软件I2C是指通过在程序中编写代码来实现I2C通信协议。它利用通用输入输出(GPIO)引脚来模拟I2C的数据线(SDA)和时钟线(SCL),通过软件控制引脚的电平变化来传输数据和生成时序信号。与硬件I2C相比,软件I2C的优势在于不需要特定的硬件支持,可以在任何支持GPIO功能的微控制器上实现。它利用了微控制器的通用IO引脚来实现I2C通信协议。
软件I2C的实现通过编程方式来模拟I2C的主机和从机设备。通过逐位地读取和写入GPIO引脚的状态,并根据I2C协议的时序要求进行相应的操作,实现数据的传输和通信。软件I2C的灵活性较高,可以根据应用需求进行定制和扩展。它可以处理多个从机设备,并支持多主机环境。因此,软件I2C广泛应用于资源受限的MCU系统,特别是那些需要与多个外部设备进行通信的应用。
尽管软件I2C的性能相对于硬件I2C较低,但在一些低速通信和简单通信需求的场景下,软件I2C是一种经济实用的解决方案。
硬件I2C
硬件I2C是指通过专门的硬件模块来处理I2C通信协议。大多数现代微控制器和一些外部设备已经集成了硬件I2C模块,这些硬件模块负责处理I2C通信的细节,包括生成正确的时序信号、自动处理信号冲突、数据传输和错误检测等。可以直接使用硬件引脚连接,无需编写时序的代码。
使用硬件I2C通常相对简单,开发者无需编写复杂的代码来处理通信协议的细节。硬件模块可以直接与外部设备连接,通过专用的引脚进行数据和时钟传输,从而实现高效且可靠的通信。
在选择软件I2C还是硬件I2C时,需要考虑应用需求和硬件资源。软件I2C适用于资源受限的系统,可以在任何支持GPIO的微控制器上实现,但相对性能较低。硬件I2C通常性能更好,但需要硬件支持,并且可能占据一些特定的引脚资源。
IIC优缺点
优点
双向传输: I2C总线支持双向传输,可以通过SDA线同时传输主设备和从设备之间的数据,节约了总线的资源。
系统集成: I2C总线可以快速集成到芯片中,减少系统实现的逻辑复杂性,提高了设计效率。
多设备共享: I2C总线可以通过地址传输实现多个设备与主控器的通信,使得多个设备可以共享总线,并直接交互。
高可靠性: I2C总线使用逻辑层次的代替电气信号来表示数据传输,具有更高的传输可靠性。
缺点
带宽不高: I2C总线的传输速度限制在400 kbps,相比较于SPI总线和CAN总线,带宽相对较低。
时序要求严格: I2C总线传输数据需要严格遵循时序要求,特别是在高速传输过程,时序容易受到干扰,造成通信失败。
最长电缆长度有限: 虽然I2C总线可以通过中继器扩展总线长度,但是由于信号线受到干扰,信号衰减和时序要求等问题,电缆最长长度一般限制在1~2米之间。
总之,I2C总线具有双向传输、系统集成、多设备共享等优点,但传输速度相对较低,时序要求严格且最长电缆长度有限等缺点。
I2C应用
I2C总线是应用最广泛的通信接口之一,以下是几个常见的应用例子:
- 温度计传感器:常见的温度计传感器,如SHT31、LM75等,都采用I2C接口,其通过I2C总线将温度数据传输到主控器进行处理。
- LED驱动器:LED驱动器,如PCA9685,常用于控制LED灯的亮度和颜色,其通过I2C总线和主控器通信,可实现快速和精确定时。
- OLED显示屏:OLED显示屏通过I2C总线与主控器通信,可实现高清晰度的图形显示,应用于像表盘、智能手表、电子血压计等低功耗设备之中。
- 触摸屏控制器:常见的15寸及以下触摸屏控制器,如STMPE610,都采用I2C接口,这些控制器可提供触摸检测和X/Y坐标的读取等功能。
- 电流电压采集:电流或电压采集芯片,如INA219,可通过I2C总线和主控器通信,实现精确高速的电流电压数据采集,应用于电源管理和工业自动化等领域。
上述只是常见的应用例子之一,I2C总线在许多领域都有广泛的应用,具有性价比高、易于移植等优点。
案例实验介绍
以LSM6DS3 6轴姿态传感器作为实验案例。通过软件I2C的方式与其进行通信,获取加速度与陀螺仪情况。
LSM6DS3 介绍
LSM6DS3 是意法半导体(STMicroelectronics)推出的一款高性能、低功耗的六轴惯性测量单元(IMU),集成了 3D 数字加速度计 和 3D 数字陀螺仪。
低功耗设计
- 组合正常工作模式下功耗仅 0.9 mA,高性能模式下为 1.25 mA(数据输出速率可达 1.6 kHz)。
- 支持智能休眠唤醒功能,根据活动状态自动切换模式以节能。
高精度与低噪声
- 加速度计量程:±2/±4/±8/±16 g
- 陀螺仪量程:±125/±245/±500/±1000/±2000 dps
- 超低噪声性能,适合高精度运动检测。
硬件功能集成
- 内置 8KB FIFO 缓冲区,支持批量处理传感器数据(包括外部传感器、计步器、温度等)。
- 支持 自由落体检测、6D方向识别、单击/双击感应、活动监测,并可生成中断信号。
- 兼容 传感器集线器(Sensor Hub) 功能,可连接外部磁力计等传感器。
通信接口
支持 I2C 和 SPI 数字接口。
模块硬件连接
LSM6DS3 与开发板的连接如下:
开发板(主机) | LSM6DS3(从机) | 说明 |
---|---|---|
GPIO230 | SDA | 数据线 |
GPIO227 | SCL | 时钟线 |
GND | GND | 电源线 |
VCC | 3V3 | 电源线 |
开发板上默认已经为大家贴好了该芯片,大家只需要了解连接的是哪一个引脚即可。
![]() |
---|
工程创建
打开CCS,创建一个新的基于 F28P55X 的工程。
![]() | ![]() |
---|---|
工程配置
配置工程选项,将我们后面写好的代码烧录到 FLASH
中,并且使用的烧录模式是 cJTAG(1149.7)2-pin
模式。
![]() | ![]() |
---|---|
软件 I2C 的配置
打开工程下的 .syscfg 文件。找到 GPIO 选项开始配置:
![]() | ![]() |
---|---|
完成I2C软件时序代码
我们在工程文件夹下新建一个文件夹:hardware
。
在hardware文件夹下再新建两个文件,分别是 bsp_i2c.c
和 bsp_i2c.h
。
更新头文件路径,新增我们保存 bsp_i2c.c 和 .h 的文件夹路径。
${PROJECT_ROOT}/hardware
以上文件的操作完成之后,在bsp_i2c.h
中编写以下代码:
#ifndef BSP_I2C_H
#define BSP_I2C_H
#include "board.h"
/************************ I2C ************************/
//设置SDA输出模式
#define SDA_OUT() { GPIO_setDirectionMode(I2C_SDA, GPIO_DIR_MODE_OUT);}
//设置SDA输入模式
#define SDA_IN() { GPIO_setDirectionMode(I2C_SDA, GPIO_DIR_MODE_IN); }
//获取SDA引脚的电平变化
#define SDA_GET() ( GPIO_readPin(I2C_SDA) )
//SDA与SCL输出
#define SDA(x) ( (x) ? (GPIO_writePin(I2C_SDA,1)) : (GPIO_writePin(I2C_SDA,0)) )
#define SCL(x) ( (x) ? (GPIO_writePin(I2C_SCL,1)) : (GPIO_writePin(I2C_SCL,0)) )
void i2c_start(void);
void i2c_stop(void);
void i2c_send_ack(unsigned char ack);
unsigned char i2c_wait_ack(void);
void i2c_send_byte(uint8_t dat);
unsigned char i2c_read_byte_ack(unsigned char ack);
#endif
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
bsp_i2c.h 的代码用到了我们在图形化生成工具配置的 I2C_SDA 和 I2C_SCL,将其适配更新到该 .h 中。
如果你在图形化生成工具中的配置跟 软件 I2C 的配置 小节的一样则不需要理会。
在bsp_i2c.c
中补充I2C时序的代码:
#include "bsp_i2c.h"
#define delay_us(X) DEVICE_DELAY_US(X) //I2C时序的微秒延时
#define I2C_DELAY_TIME 3 //I2C时序的延时时间
//发送开始信号
void i2c_start(void)
{
SDA_OUT();
SCL(0);
SDA(1);
SCL(1);
delay_us(I2C_DELAY_TIME);
SDA(0);
delay_us(I2C_DELAY_TIME);
SCL(0);
delay_us(I2C_DELAY_TIME);
}
//发送停止信号
void i2c_stop(void)
{
SDA_OUT();
SCL(0);
SDA(0);
SCL(1);
delay_us(I2C_DELAY_TIME);
SDA(1);
delay_us(I2C_DELAY_TIME);
}
/******************************************************************
* 函 数 说 明:主机发送应答或者非应答信号
* 函 数 形 参:0发送应答 1发送非应答
******************************************************************/
void i2c_send_ack(unsigned char ack)
{
SDA_OUT();
SCL(0);
SDA(0);
delay_us(I2C_DELAY_TIME);
if(!ack) SDA(0);
else SDA(1);
SCL(1);
delay_us(I2C_DELAY_TIME);
SCL(0);
SDA(1);
}
/******************************************************************
* 函 数 说 明:等待从机应答
* 函 数 返 回:0有应答 1超时无应答
******************************************************************/
unsigned char i2c_wait_ack(void)
{
char ack = 0;
char ack_flag = 50;
SDA_IN();
SDA(1);
while( (SDA_GET()==1) && ( ack_flag ) )
{
ack_flag--;
delay_us(I2C_DELAY_TIME);
}
if( ack_flag == 0 )
{
i2c_stop();
return 1;
}
else
{
SCL(1);
delay_us(I2C_DELAY_TIME);
SCL(0);
SDA_OUT();
}
return ack;
}
/******************************************************************
* 函 数 说 明:写入一个字节
* 函 数 形 参:dat要写入的数据
******************************************************************/
void i2c_send_byte(uint8_t dat)
{
int i = 0;
SDA_OUT();
SCL(0);//拉低时钟开始数据传输
for( i = 0; i < 8; i++ )
{
SDA( (dat & 0x80) >> 7 );
delay_us(2);
SCL(1);
delay_us(I2C_DELAY_TIME);
SCL(0);
delay_us(I2C_DELAY_TIME);
dat<<=1;
}
}
/******************************************************************
* 函 数 名: i2c_read_byte_ack
* 功能说明: CPU从I2C总线设备读取一个字节数据
* 形 参:ack : 1发送ACK应答,0发送nACK非应答
* 返 回 值: 读到的数据
******************************************************************/
unsigned char i2c_read_byte_ack(unsigned char ack)
{
unsigned char i,receive=0;
SDA_IN();//SDA设置为输入
for(i=0;i<8;i++ )
{
SCL(0);
delay_us(2);
SCL(1);
receive<<=1;
if(SDA_GET())
receive++;
delay_us(1);
}
if (!ack)
i2c_send_ack(1);//发送nACK
else
i2c_send_ack(0); //发送ACK
return receive;
}
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
每一个时序的实现原理请看 I2C数据传输 小节的内容。
LSM6DS3的开发
在hardware文件夹下再新建两个文件,分别是 hw_lsm6ds3.c
和 hw_lsm6ds3.h
。
I2C的设备地址
要进行I2C通信,需要知道其器件地址。并且了解如何与其进行通信。
LSM6DS3TR-C 的器件地址为 110101x
b 。其中芯片的 SDO/SA0 引脚可用于修改设备地址的最低有效位。
- 如果 SDO/SA0 引脚连接到电源电压,则最低有效位为“1”(地址为 110101
1
b); - 如果 SDO/SA0 引脚连接到地,则最低有效位为“0”(地址为 110101
0
b)。
此方案允许将两个不同的惯性模块连接到同一 I2C 总线上并进行寻址。
在我们的板子上,SA0引脚接的是地 GND,故设备地址为 1101010
,换算为16进制则为:0X6A
。
写入与读取数据
在 LSM6DS3TR-C 中,有4种读写方式:
写入一个字节时序
![]() |
---|
写入多个字节时序
![]() |
---|
读取一个字节时序
![]() |
---|
读取多个字节时序
![]() |
---|
说明
- Master 表示主机;
- Slave 表示从机;
- ST 表示发送开始信号;
- SAD+W 表示发送设备地址 + 写入指令;
- SAD+R 表示发送设备地址 + 读取指令;
- SAK 表示从机的应答信号
- MAK 表示主机发送应答信号
- NMAK 表示主机发送非应答信号
- SUB 表示寄存器地址
- DATA 表示数据
- SP 表示发送停止信号
寄存器表
寄存器表(点击展开)
寄存器名称 | 类型 | 地址(Hex) | 默认值(Hex) | 功能描述 |
---|---|---|---|---|
RESERVED | - | 00 | - | 保留 |
FUNC_CFG_ACCESS | r/w | 01 | 00 | 嵌入式功能配置寄存器 |
RESERVED | - | 02-03 | - | 保留 |
SENSOR_SYNC_TIME_FRAME | r/w | 04 | 00 | 传感器同步时间帧配置 |
SENSOR_SYNC_RES_RATIO | r/w | 05 | 00 | 传感器同步分辨率比例配置 |
FIFO_CTRL1 | r/w | 06 | 00 | FIFO控制寄存器1 |
FIFO_CTRL2 | r/w | 07 | 00 | FIFO控制寄存器2 |
FIFO_CTRL3 | r/w | 08 | 00 | FIFO控制寄存器3 |
FIFO_CTRL4 | r/w | 09 | 00 | FIFO控制寄存器4 |
FIFO_CTRL5 | r/w | 0A | 00 | FIFO控制寄存器5 |
DRDY_PULSE_CFG_G | r/w | 0B | 00 | 陀螺仪数据就绪脉冲配置 |
RESERVED | - | 0C | - | 保留 |
INT1_CTRL | r/w | 0D | 00 | INT1中断控制寄存器 |
INT2_CTRL | r/w | 0E | 00 | INT2中断控制寄存器 |
WHO_AM_I | r | 0F | 6A | 设备ID寄存器(固定值0x6A) |
CTRL1_XL | r/w | 10 | 00 | 加速度计控制寄存器1 |
CTRL2_G | r/w | 11 | 00 | 陀螺仪控制寄存器2 |
CTRL3_C | r/w | 12 | 04 | 主控制寄存器3 |
CTRL4_C | r/w | 13 | 00 | 主控制寄存器4 |
CTRL5_C | r/w | 14 | 00 | 主控制寄存器5 |
CTRL6_C | r/w | 15 | 00 | 主控制寄存器6 |
CTRL7_G | r/w | 16 | 00 | 陀螺仪控制寄存器7 |
CTRL8_XL | r/w | 17 | 00 | 加速度计控制寄存器8 |
CTRL9_XL | r/w | 18 | 00 | 加速度计控制寄存器9 |
CTRL10_C | r/w | 19 | 00 | 主控制寄存器10 |
MASTER_CONFIG | r/w | 1A | 00 | I²C主配置寄存器 |
WAKE_UP_SRC | r | 1B | - | 唤醒中断源状态寄存器 |
TAP_SRC | r | 1C | - | 敲击中断源状态寄存器 |
D6D_SRC | r | 1D | - | 6D方向检测中断源状态寄存器 |
STATUS_REG | r | 1E | - | 用户接口状态数据寄存器 |
RESERVED | - | 1F | - | 保留 |
OUT_TEMP_L | r | 20 | - | 温度输出数据寄存器(低字节) |
OUT_TEMP_H | r | 21 | - | 温度输出数据寄存器(高字节) |
OUTX_L_G | r | 22 | - | 陀螺仪X轴输出数据(低字节) |
OUTX_H_G | r | 23 | - | 陀螺仪X轴输出数据(高字节) |
OUTY_L_G | r | 24 | - | 陀螺仪Y轴输出数据(低字节) |
OUTY_H_G | r | 25 | - | 陀螺仪Y轴输出数据(高字节) |
OUTZ_L_G | r | 26 | - | 陀螺仪Z轴输出数据(低字节) |
OUTZ_H_G | r | 27 | - | 陀螺仪Z轴输出数据(高字节) |
OUTX_L_XL | r | 28 | - | 加速度计X轴输出数据(低字节) |
OUTX_H_XL | r | 29 | - | 加速度计X轴输出数据(高字节) |
OUTY_L_XL | r | 2A | - | 加速度计Y轴输出数据(低字节) |
OUTY_H_XL | r | 2B | - | 加速度计Y轴输出数据(高字节) |
OUTZ_L_XL | r | 2C | - | 加速度计Z轴输出数据(低字节) |
OUTZ_H_XL | r | 2D | - | 加速度计Z轴输出数据(高字节) |
SENSORHUB1_REG | r | 2E | - | 传感器集线器输出寄存器1 |
SENSORHUB2_REG | r | 2F | - | 传感器集线器输出寄存器2 |
SENSORHUB3_REG | r | 30 | - | 传感器集线器输出寄存器3 |
SENSORHUB4_REG | r | 31 | - | 传感器集线器输出寄存器4 |
SENSORHUB5_REG | r | 32 | - | 传感器集线器输出寄存器5 |
SENSORHUB6_REG | r | 33 | - | 传感器集线器输出寄存器6 |
SENSORHUB7_REG | r | 34 | - | 传感器集线器输出寄存器7 |
SENSORHUB8_REG | r | 35 | - | 传感器集线器输出寄存器8 |
SENSORHUB9_REG | r | 36 | - | 传感器集线器输出寄存器9 |
SENSORHUB10_REG | r | 37 | - | 传感器集线器输出寄存器10 |
SENSORHUB11_REG | r | 38 | - | 传感器集线器输出寄存器11 |
SENSORHUB12_REG | r | 39 | - | 传感器集线器输出寄存器12 |
FIFO_STATUS1 | r | 3A | - | FIFO状态寄存器1(存储采样数低8位) |
FIFO_STATUS2 | r | 3B | - | FIFO状态寄存器2(溢出/满标志) |
FIFO_STATUS3 | r | 3C | - | FIFO状态寄存器3(保留) |
FIFO_STATUS4 | r | 3D | - | FIFO状态寄存器4(水印/批处理标志) |
FIFO_DATA_OUT_L | r | 3E | - | FIFO数据输出寄存器(低字节) |
FIFO_DATA_OUT_H | r | 3F | - | FIFO数据输出寄存器(高字节) |
TIMESTAMP0_REG | r | 40 | - | 时间戳输出寄存器(字节0) |
TIMESTAMP1_REG | r | 41 | - | 时间戳输出寄存器(字节1) |
TIMESTAMP2_REG | r/w | 42 | - | 时间戳输出寄存器(字节2,可读写) |
RESERVED | - | 43-48 | - | 保留 |
STEP_TIMESTAMP_L | r | 49 | - | 计步器时间戳寄存器(低字节) |
STEP_TIMESTAMP_H | r | 4A | - | 计步器时间戳寄存器(高字节) |
STEP_COUNTER_L | r | 4B | - | 计步器输出寄存器(低字节) |
STEP_COUNTER_H | r | 4C | - | 计步器输出寄存器(高字节) |
SENSORHUB13_REG | r | 4D | - | 传感器集线器输出寄存器13 |
SENSORHUB14_REG | r | 4E | - | 传感器集线器输出寄存器14 |
SENSORHUB15_REG | r | 4F | - | 传感器集线器输出寄存器15 |
SENSORHUB16_REG | r | 50 | - | 传感器集线器输出寄存器16 |
SENSORHUB17_REG | r | 51 | - | 传感器集线器输出寄存器17 |
SENSORHUB18_REG | r | 52 | - | 传感器集线器输出寄存器18 |
FUNC_SRC1 | r | 53 | - | 功能中断源寄存器1 |
FUNC_SRC2 | r | 54 | - | 功能中断源寄存器2 |
WRIST_TILT_IA | r | 55 | - | 手腕倾斜中断状态寄存器 |
RESERVED | - | 56-57 | - | 保留 |
TAP_CFG | r/w | 58 | 00 | 敲击中断配置寄存器 |
TAP_THS_6D | r/w | 59 | 00 | 敲击阈值和6D方向检测配置 |
INT_DUR2 | r/w | 5A | 00 | 中断持续时间配置 |
WAKE_UP_THS | r/w | 5B | 00 | 唤醒阈值配置 |
WAKE_UP_DUR | r/w | 5C | 00 | 唤醒持续时间配置 |
FREE_FALL | r/w | 5D | 00 | 自由落体检测配置 |
MD1_CFG | r/w | 5E | 00 | 主设备中断1配置 |
MD2_CFG | r/w | 5F | 00 | 主设备中断2配置 |
MASTER_CMD_CODE | r/w | 60 | 00 | 主设备命令代码寄存器 |
SENS_SYNC_SPI_ERROR_CODE | r/w | 61 | 00 | SPI同步错误代码寄存器 |
RESERVED | - | 62-65 | - | 保留 |
OUT_MAG_RAW_X_L | r | 66 | - | 外部磁力计X轴原始数据(低字节) |
OUT_MAG_RAW_X_H | r | 67 | - | 外部磁力计X轴原始数据(高字节) |
OUT_MAG_RAW_Y_L | r | 68 | - | 外部磁力计Y轴原始数据(低字节) |
OUT_MAG_RAW_Y_H | r | 69 | - | 外部磁力计Y轴原始数据(高字节) |
OUT_MAG_RAW_Z_L | r | 6A | - | 外部磁力计Z轴原始数据(低字节) |
OUT_MAG_RAW_Z_H | r | 6B | - | 外部磁力计Z轴原始数据(高字节) |
RESERVED | - | 6C-72 | - | 保留 |
X_OFS_USR | r/w | 73 | 00 | 加速度计X轴用户偏移校准 |
Y_OFS_USR | r/w | 74 | 00 | 加速度计Y轴用户偏移校准 |
Z_OFS_USR | r/w | 75 | 00 | 加速度计Z轴用户偏移校准 |
RESERVED | - | 76-7F | - | 保留 |
类型说明
- r/w:可读写
- r:只读
- -:保留或无操作
关键说明
FIFO状态寄存器:
FIFO_STATUS1 和 FIFO_STATUS2 用于监控FIFO的存储状态(如采样数、溢出标志等)。
DIFF_FIFO 字段表示当前FIFO中存储的数据量(单位:字,每字7字节)。
时间戳与计步器:
时间戳寄存器(TIMESTAMPx_REG)记录传感器事件的精确时间。
计步器相关寄存器(STEP_COUNTER_L/H)输出步数和时间戳。
中断配置:
TAP_CFG 和 TAP_THS_6D 用于配置敲击检测的灵敏度和方向判断。
WAKE_UP_THS 和 FREE_FALL 与低功耗唤醒和自由落体检测相关。
传感器集线器:
SENSORHUBx_REG 用于扩展外部传感器(如磁力计)的数据采集。
在全部的寄存器中,我们关心的是:
寄存器名称 | 地址(HEX) | 功能 | 为什么关心它? |
---|---|---|---|
WHO_AM_I | 0F | 读取LSM6DS3TRC器件ID | 判断我们的设备是否正常,如果读不到或者数据不对说明有问题 |
CTRL3_C | 12 | LSM6DS3TRC重置、块数据更新等寄存器 | 初始化使用 |
CTRL1_XL | 10 | 设置加速度计的数据采样率 | 配置采样率 |
CTRL2_G | 11 | 设置陀螺仪数据速率 | 配置速率 |
CTRL8_XL | 17 | 设置加速度计输出数据率 | 配置速率 |
STATUS_REG | 1E | 从状态寄存器获取数据状态 | 读取状态,在采样还是转换还是空闲 |
OUTX_L_XL | 28 | 读取加速度计数据 | 获取加速度的原始数据 |
OUTX_L_G | 22 | 读取陀螺仪数据 | 获取陀螺仪的原始数据 |
初始化流程
- 检测设备是否存在
![]() |
---|
- 设备重置
![]() |
---|
设置加速度采样率
设置陀螺仪采样率
设置加速度满量程
设置陀螺仪满量程
设置加速度模拟链带宽
软件校准(可选)
上电手动采集多次数据,然后取平均值。
单位换算
加速度单位
LSM6DS3 的加速度读取出来的默认单位是已 mg/LSB 为单位,我们如果要转换为单位 g,则:
加速度数据 = 轴原始数据 * Typ / 1000
例如,±2g,则 加速度数据= 轴原始数据 * 0.061 (切换单位为mg)/ 1000(切换单位为g)
陀螺仪单位
LSM6DS3 的陀螺仪读取出来的默认单位是已 mdps/LSB 为单位,我们如果要转换为单位 dps,则:
角速度数据 = (pi / 180) * 轴原始数据 * Typ / 1000
例如,±2000,则 加速度数据= (pi / 180) * 轴原始数据 * 70 (切换单位为mbps)/ 1000(切换单位为bps)
代码编写
基于以上的数据手册中的关键信息,将其转换为代码。在 hw_lsm6ds3.h 中编写以下代码:
#ifndef HW_LSM6DS3_H
#define HW_LSM6DS3_H
#include "board.h"
/************************ 陀螺仪 ************************/
#define LSM6DS3TRC_I2CADDR 0x6A//SA0接GND,如果接的是VCC,则地址是0x6B
#define LSM6DS3TRC_WHO_AM_I 0x0F //Who am I
#define LSM6DS3TRC_CTRL3_C 0x12
//加速度计控制寄存器
#define LSM6DS3TRC_CTRL1_XL 0x10
//线性加速输出数据速率
#define LSM6DS3TRC_ACC_RATE_0 0x00
#define LSM6DS3TRC_ACC_RATE_1HZ6 0xB0
#define LSM6DS3TRC_ACC_RATE_12HZ5 0x10
#define LSM6DS3TRC_ACC_RATE_26HZ 0x20
#define LSM6DS3TRC_ACC_RATE_52HZ 0x30
#define LSM6DS3TRC_ACC_RATE_104HZ 0x40
#define LSM6DS3TRC_ACC_RATE_208HZ 0x50
#define LSM6DS3TRC_ACC_RATE_416HZ 0x60
#define LSM6DS3TRC_ACC_RATE_833HZ 0x70
#define LSM6DS3TRC_ACC_RATE_1660HZ 0x80
#define LSM6DS3TRC_ACC_RATE_3330HZ 0x90
#define LSM6DS3TRC_ACC_RATE_6660HZ 0xA0
//陀螺仪控制寄存器
#define LSM6DS3TRC_CTRL2_G 0x11
//线性陀螺仪输出数据速率
#define LSM6DS3TRC_GYR_RATE_0 0x00
#define LSM6DS3TRC_GYR_RATE_1HZ6 0xB0
#define LSM6DS3TRC_GYR_RATE_12HZ5 0x10
#define LSM6DS3TRC_GYR_RATE_26HZ 0x20
#define LSM6DS3TRC_GYR_RATE_52HZ 0x30
#define LSM6DS3TRC_GYR_RATE_104HZ 0x40
#define LSM6DS3TRC_GYR_RATE_208HZ 0x50
#define LSM6DS3TRC_GYR_RATE_416HZ 0x60
#define LSM6DS3TRC_GYR_RATE_833HZ 0x70
#define LSM6DS3TRC_GYR_RATE_1660HZ 0x80
#define LSM6DS3TRC_GYR_RATE_3330HZ 0x90
#define LSM6DS3TRC_GYR_RATE_6660HZ 0xA0
//加速度计全量程
#define LSM6DS3TRC_ACC_FSXL_2G 0x00
#define LSM6DS3TRC_ACC_FSXL_16G 0x04
#define LSM6DS3TRC_ACC_FSXL_4G 0x08
#define LSM6DS3TRC_ACC_FSXL_8G 0x0C
//陀螺仪全量程
#define LSM6DS3TRC_GYR_FSG_245 0x00
#define LSM6DS3TRC_GYR_FSG_500 0x04
#define LSM6DS3TRC_GYR_FSG_1000 0x08
#define LSM6DS3TRC_GYR_FSG_2000 0x0C
#define LSM6DS3TRC_CTRL1_XL 0x10
//加速度计的模拟链带宽
#define LSM6DS3TRC_ACC_BW0XL_1500HZ 0x00
#define LSM6DS3TRC_ACC_BW0XL_400HZ 0x01
#define LSM6DS3TRC_CTRL8_XL 0x17
//加速度计带宽选择
//低通滤波器
#define LSM6DS3TRC_ACC_LOW_PASS_ODR_50 0x88
#define LSM6DS3TRC_ACC_LOW_PASS_ODR_100 0xA8
#define LSM6DS3TRC_ACC_LOW_PASS_ODR_9 0xC8
#define LSM6DS3TRC_ACC_LOW_PASS_ODR_400 0xE8
//高通滤波器
#define LSM6DS3TRC_ACC_HIGH_PASS_ODR_50 0x04
#define LSM6DS3TRC_ACC_HIGH_PASS_ODR_100 0x24
#define LSM6DS3TRC_ACC_HIGH_PASS_ODR_9 0x44
#define LSM6DS3TRC_ACC_HIGH_PASS_ODR_400 0x64
//用户界面的状态数据寄存器
#define LSM6DS3TRC_STATUS_REG 0x1E
#define LSM6DS3TRC_STATUS_GYROSCOPE 0x02
#define LSM6DS3TRC_STATUS_ACCELEROMETER 0x01
//加速度计输出接口XYZ
#define LSM6DS3TRC_OUTX_L_XL 0x28
#define LSM6DS3TRC_OUTX_H_XL 0x29
#define LSM6DS3TRC_OUTY_L_XL 0x2A
#define LSM6DS3TRC_OUTY_H_XL 0x2B
#define LSM6DS3TRC_OUTZ_L_XL 0x2C
#define LSM6DS3TRC_OUTZ_H_XL 0x2D
//陀螺仪输出接口XYZ
#define LSM6DS3TRC_OUTX_L_G 0x22
#define LSM6DS3TRC_OUTX_H_G 0x23
#define LSM6DS3TRC_OUTY_L_G 0x24
#define LSM6DS3TRC_OUTY_H_G 0x25
#define LSM6DS3TRC_OUTZ_L_G 0x26
#define LSM6DS3TRC_OUTZ_H_G 0x27
//四元素
typedef struct {
float x;
float y;
float z;
float w;
} Quaternion;
//欧拉角
typedef struct {
float x;
float y;
float z;
} Angle;
extern Angle angle;
extern Quaternion quaternion;
uint8_t lsm6ds3_init(void);
void lsm6ds3_angle_return_zero(void);
void lsm6ds3_get_angle(Angle* angle);
void lsm6ds3_getAngle(Angle* angle);
void float_to_string(float num, char *str);
#endif
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
在 hw_lsm6ds3.c 中编写以下代码:
#include "hw_lsm6ds3.h"
#include "bsp_i2c.h"
#include <stdio.h>
#include <string.h>
#include <math.h>
Angle angle;
Quaternion quaternion;
//积分时间20ms
//多少秒采集一次陀螺仪时间就填入多少秒
const static float dt = 0.02f;
//陀螺仪yaw角软件校准值
static float gyro_zero_z = 0.0f;
//实现自己的毫秒延时
static void delay_syms(long X)
{
while(X--)
{
DEVICE_DELAY_US(1000);
}
}
/*******************************************************************************
* 函数名:LSM6DS3TRC_ReadOneByte
* 描述 :从LSM6DS3TRC指定地址处开始读取一个字节数据
* 输入 :reg_addr地址
* 输出 :读取的数据dat
*******************************************************************************/
uint8_t lsm6ds3_read_one_byte(uint8_t reg_addr)
{
uint8_t dat = 0;
i2c_start();//发送起始信号
i2c_send_byte((LSM6DS3TRC_I2CADDR<<1) | 0x00);//从设备地址
delay_syms(1);
if(i2c_wait_ack()) // 检测设备的ACK应答
{
i2c_stop();//产生一个停止条件
}
i2c_send_byte(reg_addr);//寄存器地址
delay_syms(1);
if(i2c_wait_ack()) // 检测设备的ACK应答
{
i2c_stop();//产生一个停止条件
}
i2c_start();//发送重复起始信号,准备读取数据
i2c_send_byte((LSM6DS3TRC_I2CADDR<<1) | 0x01);//从设备地址(读取模式)
delay_syms(1);
if(i2c_wait_ack()) // 检测设备的ACK应答
{
i2c_stop();//产生一个停止条件
}
dat = i2c_read_byte_ack(0);
i2c_stop();//发送停止信号
return dat;
}
/*******************************************************************************
* 函数名:lsm6ds3_ReadCommand
* 描述 :对LSM6DS3TRC读取数据
* 输入 :uint8_t reg_addr, uint8_t *rev_data, uint8_t length
* 输出 :void
*******************************************************************************/
void lsm6ds3_read_command(uint8_t reg_addr, uint8_t *rev_data, uint8_t length)
{
while(length)
{
*rev_data++ = lsm6ds3_read_one_byte(reg_addr++);
length--;
}
}
/*******************************************************************************
* 函数名:lsm6ds3_WriteCommand
* 描述 :往LSM6DS3TRC写入命令
* 输入 :uint8_t reg_addr, uint8_t *send_data, uint16_t length
* 输出 :void
*******************************************************************************/
void lsm6ds3_write_command(uint8_t reg_addr, uint8_t *send_data, uint16_t length)
{
i2c_start();
delay_syms(10);
i2c_send_byte((LSM6DS3TRC_I2CADDR<<1) | 0x00);//发送设备地址
if(i2c_wait_ack()) // 检测设备的ACK应答
{
i2c_stop();//产生一个停止条件
}
delay_syms(10);
i2c_send_byte(reg_addr);//发送寄存器地址
delay_syms(10);
if(i2c_wait_ack()) // 检测设备的ACK应答
{
i2c_stop();//产生一个停止条件
}
delay_syms(10);
i2c_send_byte(*send_data);//发送数据
delay_syms(10);
if(i2c_wait_ack()) // 检测设备的ACK应答
{
i2c_stop();//产生一个停止条件
}
delay_syms(10);
i2c_stop();//产生一个停止条件
}
/*******************************************************************************
* 函数名:IIC_CheckDevice
* 描述 :检测I2C总线设备,CPU向发送设备地址,然后读取设备应答来判断该设备是否存在
* 输入 :_Address:设备的I2C总线地址
*******************************************************************************/
uint8_t i2c_check_device(uint8_t _Address)
{
uint8_t ucAck;
i2c_start(); // 发送启动信号
i2c_send_byte(_Address );
ucAck = i2c_wait_ack(); // 检测设备的ACK应答
i2c_stop(); // 发送停止信号
return ucAck;
}
/*******************************************************************************
* 函数名:lsm6ds3_CheckOk
* 描述 :判断LSM6DS3TRC是否正常
* 输出 : 1 表示正常, 0 表示不正常
*******************************************************************************/
uint8_t lsm6ds3_check_ok(void)
{
if(i2c_check_device( LSM6DS3TRC_I2CADDR ) == 1)
{
//printf("Device exist\r\n");
return 1;
}
else
{
// 失败后,切记发送I2C总线停止信号
//printf("Device not exist\r\n");
i2c_stop();
return 0;
}
}
/*******************************************************************************
* 函数名:lsm6ds3_GetChipID
* 描述 :读取LSM6DS3TRC器件ID
* 输出 :返回值true表示0x6a,返回false表示不是0x6a
*******************************************************************************/
uint8_t lsm6ds3_get_Chip_id(void)
{
uint8_t buf = 0;
lsm6ds3_read_command(LSM6DS3TRC_WHO_AM_I, &buf, 1);//Who I am ID
//printf("buf 0x%02X\r\n",buf);
if (buf == 0x6a)
{
//printf("ID ok\r\n");
return 1;
}
else
{
//printf("ID error\r\n");
return 0;
}
}
/*******************************************************************************
* 函数名:lsm6ds3_Reset
* 描述 :LSM6DS3TRC重启和重置寄存器
*******************************************************************************/
void lsm6ds3_reset(void)
{
uint8_t buf[1] = {0};
//reboot modules
buf[0] = 0x80;
lsm6ds3_write_command(LSM6DS3TRC_CTRL3_C, buf, 1);//BOOT->1
delay_syms(15);
//reset register
lsm6ds3_read_command(LSM6DS3TRC_CTRL3_C, buf, 1);//读取SW_RESET状态
buf[0] |= 0x01;
lsm6ds3_write_command(LSM6DS3TRC_CTRL3_C, buf, 1);//将CTRL3_C寄存器的SW_RESET位设为1
while (buf[0] & 0x01)
lsm6ds3_read_command(LSM6DS3TRC_CTRL3_C, buf, 1);//等到CTRL3_C寄存器的SW_RESET位返回0
}
/*******************************************************************************
* 函数名:lsm6ds3_Set_BDU
* 描述 :LSM6DS3TRC设置块数据更新
* 输入 :uint8_t flag=1启动 0禁用
*******************************************************************************/
void lsm6ds3_set_BDU(uint8_t flag)
{
uint8_t buf[1] = {0};
lsm6ds3_read_command(LSM6DS3TRC_CTRL3_C, buf, 1);
if (flag == 1)
{
buf[0] |= 0x40;//启用BDU
lsm6ds3_write_command(LSM6DS3TRC_CTRL3_C, buf, 1);
}
else
{
buf[0] &= 0xbf;//禁用BDU
lsm6ds3_write_command(LSM6DS3TRC_CTRL3_C, buf, 1);
}
lsm6ds3_read_command(LSM6DS3TRC_CTRL3_C, buf, 1);
}
/*******************************************************************************
* 函数名:lsm6ds3_Set_Accelerometer_Rate
* 描述 :LSM6DS3TRC设置加速度计的数据采样率
* 输入 :uint8_t rate
*******************************************************************************/
void lsm6ds3_set_accelerometer_rate(uint8_t rate)
{
uint8_t buf[1] = {0};
lsm6ds3_read_command(LSM6DS3TRC_CTRL1_XL, buf, 1);
buf[0] |= rate;//设置加速度计的数据采样率
lsm6ds3_write_command(LSM6DS3TRC_CTRL1_XL, buf, 1);
}
/*******************************************************************************
* 函数名:lsm6ds3_Set_Gyroscope_Rate
* 描述 :LSM6DS3TRC设置陀螺仪数据速率
* 输入 :uint8_t rate
*******************************************************************************/
void lsm6ds3_set_gyroscope_rate(uint8_t rate)
{
uint8_t buf[1] = {0};
lsm6ds3_read_command(LSM6DS3TRC_CTRL2_G, buf, 1);
buf[0] |= rate;//设置陀螺仪数据速率
lsm6ds3_write_command(LSM6DS3TRC_CTRL2_G, buf, 1);
}
/*******************************************************************************
* 函数名:lsm6ds3_Set_Accelerometer_Fullscale
* 描述 :LSM6DS3TRC加速度计满量程选择
* 输入 :uint8_t value
*******************************************************************************/
void lsm6ds3_set_accelerometer_fullscale(uint8_t value)
{
uint8_t buf[1] = {0};
lsm6ds3_read_command(LSM6DS3TRC_CTRL1_XL, buf, 1);
buf[0] |= value;//设置加速度计的满量程
lsm6ds3_write_command(LSM6DS3TRC_CTRL1_XL, buf, 1);
}
/*******************************************************************************
* 函数名:lsm6ds3_Set_Gyroscope_Fullscale
* 描述 :LSM6DS3TRC陀螺仪满量程选择
* 输入 :uint8_t value
*******************************************************************************/
void lsm6ds3_set_gyroscope_fullscale(uint8_t value)
{
uint8_t buf[1] = {0};
lsm6ds3_read_command(LSM6DS3TRC_CTRL2_G, buf, 1);
buf[0] |= value;//设置陀螺仪的满量程
lsm6ds3_write_command(LSM6DS3TRC_CTRL2_G, buf, 1);
}
/*******************************************************************************
* 函数名:LSM6DS3TRC_Set_Accelerometer_Bandwidth
* 描述 :LSM6DS3TRC设置加速度计模拟链带宽
* 输入 :uint8_t BW0XL, uint8_t ODR
* 备注 :BW0XL模拟链带宽, ODR输出数据率
*******************************************************************************/
void lsm6ds3_set_accelerometer_bandwidth(uint8_t BW0XL, uint8_t ODR)
{
uint8_t buf[1] = {0};
lsm6ds3_read_command(LSM6DS3TRC_CTRL1_XL, buf, 1);
buf[0] |= BW0XL;
lsm6ds3_write_command(LSM6DS3TRC_CTRL1_XL, buf, 1);
lsm6ds3_read_command(LSM6DS3TRC_CTRL8_XL, buf, 1);
buf[0] |= ODR;
lsm6ds3_write_command(LSM6DS3TRC_CTRL8_XL, buf, 1);
}
/*******************************************************************************
* 函数名:LSM6DS3TRC_Get_Status
* 描述 :从LSM6DS3TRC状态寄存器获取数据状态
*******************************************************************************/
uint8_t lsm6ds3_get_status(void)
{
uint8_t buf[1] = {0};
lsm6ds3_read_command(LSM6DS3TRC_STATUS_REG, buf, 1);
return buf[0];
}
/*******************************************************************************
* 函数名:LSM6DS3TRC_Get_Acceleration
* 描述 :从LSM6DS3TRC读取加速度计数据
* 输入 :uint8_t fsxl, float *acc_float
* 备注 :转换为浮点数的加速度值
*******************************************************************************/
void lsm6ds3_get_acceleration(uint8_t fsxl, float *acc_float)
{
uint8_t buf[6];
int16_t acc[3];
lsm6ds3_read_command(LSM6DS3TRC_OUTX_L_XL, buf, 6);//获取加速度计原始数据
acc[0] = buf[1] << 8 | buf[0];
acc[1] = buf[3] << 8 | buf[2];
acc[2] = buf[5] << 8 | buf[4];
switch (fsxl)//根据不同量程来选择输出的数据的转换系数
{
case LSM6DS3TRC_ACC_FSXL_2G:
acc_float[0] = ((float)acc[0] * 0.061f);
acc_float[1] = ((float)acc[1] * 0.061f);
acc_float[2] = ((float)acc[2] * 0.061f);
break;
case LSM6DS3TRC_ACC_FSXL_16G:
acc_float[0] = ((float)acc[0] * 0.488f);
acc_float[1] = ((float)acc[1] * 0.488f);
acc_float[2] = ((float)acc[2] * 0.488f);
break;
case LSM6DS3TRC_ACC_FSXL_4G:
acc_float[0] = ((float)acc[0] * 0.122f);
acc_float[1] = ((float)acc[1] * 0.122f);
acc_float[2] = ((float)acc[2] * 0.122f);
break;
case LSM6DS3TRC_ACC_FSXL_8G:
acc_float[0] = ((float)acc[0] * 0.244f);
acc_float[1] = ((float)acc[1] * 0.244f);
acc_float[2] = ((float)acc[2] * 0.244f);
break;
}
}
/*******************************************************************************
* 函数名:LSM6DS3TRC_Get_Gyroscope
* 描述 :从LSM6DS3TRC读取陀螺仪数据
* 输入 :uint8_t fsg, float *gry_float
* 备注 :转换为浮点数的角速度值
*******************************************************************************/
void lsm6ds3_get_gyroscope(uint8_t fsg, float *gry_float)
{
uint8_t buf[6];
int16_t gry[3];
lsm6ds3_read_command(LSM6DS3TRC_OUTX_L_G, buf, 6);//获取陀螺仪原始数据
gry[0] = buf[1] << 8 | buf[0];
gry[1] = buf[3] << 8 | buf[2];
gry[2] = buf[5] << 8 | buf[4];
switch (fsg)//根据不同量程来选择输出的数据的转换系数
{
case LSM6DS3TRC_GYR_FSG_245:
gry_float[0] = ((float)gry[0] * 8.750f);
gry_float[1] = ((float)gry[1] * 8.750f);
gry_float[2] = ((float)gry[2] * 8.750f);
break;
case LSM6DS3TRC_GYR_FSG_500:
gry_float[0] = ((float)gry[0] * 17.50f);
gry_float[1] = ((float)gry[1] * 17.50f);
gry_float[2] = ((float)gry[2] * 17.50f);
break;
case LSM6DS3TRC_GYR_FSG_1000:
gry_float[0] = ((float)gry[0] * 35.00f);
gry_float[1] = ((float)gry[1] * 35.00f);
gry_float[2] = ((float)gry[2] * 35.00f);
break;
case LSM6DS3TRC_GYR_FSG_2000:
gry_float[0] = ((float)gry[0] * 70.00f);
gry_float[1] = ((float)gry[1] * 70.00f);
gry_float[2] = ((float)gry[2] * 70.00f);
break;
}
}
/*******************************************************************************
* 函数名:lsm6ds3_soft_calibrate_z0
* 描述 :传感器校准
* 备注 :上电前用,减小Z零点漂移
*******************************************************************************/
static void lsm6ds3_soft_calibrate_z0(void)
{
uint16_t calibration_samples = 500;//校准采样数
float gz_sum = 0.0f;
int16_t GyroZ;
uint8_t buf[2]={0};
uint16_t i;
uint8_t status = 0;
for (i = 0; i < calibration_samples; i++)
{
///////////根据Z轴的变换规律进行修正/////////
status = lsm6ds3_get_status();
if( status & LSM6DS3TRC_STATUS_GYROSCOPE )
{
// 读取Z轴数据
lsm6ds3_read_command(LSM6DS3TRC_OUTZ_L_G, buf, 2);
GyroZ = buf[1] << 8 | buf[0];
gz_sum += (float)GyroZ;
}
delay_syms(20);//要和dt同步
}
gyro_zero_z = gz_sum / calibration_samples;
}
// 卡尔曼滤波器结构体
typedef struct {
float q; // 过程噪声协方差
float r; // 测量噪声协方差
float x; // 状态估计值
float p; // 估计误差协方差
float k; // 卡尔曼增益
} MPU6050_KalmanFilter;
//定义三个欧拉角的滤波体
MPU6050_KalmanFilter kf_roll, kf_pitch, kf_yaw;
/*******************************************************************************
* 函数名:LSM6DS3TRC_Get_Gyroscope
* 描述 :卡尔曼滤波更新函数
* 输入 :kf 开发板滤波的结构体变量地址 measurement滤波前的参数
* 返回 :滤波后的参数
*******************************************************************************/
static float KalmanFilter_Update(MPU6050_KalmanFilter *kf, float measurement)
{
// 预测步骤
kf->p = kf->p + kf->q;
// 计算卡尔曼增益
kf->k = kf->p / (kf->p + kf->r);
// 更新估计值
kf->x = kf->x + kf->k * (measurement - kf->x);
// 更新估计误差协方差
kf->p = (1 - kf->k) * kf->p;
return kf->x;
}
//卡尔曼滤波参数初始化
static void KalmanFilter_init(void)
{
//roll的初始化
kf_roll.q = 0.02f;
kf_roll.r = 0.1f;
kf_roll.x = 0;
kf_roll.p = 1;
//Pitch的初始化
kf_pitch.q = 0.02f;
kf_pitch.r = 0.1f;
kf_pitch.x = 0;
kf_pitch.p = 1;
//yaw的初始化
kf_yaw.q = 0.02f;
kf_yaw.r = 0.1f;
kf_yaw.x = 0;
kf_yaw.p = 1;
}
float acc[3] = {0,0,0};
float gyr[3] = {0,0,0};
float Kp=130.0f;
float Ki=0.005f;
float halfT=0.001f;
float q0=1, q1 = 0, q2 = 0, q3 = 0;
float exInt = 0, eyInt = 0, ezInt = 0;
//如yaw值偏移逐渐加大,则加大该值
//如yaw值偏移逐渐减小,则减小该值
float z_offset = 0.1034f; //yaw轴手动偏移
//获取原始数据并转换为欧拉角
//参数angle 为传入的角度结构体
void lsm6ds3_get_angle(Angle* angle)
{
int i = 0;
uint8_t status = 0;
float norm;
float vx,vy,vz;
float ex,ey,ez;
//获取传感器状态
status = lsm6ds3_get_status();
if( (status&LSM6DS3TRC_STATUS_ACCELEROMETER) && (status&LSM6DS3TRC_STATUS_GYROSCOPE) )
{
//获取加速度原始数据
lsm6ds3_get_acceleration(LSM6DS3TRC_ACC_FSXL_2G, acc);
//单位换算
for( i = 0; i < 3; i++ )
{
acc[i] = acc[i] / 1000.0f;
}
//获取陀螺仪原始数据
lsm6ds3_get_gyroscope(LSM6DS3TRC_GYR_FSG_2000, gyr);
//单位换算
for( i = 0; i < 3; i++ )
{
//Z轴应用软件校准值
if( i == 2 )
{
gyr[i] = gyr[i] - (int16_t)gyro_zero_z;
}
gyr[i] = gyr[i] /1000.0f;
}
//测量正常化,三维向量变为单位向量
norm = sqrt(acc[0]*acc[0] + acc[1]*acc[1] + acc[2]*acc[2]);
acc[0] = acc[0] / norm;//单位化
acc[1] = acc[1] / norm;
acc[2] = acc[2] / norm;
//估计方向的重力
vx = 2* (q1*q3 - q0*q2);
vy = 2* (q0*q1 + q2*q3);
vz = q0*q0 - q1*q1 - q2*q2 + q3*q3;
ex = (acc[1]*vz - acc[2]*vy);
ey = (acc[2]*vx - acc[0]*vz);
ez = (acc[0]*vy - acc[1]*vx);
//积分误差比例积分增益
exInt = exInt + ex*Ki;
eyInt = eyInt + ey*Ki;
ezInt = ezInt + ez*Ki;
//调整后的陀螺仪测量
gyr[0] = gyr[0] + Kp*ex + exInt;
gyr[1] = gyr[1] + Kp*ey + eyInt;
gyr[2] = gyr[2] + Kp*ez + ezInt;
//整合四元数率和正常化
q0 = q0 + (-q1*gyr[0] - q2*gyr[1]- q3*gyr[2])*halfT;
q1 = q1 + (q0*gyr[0] + q2*gyr[2] - q3*gyr[1])*halfT;
q2 = q2 + (q0*gyr[1] - q1*gyr[2] + q3*gyr[0])*halfT;
q3 = q3 + (q0*gyr[2] + q1*gyr[1] - q2*gyr[0])*halfT;
//正常化四元数
norm= sqrt(q0*q0+ q1*q1+ q2*q2+ q3*q3);
//四元素
q0 = q0 / norm;//w
q1 = q1 / norm;//x
q2 = q2 / norm;//y
q3 = q3 / norm;//z
angle->x = KalmanFilter_Update(&kf_roll, asin(2 * q2 * q3 + 2 * q0 * q1 ) * 57.3);
angle->y =KalmanFilter_Update(&kf_pitch, atan2(-2 * q1 * q3 + 2 * q0 * q2, q0*q0-q1*q1-q2*q2+q3*q3)*57.3);
gyr[2] = KalmanFilter_Update(&kf_yaw, gyr[2] * dt);
//如参数小于预期请加大3.0f
//如参数大于预期请减小3.0f
angle->z += gyr[2] * 3.0f + z_offset;
}
}
/*******************************************************************************
* 函数名:lsm6ds3_angle_return_zero
* 描述 :角度归零初始化
*******************************************************************************/
void lsm6ds3_angle_return_zero(void)
{
angle.x = 0;
angle.y = 0;
angle.z = 0;
lsm6ds3_reset();
}
/*******************************************************************************
* 函数名:传感器初始化
* 描述 :lsm6ds3_init
*******************************************************************************/
uint8_t lsm6ds3_init(void)
{
//检测设备是否存在
if( lsm6ds3_check_ok() == 0 ) return 1;
//设备重启
lsm6ds3_reset();
//在读取MSB和LSB之前不更新输出寄存器
lsm6ds3_set_BDU(1);
//设置加速度计的数据采样率 1/52=19.2ms
lsm6ds3_set_accelerometer_rate(LSM6DS3TRC_ACC_RATE_52HZ);
//设置陀螺仪的数据采样率 1/52=19.2ms
lsm6ds3_set_gyroscope_rate(LSM6DS3TRC_GYR_RATE_52HZ);
//设置加速度计满量程选择
lsm6ds3_set_accelerometer_fullscale(LSM6DS3TRC_ACC_FSXL_2G);
//设置陀螺仪全量程选择
lsm6ds3_set_gyroscope_fullscale(LSM6DS3TRC_GYR_FSG_2000);
//设置加速度计模拟链带宽
lsm6ds3_set_accelerometer_bandwidth(LSM6DS3TRC_ACC_BW0XL_400HZ, LSM6DS3TRC_ACC_LOW_PASS_ODR_100);
delay_syms(100);
//软件校准,减少yaw的零点漂移
//lsm6ds3_soft_calibrate_z0();
//卡尔曼滤波初始化
KalmanFilter_init();
return 0;
}
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
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
其他配置
因为我们要看到陀螺仪的实际效果,可以通过串口调试工具 Vofa+ 查看波形数据与3D角度数据。所以我们还需要配置一个串口。
![]() | ![]() |
---|---|
在hardware文件夹下再新建两个文件,分别是 hw_uart.c
和 hw_uart.h
。
往 hw_uart.c 写入以下代码:
#include "hw_uart.h"
#include <string.h>
#include <stdio.h>
//发送单个字节
void uart0_sendChar(char ch)
{
while( SCI_isTransmitterBusy(mySCI0_BASE) != false);
SCI_writeCharNonBlocking(mySCI0_BASE, ch);
}
//发送字符串
void uart0_sendString(char* str)
{
while(*str!=0 && str != 0)
{
uart0_sendChar(*str++);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
往 hw_uart.h 写入以下代码:
#ifndef HW_UART_H
#define HW_UART_H
#include "driverlib.h"
#include "device.h"
#include "board.h"
void uart0_sendChar(char ch);
void uart0_sendString(char* str);
#endif
2
3
4
5
6
7
8
9
10
11
案例验证
更新主函数代码
更新工程的 empty_driverlib_main.c 文件为以下代码:
#include "driverlib.h"
#include "device.h"
#include "board.h"
#include "c2000ware_libraries.h"
#include "hw_uart.h"
#include "hw_lsm6ds3.h"
#include "stdio.h"
#include <math.h>
#include <stdlib.h>
#include <string.h>
//浮点转字符串
//只转换小数点后两位
void float_to_str(float num, char* output)
{
int num_int = 0;
int point1;
int point2;
num_int = num * 100;
point1 = num_int/10%10;
point2 = num_int%10;
num_int = num_int/100;
if( point2 < 0 ) point2=-point2;
if( point1 < 0 ) point1=-point1;
sprintf(output, "%d.%d%d",num_int, point1, point2);
}
void main(void)
{
char x[20]={0},y[20]={0},z[20]={0},bufs[50]={0};
Device_init();
Device_initGPIO();
Interrupt_initModule();
Interrupt_initVectorTable();
Board_init();
C2000Ware_libraries_init();
EINT;
ERTM;
// 陀螺仪初始化
lsm6ds3_init();
while(1)
{
//获取欧拉角
lsm6ds3_get_angle(&angle);
//将欧拉角的浮点型数据转换字符串
float_to_str(angle.x,x);
float_to_str(angle.y,y);
float_to_str(angle.z,z);
//格式化字符串
sprintf(bufs,"xyz: %s,%s,%s\r\n",x,y,z);
//串口发送字符串
uart0_sendString(bufs);
DEVICE_DELAY_US(15000);
}
}
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
下载器连接
XDS110下载器 | 开发板 |
---|---|
SWD | SWD/TMS |
CLK | CLK/TCK |
TXD | RX |
RXD | TX |
GND | GND |
5V | 5V |
代码烧录
GIF 动图
案例现象
使用vofa+ 显示串口的数据,同时调用vofa+的3D控件显示3D效果,波形控件显示3个轴的效果。
![]() |
---|