Skip to content

LIS3DH 加速度传感器

LIS3DH 是一个 3D 加速度传感器,可以测量 3 个轴的加速度。它具有高分辨率、低功耗和宽工作温度范围的特点,适用于各种应用场景,如智能手机、平板电脑、可穿戴设备和工业设备等。

数据手册

LIS3DH 数据手册
LIS3DH 数据手册(中文版)

主要特性

  • 3D 加速度传感器,可以测量 X、Y 和 Z 三个轴的加速度
  • 高分辨率,最高可达 12 位,分辨率为 0.061 mg/LSB
  • 低功耗,工作电流可低至 200 μA
  • 中断功能,可检测到 X、Y 和 Z 三个轴的加速度变化

程序开发要点

  • 初始化传感器中的寄存器
c
// LIS30DH寄存器地址
#define LIS3DH_WHO_AM_I   0x0F
#define LIS3DH_CTRL_REG1  0x20
#define LIS3DH_CTRL_REG4  0x23
#define LIS3DH_OUT_X_L    0x28
#define LIS3DH_OUT_X_H    0x29
#define LIS3DH_OUT_Y_L    0x2A
#define LIS3DH_OUT_Y_H    0x2B
#define LIS3DH_OUT_Z_L    0x2C
#define LIS3DH_OUT_Z_H    0x2D
void init_lis3dh(void) {
    // 验证设备ID
    uint8_t id = lis3dh_read_reg(LIS3DH_WHO_AM_I);
    if (id != 0x33) {
        ESP_LOGE(TAG, "设备ID错误: 0x%02X (应为0x33)", id);
        return;
    }
    ESP_LOGI(TAG, "检测到LIS3DH (ID: 0x%02X)", id);

    // 配置CTRL_REG1: 100Hz ODR, 使能XYZ轴
    lis3dh_write_reg(LIS3DH_CTRL_REG1, 0x57); // 0101 0111

    // 配置CTRL_REG4: +-2g量程, 高分辨率模式
    lis3dh_write_reg(LIS3DH_CTRL_REG4, 0x88); // 1000 1000

    vTaskDelay(pdMS_TO_TICKS(20)); // 等待配置生效
}

这里配置了两个寄存器的值,分别表示:
1. CTRL_REG1: 100Hz ODR, 使能XYZ轴 (数据手册的第35页)
2. CTRL_REG4: +-2g量程, 高分辨率模式(数据手册的第37页)

  • 读取传感器数据
c
// 读取XYZ轴加速度
void read_acceleration(int16_t *x, int16_t *y, int16_t *z) {
    uint8_t data[6];

    // 多字节读取(自动递增地址)
    spi_transaction_t t = {
        .cmd = (LIS3DH_OUT_X_L | 0x80 | 0x40), // 读+自动递增
        .length = 48, // 6字节 * 8位
        .rx_buffer = data,
    };
    ESP_ERROR_CHECK(spi_device_transmit(spi, &t));

    // 组合高低字节(数据为小端模式)
    *x = (int16_t)((data[1] << 8) | data[0]);
    *y = (int16_t)((data[3] << 8) | data[2]);
    *z = (int16_t)((data[5] << 8) | data[4]);

    // 转换为12位有符号数(右移4位)
    *x >>= 4;
    *y >>= 4;
    *z >>= 4;
}

这里使用了多字节读取,因为LIS3DH的寄存器是连续的,使用自动递增地址可以一次性读取多个寄存器的数据。同时,由于数据是12位有符号数,需要右移4位才能得到正确的值。参考手册的第28页的说明.

使用SPI接口来驱动LIS3DH

使用ESP32-C3开发板

c
#include <stdio.h>
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/spi_master.h"
#include "driver/gpio.h"
#include "esp_log.h"
// #include "esp_timer.h"

// 引脚配置(根据实际硬件连接修改)
#define PIN_NUM_MISO  2  // SPI MISO
#define PIN_NUM_MOSI  1  // SPI MOSI
#define PIN_NUM_CLK   0  // SPI CLK
#define PIN_NUM_CS    3  // SPI CS
#define PIN_LED       GPIO_NUM_4  // LED指示灯

