This is an automated email from the ASF dual-hosted git repository.

acassis pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx.git


The following commit(s) were added to refs/heads/master by this push:
     new 0b3848ef6b4 arch/nrf52: add SAADC continuous sampling mode
0b3848ef6b4 is described below

commit 0b3848ef6b4744ae7871c05374a97828a65f5974
Author: raiden00pl <[email protected]>
AuthorDate: Thu Jul 16 16:28:11 2026 +0200

    arch/nrf52: add SAADC continuous sampling mode
    
    Add CONFIG_NRF52_SAADC_CONTINUOUS for gapless timer-triggered sampling
    with double-buffered EasyDMA. The SAADC is auto-restarted directly from
    the END event through a PPI channel (no CPU in the loop) and each
    completed buffer is delivered to the ADC upper half via the batch
    interface. Enables high-rate single-channel streaming.
    
    Signed-off-by: raiden00pl <[email protected]>
    Assisted-by: Claude Code
---
 arch/arm/src/nrf52/Kconfig     |  25 ++++++++
 arch/arm/src/nrf52/nrf52_adc.c | 127 +++++++++++++++++++++++++++++++++++++++++
 2 files changed, 152 insertions(+)

diff --git a/arch/arm/src/nrf52/Kconfig b/arch/arm/src/nrf52/Kconfig
index 3c6ac405212..8548071edf6 100644
--- a/arch/arm/src/nrf52/Kconfig
+++ b/arch/arm/src/nrf52/Kconfig
@@ -695,6 +695,31 @@ config NRF52_SAADC_TIMER_CC
        default 0
        range 80 2047
 
+config NRF52_SAADC_CONTINUOUS
+       bool "SAADC continuous mode"
+       select NRF52_PPI
+       default n
+       ---help---
+               Continuous timer-triggered sampling with double-buffered 
EasyDMA.
+               The SAADC is restarted directly from the END event through a PPI
+               channel and completed buffers are passed to the upper-half with
+               the batch interface.
+
+if NRF52_SAADC_CONTINUOUS
+
+config NRF52_SAADC_CONTINUOUS_BUFLEN
+       int "SAADC continuous mode DMA buffer length"
+       default 32
+       ---help---
+               Number of samples in each of the two EasyDMA buffers.
+
+config NRF52_SAADC_CONTINUOUS_PPI_CH
+       int "SAADC continuous mode PPI channel"
+       range 0 19
+       default 0
+
+endif # NRF52_SAADC_CONTINUOUS
+
 endif # NRF52_SAADC_TIMER
 
 config NRF52_SAADC_OVERSAMPLE
diff --git a/arch/arm/src/nrf52/nrf52_adc.c b/arch/arm/src/nrf52/nrf52_adc.c
index 342a02e2b08..deaac22e33b 100644
--- a/arch/arm/src/nrf52/nrf52_adc.c
+++ b/arch/arm/src/nrf52/nrf52_adc.c
@@ -40,6 +40,9 @@
 #include "arm_internal.h"
 #include "nrf52_gpio.h"
 #include "nrf52_adc.h"
+#ifdef CONFIG_NRF52_SAADC_CONTINUOUS
+#  include "nrf52_ppi.h"
+#endif
 
 #include "hardware/nrf52_saadc.h"
 #include "hardware/nrf52_utils.h"
@@ -66,6 +69,18 @@ struct nrf52_adc_s
 
   int16_t                    buffer[CONFIG_NRF52_SAADC_CHANNELS];
 
+#ifdef CONFIG_NRF52_SAADC_CONTINUOUS
+  /* Double-buffered EasyDMA for gapless continuous sampling. The SAADC
+   * fills one dmabuf while the driver processes the other; the PPI channel
+   * auto-restarts the SAADC on END and the STARTED interrupt reloads the
+   * DMA pointer to the free buffer. 'next' is the buffer being filled.
+   */
+
+  int16_t                    dmabuf[2][CONFIG_NRF52_SAADC_CONTINUOUS_BUFLEN];
+  uint32_t                   batch[CONFIG_NRF52_SAADC_CONTINUOUS_BUFLEN];
+  uint8_t                    next;
+#endif
+
   uint8_t                    chan_len;   /* Configured channels */
   uint32_t                   base;       /* Base address of ADC register */
   uint32_t                   irq;        /* ADC interrupt */
