ESP32学习笔记(52)——三轴加速度ADXL345使用(SPI方式)

一、简介

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

二、硬件连接

功能口引脚
MISO19
MOSI23
SCLK18
CS5

三、添加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

/*********************************************************************
 * INCLUDES
 */
#include <string.h>
#include "driver/spi_master.h"

#include "board_spi.h"

/*********************************************************************
 * LOCAL VARIABLES
 */
static spi_device_handle_t s_spiHandle;

/*********************************************************************
 * PUBLIC FUNCTIONS
 */
/**
 @brief ADXL345 SPI驱动初始化
 @param 无
 @return 无
*/
void ADXL345_SPI_Init(void)
{
    esp_err_t ret;

    spi_bus_config_t spiBusConfig =
    {
        .miso_io_num = ADXL345_SPI_MISO_PIN,				// MISO信号线
        .mosi_io_num = ADXL345_SPI_MOSI_PIN,                // MOSI信号线
        .sclk_io_num = ADXL345_SPI_SCLK_PIN,                // SCLK信号线
        .quadwp_io_num = -1,                                // WP信号线,专用于QSPI的D2
        .quadhd_io_num = -1,                                // HD信号线,专用于QSPI的D3
        .max_transfer_sz = 64 * 8,                          // 最大传输数据大小
    };

    spi_device_interface_config_t spiDeviceConfig =
    {
        .clock_speed_hz = SPI_MASTER_FREQ_10M,              // Clock out at 10 MHz,
        .mode = 3,                                          // SPI mode 0
        /*
         * The timing requirements to read the busy signal from the EEPROM cannot be easily emulated
         * by SPI transactions. We need to control CS pin by SW to check the busy signal manually.
         */
        .spics_io_num = -1,
        .queue_size = 7,                                    // 传输队列大小,决定了等待传输数据的数量
    };

    //Initialize the SPI bus
    ret = spi_bus_initialize(SPI3_HOST, &spiBusConfig, DMA_CHAN);
    ESP_ERROR_CHECK(ret);
    ret = spi_bus_add_device(SPI3_HOST, &spiDeviceConfig, &s_spiHandle);
    ESP_ERROR_CHECK(ret);

    // 配置CS引脚
    gpio_config_t io_conf;  // 定义一个gpio_config类型的结构体,下面的都算对其进行的配置
    io_conf.intr_type = GPIO_INTR_DISABLE;  	// 禁止中断
    io_conf.mode = GPIO_MODE_INPUT;             // 选择输入模式
    io_conf.pin_bit_mask = (1ULL<<ADXL345_SPI_CS_PIN);  // 配置GPIO_IN寄存器
    io_conf.pull_down_en = 0;                   // 禁止下拉
    io_conf.pull_up_en = 0;                     // 禁止上拉
    gpio_config(&io_conf);                      // 最后配置使能

    gpio_set_direction(ADXL345_SPI_CS_PIN, GPIO_MODE_OUTPUT);// 把这个GPIO作为输出
}

/**
 @brief ADXL345 SPI写入数据
 @param pData -[in] 写入数据
 @param dataLen -[in] 写入数据长度
 @return 无
*/
void ADXL345_SPI_Write(uint8_t *pData, uint32_t dataLen)
{
    esp_err_t ret;
    spi_transaction_t t;
    if(0 == dataLen)                                        // no need to send anything
    {
        return;
    }

    memset(&t, 0, sizeof(t));                               // Zero out the transaction
    t.length = dataLen * 8;                                 // Len is in bytes, transaction length is in bits.
    t.tx_buffer = pData;                                    // Data
    ret = spi_device_polling_transmit(s_spiHandle, &t);     // Transmit!
    assert(ret == ESP_OK);                                  // Should have had no issues.
}

/**
 @brief ADXL345 SPI读取数据
 @param pData -[out] 读取数据
 @param dataLen -[in] 读取数据长度
 @return 无
*/
void ADXL345_SPI_Read(uint8_t *pData, uint32_t dataLen)
{
    spi_transaction_t t;
    if(0 == dataLen)                                        // no need to receivce anything
    {
        return;
    }

    memset(&t, 0, sizeof(t));                               // Zero out the transaction
    t.length = dataLen * 8;                                 // Len is in bytes, transaction length is in bits.
    t.rx_buffer = pData;
    esp_err_t ret = spi_device_polling_transmit(s_spiHandle, &t);
    assert(ret == ESP_OK);
}

/**
 @brief ADXL345 SPI CS引脚设置
 @param level -[in] 电平
 @return 无
*/
void ADXL345_SPI_CS_Set(uint8_t level)
{
	gpio_set_level(ADXL345_SPI_CS_PIN, level);
}

/****************************************************END OF FILE****************************************************/

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数据读取

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Leung_ManWah

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值