// LIS30DH寄存器地址
#define LIS3DH_WHO_AM_I   0x0F
#define LIS3DH_CTRL_REG1  0x20
#define LIS3DH_CTRL_REG4  0x23
#define LIS3DH_OUT_X_L    0x28
#define LIS3DH_OUT_X_H    0x29
#define LIS3DH_OUT_Y_L    0x2A
#define LIS3DH_OUT_Y_H    0x2B
#define LIS3DH_OUT_Z_L    0x2C
#define LIS3DH_OUT_Z_H    0x2D

// 配置参数
#define SPI_CLOCK_SPEED   1000000  // 1 MHz SPI时钟
#define SAMPLE_RATE_HZ    100      // 采样率100Hz
#define VIBRATION_THRESHOLD 5    // 振动阈值(约0.1g)
#define MOVING_AVG_SIZE   5       // 移动平均窗口大小

static const char *TAG = "LIS3DH";
spi_device_handle_t spi;

// SPI初始化
void init_spi(void) {
    spi_bus_config_t buscfg = {
        .miso_io_num = PIN_NUM_MISO,
        .mosi_io_num = PIN_NUM_MOSI,
        .sclk_io_num = PIN_NUM_CLK,
        .quadwp_io_num = -1,
        .quadhd_io_num = -1,
        .max_transfer_sz = 0
    };

    spi_device_interface_config_t devcfg = {
        .clock_speed_hz = SPI_CLOCK_SPEED,
        .mode = 3,                  // CPOL=1, CPHA=1
        .spics_io_num = PIN_NUM_CS,
        .queue_size = 7,
        .command_bits = 8,           // 寄存器地址作为command发送
        .address_bits = 0,
    };

    // 初始化SPI总线
    ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO));
    ESP_ERROR_CHECK(spi_bus_add_device(SPI2_HOST, &devcfg, &spi));
}

// 写入寄存器
void lis3dh_write_reg(uint8_t reg, uint8_t value) {
    spi_transaction_t t = {
        .cmd = reg, // 清除读写位(bit7)和地址递增位(bit6)
        .length = 8,
        .tx_buffer = &value,
    };
    ESP_ERROR_CHECK(spi_device_transmit(spi, &t));
}

// 读取寄存器
uint8_t lis3dh_read_reg(uint8_t reg) {
    uint8_t rx_data = 0;
    spi_transaction_t t = {
        .cmd = (reg | 0x80), // 设置读位(bit7)
        .length = 8,
        .rx_buffer = &rx_data,
    };
    ESP_ERROR_CHECK(spi_device_transmit(spi, &t));
    return rx_data;
}

// 初始化LIS3DH
void init_lis3dh(void) {
    // 验证设备ID
    uint8_t id = lis3dh_read_reg(LIS3DH_WHO_AM_I);
    if (id != 0x33) {
        ESP_LOGE(TAG, "设备ID错误: 0x%02X (应为0x33)", id);
        return;
    }
    ESP_LOGI(TAG, "检测到LIS3DH (ID: 0x%02X)", id);

    // 配置CTRL_REG1: 100Hz ODR, 使能XYZ轴
    lis3dh_write_reg(LIS3DH_CTRL_REG1, 0x57); // 0101 0111

    // 配置CTRL_REG4: +-2g量程, 高分辨率模式
    lis3dh_write_reg(LIS3DH_CTRL_REG4, 0x88); // 1000 1000

    vTaskDelay(pdMS_TO_TICKS(20)); // 等待配置生效
}

// 读取三轴加速度
void read_acceleration(int16_t *x, int16_t *y, int16_t *z) {
    uint8_t data[6];

    // 多字节读取(自动递增地址)
    spi_transaction_t t = {
        .cmd = (LIS3DH_OUT_X_L | 0x80 | 0x40), // 读+自动递增
        .length = 48, // 6字节 * 8位
        .rx_buffer = data,
    };
    ESP_ERROR_CHECK(spi_device_transmit(spi, &t));

    // 组合高低字节(数据为小端模式)
    *x = (int16_t)((data[1] << 8) | data[0]);
    *y = (int16_t)((data[3] << 8) | data[2]);
    *z = (int16_t)((data[5] << 8) | data[4]);

    // 转换为12位有符号数(右移4位)
    *x >>= 4;
    *y >>= 4;
    *z >>= 4;
}

