Hello CircuitDojo Developers,

This may be a basic question or oversight I have made, but given Zephyr RTOS sensors API, “fetch” and “get” routines, is there a way to return more than a sensor_value worth of data to code calling a sensor’s API? Per this Zephyr 2.6.0 documentation on Zephyr’s sensors API it seems as though we are bound to “getting” back two integers worth of sensor data per call.

This Zephyr doc speaks more directly of API returning sensor_value amount of data per “channel get” request: https://docs.zephyrproject.org/latest/reference/peripherals/sensor.html#values.

Reviewing Air Quality Wing sensor drivers such as Sensirion SHTC3 driver source and similar drivers I find that the given sensor’s data structure is scoped privately to the source file of the driver, and seems intended to be encapsulated there and not shared. When we need multiple channels’ data returned per API call are we faced with creating an API extension to provide this?

  • Ted
  • jaredwolff replied to this.
  • Hello @jaredwolff and CircuitDojo Community,

    I think I’ve found an answer to my question about how Zephyr structured sensor API can return multiple sensor values. These are the values of type sensor_value, which is a structure of two integers val1 and val2, defined in first fifty lines of Zephyr sensor.h.

    So in a third party driver file for STMicro’s IIS2DH accelerometer, “sensor get reading” routines return one or all three axes X, Y, Z. The entry “get” routine accepts a pointer to a sensor_value structure. I had not realized the simple reality of C language, that such a pointer can point to one or more structure instances, not just one. This appears to be the case in the following code from [current_west_workspace_sdk-nrf-1p6p1]/zephyr/drivers/sensor/iis2dh/iis2dh.c, excerpt of code from this file:

    116 static inline void iis2dh_channel_get_acc(const struct device *dev,
    117                                           enum sensor_channel chan,
    118                                           struct sensor_value *val)
    119 {
    120         int i;
    121         uint8_t ofs_start, ofs_stop;
    122         struct iis2dh_data *iis2dh = dev->data;
    123         struct sensor_value *pval = val;
    124 
    125         switch (chan) {
    126         case SENSOR_CHAN_ACCEL_X:
    127                 ofs_start = ofs_stop = 0U;
    128       <!-- comment -->          break;
    129         case SENSOR_CHAN_ACCEL_Y:
    130                 ofs_start = ofs_stop = 1U;
    131                 break;
    132         case SENSOR_CHAN_ACCEL_Z:
    133                 ofs_start = ofs_stop = 2U;
    134                 break;
    135         default:
    136                 ofs_start = 0U; ofs_stop = 2U;
    137                 break;
    138         }
    139 
    140         for (i = ofs_start; i <= ofs_stop ; i++) {
    141                 iis2dh_convert(pval++, iis2dh->acc[i], iis2dh->gain);
    142         }
    143 }
    144 
    145 static int iis2dh_channel_get(const struct device *dev,
    146                               enum sensor_channel chan,
    147                               struct sensor_value *val)
    148 {
    149         switch (chan) {
    150         case SENSOR_CHAN_ACCEL_X:
    151         case SENSOR_CHAN_ACCEL_Y:
    152         case SENSOR_CHAN_ACCEL_Z:
    153         case SENSOR_CHAN_ACCEL_XYZ:
    154                 iis2dh_channel_get_acc(dev, chan, val);
    155                 return 0;
    156         default:
    157                 LOG_DBG("Channel not supported");
    158                 break;
    159         }
    160 
    161         return -ENOTSUP;
    162 }

    On line 141 pointer pval is incremented in the first of these two routines. That pointer arithmetic is where “the magic” happens to enable the return of multiple sensor readings to calling code.

    For experienced C coders this is really basic I am sure. But from Zephyr’s sensor API documentation, which is great, it was not obvious to me that Zephyr’s generalized “sensor_get” routine API could return more than a single sensor_value structure. This code example corrects my misunderstanding, and makes my original posted question moot.

    Want to share this finding so that this post can be marked ‘closed’.

    • Ted

    tedhavelka66 is this data you’re trying to get for different “sensor types” on the same chip? The options that are built into Zephyr are pretty extensive:

    /**
     * @brief Sensor channels.
     */
    enum sensor_channel {
    	/** Acceleration on the X axis, in m/s^2. */
    	SENSOR_CHAN_ACCEL_X,
    	/** Acceleration on the Y axis, in m/s^2. */
    	SENSOR_CHAN_ACCEL_Y,
    	/** Acceleration on the Z axis, in m/s^2. */
    	SENSOR_CHAN_ACCEL_Z,
    	/** Acceleration on the X, Y and Z axes. */
    	SENSOR_CHAN_ACCEL_XYZ,
    	/** Angular velocity around the X axis, in radians/s. */
    	SENSOR_CHAN_GYRO_X,
    	/** Angular velocity around the Y axis, in radians/s. */
    	SENSOR_CHAN_GYRO_Y,
    	/** Angular velocity around the Z axis, in radians/s. */
    	SENSOR_CHAN_GYRO_Z,
    	/** Angular velocity around the X, Y and Z axes. */
    	SENSOR_CHAN_GYRO_XYZ,
    	/** Magnetic field on the X axis, in Gauss. */
    	SENSOR_CHAN_MAGN_X,
    	/** Magnetic field on the Y axis, in Gauss. */
    	SENSOR_CHAN_MAGN_Y,
    	/** Magnetic field on the Z axis, in Gauss. */
    	SENSOR_CHAN_MAGN_Z,
    	/** Magnetic field on the X, Y and Z axes. */
    	SENSOR_CHAN_MAGN_XYZ,
    	/** Device die temperature in degrees Celsius. */
    	SENSOR_CHAN_DIE_TEMP,
    	/** Ambient temperature in degrees Celsius. */
    	SENSOR_CHAN_AMBIENT_TEMP,
    	/** Pressure in kilopascal. */
    	SENSOR_CHAN_PRESS,
    	/**
    	 * Proximity.  Adimensional.  A value of 1 indicates that an
    	 * object is close.
    	 */
    	SENSOR_CHAN_PROX,
    	/** Humidity, in percent. */
    	SENSOR_CHAN_HUMIDITY,
    	/** Illuminance in visible spectrum, in lux. */
    	SENSOR_CHAN_LIGHT,
    	/** Illuminance in infra-red spectrum, in lux. */
    	SENSOR_CHAN_IR,
    	/** Illuminance in red spectrum, in lux. */
    	SENSOR_CHAN_RED,
    	/** Illuminance in green spectrum, in lux. */
    	SENSOR_CHAN_GREEN,
    	/** Illuminance in blue spectrum, in lux. */
    	SENSOR_CHAN_BLUE,
    	/** Altitude, in meters */
    	SENSOR_CHAN_ALTITUDE,
    
    	/** 1.0 micro-meters Particulate Matter, in ug/m^3 */
    	SENSOR_CHAN_PM_1_0,
    	/** 2.5 micro-meters Particulate Matter, in ug/m^3 */
    	SENSOR_CHAN_PM_2_5,
    	/** 10 micro-meters Particulate Matter, in ug/m^3 */
    	SENSOR_CHAN_PM_10,
    	/** Distance. From sensor to target, in meters */
    	SENSOR_CHAN_DISTANCE,
    
    	/** CO2 level, in parts per million (ppm) **/
    	SENSOR_CHAN_CO2,
    	/** VOC level, in parts per billion (ppb) **/
    	SENSOR_CHAN_VOC,
    	/** Gas sensor resistance in ohms. */
    	SENSOR_CHAN_GAS_RES,
    
    	/** Voltage, in volts **/
    	SENSOR_CHAN_VOLTAGE,
    	/** Current, in amps **/
    	SENSOR_CHAN_CURRENT,
    	/** Resistance , in Ohm **/
    	SENSOR_CHAN_RESISTANCE,
    
    	/** Angular rotation, in degrees */
    	SENSOR_CHAN_ROTATION,
    
    	/** Position change on the X axis, in points. */
    	SENSOR_CHAN_POS_DX,
    	/** Position change on the Y axis, in points. */
    	SENSOR_CHAN_POS_DY,
    	/** Position change on the Z axis, in points. */
    	SENSOR_CHAN_POS_DZ,
    
    	/** Revolutions per minute, in RPM. */
    	SENSOR_CHAN_RPM,
    
    	/** Voltage, in volts **/
    	SENSOR_CHAN_GAUGE_VOLTAGE,
    	/** Average current, in amps **/
    	SENSOR_CHAN_GAUGE_AVG_CURRENT,
    	/** Standy current, in amps **/
    	SENSOR_CHAN_GAUGE_STDBY_CURRENT,
    	/** Max load current, in amps **/
    	SENSOR_CHAN_GAUGE_MAX_LOAD_CURRENT,
    	/** Gauge temperature  **/
    	SENSOR_CHAN_GAUGE_TEMP,
    	/** State of charge measurement in % **/
    	SENSOR_CHAN_GAUGE_STATE_OF_CHARGE,
    	/** Full Charge Capacity in mAh **/
    	SENSOR_CHAN_GAUGE_FULL_CHARGE_CAPACITY,
    	/** Remaining Charge Capacity in mAh **/
    	SENSOR_CHAN_GAUGE_REMAINING_CHARGE_CAPACITY,
    	/** Nominal Available Capacity in mAh **/
    	SENSOR_CHAN_GAUGE_NOM_AVAIL_CAPACITY,
    	/** Full Available Capacity in mAh **/
    	SENSOR_CHAN_GAUGE_FULL_AVAIL_CAPACITY,
    	/** Average power in mW **/
    	SENSOR_CHAN_GAUGE_AVG_POWER,
    	/** State of health measurement in % **/
    	SENSOR_CHAN_GAUGE_STATE_OF_HEALTH,
    	/** Time to empty in minutes **/
    	SENSOR_CHAN_GAUGE_TIME_TO_EMPTY,
    	/** Time to full in minutes **/
    	SENSOR_CHAN_GAUGE_TIME_TO_FULL,
    	/** Cycle count (total number of charge/discharge cycles) **/
    	SENSOR_CHAN_GAUGE_CYCLE_COUNT,
    	/** Design voltage of cell in V (max voltage)*/
    	SENSOR_CHAN_GAUGE_DESIGN_VOLTAGE,
    	/** Desired voltage of cell in V (nominal voltage) */
    	SENSOR_CHAN_GAUGE_DESIRED_VOLTAGE,
    	/** Desired charging current in mA */
    	SENSOR_CHAN_GAUGE_DESIRED_CHARGING_CURRENT,
    
    	/** All channels. */
    	SENSOR_CHAN_ALL,
    
    	/**
    	 * Number of all common sensor channels.
    	 */
    	SENSOR_CHAN_COMMON_COUNT,
    
    	/**
    	 * This and higher values are sensor specific.
    	 * Refer to the sensor header file.
    	 */
    	SENSOR_CHAN_PRIV_START = SENSOR_CHAN_COMMON_COUNT,
    
    	/**
    	 * Maximum value describing a sensor channel type.
    	 */
    	SENSOR_CHAN_MAX = INT16_MAX,
    };

    If it’s a different representation of the same value, then you can use the set attribute method to change the “mode” of the device. For instance, I use one attribute to change the output mode from the numerical representation to the “raw” value of the SHTC3 output.

    If you’re looking to get multiple readings from the same sensor, you can simply request the same data multiple times.

    Hope that helps.

    Good evening Jared,

    Thank you for the quick reply! The enum you share is one I am referencing daily at this time, as I build out a more complete Zephyr driver for Kionix’ KX132-1211 accelerometer. This enum is an important element of Zephyr RTOS. At this point also, I have a clearer grasp of the reasons why Zephyr developers and community have built on this architecture pattern. This makes and will make sensor drivers more consistent and sane as new drivers get written and shared, I am sure.

    The key take-away in your answer is “you may request the . . . data multiple times”. I neglected to explain that I’m faced with capturing a larger data set at a sampling rate that I may not be able to keep up with if my code must call [sensor]_channel_get() three or more times for a single set of readings with one and the same timestamp. I’d like to sample X, Y and Z accelerometer readings between 5kHz and 20kHz, for a few seconds’ period of time.

    These data capture rates exceed the I2C bus bandwidth available to me. I have not yet penciled out whether I’ll be able to meet faster sample rates in this range by using SPI interface instead of I2C. Perhaps what I meant to ask is whether any sensors.h channel enumerator can be used to represent a bulk read or bulk data transfer?

    Recently too I had noticed the “raw read” mode in your SHTC3 driver code. In that routine there’s code which converts the raw data from the sensor into floating point format. I won’t need that conversion at the moment of gathering sets of readings, but I will need the raw format readings at those sample rates and with consistent, equal times between readings.

    I’ll need to implement some tests to see whether over SPI the code is able to get individual axis readings one at a time quickly enough, to be sampling all three axes at the needed output data rate.

    Let me know if there is some kind of “bulk data read” in Zephyr’s sensor driver API. I have not seen such a feature so far, though have searched. Thank you again for the prompt reply!

    • Ted
    12 days later

    Hello @jaredwolff and CircuitDojo Community,

    I think I’ve found an answer to my question about how Zephyr structured sensor API can return multiple sensor values. These are the values of type sensor_value, which is a structure of two integers val1 and val2, defined in first fifty lines of Zephyr sensor.h.

    So in a third party driver file for STMicro’s IIS2DH accelerometer, “sensor get reading” routines return one or all three axes X, Y, Z. The entry “get” routine accepts a pointer to a sensor_value structure. I had not realized the simple reality of C language, that such a pointer can point to one or more structure instances, not just one. This appears to be the case in the following code from [current_west_workspace_sdk-nrf-1p6p1]/zephyr/drivers/sensor/iis2dh/iis2dh.c, excerpt of code from this file:

    116 static inline void iis2dh_channel_get_acc(const struct device *dev,
    117                                           enum sensor_channel chan,
    118                                           struct sensor_value *val)
    119 {
    120         int i;
    121         uint8_t ofs_start, ofs_stop;
    122         struct iis2dh_data *iis2dh = dev->data;
    123         struct sensor_value *pval = val;
    124 
    125         switch (chan) {
    126         case SENSOR_CHAN_ACCEL_X:
    127                 ofs_start = ofs_stop = 0U;
    128       <!-- comment -->          break;
    129         case SENSOR_CHAN_ACCEL_Y:
    130                 ofs_start = ofs_stop = 1U;
    131                 break;
    132         case SENSOR_CHAN_ACCEL_Z:
    133                 ofs_start = ofs_stop = 2U;
    134                 break;
    135         default:
    136                 ofs_start = 0U; ofs_stop = 2U;
    137                 break;
    138         }
    139 
    140         for (i = ofs_start; i <= ofs_stop ; i++) {
    141                 iis2dh_convert(pval++, iis2dh->acc[i], iis2dh->gain);
    142         }
    143 }
    144 
    145 static int iis2dh_channel_get(const struct device *dev,
    146                               enum sensor_channel chan,
    147                               struct sensor_value *val)
    148 {
    149         switch (chan) {
    150         case SENSOR_CHAN_ACCEL_X:
    151         case SENSOR_CHAN_ACCEL_Y:
    152         case SENSOR_CHAN_ACCEL_Z:
    153         case SENSOR_CHAN_ACCEL_XYZ:
    154                 iis2dh_channel_get_acc(dev, chan, val);
    155                 return 0;
    156         default:
    157                 LOG_DBG("Channel not supported");
    158                 break;
    159         }
    160 
    161         return -ENOTSUP;
    162 }

    On line 141 pointer pval is incremented in the first of these two routines. That pointer arithmetic is where “the magic” happens to enable the return of multiple sensor readings to calling code.

    For experienced C coders this is really basic I am sure. But from Zephyr’s sensor API documentation, which is great, it was not obvious to me that Zephyr’s generalized “sensor_get” routine API could return more than a single sensor_value structure. This code example corrects my misunderstanding, and makes my original posted question moot.

    Want to share this finding so that this post can be marked ‘closed’.

    • Ted
    Terms and Conditions | Privacy Policy