From 769862ac6ae2057acdd711aaaf035aaf9fd8e793 Mon Sep 17 00:00:00 2001 From: Justin Hammond Date: Tue, 4 Aug 2026 22:42:12 +0800 Subject: [PATCH] sensors/tmp112: Add a uORB interface. The TMP112 driver was character mode only, and carried the warning that says so: a read returns a bare float, at a size the driver chose, and nothing but code written for this one part can make sense of it. Add the sensor framework version beside it, in the shape the tree already uses for a part that has both. The old driver is untouched and still builds by default; the new one replaces it when SENSORS_TMP112_UORB is set, and the part then appears as a temperature topic that the common sensor tools can read without knowing what a TMP112 is. It reads on the low priority work queue at whatever interval the caller asks for. The part converts continuously out of reset, so nothing is configured and the temperature register always holds the last completed conversion: a reading is one bus transaction with nothing to wait for. Reading faster than the part converts repeats a value, which costs bus traffic and nothing else, so the interval is taken as given: the upper half treats a lower half that hands back a longer interval than it was given as a failed request, so clamping here would refuse a fast caller rather than serve it slowly. get_info reports what the part is and what its readings mean, so a consumer need not know it is talking to a TMP112 to know the range and the resolution. It also sign extends the reading. The register holds twelve bits, and the character mode driver treats them as unsigned, so anything below freezing comes back as a large positive temperature; the part is specified down to -40C. Fixing that in the old driver would change what existing callers see, so it is fixed here, where there are no callers yet to surprise. This driver also covers the TMP102, which differs in accuracy rather than in its registers: only the two both parts have are touched. Documented under the sensors section, beside the other parts with a page of their own, and listed among the uORB drivers. Assisted-by: Claude:claude-opus-5 Signed-off-by: Justin Hammond --- .../components/drivers/special/sensors.rst | 1 + .../drivers/special/sensors/sensors_uorb.rst | 1 + .../drivers/special/sensors/tmp112.rst | 59 +++ drivers/sensors/CMakeLists.txt | 6 +- drivers/sensors/Kconfig | 18 + drivers/sensors/Make.defs | 4 + drivers/sensors/tmp112_uorb.c | 408 ++++++++++++++++++ include/nuttx/sensors/tmp112.h | 22 + 8 files changed, 518 insertions(+), 1 deletion(-) create mode 100644 Documentation/components/drivers/special/sensors/tmp112.rst create mode 100644 drivers/sensors/tmp112_uorb.c diff --git a/Documentation/components/drivers/special/sensors.rst b/Documentation/components/drivers/special/sensors.rst index a8c91a0ce3b..08b1122b181 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/tmp112.rst sensors/lsm6dso32.rst sensors/lis2mdl.rst sensors/l86xxx.rst diff --git a/Documentation/components/drivers/special/sensors/sensors_uorb.rst b/Documentation/components/drivers/special/sensors/sensors_uorb.rst index da61b475c10..9e6a2fea858 100644 --- a/Documentation/components/drivers/special/sensors/sensors_uorb.rst +++ b/Documentation/components/drivers/special/sensors/sensors_uorb.rst @@ -557,5 +557,6 @@ Implemented Drivers - :doc:`nau7802` - :doc:`qmi8658` - :doc:`sht4x` +- :doc:`tmp112` - :doc:`lsm6dso32` - wtgahrs2 diff --git a/Documentation/components/drivers/special/sensors/tmp112.rst b/Documentation/components/drivers/special/sensors/tmp112.rst new file mode 100644 index 00000000000..a1315867848 --- /dev/null +++ b/Documentation/components/drivers/special/sensors/tmp112.rst @@ -0,0 +1,59 @@ +====== +TMP112 +====== + +The TMP112 is a Texas Instruments I2C temperature sensor, specified from +-40°C to +125°C, with a twelve bit reading of 0.0625°C a count. Two address +pins select one of four bus addresses, 0x48 through 0x4b, so four parts can +share a bus. + +Two drivers exist for this part. This one uses the :doc:`uorb +` interface, so the reading +appears as a topic the common sensor tools can read. The older driver presents +a character device instead. Only one of the two is built: +``CONFIG_SENSORS_TMP112_UORB`` selects this one, and the board must then call +``tmp112_register_uorb()`` rather than ``tmp112_register()``, since the +registration interfaces differ. + +The uORB driver sign extends the reading. The character device driver does +not, so it reports anything below freezing as a large positive number, and +this part is specified down to -40°C. + +Application Programming Interface +================================= + +The header file for the TMP112 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 tmp112_register_uorb(int devno, FAR struct i2c_master_s *i2c, + uint8_t addr); + +For example, a part strapped to 0x48 on I2C bus 0, published as +``sensor_temp0``: + +.. code-block:: c + + struct i2c_master_s *i2c = board_i2cbus_initialize(0); + + tmp112_register_uorb(0, i2c, 0x48); + +Reading +======= + +The part converts continuously out of reset, so the driver configures nothing +and the temperature register always holds the last completed conversion. A +read is therefore one bus transaction with no wait, and the driver polls on +the low priority work queue at whatever interval a consumer asks for. + +Asking for samples faster than the part produces them returns the same value +again, so the driver reports back the interval it will really use rather than +the one requested. A consumer that wants to know what it is getting should +read the interval back after setting it. diff --git a/drivers/sensors/CMakeLists.txt b/drivers/sensors/CMakeLists.txt index bfcf3614c25..616af9d301d 100644 --- a/drivers/sensors/CMakeLists.txt +++ b/drivers/sensors/CMakeLists.txt @@ -349,7 +349,11 @@ if(CONFIG_SENSORS) endif() if(CONFIG_SENSORS_TMP112) - list(APPEND SRCS tmp112.c) + if(CONFIG_SENSORS_TMP112_UORB) + list(APPEND SRCS tmp112_uorb.c) + else() + list(APPEND SRCS tmp112.c) + endif() endif() # QMI8658 6-axis IMU diff --git a/drivers/sensors/Kconfig b/drivers/sensors/Kconfig index f10ef20d0c1..43f4a441ded 100644 --- a/drivers/sensors/Kconfig +++ b/drivers/sensors/Kconfig @@ -2262,6 +2262,24 @@ config TMP112_I2C_FREQUENCY int "TMP112 I2C frequency" default 400000 +config SENSORS_TMP112_UORB + bool "TMP112 UORB Interface" + default n + depends on UORB + ---help--- + Build the sensor framework version of this driver rather than + the character device one. The part then appears as a uORB + temperature topic, which the common sensor tools can read, + instead of as a node returning a bare float. + + The two cannot be built together, and the register interface + differs, so a board choosing this must call + tmp112_register_uorb() rather than tmp112_register(). + + This version also sign extends the reading, which the + character device one does not, so temperatures below freezing + are reported correctly rather than as large positive ones. + endif #SENSORS_TMP112 config SENSORS_QMI8658 diff --git a/drivers/sensors/Make.defs b/drivers/sensors/Make.defs index 13cd2adebea..6cfcd615bd4 100644 --- a/drivers/sensors/Make.defs +++ b/drivers/sensors/Make.defs @@ -343,8 +343,12 @@ ifeq ($(CONFIG_SENSORS_AMG88XX),y) endif ifeq ($(CONFIG_SENSORS_TMP112),y) +ifeq ($(CONFIG_SENSORS_TMP112_UORB),y) + CSRCS += tmp112_uorb.c +else CSRCS += tmp112.c endif +endif endif # CONFIG_I2C diff --git a/drivers/sensors/tmp112_uorb.c b/drivers/sensors/tmp112_uorb.c new file mode 100644 index 00000000000..ab6edb34b3f --- /dev/null +++ b/drivers/sensors/tmp112_uorb.c @@ -0,0 +1,408 @@ +/**************************************************************************** + * drivers/sensors/tmp112_uorb.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 + +#if defined(CONFIG_SENSORS_TMP112) && defined(CONFIG_SENSORS_TMP112_UORB) + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#ifndef CONFIG_TMP112_I2C_FREQUENCY +# define CONFIG_TMP112_I2C_FREQUENCY 400000 +#endif + +/* One reading a second, until something asks for another rate. This part + * measures the room rather than anything that moves quickly. + */ + +#define TMP112_DEFAULT_INTERVAL 1000000 + +/* Twelve bits, a sixteenth of a degree each. */ + +#define TMP112_SIGN_BIT 0x0800 +#define TMP112_SIGN_EXTEND 0xf000 +#define TMP112_LSB_NUM 1 +#define TMP112_LSB_DEN 16 + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct tmp112_dev_uorb_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 tmp112_uorb_activate(FAR struct sensor_lowerhalf_s *lower, + FAR struct file *filep, bool enable); +static int tmp112_uorb_set_interval(FAR struct sensor_lowerhalf_s *lower, + FAR struct file *filep, + FAR uint32_t *period_us); +static int tmp112_uorb_get_info(FAR struct sensor_lowerhalf_s *lower, + FAR struct file *filep, + FAR struct sensor_device_info_s *info); +static void tmp112_uorb_worker(FAR void *arg); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct sensor_ops_s g_tmp112_uorb_ops = +{ + .activate = tmp112_uorb_activate, + .set_interval = tmp112_uorb_set_interval, + .get_info = tmp112_uorb_get_info, +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: tmp112_uorb_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 tmp112_uorb_delay(FAR struct tmp112_dev_uorb_s *priv) +{ + clock_t ticks = priv->interval / USEC_PER_TICK; + + return ticks > 0 ? ticks : 1; +} + +/**************************************************************************** + * Name: tmp112_uorb_readraw + * + * Description: + * Read the temperature register and return it as the twelve bit signed + * count the part holds. + * + * Input Parameters: + * priv - The driver state + * raw - Where to return the count, sign extended from twelve bits + * + * Returned Value: + * Zero on success, or a negated errno on failure. + * + ****************************************************************************/ + +static int tmp112_uorb_readraw(FAR struct tmp112_dev_uorb_s *priv, + FAR int16_t *raw) +{ + struct i2c_msg_s msg[2]; + uint8_t regaddr = TMP112_REG_TEMP; + uint8_t buffer[2]; + uint16_t value; + int ret; + + msg[0].frequency = CONFIG_TMP112_I2C_FREQUENCY; + msg[0].addr = priv->addr; + msg[0].flags = 0; + msg[0].buffer = ®addr; + msg[0].length = 1; + + msg[1].frequency = CONFIG_TMP112_I2C_FREQUENCY; + msg[1].addr = priv->addr; + msg[1].flags = I2C_M_READ; + msg[1].buffer = buffer; + msg[1].length = 2; + + /* The pointer and the read go out as one transaction. The part converts + * continuously and this register always holds the last completed + * conversion, so there is nothing to wait for between the two. + */ + + ret = I2C_TRANSFER(priv->i2c, msg, 2); + if (ret < 0) + { + snerr("ERROR: cannot read the temperature: %d\n", ret); + return ret; + } + + /* The part sends the high byte first, and the reading occupies the top + * twelve bits of the pair. + */ + + value = ((uint16_t)buffer[0] << 4) | (buffer[1] >> 4); + + /* Sign extend from twelve bits. The character mode driver does not do + * this, so it reads anything below freezing as a large positive number; + * the part itself is specified down to -40C. + */ + + if ((value & TMP112_SIGN_BIT) != 0) + { + value |= TMP112_SIGN_EXTEND; + } + + *raw = (int16_t)value; + return OK; +} + +/**************************************************************************** + * Name: tmp112_uorb_worker + * + * Description: + * Take one reading and publish it, then requeue for the next. The + * requeue happens first so a failed transfer costs one sample rather + * than ending the stream. + * + * Input Parameters: + * arg - The driver state, as passed to work_queue() + * + * Returned Value: + * None. + * + ****************************************************************************/ + +static void tmp112_uorb_worker(FAR void *arg) +{ + FAR struct tmp112_dev_uorb_s *priv = arg; + struct sensor_temp temp; + int16_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, tmp112_uorb_worker, priv, + tmp112_uorb_delay(priv)); + + if (tmp112_uorb_readraw(priv, &raw) < 0) + { + return; + } + + temp.temperature = sensor_data_divi(sensor_data_itof(raw), + TMP112_LSB_DEN); + temp.timestamp = sensor_get_timestamp(); + + priv->lower.push_event(priv->lower.priv, &temp, sizeof(temp)); +} + +/**************************************************************************** + * Name: tmp112_uorb_activate + * + * Description: + * Start or stop the reading stream. The part measures whether or not + * anything is listening, so this only starts and stops the work that + * collects and publishes. + * + * 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 tmp112_uorb_activate(FAR struct sensor_lowerhalf_s *lower, + FAR struct file *filep, bool enable) +{ + FAR struct tmp112_dev_uorb_s *priv = + (FAR struct tmp112_dev_uorb_s *)lower; + + if (enable == priv->enabled) + { + return OK; + } + + if (enable) + { + work_queue(LPWORK, &priv->work, tmp112_uorb_worker, priv, + tmp112_uorb_delay(priv)); + } + else + { + work_cancel(LPWORK, &priv->work); + } + + priv->enabled = enable; + return OK; +} + +/**************************************************************************** + * Name: tmp112_uorb_set_interval + * + * Description: + * Set how often to read the part. + * + * The interval is taken as asked. The upper half rejects a lower half + * that hands back a longer interval than it was given, so clamping here + * would fail the request rather than grant a slower rate. Reading faster + * than the part converts repeats a value, which costs bus traffic and + * nothing else. + * + * 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 tmp112_uorb_set_interval(FAR struct sensor_lowerhalf_s *lower, + FAR struct file *filep, + FAR uint32_t *period_us) +{ + FAR struct tmp112_dev_uorb_s *priv = + (FAR struct tmp112_dev_uorb_s *)lower; + + priv->interval = *period_us; + return OK; +} + +/**************************************************************************** + * Name: tmp112_uorb_get_info + * + * Description: + * Describe the part to a consumer that asks: what it is, who makes it, and + * the range and resolution its readings carry. Without this a consumer + * would have to know it was talking to a TMP112 to know what the numbers + * mean. + * + * 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 tmp112_uorb_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.01f; /* 10uA quiescent */ + info->max_range = 125.0f; /* Specified -40C to +125C */ + info->resolution = 0.0625f; /* Twelve bits over 128C */ + info->min_delay = 0; + info->max_delay = 0; + info->fifo_reserved_event_count = 0; + info->fifo_max_event_count = 0; + strlcpy(info->name, "TMP112", sizeof(info->name)); + strlcpy(info->vendor, "Texas Instruments", sizeof(info->vendor)); + return OK; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: tmp112_register_uorb + * + * Description: + * Register the TMP112 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 bus address, 0x48 to 0x4b as the pin straps say + * + * Returned Value: + * Zero (OK) on success; a negated errno on failure. + * + ****************************************************************************/ + +int tmp112_register_uorb(int devno, FAR struct i2c_master_s *i2c, + uint8_t addr) +{ + FAR struct tmp112_dev_uorb_s *priv; + int ret; + + DEBUGASSERT(i2c != NULL); + + priv = kmm_zalloc(sizeof(struct tmp112_dev_uorb_s)); + if (priv == NULL) + { + return -ENOMEM; + } + + priv->i2c = i2c; + priv->addr = addr; + priv->interval = TMP112_DEFAULT_INTERVAL; + priv->lower.ops = &g_tmp112_uorb_ops; + priv->lower.type = SENSOR_TYPE_TEMPERATURE; + + /* The part measures continuously out of reset, so there is nothing to + * configure before it will answer. + */ + + ret = sensor_register(&priv->lower, devno); + if (ret < 0) + { + snerr("ERROR: cannot register: %d\n", ret); + kmm_free(priv); + return ret; + } + + sninfo("TMP112 at %02x registered as sensor_temp%d\n", addr, devno); + return OK; +} + +#endif /* CONFIG_SENSORS_TMP112 && CONFIG_SENSORS_TMP112_UORB */ diff --git a/include/nuttx/sensors/tmp112.h b/include/nuttx/sensors/tmp112.h index f6821686146..a8f57242220 100644 --- a/include/nuttx/sensors/tmp112.h +++ b/include/nuttx/sensors/tmp112.h @@ -105,8 +105,30 @@ extern "C" * ****************************************************************************/ +#ifndef CONFIG_SENSORS_TMP112_UORB int tmp112_register(FAR const char *devpath, FAR struct i2c_master_s *i2c, uint8_t addr); +#else +/**************************************************************************** + * Name: tmp112_register_uorb + * + * Description: + * Register the part as a uORB temperature sensor, appearing as + * /dev/uorb/sensor_temp. + * + * Input Parameters: + * devno - The topic number + * i2c - The bus the part is on + * addr - The bus address, 0x48 to 0x4b as the pin straps say + * + * Returned Value: + * Zero (OK) on success; a negated errno value on failure. + * + ****************************************************************************/ + +int tmp112_register_uorb(int devno, FAR struct i2c_master_s *i2c, + uint8_t addr); +#endif /* CONFIG_SENSORS_TMP112_UORB */ #undef EXTERN #ifdef __cplusplus