SAADC PPI trigger channels separately

Hello, I'm working on an application with precise timing requirements. Basically I need to generate a short pulse and sample two channels during the pulse. In addition, I would like to use a third channel for a slower/low-priority battery monitor task.

I already have implemented the GPIO control for the pulse via Timers and PPI, and I was planning to use it as well to trigger ADC acquisitions during the pulse. However, I have two doubts:

  • Since the pulses happen with high frequency, I would like the ADC to store samples in a buffer autonomously. While the ADC supports buffering, it's unclear to me if I can still trigger manually each acquisition (via PPI) when using this feature (nrf_drv_saadc_buffer_convert or similar), or if the ADC just samples on its own time. For me, it's critical that the trigger is controlled for each sample by the PPI. To clarify with an example, I would like to setup a buffer (eg: 10 samples), then trigger via PPI 10 single acquisitions and only after the 10th get the callback/read the ADC buffered data. Is this possible?
  • Secondly, I would like to add a third channel that I can trigger occasionally for battery monitoring, independent of the time-sensitive application.

If not, what approach do you suggest?

Thank you!

Parents
  • Hi Andrea

    For me, it's critical that the trigger is controlled for each sample by the PPI. To clarify with an example, I would like to setup a buffer (eg: 10 samples), then trigger via PPI 10 single acquisitions and only after the 10th get the callback/read the ADC buffered data. Is this possible?

    Yes, this is possible, and as it happens there is a chapter in the DevAcademy that walks through this specifically. Please have a look at the relevant chapter here

    Essentially you can trigger the SAADC SAMPLE task from anywhere over PPI, and only get a callback from the ADC driver when the buffer is full. 

    Secondly, I would like to add a third channel that I can trigger occasionally for battery monitoring, independent of the time-sensitive application.

    There is no good way to implement different channels that you sample at different frequencies unfortunately. Essentially you have to choose between one of the following workarounds:

    a) Sample all the channels at the same, high frequency, and discard the samples you don't need for the slow channel. 

    b) Reconfigure the SAADC module every time you want to sample the slow channel, and add the slow channel to the list. This only works if you can accept a bit of downtime when the SAADC module is being reconfigured. 

    Best regards
    Torbjørn

  • Regarding the second point, ok I will try to think of something thanks for confirming

  • Hi Andrea

    Yaxit said:
    If you update the buffer in the callback, doesn't the ADC continue sampling? In my setup I want it to stop once that buffer is full, and later retrieve the data and trigger manually.

    Updating the buffer will start the SAADC module, true, but it won't sample it. Sampling is handled through PPI as mentioned earlier. 

    If you want it should certainly be possible not to set the buffers automatically in the callback, but rather do that at a later point in time when you want to run a new sample sequence. 

    Yaxit said:
    In your setup, are you getting the FINISHED event as well?

    I am getting the END event, yes, if that is what you mean by finished?

    I enabled logging for the nrfx_saadc module, and I can't spot any important differences when changing from two buffers to one. 

    Best regards
    Torbjørn

  • Hi Torbjørn
    No, with FINISHED I mean NRFX_SAADC_EVT_FINISHED as in the enum below.

    /** @brief SAADC driver event types. */
    typedef enum
    {
        NRFX_SAADC_EVT_DONE,          ///< Event generated when the buffer is filled with samples.
        NRFX_SAADC_EVT_LIMIT,         ///< Event generated when one of the limits is reached.
        NRFX_SAADC_EVT_CALIBRATEDONE, ///< Event generated when the calibration is complete.
        NRFX_SAADC_EVT_BUF_REQ,       ///< Event generated when the next buffer for continuous conversion is requested.
        NRFX_SAADC_EVT_READY,         ///< Event generated when the first buffer is acquired by the peripheral and sampling can be started.
        NRFX_SAADC_EVT_FINISHED,      ///< Event generated when all supplied buffers are filled with results.
    } nrfx_saadc_evt_type_t;


    This triggers when all the buffers are filled.
    Did you remove the nrf_drv_saadc_buffer_convert from the callback to replicate my setup?
  • Hi Andrea

    Which SDK version are you using? I tested this in v17.1.0 of the nRF5 SDK, where the nrfx_saadc_evt_type_t enum does not include the NRFX_SAADC_EVT_FINISHED definition. 

    If you let me know which version you are using I can test it again, and also try to call nrf_drv_saadc_buffer_convert from outside the callback. 

    Best regards
    Torbjørn

  • Ah interesting! I'm not 100% sure, I think I'm using the nrfx 3.3.0 as in https://github.com/NordicSemiconductor/nrfx/tree/v3.3.0

    My project is based on nrf-conenct v2.6.1, which pulls this nrfx version (accordingly to the README and CHANGELOG files in the main folder)
    Hope this helps!

  • Hi there,

    Torbjørn was using nRF5SDK SAADC example that use the SAADC v1 driver not V2 as used in nRF Connect SDK. END event is generated each time the SAADC has filled up the buffer, the NRFX_SAADC_EVT_FINISHED is generated once all the provided buffers has been filled. In your case, they are equal since you're only using 1 buffer. 

    Yaxit said:

    For example, while the double-buffered solution works, with a single buffer it does not, and I just have an idea why that might be the case.

    With the doubled buffered solution, I add the new buffer in the callback as shown in the example, and I can always trigger the ADC by 1) triggering TASK_START and then 2) triggering TASK_SAMPLE via PPI.

    However, if I only use one buffer TASK_START is not sufficient, and I need to call nrfx_saadc_mode_trigger() again before I can start sampling with PPI.

    I believe this is the case because in the first case the acquisition is never "finished" (intended as EVT_FINISHED is asserted, aka all buffers are filled), while in the second case since there is a single buffer EVT_DONE and EVT_FINISHED both trigger. And it seems that after a EVT_FINISH I cannot simply use a TASK_START to re-trigger the ADC (not even if I add a buffer first).

    Could you share your code?

    regards

    Jared 

Reply
  • Hi there,

    Torbjørn was using nRF5SDK SAADC example that use the SAADC v1 driver not V2 as used in nRF Connect SDK. END event is generated each time the SAADC has filled up the buffer, the NRFX_SAADC_EVT_FINISHED is generated once all the provided buffers has been filled. In your case, they are equal since you're only using 1 buffer. 

    Yaxit said:

    For example, while the double-buffered solution works, with a single buffer it does not, and I just have an idea why that might be the case.

    With the doubled buffered solution, I add the new buffer in the callback as shown in the example, and I can always trigger the ADC by 1) triggering TASK_START and then 2) triggering TASK_SAMPLE via PPI.

    However, if I only use one buffer TASK_START is not sufficient, and I need to call nrfx_saadc_mode_trigger() again before I can start sampling with PPI.

    I believe this is the case because in the first case the acquisition is never "finished" (intended as EVT_FINISHED is asserted, aka all buffers are filled), while in the second case since there is a single buffer EVT_DONE and EVT_FINISHED both trigger. And it seems that after a EVT_FINISH I cannot simply use a TASK_START to re-trigger the ADC (not even if I add a buffer first).

    Could you share your code?

    regards

    Jared 

Children
No Data
Related