// 检测振动
bool detect_vibration(int16_t x, int16_t y, int16_t z) {
    static int16_t history_x[MOVING_AVG_SIZE] = {0};
    static int16_t history_y[MOVING_AVG_SIZE] = {0};
    static int16_t history_z[MOVING_AVG_SIZE] = {0};
    static uint8_t index = 0;

    // 更新历史数据
    history_x[index] = x;
    history_y[index] = y;
    history_z[index] = z;
    index = (index + 1) % MOVING_AVG_SIZE;

    // 计算移动平均
    int32_t avg_x = 0, avg_y = 0, avg_z = 0;
    for (int i = 0; i < MOVING_AVG_SIZE; i++) {
        avg_x += history_x[i];
        avg_y += history_y[i];
        avg_z += history_z[i];
    }
    avg_x /= MOVING_AVG_SIZE;
    avg_y /= MOVING_AVG_SIZE;
    avg_z /= MOVING_AVG_SIZE;

    // 计算与平均值的差异(瞬时振动)
    int16_t diff_x = abs(x - avg_x);
    int16_t diff_y = abs(y - avg_y);
    int16_t diff_z = abs(z - avg_z);

    // 检查是否超过阈值
    return (diff_x > VIBRATION_THRESHOLD) ||
           (diff_y > VIBRATION_THRESHOLD) ||
           (diff_z > VIBRATION_THRESHOLD);
}

void app_main() {
    // 初始化外设
    // 将 PIN_LED 设置为推挽输出
    gpio_reset_pin(PIN_LED);
    gpio_set_direction(PIN_LED, GPIO_MODE_OUTPUT);

    init_spi();
    init_lis3dh();

    int16_t x, y, z;
    uint32_t last_wake_time = xTaskGetTickCount();
    uint8_t led_state = 0;

    while (1) {
        // 读取加速度数据
        read_acceleration(&x, &y, &z);


        // 检测振动
        if (detect_vibration(x, y, z)) {
            // 翻转 PIN_LED 状态
            led_state = led_state == 1 ? 0 : 1; // 翻转LED状态
            gpio_set_level(PIN_LED, led_state);
            ESP_LOGI(TAG, "检测到振动! X:%6d Y:%6d Z:%6d", x, y, z);
        }

        // 按固定频率采样
        vTaskDelayUntil(&last_wake_time, pdMS_TO_TICKS(1000 / SAMPLE_RATE_HZ));
    }
}

使用 STC8G1K08A-8pin 板

如何使用INT中断

中断引脚的核心作用

LIS3DH的中断引脚(INT1和INT2)提供了高效的事件驱动机制,无需微控制器持续轮询传感器,具有以下核心优势:

  • 即时响应:检测到特定事件时立即通知处理器
  • 降低功耗:主处理器可休眠,仅当中断触发时唤醒
  • 减少总线负载:避免持续SPI通信消耗带宽
  • 简化程序设计:事件驱动编程模型更简洁高效

中断使用方法详解

  • 1.运动检测中断: 振动唤醒、跌倒检测、运动检测
c
   // 配置运动检测中断
   lis3dh_write_reg(0x30, 0x2A); // XYZ轴高阈值检测
   lis3dh_write_reg(0x32, 0x20); // 设置阈值(约0.4g)
  • 2.静止检测中断: 静止检测、静止唤醒
c
   // 配置静止检测中断
   lis3dh_write_reg(0x30, 0x15); // XYZ轴低阈值检测
   lis3dh_write_reg(0x32, 0x10); // 设置低阈值(约0.2g)
  • 3.单击/双击中断: 单击/双击检测
c
   // 配置单击检测
   lis3dh_write_reg(0x38, 0x15); // CLICK_CFG: 使能XYZ轴单击检测
   lis3dh_write_reg(0x3A, 0x10); // CLICK_THS: 单击阈值
  • 4.方向检测中断: 屏幕的旋转检测
c
   // 配置6D方向检测
   lis3dh_write_reg(0x30, 0x40); // 6D方向检测模式
  • 5.数据就绪中断: ADC数据采集
c
   // 配置数据就绪中断
   lis3dh_write_reg(0x22, 0x10); // I1_ZYXDA使能

中断的使用代码示例:

c

// 寄存器定义
#define LIS3DH_CTRL_REG1  0x20
#define LIS3DH_CTRL_REG3  0x22
#define LIS3DH_CTRL_REG4  0x23
#define LIS3DH_INT1_CFG   0x30
#define LIS3DH_INT1_SRC   0x31
#define LIS3DH_INT1_THS   0x32
#define LIS3DH_INT1_DUR   0x33
#define LIS3DH_STATUS_REG 0x27

