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 <justin@dynam.ac>
This commit is contained in:
Justin Hammond 2026-08-04 22:42:12 +08:00 committed by Alan C. Assis
parent 3455deb83c
commit 769862ac6a
8 changed files with 518 additions and 1 deletions

View file

@ -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

View file

@ -557,5 +557,6 @@ Implemented Drivers
- :doc:`nau7802`
- :doc:`qmi8658`
- :doc:`sht4x`
- :doc:`tmp112`
- :doc:`lsm6dso32`
- wtgahrs2

View file

@ -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
</components/drivers/special/sensors/sensors_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 <nuttx/sensors/tmp112.h>
Registering the driver creates one topic, ``sensor_temp<n>``, 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.

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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 <nuttx/config.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <nuttx/kmalloc.h>
#include <nuttx/i2c/i2c_master.h>
#include <nuttx/sensors/sensor.h>
#include <nuttx/sensors/tmp112.h>
#include <nuttx/wqueue.h>
#include <nuttx/debug.h>
#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 = &regaddr;
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<devno>
* 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 */

View file

@ -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<devno>.
*
* 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