altos: Add ADXL375 driver [v2]
authorKeith Packard <keithp@keithp.com>
Sat, 13 Oct 2018 16:41:31 +0000 (09:41 -0700)
committerKeith Packard <keithp@keithp.com>
Fri, 19 Oct 2018 15:38:32 +0000 (08:38 -0700)
Includes self-test code and multi-byte mode operation for reading
sample registers.

Signed-off-by: Keith Packard <keithp@keithp.com>
src/drivers/ao_adxl375.c [new file with mode: 0644]
src/drivers/ao_adxl375.h [new file with mode: 0644]
src/kernel/ao_data.h

diff --git a/src/drivers/ao_adxl375.c b/src/drivers/ao_adxl375.c
new file mode 100644 (file)
index 0000000..e0d094f
--- /dev/null
@@ -0,0 +1,263 @@
+/*
+ * Copyright © 2018 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#include <ao.h>
+#include "ao_adxl375.h"
+
+#define DEBUG_LOW      1
+#define DEBUG_HIGH     2
+
+#define DEBUG          0
+
+#if DEBUG
+#define PRINTD(l, ...) do { if (DEBUG & (l)) { printf ("\r%5u %s: ", ao_tick_count, __func__); printf(__VA_ARGS__); flush(); } } while(0)
+#else
+#define PRINTD(l,...)
+#endif
+
+struct ao_adxl375_sample       ao_adxl375_current;
+
+static void
+ao_adxl375_start(void) {
+       ao_spi_get_bit(AO_ADXL375_CS_PORT,
+                      AO_ADXL375_CS_PIN,
+                      AO_ADXL375_SPI_INDEX,
+                      AO_ADXL375_SPI_SPEED);
+}
+
+static void
+ao_adxl375_stop(void) {
+       ao_spi_put_bit(AO_ADXL375_CS_PORT,
+                      AO_ADXL375_CS_PIN,
+                      AO_ADXL375_SPI_INDEX);
+}
+
+static uint8_t
+ao_adxl375_reg_read(uint8_t addr)
+{
+       uint8_t d[2];
+
+       d[0] = addr | AO_ADXL375_READ;
+       ao_adxl375_start();
+       ao_spi_duplex(d, d, 2, AO_ADXL375_SPI_INDEX);
+       ao_adxl375_stop();
+
+       PRINTD(DEBUG_LOW, "read %x = %x\n", addr, d);
+
+       return d[1];
+}
+
+static void
+ao_adxl375_reg_write(uint8_t addr, uint8_t value)
+{
+       uint8_t d[2];
+
+       PRINTD(DEBUG_LOW, "write %x %x\n", addr, value);
+       d[0] = addr;
+       d[1] = value;
+       ao_adxl375_start();
+       ao_spi_send(d, 2, AO_ADXL375_SPI_INDEX);
+       ao_adxl375_stop();
+
+#if DEBUG & DEBUG_LOW
+       d[0] = addr | AO_ADXL375_READ
+       d[1] = 0;
+       ao_adxl375_start();
+       ao_spi_duplex(d, d, 2, AO_ADXL375_SPI_INDEX);
+       ao_adxl375_stop();
+       PRINTD(DEBUG_LOW, "readback %x %x\n", d[0], d[1]);
+#endif
+}
+
+static void
+ao_adxl375_value(struct ao_adxl375_sample *value)
+{
+       uint8_t d[7];
+
+       d[0] = AO_ADXL375_DATAX0 | AO_ADXL375_READ | AO_ADXL375_MULTI_BYTE;
+       ao_adxl375_start();
+       ao_spi_duplex(d, d, 7, AO_ADXL375_SPI_INDEX);
+       ao_adxl375_stop();
+       memcpy(value, &d[1], 6);
+}
+
+struct ao_adxl375_total {
+       int32_t x;
+       int32_t y;
+       int32_t z;
+};
+
+#define AO_ADXL375_SELF_TEST_SAMPLES   16
+#define AO_ADXL375_SELF_TEST_SETTLE    6
+
+#define MIN_LSB_G      18.4
+#define MAX_LSB_G      22.6
+#define SELF_TEST_MIN_G        6.0
+#define SELF_TEST_MAX_G        6.8
+
+#define MIN_SELF_TEST  ((int32_t) (MIN_LSB_G * SELF_TEST_MIN_G * AO_ADXL375_SELF_TEST_SAMPLES + 0.5))
+#define MAX_SELF_TEST  ((int32_t) (MAX_LSB_G * SELF_TEST_MAX_G * AO_ADXL375_SELF_TEST_SAMPLES + 0.5))
+
+static const int32_t   min_self_test = MIN_SELF_TEST;
+static const int32_t   max_self_test = MAX_SELF_TEST;
+
+static void
+ao_adxl375_total_value(struct ao_adxl375_total *total, int samples)
+{
+       struct ao_adxl375_sample        value;
+
+       *total = (struct ao_adxl375_total) { 0, 0, 0 };
+       for (int i = 0; i < samples; i++) {
+               ao_adxl375_value(&value);
+               total->x += value.x;
+               total->y += value.y;
+               total->z += value.z;
+               ao_delay(AO_MS_TO_TICKS(10));
+       }
+}
+
+#define AO_ADXL375_DATA_FORMAT_SETTINGS(self_test) (                   \
+               AO_ADXL375_DATA_FORMAT_FIXED |                          \
+               (self_test << AO_ADXL375_DATA_FORMAT_SELF_TEST) |       \
+               (AO_ADXL375_DATA_FORMAT_SPI_4_WIRE << AO_ADXL375_DATA_FORMAT_SPI_4_WIRE) | \
+               (0 << AO_ADXL375_DATA_FORMAT_INT_INVERT) |              \
+               (0 << AO_ADXL375_DATA_FORMAT_JUSTIFY))
+
+static int32_t self_test_value;
+
+static void
+ao_adxl375_setup(void)
+{
+       /* Get the device into 4-wire SPI mode before proceeding */
+       ao_adxl375_reg_write(AO_ADXL375_DATA_FORMAT,
+                            AO_ADXL375_DATA_FORMAT_SETTINGS(0));
+
+
+       uint8_t devid = ao_adxl375_reg_read(AO_ADXL375_DEVID);
+       if (devid != AO_ADXL375_DEVID_ID)
+               ao_sensor_errors = 1;
+
+       /* Set the data rate */
+       ao_adxl375_reg_write(AO_ADXL375_BW_RATE,
+                            (0 << AO_ADXL375_BW_RATE_LOW_POWER) |
+                            (AO_ADXL375_BW_RATE_RATE_200 << AO_ADXL375_BW_RATE_RATE));
+
+       /* Set the offsets all to zero */
+       ao_adxl375_reg_write(AO_ADXL375_OFSX, 0);
+       ao_adxl375_reg_write(AO_ADXL375_OFSY, 0);
+       ao_adxl375_reg_write(AO_ADXL375_OFSZ, 0);
+
+       /* Clear interrupts */
+       ao_adxl375_reg_write(AO_ADXL375_INT_ENABLE, 0);
+
+       /* Configure FIFO (disable) */
+       ao_adxl375_reg_write(AO_ADXL375_FIFO_CTL,
+                            (AO_ADXL375_FIFO_CTL_FIFO_MODE_BYPASS << AO_ADXL375_FIFO_CTL_FIFO_MODE) |
+                            (0 << AO_ADXL375_FIFO_CTL_TRIGGER) |
+                            (0 << AO_ADXL375_FIFO_CTL_SAMPLES));
+
+       /* Place part in measurement mode */
+       ao_adxl375_reg_write(AO_ADXL375_POWER_CTL,
+                            (0 << AO_ADXL375_POWER_CTL_LINK) |
+                            (0 << AO_ADXL375_POWER_CTL_AUTO_SLEEP) |
+                            (1 << AO_ADXL375_POWER_CTL_MEASURE) |
+                            (0 << AO_ADXL375_POWER_CTL_SLEEP) |
+                            (AO_ADXL375_POWER_CTL_WAKEUP_8 << AO_ADXL375_POWER_CTL_WAKEUP));
+
+       (void) ao_adxl375_total_value;
+       /* Perform self-test */
+
+#define AO_ADXL375_SELF_TEST_SAMPLES   16
+#define AO_ADXL375_SELF_TEST_SETTLE    6
+
+       struct ao_adxl375_total self_test_off, self_test_on;
+
+       /* Discard some samples to let it settle down */
+       ao_adxl375_total_value(&self_test_off, AO_ADXL375_SELF_TEST_SETTLE);
+
+       /* Get regular values */
+       ao_adxl375_total_value(&self_test_off, AO_ADXL375_SELF_TEST_SAMPLES);
+
+       /* Turn on self test */
+       ao_adxl375_reg_write(AO_ADXL375_DATA_FORMAT,
+                            AO_ADXL375_DATA_FORMAT_SETTINGS(1));
+
+       /* Discard at least 4 samples to let the device settle */
+       ao_adxl375_total_value(&self_test_on, AO_ADXL375_SELF_TEST_SETTLE);
+
+       /* Read self test samples */
+       ao_adxl375_total_value(&self_test_on, AO_ADXL375_SELF_TEST_SAMPLES);
+
+       /* Reset back to normal mode */
+
+       ao_adxl375_reg_write(AO_ADXL375_DATA_FORMAT,
+                            AO_ADXL375_DATA_FORMAT_SETTINGS(0));
+
+       /* Verify Z axis value is in range */
+
+       int32_t z_change = self_test_on.z - self_test_off.z;
+
+       self_test_value = z_change;
+
+       if (z_change < min_self_test || max_self_test < z_change)
+               ao_sensor_errors = 1;
+
+       /* Discard some samples to let it settle down */
+       ao_adxl375_total_value(&self_test_off, AO_ADXL375_SELF_TEST_SETTLE);
+}
+
+static int     adxl375_count;
+
+static void
+ao_adxl375(void)
+{
+       ao_adxl375_setup();
+       for (;;) {
+               ao_adxl375_value(&ao_adxl375_current);
+               ++adxl375_count;
+               ao_arch_critical(
+                       AO_DATA_PRESENT(AO_DATA_ADXL375);
+                       AO_DATA_WAIT();
+                       );
+       }
+}
+
+static struct ao_task ao_adxl375_task;
+
+static void
+ao_adxl375_dump(void)
+{
+       printf ("ADXL375 value %d %d %d count %d self test %d min %d max %d\n",
+               ao_adxl375_current.x,
+               ao_adxl375_current.y,
+               ao_adxl375_current.z,
+               adxl375_count,
+               self_test_value,
+               MIN_SELF_TEST,
+               MAX_SELF_TEST);
+}
+
+const struct ao_cmds ao_adxl375_cmds[] = {
+       { ao_adxl375_dump,      "A\0Display ADXL375 data" },
+       { 0, NULL },
+};
+
+void
+ao_adxl375_init(void)
+{
+       ao_cmd_register(ao_adxl375_cmds);
+       ao_spi_init_cs(AO_ADXL375_CS_PORT, (1 << AO_ADXL375_CS_PIN));
+
+       ao_add_task(&ao_adxl375_task, ao_adxl375, "adxl375");
+}
diff --git a/src/drivers/ao_adxl375.h b/src/drivers/ao_adxl375.h
new file mode 100644 (file)
index 0000000..a1ed216
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+ * Copyright © 2018 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#ifndef _AO_ADXL375_H_
+#define _AO_ADXL375_H_
+
+#define AO_ADXL375_READ                        0x80    /* read mode */
+#define AO_ADXL375_MULTI_BYTE          0x40    /* multi-byte mode */
+
+#define AO_ADXL375_DEVID               0x00
+#define  AO_ADXL375_DEVID_ID                   0xe5
+#define AO_ADXL375_THRESH_SHOCK                0x1d
+#define AO_ADXL375_OFSX                        0x1e
+#define AO_ADXL375_OFSY                        0x1f
+#define AO_ADXL375_OFSZ                        0x20
+#define AO_ADXL375_DUR                 0x21
+#define AO_ADXL375_LATENT              0x22
+#define AO_ADXL375_WINDOW              0x23
+#define AO_ADXL375_THRESH_ACT          0x24
+#define AO_ADXL375_THRESH_INACT                0x25
+#define AO_ADXL375_TIME_INACT          0x26
+#define AO_ADXL375_ACT_INACT_CTL       0x27
+#define AO_ADXL375_SHOCK_AXES          0x2a
+#define AO_ADXL375_ACT_SHOCK_STATUS    0x2b
+#define AO_ADXL375_BW_RATE             0x2c
+
+#define  AO_ADXL375_BW_RATE_LOW_POWER          4
+#define  AO_ADXL375_BW_RATE_RATE               0
+#define   AO_ADXL375_BW_RATE_RATE_3200         0xf
+#define   AO_ADXL375_BW_RATE_RATE_1600         0xe
+#define   AO_ADXL375_BW_RATE_RATE_800          0xd
+#define   AO_ADXL375_BW_RATE_RATE_400          0xc
+#define   AO_ADXL375_BW_RATE_RATE_200          0xb
+#define   AO_ADXL375_BW_RATE_RATE_100          0xa
+#define   AO_ADXL375_BW_RATE_RATE_50           0x9
+#define   AO_ADXL375_BW_RATE_RATE_25           0x8
+#define   AO_ADXL375_BW_RATE_RATE_12_5         0x7
+#define   AO_ADXL375_BW_RATE_RATE_6_25         0x6
+#define   AO_ADXL375_BW_RATE_RATE_3_13         0x5
+#define   AO_ADXL375_BW_RATE_RATE_1_56         0x4
+#define   AO_ADXL375_BW_RATE_RATE_0_78         0x3
+#define   AO_ADXL375_BW_RATE_RATE_0_39         0x2
+#define   AO_ADXL375_BW_RATE_RATE_0_20         0x1
+#define   AO_ADXL375_BW_RATE_RATE_0_10         0x0
+
+#define AO_ADXL375_POWER_CTL           0x2d
+#define  AO_ADXL375_POWER_CTL_LINK             5
+#define  AO_ADXL375_POWER_CTL_AUTO_SLEEP       4
+#define  AO_ADXL375_POWER_CTL_MEASURE          3
+#define  AO_ADXL375_POWER_CTL_SLEEP            2
+#define  AO_ADXL375_POWER_CTL_WAKEUP           0
+#define  AO_ADXL375_POWER_CTL_WAKEUP_8                 0
+#define  AO_ADXL375_POWER_CTL_WAKEUP_4                 1
+#define  AO_ADXL375_POWER_CTL_WAKEUP_2                 2
+#define  AO_ADXL375_POWER_CTL_WAKEUP_1                 3
+
+#define AO_ADXL375_INT_ENABLE          0x2e
+#define AO_ADXL375_INT_MAP             0x2f
+#define AO_ADXL375_INT_SOURCE          0x30
+#define AO_ADXL375_DATA_FORMAT         0x31
+# define AO_ADXL375_DATA_FORMAT_FIXED          0x0b    /* these bits must be set to 1 */
+# define AO_ADXL375_DATA_FORMAT_SELF_TEST      7
+# define AO_ADXL375_DATA_FORMAT_SPI            6
+# define  AO_ADXL375_DATA_FORMAT_SPI_3_WIRE            0
+# define  AO_ADXL375_DATA_FORMAT_SPI_4_WIRE            1
+# define AO_ADXL375_DATA_FORMAT_INT_INVERT     5
+# define AO_ADXL375_DATA_FORMAT_JUSTIFY                2
+#define AO_ADXL375_DATAX0              0x32
+#define AO_ADXL375_DATAX1              0x33
+#define AO_ADXL375_DATAY0              0x34
+#define AO_ADXL375_DATAY1              0x35
+#define AO_ADXL375_DATAZ0              0x36
+#define AO_ADXL375_DATAZ1              0x37
+#define AO_ADXL375_FIFO_CTL            0x38
+#define  AO_ADXL375_FIFO_CTL_FIFO_MODE         6
+#define   AO_ADXL375_FIFO_CTL_FIFO_MODE_BYPASS         0
+#define   AO_ADXL375_FIFO_CTL_FIFO_MODE_FIFO           1
+#define   AO_ADXL375_FIFO_CTL_FIFO_MODE_STREAM         2
+#define   AO_ADXL375_FIFO_CTL_FIFO_MODE_TRIGGER                3
+#define  AO_ADXL375_FIFO_CTL_TRIGGER           5
+#define  AO_ADXL375_FIFO_CTL_SAMPLES           0
+
+#define AO_ADXL375_FIFO_STATUS         0x39
+
+struct ao_adxl375_sample {
+       int16_t x;
+       int16_t y;
+       int16_t z;
+};
+
+extern struct ao_adxl375_sample        ao_adxl375_current;
+
+void
+ao_adxl375_init(void);
+
+#endif /* _AO_ADXL375_H_ */
index 97f7e06e5669943c99266df66c63e497d2a4f6fd..3918f4e04e4b9cf6931fcb5aaa85ad50695bc083 100644 (file)
 #define AO_DATA_MMA655X 0
 #endif
 
 #define AO_DATA_MMA655X 0
 #endif
 
