一、简介
ADXL345 是一款 ADI 公司推出的基于 iMEMS 技术的超低功耗3轴加速度计,分辨率高(13位),测量范围达 ±16g。数字输出数据为 16 位二进制补码格式,可通过
SPI(3线或4线)
或I2C
数字接口访问。ADXL345 非常适合移动设备应用。它可以在倾斜检测应用中测量静态重力加速度,还可以测量运动或冲击导致的动态加速度。其高分辨率(3.9mg/LSB),能够测量不到 1.0° 的倾斜角度变化。该器件提供多种特殊检测功能。活动和非活动检测功能通过比较任意轴上的加速度与用户设置的阈值来检测有无运动发生。敲击检测功能可以检测任意方向的单振和双振动作。自由落体检测功能可以检测器件是否正在掉落。这些功能可以独立映射到两个中断输出引脚中的一个。正在申请专利的集成式存储器管理系统采用一个32级先进先出(FIFO)缓冲器,可用于存储数据,从而将主机处理器负荷降至最低,并降低整体系统功耗。低功耗模式支持基于运动的智能电源管理,从而以极低的
功耗进行阈值感测和运动加速度测量。
官方数据手册:https://pan.baidu.com/s/1-UCkno6kQFugSy8NEhNhHg?pwd=tp7p 提取码:tp7p
二、硬件连接
功能口 | 引脚 |
---|---|
MISO | 17 |
MOSI | 18 |
SCLK | 20 |
CS | 19 |
三、添加SPI驱动
查看 ESP32学习笔记(19)——SPI(主机)接口使用
四、SPI通信注意事项
4.1 SPI模式
一般内置 SPI 功能的单片机上,都有两个寄存器配置位 CPOL 和 CPHA。
CPOL
就是决定 SCLK 这个时钟信号线,在没有数据传输的时候的电平状态。
CPHA
就是决定数据位传输是从第一个时钟 (SCLK) 边沿开始,还是第二个从二个时钟 (SCLK) 边沿开始。
因此 CPOL 和 CPHA 合体就形成了SPI四种模式。
-
模式0(CPOL=0,CPHA=0)
CPOL = 0:空闲时是低电平,第1个跳变沿是上升沿,第2个跳变沿是下降沿
CPHA = 0:数据在第1个跳变沿(上升沿)采样
-
模式1(CPOL=0,CPHA=1)
CPOL = 0:空闲时是低电平,第1个跳变沿是上升沿,第2个跳变沿是下降沿
CPHA = 1:数据在第2个跳变沿(下降沿)采样
-
模式2(CPOL=1,CPHA=0)
CPOL = 1:空闲时是高电平,第1个跳变沿是下降沿,第2个跳变沿是上升沿
CPHA = 0:数据在第1个跳变沿(下降沿)采样
-
模式3(CPOL=1,CPHA=1)
CPOL = 1:空闲时是高电平,第1个跳变沿是下降沿,第2个跳变沿是上升沿
CPHA = 1:数据在第2个跳变沿(上升沿)采样
查看ADXL345中文数据手册第15页,得知ADXL345 SPI模式应为 模式3(CPOL=1,CPHA=1)
。
4.2 SPI读写以及多字节读取指令的区别
查看ADXL345中文数据手册第14、15页,得知读的时候,地址的最高位为1;写的时候,地址的最高位为0;在进行多字节读取的时候,次高位为1,这样才可以多字节写和读
。
4.3 SPI时钟要求
查看ADXL345中文数据手册第14页,得知在设置读取速率的时候,要和SPI的时钟匹配起来,否则可能读到错误的数据,比如设置1600HZ,SPI时钟要大于2MHZ。SPI读取数据时钟最大5MHZ
。
4.4 器件ID寄存器
查看ADXL345中文数据手册第23页,得知寄存器0x00保存0xE5的固定器件ID代码,可用于校验SPI是否通信成功
。
五、移植文件
5.1 board_spi.c
#include <math.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "board_spi.h"
#include "board_adxl345.h"
static void delayMs(uint32_t time);
void ADXL345_Init(void)
{
ADXL345_SPI_Init();
while(ADXL345_GetDeviceId() != 0xE5)
{
printf("ADXL345 Init Fail!\n");
delayMs(1000);
}
ADXL345_WriteReg(DATA_FORMAT, 0x0B); // 13位全分辨率,输出数据右对齐,16g量程
ADXL345_WriteReg(BW_RATE, 0x0A); // 数据输出速度为100Hz
ADXL345_WriteReg(POWER_CTL, 0x08); // 无链接,测量模式
ADXL345_WriteReg(INT_ENABLE, 0x80); // DATA_READY中断
printf("ADXL345 Init Success!\n");
}
uint8_t ADXL345_GetDeviceId(void)
{
uint8_t ret = ADXL345_ReadReg(DEVICE_ID);
return ret;
}
void ADXL345_ReadXYZ(short *x, short *y, short *z)
{
uint8_t x0,y0,z0;
uint8_t x1,y1,z1;
x0 = ADXL345_ReadReg(DATA_X0);
y0 = ADXL345_ReadReg(DATA_Y0);
z0 = ADXL345_ReadReg(DATA_Z0);
x1 = ADXL345_ReadReg(DATA_X1);
y1 = ADXL345_ReadReg(DATA_Y1);
z1 = ADXL345_ReadReg(DATA_Z1);
// printf("--------------x0:%d,y0:%d,z0:%d\tx1:%d,y1:%d,z1:%d", x0,y0,z0,x1,y1,z1);
*x = (short)(((uint16_t)x1 << 8) + x0); // DATA_X1为高位有效字节
*y = (short)(((uint16_t)y1 << 8) + y0); // DATA_Y1为高位有效字节
*z = (short)(((uint16_t)z1 << 8) + z0); // DATA_Z1为高位有效字节
// printf("--------------x%d,y%d,z%d",*x,*y,*z);
}
/*读取ADXL345的数据并做滤波处理,读times次再取平均值*/
void ADXL345_ReadAverage(short *x, short *y, short *z, uint8_t times)
{
if(0 == times)
{
return;
}
uint8_t i;
short x_temp,y_temp,z_temp;
*x = 0;
*y = 0;
*z = 0;
for(i = 0; i < times; i++)
{
ADXL345_ReadXYZ(&x_temp, &y_temp, &z_temp);
*x += x_temp;
*y += y_temp;
*z += z_temp;
delayMs(5);
}
*x /= times;
*y /= times;
*z /= times;
}
/*使用偏移寄存器,进行偏移校准*/
void ADXL345_AutoAdjust(void)
{
uint8_t i;
short x_temp,y_temp,z_temp;
short x_offset = 0;
short y_offset = 0;
short z_offset = 0;
char x_calib = 0;
char y_calib = 0;
char z_calib = 0;
ADXL345_WriteReg(DATA_FORMAT, 0x0B); // 13位全分辨率,输出数据右对齐,16g量程
ADXL345_WriteReg(BW_RATE, 0x0A); // 数据输出速度为100Hz
ADXL345_WriteReg(POWER_CTL, 0x08); // 无链接,测量模式
ADXL345_WriteReg(INT_ENABLE, 0x80); // DATA_READY中断
delayMs(12);
for(i = 0; i < 10; i++)
{
ADXL345_ReadAverage(&x_temp, &y_temp, &z_temp, 10);
x_offset += x_temp;
y_offset += y_temp;
z_offset += z_temp;
}
x_offset /= 10;
y_offset /= 10;
z_offset /= 10;
x_calib =- x_offset / 4;
y_calib =- y_offset / 4;
z_calib =- (z_offset - 256) / 4;
ADXL345_WriteReg(OFSX, x_calib);
ADXL345_WriteReg(OFSY, y_calib);
ADXL345_WriteReg(OFSZ, z_calib);
}
/*计算ADXL345角度,x/y/表示各方向上的加速度分量,dir表示要获得的角度*/
short ADXL345_GetAngle(float x, float y, float z, uint8_t dir)
{
float temp;
float res = 0; // 弧度值
switch(dir)
{
case 0: // 0表示与Z轴的角度
temp = sqrt((x*x + y*y)) / z;
res = atan(temp);
break;
case 1: // 1表示与X轴的角度
temp = x / sqrt((y*y + z*z));
res = atan(temp);
break;
case 2: // 2表示与Y轴的角度
temp = y / sqrt((x*x + z*z));
res = atan(temp);
break;
}
return res * 180 / 3.14; // 返回角度值
// return res*180/3.14*10; //乘以10是为了取一位小数,角度精确到0.1°所以要乘以10
}
/**
@brief 写寄存器
@param addr -[in] 寄存器地址
@param data -[in] 写入数据
@return 无
*/
void ADXL345_WriteReg(uint8_t addr, uint8_t data)
{
uint8_t send_data[2];
uint32_t size = 2;
addr &= 0x3F;
send_data[0] = addr;
send_data[1] = data;
SPI_CS_LOW;
ADXL345_SPI_Write(send_data, size);
SPI_CS_HIGH;
}
/**
@brief 读寄存器
@param addr -[in] 寄存器地址
@return 读出一字节数据
*/
uint8_t ADXL345_ReadReg(uint8_t addr)
{
uint8_t receive_data;
uint32_t size = 1;
addr &= 0x3F;
addr |= 0x80;
SPI_CS_LOW;
ADXL345_SPI_Write(&addr, size);
ADXL345_SPI_Read(&receive_data, size);
SPI_CS_HIGH;
return receive_data;
}
/**
@brief 毫秒级延时函数
@param time -[in] 延时时间(毫秒)
@return 无
*/
static void delayMs(uint32_t time)
{
vTaskDelay(time / portTICK_PERIOD_MS);
}
5.2 board_spi.h
#ifndef _BOARD_SPI_H_
#define _BOARD_SPI_H_
/*********************************************************************
* INCLUDES
*/
#include <stdint.h>
#include "driver/gpio.h"
/*********************************************************************
* DEFINITIONS
*/
#define ADXL345_SPI_MISO_PIN GPIO_NUM_19
#define ADXL345_SPI_MOSI_PIN GPIO_NUM_23
#define ADXL345_SPI_SCLK_PIN GPIO_NUM_18
#define ADXL345_SPI_CS_PIN GPIO_NUM_5
#define DMA_CHAN 2
#define SPI_CS_LOW ADXL345_SPI_CS_Set(0)
#define SPI_CS_HIGH ADXL345_SPI_CS_Set(1)
/*********************************************************************
* API FUNCTIONS
*/
void ADXL345_SPI_Init(void);
void ADXL345_SPI_Write(uint8_t *pData, uint32_t dataLen);
void ADXL345_SPI_Read(uint8_t *pData, uint32_t dataLen);
void ADXL345_SPI_CS_Set(uint8_t level);
#endif /* _BOARD_SPI_H_ */
5.3 board_adxl345.c
#include <math.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "board_spi.h"
#include "board_adxl345.h"
static void delayMs(uint32_t time);
void ADXL345_Init(void)
{
ADXL345_SPI_Init();
while(ADXL345_GetDeviceId() != 0xE5)
{
printf("ADXL345 Init Fail!\n");
delayMs(1000);
}
ADXL345_WriteReg(DATA_FORMAT, 0x0B); // 13位全分辨率,输出数据右对齐,16g量程
ADXL345_WriteReg(BW_RATE, 0x0A); // 数据输出速度为100Hz
ADXL345_WriteReg(POWER_CTL, 0x08); // 无链接,测量模式
ADXL345_WriteReg(INT_ENABLE, 0x80); // DATA_READY中断
printf("ADXL345 Init Success!\n");
}
uint8_t ADXL345_GetDeviceId(void)
{
uint8_t ret = ADXL345_ReadReg(DEVICE_ID);
return ret;
}
void ADXL345_ReadXYZ(short *x, short *y, short *z)
{
uint8_t x0,y0,z0;
uint8_t x1,y1,z1;
x0 = ADXL345_ReadReg(DATA_X0);
y0 = ADXL345_ReadReg(DATA_Y0);
z0 = ADXL345_ReadReg(DATA_Z0);
x1 = ADXL345_ReadReg(DATA_X1);
y1 = ADXL345_ReadReg(DATA_Y1);
z1 = ADXL345_ReadReg(DATA_Z1);
// printf("--------------x0:%d,y0:%d,z0:%d\tx1:%d,y1:%d,z1:%d", x0,y0,z0,x1,y1,z1);
*x = (short)(((uint16_t)x1 << 8) + x0); // DATA_X1为高位有效字节
*y = (short)(((uint16_t)y1 << 8) + y0); // DATA_Y1为高位有效字节
*z = (short)(((uint16_t)z1 << 8) + z0); // DATA_Z1为高位有效字节
// printf("--------------x%d,y%d,z%d",*x,*y,*z);
}
/*读取ADXL345的数据并做滤波处理,读times次再取平均值*/
void ADXL345_ReadAverage(short *x, short *y, short *z, uint8_t times)
{
if(0 == times)
{
return;
}
uint8_t i;
short x_temp,y_temp,z_temp;
*x = 0;
*y = 0;
*z = 0;
for(i = 0; i < times; i++)
{
ADXL345_ReadXYZ(&x_temp, &y_temp, &z_temp);
*x += x_temp;
*y += y_temp;
*z += z_temp;
delayMs(5);
}
*x /= times;
*y /= times;
*z /= times;
}
/*使用偏移寄存器,进行偏移校准*/
void ADXL345_AutoAdjust(void)
{
uint8_t i;
short x_temp,y_temp,z_temp;
short x_offset = 0;
short y_offset = 0;
short z_offset = 0;
char x_calib = 0;
char y_calib = 0;
char z_calib = 0;
ADXL345_WriteReg(DATA_FORMAT, 0x0B); // 13位全分辨率,输出数据右对齐,16g量程
ADXL345_WriteReg(BW_RATE, 0x0A); // 数据输出速度为100Hz
ADXL345_WriteReg(POWER_CTL, 0x08); // 无链接,测量模式
ADXL345_WriteReg(INT_ENABLE, 0x80); // DATA_READY中断
delayMs(12);
for(i = 0; i < 10; i++)
{
ADXL345_ReadAverage(&x_temp, &y_temp, &z_temp, 10);
x_offset += x_temp;
y_offset += y_temp;
z_offset += z_temp;
}
x_offset /= 10;
y_offset /= 10;
z_offset /= 10;
x_calib =- x_offset / 4;
y_calib =- y_offset / 4;
z_calib =- (z_offset - 256) / 4;
ADXL345_WriteReg(OFSX, x_calib);
ADXL345_WriteReg(OFSY, y_calib);
ADXL345_WriteReg(OFSZ, z_calib);
}
/*计算ADXL345角度,x/y/表示各方向上的加速度分量,direction表示要获得的角度*/
short ADXL345_GetAngle(float x, float y, float z, uint8_t direction)
{
float temp;
float res = 0; // 弧度值
switch(direction)
{
case 0: // 0表示与Z轴的角度
temp = sqrt((x*x + y*y)) / z;
res = atan(temp);
break;
case 1: // 1表示与X轴的角度
temp = x / sqrt((y*y + z*z));
res = atan(temp);
break;
case 2: // 2表示与Y轴的角度
temp = y / sqrt((x*x + z*z));
res = atan(temp);
break;
}
return res * 180 / 3.14; // 返回角度值
// return res*180/3.14*10; //乘以10是为了取一位小数,角度精确到0.1°所以要乘以10
}
/**
@brief 写寄存器
@param addr -[in] 寄存器地址
@param data -[in] 写入数据
@return 无
*/
void ADXL345_WriteReg(uint8_t addr, uint8_t data)
{
uint8_t send_data[2];
uint32_t size = 2;
addr &= 0x3F;
send_data[0] = addr;
send_data[1] = data;
SPI_CS_LOW;
ADXL345_SPI_Write(send_data, size);
SPI_CS_HIGH;
}
/**
@brief 读寄存器
@param addr -[in] 寄存器地址
@return 读出一字节数据
*/
uint8_t ADXL345_ReadReg(uint8_t addr)
{
uint8_t receive_data;
uint32_t size = 1;
addr &= 0x3F;
addr |= 0x80;
SPI_CS_LOW;
ADXL345_SPI_Write(&addr, size);
ADXL345_SPI_Read(&receive_data, size);
SPI_CS_HIGH;
return receive_data;
}
/**
@brief 毫秒级延时函数
@param time -[in] 延时时间(毫秒)
@return 无
*/
static void delayMs(uint32_t time)
{
vTaskDelay(time / portTICK_PERIOD_MS);
}
5.4 board_adxl345.h
#ifndef _BOARD_ADXL345_H_
#define _BOARD_ADXL345_H_
#ifdef __cplusplus
extern "C"
{
#endif
#include <stdint.h>
#define DEVICE_ID 0X00 // 器件ID,0XE5
#define THRESH_TAP 0X1D // 敲击阀值寄存器
#define OFSX 0X1E
#define OFSY 0X1F
#define OFSZ 0X20
#define DUR 0X21
#define Latent 0X22
#define Window 0X23
#define THRESH_ACT 0X24 // 运动阈值寄存器
#define THRESH_INACT 0X25 // 静止阈值寄存器
#define TIME_INACT 0X26 // 静止时间 比例1 sec /LSB
#define ACT_INACT_CTL 0X27 // 启用运动/静止检测
#define THRESH_FF 0X28 // 自由下落阈值 建议采用300 mg与600 mg(0x05至0x09)之间的值 比例62.5 mg/LSB
#define TIME_FF 0X29 // 自由下落时间 建议采用100 ms与350 ms(0x14至0x46)之间的值 比例5ms/LSB
#define TAP_AXES 0X2A
#define ACT_TAP_STATUS 0X2B
#define BW_RATE 0X2C
#define POWER_CTL 0X2D
#define INT_ENABLE 0X2E // 设置中断配置
#define INT_MAP 0X2F
#define INT_SOURCE 0X30
#define DATA_FORMAT 0X31
#define DATA_X0 0X32
#define DATA_X1 0X33
#define DATA_Y0 0X34
#define DATA_Y1 0X35
#define DATA_Z0 0X36
#define DATA_Z1 0X37
#define FIFO_CTL 0X38
#define FIFO_STATUS 0X39
#define Z_AXIS 0
#define X_AXIS 1
#define Y_AXIS 2
void ADXL345_Init(void);
uint8_t ADXL345_GetDeviceId(void);
void ADXL345_ReadXYZ(short *x, short *y, short *z);
void ADXL345_ReadAverage(short *x, short *y, short *z, uint8_t times);
short ADXL345_GetAngle(float x, float y, float z, uint8_t direction);
void ADXL345_WriteReg(uint8_t addr, uint8_t data);
uint8_t ADXL345_ReadReg(uint8_t addr);
#ifdef __cplusplus
}
#endif
#endif /* _BOARD_ADXL345_H_ */
六、初始化流程
开机先等待 1.1ms——>设置数据格式——>设置功耗模式——>使能相应中断——>结束。
七、角度值转换公式
八、API调用
需包含头文件 board_adxl345.h
ADXL345_Init
功能 | ADXL345的初始化函数 |
---|---|
函数定义 | void ADXL345_Init(void) |
参数 | 无 |
返回 | 无 |
ADXL345_GetDeviceId
功能 | 检查SPI通信,返回值为0xE5则通信正常 |
---|---|
函数定义 | uint8_t ADXL345_GetDeviceId(void) |
参数 | 无 |
返回 | 固定器件ID值,0xE5 |
ADXL345_ReadXYZ
功能 | 读取X、Y、Z轴加速度值 |
---|---|
函数定义 | void ADXL345_ReadXYZ(short *x, short *y, short *z) |
参数 | 要读取的方向X,Y,Z轴返回值 |
返回 | 无 |
ADXL345_GetAngle
功能 | 读取角度 |
---|---|
函数定义 | short ADXL345_GetAngle(float x, float y, float z, uint8_t direction) |
参数 | x,y,z:X、Y、Z轴加速度值 direction:要读取的方向X,Y,Z轴 |
返回 | 角度数 |
九、使用例子
1)添加头文件
#include "board_adxl345.h"
2)添加初始化代码(main.c的main函数中)
首先调用 ADXL345_SPI_Init()
初始化 SPI 通信,最后调用 ADXL345_Init()
初始化加速度传感器模块功能。
#include <stdio.h>
#include <stdbool.h>
#include <unistd.h>
void app_main(void)
{
ADXL345_Init();
/*-------------------------- 创建线程 ---------------------------*/
xTaskCreate(monitor_task, "monitor_task", 2048, NULL, 4, NULL);
}
3)添加任务,定时读取数据
static void monitor_task(void *arg)
{
while(1) // 任务都是一个无限循环,不能返回
{
short x_value,y_value,z_value;
short x_angle,y_angle,z_angle;
ADXL345_ReadAverage(&x_value, &y_value, &z_value, 10);
x_angle = ADXL345_GetAngle(x_value, y_value, z_value, X_AXIS);
y_angle = ADXL345_GetAngle(x_value, y_value, z_value, Y_AXIS);
z_angle = ADXL345_GetAngle(x_value, y_value, z_value, Z_AXIS);
printf("x_ang:%d y_ang:%d z_ang:%d\n", x_angle, y_angle, z_angle);
vTaskDelay(1000 / portTICK_PERIOD_MS); // 1s
}
}
4)查看打印
十、测量方向
• 由 Leung 写于 2023 年 8 月 7 日
• 参考:STC8H开发(六): SPI驱动ADXL345三轴加速度检测模块
STM32 HAL库学习笔记-(SPI驱动ADXL345)
STM32CubeMX系列|ADXL345传感器
ADXL345 三轴加速度数据SPI读取、多字节读取、DMA SPI读取和FIFO数据读取