1. 在树莓派中打开I2C
树莓派的内核支持 I2C 驱动,但是需要先打开。打开方法如下:
在树莓派屏幕的左上角选择Preferences,选择Raspberry Pi Configuration 然后选择Interfaces,Enable I2C ,完成之后,需要重启树莓派,就可以使用I2C了。

连线部分
| 传感器引脚 | 功能 | 树莓派引脚 |
|---|---|---|
| SDA | 数据线 | SDA.1 |
| SCL | 时钟线 | SCL.1 |
| VCC | 3.3V | 3.3V |
| GND | 接地 | GND |

2. QT中使用WiringPi控制I2C
2.1 添加头文件
WiringPi 库里面也实现了I2C 相关的接口,在需要用的地方,引入下面两个头文件
#include "wiringPi.h"#include "wiringPiI2C.h"
2.2 声明fd
在.h 文件中,声明一个fd,类型是int。
fd其实代表的是文件描述符号( file descriptor ),在linux系统里面,硬件设备在操作系统里面都以fd来标识和索引,可以通过fd来对一个设备进行操作,一般就是一个数字。I2C在树莓派的操作系统中,也是作为一个fd来进行识别。因此需要在初始化的时候声明一个int类型保存fd。
class MainWindow : public QMainWindow{Q_OBJECTpublic:explicit MainWindow(QWidget *parent = nullptr);~MainWindow();int fd // i2c设备的文件描述符private:Ui::MainWindow *ui;};
2.3 初始化
在初始化函数内, 初始化wiringPi和wiringPiI2C这两个模块。其中wiringPiI2CSetup要传入一个参数,是传感器的识别地址。ADXL345的默认地址是0x53 ,所以加了一个宏定义,下面是初始化的代码
#define I2CADDR 0x53MainWindow::MainWindow(QWidget *parent) :QMainWindow(parent),ui(new Ui::MainWindow){ui->setupUi(this);wiringPiSetup();fd = wiringPiI2CSetup(I2CADDR);}
2.3 写数据
i2c写入数据的api如下:
| API | 解释 |
|---|---|
| int** wiringPiI2CWrite(int fd, int **data) | 向设备连续写入数据,部分设备支持 |
| int **wiringPiI2CWriteReg8(int fd, int reg, int **data) ; | 向寄存器写8位数据,reg是地址,data是数据 |
| int **wiringPiI2CWriteReg16(int fd, int reg, int **data) ; | 向寄存器写16**位数据,reg是地址**,data是数据 |
2.4 读数据
| API | 解释 |
|---|---|
| int** wiringPiI2CRead(int **fd) | 向设备连续写入数据,部分设备支持 |
| int **wiringPiI2CReadReg8(int fd, int **reg) ; | 向寄存器读8位数据,reg是地址,返回值是数据 |
| int **wiringPiI2CReadReg16(int fd, int **reg) ; | 向寄存器读16**位数据,reg是地址**,返回值是数据 |
3. ADXL345详细介绍
3.1 ADXL345数据手册
以下是ADXL345的中文数据手册。数据手册比较复杂,下面的其他点会有对数据手册的简单总结。
3.2 ADXL345的使用流程
3.3 ADXL345的关键寄存器
ADXL345传感器有几个关键参数,需要配置对应**地址**的寄存器,来调整传感器的参数。
接下来会对几个关键从寄存器进行介绍:
3.3.1 设备ID寄存器:0x00
这个寄存器是只读寄存器,保存了设备id,每个ADXL345传感器都有相同的**设备ID,是0XE5**,因此在程序初始化的时候,经常会先读取一下设备id,看一下传感器状态是否正常。
void MainWindow::on_detectButton_clicked(){int id = wiringPiI2CReadReg8(fd, 0x00); // 0x00,是设备id的寄存器if(id == 0xe5){// 检测到0xe5,传感器正常工作ui->idEdit->setText("0x" + QString::number(id, 16));}else {// 没有检测到id,传感器工作不正常ui->idEdit->setText("none");}}
3.3.2 速度/功率寄存器:0x2C
这个寄存器可以配置传感器的工作状态是低功耗模式还是正常模式,以及配置读取的速度
| D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
|---|---|---|---|---|---|---|---|
| 0 | 0 | 0 | LOW_POWER | 速率 |
- D4 位如果是0,为正常模式;D4如果是1,是低功耗模式
- D3到D0这4位数的部分是指定读取的速度,下面是不同速率的对照表

常见默认的配置,是将速度设置为50Hz,模式为正常模式,可以写入0x0a
| D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
|---|---|---|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 |
3.3.3 数据格式寄存器:0x31
这个寄存器是用来配置ADXL345的输出数据格式。这个寄存器主要配置以下两个方面的内容:
- 通信格式(4线SPI or 3线SPI)
- 测量分辨率(测量精度)
- 测量范围(量程)
寄存器的数据格式如下:
| D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
|---|---|---|---|---|---|---|---|
| 自测力 | SPI | 中断电平 | 0 | 全分辨率 | 对齐 | 范围 |
- D6:1为3线spi,0为4线spi
- D3:1为全分辨率模式,代表数据精度为13位,精度最高;0为10位模式,精度较低
- D1 D0:设置量程,对应值如下,其中g是重力加速度,1g=9.8m/s** | 设置 | | 范围 | | —- | —- | —- | | D1 | D0 | -2g ~ 2g | | 0 | 0 | -4g ~ 4g | | 0 | 1 | -8g ~ 8g | | 1 | 0 | -16g ~ 16g |
常见默认的配置是,全分辨率,量程最大到16g:
| D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
|---|---|---|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 |
3.3.4 数据寄存器0x32 ~ 0x37
这六组寄存器存储了加速度的原始值,下面先介绍寄存器的组成
| 0x32 | DATAX0 |
|---|---|
| 0x33 | DATAX1 |
| 0x34 | DATAY0 |
| 0x35 | DATAY1 |
| 0x36 | DATAZ0 |
| 0x37 | DATAZ1 |
组合原始数据
0分量是数据的低8位,1分量是数据的高8位。最终组合的数字以补码的形式组成。
即X轴的数据格式为
| DATAX1(8位) | DATAX0(8位) |
|---|---|
形式是补码,即可以直接将数据转换成成带符号的int16类型。即可以得到.
组合方式是,将高8位的数据左移动8位,例如11111111 变成 1111111100000000,然后在将低8位的数据与高八位的数据进行 | 操作,例如:
1111111100000000 | 10010001 = 1111111110010001
原始输出数据 = (int)((DATAX1 << 8) | DATAX1)
缩放分量
从数据手册可以得到,原始输出数据,除以对应的比例系数,才能得到真实的加速度值。
当选择全分辨率时候,比例系数都为 256LSB/g (LSB就代表原始输出值)
因此将原始输出值除以256,就可以得到加速度(单位为g)
加速度 = (float)((int)((DATAX1 << 8) | DATAX0)) / 256
默认数据中,在平放时,X轴和Y轴都没有加速度,Z轴有一个加速度,因此g轴的数据趋近为1,其他两个轴的数据趋近为0:
3.3.5 偏移寄存器:0X1E 0X1F 0X20
这三个寄存器是用来进行校准。由于加速度传感器随着使用可能存在偏差。ADXL345 提供了校准寄存器用来教程
校准流程如下
在数据手册中写到:
在无调头或单点校准方案中,器件调整为:一个轴通常为 z轴在1 g重力场,其余轴,通常是x和y轴在0 g场。然后取一 系列样本的平均值,测量其输出。系统设计人员可选择平 均样本数,但建议100 Hz或更高数据率的起点为0.1 sec。这 相当于100 Hz的数据速率10个样本。对于低于100 Hz的数据 速率,建议平均至少有10个样本。x和y轴上0 g测量和Z轴的 1 g测量的值分别存储为X0g、Y0g和Z+1g 。
使用偏移寄存器(寄存器0x1E、寄存器0x1F和寄存器 0x20),ADXL345可以自动补偿偏移输出。这些寄存器包含 8位二进制补码值,为自动添加到所有测得的加速度值, 其结果随后置入到DATA寄存器。因为置于偏移寄存器的 值为附加值,负值置于寄存器,消除正偏移,相反则消除 负偏移。该寄存器比例因子为15.6 mg/LSB,与选定的g范围 无关。
例子如下:
- step1: 读一组原始数取平均
在平放状态下,取了一组原始数平均值
- X=0.001g=10mg
- Y=0.0017g=17mg
- Z=1.021g=1021mg
- step2:计算差值
X,Y两个坐标轴-去0,差值就是它本身
Z轴需要减去1g(1000mg),因为有重力加速度
- deltaX = 10mg
- deltaY = 17mg
- deltaZ = 21mg
- step3:缩放比例
当全分辨率的时候,比例因子为 1/256 = 3.9mg/LSB,而偏移寄存器的固定比例因子为15.6mg/LSB
其关系是4倍,因此将所有的delta除以4,并取负数(因为要补偿这些差的值)
- offsetX = -(10/4)mg = -2.5mg
- offsetY = -(17/4)mg = -4.25mg
- offserZ = -(21/4)mg = -5.25mg
- step4:写回
将所有数值写回对应的偏移寄存器当中
以上过程就完成了所有的校准。在下一章中会有对应实现的代码。
3.3.6 电源状态传感器 0x2d
该寄存器指定是否进行电源自动休眠,也可以强制指定为待机模式或工作模式
| D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
|---|---|---|---|---|---|---|---|
| 0 | 0 | 链接位 | 自动休眠位 | 测量位 | 休眠位 | 唤醒位 |
使传感器正常工作需要将休眠位置0,常用值是0x08
4.代码实现
4.1 头文件以及宏定义
引入wiringPi 相关的头文件,并用宏定义的方式,定义常用寄存器的地址。并定义一个结构体,统一管理三个方向的加速度。
#include "wiringPi.h"#include "wiringPiI2C.h"#define REG_DEVID 0x00 // 设备id寄存器#define REG_BW_RATE 0x2c // 速度_功率寄存器#define REG_DATA_FORMAT 0x31 //数据格式寄存器#define REG_DATAX0 0x32 // x0#define REG_DATAX1 0x33 // x1#define REG_DATAY0 0x34 // y0#define REG_DATAY1 0x35 // y1#define REG_DATAZ0 0x36 // z0#define REG_DATAZ1 0x37 // z1#define REG_POWER_CTL 0x2d //工作模式寄存器#define REG_OFSX 0x1e // x偏移寄存器#define REG_OFSY 0x1f // y偏移寄存器#define REG_OFSZ 0x20 // z偏移寄存器struct acc_dat{double x;double y;double z;};
4.2 传感器初始化代码
传感器初始化主要需要配置3.3 提到的几个关键寄存器
void MainWindow::adxl345_init(){wiringPiI2CWriteReg8(fd, REG_DATA_FORMAT, 0x0b);//设置数据格式为全分辨率,范围最大wiringPiI2CWriteReg8(fd, REG_BW_RATE, 0x0a);//设置速度为50hzwiringPiI2CWriteReg8(fd, REG_OFSX, 0x00);//清空x偏移设置wiringPiI2CWriteReg8(fd, REG_OFSY, 0x00);//清空y偏移设置wiringPiI2CWriteReg8(fd, REG_OFSZ, 0x00);//清空z偏移设置wiringPiI2CWriteReg8(fd, REG_POWER_CTL, 0x08); //配置为唤醒模式}
4.3 检测设备id代码
void MainWindow::on_detectButton_clicked(){int id = wiringPiI2CReadReg8(fd, 0x00);if(id == 0xe5){ui->idEdit->setText("0x" + QString::number(id, 16)); // 检测到id,并显示在文本框}else {ui->idEdit->setText("none");}}
4.4 校准代码
校准部分,按照3.3.5中的步骤,编写下列代码:
void MainWindow::on_modifyButton_clicked(){char x0, y0, z0, x1, y1, z1;//qint16 x = 0;qint16 y = 0;qint16 z = 0;for(int i = 0;i < 10;i++){// 读取分量x0 = (char)wiringPiI2CReadReg8(fd, REG_DATAX0);x1 = (char)wiringPiI2CReadReg8(fd, REG_DATAX1);y0 = (char)wiringPiI2CReadReg8(fd, REG_DATAY0);y1 = (char)wiringPiI2CReadReg8(fd, REG_DATAY1);z0 = (char)wiringPiI2CReadReg8(fd, REG_DATAZ0);z1 = (char)wiringPiI2CReadReg8(fd, REG_DATAZ1);// 合成原始数据并累加x += (qint16)((quint16)(x1 << 8) | x0); // 高8位左移后,与第八位相与,结合成最终结果y += (qint16)((quint16)(y1 << 8) | y0);z += (qint16)((quint16)(z1-1 << 8) | z0);delay(100);}//求平均值x = x/10;y = y/10;z = z/10;// 求差并缩放qint16 xoffset = -(x/4);qint16 yoffset = -(y/4);qint16 zoffset = -((z-1)/4);// 写回寄存器wiringPiI2CWriteReg8(fd, REG_OFSX, xoffset);wiringPiI2CWriteReg8(fd, REG_OFSY, yoffset);wiringPiI2CWriteReg8(fd, REG_OFSZ, zoffset);}
4.5 读取数据代码
读取数据整体流程和校准类似,因为全分辨率情况下,比例为256:1(在3.3.4中有详细解释),故代码如下:
struct acc_dat MainWindow::adxl345_read_xyz(){char x0, y0, z0, x1, y1, z1;//struct acc_dat acc_xyz;x0 = (char)wiringPiI2CReadReg8(fd, REG_DATAX0);x1 = (char)wiringPiI2CReadReg8(fd, REG_DATAX1);y0 = (char)wiringPiI2CReadReg8(fd, REG_DATAY0);y1 = (char)wiringPiI2CReadReg8(fd, REG_DATAY1);z0 = (char)wiringPiI2CReadReg8(fd, REG_DATAZ0);z1 = (char)wiringPiI2CReadReg8(fd, REG_DATAZ1);acc_xyz.x = ((quint16)(x1 << 8) | x0) /(double)256; //缩放acc_xyz.y = ((quint16)(y1 << 8) | y0) /(double)256 ; //缩放acc_xyz.z = ((quint16)(z1 << 8) | z0) /(double)256; //缩放return acc_xyz;}
5.三轴传感器的姿态倾角计算
ADXL345 可以测3轴的加速度,但没有检测地磁的功能。只能通过借助Z轴的重力加速度在其他两轴上的投影,获取俯仰角与翻滚角(Pitch and Roll),不能检测纯水平方向的角度变化(Yaw)
数学计算公式可见这3篇博客:
https://wiki.dfrobot.com/How_to_Use_a_Three-Axis_Accelerometer_for_Tilt_Sensing#Introduction
https://www.cnblogs.com/lifexy/archive/2019/04/13/10699502.html
https://blog.csdn.net/qcopter/article/details/51848544
通过c语言定义的atan2,可以直接得到对应两个角的结果,需要引入头文件:
#include <math.h>//calculate the Roll&Pitchvoid RP_calculate(){double x_Buff = float(x);double y_Buff = float(y);double z_Buff = float(z);roll = atan2(y_Buff , z_Buff) * 57.3;pitch = atan2((- x_Buff) , sqrt(y_Buff * y_Buff + z_Buff * z_Buff)) * 57.3;}
acc_xyz.roll = atan2(acc_xyz.x , acc_xyz.y) * 57.3;acc_xyz.pitch = atan2((- acc_xyz.x) , sqrt(acc_xyz.y * acc_xyz.y + acc_xyz.z * acc_xyz.z)) * 57.3;
6.附:完整测试代码
mainwindow.h
#ifndef MAINWINDOW_H#define MAINWINDOW_H#include <QMainWindow>#include <QTimer>#include "wiringPi.h"#include "wiringPiI2C.h"#define REG_DEVID 0x00 // 设备id寄存器#define REG_BW_RATE 0x2c // 速度_功率寄存器#define REG_DATA_FORMAT 0x31 //数据格式寄存器#define REG_DATAX0 0x32 // x0#define REG_DATAX1 0x33 // x1#define REG_DATAY0 0x34 // y0#define REG_DATAY1 0x35 // y1#define REG_DATAZ0 0x36 // z0#define REG_DATAZ1 0x37 // z1#define REG_OFSX 0x1e // x偏移寄存器#define REG_OFSY 0x1f // y偏移寄存器#define REG_OFSZ 0x20 // z偏移寄存器typedef struct acc_dat{double x;double y;double z;double pitch;double roll;}info;namespace Ui {class MainWindow;}class MainWindow : public QMainWindow{Q_OBJECTpublic:explicit MainWindow(QWidget *parent = nullptr);~MainWindow();int fd;bool sta;QTimer *timer;void adxl345_init();info adxl345_read_xyz();private slots:void on_modifyButton_clicked();void on_startButton_clicked();void on_detectButton_clicked();void onReadData();private:Ui::MainWindow *ui;};#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"#include "ui_mainwindow.h"#include <QMessageBox>#include <QDebug>#include <math.h>#define I2CADDR 0x53MainWindow::MainWindow(QWidget *parent) :QMainWindow(parent),ui(new Ui::MainWindow){ui->setupUi(this);timer = new QTimer;connect(timer,SIGNAL(timeout()), this, SLOT(onReadData()));sta = false;wiringPiSetup();fd = wiringPiI2CSetup(I2CADDR);adxl345_init();}MainWindow::~MainWindow(){delete ui;}void MainWindow::adxl345_init(){wiringPiI2CWriteReg8(fd, REG_DATA_FORMAT, 0x0b);//wiringPiI2CWriteReg8(fd, REG_BW_RATE, 0x0a);//设置正常模式(不低功耗)wiringPiI2CWriteReg8(fd, REG_OFSX, 0x00);//清空x偏移设置wiringPiI2CWriteReg8(fd, REG_OFSY, 0x00);//清空y偏移设置wiringPiI2CWriteReg8(fd, REG_OFSZ, 0x00);//清空z偏移设置wiringPiI2CWriteReg8(fd, REG_POWER_CTL, 0x08); //配置为唤醒模式}void MainWindow::onReadData(){auto res = adxl345_read_xyz();ui->textBrowser->append("x:" + QString("%1").arg(res.x));ui->textBrowser->append("y:" + QString("%1").arg(res.y));ui->textBrowser->append("z:" + QString("%1").arg(res.z));ui->textBrowser->append("pitch:" + QString("%1").arg(res.pitch));ui->textBrowser->append("roll:" + QString("%1").arg(res.roll));ui->textBrowser->append("---------------------------");}info MainWindow::adxl345_read_xyz(){char x0, y0, z0, x1, y1, z1;//info acc_xyz;x0 = (char)wiringPiI2CReadReg8(fd, REG_DATAX0);x1 = (char)wiringPiI2CReadReg8(fd, REG_DATAX1);y0 = (char)wiringPiI2CReadReg8(fd, REG_DATAY0);y1 = (char)wiringPiI2CReadReg8(fd, REG_DATAY1);z0 = (char)wiringPiI2CReadReg8(fd, REG_DATAZ0);z1 = (char)wiringPiI2CReadReg8(fd, REG_DATAZ1);acc_xyz.x = (qint16)((quint16)(x1 << 8) | x0) /(double)256;acc_xyz.y = (qint16)((quint16)(y1 << 8) | y0) /(double)256 ;acc_xyz.z = (qint16)((quint16)(z1 << 8) | z0) /(double)256;acc_xyz.roll = atan2(acc_xyz.x , acc_xyz.y) * 57.3;acc_xyz.pitch = atan2((- acc_xyz.x) , sqrt(acc_xyz.y * acc_xyz.y + acc_xyz.z * acc_xyz.z)) * 57.3;return acc_xyz;}void MainWindow::on_modifyButton_clicked(){char x0, y0, z0, x1, y1, z1;//qint16 x = 0;qint16 y = 0;qint16 z = 0;for(int i = 0;i < 10;i++){x0 = (char)wiringPiI2CReadReg8(fd, REG_DATAX0);x1 = (char)wiringPiI2CReadReg8(fd, REG_DATAX1);y0 = (char)wiringPiI2CReadReg8(fd, REG_DATAY0);y1 = (char)wiringPiI2CReadReg8(fd, REG_DATAY1);z0 = (char)wiringPiI2CReadReg8(fd, REG_DATAZ0);z1 = (char)wiringPiI2CReadReg8(fd, REG_DATAZ1);x += (qint16)((quint16)(x1 << 8) | x0);y += (qint16)((quint16)(y1 << 8) | y0);z += (qint16)((quint16)(z1-1 << 8) | z0);delay(100);}x = x/10;y = y/10;z = z/10;qint16 xoffset = -(x/4);qint16 yoffset = -(y/4);qint16 zoffset = -((z-1)/4);wiringPiI2CWriteReg8(fd, REG_OFSX, xoffset);wiringPiI2CWriteReg8(fd, REG_OFSY, yoffset);wiringPiI2CWriteReg8(fd, REG_OFSZ, zoffset);QMessageBox::information(this,tr("Finish"),QStringLiteral("modify finish!"),QMessageBox::Ok);}void MainWindow::on_startButton_clicked(){timer->start(500);}void MainWindow::on_detectButton_clicked(){int id = wiringPiI2CReadReg8(fd, 0x00);if(id == 0xe5){ui->idEdit->setText("0x" + QString::number(id, 16));}else {ui->idEdit->setText("none");}}
