This is an automated email from the ASF dual-hosted git repository.
xiaoxiang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx.git
The following commit(s) were added to refs/heads/master by this push:
new 36507cc7b2 drivers/sensors: Wrote a driver for the MCP9600
thermocouple amplifier.
36507cc7b2 is described below
commit 36507cc7b2b239cbea3872489a075c0c06a43c2d
Author: Matteo Golin <[email protected]>
AuthorDate: Mon Jan 6 22:54:58 2025 -0500
drivers/sensors: Wrote a driver for the MCP9600 thermocouple amplifier.
---
.../components/drivers/special/sensors/mcp9600.rst | 146 ++++
.../arm/rp2040/common/src/rp2040_common_bringup.c | 15 +
drivers/sensors/Kconfig | 17 +
drivers/sensors/Make.defs | 4 +
drivers/sensors/mcp9600.c | 856 +++++++++++++++++++++
include/nuttx/sensors/mcp9600.h | 197 +++++
6 files changed, 1235 insertions(+)
diff --git a/Documentation/components/drivers/special/sensors/mcp9600.rst
b/Documentation/components/drivers/special/sensors/mcp9600.rst
new file mode 100644
index 0000000000..1d7852b45f
--- /dev/null
+++ b/Documentation/components/drivers/special/sensors/mcp9600.rst
@@ -0,0 +1,146 @@
+MCP9600
+=======
+
+Contributed by Matteo Golin.
+
+The MCP9600 is a thermocouple EMF to temperature converter made by Microchip.
It is also sold as a `breakout board module
+by Adafruit
<https://learn.adafruit.com/adafruit-mcp9600-i2c-thermocouple-amplifier>`_.
+
+Application Programming Interface
+==================================
+
+The header file for the MCP9600 driver interface can be included using:
+
+.. code-block:: c
+
+ #include <nuttx/sensors/mcp9600.h>
+
+The MCP9600 registration function allows the driver to be registered as a POSIX
+character driver.
+
+The standard POSIX `read()` operation will return the device information in
+plain-text, which is useful when debugging/testing the driver using `cat` from
+the shell.
+
+The `write()` operation is not implemented for this sensor.
+
+Specific operations the sensor offers can be performed via the POSIX `ioctl`
+operation. The supported commands are:
+
+ * :c:macro:`SNIOC_WHO_AM_I`
+ * :c:macro:`SNIOC_READ_RAW_DATA`
+ * :c:macro:`SNIOC_CHECK_STATUS_REG`
+ * :c:macro:`SNIOC_CONFIGURE`
+ * :c:macro:`SNIOC_WRITECONF`
+ * :c:macro:`SNIOC_READTEMP`
+ * :c:macro:`SNIOC_SHUTDOWN`
+ * :c:macro:`SNIOC_START`
+
+``SNIOC_WHO_AM_I``
+------------------
+
+This command reads the device ID register of the MCP9600 sensor. The device ID,
+major and minor revision numbers are returned in the argument, which must be of
+type ``struct mcp9600_devinfo_s *``.
+
+.. code-block:: c
+
+ struct mcp9600_devinfo_s devinfo;
+ err = ioctl(sensor, SNIOC_WHO_AM_I, &devinfo);
+
+ uint8_t revision_minor = MCP9600_REV_MINOR(devinfo.revision);
+ uint8_t revision_major = MCP9600_REV_MAJOR(devinfo.revision);
+
+``SNIOC_READ_RAW_DATA``
+-----------------------
+
+This command allows the caller to read the raw data returned from the sensor's
+ADC.
+
+The argument to this command must be an ``int32_t`` pointer. The raw data will
+be returned here. The process to convert the raw ADC data depends on the
+configured resolution; consult the data sheet.
+
+.. code-block:: c
+
+ int32_t raw;
+ err = ioctl(sensor, SNIOC_READ_RAW_DATA, &raw);
+
+``SNIOC_CHECK_STATUS_REG``
+--------------------------
+
+This command lets you check the status register of the device. The argument to
+this command must be a pointer to type ``struct mcp9600_status_s``.
+
+.. code-block:: c
+
+ struct mcp9600_status_s status;
+ err = ioctl(sensor, SNIOC_CHECK_STATUS_REG, &status);
+
+``SNIOC_CONFIGURE``
+-------------------
+
+This command lets you configure the MCP9600's operation, including thermocouple
+type, operating mode, ADC resolution, etc.
+
+The argument to this command must be a pointer to type ``struct
+mcp9600_devconf_s``.
+
+.. code-block:: c
+
+ struct mcp9600_devconf_s conf = {
+ .thermo_type = MCP9600_THERMO_TYPE_K,
+ .resolution = MCP9600_ADC_RES_18,
+ /* More fields ... */
+ };
+ err = ioctl(sensor, SNIOC_CONFIGURE, &conf);
+
+``SNIOC_WRITECONF``
+-------------------
+
+This command lets you configure the MCP9600's alerts on a per-alert basis.
+
+The argument to this command must be a pointer to type ``struct
+mcp9600_alertconf_s``.
+
+.. code-block:: c
+
+ struct mcp9600_alertconf_s conf = {
+ .alert = MCP9600_ALERT1,
+ .enable = true,
+ .limit = 40 / 0.25,
+ /* More fields ... */
+ };
+ err = ioctl(sensor, SNIOC_WRITECONF, &conf);
+
+``SNIOC_READTEMP``
+------------------
+
+This command lets you read the three different types of temperature that the
+MCP9600 can measure. The argument to this command must be a pointer to type
+``struct mcp9600_temp_s``.
+
+.. code-block:: c
+
+ struct mcp9600_temp_s temps;
+ err = ioctl(sensor, SNIOC_READTEMP, &temps);
+
+ printf("Temperature: %d C\n", temps.hot_junc);
+
+``SNIOC_SHUTDOWN``
+------------------
+
+This command shuts down the sensor. It takes no arguments.
+
+.. code-block:: c
+
+ err = ioctl(sensor, SNIOC_SHUTDOWN, NULL);
+
+``SNIOC_START``
+---------------
+
+This command starts the sensor in normal mode. It takes no arguments.
+
+.. code-block:: c
+
+ err = ioctl(sensor, SNIOC_START, NULL);
diff --git a/boards/arm/rp2040/common/src/rp2040_common_bringup.c
b/boards/arm/rp2040/common/src/rp2040_common_bringup.c
index 8b4a4f759a..37393eff43 100644
--- a/boards/arm/rp2040/common/src/rp2040_common_bringup.c
+++ b/boards/arm/rp2040/common/src/rp2040_common_bringup.c
@@ -75,6 +75,11 @@
#include "rp2040_i2c.h"
#endif
+#ifdef CONFIG_SENSORS_MCP9600
+#include <nuttx/sensors/mcp9600.h>
+#include "rp2040_i2c.h"
+#endif
+
#ifdef CONFIG_SENSORS_MAX6675
#include <nuttx/sensors/max6675.h>
#include "rp2040_max6675.h"
@@ -538,6 +543,16 @@ int rp2040_common_bringup(void)
}
#endif
+#ifdef CONFIG_SENSORS_MCP9600
+ /* Try to register MCP9600 device as /dev/thermo0 at I2C0. */
+
+ ret = mcp9600_register("/dev/thermo0", rp2040_i2cbus_initialize(0), 0x60);
+ if (ret < 0)
+ {
+ syslog(LOG_ERR, "ERROR: couldn't initialize MCP9600: %d\n", ret);
+ }
+#endif
+
#ifdef CONFIG_VIDEO_FB
ret = fb_register(0, 0);
if (ret < 0)
diff --git a/drivers/sensors/Kconfig b/drivers/sensors/Kconfig
index b43ba06f61..14986498af 100644
--- a/drivers/sensors/Kconfig
+++ b/drivers/sensors/Kconfig
@@ -950,6 +950,23 @@ config MLX90614_CRC
endif # SENSORS_MLX90614
+config SENSORS_MCP9600
+ bool "MCP9600 Thermocouple Amplifier"
+ default n
+ select I2C
+ ---help---
+ Enable driver support for the MCP9600 thermocouple amplifier
+
+if SENSORS_MCP9600
+
+config MCP9600_I2C_FREQUENCY
+ int "MCP9600 I2C frequency"
+ default 100000
+ range 10000 100000
+ depends on SENSORS_MCP9600
+
+endif # SENSORS_MCP9600
+
config SENSORS_MCP9844
bool "MCP9844 Temperature Sensor"
default n
diff --git a/drivers/sensors/Make.defs b/drivers/sensors/Make.defs
index f20424b4c5..ac6ec09022 100644
--- a/drivers/sensors/Make.defs
+++ b/drivers/sensors/Make.defs
@@ -224,6 +224,10 @@ ifeq ($(CONFIG_SENSORS_MB7040),y)
CSRCS += mb7040.c
endif
+ifeq ($(CONFIG_SENSORS_MCP9600),y)
+ CSRCS += mcp9600.c
+endif
+
ifeq ($(CONFIG_SENSORS_MCP9844),y)
CSRCS += mcp9844.c
endif
diff --git a/drivers/sensors/mcp9600.c b/drivers/sensors/mcp9600.c
new file mode 100644
index 0000000000..4c48539dc0
--- /dev/null
+++ b/drivers/sensors/mcp9600.c
@@ -0,0 +1,856 @@
+/****************************************************************************
+ * drivers/sensors/mcp9600.c
+ *
+ * Contributed by Matteo Golin
+ *
+ * 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 <nuttx/config.h>
+
+#include <assert.h>
+#include <debug.h>
+#include <errno.h>
+#include <stdio.h>
+
+#include <nuttx/fs/fs.h>
+#include <nuttx/i2c/i2c_master.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/sensors/mcp9600.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* I2C frequency to use during transfers */
+
+#ifdef CONFIG_MCP9600_I2C_FREQUENCY
+#define CONFIG_MCP9600_I2C_FREQUENCY 100000
+#endif
+
+#define REG_THERMO_HOT_JUNC 0x0 /* Thermocouple Hot-Junction, T H */
+#define REG_JUNC_TEMP_DELTA 0x1 /* Junctions Temperature Delta, TΔ */
+#define REG_COLD_JUNC_TEMP 0x2 /* Cold-Junction Temperature, T C */
+#define REG_RAW_ADC 0x3 /* Raw ADC Data */
+#define REG_STATUS 0x4 /* STATUS */
+#define REG_THERMO_SEN_CONF 0x5 /* Thermocouple Sensor Configuration */
+#define REG_DEV_CONFIG 0x6 /* Device Configuration */
+#define REG_ALERT1_CONF 0x8 /* Alert 1 Configuration */
+#define REG_ALERT2_CONF 0x9 /* Alert 2 Configuration */
+#define REG_ALERT3_CONF 0xa /* Alert 3 Configuration */
+#define REG_ALERT4_CONF 0xb /* Alert 4 Configuration */
+#define REG_ALERT1_HYST 0xc /* Alert 1 Hysteresis, THYST1 */
+#define REG_ALERT2_HYST 0xd /* Alert 2 Hysteresis, THYST2 */
+#define REG_ALERT3_HYST 0xe /* Alert 3 Hysteresis, THYST3 */
+#define REG_ALERT4_HYST 0xf /* Alert 4 Hysteresis, THYST4 */
+#define REG_ALERT1_TEMP 0x10 /* Temperature Alert 1 Limit, TALERT1 */
+#define REG_ALERT2_TEMP 0x11 /* Temperature Alert 2 Limit, TALERT2 */
+#define REG_ALERT3_TEMP 0x12 /* Temperature Alert 3 Limit, TALERT3 */
+#define REG_ALERT4_TEMP 0x13 /* Temperature Alert 4 Limit, TALERT4 */
+#define REG_DEVID 0x20 /* Device ID/Revision */
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+struct mcp9600_dev_s
+{
+ FAR struct i2c_master_s *i2c; /* I2C interface */
+ uint8_t addr; /* I2C address */
+ struct mcp9600_devconf_s conf; /* Device configuration */
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+ int16_t crefs; /* Number of open references. */
+ bool unlinked; /* True, driver has been unlinked. */
+#endif
+ mutex_t devlock;
+};
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+static int mcp9600_open(FAR struct file *filep);
+static int mcp9600_close(FAR struct file *filep);
+static ssize_t mcp9600_write(FAR struct file *filep, FAR const char *buffer,
+ size_t buflen);
+static ssize_t mcp9600_read(FAR struct file *filep, FAR char *buffer,
+ size_t buflen);
+static int mcp9600_ioctl(FAR struct file *filep, int cmd, unsigned long arg);
+static int mcp9600_unlink(FAR struct inode *inode);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations g_mcp9600fops =
+{
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+ .open = mcp9600_open,
+ .close = mcp9600_close,
+#else
+ .open = NULL,
+ .close = NULL,
+#endif
+ .read = mcp9600_read,
+ .write = mcp9600_write,
+ .seek = NULL,
+ .ioctl = mcp9600_ioctl,
+ .mmap = NULL,
+ .truncate = NULL,
+ .poll = NULL,
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+ .unlink = mcp9600_unlink,
+#endif
+};
+
+/* Alert hysterisis registers */
+
+static const enum mcp9600_alert_e g_alert_hysts[] =
+{
+ [MCP9600_ALERT1] = REG_ALERT1_HYST,
+ [MCP9600_ALERT2] = REG_ALERT2_HYST,
+ [MCP9600_ALERT3] = REG_ALERT3_HYST,
+ [MCP9600_ALERT4] = REG_ALERT4_HYST,
+};
+
+/* Alert limit registers */
+
+static const enum mcp9600_alert_e g_alert_limits[] =
+{
+ [MCP9600_ALERT1] = REG_ALERT1_TEMP,
+ [MCP9600_ALERT2] = REG_ALERT2_TEMP,
+ [MCP9600_ALERT3] = REG_ALERT3_TEMP,
+ [MCP9600_ALERT4] = REG_ALERT4_TEMP,
+};
+
+/* Alert configuration registers */
+
+static const enum mcp9600_alert_e g_alert_configs[] =
+{
+ [MCP9600_ALERT1] = REG_ALERT1_CONF,
+ [MCP9600_ALERT2] = REG_ALERT2_CONF,
+ [MCP9600_ALERT3] = REG_ALERT3_CONF,
+ [MCP9600_ALERT4] = REG_ALERT4_CONF,
+};
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: mcp9600_read_reg
+ *
+ * Description:
+ * Reads the value of a single register into the buffer. Since registers
+ * are variable in size, the number of bytes to read can be specified.
+ *
+ ****************************************************************************/
+
+static int mcp9600_read_reg(FAR struct mcp9600_dev_s *priv, uint8_t reg,
+ FAR void *buf, size_t nbytes)
+{
+ struct i2c_msg_s read_cmd[] = {
+ {
+ .frequency = CONFIG_MCP9600_I2C_FREQUENCY,
+ .addr = priv->addr,
+ .flags = 0,
+ .buffer = ®,
+ .length = sizeof(reg),
+ },
+ {
+ .frequency = CONFIG_MCP9600_I2C_FREQUENCY,
+ .addr = priv->addr,
+ .flags = I2C_M_READ,
+ .buffer = buf,
+ .length = nbytes,
+ },
+ };
+
+ return I2C_TRANSFER(priv->i2c, read_cmd,
+ sizeof(read_cmd) / sizeof(struct i2c_msg_s));
+}
+
+/****************************************************************************
+ * Name: mcp9600_write_reg
+ *
+ * Description:
+ * Writes `nbytes` of the value in `buf` to the register specified by
+ * `reg`.
+ *
+ ****************************************************************************/
+
+static int mcp9600_write_reg(FAR struct mcp9600_dev_s *priv, uint8_t reg,
+ FAR void *buf, size_t nbytes)
+{
+ struct i2c_msg_s read_cmd[] = {
+ {
+ .frequency = CONFIG_MCP9600_I2C_FREQUENCY,
+ .addr = priv->addr,
+ .flags = 0,
+ .buffer = ®,
+ .length = sizeof(reg),
+ },
+ {
+ .frequency = CONFIG_MCP9600_I2C_FREQUENCY,
+ .addr = priv->addr,
+ .flags = 0,
+ .buffer = buf,
+ .length = nbytes,
+ },
+ };
+
+ return I2C_TRANSFER(priv->i2c, read_cmd,
+ sizeof(read_cmd) / sizeof(struct i2c_msg_s));
+}
+
+/****************************************************************************
+ * Name: mcp9600_read_temp
+ *
+ * Description:
+ * Reads the value of a temperature register and performs the conversion to
+ * put it into degrees Celsius.
+ *
+ ****************************************************************************/
+
+static int mcp9600_read_temp(FAR struct mcp9600_dev_s *priv, uint8_t reg,
+ FAR int16_t *temp)
+{
+ int err;
+ uint8_t raw[2];
+
+ err = mcp9600_read_reg(priv, reg, raw, sizeof(raw));
+ if (err < 0)
+ {
+ return err;
+ }
+
+ /* Positive temperature */
+
+ *temp = (raw[0] * 16 + raw[1] / 16);
+
+ /* Negative temperature */
+
+ if (raw[0] & 0x80)
+ {
+ *temp -= 4096;
+ }
+
+ return err;
+}
+
+/****************************************************************************
+ * Name: mcp9600_config_alert
+ *
+ * Description:
+ * Configure an alert of the MCP9600.
+ *
+ ****************************************************************************/
+
+static int mcp9600_config_alert(FAR struct mcp9600_dev_s *priv,
+ FAR struct mcp9600_alert_conf_s *config)
+{
+ int err;
+
+ /* Configure hysteresis threshold first */
+
+ err = mcp9600_write_reg(priv, g_alert_hysts[config->alert], &config->temp,
+ sizeof(config->temp));
+ if (err < 0)
+ {
+ return err;
+ }
+
+ /* Configure limit */
+
+ int16_t limit = config->limit << 2; /* 2 LSBs must be 0 for this reg */
+ err = mcp9600_write_reg(priv, g_alert_limits[config->alert], &limit,
+ sizeof(limit));
+ if (err < 0)
+ {
+ return err;
+ }
+
+ /* Configure the config register */
+
+ uint8_t config_reg = 0;
+ config_reg |= (config->enable);
+ config_reg |= (config->int_mode << 1);
+ config_reg |= (config->active_high << 2);
+ config_reg |= (config->falling_temp << 3);
+ config_reg |= (config->cold_junc << 4);
+
+ return mcp9600_write_reg(priv, g_alert_configs[config->alert], &config_reg,
+ sizeof(config_reg));
+}
+
+/****************************************************************************
+ * Name: mcp9600_validate_conf
+ *
+ * Description:
+ * Validates the device configuration settings passed by a user. Returns
+ * -EINVAL if any field is invalid, and returns 0 if okay.
+ *
+ ****************************************************************************/
+
+static int mcp9600_validate_conf(FAR struct mcp9600_devconf_s *conf)
+{
+ if (conf == NULL)
+ {
+ return -EINVAL;
+ }
+
+ if (conf->thermo_type < MCP9600_THERMO_TYPE_K ||
+ conf->thermo_type > MCP9600_THERMO_TYPE_R)
+ {
+ return -EINVAL;
+ }
+
+ if (conf->filter_coeff > 0 || conf->filter_coeff > 8)
+ {
+ return -EINVAL;
+ }
+
+ if (conf->resolution < MCP9600_ADC_RES_18 ||
+ conf->resolution > MCP9600_ADC_RES_12)
+ {
+ return -EINVAL;
+ }
+
+ if (conf->num_samples < MCP9600_SAMPLE_1 ||
+ conf->num_samples > MCP9600_SAMPLE_128)
+ {
+ return -EINVAL;
+ }
+
+ if (conf->mode < MCP9600_MODE_NORMAL || conf->mode > MCP9600_MODE_BURST)
+ {
+ return -EINVAL;
+ }
+
+ if (conf->cold_res != MCP9600_COLDRES_0625 ||
+ conf->cold_res != MCP9600_COLDRES_25)
+ {
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/****************************************************************************
+ * Name: mcp9600_open
+ *
+ * Description:
+ * This function is called whenever the MCP9600 device is opened.
+ *
+ ****************************************************************************/
+
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+static int mcp9600_open(FAR struct file *filep)
+{
+ FAR struct inode *inode = filep->f_inode;
+ FAR struct mcp9600_dev_s *priv = inode->i_private;
+ int err;
+
+ err = nxmutex_lock(&priv->devlock);
+ if (err < 0)
+ {
+ return err;
+ }
+
+ /* Increment the count of open references on the driver */
+
+ priv->crefs++;
+ DEBUGASSERT(priv->crefs > 0);
+
+ nxmutex_unlock(&priv->devlock);
+ return 0;
+}
+#endif
+
+/****************************************************************************
+ * Name: mcp9600_close
+ *
+ * Description:
+ * This routine is called when the MCP9600 device is closed.
+ *
+ ****************************************************************************/
+
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+static int mcp9600_close(FAR struct file *filep)
+{
+ FAR struct inode *inode = filep->f_inode;
+ FAR struct mcp9600_dev_s *priv = inode->i_private;
+ int err;
+
+ err = nxmutex_lock(&priv->devlock);
+ if (err < 0)
+ {
+ return err;
+ }
+
+ /* Decrement the count of open references on the driver */
+
+ DEBUGASSERT(priv->crefs > 0);
+ priv->crefs--;
+
+ /* If the count has decremented to zero and the driver has been unlinked,
+ * then free memory now.
+ */
+
+ if (priv->crefs <= 0 && priv->unlinked)
+ {
+ nxmutex_destroy(&priv->devlock);
+ kmm_free(priv);
+ return 0;
+ }
+
+ nxmutex_unlock(&priv->devlock);
+ return 0;
+}
+#endif
+
+/****************************************************************************
+ * Name: mcp9600_unlink
+ ****************************************************************************/
+
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+static int mcp9600_unlink(FAR struct inode *inode)
+{
+ FAR struct mcp9600_dev_s *priv;
+ int err;
+
+ DEBUGASSERT(inode->i_private != NULL);
+ priv = inode->i_private;
+
+ err = nxmutex_lock(&priv->devlock);
+ if (err < 0)
+ {
+ return err;
+ }
+
+ /* Are there open references to the driver data structure? */
+
+ if (priv->crefs <= 0)
+ {
+ nxmutex_destroy(&priv->devlock);
+ kmm_free(priv);
+ return 0;
+ }
+
+ /* No. Just mark the driver as unlinked and free the resources when
+ * the last client closes their reference to the driver.
+ */
+
+ priv->unlinked = true;
+ nxmutex_unlock(&priv->devlock);
+ return OK;
+}
+#endif
+
+/****************************************************************************
+ * Name: mcp9600_read
+ *
+ * Description:
+ * Character driver interface to sensor for debugging.
+ *
+ ****************************************************************************/
+
+static ssize_t mcp9600_read(FAR struct file *filep, FAR char *buffer,
+ size_t buflen)
+{
+ FAR struct inode *inode = filep->f_inode;
+ FAR struct mcp9600_dev_s *priv = inode->i_private;
+ int err;
+ int16_t hot_junc_temp;
+
+ /* If file position is non-zero, then we're at the end of file. */
+
+ if (filep->f_pos > 0)
+ {
+ return 0;
+ }
+
+ /* Get exclusive access */
+
+ err = nxmutex_lock(&priv->devlock);
+ if (err < 0)
+ {
+ return err;
+ }
+
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+ if (priv->unlinked)
+ {
+ /* Do not allow operations on unlinked sensors. This allows
+ * sensor use on hot swappable I2C bus.
+ */
+
+ goto finish_unlock;
+ }
+#endif
+
+ err = mcp9600_read_temp(priv, REG_THERMO_HOT_JUNC, &hot_junc_temp);
+
+ if (err < 0)
+ {
+ goto finish_unlock;
+ }
+
+ err = snprintf(buffer, buflen, "%d C\n", hot_junc_temp);
+
+ if (err > buflen)
+ {
+ err = buflen;
+ }
+
+ filep->f_pos += err;
+
+finish_unlock:
+ nxmutex_unlock(&priv->devlock);
+ return err;
+}
+
+/****************************************************************************
+ * Name: mcp9600_write
+ *
+ * Description:
+ * Not implemented.
+ ****************************************************************************/
+
+static ssize_t mcp9600_write(FAR struct file *filep, FAR const char *buffer,
+ size_t buflen)
+{
+ return -ENOSYS;
+}
+
+/****************************************************************************
+ * Name: mcp9600_ioctl
+ ****************************************************************************/
+
+static int mcp9600_ioctl(FAR struct file *filep, int cmd, unsigned long arg)
+{
+ FAR struct inode *inode = filep->f_inode;
+ FAR struct mcp9600_dev_s *priv = inode->i_private;
+ int err;
+
+ err = nxmutex_lock(&priv->devlock);
+ if (err < 0)
+ {
+ return err;
+ }
+
+#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
+ if (priv->unlinked)
+ {
+ /* Do not allow operations on unlinked sensors. This allows
+ * sensor use on hot swappable I2C bus.
+ */
+
+ nxmutex_unlock(&priv->devlock);
+ return -ENODEV;
+ }
+#endif
+
+ switch (cmd)
+ {
+ /* Device ID */
+
+ case SNIOC_WHO_AM_I:
+ {
+ struct mcp9600_devinfo_s *devinfo =
+ (struct mcp9600_devinfo_s *)(arg);
+ if (devinfo == NULL)
+ {
+ err = -EINVAL;
+ break;
+ }
+
+ err = mcp9600_read_reg(priv, REG_DEVID, devinfo, sizeof(*devinfo));
+ }
+ break;
+
+ /* Raw ADC data */
+
+ case SNIOC_READ_RAW_DATA:
+ {
+ int32_t *raw_data = (int32_t *)(arg);
+ if (raw_data == NULL)
+ {
+ err = -EINVAL;
+ break;
+ }
+
+ err = mcp9600_read_reg(priv, REG_RAW_ADC, raw_data,
+ 3); /* Only read 24 bits */
+
+ /* Sign bit 1, set all upper bits to 1 for correct value in 32-bit
+ * signed integer.
+ */
+
+ if (*raw_data & 0x100000)
+ {
+ *raw_data |= 0xfffc0000;
+ }
+ }
+
+ case SNIOC_CHECK_STATUS_REG:
+ {
+ uint8_t status_reg;
+ struct mcp9600_status_s *status = (struct mcp9600_status_s *)(arg);
+ if (status == NULL)
+ {
+ err = -EINVAL;
+ break;
+ }
+
+ err = mcp9600_read_reg(priv, REG_STATUS, &status_reg,
+ sizeof(status_reg));
+ if (err < 0)
+ {
+ break;
+ }
+
+ /* Set bits */
+
+ status->burst_complete = status_reg & 0x80;
+ status->temp_update = status_reg & 0x40;
+ status->temp_exceeded = status_reg & 0x10;
+ status->alerts[0] = status_reg & 0x1;
+ status->alerts[1] = status_reg & 0x2;
+ status->alerts[2] = status_reg & 0x4;
+ status->alerts[3] = status_reg & 0x8;
+
+ /* Clear what has been read (burst & temp registers) */
+
+ status_reg &= 0x3f;
+ err = mcp9600_write_reg(priv, REG_STATUS, &status_reg,
+ sizeof(status_reg));
+ }
+ break;
+
+ /* Configure the MCP9600 */
+
+ case SNIOC_CONFIGURE:
+ {
+ uint8_t registers[2] =
+ {
+ 0, 0
+ };
+
+ struct mcp9600_devconf_s *conf = (struct mcp9600_devconf_s *)(arg);
+
+ /* Validate options */
+
+ err = mcp9600_validate_conf(conf);
+ if (err < 0)
+ {
+ break;
+ };
+
+ /* Sensor configuration */
+
+ registers[0] |= ((conf->thermo_type & 0x7) << 4);
+ registers[0] |= (conf->filter_coeff & 0x7);
+
+ /* Device configuration */
+
+ registers[1] |= (conf->mode & 0x3);
+ registers[1] |= ((conf->num_samples & 0x7) << 2);
+ registers[1] |= ((conf->resolution & 0x3) << 5);
+ registers[1] |= ((conf->cold_res & 0x1) << 7);
+
+ /* Copy in options. Since the sensor configuration and device
+ * configuration registers are sequential, we can do this in one
+ * write operation.
+ */
+
+ err = mcp9600_write_reg(priv, REG_THERMO_SEN_CONF, registers,
+ sizeof(registers));
+ if (err < 0)
+ {
+ break;
+ };
+
+ /* Store this as the official configuration */
+
+ memcpy(&priv->conf, conf, sizeof(priv->conf));
+ }
+
+ /* Configure alerts */
+
+ case SNIOC_WRITECONF:
+ {
+ struct mcp9600_alert_conf_s *conf =
+ (struct mcp9600_alert_conf_s *)(arg);
+ if (conf == NULL)
+ {
+ err = -EINVAL;
+ break;
+ }
+
+ err = mcp9600_config_alert(priv, conf);
+ }
+
+ /* Read temperature data */
+
+ case SNIOC_READTEMP:
+ {
+ struct mcp9600_temp_s *temps = (struct mcp9600_temp_s *)(arg);
+ if (temps == NULL)
+ {
+ err = -EINVAL;
+ break;
+ }
+
+ err =
+ mcp9600_read_temp(priv, REG_JUNC_TEMP_DELTA, &temps->temp_delta);
+ if (err < 0)
+ {
+ break;
+ };
+
+ err = mcp9600_read_temp(priv, REG_THERMO_HOT_JUNC, &temps->hot_junc);
+ if (err < 0)
+ {
+ break;
+ };
+
+ err = mcp9600_read_temp(priv, REG_COLD_JUNC_TEMP, &temps->cold_junc);
+ }
+
+ /* Shutdown the device (argument unused) */
+
+ case SNIOC_SHUTDOWN:
+ {
+ uint8_t reg = 0;
+ priv->conf.mode = MCP9600_MODE_SHUTDOWN;
+
+ reg |= (priv->conf.mode & 0x3);
+ reg |= ((priv->conf.num_samples & 0x7) << 2);
+ reg |= ((priv->conf.resolution & 0x3) << 5);
+ reg |= ((priv->conf.cold_res & 0x1) << 7);
+
+ err = mcp9600_write_reg(priv, REG_DEV_CONFIG, ®, sizeof(reg));
+ }
+
+ /* Start the device again */
+
+ case SNIOC_START:
+ {
+ uint8_t reg = 0;
+ priv->conf.mode = MCP9600_MODE_NORMAL;
+
+ reg |= (priv->conf.mode & 0x3);
+ reg |= ((priv->conf.num_samples & 0x7) << 2);
+ reg |= ((priv->conf.resolution & 0x3) << 5);
+ reg |= ((priv->conf.cold_res & 0x1) << 7);
+
+ err = mcp9600_write_reg(priv, REG_DEV_CONFIG, ®, sizeof(reg));
+ }
+
+ default:
+ err = -EINVAL;
+ break;
+ }
+
+ nxmutex_unlock(&priv->devlock);
+ return err;
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: mcp9600_register
+ *
+ * Description:
+ * Register the MCP9600 character device as 'devpath'
+ *
+ * Input Parameters:
+ * devpath - The full path to the driver to register. E.g., "/dev/temp0"
+ * i2c - An instance of the I2C interface to use to communicate with
+ * the MCP9600
+ * addr - The I2C address of the MCP9600, between 0x60 and 0x67
+ *
+ * Returned Value:
+ * Zero (OK) on success; a negated errno value on failure.
+ *
+ ****************************************************************************/
+
+int mcp9600_register(FAR const char *devpath, FAR struct i2c_master_s *i2c,
+ uint8_t addr)
+{
+ FAR struct mcp9600_dev_s *priv;
+ int err;
+
+ DEBUGASSERT(i2c != NULL);
+ DEBUGASSERT(addr <= 0x67 && addr >= 0x60);
+
+ /* Initialize the device structure */
+
+ priv = kmm_zalloc(sizeof(struct mcp9600_dev_s));
+ if (priv == NULL)
+ {
+ snerr("ERROR: Failed to allocate instance.\n");
+ return -ENOMEM;
+ }
+
+ priv->i2c = i2c;
+ priv->addr = addr;
+
+ priv->conf = (struct mcp9600_devconf_s)
+ {
+ .thermo_type = MCP9600_THERMO_TYPE_T,
+ .filter_coeff = 0,
+ .resolution = MCP9600_ADC_RES_18,
+ .num_samples = MCP9600_SAMPLE_1,
+ .mode = MCP9600_MODE_NORMAL,
+ .mode = MCP9600_COLDRES_0625,
+ };
+
+ priv->unlinked = false;
+ priv->crefs = 0;
+
+ /* Initialize mutex */
+
+ err = nxmutex_init(&priv->devlock);
+ if (err < 0)
+ {
+ snerr("ERROR: Failed to register MCP9600 driver: %d\n", err);
+ kmm_free(priv);
+ return err;
+ }
+
+ /* Register the character driver */
+
+ err = register_driver(devpath, &g_mcp9600fops, 0666, priv);
+ if (err < 0)
+ {
+ snerr("ERROR: Failed to register MCP9600 driver: %d\n", err);
+ nxmutex_destroy(&priv->devlock);
+ kmm_free(priv);
+ }
+
+ return err;
+}
diff --git a/include/nuttx/sensors/mcp9600.h b/include/nuttx/sensors/mcp9600.h
new file mode 100644
index 0000000000..e336cbaef2
--- /dev/null
+++ b/include/nuttx/sensors/mcp9600.h
@@ -0,0 +1,197 @@
+/****************************************************************************
+ * include/nuttx/sensors/mcp9600.h
+ *
+ * Contributed by Matteo Golin
+ *
+ * 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_MCP9600_H
+#define __INCLUDE_NUTTX_SENSORS_MCP9600_H
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/sensors/ioctl.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+/****************************************************************************
+ * Pre-processor definitions
+ ****************************************************************************/
+
+#define MCP9600_REV_MAJOR(rev) (((rev) & 0xf0) >> 4) /* Major revision */
+#define MCP9600_REV_MINOR(rev) ((rev) & 0x0f) /* Minor revision */
+
+/****************************************************************************
+ * Public Types
+ ****************************************************************************/
+
+struct i2c_master_s; /* Forward reference */
+
+/* Alerts */
+
+enum mcp9600_alert_e
+{
+ MCP9600_ALERT1 = 0, /* Alert 1 */
+ MCP9600_ALERT2 = 1, /* Alert 2 */
+ MCP9600_ALERT3 = 2, /* Alert 3 */
+ MCP9600_ALERT4 = 3, /* Alert 4 */
+};
+
+/* Thermocouple types */
+
+enum mcp9600_thermocouple_e
+{
+ MCP9600_THERMO_TYPE_K = 0x0,
+ MCP9600_THERMO_TYPE_J = 0x1,
+ MCP9600_THERMO_TYPE_T = 0x2,
+ MCP9600_THERMO_TYPE_N = 0x3,
+ MCP9600_THERMO_TYPE_S = 0x4,
+ MCP9600_THERMO_TYPE_E = 0x5,
+ MCP9600_THERMO_TYPE_B = 0x6,
+ MCP9600_THERMO_TYPE_R = 0x7,
+};
+
+/* ADC resolution */
+
+enum mcp9600_resolution_e
+{
+ MCP9600_ADC_RES_12 = 0x3, /* 12 bits */
+ MCP9600_ADC_RES_14 = 0x2, /* 14 bits */
+ MCP9600_ADC_RES_16 = 0x1, /* 16 bits */
+ MCP9600_ADC_RES_18 = 0x0, /* 18 bits */
+};
+
+/* Number of samples */
+
+enum mcp9600_samples_e
+{
+ MCP9600_SAMPLE_1 = 0x0,
+ MCP9600_SAMPLE_2 = 0x1,
+ MCP9600_SAMPLE_4 = 0x2,
+ MCP9600_SAMPLE_8 = 0x3,
+ MCP9600_SAMPLE_16 = 0x4,
+ MCP9600_SAMPLE_32 = 0x5,
+ MCP9600_SAMPLE_64 = 0x6,
+ MCP9600_SAMPLE_128 = 0x7,
+};
+
+/* Shutdown mode options */
+
+enum mcp9600_modes_e
+{
+ MCP9600_MODE_NORMAL = 0x0, /* Normal mode */
+ MCP9600_MODE_SHUTDOWN = 0x1, /* Shutdown mode */
+ MCP9600_MODE_BURST = 0x2, /* Burst mode */
+};
+
+/* Cold junction resolutions */
+
+enum mcp9600_cold_res_e
+{
+ MCP9600_COLDRES_25 = 1, /* 0.25 degrees Celsius */
+ MCP9600_COLDRES_0625 = 0, /* 0.0625 degrees Celsius */
+};
+
+/* Alert configuration */
+
+struct mcp9600_alert_conf_s
+{
+ enum mcp9600_alert_e
+ alert; /* The alert associated with this configuration */
+ int16_t limit; /* The temperature limit for the alert, in 0.25 degrees
+ * Celsius/LSB */
+ uint8_t temp; /* The temperature for the hysteresis threshold, in
degrees
+ * Celsius. */
+ bool cold_junc; /* True to monitor cold junction, false to monitor
+ * thermocouple */
+ bool falling_temp; /* True to monitor for falling temperature, false to
+ * monitor rising */
+ bool active_high; /* False for active low */
+ bool int_mode; /* Interrupt mode, or false for comparator mode */
+ bool enable; /* Enable alert output */
+};
+
+/* Device configuration of the MCP9600 */
+
+struct mcp9600_devconf_s
+{
+ enum mcp9600_thermocouple_e thermo_type; /* Thermocouple type */
+ uint8_t filter_coeff; /* Filter coefficient */
+ enum mcp9600_resolution_e resolution; /* ADC resolution */
+ enum mcp9600_samples_e num_samples; /* Number of samples */
+ enum mcp9600_modes_e mode; /* Mode of operation */
+ enum mcp9600_cold_res_e cold_res; /* Resolution of the cold
+ * junction */
+};
+
+/* Device information of the MCP9600 */
+
+struct mcp9600_devinfo_s
+{
+ uint8_t devid; /* Device ID */
+ uint8_t revision; /* Revision number; major 4 MSBs, minor 3 LSBs */
+};
+
+/* Device status */
+
+struct mcp9600_status_s
+{
+ bool burst_complete; /* Burst mode conversions complete */
+ bool temp_update; /* Temperature updated */
+ bool temp_exceeded; /* Temperature range exceeded */
+ bool alerts[4]; /* Alert statuses for alerts 1-4 (0-3) */
+};
+
+/* Temperature readings */
+
+struct mcp9600_temp_s
+{
+ int16_t temp_delta; /* Temperature delta in degrees Celsius */
+ int16_t hot_junc; /* Hot junction temperature in degrees Celsius */
+ int16_t cold_junc; /* Cold junction temperature in degrees Celsius */
+};
+
+/****************************************************************************
+ * Public Function Prototypes
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: mcp9600_register
+ *
+ * Description:
+ * Register the MCP9600 character device as 'devpath'
+ *
+ * Input Parameters:
+ * devpath - The full path to the driver to register. E.g., "/dev/temp0"
+ * i2c - An instance of the I2C interface to use to communicate with
+ * the MCP9600
+ * addr - The I2C address of the MCP9600, between 0x60 and 0x67
+ *
+ * Returned Value:
+ * Zero (OK) on success; a negated errno value on failure.
+ *
+ ****************************************************************************/
+
+int mcp9600_register(FAR const char *devpath, FAR struct i2c_master_s *i2c,
+ uint8_t addr);
+
+#endif /* __INCLUDE_NUTTX_SENSORS_MCP9600_H */