This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

ADC interupt based on SAADC limit monitoring or LPCOMP

Greetings,

We are using nrf-sdk v1.3.0.

We would like to monitor an adc channel using either limit event monitoring or maybe LPCOMP.

I was able to find "low level" documentation, describing the registers and such, but no actual example code using zephyr.
https://infocenter.nordicsemi.com/index.jsp?topic=%2Fps_nrf52840%2Flpcomp.html&cp=4_0_0_5_11
https://infocenter.nordicsemi.com/index.jsp?topic=%2Fps_nrf52840%2Fsaadc.html&cp=4_0_0_5_22_6&anchor=saadc_limits

What we would like to achieve is the following:
1. On device startup, sample the adc channel 1000x -> calculate average baseline value. This value would then used as the trigger threshold.
2. go to sleep
3. when the sample value on the channel is bellow 70% of threshold or above 130% of threshold, trigger an event handler in code (waking the system).

NOTE:
we know how to do #1.
The percentages in #3 are just an example, we would like to be able to change them in the code every so often.
We need to consume as little power as possible.
The comparison should be "continious", if possible. Or, if ADC must be sampled, once per second should be OK

Reading this thread:
https://devzone.nordicsemi.com/f/nordic-q-a/20895/saadc-low-power-scan-mode

I can see that this can be done, but there are not enough code samples to be able to produce something working. 
Must I set the registers directly? is there no wrapper API for this?

I would be very happy to see a working code example.

Regards,
Tjaž