// 配置振动检测中断
void init_lis3dh(void) {
        // 复位设备
    lis3dh_write_reg(0x20, 0x57); // CTRL_REG1: 100Hz ODR, 高功率模式
    
    // 配置高通滤波器 (关键!)
    lis3dh_write_reg(0x21, 0x19); // CTRL_REG2: HPF使能, 截止频率1Hz
    
    // 配置中断1
    lis3dh_write_reg(0x30, 0x7F); // INT1_CFG: 差分阈值检测(XLIE,YLIE,ZLIE)
    lis3dh_write_reg(0x32, 0x05); // INT1_THS: 0.06g阈值(用于微小振动)
    lis3dh_write_reg(0x33, 0x01); // INT1_DUR: 50ms持续时间(用于微小振动)
    // 配置量程和分辨率
    lis3dh_write_reg(0x23, 0x08); // CTRL_REG4: ±2g, 高分辨率模式
    // 配置中断引脚
    lis3dh_write_reg(0x25, 0x02); // CTRL_REG6: INT1引脚推挽输出, 

    // 启用中断路由
    lis3dh_write_reg(0x22, 0x40); // CTRL_REG3: 路由中断发生器1到INT1
    
}

这段代码设置后,会感知轻微的震动.并触发INT1引脚的中断.

关于阀值的设置

在设置 INT1_THS (32h) 的值作为阈值时.需要考虑量程和阀值的位数(7位).其阀值的计算公式为:

c
threshold = (INT1_THS * 量程) / (2^7)

量程是由 CTRL_REG4FS 位决定的.其值如下: 量程选择. 默认值: 00 (00: ±2 g; 01: ±4 g; 10: ±8 g; 11: ±16 g)

默认情况下,量程为 ±2 g.因此,阀值的计算公式为:

c
threshold = (INT1_THS * 2) / (2^7)

例如,如果 INT1_THS 的值为 0x30 (48),则阀值为:

c
threshold = (48 * 2) / (2^7) = 0.5 g

如何使用ADC引脚采集数据

LIS3DH芯片有三个adc引脚,分别是:ADC1,ADC2,ADC3,可以用来采集加速度数据,也可以用来采集温度数据. LIS3DH 的 ADC 引脚是一个模拟输入通道,它扩展了加速度计的功能,使其不仅能测量加速度,还能采集外部模拟信号。这个引脚的关键作用在于:

  • 多传感器集成:允许在不增加额外ADC芯片的情况下连接外部模拟传感器
  • 系统简化:减少电路板上元件数量和PCB空间占用
  • 同步采集:可实现加速度数据与模拟信号的精确时间同步
  • 功耗优化:比外置ADC更省电,适用于电池供电设备

ADC原理框图

LIS3DH_ADC

配置与使用流程

  1. 启用ADC功能
c
// 启用ADC和温度传感器(需同时启用)
#define LIS3DH_TEMP_CFG_REG 0x1F
void enable_adc(void) {
    // 写入0xC0: 启用ADC+温度传感器
    lis3dh_write_reg(LIS3DH_TEMP_CFG_REG, 0xC0);
    
    // 设置ODR启动转换(示例100Hz)
    lis3dh_write_reg(0x20, 0x57); // CTRL_REG1: 100Hz ODR
    vTaskDelay(pdMS_TO_TICKS(10)); // 等待稳定
}
  1. 读取ADC数据
c
// 读取ADC1的10位值(0-1023)
uint16_t read_adc1(void) {
    uint8_t adc_l = lis3dh_read_reg(0x08); // OUT_ADC1_L
    uint8_t adc_h = lis3dh_read_reg(0x09); // OUT_ADC1_H
    
    // 组合10位值: adc_h[1:0] + adc_l[7:0]
    return (uint16_t)(((adc_h & 0x03) << 8) | adc_l);
}
  1. 转为实际值
c
// 假设Vdd=3.3V
float adc_to_voltage(uint16_t adc_value) {
    return (adc_value / 1023.0f) * 3.3f;
}

// 使用示例:
uint16_t adc_val = read_adc1();
float voltage = adc_to_voltage(adc_val);
ESP_LOGI("ADC", "ADC1电压: %.2fV", voltage);