From 895067a7977e2964705625cb7ab4c303c8b38ce0 Mon Sep 17 00:00:00 2001 From: "Daniel P. Carvalho" Date: Thu, 17 Sep 2026 07:00:57 -0300 Subject: [PATCH] drivers/sensors: add Microchip TC74 temperature sensor driver Add support for the Microchip TC74 digital temperature sensor using the Sensor Driver Framework (uORB). The TC74 is an 8-bit I2C temperature sensor with a measurement range from -40C to +125C and a resolution of 1C. The driver registers as a uORB topic (/dev/uorb/sensor_temp) and polls on the low-priority work queue. It supports dynamic interval configuration and automatically enters low-power standby mode when the topic is deactivated. Validated against a real TC74A5-3.3 on a custom STM32H743BI board. Assisted-by: Gemini:gemini-3.8-pro Signed-off-by: Daniel P. Carvalho --- .../components/drivers/special/sensors.rst | 1 + .../drivers/special/sensors/sensors_uorb.rst | 1 + .../drivers/special/sensors/tc74.rst | 46 ++ drivers/sensors/CMakeLists.txt | 4 + drivers/sensors/Kconfig | 18 + drivers/sensors/Make.defs | 4 + drivers/sensors/tc74.c | 412 ++++++++++++++++++ include/nuttx/sensors/tc74.h | 94 ++++ 8 files changed, 580 insertions(+) create mode 100644 Documentation/components/drivers/special/sensors/tc74.rst create mode 100644 drivers/sensors/tc74.c create mode 100644 include/nuttx/sensors/tc74.h diff --git a/Documentation/components/drivers/special/sensors.rst b/Documentation/components/drivers/special/sensors.rst index 8b0efc303ba2a..9cd4ee931fc16 100644 --- a/Documentation/components/drivers/special/sensors.rst +++ b/Documentation/components/drivers/special/sensors.rst @@ -37,6 +37,7 @@ tool for monitoring sensor activity at runtime. sensors/nau7802.rst sensors/qmi8658.rst sensors/sht4x.rst + sensors/tc74.rst sensors/tmp112.rst sensors/lsm6dso32.rst sensors/lsm6ds3trc.rst diff --git a/Documentation/components/drivers/special/sensors/sensors_uorb.rst b/Documentation/components/drivers/special/sensors/sensors_uorb.rst index a2d5547dffde2..fbccd910baf43 100644 --- a/Documentation/components/drivers/special/sensors/sensors_uorb.rst +++ b/Documentation/components/drivers/special/sensors/sensors_uorb.rst @@ -557,6 +557,7 @@ Implemented Drivers - :doc:`nau7802` - :doc:`qmi8658` - :doc:`sht4x` +- :doc:`tc74` - :doc:`tmp112` - :doc:`lsm6dso32` - :doc:`lsm6ds3trc` diff --git a/Documentation/components/drivers/special/sensors/tc74.rst b/Documentation/components/drivers/special/sensors/tc74.rst new file mode 100644 index 0000000000000..3888a80d82b50 --- /dev/null +++ b/Documentation/components/drivers/special/sensors/tc74.rst @@ -0,0 +1,46 @@ +==== +TC74 +==== + +The TC74 is a Microchip serial digital thermal sensor, specified from +-40°C to +125°C, with an eight-bit reading of 1°C a count. Factory-programmed +I2C addresses range from 0x48 through 0x4f (suffixes A0 through A7), allowing +up to eight parts on the same bus. + +This driver uses the :doc:`uorb +` interface, so the reading +appears as a topic the common sensor tools can read. + +Application Programming Interface +================================= + +The header file for the TC74 driver interface can be included using: + +.. code-block:: c + + #include + +Registering the driver creates one topic, ``sensor_temp``, where ``n`` is +the ``devno`` passed in: + +.. code-block:: c + + int tc74_register(int devno, FAR struct i2c_master_s *i2c, uint8_t addr); + +For example, a part with address 0x4d (TC74A5) on I2C bus 0, published as +``sensor_temp0``: + +.. code-block:: c + + FAR struct i2c_master_s *i2c = board_i2cbus_initialize(0); + + tc74_register(0, i2c, TC74_ADDR_A5); + +Reading +======= + +The driver polls on the low priority work queue at whatever interval a +consumer asks for. When activated (for example when an application subscribes +to the sensor topic), the driver wakes the part up from standby mode. When +deactivated (when all subscribers close the topic), it places the part back +into low-power standby mode to save energy. diff --git a/drivers/sensors/CMakeLists.txt b/drivers/sensors/CMakeLists.txt index cd1236853a956..53f8d38b58116 100644 --- a/drivers/sensors/CMakeLists.txt +++ b/drivers/sensors/CMakeLists.txt @@ -360,6 +360,10 @@ if(CONFIG_SENSORS) endif() endif() + if(CONFIG_SENSORS_TC74) + list(APPEND SRCS tc74.c) + endif() + # QMI8658 6-axis IMU if(CONFIG_SENSORS_QMI8658) diff --git a/drivers/sensors/Kconfig b/drivers/sensors/Kconfig index bab401daec886..8e08788fe7f1a 100644 --- a/drivers/sensors/Kconfig +++ b/drivers/sensors/Kconfig @@ -2354,6 +2354,24 @@ config SENSORS_TMP112_UORB endif #SENSORS_TMP112 +config SENSORS_TC74 + bool "Microchip TC74 Temperature Sensor support" + default n + select I2C + ---help--- + Enable driver support for the Microchip TC74 digital temperature sensor. + +if SENSORS_TC74 + +config TC74_I2C_FREQUENCY + int "TC74 I2C frequency" + default 100000 + range 1 100000 + ---help--- + I2C clock frequency for the TC74 sensor. Default is 100 kHz. + +endif # SENSORS_TC74 + config SENSORS_QMI8658 bool "QST QMI8658 6-Axis IMU Sensor support" default n diff --git a/drivers/sensors/Make.defs b/drivers/sensors/Make.defs index d4ef1f00858e5..9ad42cb74ee4e 100644 --- a/drivers/sensors/Make.defs +++ b/drivers/sensors/Make.defs @@ -354,6 +354,10 @@ else endif endif +ifeq ($(CONFIG_SENSORS_TC74),y) + CSRCS += tc74.c +endif + endif # CONFIG_I2C # These drivers depend on SPI support diff --git a/drivers/sensors/tc74.c b/drivers/sensors/tc74.c new file mode 100644 index 0000000000000..390e0f19015a1 --- /dev/null +++ b/drivers/sensors/tc74.c @@ -0,0 +1,412 @@ +/**************************************************************************** + * drivers/sensors/tc74.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 + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#ifndef CONFIG_TC74_I2C_FREQUENCY +# define CONFIG_TC74_I2C_FREQUENCY 100000 +#endif + +/* TC74 Register Addresses */ + +#define TC74_TEMP_REG 0x00 /* Temperature Register (read-only) */ +#define TC74_CONFIG_REG 0x01 /* Configuration Register (read/write) */ + +/* Configuration Register Bit Definitions */ + +#define TC74_CONFIG_STANDBY (1 << 7) /* Bit 7: 1=Standby mode, 0=Normal mode */ + +/* One reading a second by default until requested otherwise */ + +#define TC74_DEFAULT_INTERVAL 1000000 + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct tc74_dev_s +{ + struct sensor_lowerhalf_s lower; /* Must be first */ + FAR struct i2c_master_s *i2c; + uint8_t addr; + uint32_t interval; /* Microseconds between readings */ + struct work_s work; + bool enabled; +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static int tc74_activate(FAR struct sensor_lowerhalf_s *lower, + FAR struct file *filep, bool enable); +static int tc74_set_interval(FAR struct sensor_lowerhalf_s *lower, + FAR struct file *filep, + FAR uint32_t *period_us); +static int tc74_get_info(FAR struct sensor_lowerhalf_s *lower, + FAR struct file *filep, + FAR struct sensor_device_info_s *info); +static void tc74_worker(FAR void *arg); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct sensor_ops_s g_tc74_ops = +{ + .activate = tc74_activate, + .set_interval = tc74_set_interval, + .get_info = tc74_get_info, +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: tc74_delay + * + * Description: + * The requeue delay in ticks for the interval in force, never zero: a + * zero delay would requeue the worker without it ever yielding. + * + * Input Parameters: + * priv - The driver state + * + * Returned Value: + * The delay in clock ticks, at least one. + * + ****************************************************************************/ + +static clock_t tc74_delay(FAR struct tc74_dev_s *priv) +{ + clock_t ticks = priv->interval / USEC_PER_TICK; + + return ticks > 0 ? ticks : 1; +} + +/**************************************************************************** + * Name: tc74_putreg8 + * + * Description: + * Write an 8-bit value to a TC74 register. + * + * Input Parameters: + * priv - The driver state + * regaddr - The register address + * regval - The value to write + * + * Returned Value: + * Zero on success; a negated errno value on failure. + * + ****************************************************************************/ + +static int tc74_putreg8(FAR struct tc74_dev_s *priv, + uint8_t regaddr, uint8_t regval) +{ + struct i2c_msg_s msg; + uint8_t buffer[2]; + int ret; + + buffer[0] = regaddr; + buffer[1] = regval; + + msg.frequency = CONFIG_TC74_I2C_FREQUENCY; + msg.addr = priv->addr; + msg.flags = 0; + msg.buffer = buffer; + msg.length = 2; + + ret = I2C_TRANSFER(priv->i2c, &msg, 1); + if (ret < 0) + { + snerr("ERROR: I2C_TRANSFER failed: %d\n", ret); + } + + return ret; +} + +/**************************************************************************** + * Name: tc74_readraw + * + * Description: + * Read the temperature register and return it as the signed 8-bit count. + * + * Input Parameters: + * priv - The driver state + * raw - Where to return the signed 8-bit temperature in degrees C + * + * Returned Value: + * Zero on success, or a negated errno on failure. + * + ****************************************************************************/ + +static int tc74_readraw(FAR struct tc74_dev_s *priv, FAR int8_t *raw) +{ + struct i2c_msg_s msg[2]; + uint8_t regaddr = TC74_TEMP_REG; + uint8_t val; + int ret; + + msg[0].frequency = CONFIG_TC74_I2C_FREQUENCY; + msg[0].addr = priv->addr; + msg[0].flags = 0; + msg[0].buffer = ®addr; + msg[0].length = 1; + + msg[1].frequency = CONFIG_TC74_I2C_FREQUENCY; + msg[1].addr = priv->addr; + msg[1].flags = I2C_M_READ; + msg[1].buffer = &val; + msg[1].length = 1; + + ret = I2C_TRANSFER(priv->i2c, msg, 2); + if (ret < 0) + { + snerr("ERROR: cannot read temperature: %d\n", ret); + return ret; + } + + *raw = (int8_t)val; + return OK; +} + +/**************************************************************************** + * Name: tc74_worker + * + * Description: + * Take one reading and publish it, then requeue for the next. + * + * Input Parameters: + * arg - The driver state, as passed to work_queue() + * + * Returned Value: + * None. + * + ****************************************************************************/ + +static void tc74_worker(FAR void *arg) +{ + FAR struct tc74_dev_s *priv = arg; + struct sensor_temp temp; + int8_t raw; + + DEBUGASSERT(priv != NULL); + + /* Queue the next reading first, so that a failed transfer costs one + * sample rather than the whole stream. + */ + + work_queue(LPWORK, &priv->work, tc74_worker, priv, + tc74_delay(priv)); + + if (tc74_readraw(priv, &raw) < 0) + { + return; + } + + temp.temperature = sensor_data_itof(raw); + temp.timestamp = sensor_get_timestamp(); + + priv->lower.push_event(priv->lower.priv, &temp, sizeof(temp)); +} + +/**************************************************************************** + * Name: tc74_activate + * + * Description: + * Start or stop the reading stream. + * + * Input Parameters: + * lower - The sensor lower half + * filep - The file that asked, unused + * enable - True to start reading, false to stop + * + * Returned Value: + * Zero on success, or a negated errno on failure. + * + ****************************************************************************/ + +static int tc74_activate(FAR struct sensor_lowerhalf_s *lower, + FAR struct file *filep, bool enable) +{ + FAR struct tc74_dev_s *priv = (FAR struct tc74_dev_s *)lower; + + if (enable == priv->enabled) + { + return OK; + } + + if (enable) + { + /* Wake up TC74 from standby mode */ + + tc74_putreg8(priv, TC74_CONFIG_REG, 0); + + work_queue(LPWORK, &priv->work, tc74_worker, priv, + tc74_delay(priv)); + } + else + { + work_cancel(LPWORK, &priv->work); + + /* Place TC74 into low-power standby mode */ + + tc74_putreg8(priv, TC74_CONFIG_REG, TC74_CONFIG_STANDBY); + } + + priv->enabled = enable; + return OK; +} + +/**************************************************************************** + * Name: tc74_set_interval + * + * Description: + * Set how often to read the part. + * + * Input Parameters: + * lower - The sensor lower half + * filep - The file that asked, unused + * period_us - The interval wanted, updated to the interval granted + * + * Returned Value: + * Zero on success, or a negated errno on failure. + * + ****************************************************************************/ + +static int tc74_set_interval(FAR struct sensor_lowerhalf_s *lower, + FAR struct file *filep, + FAR uint32_t *period_us) +{ + FAR struct tc74_dev_s *priv = (FAR struct tc74_dev_s *)lower; + + priv->interval = *period_us; + return OK; +} + +/**************************************************************************** + * Name: tc74_get_info + * + * Description: + * Describe the part: vendor, range, and resolution. + * + * Input Parameters: + * lower - The sensor lower half + * filep - The file that asked, unused + * info - Where to return the description + * + * Returned Value: + * Zero on success. + * + ****************************************************************************/ + +static int tc74_get_info(FAR struct sensor_lowerhalf_s *lower, + FAR struct file *filep, + FAR struct sensor_device_info_s *info) +{ + info->version = 0; + info->power = 0.2f; /* 200 uA operating current */ + info->max_range = 125.0f; /* Specified -40C to +125C */ + info->resolution = 1.0f; /* 8-bit signed, 1C step */ + info->min_delay = 0; + info->max_delay = 0; + info->fifo_reserved_event_count = 0; + info->fifo_max_event_count = 0; + strlcpy(info->name, "TC74", sizeof(info->name)); + strlcpy(info->vendor, "Microchip", sizeof(info->vendor)); + return OK; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: tc74_register + * + * Description: + * Register the TC74 as a uORB temperature sensor. + * + * Input Parameters: + * devno - The topic number, giving /dev/uorb/sensor_temp + * i2c - The bus the part is on + * addr - The 7-bit bus address (e.g., TC74_ADDR_A0 through TC74_ADDR_A7) + * + * Returned Value: + * Zero (OK) on success; a negated errno on failure. + * + ****************************************************************************/ + +int tc74_register(int devno, FAR struct i2c_master_s *i2c, uint8_t addr) +{ + FAR struct tc74_dev_s *priv; + int ret; + + DEBUGASSERT(i2c != NULL); + + priv = kmm_zalloc(sizeof(struct tc74_dev_s)); + if (priv == NULL) + { + return -ENOMEM; + } + + priv->i2c = i2c; + priv->addr = addr; + priv->interval = TC74_DEFAULT_INTERVAL; + priv->lower.ops = &g_tc74_ops; + priv->lower.type = SENSOR_TYPE_TEMPERATURE; + + /* Put sensor in standby by default until activated */ + + tc74_putreg8(priv, TC74_CONFIG_REG, TC74_CONFIG_STANDBY); + + ret = sensor_register(&priv->lower, devno); + if (ret < 0) + { + snerr("ERROR: cannot register: %d\n", ret); + kmm_free(priv); + return ret; + } + + sninfo("TC74 at %02x registered as sensor_temp%d\n", addr, devno); + return OK; +} diff --git a/include/nuttx/sensors/tc74.h b/include/nuttx/sensors/tc74.h new file mode 100644 index 0000000000000..52d14a919e9f5 --- /dev/null +++ b/include/nuttx/sensors/tc74.h @@ -0,0 +1,94 @@ +/**************************************************************************** + * include/nuttx/sensors/tc74.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_TC74_H +#define __INCLUDE_NUTTX_SENSORS_TC74_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#if defined(CONFIG_I2C) && defined(CONFIG_SENSORS_TC74) + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* TC74 Factory I2C Addresses (7-bit) + * The address is factory programmed according to the part suffix (A0-A7). + */ + +#define TC74_ADDR_A0 0x48 +#define TC74_ADDR_A1 0x49 +#define TC74_ADDR_A2 0x4a +#define TC74_ADDR_A3 0x4b +#define TC74_ADDR_A4 0x4c +#define TC74_ADDR_A5 0x4d +#define TC74_ADDR_A6 0x4e +#define TC74_ADDR_A7 0x4f + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +struct i2c_master_s; + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +#ifdef __cplusplus +#define EXTERN extern "C" +extern "C" +{ +#else +#define EXTERN extern +#endif + +/**************************************************************************** + * Name: tc74_register + * + * Description: + * Register the TC74 sensor as a uORB temperature sensor, appearing as + * /dev/uorb/sensor_temp. + * + * Input Parameters: + * devno - The topic number, giving /dev/uorb/sensor_temp + * i2c - An instance of the I2C interface to communicate with the TC74 + * addr - The 7-bit I2C address of the TC74 (e.g., TC74_ADDR_A5) + * + * Returned Value: + * Zero (OK) on success; a negated errno value on failure. + * + ****************************************************************************/ + +int tc74_register(int devno, FAR struct i2c_master_s *i2c, uint8_t addr); + +#undef EXTERN +#ifdef __cplusplus +} +#endif + +#endif /* CONFIG_I2C && CONFIG_SENSORS_TC74 */ +#endif /* __INCLUDE_NUTTX_SENSORS_TC74_H */