Parents Reply Children
  •  Note that this is a 4 year old answer which is not related to nRF Connect SDK.

     

    Tjaz said:
    Regardless of the wrong links, the question remains the same: how to register a handler to run when adc sample is "out of range", and to consume as little power as possible?

     There is currently no API for this in Zephyr, but you can use the nrfx saadc driver directly (modules\hal\nordic\nrfx\drivers\include\nrfx_saadc.h)

    /**
     * @brief Function for setting the SAADC channel limits.
     *
     * When limits are enabled and the conversion result exceeds the defined bounds,
     * the handler function is called with the corresponding event as parameter.
     *
     * @note Before the limits are set, the driver operation mode (simple or advanced) has
     *       to be configured. Only non-blocking conversions can be monitored.
     *
     * @note Changing of the driver operation mode disables all configured limits.
     *
     * @param[in] channel    Channel index.
     * @param[in] limit_low  Limit low value to generate interrupt. Use @c INT16_MIN
     *                       to disable interrupt generation.
     * @param[in] limit_high Limit high value to generate interrupt. Use @c INT16_MAX
     *                       to disable interrupt generation.
     *
     * @retval NRFX_SUCCESS             Requested channel limits were set.
     * @retval NRFX_ERROR_INVALID_PARAM Attempt to activate the limits on disabled channel.
     * @retval NRFX_ERROR_FORBIDDEN     Attempt to activate the limits for blocking conversions.
     * @retval NRFX_ERROR_INVALID_STATE Attempt to activate the limits without configured mode.
     */
    nrfx_err_t nrfx_saadc_limits_set(uint8_t channel, int16_t limit_low, int16_t limit_high);

  • Thank you for the link.

    I have some further questions:

    1. Is there a driver for setting RTC to trigger SAADC sampling on an interval?

    2. Can I use this driver you linked in addition to my existing code, that uses zephyrs driver (#include <drivers/adc.h>)? Or will using nrfx_saadc.h directly mess up things that I configure with adc.h?

    Regards,
    Tjaž

  • Tjaz said:
    1. Is there a driver for setting RTC to trigger SAADC sampling on an interval?

     Yes, I believe the DPPI can be used for this purpose. Have a look here. Just change TIMER0 to RTC.

    One-to-one connection

    This example shows how to create a one-to-one connection between TIMER compare register and SAADC start task.

    The channel configuration is set up first. TIMER0 will publish its COMPARE0 event on channel 0, and SAADC will subscribe its START task to events on the same channel. After that, the channel is enabled through the DPPIC.

    
    NRF_TIMER0->PUBLISH_COMPARE0 = (DPPI_PUB_CHIDX_Ch0) | 
                                   (DPPI_PUB_EN_Msk);
    NRF_SAADC->SUBSCRIBE_START   = (DPPI_SUB_CHIDX_Ch0) | 
                                   (DPPI_SUB_EN_Msk);
      
    NRF_DPPIC->CHENSET = (DPPI_CHENSET_CH0_Set << DPPI_CHENSET_CH0_Pos);

     

    Tjaz said:
    2. Can I use this driver you linked in addition to my existing code, that uses zephyrs driver (#include <drivers/adc.h>)? Or will using nrfx_saadc.h directly mess up things that I configure with adc.h?
    The Zephyr ADC (zephyr\include\drivers\adc.h-->zephyr\drivers\adc\adc_nrfx_saadc.c) uses the functions from hal/nrf_saadc.h. If you configure stuff on the lowest level first (nrf_saadc.h.) the Zephyr layer will not know about it. I would recommend sticking to one layer. But if you know what you're doing, I think it should be possible to mix them
  • Before configuring this "link" via DPPI, I have to get the rtc to work first tho...

    Using nrfx_rtc.h and writing code as such:

    prj.conf

    CONFIG_NRFX_RTC0=y
    CONFIG_NRFX_DPPI=y

    main.c

    #include <nrfx_saadc.h>
    #include <nrfx_rtc.h>
    
    #include <logging/log.h>
    LOG_MODULE_REGISTER(main);
    
    void rtc_handler(nrfx_rtc_int_type_t int_type)
    {
        LOG_INF("RTC HANDLER");
    }
    
    
    void main(void)
    { 
        LOG_INF("INIT NRFX RTC");
        nrfx_rtc_t rtc_dev = NRFX_RTC_INSTANCE(0);
        // copy of default config
        nrfx_rtc_config_t rtc_conf = {
            .prescaler = RTC_FREQ_TO_PRESCALER(32768),
            .interrupt_priority = NRFX_RTC_DEFAULT_CONFIG_IRQ_PRIORITY,
            .tick_latency = NRFX_RTC_US_TO_TICKS(2000, 32768),
            .reliable = false,
        };
        nrfx_err_t nerr = nrfx_rtc_init(&rtc_dev, &rtc_conf, rtc_handler);
        if (nerr != NRFX_SUCCESS)
        {
            LOG_ERR("err: %d", nerr);
        }
    
        nrfx_rtc_cc_set(&rtc_dev,
                        0,
                        1000000,
                        true);
    
        nrfx_rtc_enable(&rtc_dev);
        
        LOG_INF("AFTER INIT NRFX RTC");
    
        // main goes to sleep;
        while (1)
        {
            k_sleep(K_SECONDS(3600));
        }
    }

    I get the following runtime error when the interrupt should happen (there are no compile errors or warnings):

    <err> os: >>> ZEPHYR FATAL ERROR 1: Unhandled interrupt on CPU 0
    <err> os: Current thread: 0x200238c8 (unknown)
    <err> os: Halting system
    

    How do I make zephyr aware of my interupt?

    In the final set up, i will probably have to pass false as the last param to nrfx_rtc_cc_setsince I only want ADC to trigger an interupt. But If the adc interupt is configured only using the nrfx layer, wont a similar error occur?

    Finaly, lets say I add this to the code, as you suggested, to set up DPPI:

        NRF_RTC0->PUBLISH_COMPARE[0] = (DPPI_PUB_CHIDX_Ch0) |
                                       (DPPI_PUB_EN_Msk);
        NRF_SAADC->SUBSCRIBE_START = (DPPI_SUB_CHIDX_Ch0) |
                                     (DPPI_SUB_EN_Msk);
    
        NRF_DPPIC->CHENSET = (DPPI_CHENSET_CH0_Set << DPPI_CHENSET_CH0_Pos);

    This does not compile, with the following errors:

    ../src/main.c: In function 'main':
    ../src/main.c:311:37: error: 'DPPI_PUB_CHIDX_Ch0' undeclared (first use in this function)
         NRF_RTC0->PUBLISH_COMPARE[0] = (DPPI_PUB_CHIDX_Ch0) |
                                         ^~~~~~~~~~~~~~~~~~
    ../src/main.c:311:37: note: each undeclared identifier is reported only once for each function it appears in
    ../src/main.c:312:37: error: 'DPPI_PUB_EN_Msk' undeclared (first use in this function); did you mean 'DPPIC_CHG_CH2_Msk'?
                                        (DPPI_PUB_EN_Msk);
                                         ^~~~~~~~~~~~~~~
                                         DPPIC_CHG_CH2_Msk
    ../src/main.c:313:35: error: 'DPPI_SUB_CHIDX_Ch0' undeclared (first use in this function); did you mean 'DPPI_PUB_CHIDX_Ch0'?
         NRF_SAADC->SUBSCRIBE_START = (DPPI_SUB_CHIDX_Ch0) |
                                       ^~~~~~~~~~~~~~~~~~
                                       DPPI_PUB_CHIDX_Ch0
    ../src/main.c:314:35: error: 'DPPI_SUB_EN_Msk' undeclared (first use in this function); did you mean 'DPPI_PUB_EN_Msk'?
                                      (DPPI_SUB_EN_Msk);
                                       ^~~~~~~~~~~~~~~
                                       DPPI_PUB_EN_Msk
    ../src/main.c:316:27: error: 'DPPI_CHENSET_CH0_Set' undeclared (first use in this function); did you mean 'DPPIC_CHENSET_CH0_Set'?
         NRF_DPPIC->CHENSET = (DPPI_CHENSET_CH0_Set << DPPI_CHENSET_CH0_Pos);
                               ^~~~~~~~~~~~~~~~~~~~
                               DPPIC_CHENSET_CH0_Set
    ../src/main.c:316:51: error: 'DPPI_CHENSET_CH0_Pos' undeclared (first use in this function); did you mean 'DPPIC_CHENSET_CH0_Pos'?
         NRF_DPPIC->CHENSET = (DPPI_CHENSET_CH0_Set << DPPI_CHENSET_CH0_Pos);
                                                       ^~~~~~~~~~~~~~~~~~~~
                                                       DPPIC_CHENSET_CH0_Pos
    ninja: build stopped: subcommand failed.
    FATAL ERROR: command exited with status 1: /home/comemaster/.local/bin/cmake --build /home/comemaster/Synology-Drive/IRNAS-Users/tjaz/Nordic/ncs_v1.3.0/nrf/applications/saadc-test/build
    

    Looking at nrf9160_bitfields.h I can see that some of these identifiers have changed (like  DPPI_CHENSET_CH0_Set -> DPPIC_CHENSET_CH0_Set), while others I can not find at all (like DPPI_PUB_CHIDX_Ch0 or DPPI_PUB_EN_Msk).

    It would be nice, as I have asked before, to get a working example of at lease each element on its own - so rtc configuration, adc configuration and dppi configuration to link them together

    Regards,
    Tjaž

  • Hello , 

    You need to use nrfx_saadc in continous mode to sample on an interval:

    Continuous mode

    Continuous sampling can be achieved by using the internal timer in the ADC, or triggering the SAMPLE task from one of the general purpose timers through the PPI system.

    Care shall be taken to ensure that the sample rate fulfils the following criteria, depending on how many channels are active:
    fSAMPLE < 1/(tACQ + tconv)
    The SAMPLERATE register can be used as a local timer instead of triggering individual SAMPLE tasks. When SAMPLERATE.MODE is set to Timers, it is sufficient to trigger SAMPLE task only once in order to start the SAADC and triggering the STOP task will stop sampling. The SAMPLERATE.CC field controls the sample rate.

    The SAMPLERATE timer mode cannot be combined with SCAN mode, and only one channel can be enabled in this mode.

    A DONE event signals that one sample has been taken.

    In this mode, the RESULTDONE event has the same meaning as DONE when no oversampling takes place. Note that both events may occur before the actual value has been transferred into RAM by EasyDMA.

    /**
     * @brief Function for setting the SAADC driver in the advanced mode.
     *
     * The advanced mode allows performing double-buffered conversions of arbitrary length.
     * The conversions can be done in a blocking or non-blocking manner. When performing conversions
     * in the non-blocking manner and @ref nrfx_saadc_adv_config_t.internal_timer_cc is set to 0,
     * sampling needs to be done by triggering @ref NRF_SAADC_TASK_SAMPLE externally
     * (for example by using the TIMER and/or the PPI/DPPI).
     * When performing conversions in the non-blocking manner and @ref nrfx_saadc_adv_config_t.start_on_end
     * is false, the @ref NRF_SAADC_TASK_START needs to be triggered on @ref NRF_SAADC_EVENT_END
     * externally (for example by using the PPI/DPPI).
     * Sampling is initiated by calling @ref nrfx_saadc_mode_trigger(). In case of performing
     * conversions in the blocking manner, @ref nrfx_saadc_mode_trigger() may need to be called several
     * times as each call sample each requested channel once.
     *
     * @note The internal timer can only be used when a single input channel is enabled.
     * @note The internal timer can only be used in the non-blocking mode.
     *
     * @param[in] channel_mask  Bitmask of channels to be used in the advanced mode.
     * @param[in] resolution    Resolution configuration.
     * @param[in] p_config      Pointer to the structure with the advanced mode configuration.
     * @param[in] event_handler Event handler provided by the user. In case of providing NULL,
     *                          the conversion will be performed in the blocking manner.
     *
     * @retval NRFX_SUCCESS             Initialization was successful.
     * @retval NRFX_ERROR_BUSY          There is a conversion or calibration ongoing.
     * @retval NRFX_ERROR_INVALID_PARAM Attempt to activate channel that is not configured.
     * @retval NRFX_ERROR_NOT_SUPPORTED Attempt to activate internal timer or oversampling without burst
     *                                  with multiple channels enabled.
     */
    nrfx_err_t nrfx_saadc_advanced_mode_set(uint32_t                        channel_mask,
                                            nrf_saadc_resolution_t          resolution,
                                            nrfx_saadc_adv_config_t const * p_config,
                                            nrfx_saadc_event_handler_t      event_handler);

     

    Tjaz said:
    It would be nice, as I have asked before, to get a working example of at lease each element on its own - so rtc configuration, adc configuration and dppi configuration to link them together

    Yes, unfortunately, there are very few samples available for this. Zephyr does have some samples available, e.g. nRF91 nrfx sample

    Kind regards,

    Øyvind 

Related