From b0528ff45603cb26fbaf5092b4a6d7c99cc4ba1a Mon Sep 17 00:00:00 2001 From: William Clark Date: Tue, 2 Jan 2024 10:56:58 +0000 Subject: [PATCH] inertial wakeup --- example/README.md | 49 +++++++++++--- example/inertial-wakeup.c | 134 ++++++++++++++++++++++++++++++++++++++ lis3dh.c | 12 ++-- lis3dh.h | 4 +- 4 files changed, 185 insertions(+), 14 deletions(-) create mode 100644 example/inertial-wakeup.c diff --git a/example/README.md b/example/README.md index 4ac9831..971458a 100644 --- a/example/README.md +++ b/example/README.md @@ -1,9 +1,9 @@ # lis3dh/example -### simple.c +### file: simple.c Basic example of how to use this device -### fifo.c +### file: fifo.c Instead of polling for every single [x y z] set, a FIFO with programmable capacity ("watermark") can be used, and then dumped into memory once full. All FIFO readings use 10-bit resolution regardless of the mode set in `lis.cfg.mode`. @@ -12,18 +12,18 @@ The watermark level can be adjusted to a value [0-31] by modifying the `lis.cfg. The LIS3DH can optionally apply a HP filter on the sample data. It can be used to greatly reduce the "DC acceleration" present. -### interrupts.c +### file: interrupts.c This device supports two different interrupt "output pins," `INT1` and `INT2`. The appropriate flag must be set in either `cfg.pin1` or `cfg.pin2` and the interrupt source must be configured to trigger into `INT1` or `INT2`. This file contains example code that listens and receives an interrupt when the FIFO watermark is reached i.e. it is full. -### single-click.c +### file: single-click.c Set up single-click detection -### double-click.c +### file: double-click.c Set up double-click detection -### adc.c +### file: adc.c Enable and read built-in ADCs. @@ -31,9 +31,42 @@ Enable and read built-in ADCs. > - Resolution: 8-bit in LP mode, 10-bit in normal and in HR mode. > - Sampling frequency: same as ODR -### temp.c +### file: temp.c Enable and read built-in temperature sensor > - Operating range: -40 to 85°C -> - Step size: ±1°C \ No newline at end of file +> - Step size: ±1°C + +### Inertial interrupts + + +| aoi | en_6d | interrupt mode | +|-----|-------|-------------------------| +| 0 | 0 | OR combination | +| 0 | 1 | 6d MOVEMENT recognition | +| 1 | 0 | AND combination | +| 1 | 1 | 6d POSITION recognition | + + +#### OR combination + +An interrupt is generated when at least one of the configured axes is at or above the threshold level. + +#### 6D MOVEMENT recognition + +An interrupt is generated when the device moves from a direction (known or unknown) to a different known direction. The interrupt is only active for 1/ODR. + +#### AND combination + +An interrupt is generated when all of the configures axes are at or above the threshold level. + +#### 6D POSITION recognition + +An interrupt is generated when the device is "stable" in a known direction. The interrupt is is active as long as the direction is maintained. + +There are two interrupt registers, `int1` and `int2` that can be configured for inertial interrupts. The config structs are identical and contain the fields: `zh`, `zl`, `yh`, `yl`, `xh`, `xl`. `zh` stands for `Z_axis_high` and `zl` stands for `Z_axis_low`. If both are enabled, the device will generate an interrupt upon Z-axis acceleration exceeding `threshold`, or upon Z-axis acceleration reading at or below `-threshold`. + +### file: inertial-wakeup.c + +Inertial interrupt example in OR mode (easily changed to AND mode) with configurable axes, threshold and minimum acceleration duration. \ No newline at end of file diff --git a/example/inertial-wakeup.c b/example/inertial-wakeup.c new file mode 100644 index 0000000..f8b24cb --- /dev/null +++ b/example/inertial-wakeup.c @@ -0,0 +1,134 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include "lis3dh.h" +#include "interrupt.h" +#include "i2c.h" + +#define GPIO_INTERRUPT_PIN_INT1 12 + +int main() { + + lis3dh_t lis; + + /* set fn ptrs to rw on bus (i2c or SPI) */ + lis.dev.init = i2c_init; + lis.dev.read = i2c_read; + lis.dev.write = i2c_write; + lis.dev.sleep = usleep; + lis.dev.deinit = i2c_deinit; + + /* initialise LIS3DH struct */ + if (lis3dh_init(&lis)) { + /* error handling */ + } + + /* reset device because it sometimes corrupts itself */ + if (lis3dh_reset(&lis)) { + /* error handling */ + } + + /* register interrupt */ + if (int_register(GPIO_INTERRUPT_PIN_INT1)) { + /* error handling */ + } + + /* set up config */ + lis.cfg.mode = LIS3DH_MODE_HR; + lis.cfg.range = LIS3DH_FS_2G; + lis.cfg.rate = LIS3DH_ODR_400_HZ; + + lis.cfg.pin1.ia1 = 1; /* allow INT1 through INT_PIN1 */ + + + /* 1 LSb = 16 mg @ FS_2G + * 0.3g threshold = 300/16 = 18.75 + * add read error, +40mg => 240/16 = 21.25 ~= 21 + * if you for some reason don't want to use the HP filter, + * just add 1g to the threshold calculation. + */ + lis.cfg.int1_ths = 21; + + /* Duration time is measured in N/ODR where: + * --- N = The content of the intX_dur integer + * --- ODR = the data rate, eg 100, 400... + * [ODR] [1 LSb in milliseconds] + * 400 2.5 + * + * For ODR=400: + * 10 ms => 10/2.5 = 5 + * lis.cfg.int1_dur = 5; <== 10 ms minimum duration to wake up + */ + lis.cfg.int1_dur = 0; /* instantaneous */ + + /* enable X_high, Y_high and Z_high */ + lis.cfg.int1.yh = 1; + lis.cfg.int1.zh = 1; + lis.cfg.int1.xh = 1; + + /* OR mode. Think about the axis combinations for AND mode */ + lis.cfg.int1.aoi = 0; /* set to 1 for AND mode */ + lis.cfg.int1.en_6d = 0; + + + /* latch interrupt. might not work. */ + lis.cfg.int1.latch = 1; + + /* set up a HP filter to ignore constant earth acceleration */ + lis.cfg.filter.mode = LIS3DH_FILTER_MODE_NORMAL_REF; + lis.cfg.filter.cutoff = LIS3DH_FILTER_CUTOFF_8; + lis.cfg.filter.ia1 = 1; /* enable filter for INT1 generator */ + + + /* write device config */ + if (lis3dh_configure(&lis)) { + /* error handling */ + } + + /* read REFERENCE to set filter to current accel field */ + if (lis3dh_reference(&lis)) { + /* error handling */ + } + + /* read INT1_SRC to clear old interrupt if any */ + if (lis3dh_read_int1(&lis)) { + /* error handling */ + } + + for( ;; ) { + + /* wait for INT1 to go active */ + if (int_poll(GPIO_INTERRUPT_PIN_INT1)) { + /* error handling */ + } + + /* read INT1_SRC */ + if (lis3dh_read_int1(&lis)) { + /* error handling */ + } + + /* print received interrupt .. */ + printf("IA=%d ZH=%d ZL=%d YH=%d YL=%d XH=%d XL=%d\n", + !!(lis.src.int1 & 0x80), /* seems to always be cleared */ + LIS3DH_INT_SRC_Z_HIGH(lis.src.int1), + LIS3DH_INT_SRC_Z_LOW(lis.src.int1), + LIS3DH_INT_SRC_Y_HIGH(lis.src.int1), + LIS3DH_INT_SRC_Y_LOW(lis.src.int1), + LIS3DH_INT_SRC_X_HIGH(lis.src.int1), + LIS3DH_INT_SRC_X_LOW(lis.src.int1)); + } + + /* unregister interrupt */ + if (int_unregister(GPIO_INTERRUPT_PIN_INT1)) { + /* error handling */ + } + + /* deinitalise struct */ + if (lis3dh_deinit(&lis)) { + /* error handling */ + } + + return 0; +} \ No newline at end of file diff --git a/lis3dh.c b/lis3dh.c index edeb370..f86400f 100644 --- a/lis3dh.c +++ b/lis3dh.c @@ -90,6 +90,7 @@ int lis3dh_configure(lis3dh_t *lis3dh) { /* set interrupt registers */ + /* INT PIN 1 */ ctrl_reg3 |= (lis3dh->cfg.pin1.click & 1) << 7; ctrl_reg3 |= (lis3dh->cfg.pin1.ia1 & 1) << 6; ctrl_reg3 |= (lis3dh->cfg.pin1.ia2 & 1) << 5; @@ -98,6 +99,7 @@ int lis3dh_configure(lis3dh_t *lis3dh) { ctrl_reg3 |= (lis3dh->cfg.pin1.wtm & 1) << 2; ctrl_reg3 |= (lis3dh->cfg.pin1.overrun & 1) << 1; + /* INT PIN 2 */ ctrl_reg6 |= (lis3dh->cfg.pin2.click & 1) << 7; ctrl_reg6 |= (lis3dh->cfg.pin2.ia1 & 1) << 6; ctrl_reg6 |= (lis3dh->cfg.pin2.ia2 & 1) << 5; @@ -107,9 +109,9 @@ int lis3dh_configure(lis3dh_t *lis3dh) { /* set some of CTRL_REG5 */ ctrl_reg5 |= (lis3dh->cfg.int2.en_4d & 1); - ctrl_reg5 |= (lis3dh->cfg.pin2.latch & 1) << 1; + ctrl_reg5 |= (lis3dh->cfg.int2.latch & 1) << 1; ctrl_reg5 |= (lis3dh->cfg.int1.en_4d & 1) << 2; - ctrl_reg5 |= (lis3dh->cfg.pin1.latch & 1) << 3; + ctrl_reg5 |= (lis3dh->cfg.int1.latch & 1) << 3; /* set INT1_CFG and INT2_CFG */ @@ -131,9 +133,11 @@ int lis3dh_configure(lis3dh_t *lis3dh) { int2_cfg |= (lis3dh->cfg.int2.en_6d & 1) << 6; int2_cfg |= (lis3dh->cfg.int2.aoi & 1) << 7; + /* duration values */ int1_dur = lis3dh->cfg.int1_dur & 0x7F; int2_dur = lis3dh->cfg.int2_dur & 0x7F; + /* threshold values */ int1_ths = lis3dh->cfg.int1_ths & 0x7F; int2_ths = lis3dh->cfg.int2_ths & 0x7F; @@ -157,8 +161,8 @@ int lis3dh_configure(lis3dh_t *lis3dh) { ctrl_reg2 |= ((lis3dh->cfg.filter.cutoff & 0x03) << 4); ctrl_reg2 |= ((lis3dh->cfg.filter.fds & 1) << 3); ctrl_reg2 |= ((lis3dh->cfg.filter.click & 1) << 2); - ctrl_reg2 |= ((lis3dh->cfg.filter.ia1 & 1) << 1); - ctrl_reg2 |= (lis3dh->cfg.filter.ia2 & 1); + ctrl_reg2 |= ((lis3dh->cfg.filter.ia2 & 1) << 1); + ctrl_reg2 |= (lis3dh->cfg.filter.ia1 & 1); } /* always set block update (BDU) */ diff --git a/lis3dh.h b/lis3dh.h index 5f662a5..06b89da 100644 --- a/lis3dh.h +++ b/lis3dh.h @@ -91,6 +91,7 @@ #define LIS3DH_FILTER_CUTOFF_2 0x02 #define LIS3DH_FILTER_CUTOFF_1 0x03 /* lowest freq */ +/* Note: IA{1,2} is interrupt activity {1,2} or interrupt generators */ /* user provided functions, init and deinit can be set to NULL and won't be used */ struct lis3dh_device { @@ -122,6 +123,7 @@ struct lis3dh_int_config { uint8_t yl; /* interrupt generation on Y low event / Dir. recog. */ uint8_t xh; /* interrupt generation on X high event / Dir. recog. */ uint8_t xl; /* interrupt generation on X low event / Dir. recog. */ + uint8_t latch; /* active until INT1_SRC/INT2_SRC is read */ }; /* config for INT2 trigger output */ @@ -132,7 +134,6 @@ struct lis3dh_int_pin2_config { uint8_t boot; /* enable BOOT on pin 2 */ uint8_t act; /* interrupt on activity */ uint8_t polarity; /* INT1 & INT2 polarity. 0 active high, 1 active low */ - uint8_t latch; /* active until INT2_SRC read (reg5:1) */ }; /* config for INT1 trigger output */ @@ -144,7 +145,6 @@ struct lis3dh_int_pin1_config { uint8_t drdy_321; /* not sure */ uint8_t wtm; /* FIFO reached watermark level */ uint8_t overrun; /* FIFO has overrun */ - uint8_t latch; /* active until INT1_SRC read (reg5:0) */ }; /* config for high-pass filter */