diff --git a/drivers/sensors/Kconfig b/drivers/sensors/Kconfig index 4282d57c993..ac5dcff391d 100644 --- a/drivers/sensors/Kconfig +++ b/drivers/sensors/Kconfig @@ -431,6 +431,48 @@ config SENSORS_ADXL372 ---help--- Enable driver support for the Analog Devices ADXL372 Sensor. +config SENSORS_MPU60X0 + bool "Invensense MPU60x0 Sensor support" + default n + ---help--- + Enable driver support for Invensense MPU60x0 MotionTracker(tm) device. + +if SENSORS_MPU60X0 + +choice + prompt "MPU60x0 Interface" + default MPU60X0_SPI + +config MPU60X0_SPI + bool "MPU6000 SPI Interface" + select SPI + ---help--- + Enables support for the SPI interface (MPU6000 only) + +config MPU60X0_I2C + bool "MPU60x0 I2C Interface" + select I2C + ---help--- + Enables support for the I2C interface (MPU6000 + or MPU6050, autodetected during driver initialization) + +endchoice + +config MPU60X0_EXTI + bool "Enable interrupts" + default n + ---help--- + Select this when the MPU60x0's INT pin is connected to an + interrupt line on the host processor, and identify the pin + during device registration. The driver will use interrupts to + manage FIFO-full and/or sample-ready events, depending on how + the device is being used. The interface will block until the + next sample is ready, which will naturally synchronize the user + to the sampling rate chosen during operation. + Default: No interrupts or blocking, i.e. user-driven sampling. + +endif # SENSORS_MPU60X0 + config SENSORS_MAX44009 bool "Maxim MAX44009 ALS sensor" default n diff --git a/drivers/sensors/Make.defs b/drivers/sensors/Make.defs index b1f0968ef96..bd0f730a5fb 100644 --- a/drivers/sensors/Make.defs +++ b/drivers/sensors/Make.defs @@ -182,6 +182,7 @@ endif # CONFIG_I2C # These drivers depend on SPI support ifeq ($(CONFIG_SPI),y) + ifeq ($(CONFIG_ADXL345_SPI),y) CSRCS += adxl345_spi.c endif @@ -198,6 +199,10 @@ ifeq ($(CONFIG_LIS3DH),y) CSRCS += lis3dh.c endif +ifeq ($(CONFIG_SENSORS_MPU60X0),y) + CSRCS += mpu60x0.c +endif + ifeq ($(CONFIG_SENSORS_MAX31855),y) CSRCS += max31855.c endif diff --git a/drivers/sensors/mpu60x0.c b/drivers/sensors/mpu60x0.c new file mode 100644 index 00000000000..7cfa7691008 --- /dev/null +++ b/drivers/sensors/mpu60x0.c @@ -0,0 +1,962 @@ +/**************************************************************************** + * drivers/sensors/mpu60x0.c + * + * Support for the Invensense MPU6000 and MPU6050 MotionTracking(tm) + * 6-axis accelerometer and gyroscope. + * + * Copyright (C) 2019 Bill Gatliff. All rights reserved. + * Author: Bill Gatliff + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright+ + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + *****************************************************************************/ + +/**************************************************************************** + * TODO: Theory of Operation + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Sets bit @n */ + +#define BIT(n) (1 << (n)) + +/* Creates a mask of @m bits, i.e. MASK(2) -> 00000011 */ + +#define MASK(m) (BIT((m) + 1) - 1) + +/* Masks and shifts @v into bit field @m */ + +#define TO_BITFIELD(m,v) ((v) & MASK(m ##__WIDTH) << (m ##__SHIFT)) + +/* Un-masks and un-shifts bit field @m from @v */ + +#define FROM_BITFIELD(m,v) (((v) >> (m ##__SHIFT)) & MASK(m ##__WIDTH)) + +/* SPI read/write codes */ + +#define MPU_REG_READ 0x80 +#define MPU_REG_WRITE 0 + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +enum mpu_regaddr_e +{ + SELF_TEST_X = 0x0d, + SELF_TEST_Y = 0x0e, + SELF_TEST_Z = 0x0f, + SELF_TEST_A = 0x10, + SMPLRT_DIV = 0x19, + + /* __SHIFT : number of empty bits to the right of the field + * __WIDTH : width of the field, in bits + * + * single-bit fields don't have __SHIFT or __mask + */ + + CONFIG = 0x1a, + CONFIG__EXT_SYNC_SET__SHIFT = 3, + CONFIG__EXT_SYNC_SET__WIDTH = 2, + CONFIG__DLPF_CFG__SHIFT = 0, + CONFIG__DLPF_CFG__WIDTH = 2, + + GYRO_CONFIG = 0x1b, + GYRO_CONFIG__XG_ST = BIT(7), + GYRO_CONFIG__YG_ST = BIT(6), + GYRO_CONFIG__ZG_ST = BIT(5), + GYRO_CONFIG__FS_SEL__SHIFT = 3, + GYRO_CONFIG__FS_SEL__WIDTH = 2, + + ACCEL_CONFIG = 0x1c, + ACCEL_CONFIG__XA_ST = BIT(7), + ACCEL_CONFIG__YA_ST = BIT(6), + ACCEL_CONFIG__ZA_ST = BIT(5), + ACCEL_CONFIG__AFS_SEL__SHIFT = 3, + ACCEL_CONFIG__AFS_SEL__WIDTH = 2, + + MOT_THR = 0x1f, + FIFO_EN = 0x23, + I2C_MST_CTRL = 0x24, + I2C_SLV0_ADDR = 0x25, + I2C_SLV0_REG = 0x26, + I2C_SLV0_CTRL = 0x27, + I2C_SLV1_ADDR = 0x28, + I2C_SLV1_REG = 0x29, + I2C_SLV1_CTRL = 0x2a, + I2C_SLV2_ADDR = 0x2b, + I2C_SLV2_REG = 0x2c, + I2C_SLV2_CTRL = 0x2d, + I2C_SLV3_ADDR = 0x2e, + I2C_SLV3_REG = 0x2f, + I2C_SLV3_CTRL = 0x30, + I2C_SLV4_ADDR = 0x31, + I2C_SLV4_REG = 0x32, + I2C_SLV4_DO = 0x33, + I2C_SLV4_CTRL = 0x34, + I2C_SLV4_DI = 0x35, /* RO */ + I2C_MST_STATUS = 0x36, /* RO */ + + INT_PIN_CFG = 0x37, + INT_PIN_CFG__INT_LEVEL = BIT(7), + INT_PIN_CFG__INT_OPEN = BIT(6), + INT_PIN_CFG__LATCH_INT_EN = BIT(5), + INT_PIN_CFG__INT_RD_CLEAR = BIT(4), + INT_PIN_CFG__FSYNC_INT_LEVEL = BIT(3), + INT_PIN_CFG__FSYNC_INT_EN = BIT(2), + INT_PIN_CFG__I2C_BYPASS_EN = BIT(1), + + INT_ENABLE = 0x38, + INT_STATUS = 0x3a, /* RO */ + + ACCEL_XOUT_H = 0x3b, /* RO */ + ACCEL_XOUT_L = 0x3c, /* RO */ + ACCEL_YOUT_H = 0x3d, /* RO */ + ACCEL_YOUT_L = 0x3e, /* RO */ + ACCEL_ZOUT_H = 0x3f, /* RO */ + ACCEL_ZOUT_L = 0x40, /* RO */ + TEMP_OUT_H = 0x41, /* RO */ + TEMP_OUT_L = 0x42, /* RO */ + GYRO_XOUT_H = 0x43, /* RO */ + GYRO_XOUT_L = 0x44, /* RO */ + GYRO_YOUT_H = 0x45, /* RO */ + GYRO_YOUT_L = 0x46, /* RO */ + GYRO_ZOUT_H = 0x47, /* RO */ + GYRO_ZOUT_L = 0x48, /* RO */ + + EXT_SENS_DATA_00 = 0x49, /* RO */ + EXT_SENS_DATA_01 = 0x4a, /* RO */ + EXT_SENS_DATA_02 = 0x4b, /* RO */ + EXT_SENS_DATA_03 = 0x4c, /* RO */ + EXT_SENS_DATA_04 = 0x4d, /* RO */ + EXT_SENS_DATA_05 = 0x4e, /* RO */ + EXT_SENS_DATA_06 = 0x4f, /* RO */ + EXT_SENS_DATA_07 = 0x50, /* RO */ + EXT_SENS_DATA_08 = 0x51, /* RO */ + EXT_SENS_DATA_09 = 0x52, /* RO */ + EXT_SENS_DATA_10 = 0x53, /* RO */ + EXT_SENS_DATA_11 = 0x54, /* RO */ + EXT_SENS_DATA_12 = 0x55, /* RO */ + EXT_SENS_DATA_13 = 0x56, /* RO */ + EXT_SENS_DATA_14 = 0x57, /* RO */ + EXT_SENS_DATA_15 = 0x58, /* RO */ + EXT_SENS_DATA_16 = 0x59, /* RO */ + EXT_SENS_DATA_17 = 0x5a, /* RO */ + EXT_SENS_DATA_18 = 0x5b, /* RO */ + EXT_SENS_DATA_19 = 0x5c, /* RO */ + EXT_SENS_DATA_20 = 0x5d, /* RO */ + EXT_SENS_DATA_21 = 0x5e, /* RO */ + EXT_SENS_DATA_22 = 0x5f, /* RO */ + EXT_SENS_DATA_23 = 0x60, /* RO */ + + I2C_SLV0_DO = 0x63, + I2C_SLV1_DO = 0x64, + I2C_SLV2_DO = 0x65, + I2C_SLV3_DO = 0x66, + I2C_MST_DELAY_CTRL = 0x67, + + SIGNAL_PATH_RESET = 0x68, + SIGNAL_PATH_RESET__GYRO_RESET = BIT(2), + SIGNAL_PATH_RESET__ACCEL_RESET = BIT(1), + SIGNAL_PATH_RESET__TEMP_RESET = BIT(0), + SIGNAL_PATH_RESET__ALL_RESET = BIT(3) - 1, + + MOT_DETECT_CTRL = 0x69, + + USER_CTRL = 0x6a, + USER_CTRL__FIFO_EN = BIT(6), + USER_CTRL__I2C_MST_EN = BIT(5), + USER_CTRL__I2C_IF_DIS = BIT(4), + USER_CTRL__FIFO_RESET = BIT(2), + USER_CTRL__I2C_MST_RESET = BIT(1), + USER_CTRL__SIG_COND_RESET = BIT(0), + + PWR_MGMT_1 = 0x6b, /* Reset: 0x40 */ + PWR_MGMT_1__DEVICE_RESET = BIT(7), + PWR_MGMT_1__SLEEP = BIT(6), + PWR_MGMT_1__CYCLE = BIT(5), + PWR_MGMT_1__TEMP_DIS = BIT(3), + PWR_MGMT_1__CLK_SEL__SHIFT = 0, + PWR_MGMT_1__CLK_SEL__WIDTH = 3, + + PWR_MGMT_2 = 0x6c, + FIFO_COUNTH = 0x72, + FIFO_COUNTL = 0x73, + FIFO_R_W = 0x74, + WHO_AM_I = 0x75, /* RO reset: 0x68 */ +}; + +/* Describes the mpu60x0 sensor register file. This structure reflects + * the underlying hardware, so don't change it! + */ + +begin_packed_struct struct sensor_data_s +{ + int16_t x_accel; + int16_t y_accel; + int16_t z_accel; + int16_t temp; + int16_t x_gyro; + int16_t y_gyro; + int16_t z_gyro; +} end_packed_struct; + +/* Used by the driver to manage the device */ + +struct mpu_dev_s +{ + mutex_t lock; /* mutex for this structure */ + struct mpu_config_s config; /* board-specific information */ + + struct sensor_data_s buf; /* temporary buffer (for read(), etc.) */ + size_t bufpos; /* cursor into @buf, in bytes (!) */ +}; + +/**************************************************************************** + * Private Function Function Prototypes + ****************************************************************************/ + +static int mpu_open(FAR struct file *filep); +static int mpu_close(FAR struct file *filep); +static ssize_t mpu_read(FAR struct file *filep, FAR char *buf, size_t len); +static ssize_t mpu_write(FAR struct file *filep, FAR const char *buf, + size_t len); +static off_t mpu_seek(FAR struct file *filep, off_t offset, int whence); +static int mpu_ioctl(FAR struct file *filep, int cmd, unsigned long arg); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct file_operations g_mpu_fops = +{ + mpu_open, + mpu_close, + mpu_read, + mpu_write, + mpu_seek, + mpu_ioctl +#ifndef CONFIG_DISABLE_POLL + , NULL +#endif +#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS + , NULL +#endif +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/* NOTE : + * + * In all of the following code, functions named with a double leading + * underscore '__' must be invoked ONLY if the mpu_dev_s lock is + * already held. Failure to do this might cause the transaction to get + * interrupted, which will likely confuse the data you get back. + * + * The mpu_dev_s lock is NOT the same thing as, i.e. the SPI master + * interface lock: the latter protects the bus interface hardware + * (which may have other SPI devices attached), the former protects + * the chip and its associated data. + */ + +#ifdef CONFIG_SPI +/* __mpu_read_reg(), but for spi-connected devices. See that function + * for documentation. + */ + +static int __mpu_read_reg_spi(FAR struct mpu_dev_s *dev, + enum mpu_regaddr_e reg_addr, + FAR uint8_t *buf, uint8_t len) +{ + int ret; + FAR struct spi_dev_s *spi = dev->config.spi; + int id = dev->config.spi_devid; + + /* We'll probably return the number of bytes asked for. */ + + ret = len; + + /* Grab and configure the SPI master device: always mode 0, 20MHz if it's a + * data register, 1MHz otherwise (per datasheet). + */ + + SPI_LOCK(spi, true); + SPI_SETMODE(spi, SPIDEV_MODE0); + + if ((reg_addr >= ACCEL_XOUT_H) && ((reg_addr + len) <= I2C_SLV0_DO)) + { + SPI_SETFREQUENCY(spi, 20000000); + } + else + { + SPI_SETFREQUENCY(spi, 1000000); + } + + /* Select the chip. */ + + SPI_SELECT(spi, id, true); + + /* Send the read request. */ + + SPI_SEND(spi, reg_addr | MPU_REG_READ); + + /* Clock in the data. */ + + while (0 != len--) + { + *buf++ = (uint8_t) (SPI_SEND(spi, 0xff)); + } + + /* Deselect the chip, release the SPI master. */ + + SPI_SELECT(spi, id, false); + SPI_LOCK(spi, false); + + return ret; +} + +/* __mpu_write_reg(), but for SPI connections. */ + +static int __mpu_write_reg_spi(FAR struct mpu_dev_s *dev, + enum mpu_regaddr_e reg_addr, + FAR const uint8_t * buf, uint8_t len) +{ + int ret; + FAR struct spi_dev_s *spi = dev->config.spi; + int id = dev->config.spi_devid; + + /* Hopefully, we'll return all the bytes they're asking for. */ + + ret = len; + + /* Grab and configure the SPI master device. */ + + SPI_LOCK(spi, true); + SPI_SETMODE(spi, SPIDEV_MODE0); + SPI_SETFREQUENCY(spi, 1000000); + + /* Select the chip. */ + + SPI_SELECT(spi, id, true); + + /* Send the write request. */ + + SPI_SEND(spi, reg_addr | MPU_REG_WRITE); + + /* Send the data. */ + + while (0 != len--) + { + SPI_SEND(spi, *buf++); + } + + /* Release the chip and SPI master. */ + + SPI_SELECT(spi, id, false); + SPI_LOCK(spi, false); + + return ret; +} +#endif + +#ifdef CONFIG_I2C + +/* __mpu_read_reg(), but for i2c-connected devices. */ + +static int __mpu_read_reg_i2c(FAR struct mpu_dev_s *dev, + enum mpu_regaddr_e reg_addr, + FAR uint8_t *buf, uint8_t len) +{ + /* We don't support i2c yet. */ + + return -EINVAL; +} + +static int __mpu_write_reg_i2c(FAR struct mpu_dev_s *dev, + enum mpu_regaddr_e reg_addr, + FAR const uint8_t *buf, uint8_t len) +{ + /* We don't support i2c yet. */ + + return -EINVAL; +} +#endif + +/* __mpu_read_reg() + * + * Reads a block of @len byte-wide registers, starting at @reg_addr, + * from the device connected to @dev. Bytes are returned in @buf, + * which must have a capacity of at least @len bytes. + * + * Note: The caller must hold @dev->lock before calling this function. + * + * Returns number of bytes read, or a negative errno. + */ + +static inline int __mpu_read_reg(FAR struct mpu_dev_s *dev, + enum mpu_regaddr_e reg_addr, + FAR uint8_t *buf, uint8_t len) +{ +#ifdef CONFIG_SPI + /* If we're wired to SPI, use that function. */ + + if (dev->config.spi != NULL) + { + return __mpu_read_reg_spi(dev, reg_addr, buf, len); + } +#endif + +#ifdef CONFIG_I2C + /* If we're wired to I2C, use that function. */ + + if (dev->config.i2c != NULL) + { + return __mpu_read_reg_i2c(dev, reg_addr, buf, len); + } +#endif + + /* If we get this far, it's because we can't "find" our device. */ + + return -ENODEV; +} + +/* __mpu_write_reg() + * + * Writes a block of @len byte-wide registers, starting at @reg_addr, + * using the values in @buf to the device connected to @dev. Register + * values are taken in numerical order from @buf, i.e.: + * + * buf[0] -> register[@reg_addr] + * buf[1] -> register[@reg_addr + 1] + * ... + * + * Note: The caller must hold @dev->lock before calling this function. + * + * Returns number of bytes written, or a negative errno. + */ + +static inline int __mpu_write_reg(FAR struct mpu_dev_s *dev, + enum mpu_regaddr_e reg_addr, + FAR const uint8_t *buf, uint8_t len) +{ +#ifdef CONFIG_SPI + /* If we're connected to SPI, use that function. */ + + if (dev->config.spi != NULL) + { + return __mpu_write_reg_spi(dev, reg_addr, buf, len); + } +#endif + +#ifdef CONFIG_I2C + if (dev->config.i2c != NULL) + { + return __mpu_write_reg_i2c(dev, reg_addr, buf, len); + } +#endif + + /* If we get this far, it's because we can't "find" our device. */ + + return -ENODEV; +} + +/* __mpu_read_imu() + * + * Reads the whole IMU data file from @dev in one uninterrupted pass, + * placing the sampled values into @buf. This function is the only way + * to guarantee that the measured values are sampled as closely-spaced + * in time as the hardware permits, which is almost always what you + * want. + */ + +static inline int __mpu_read_imu(FAR struct mpu_dev_s *dev, + FAR struct sensor_data_s *buf) +{ + return __mpu_read_reg(dev, ACCEL_XOUT_H, (uint8_t *) buf, sizeof(*buf)); +} + +/* __mpu_read_pwr_mgmt_1() + * + * Returns the value of the PWR_MGMT_1 register from @dev. + */ + +static inline uint8_t __mpu_read_pwr_mgmt_1(FAR struct mpu_dev_s *dev) +{ + uint8_t buf = 0xff; + __mpu_read_reg(dev, PWR_MGMT_1, &buf, sizeof(buf)); + return buf; +} + +static inline int __mpu_write_signal_path_reset(FAR struct mpu_dev_s *dev, + uint8_t val) +{ + return __mpu_write_reg(dev, SIGNAL_PATH_RESET, &val, sizeof(val)); +} + +static inline int __mpu_write_int_pin_cfg(FAR struct mpu_dev_s *dev, + uint8_t val) +{ + return __mpu_write_reg(dev, INT_PIN_CFG, &val, sizeof(val)); +} + +static inline int __mpu_write_pwr_mgmt_1(FAR struct mpu_dev_s *dev, + uint8_t val) +{ + return __mpu_write_reg(dev, PWR_MGMT_1, &val, sizeof(val)); +} + +static inline int __mpu_write_pwr_mgmt_2(FAR struct mpu_dev_s *dev, + uint8_t val) +{ + return __mpu_write_reg(dev, PWR_MGMT_2, &val, sizeof(val)); +} + +static inline int __mpu_write_user_ctrl(FAR struct mpu_dev_s *dev, + uint8_t val) +{ + return __mpu_write_reg(dev, USER_CTRL, &val, sizeof(val)); +} + +/* __mpu_write_gyro_config() : + * + * Sets the @fs_sel bit in GYRO_CONFIG to the value provided. Per the + * datasheet, the meaning of @fs_sel is as follows: + * + * GYRO_CONFIG(0x1b) : XG_ST YG_ST ZG_ST FS_SEL1 FS_SEL0 x x x + * + * XG_ST, YG_ST, ZG_ST : self-test (unsupported in this driver) + * 1 -> activate self-test on X, Y, and/or Z gyros + * + * FS_SEL[10] : full-scale range select + * 0 -> ± 250 deg/sec + * 1 -> ± 500 deg/sec + * 2 -> ± 1000 deg/sec + * 3 -> ± 2000 deg/sec + */ + +static inline int __mpu_write_gyro_config(FAR struct mpu_dev_s *dev, + uint8_t fs_sel) +{ + uint8_t val = TO_BITFIELD(GYRO_CONFIG__FS_SEL, fs_sel); + return __mpu_write_reg(dev, GYRO_CONFIG, &val, sizeof(val)); +} + +/* __mpu_write_accel_config() : + * + * Sets the @afs_sel bit in ACCEL_CONFIG to the value provided. Per + * the datasheet, the meaning of @afs_sel is as follows: + * + * ACCEL_CONFIG(0x1c) : XA_ST YA_ST ZA_ST AFS_SEL1 AFS_SEL0 x x x + * + * XA_ST, YA_ST, ZA_ST : self-test (unsupported in this driver) + * 1 -> activate self-test on X, Y, and/or Z accelerometers + * + * AFS_SEL[10] : full-scale range select + * 0 -> ± 2 g + * 1 -> ± 4 g + * 2 -> ± 8 g + * 3 -> ± 16 g + */ + +static inline int __mpu_write_accel_config(FAR struct mpu_dev_s *dev, + uint8_t afs_sel) +{ + uint8_t val = TO_BITFIELD(ACCEL_CONFIG__AFS_SEL, afs_sel); + return __mpu_write_reg(dev, ACCEL_CONFIG, &val, sizeof(val)); +} + +/* CONFIG (0x1a) : x x EXT_SYNC_SET[2..0] DLPF_CFG[2..0] + * + * EXT_SYNC_SET : frame sync bit position + * DLPF_CFG : digital low-pass filter bandwidth + * (see datasheet, it's ... complicated) + */ + +static inline int __mpu_write_config(FAR struct mpu_dev_s *dev, + uint8_t ext_sync_set, uint8_t dlpf_cfg) +{ + uint8_t val = TO_BITFIELD(CONFIG__EXT_SYNC_SET, ext_sync_set) | + TO_BITFIELD(CONFIG__DLPF_CFG, dlpf_cfg); + return __mpu_write_reg(dev, CONFIG, &val, sizeof(val)); +} + +/* WHO_AM_I (0x75) : read-only, always returns 0x68 for mpu60x0 */ + +static inline uint8_t __mpu_read_who_am_i(FAR struct mpu_dev_s *dev) +{ + uint8_t val = 0xff; + __mpu_read_reg(dev, WHO_AM_I, &val, sizeof(val)); + return val; +} + +/* Locks and unlocks the @dev data structure (mutex). + * + * Use these functions any time you call one of the lock-dependent + * helper functions defined above. + */ + +static void inline mpu_lock(FAR struct mpu_dev_s *dev) +{ + nxmutex_lock(&dev->lock); +} + +static void inline mpu_unlock(FAR struct mpu_dev_s *dev) +{ + nxmutex_unlock(&dev->lock); +} + +/* Resets the mpu60x0, sets it to a default configuration. */ + +static int mpu_reset(FAR struct mpu_dev_s *dev) +{ + /* We support only SPI right now. */ + + if (dev->config.spi == NULL) + { + return -EINVAL; + } + + mpu_lock(dev); + + /* Awaken chip, issue hardware reset */ + + __mpu_write_pwr_mgmt_1(dev, PWR_MGMT_1__DEVICE_RESET); + + /* Wait for reset cycle to finish (note: per the datasheet, we don't need to + * hold NSS for this) + */ + + do + { + up_mdelay(50); /* msecs (arbitrary) */ + } + while (__mpu_read_pwr_mgmt_1(dev) & PWR_MGMT_1__DEVICE_RESET); + + /* Reset signal paths */ + + __mpu_write_signal_path_reset(dev, SIGNAL_PATH_RESET__ALL_RESET); + up_mdelay(2); + + /* Disable SLEEP, use PLL with z-axis clock source */ + + __mpu_write_pwr_mgmt_1(dev, 3); + up_mdelay(2); + + /* Disable i2c if we're on spi. */ + + if (dev->config.spi) + { + __mpu_write_user_ctrl(dev, USER_CTRL__I2C_IF_DIS); + } + + /* Disable low-power mode, enable all gyros and accelerometers */ + + __mpu_write_pwr_mgmt_2(dev, 0); + + /* No FSYNC, set accel LPF at 184 Hz, gyro LPF at 188 Hz */ + + __mpu_write_config(dev, 0, 1); + + /* ± 1000 deg/sec */ + + __mpu_write_gyro_config(dev, 2); + + /* ± 8g */ + + __mpu_write_accel_config(dev, 2); + + /* clear INT on any read (we aren't using that pin right now) */ + + __mpu_write_int_pin_cfg(dev, INT_PIN_CFG__INT_RD_CLEAR); + + mpu_unlock(dev); + return 0; +} + +/**************************************************************************** + * Name: mpu_open + * + * Note: we don't deal with multiple users trying to access this interface at + * the same time. Until further notice, don't do that. + * + * And no, it's not as simple as just prohibiting concurrent opens or + * reads with a mutex: there are legit reasons for truy concurrent + * access, but they must be treated carefully in this interface lest a + * partial reader end up with a mixture of old and new samples. This + * will make some users unhappy. + * + ****************************************************************************/ + +static int mpu_open(FAR struct file *filep) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct mpu_dev_s *dev = inode->i_private; + + /* Reset the register cache */ + + mpu_lock(dev); + dev->bufpos = 0; + mpu_unlock(dev); + + return 0; +} + +/**************************************************************************** + * Name: mpu_close + ****************************************************************************/ + +static int mpu_close(FAR struct file *filep) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct mpu_dev_s *dev = inode->i_private; + + /* Reset (clear) the register cache. */ + + mpu_lock(dev); + dev->bufpos = 0; + mpu_unlock(dev); + + return 0; +} + +/**************************************************************************** + * Name: mpu_read + * + * Returns a snapshot of the accelerometer, temperature, and gyro registers. + * + * Note: the chip uses traditional, twos-complement notation, i.e. "0" + * is encoded as 0, and full-scale-negative is 0x8000, and + * full-scale-positive is 0x7fff. If we read the registers + * sequentially and directly into memory (as we do), the measurements + * from each sensor are captured as big endian words. + * + * In contrast, ASN.1 maps "0" to 0x8000, full-scale-negative to 0, + * and full-scale-positive to 0xffff. So if we want to send in a + * format that an ASN.1 PER-decoder would recognize, must: + * + * 1. Treat the register data/measurements as unsigned, + * 2. Add 0x8000 to each measurement, and then, + * 3. Send each word in big-endian order. + * + * The result of the above will be something you could neatly describe + * like this (confirmed with asn1scc): + * + * Sint16 ::= INTEGER(-32768..32767) + * + * Mpu60x0Sample ::= SEQUENCE + * { + * accel-X Sint16, + * accel-Y Sint16, + * accel-Z Sint16, + * temp Sint16, + * gyro-X Sint16, + * gyro-Y Sint16, + * gyro-Z Sint16 + * } + * + ****************************************************************************/ + +static ssize_t mpu_read(FAR struct file *filep, FAR char *buf, size_t len) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct mpu_dev_s *dev = inode->i_private; + size_t send_len = 0; + + mpu_lock(dev); + + /* Populate the register cache if it seems empty. */ + + if (!dev->bufpos) + { + __mpu_read_imu(dev, &dev->buf); + } + + /* Send the lesser of: available bytes, or amount requested. */ + + send_len = sizeof(dev->buf) - dev->bufpos; + if (send_len > len) + { + send_len = len; + } + + if (send_len) + { + memcpy(buf, ((uint8_t *) & dev->buf) + dev->bufpos, send_len); + } + + /* Move the cursor, to mark them as sent. */ + + dev->bufpos += send_len; + + /* If we've sent the last byte, reset the buffer. */ + + if (dev->bufpos >= sizeof(dev->buf)) + { + dev->bufpos = 0; + } + + mpu_unlock(dev); + + return send_len; +} + +/**************************************************************************** + * Name: mpu_write + ****************************************************************************/ + +static ssize_t mpu_write(FAR struct file *filep, FAR const char *buf, + size_t len) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct mpu_dev_s *dev = inode->i_private; + + UNUSED(inode); + UNUSED(dev); + snerr("%s: %p %p %d\n", __FUNCTION__, inode, dev, len); + + return len; +} + +/**************************************************************************** + * Name: mpu60x0_seek + ****************************************************************************/ + +static off_t mpu_seek(FAR struct file *filep, off_t offset, int whence) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct mpu_dev_s *dev = inode->i_private; + + UNUSED(inode); + UNUSED(dev); + + snerr("%s: %p %p\n", __FUNCTION__, inode, dev); + + return 0; +} + +/**************************************************************************** + * Name: mpu60x0_ioctl + ****************************************************************************/ + +static int mpu_ioctl(FAR struct file *filep, int cmd, unsigned long arg) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct mpu_dev_s *dev = inode->i_private; + + UNUSED(inode); + UNUSED(dev); + + snerr("%s: %p %p\n", __FUNCTION__, inode, dev); + + return -EINVAL; +} + +/**************************************************************************** + * Name: mpu60x0_register + * + * Description: + * Registers the mpu60x0 interface as 'devpath' + * + * Input Parameters: + * devpath - The full path to the interface to register. E.g., "/dev/imu0" + * spi - SPI interface for chip communications + * config - Configuration information + * + * Returned Value: + * Zero (OK) on success; a negated errno value on failure. + * + ****************************************************************************/ + +int mpu60x0_register(FAR const char *path, FAR struct mpu_config_s *config) +{ + FAR struct mpu_dev_s *priv; + int ret; + + /* Without config info, we can't do anything. */ + + if (config == NULL) + { + return -EINVAL; + } + + /* Initialize the device structure. */ + + priv = (FAR struct mpu_dev_s *)kmm_malloc(sizeof(struct mpu_dev_s)); + if (priv == NULL) + { + snerr("ERROR: Failed to allocate mpu60x0 device instance\n"); + return -ENOMEM; + } + + memset(priv, 0, sizeof(*priv)); + nxmutex_init(&priv->lock); + + /* Keep a copy of the config structure, in case the caller discards theirs. */ + + priv->config = *config; + + /* Register the device node. */ + + ret = register_driver(path, &g_mpu_fops, 0666, priv); + if (ret < 0) + { + snerr("ERROR: Failed to register mpu60x0 interface: %d\n", ret); + + nxmutex_destroy(&priv->lock); + + kmm_free(priv); + return ret; + } + + /* Reset the chip, to give it an initial configuration. */ + + return mpu_reset(priv); +} diff --git a/include/nuttx/sensors/mpu60x0.h b/include/nuttx/sensors/mpu60x0.h new file mode 100644 index 00000000000..49531033a12 --- /dev/null +++ b/include/nuttx/sensors/mpu60x0.h @@ -0,0 +1,127 @@ +/**************************************************************************** + * include/nuttx/sensors/mpu60x0.h + * + * Support for the Invensense MPU6000 and MPU6050 MotionTracking(tm) + * 6-axis accelerometer and gyroscope. + * + * Copyright (C) 2019 Bill Gatliff. All rights reserved. + * Author: Bill Gatliff + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright+ + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + *****************************************************************************/ + +#ifndef __INCLUDE_NUTTX_SENSORS_MPU60X0_H +#define __INCLUDE_NUTTX_SENSORS_MPU60X0_H + +/**************************************************************************** + * Included Files + *****************************************************************************/ + +#include + +/**************************************************************************** + * Public Types + *****************************************************************************/ + +/* These structures are defined elsewhere, and we don't need their definitions + * here. + */ + +struct spi_dev_s; +struct i2c_master_s; + +/* Specifies the initial chip configuration and location. + * + * The chip supports both SPI and I2C interfaces, but you wouldn't use + * both at the same time on the same chip. It isn't an error to have + * one chip of each flavor in the system, though, so it's not an + * either-or configuration item. + * + * Important note : + * + * The driver determines which interface type to use according to + * which of the two groups of fields is non-NULL. Since support for + * I2C and SPI are individually configurable, however, users should + * let the compiler clear unused fields instead of setting unused + * fields to NULL directly. For example, if using SPI and a + * stack-allocated instance: + * + * struct mpu_config_s mpuc; + * memset(&mpuc, 0, sizeof(mpuc)); * sets i2c to NULL, if present * + * mpuc.spi = ...; + * + * Or, if using dynamic memory allocation and I2C: + * + * struct mpu_config_s* mpuc; + * mpuc = malloc(sizeof(*mpuc)); + * memset(mpuc, 0, sizeof(*mpuc)); * sets spi to NULL, if present * + * mpuc.i2c = ...; + * + * The above examples will avoid compile-time errors unless the user + * forgets to enable their preferred interface type, and will allow + * them to disable or enable the unused interface type without + * changing their code. + * + * Note, I2C support is unimplemented at present. + */ + +struct mpu_config_s +{ +#ifdef CONFIG_SPI + /* For users on SPI. + * + * spi_devid : the SPI master's slave-select number + * for the chip, as used in SPI_SELECT(..., dev_id, ...) + * spi : the SPI master device, as used in SPI_SELECT(spi, ..., ...) + */ + + FAR struct spi_dev_s *spi; + int spi_devid; +#endif + +#ifdef CONFIG_I2C + /* For users on I2C. (Unimplemented.) */ + + FAR struct i2c_master_s *i2c; +#endif + }; + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +/* Declares the existence of an mpu60x0 chip, wired according to + * config; creates an interface to it at path. + * + * Returns 0 on success, or negative errno. + */ + +int mpu60x0_register(FAR const char *path, FAR struct mpu_config_s *config); + +#endif /* __INCLUDE_NUTTX_SENSORS_MPU60X0_H */