Sensors¶
The sensor subsystem exposes an API to uniformly access sensor devices. Common operations are: reading data and executing code when specific conditions are met.
Basic Operation¶
Channels¶
Fundamentally, a channel is a quantity that a sensor device can measure.
Sensors can have multiple channels, either to represent different axes of the same physical property (e.g. acceleration); or because they can measure different properties altogether (ambient temperature, pressure and humidity). Complex sensors cover both cases, so a single device can expose three acceleration channels and a temperature one.
It is imperative that all sensors that support a given channel express results in the same unit of measurement. The following is a list of all supported channels, along with their description and units of measurement:
-
enum
sensor_channel
¶ Sensor channels.
Values:
-
SENSOR_CHAN_ACCEL_X
¶ Acceleration on the X axis, in m/s^2.
-
SENSOR_CHAN_ACCEL_Y
¶ Acceleration on the Y axis, in m/s^2.
-
SENSOR_CHAN_ACCEL_Z
¶ Acceleration on the Z axis, in m/s^2.
-
SENSOR_CHAN_ACCEL_XYZ
¶ Acceleration on the X, Y and Z axes.
-
SENSOR_CHAN_ACCEL_ANY
= SENSOR_CHAN_ACCEL_XYZ¶ This enum value will be deprecated. Please use SENSOR_CHAN_ACCEL_XYZ instead.
Acceleration on any axis.
-
SENSOR_CHAN_GYRO_X
¶ Angular velocity around the X axis, in radians/s.
-
SENSOR_CHAN_GYRO_Y
¶ Angular velocity around the Y axis, in radians/s.
-
SENSOR_CHAN_GYRO_Z
¶ Angular velocity around the Z axis, in radians/s.
-
SENSOR_CHAN_GYRO_XYZ
¶ Angular velocity around the X, Y and Z axes.
-
SENSOR_CHAN_GYRO_ANY
= SENSOR_CHAN_GYRO_XYZ¶ This enum value will be deprecated. Please use SENSOR_CHAN_GYRO_XYZ instead.
Angular velocity on any axis.
-
SENSOR_CHAN_MAGN_X
¶ Magnetic field on the X axis, in Gauss.
-
SENSOR_CHAN_MAGN_Y
¶ Magnetic field on the Y axis, in Gauss.
-
SENSOR_CHAN_MAGN_Z
¶ Magnetic field on the Z axis, in Gauss.
-
SENSOR_CHAN_MAGN_XYZ
¶ Magnetic field on the X, Y and Z axes.
-
SENSOR_CHAN_MAGN_ANY
= SENSOR_CHAN_MAGN_XYZ¶ This enum value will be deprecated. Please use SENSOR_CHAN_MAGN_XYZ instead.
Magnetic field on any axis.
-
SENSOR_CHAN_TEMP
¶ Temperature in degrees Celsius. (deprecated)
-
SENSOR_CHAN_DIE_TEMP
¶ Device die temperature in degrees Celsius.
-
SENSOR_CHAN_AMBIENT_TEMP
¶ Ambient temperature in degrees Celsius.
-
SENSOR_CHAN_PRESS
¶ Pressure in kilopascal.
-
SENSOR_CHAN_PROX
¶ Proximity. Adimensional. A value of 1 indicates that an object is close.
-
SENSOR_CHAN_HUMIDITY
¶ Humidity, in percent.
-
SENSOR_CHAN_LIGHT
¶ Illuminance in visible spectrum, in lux.
-
SENSOR_CHAN_IR
¶ Illuminance in infra-red spectrum, in lux.
-
SENSOR_CHAN_RED
¶ Illuminance in red spectrum, in lux.
-
SENSOR_CHAN_GREEN
¶ Illuminance in green spectrum, in lux.
-
SENSOR_CHAN_BLUE
¶ Illuminance in blue spectrum, in lux.
-
SENSOR_CHAN_ALTITUDE
¶ Altitude, in meters
-
SENSOR_CHAN_PM_1_0
¶ 1.0 micro-meters Particulate Matter, in ug/m^3
-
SENSOR_CHAN_PM_2_5
¶ 2.5 micro-meters Particulate Matter, in ug/m^3
-
SENSOR_CHAN_PM_10
¶ 10 micro-meters Particulate Matter, in ug/m^3
-
SENSOR_CHAN_DISTANCE
¶ Distance. From sensor to target, in meters
-
SENSOR_CHAN_CO2
¶ CO2 level, in parts per million (ppm)
-
SENSOR_CHAN_VOC
¶ VOC level, in parts per billion (ppb)
-
SENSOR_CHAN_VOLTAGE
¶ Voltage, in volts
-
SENSOR_CHAN_CURRENT
¶ Current, in amps
-
SENSOR_CHAN_ALL
¶ All channels.
-
Values¶
Sensor devices return results as struct sensor_value
. This
representation avoids use of floating point values as they may not be
supported on certain setups.
-
struct
sensor_value
Representation of a sensor readout value.
The value is represented as having an integer and a fractional part, and can be obtained using the formula val1 + val2 * 10^(-6). Negative values also adhere to the above formula, but may need special attention. Here are some examples of the value representation:
0.5: val1 = 0, val2 = 500000 -0.5: val1 = 0, val2 = -500000 -1.0: val1 = -1, val2 = 0 -1.5: val1 = -1, val2 = -500000
Fetching Values¶
Getting a reading from a sensor requires two operations. First, an
application instructs the driver to fetch a sample of all its channels.
Then, individual channels may be read. In the case of channels with
multiple axes, they can be read in a single operation by supplying
the corresponding _XYZ
channel type and a buffer of 3
struct sensor_value
objects. This approach ensures consistency
of channels between reads and efficiency of communication by issuing a
single transaction on the underlying bus.
Below is an example illustrating the usage of the BME280 sensor, which
measures ambient temperature and atmospheric pressure. Note that
sensor_sample_fetch()
is only called once, as it reads and
compensates data for both channels.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | void main(void)
{
struct device *dev = device_get_binding("BME280");
printf("dev %p name %s\n", dev, dev->config->name);
while (1) {
struct sensor_value temp, press, humidity;
sensor_sample_fetch(dev);
sensor_channel_get(dev, SENSOR_CHAN_AMBIENT_TEMP, &temp);
sensor_channel_get(dev, SENSOR_CHAN_PRESS, &press);
sensor_channel_get(dev, SENSOR_CHAN_HUMIDITY, &humidity);
printf("temp: %d.%06d; press: %d.%06d; humidity: %d.%06d\n",
temp.val1, temp.val2, press.val1, press.val2,
humidity.val1, humidity.val2);
k_sleep(1000);
}
}
|
The example assumes that the returned values have type struct
sensor_value
, which is the case for BME280. A real application
supporting multiple sensors should inspect the type
field of
the temp
and press
values and use the other fields
of the structure accordingly.
Configuration and Attributes¶
Setting the communication bus and address is considered the most basic configuration for sensor devices. This setting is done at compile time, via the configuration menu. If the sensor supports interrupts, the interrupt lines and triggering parameters described below are also configured at compile time.
Alongside these communication parameters, sensor chips typically expose multiple parameters that control the accuracy and frequency of measurement. In compliance with Zephyr’s design goals, most of these values are statically configured at compile time.
However, certain parameters could require runtime configuration, for example, threshold values for interrupts. These values are configured via attributes. The example in the following section showcases a sensor with an interrupt line that is triggered when the temperature crosses a threshold. The threshold is configured at runtime using an attribute.
Triggers¶
Triggers in Zephyr refer to the interrupt lines of the sensor chips. Many sensor chips support one or more triggers. Some examples of triggers include: new data is ready for reading, a channel value has crossed a threshold, or the device has sensed motion.
To configure a trigger, an application needs to supply a struct
sensor_trigger
and a handler function. The structure contains the trigger
type and the channel on which the trigger must be configured.
Because most sensors are connected via SPI or I2C busses, it is not possible to communicate with them from the interrupt execution context. The execution of the trigger handler is deferred to a thread, so that data fetching operations are possible. A driver can spawn its own thread to fetch data, thus ensuring minimum latency. Alternatively, multiple sensor drivers can share a system-wide thread. The shared thread approach increases the latency of handling interrupts but uses less memory. You can configure which approach to follow for each driver. Most drivers can entirely disable triggers resulting in a smaller footprint.
The following example contains a trigger fired whenever temperature crosses the 26 degree Celsius threshold. It also samples the temperature every second. A real application would ideally disable periodic sampling in the interest of saving power. Since the application has direct access to the kernel config symbols, no trigger is registered when triggering was disabled by the driver’s configuration.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | #ifdef CONFIG_MCP9808_TRIGGER
static void trigger_handler(struct device *dev, struct sensor_trigger *trig)
{
struct sensor_value temp;
sensor_sample_fetch(dev);
sensor_channel_get(dev, SENSOR_CHAN_AMBIENT_TEMP, &temp);
printf("trigger fired, temp %d.%06d\n", temp.val1, temp.val2);
}
#endif
void main(void)
{
struct device *dev = device_get_binding("MCP9808");
if (dev == NULL) {
printf("device not found. aborting test.\n");
return;
}
#ifdef DEBUG
printf("dev %p\n", dev);
printf("dev %p name %s\n", dev, dev->config->name);
#endif
#ifdef CONFIG_MCP9808_TRIGGER
struct sensor_value val;
struct sensor_trigger trig;
val.val1 = 26;
val.val2 = 0;
sensor_attr_set(dev, SENSOR_CHAN_AMBIENT_TEMP,
SENSOR_ATTR_UPPER_THRESH, &val);
trig.type = SENSOR_TRIG_THRESHOLD;
trig.chan = SENSOR_CHAN_AMBIENT_TEMP;
if (sensor_trigger_set(dev, &trig, trigger_handler)) {
printf("Could not set trigger. aborting test.\n");
return;
}
#endif
while (1) {
struct sensor_value temp;
int rc;
rc = sensor_sample_fetch(dev);
if (rc != 0) {
printf("sensor_sample_fetch error: %d\n", rc);
break;
}
rc = sensor_channel_get(dev, SENSOR_CHAN_AMBIENT_TEMP, &temp);
if (rc != 0) {
printf("sensor_channel_get error: %d\n", rc);
break;
}
printf("temp: %d.%06d\n", temp.val1, temp.val2);
k_sleep(2000);
}
}
|