diff --git a/drivers/sensors/Kconfig b/drivers/sensors/Kconfig index 587c75ee6353c..a3528a9849ce1 100644 --- a/drivers/sensors/Kconfig +++ b/drivers/sensors/Kconfig @@ -159,6 +159,14 @@ config SENSORS_AS5048A ---help--- Enable driver support for the AMS AS5048A magnetic rotary encoder. +config SENSORS_AS5047D + bool "AMS AS5047D Magnetic Rotary Encoder support" + default n + select SPI + select SENSORS_QENCODER + ---help--- + Enable driver support for the AMS AS5047D magnetic rotary encoder. + config SENSORS_AS726X bool "AMS AS726X Spetral sensor support" default n diff --git a/drivers/sensors/Make.defs b/drivers/sensors/Make.defs index cc8cf42605690..b9c501a18b6fa 100644 --- a/drivers/sensors/Make.defs +++ b/drivers/sensors/Make.defs @@ -408,6 +408,10 @@ ifeq ($(CONFIG_SENSORS_AS5048A),y) CSRCS += as5048a.c endif +ifeq ($(CONFIG_SENSORS_AS5047D),y) + CSRCS += as5047d.c +endif + endif # CONFIG_SPI # These drivers depend on 1WIRE support diff --git a/drivers/sensors/as5047d.c b/drivers/sensors/as5047d.c new file mode 100644 index 0000000000000..d656aa2fdba5c --- /dev/null +++ b/drivers/sensors/as5047d.c @@ -0,0 +1,417 @@ +/**************************************************************************** + * drivers/sensors/as5047d.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#if defined(CONFIG_SENSORS_AS5047D) + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct as5047d_dev_s +{ + struct qe_lowerhalf_s lower; /* AS5047D quadrature encoder lower half */ + FAR struct spi_dev_s *spi; /* SPI interface */ + + /* Since multiple AS5047D can be connected to the same SPI bus we need + * to use multiple spi device ids which are employed by NuttX to select/ + * deselect the desired AS5047D chip via their chip select inputs. + */ + + int spi_devid; + + /* AS5047D returns the response of the previous frame. Track which + * register is currently staged in the pipeline. + */ + + bool pipeline_valid; + uint16_t pipeline_reg; +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static int as5047d_exchange(FAR struct as5047d_dev_s *priv, + uint16_t regaddr, FAR uint16_t *regval); +static uint16_t as5047d_calc_even_parity(uint16_t value); +static int as5047d_readang(FAR struct as5047d_dev_s *priv, + FAR uint16_t *ang); +static int as5047d_readmag(FAR struct as5047d_dev_s *priv, + FAR uint16_t *mag); +static int as5047d_readdiag(FAR struct as5047d_dev_s *priv, + FAR uint16_t *diag); + +/* Character Driver Methods */ + +static int as5047d_setup(FAR struct qe_lowerhalf_s *lower); +static int as5047d_shutdown(FAR struct qe_lowerhalf_s *lower); +static int as5047d_position(FAR struct qe_lowerhalf_s *lower, + FAR int32_t *pos); +static int as5047d_reset(FAR struct qe_lowerhalf_s *lower); +static int as5047d_ioctl(FAR struct qe_lowerhalf_s *lower, int cmd, + unsigned long arg); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct qe_ops_s g_qeops = +{ + as5047d_setup, /* setup */ + as5047d_shutdown, /* shutdown */ + as5047d_position, /* position */ + NULL, /* setposmax */ + as5047d_reset, /* reset */ + NULL, /* setindex */ + as5047d_ioctl /* ioctl */ +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: as5047d_configspi + * + * Description: + * Configure SPI for the AS5047D. + * + ****************************************************************************/ + +static inline void as5047d_configspi(FAR struct spi_dev_s *spi) +{ + SPI_SETMODE(spi, AS5047D_SPI_MODE); + SPI_SETBITS(spi, 16); + SPI_HWFEATURES(spi, 0); + SPI_SETFREQUENCY(spi, AS5047D_SPI_MAXFREQUENCY); +} + +/**************************************************************************** + * Name: as5047d_calc_even_parity + * + * Description: + * Get the even parity of input value. + * + ****************************************************************************/ + +static uint16_t as5047d_calc_even_parity(uint16_t value) +{ + uint16_t cnt = 0; + uint8_t i; + + for (i = 0; i < 16; i++) + { + if (value & 0x1) + { + cnt++; + } + + value >>= 1; + } + + return cnt & 0x1; +} + +/**************************************************************************** + * Name: as5047d_exchange + * + * Description: + * Read from 16-bit registers. + * + ****************************************************************************/ + +static int as5047d_exchange(FAR struct as5047d_dev_s *priv, + uint16_t regaddr, FAR uint16_t *regval) +{ + uint16_t cmd; + uint16_t tx; + uint16_t rx; + uint16_t sample; + + DEBUGASSERT(priv != NULL && regval != NULL); + + SPI_LOCK(priv->spi, true); + as5047d_configspi(priv->spi); + + cmd = AS5047D_CMD_READ | (regaddr & AS5047D_VALUE_MASK); + cmd |= as5047d_calc_even_parity(cmd) << 15; + + /* AS5047D expects framed 16-bit transfers. Keep the SPI lock but toggle + * CS for each command/response frame. + */ + + SPI_SELECT(priv->spi, priv->spi_devid, true); + tx = cmd; + SPI_EXCHANGE(priv->spi, &tx, &rx, 1); + SPI_SELECT(priv->spi, priv->spi_devid, false); + + if (priv->pipeline_valid && priv->pipeline_reg == regaddr) + { + sample = rx; + } + else + { + /* Pipeline is empty or points to a different register. Send one more + * READ frame so received data corresponds to regaddr. + */ + + SPI_SELECT(priv->spi, priv->spi_devid, true); + tx = cmd; + SPI_EXCHANGE(priv->spi, &tx, &sample, 1); + SPI_SELECT(priv->spi, priv->spi_devid, false); + } + + priv->pipeline_valid = true; + priv->pipeline_reg = regaddr; + + SPI_LOCK(priv->spi, false); + + if ((sample & AS5047D_FLAG_ERR) != 0) + { + snerr("ERROR: as5047d error flag set: %04x reg: %04x\n", + sample, regaddr); + return -EIO; + } + + *regval = sample & AS5047D_VALUE_MASK; + return OK; +} + +/**************************************************************************** + * Name: as5047d_readang + ****************************************************************************/ + +static int as5047d_readang(FAR struct as5047d_dev_s *priv, FAR uint16_t *ang) +{ + int ret; + + ret = as5047d_exchange(priv, AS5047D_REG_ANGLEUNC, ang); + if (ret < 0) + { + snerr("ERROR: as5047d_exchange failed: %d\n", ret); + return ret; + } + + return ret; +} + +/**************************************************************************** + * Name: as5047d_readmag + ****************************************************************************/ + +static int as5047d_readmag(FAR struct as5047d_dev_s *priv, FAR uint16_t *mag) +{ + int ret; + + ret = as5047d_exchange(priv, AS5047D_REG_MAG, mag); + if (ret < 0) + { + snerr("ERROR: as5047d_exchange failed: %d\n", ret); + return ret; + } + + return ret; +} + +/**************************************************************************** + * Name: as5047d_readdiag + ****************************************************************************/ + +static int as5047d_readdiag(FAR struct as5047d_dev_s *priv, + FAR uint16_t *diag) +{ + int ret; + + ret = as5047d_exchange(priv, AS5047D_REG_DIAAGC, diag); + if (ret < 0) + { + snerr("ERROR: as5047d_exchange failed: %d\n", ret); + return ret; + } + + return ret; +} + +/**************************************************************************** + * Name: as5047d_setup + ****************************************************************************/ + +static int as5047d_setup(FAR struct qe_lowerhalf_s *lower) +{ + return OK; +} + +/**************************************************************************** + * Name: as5047d_shutdown + ****************************************************************************/ + +static int as5047d_shutdown(FAR struct qe_lowerhalf_s *lower) +{ + return OK; +} + +/**************************************************************************** + * Name: as5047d_position + * + * Description: + * Return the current position measurement relative to reset position. + * + ****************************************************************************/ + +static int as5047d_position(FAR struct qe_lowerhalf_s *lower, + FAR int32_t *pos) +{ + FAR struct as5047d_dev_s *priv = (FAR struct as5047d_dev_s *)lower; + uint16_t rawang; + int ret; + + ret = as5047d_readang(priv, &rawang); + if (ret < 0) + { + snerr("ERROR: as5047d_readang failed: %d\n", ret); + return ret; + } + + *pos = (int32_t)(rawang & AS5047D_VALUE_MASK); + return ret; +} + +/**************************************************************************** + * Name: as5047d_reset + * + * Description: + * Reset the position measurement to zero. + * + ****************************************************************************/ + +static int as5047d_reset(FAR struct qe_lowerhalf_s *lower) +{ + FAR struct as5047d_dev_s *priv = (FAR struct as5047d_dev_s *)lower; + uint16_t rawang; + int ret; + + ret = as5047d_readang(priv, &rawang); + if (ret < 0) + { + snerr("ERROR: as5047d_readang failed: %d\n", ret); + return ret; + } + + return OK; +} + +/**************************************************************************** + * Name: as5047d_ioctl + ****************************************************************************/ + +static int as5047d_ioctl(FAR struct qe_lowerhalf_s *lower, int cmd, + unsigned long arg) +{ + FAR struct as5047d_dev_s *priv = (FAR struct as5047d_dev_s *)lower; + int ret = OK; + + switch (cmd) + { + case QEIOC_AS5047D_DIAGNOSTICS: + { + FAR uint16_t *ptr = (FAR uint16_t *)((uintptr_t)arg); + DEBUGASSERT(ptr != NULL); + ret = as5047d_readdiag(priv, ptr); + } + break; + + case QEIOC_AS5047D_MAGNITUDE: + { + FAR uint16_t *ptr = (FAR uint16_t *)((uintptr_t)arg); + DEBUGASSERT(ptr != NULL); + ret = as5047d_readmag(priv, ptr); + } + break; + + default: + snerr("ERROR: Unrecognized cmd: %d arg: %ld\n", cmd, arg); + ret = -ENOTTY; + break; + } + + return ret; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: as5047d_initialize + * + * Description: + * Initialize the AS5047D device. + * + * Input Parameters: + * spi - An SPI driver instance. + * + * Returned Value: + * A new lower half encoder interface for the AS5047D on success; + * NULL on failure. + * + ****************************************************************************/ + +FAR struct qe_lowerhalf_s *as5047d_initialize(FAR struct spi_dev_s *spi, + int spi_devid) +{ + FAR struct as5047d_dev_s *priv; + + DEBUGASSERT(spi != NULL); + + priv = kmm_zalloc(sizeof(*priv)); + if (priv == NULL) + { + snerr("ERROR: Failed to allocate instance\n"); + return NULL; + } + + priv->lower.ops = &g_qeops; + priv->spi = spi; + priv->spi_devid = spi_devid; + + return &priv->lower; +} + +#endif /* CONFIG_SENSORS_AS5047D */ diff --git a/include/nuttx/sensors/as5047d.h b/include/nuttx/sensors/as5047d.h new file mode 100644 index 0000000000000..80a5df89efab6 --- /dev/null +++ b/include/nuttx/sensors/as5047d.h @@ -0,0 +1,111 @@ +/**************************************************************************** + * include/nuttx/sensors/as5047d.h + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __INCLUDE_NUTTX_SENSORS_AS5047D_H +#define __INCLUDE_NUTTX_SENSORS_AS5047D_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include + +#if defined(CONFIG_SENSORS_AS5047D) + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* The device always operates in mode 1 */ + +#define AS5047D_SPI_MODE SPIDEV_MODE1 + +/* SPI frequency */ + +#define AS5047D_SPI_MAXFREQUENCY 1000000 + +/* IOCTL Commands ***********************************************************/ + +/* Arg: uint16_t* pointer */ + +#define QEIOC_AS5047D_DIAGNOSTICS _QEIOC(QE_AS5047D_FIRST + 0) + +/* Arg: uint16_t* pointer */ + +#define QEIOC_AS5047D_MAGNITUDE _QEIOC(QE_AS5047D_FIRST + 1) + +/* Resolution ***************************************************************/ + +#define AS5047D_RESOLUTION 16384 +#define AS5047D_VALUE_MASK 0x3fff + +/* Register Definitions *****************************************************/ + +#define AS5047D_CMD_READ 0x4000 +#define AS5047D_CMD_WRITE 0x0000 +#define AS5047D_FLAG_ERR 0x4000 +#define AS5047D_REG_NOP 0x0000 +#define AS5047D_REG_ERRFL 0x0001 +#define AS5047D_REG_PROG 0x0003 +#define AS5047D_REG_DIAAGC 0x3ffc +#define AS5047D_REG_MAG 0x3ffd +#define AS5047D_REG_ANGLEUNC 0x3ffe +#define AS5047D_REG_ANGLE 0x3fff + +/* Error Flags (ERRFL register bits) */ + +#define AS5047D_ERR_PARITY (1 << 2) +#define AS5047D_ERR_INV_CMD (1 << 1) +#define AS5047D_ERR_FRAMING (1 << 0) + +/* DIAAGC Register Bits */ + +#define AS5047D_DIAAGC_MAGL (1 << 11) +#define AS5047D_DIAAGC_MAGH (1 << 10) +#define AS5047D_DIAAGC_COF (1 << 9) +#define AS5047D_DIAAGC_OCF (1 << 8) + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +#ifdef __cplusplus +#define EXTERN extern "C" +extern "C" +{ +#else +#define EXTERN extern +#endif + +FAR struct qe_lowerhalf_s *as5047d_initialize(FAR struct spi_dev_s *spi, + int spi_devid); + +#undef EXTERN +#ifdef __cplusplus +} +#endif + +#endif /* CONFIG_SENSORS_AS5047D */ +#endif /* __INCLUDE_NUTTX_SENSORS_AS5047D_H */ diff --git a/include/nuttx/sensors/qencoder.h b/include/nuttx/sensors/qencoder.h index 6c78d224eecc2..4bb40f345e3d4 100644 --- a/include/nuttx/sensors/qencoder.h +++ b/include/nuttx/sensors/qencoder.h @@ -98,6 +98,11 @@ #define QE_AS5048A_FIRST (QE_IMXRT_FIRST + QE_IMXRT_NCMDS) #define QE_AS5048A_NCMDS 4 +/* See include/nuttx/sensors/as5047d.h */ + +#define QE_AS5047D_FIRST (QE_AS5048A_FIRST + QE_AS5048A_NCMDS) +#define QE_AS5047D_NCMDS 2 + /**************************************************************************** * Public Types ****************************************************************************/