@@ -198,6 +213,65 @@ static int nrf52_adc_isr(int irq, void *context, void *arg)
 
   ainfo("nrf52_adc_isr\n");
 
+#ifdef CONFIG_NRF52_SAADC_CONTINUOUS
+  /* END event: the current DMA buffer is full. Hand it to the upper half
+   * as a batch. The PPI channel has already retriggered START (into the
+   * buffer the STARTED handler preloaded). END must be handled before
+   * STARTED - both can be pending at once: END consumes priv->next and
+   * advances it, then STARTED uses the advanced value to reload the free
+   * buffer's DMA pointer.
+   */
+
+  if (nrf52_adc_getreg(priv, NRF52_SAADC_EVENTS_END_OFFSET) == 1)
+    {
+      DEBUGASSERT(priv->cb != NULL);
+      DEBUGASSERT(priv->cb->au_receive_batch != NULL);
+
+      /* Give the completed buffer to the ADC driver */
+
+      for (i = 0; i < CONFIG_NRF52_SAADC_CONTINUOUS_BUFLEN; i += 1)
+        {
+          priv->batch[i] = (uint32_t)priv->dmabuf[priv->next][i];
+        }
+
+      ret = priv->cb->au_receive_batch(dev, NULL, priv->batch,
+                                       CONFIG_NRF52_SAADC_CONTINUOUS_BUFLEN);
+      if (ret == -ENOMEM)
+        {
+          /* Receive FIFO overrun */
+
+          DEBUGASSERT(priv->cb->au_reset != NULL);
+          priv->cb->au_reset(dev);
+        }
+
+      /* The next END completes the other buffer */
+
+      priv->next ^= 1;
+
+      /* Clear event (read back to flush the write so the IRQ does not
+       * spuriously re-fire before the clear takes effect).
+       */
+
+      nrf52_adc_putreg(priv, NRF52_SAADC_EVENTS_END_OFFSET, 0);
+      (void)nrf52_adc_getreg(priv, NRF52_SAADC_EVENTS_END_OFFSET);
+    }
+
+  /* STARTED event: the SAADC latched the preloaded pointer and began
+   * sampling. Point the DMA pointer at the other (now free) buffer so the
+   * PPI END->START restart fills it next.
+   */
+
+  if (nrf52_adc_getreg(priv, NRF52_SAADC_EVENTS_STARTED_OFFSET) == 1)
+    {
+      nrf52_adc_putreg(priv, NRF52_SAADC_PTR_OFFSET,
+                       (uint32_t)&priv->dmabuf[priv->next ^ 1]);
+
+      /* Clear event (read back to flush the write) */
+
+      nrf52_adc_putreg(priv, NRF52_SAADC_EVENTS_STARTED_OFFSET, 0);
+      (void)nrf52_adc_getreg(priv, NRF52_SAADC_EVENTS_STARTED_OFFSET);
+    }
+#else
   /* END event */
 
   if (nrf52_adc_getreg(priv, NRF52_SAADC_EVENTS_END_OFFSET) == 1)
@@ -216,6 +290,7 @@ static int nrf52_adc_isr(int irq, void *context, void *arg)
 
       nrf52_adc_putreg(priv, NRF52_SAADC_EVENTS_END_OFFSET, 0);
     }
+#endif
 
   return ret;
 }
@@ -264,12 +339,34 @@ static int nrf52_adc_configure(struct nrf52_adc_s *priv)
 
   /* Configure ADC buffer */
 