+#if HAS_ADXL375
+#include <ao_adxl375.h>
+#define AO_DATA_ADXL375 (1 << 4)
+#else
+#define AO_DATA_ADXL375 0
+#endif
+
 #ifdef AO_DATA_RING
 
 #ifdef AO_DATA_RING
 
-#define AO_DATA_ALL    (AO_DATA_ADC|AO_DATA_MS5607|AO_DATA_MPU6000|AO_DATA_HMC5883|AO_DATA_MMA655X|AO_DATA_MPU9250)
+#define AO_DATA_ALL    (AO_DATA_ADC|AO_DATA_MS5607|AO_DATA_MPU6000|AO_DATA_HMC5883|AO_DATA_MMA655X|AO_DATA_MPU9250|AO_DATA_ADXL375)
 
 struct ao_data {
        uint16_t                        tick;
 
 struct ao_data {
        uint16_t                        tick;
@@ -90,6 +97,9 @@ struct ao_data {
 #if HAS_MMA655X
        uint16_t                        mma655x;
 #endif
 #if HAS_MMA655X
        uint16_t                        mma655x;
 #endif
+#if HAS_ADXL375
+       struct ao_adxl375_sample        adxl375;
+#endif
 };
 
 #define ao_data_ring_next(n)   (((n) + 1) & (AO_DATA_RING - 1))
 };
 
 #define ao_data_ring_next(n)   (((n) + 1) & (AO_DATA_RING - 1))
@@ -293,6 +303,27 @@ typedef int16_t accel_t;
 
 #endif
 
 
 #endif
 
+#if !HAS_ACCEL && HAS_ADXL375
+
+#define HAS_ACCEL      1
+
+typedef int16_t        accel_t;
+
+#ifndef AO_ADXL375_INVERT
+#error AO_ADXL375_INVERT not defined
+#endif
+
+#define ao_data_accel(packet)                  ((packet)->adxl375.AO_ADXL375_AXIS)
+#if AO_ADXL375_INVERT
+#define ao_data_accel_cook(packet)             (-ao_data_accel(packet))
+#else
+#define ao_data_accel_cook(packet)             ao_data_accel(packet)
+#endif
+#define ao_data_set_accel(packet, accel)       (ao_data_accel(packet) = (accel))
+#define ao_data_accel_invert(accel)            (-(accel))
+
+#endif /* HAS_ADXL375 */
+
 #if !HAS_ACCEL && HAS_MPU6000
 
 #define HAS_ACCEL      1
 #if !HAS_ACCEL && HAS_MPU6000
 
 #define HAS_ACCEL      1
@@ -419,6 +450,9 @@ ao_data_fill(int head) {
 #endif
 #if HAS_MPU9250
                ao_data_ring[head].mpu9250 = ao_mpu9250_current;
 #endif
 #if HAS_MPU9250
                ao_data_ring[head].mpu9250 = ao_mpu9250_current;
+#endif
+#if HAS_ADXL375
+               ao_data_ring[head].adxl375 = ao_adxl375_current;
 #endif
                ao_data_ring[head].tick = ao_tick_count;
                ao_data_head = ao_data_ring_next(head);
 #endif
                ao_data_ring[head].tick = ao_tick_count;
                ao_data_head = ao_data_ring_next(head);