Usage of interrupts from I2C device

I am using the nrf9160dk and an ST lis2dw12 accelerometer. I want to be able to set up an interrupt to fire on an anymotion event. From what I understand from the nrf-connect-sdk fundamentals course, I need to configure my device tree overlay like this:
```cpp
&i2c2 {
        lis2dw12: lis2dw12@19{
            compatible = "st,lis2dw12";
            status = "okay";
            reg = < 0x19 >;
            label="LIS2DW12";
            irq-gpios=<&gpio0 15 GPIO_ACTIVE_HIGH>;
            //was 1
            int-pin=<2>;
            range=<2>;
            power-mode=<0>;
        };
};

&gpio0 {
    status = "okay";
    //use SENSE for low power consumption on interrupt pin
    sense-edge-mask = < (1 << 15) >;
};

```

I'm not quite sure how to set the interrupts correctly in the main program however. If I try to call `sensor_trigger_set(accelerometer, &trig, some_trigger_handler);`, where trig.type is SENSOR_TRIG_DELTA, I receive -88 as the return code (which corresponds to ENOSYS?)
Also, I am not sure how this SENSOR_TRIG_DELTA is measured. According to ST's datasheet, this can be done on the MEMS accelerometer itself (for low-power wakeup applications), but I am worried that the implementation may require constant polling.

Note: I am able to use sensor_sample_fetch and sensor_channel_get to measure the current acceleration values, so I2C works.

Parents
  • The driver code for lis2dw12 is found in $ZEPHYR_BASE/drivers/sensor/lis2dw12    The code for handling the interrupts (aka triggers) is found in lis2dw12_trigger.c.

    The supported triggers in that code are:

    SENSOR_TRIG_DATA_READY

     SENSOR_TRIG_TAP  (needs Kconfig CONFIG_LIS2DW12_TAP=y)

    SENSOR_TRIG_DOUBLE_TAP (needs Kconfig CONFIG_LIS2DW12_TAP=y)

    SENSOR_TRIG_THRESHOLD (needs Kconfig CONFIG_LIS2DW12_THRESHOLD, set attrs SENSOR_ATTR_LOWER_THRESH and SENSOR_ATTR_UPPER_THRESH)

    SENSOR_TRIG_FREEFALL (needs Kconfig CONFIG_LIS2DW12_FREEFALL)

    For other triggers, you will need to modify the driver code.  The drivers that Zephyr provides do not implement every feature found in the hardware.

    BTW -- if you know anyone looking for an embedded software developer, I'm currently looking.  I've been actively working with Zephyr and have worked with Nordic chips since 2013.  Here is my linkedin profile:

    www.linkedin.com/.../

  • Thank you so much for your advice! I think I have managed to set up the DRDY pin correctly now. I am still not quite sure how to have an interrupt service routine run on change of a pin state. How would I need to set up my devicetree overlay for this?

Reply Children
  • You specify the board configuration in the device tree for example:

    accel: lis2dw12@18 {
    compatible = "st,lis2dw12";
    status = "okay";
    reg = <0x18>;
    irq-gpios = <&gpio0 5 (GPIO_PULL_UP | GPIO_ACTIVE_LOW) >;
    };

    irq-gpios can hold 1-2 gpio specifiers.  In the code sample it has 1.  The &gpio0 indicates another device tree object named gpio0 (which happens to be a gpio device driver) the, 5 means pin 5 in gpio0 and the third option is the initial gpio configuration.

    These settings get populated into the device driver config through this macro:

    #ifdef CONFIG_LIS2DW12_TRIGGER
    #define LIS2DW12_CFG_IRQ(inst) \
    	.gpio_int = GPIO_DT_SPEC_INST_GET(inst, irq_gpios),		\
    	.int_pin = DT_INST_PROP(inst, int_pin),
    #else
    #define LIS2DW12_CFG_IRQ(inst)
    #endif /* CONFIG_LIS2DW12_TRIGGER *

    Then in the lis2dw12_trigger code the gpio initialization includes:

    	ret = gpio_pin_configure_dt(&cfg->gpio_int, GPIO_INPUT);
    	if (ret < 0) {
    		LOG_ERR("Could not configure gpio");
    		return ret;
    	}
    
    	LOG_INF("%s: int on %s.%02u", dev->name, cfg->gpio_int.port->name,
    				      cfg->gpio_int.pin);
    
    	gpio_init_callback(&lis2dw12->gpio_cb,
    			   lis2dw12_gpio_callback,
    			   BIT(cfg->gpio_int.pin));
    
    	if (gpio_add_callback(cfg->gpio_int.port, &lis2dw12->gpio_cb) < 0) {
    		LOG_DBG("Could not set gpio callback");
    		return -EIO;
    	}
    

    Since the callback for the gpio driver may be directly in the ISR, and its not allowed to perform blocking operations such as i2c reads in an ISR, the callback code triggers another thread to perform additional processing including calling the user specified callback.  

    #if defined(CONFIG_LIS2DW12_TRIGGER_OWN_THREAD)
    	k_sem_give(&lis2dw12->gpio_sem);
    #elif defined(CONFIG_LIS2DW12_TRIGGER_GLOBAL_THREAD)
    	k_work_submit(&lis2dw12->work);
    #endif /* CONFIG_LIS2DW12_TRIGGER_OWN_THREAD *

    I have found some drivers will directly call the user callback from the ISR so its important to review the actual driver.

    Also note that the gpio0 is itself a driver which means it might be a direct gpio interface or it might be an I/O expander, the api is the same but the actual implementation of the gpio operations will vary.  

    Hopefully that explains how it all works.  I will also add if you question is how to run your own user callback, you do that with the sensor_trigger_set API.

    The details of sensor_trigger_set can be found in zephyr/include/zephyr/drivers/sensor.h

    /**
     * @typedef sensor_trigger_handler_t
     * @brief Callback API upon firing of a trigger
     *
     * @param dev Pointer to the sensor device
     * @param trigger The trigger
     */
    typedef void (*sensor_trigger_handler_t)(const struct device *dev,
    					 const struct sensor_trigger *trigger);
    
    /**
     * @brief Activate a sensor's trigger and set the trigger handler
     *
     * @param dev Pointer to the sensor device
     * @param trig The trigger to activate
     * @param handler The function that should be called when the trigger
     * fires
     *
     * @return 0 if successful, negative errno code if failure.
     */
    static inline int sensor_trigger_set(const struct device *dev,
    				     const struct sensor_trigger *trig,
    				     sensor_trigger_handler_t handler

Related