+#ifdef CONFIG_NRF52_SAADC_CONTINUOUS
+  /* Continuous mode: gapless double-buffered DMA auto-restarted by PPI.
+   * Only the single measurement channel is sampled (requires SAADC_TIMER).
+   */
+
+  priv->next = 0;
+
+  regval = (uint32_t)&priv->dmabuf[0];
+  DEBUGASSERT(nrf52_easydma_valid(regval));
+  nrf52_adc_putreg(priv, NRF52_SAADC_PTR_OFFSET, regval);
+
+  regval = CONFIG_NRF52_SAADC_CONTINUOUS_BUFLEN;
+  nrf52_adc_putreg(priv, NRF52_SAADC_MAXCNT_OFFSET, regval);
+
+  /* Auto-restart the SAADC: END event -> START task via PPI */
+
+  nrf52_ppi_set_event_ep(CONFIG_NRF52_SAADC_CONTINUOUS_PPI_CH,
+                         priv->base + NRF52_SAADC_EVENTS_END_OFFSET);
+  nrf52_ppi_set_task_ep(CONFIG_NRF52_SAADC_CONTINUOUS_PPI_CH,
+                        priv->base + NRF52_SAADC_TASKS_START_OFFSET);
+#else
   regval = (uint32_t)&priv->buffer;
   DEBUGASSERT(nrf52_easydma_valid(regval));
   nrf52_adc_putreg(priv, NRF52_SAADC_PTR_OFFSET, regval);
 
   regval = priv->chan_len;
   nrf52_adc_putreg(priv, NRF52_SAADC_MAXCNT_OFFSET, regval);
+#endif
 
   return OK;
 }
@@ -811,6 +908,12 @@ static void nrf52_adc_shutdown(struct adc_dev_s *dev)
   DEBUGASSERT(dev);
   DEBUGASSERT(priv);
 
+#ifdef CONFIG_NRF52_SAADC_CONTINUOUS
+  /* Stop the auto-restart PPI channel so STOP actually halts the SAADC */
+
+  nrf52_ppi_channel_enable(CONFIG_NRF52_SAADC_CONTINUOUS_PPI_CH, false);
+#endif
+
   /* Stop SAADC */
 
   nrf52_adc_putreg(priv, NRF52_SAADC_TASKS_STOP_OFFSET, 1);
@@ -842,7 +945,13 @@ static void nrf52_adc_rxint(struct adc_dev_s *dev, bool 
enable)
 
   ainfo("RXINT enable: %d\n", enable ? 1 : 0);
 
+#ifdef CONFIG_NRF52_SAADC_CONTINUOUS
+  /* STARTED reloads the double-buffer DMA pointer; END delivers batches */
+
+  regval = SAADC_INT_END | SAADC_INT_STARTED;
+#else
   regval = SAADC_INT_END;
+#endif
 
   if (enable)
     {
@@ -875,6 +984,23 @@ static int nrf52_adc_ioctl(struct adc_dev_s *dev, int cmd,
     {
       case ANIOC_TRIGGER:
         {
+#ifdef CONFIG_NRF52_SAADC_CONTINUOUS
+          /* Enable the auto-restart PPI channel, then start. The local
+           * timer drives sampling and the PPI keeps the SAADC running,
+           * so no manual SAMPLE trigger is needed.
+           */
+
+          nrf52_ppi_channel_enable(CONFIG_NRF52_SAADC_CONTINUOUS_PPI_CH,
+                                   true);
+
+          nrf52_adc_putreg(priv, NRF52_SAADC_TASKS_START_OFFSET, 1);
+
+          /* Kick the first conversion; the local timer and PPI keep it
+           * running from there.
+           */
+
+          nrf52_adc_putreg(priv, NRF52_SAADC_TASKS_SAMPLE_OFFSET, 1);
+#else
           /* Start ADC */
 
           nrf52_adc_putreg(priv, NRF52_SAADC_TASKS_START_OFFSET, 1);
@@ -882,6 +1008,7 @@ static int nrf52_adc_ioctl(struct adc_dev_s *dev, int cmd,
           /* Trigger first sample */
 
           nrf52_adc_putreg(priv, NRF52_SAADC_TASKS_SAMPLE_OFFSET, 1);
+#endif
         }
         break;
 

Reply via email to