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

michallenc 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 a8ac2e74e0c arch/nrf52: add SAADC external TIMER trigger over PPI
a8ac2e74e0c is described below

commit a8ac2e74e0c65c6e8c99009078d63dc66352a2d7
Author: raiden00pl <[email protected]>
AuthorDate: Sun Aug 9 12:04:56 2026 +0200

    arch/nrf52: add SAADC external TIMER trigger over PPI
    
    The SAADC internal sample timer only works with a single enabled
    channel, so hardware-timed multi-channel scan was not possible.  Add
    NRF52_SAADC_TIMER_PPI, a third trigger mode in which a general-purpose
    TIMER compare event is routed to TASKS_SAMPLE over PPI.  All enabled
    channels are scanned, and the TIMER prescaler allows much lower sample
    rates than the internal timer, which is limited to 16MHz/CC with CC in
    80..2047.
    
    NRF52_SAADC_CONTINUOUS is no longer tied to the internal timer and
    works with either source.  Its EasyDMA buffers now hold
    NRF52_SAADC_CONTINUOUS_BUFLEN whole scans rather than that many single
    samples, so MAXCNT becomes chan_len * BUFLEN.  Samples are interleaved
    scan by scan, so a channel map is built once at configure() time and
    passed to the upper half with the batch; the upper half already accepts
    a per-sample channel array.  A single-channel configuration produces
    the same MAXCNT and the same delivery as before.
    
    Because both features want a PPI channel, add a build-time check that
    NRF52_SAADC_PPI_CHANNEL and NRF52_SAADC_CONTINUOUS_PPI_CH differ, and
    constrain the latter under the SoftDevice controller like the former.
    
    NRF52_SAADC_CHANNELS gains a default and range for the new mode, and
    documents that the internal timer is restricted to one channel.
    
    Assisted-by: Claude Code
    Signed-off-by: raiden00pl <[email protected]>
---
 Documentation/platforms/arm/nrf52/index.rst |  53 +++++
 arch/arm/src/nrf52/Kconfig                  |  88 +++++++-
 arch/arm/src/nrf52/nrf52_adc.c              | 312 +++++++++++++++++++++++++---
 3 files changed, 419 insertions(+), 34 deletions(-)

diff --git a/Documentation/platforms/arm/nrf52/index.rst 
b/Documentation/platforms/arm/nrf52/index.rst
index f2fd75315a6..34cb8cff1b3 100644
--- a/Documentation/platforms/arm/nrf52/index.rst
+++ b/Documentation/platforms/arm/nrf52/index.rst
@@ -109,6 +109,59 @@ ADC
 The SAADC peripheral is exposed via standard ADC driver. The lower-half of 
this driver
 is initialized by calling :c:func:`nrf52_adcinitialize`.
 
+Three sampling trigger modes are available:
+
+* ``CONFIG_NRF52_SAADC_TASK``: each ``ANIOC_TRIGGER`` starts one scan of
+  all configured channels.
+* ``CONFIG_NRF52_SAADC_TIMER``: the internal SAADC timer supports one
+  channel only. The sample rate is ``16000000 / CONFIG_NRF52_SAADC_TIMER_CC``
+  Hz, with a compare value from 80 to 2047 (about 7.8 kHz to 200 kHz).
+* ``CONFIG_NRF52_SAADC_TIMER_PPI``: a general-purpose TIMER compare event
+  triggers a scan over PPI. This supports up to eight channels and lower
+  scan rates through the TIMER prescaler.
+
+For TIMER/PPI sampling, enable the TIMER instance selected by
+``CONFIG_NRF52_SAADC_PPI_TIMER`` and reserve it for the ADC. Select an unused
+PPI channel with ``CONFIG_NRF52_SAADC_PPI_CHANNEL``. The scan rate is
+``16000000 / (2^CONFIG_NRF52_SAADC_PPI_PRE * CONFIG_NRF52_SAADC_PPI_CC)`` Hz.
+For example, these settings select TIMER2 and a 1 kHz scan rate:
+
+.. code-block:: ini
+
+   CONFIG_NRF52_SAADC=y
+   CONFIG_NRF52_TIMER2=y
+   CONFIG_NRF52_SAADC_TIMER_PPI=y
+   CONFIG_NRF52_SAADC_PPI_TIMER=2
+   CONFIG_NRF52_SAADC_PPI_CHANNEL=0
+   CONFIG_NRF52_SAADC_PPI_PRE=4
+   CONFIG_NRF52_SAADC_PPI_CC=1000
+   CONFIG_NRF52_SAADC_CHANNELS=2
+
+The board must pass the channel configuration to ``nrf52_adcinitialize``;
+``CONFIG_NRF52_SAADC_CHANNELS`` sets the maximum number of channels.
+With either timer source, issue ``ANIOC_TRIGGER`` once to start sampling.
+
+``CONFIG_NRF52_SAADC_CONTINUOUS`` enables double-buffered EasyDMA with
+either timer source. A second PPI channel restarts the SAADC on each END
+event. For example, add:
+
+.. code-block:: ini
+
+   CONFIG_NRF52_SAADC_CONTINUOUS=y
+   CONFIG_NRF52_SAADC_CONTINUOUS_BUFLEN=32
+   CONFIG_NRF52_SAADC_CONTINUOUS_PPI_CH=1
+
+``CONFIG_NRF52_SAADC_CONTINUOUS_BUFLEN`` counts whole scans per buffer.
+With two configured channels and a buffer length of 32, each buffer holds
+64 samples in channel order: ``0, 1, 0, 1, ...``. Completed buffers are
+delivered to the ADC upper half with a per-sample channel map. The number
+of channels multiplied by the buffer length must not exceed 32767.
+Single-channel buffer sizing is unchanged.
+
+The sampling and restart PPI channels must differ and must be unused by
+other drivers. Both channel selections are restricted to 0--15 when
+``CONFIG_NRF52_SOFTDEVICE_CONTROLLER`` is enabled, or 0--19 otherwise.
+
 I2C
 ---
 
diff --git a/arch/arm/src/nrf52/Kconfig b/arch/arm/src/nrf52/Kconfig
index 8548071edf6..70c8e3b1878 100644
--- a/arch/arm/src/nrf52/Kconfig
+++ b/arch/arm/src/nrf52/Kconfig
@@ -682,9 +682,27 @@ choice
 
 config NRF52_SAADC_TASK
        bool "SAADC Task trigger"
+       ---help---
+               Every conversion is triggered explicitly by the CPU writing the
+               SAMPLE task.
 
 config NRF52_SAADC_TIMER
        bool "SAADC Timer trigger"
+       ---help---
+               Sampling is driven by the SAADC internal sample timer.  The
+               hardware supports this only with a single enabled channel, so
+               NRF52_SAADC_CHANNELS is forced to 1 in this mode.  The 
achievable
+               sample rate is 16MHz/CC, which limits it to roughly 
7.8kHz-200kHz.
+
+config NRF52_SAADC_TIMER_PPI
+       bool "SAADC Timer trigger via PPI"
+       depends on NRF52_TIMER0 || NRF52_TIMER1 || NRF52_TIMER2 || NRF52_TIMER3 
|| NRF52_TIMER4
+       select NRF52_PPI
+       ---help---
+               Sampling is driven by a general-purpose TIMER compare event 
routed
+               to the SAADC SAMPLE task over PPI.  Unlike the internal sample
+               timer this supports multi-channel scan and, thanks to the TIMER
+               prescaler, much lower sample rates.
 
 endchoice # SAADC trigger selection
 
@@ -695,8 +713,11 @@ config NRF52_SAADC_TIMER_CC
        default 0
        range 80 2047
 
+endif # NRF52_SAADC_TIMER
+
 config NRF52_SAADC_CONTINUOUS
        bool "SAADC continuous mode"
+       depends on NRF52_SAADC_TIMER || NRF52_SAADC_TIMER_PPI
        select NRF52_PPI
        default n
        ---help---
@@ -705,22 +726,66 @@ config NRF52_SAADC_CONTINUOUS
                channel and completed buffers are passed to the upper-half with
                the batch interface.
 
+               Works with either timer source.  With NRF52_SAADC_TIMER_PPI the
+               buffers hold whole interleaved scans and the batch is delivered
+               together with a per-sample channel map.
+
 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.
+               Number of scans in each of the two EasyDMA buffers.  A scan is 
one
+               conversion of every enabled channel, so each buffer holds
+               NRF52_SAADC_CHANNELS * NRF52_SAADC_CONTINUOUS_BUFLEN samples.
+               That product must not exceed 32767 (the SAADC MAXCNT field).
 
 config NRF52_SAADC_CONTINUOUS_PPI_CH
        int "SAADC continuous mode PPI channel"
-       range 0 19
+       range 0 15 if NRF52_SOFTDEVICE_CONTROLLER
+       range 0 19 if !NRF52_SOFTDEVICE_CONTROLLER
        default 0
+       ---help---
+               PPI channel used to restart the SAADC from its own END event.
+               Must differ from NRF52_SAADC_PPI_CHANNEL.
 
 endif # NRF52_SAADC_CONTINUOUS
 
-endif # NRF52_SAADC_TIMER
+if NRF52_SAADC_TIMER_PPI
+
+config NRF52_SAADC_PPI_TIMER
+       int "SAADC PPI trigger TIMER instance"
+       default 0
+       range 0 4
+       ---help---
+               General-purpose TIMER instance used to generate periodic compare
+               events routed via PPI into SAADC TASKS_SAMPLE.
+
+config NRF52_SAADC_PPI_CHANNEL
+       int "SAADC PPI channel"
+       default 0
+       range 0 15 if NRF52_SOFTDEVICE_CONTROLLER
+       range 0 19 if !NRF52_SOFTDEVICE_CONTROLLER
+       ---help---
+               PPI channel used to connect TIMER compare event to SAADC SAMPLE 
task.
+
+config NRF52_SAADC_PPI_PRE
+       int "SAADC PPI timer prescaler"
+       default 4
+       range 0 9
+       ---help---
+               TIMER prescaler value. Timer clock is 16 MHz / (2^prescaler).
+
+config NRF52_SAADC_PPI_CC
+       int "SAADC PPI timer compare value"
+       default 1000
+       range 1 2147483647
+       ---help---
+               Timer compare value used as sampling period tick.
+               Sample period = CC / (16 MHz / (2^prescaler)).
+
+endif # NRF52_SAADC_TIMER_PPI
 
 config NRF52_SAADC_OVERSAMPLE
        int "SAADC oversample"
@@ -738,10 +803,21 @@ config NRF52_SAADC_RESOLUTION
 
 config NRF52_SAADC_CHANNELS
        int "SAADC channels"
-       default 8
-       range 0 8
+       default 8 if NRF52_SAADC_TASK
+       default 1 if NRF52_SAADC_TIMER
+       default 8 if NRF52_SAADC_TIMER_PPI
+       range 1 8 if NRF52_SAADC_TASK
+       range 1 1 if NRF52_SAADC_TIMER
+       range 1 8 if NRF52_SAADC_TIMER_PPI
        ---help---
-               SAADC channels
+               SAADC channels.
+
+               The SAADC internal sample timer works with a single channel 
only,
+               so NRF52_SAADC_TIMER is restricted to 1.  The other trigger 
modes
+               scan all enabled channels on every SAMPLE task.
+
+               Batching several scans per interrupt is done with
+               NRF52_SAADC_CONTINUOUS_BUFLEN in continuous mode.
 
 config NRF52_SAADC_LIMITS
        bool "SAADC limits enable"
diff --git a/arch/arm/src/nrf52/nrf52_adc.c b/arch/arm/src/nrf52/nrf52_adc.c
index c03977a780d..eb9f5d64192 100644
--- a/arch/arm/src/nrf52/nrf52_adc.c
+++ b/arch/arm/src/nrf52/nrf52_adc.c
@@ -44,13 +44,44 @@
 #  include "nrf52_ppi.h"
 #endif
 
+#ifdef CONFIG_NRF52_SAADC_TIMER_PPI
+#  include "nrf52_ppi.h"
+#  include "nrf52_tim.h"
+#endif
+
 #include "hardware/nrf52_saadc.h"
 #include "hardware/nrf52_utils.h"
+#ifdef CONFIG_NRF52_SAADC_TIMER_PPI
+#  include "hardware/nrf52_ppi.h"
+#  include "hardware/nrf52_tim.h"
+#endif
 
 /****************************************************************************
  * Pre-processor Definitions
  ****************************************************************************/
 
+#if !defined(CONFIG_NRF52_SAADC_TASK) && \
+    !defined(CONFIG_NRF52_SAADC_TIMER) && \
+    !defined(CONFIG_NRF52_SAADC_TIMER_PPI)
+#  error SAADC trigger not selected
+#endif
+
+#if defined(CONFIG_NRF52_SAADC_TIMER_PPI) && \
+    defined(CONFIG_NRF52_SAADC_CONTINUOUS)
+#  if CONFIG_NRF52_SAADC_PPI_CHANNEL == CONFIG_NRF52_SAADC_CONTINUOUS_PPI_CH
+#    error SAADC sample and restart PPI channels must differ
+#  endif
+#endif
+
+#ifdef CONFIG_NRF52_SAADC_CONTINUOUS
+/* Samples in each of the two EasyDMA buffers.  A scan is one conversion of
+ * every enabled channel, so a buffer holds CONTINUOUS_BUFLEN whole scans.
+ */
+
+#  define NRF52_SAADC_DMALEN (CONFIG_NRF52_SAADC_CHANNELS * \
+                              CONFIG_NRF52_SAADC_CONTINUOUS_BUFLEN)
+#endif
+
 /****************************************************************************
  * Private Types
  ****************************************************************************/
@@ -74,10 +105,15 @@ struct nrf52_adc_s
    * 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.
+   *
+   * With more than one channel enabled a buffer holds whole interleaved
+   * scans and 'chanmap' carries the channel index of every sample.
    */
 
-  int16_t                    dmabuf[2][CONFIG_NRF52_SAADC_CONTINUOUS_BUFLEN];
-  uint32_t                   batch[CONFIG_NRF52_SAADC_CONTINUOUS_BUFLEN];
+  int16_t                    dmabuf[2][NRF52_SAADC_DMALEN];
+  uint32_t                   batch[NRF52_SAADC_DMALEN];
+  uint8_t                    chanmap[NRF52_SAADC_DMALEN];
+  uint16_t                   dmalen;  /* Samples per buffer: chan_len*BUFLEN */
   uint8_t                    next;
 #endif
 
@@ -85,6 +121,10 @@ struct nrf52_adc_s
   uint32_t                   base;       /* Base address of ADC register */
   uint32_t                   irq;        /* ADC interrupt */
   uint8_t                    resolution; /* ADC resolution */
+#ifdef CONFIG_NRF52_SAADC_TIMER_PPI
+  struct nrf52_tim_dev_s    *tim;        /* Timer used for PPI sampling */
+  bool                       ppi_en;     /* PPI channel enabled */
+#endif
 };
 
 /****************************************************************************
@@ -108,6 +148,11 @@ static uint32_t nrf52_adc_chanpsel(int psel);
 static int nrf52_adc_chancfg(struct nrf52_adc_s *priv, uint8_t chan,
                              struct nrf52_adc_channel_s *cfg);
 static int nrf52_adc_isr(int irq, void *context, void *arg);
+#ifdef CONFIG_NRF52_SAADC_TIMER_PPI
+static int nrf52_adc_ppi_setup(struct nrf52_adc_s *priv);
+static void nrf52_adc_ppi_shutdown(struct nrf52_adc_s *priv);
+static int nrf52_adc_ppi_start(struct nrf52_adc_s *priv);
+#endif
 
 /* ADC Driver Methods */
 
@@ -143,7 +188,11 @@ struct nrf52_adc_s g_nrf52_adcpriv =
   .cb         = NULL,
   .base       = NRF52_SAADC_BASE,
   .irq        = NRF52_IRQ_SAADC,
-  .resolution = CONFIG_NRF52_SAADC_RESOLUTION
+  .resolution = CONFIG_NRF52_SAADC_RESOLUTION,
+#ifdef CONFIG_NRF52_SAADC_TIMER_PPI
+  .tim        = NULL,
+  .ppi_en     = false
+#endif
 };
 
 /* Upper-half ADC device */
@@ -191,6 +240,152 @@ static inline uint32_t nrf52_adc_getreg(struct 
nrf52_adc_s *priv,
   return getreg32(priv->base + offset);
 }
 
+#ifdef CONFIG_NRF52_SAADC_TIMER_PPI
+/****************************************************************************
+ * Name: nrf52_adc_tim_base
+ *
+ * Description:
+ *   Get TIMER base address for configured SAADC PPI trigger timer.
+ *
+ ****************************************************************************/
+
+static inline uint32_t nrf52_adc_tim_base(void)
+{
+#if CONFIG_NRF52_SAADC_PPI_TIMER == 0
+  return NRF52_TIMER0_BASE;
+#elif CONFIG_NRF52_SAADC_PPI_TIMER == 1
+  return NRF52_TIMER1_BASE;
+#elif CONFIG_NRF52_SAADC_PPI_TIMER == 2
+  return NRF52_TIMER2_BASE;
+#elif CONFIG_NRF52_SAADC_PPI_TIMER == 3
+  return NRF52_TIMER3_BASE;
+#elif CONFIG_NRF52_SAADC_PPI_TIMER == 4
+  return NRF52_TIMER4_BASE;
+#else
+#  error Unsupported CONFIG_NRF52_SAADC_PPI_TIMER value
+#endif
+}
+
+/****************************************************************************
+ * Name: nrf52_adc_ppi_setup
+ *
+ * Description:
+ *   Configure a general purpose TIMER and route its compare event to SAADC
+ *   SAMPLE task over PPI.
+ *
+ ****************************************************************************/
+
+static int nrf52_adc_ppi_setup(struct nrf52_adc_s *priv)
+{
+  uint32_t tim_base = 0;
+  uint32_t ppi_ch   = CONFIG_NRF52_SAADC_PPI_CHANNEL;
+  int      ret      = OK;
+
+  DEBUGASSERT(priv);
+
+  priv->tim = nrf52_tim_init(CONFIG_NRF52_SAADC_PPI_TIMER);
+  if (priv->tim == NULL)
+    {
+      aerr("ERROR: failed to get TIMER%d for SAADC PPI\n",
+           CONFIG_NRF52_SAADC_PPI_TIMER);
+      ret = -EBUSY;
+      goto errout;
+    }
+
+  tim_base = nrf52_adc_tim_base();
+
+  /* Configure TIMER for periodic compare events */
+
+  NRF52_TIM_STOP(priv->tim);
+  NRF52_TIM_CLEAR(priv->tim);
+  NRF52_TIM_CONFIGURE(priv->tim, NRF52_TIM_MODE_TIMER, NRF52_TIM_WIDTH_32B);
+  NRF52_TIM_SETPRE(priv->tim, CONFIG_NRF52_SAADC_PPI_PRE);
+  NRF52_TIM_SETCC(priv->tim, NRF52_TIM_CC0, CONFIG_NRF52_SAADC_PPI_CC);
+  NRF52_TIM_SHORTS(priv->tim, NRF52_TIM_SHORT_COMPARE_CLEAR,
+                   NRF52_TIM_CC0, true);
+
+  /* Ensure compare event is clear before enabling PPI channel */
+
+  putreg32(0, tim_base + NRF52_TIM_EVENTS_COMPARE_OFFSET(0));
+
+  if ((getreg32(NRF52_PPI_CHEN) & PPI_CHEN_CH(ppi_ch)) != 0)
+    {
+      aerr("ERROR: PPI channel %ld already in use\n", (long)ppi_ch);
+      ret = -EBUSY;
+      goto errout;
+    }
+
+  nrf52_ppi_set_event_ep(ppi_ch,
+                         tim_base + NRF52_TIM_EVENTS_COMPARE_OFFSET(0));
+  nrf52_ppi_set_task_ep(ppi_ch,
+                        priv->base + NRF52_SAADC_TASKS_SAMPLE_OFFSET);
+  nrf52_ppi_channel_enable(ppi_ch, true);
+  priv->ppi_en = true;
+
+errout:
+  if (ret < 0)
+    {
+      nrf52_adc_ppi_shutdown(priv);
+    }
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: nrf52_adc_ppi_start
+ *
+ * Description:
+ *   Start TIMER used for PPI-driven sampling.
+ *
+ ****************************************************************************/
+
+static int nrf52_adc_ppi_start(struct nrf52_adc_s *priv)
+{
+  int ret = OK;
+
+  DEBUGASSERT(priv);
+  DEBUGASSERT(priv->tim != NULL);
+
+  /* Clear event/counter and then start periodic triggering */
+
+  NRF52_TIM_STOP(priv->tim);
+  NRF52_TIM_CLEAR(priv->tim);
+  ret = NRF52_TIM_START(priv->tim);
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: nrf52_adc_ppi_shutdown
+ *
+ * Description:
+ *   Release TIMER/PPI resources used for PPI-driven sampling.
+ *
+ ****************************************************************************/
+
+static void nrf52_adc_ppi_shutdown(struct nrf52_adc_s *priv)
+{
+  uint32_t ppi_ch = CONFIG_NRF52_SAADC_PPI_CHANNEL;
+
+  DEBUGASSERT(priv);
+
+  if (priv->ppi_en)
+    {
+      nrf52_ppi_channel_enable(ppi_ch, false);
+      priv->ppi_en = false;
+    }
+
+  if (priv->tim != NULL)
+    {
+      NRF52_TIM_SHORTS(priv->tim, NRF52_TIM_SHORT_COMPARE_CLEAR,
+                       NRF52_TIM_CC0, false);
+      NRF52_TIM_STOP(priv->tim);
+      nrf52_tim_deinit(priv->tim);
+      priv->tim = NULL;
+    }
+}
+#endif
+
 /****************************************************************************
  * Name: nrf52_adc_isr
  *
@@ -229,13 +424,13 @@ static int nrf52_adc_isr(int irq, void *context, void 
*arg)
 
       /* Give the completed buffer to the ADC driver */
 
-      for (i = 0; i < CONFIG_NRF52_SAADC_CONTINUOUS_BUFLEN; i += 1)
+      for (i = 0; i < priv->dmalen; 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);
+      ret = priv->cb->au_receive_batch(dev, priv->chanmap, priv->batch,
+                                       priv->dmalen);
       if (ret == -ENOMEM)
         {
           /* Receive FIFO overrun */
@@ -289,6 +484,16 @@ static int nrf52_adc_isr(int irq, void *context, void *arg)
       /* Clear event */
 
       nrf52_adc_putreg(priv, NRF52_SAADC_EVENTS_END_OFFSET, 0);
+
+#if defined(CONFIG_NRF52_SAADC_TIMER) || defined(CONFIG_NRF52_SAADC_TIMER_PPI)
+      /* In timer mode, END means the DMA buffer is full.  Re-start the
+       * SAADC so that the trigger source keeps producing conversions
+       * after a single ANIOC_TRIGGER.  This applies to both the internal
+       * sample timer and an external TIMER feeding SAMPLE over PPI.
+       */
+
+      nrf52_adc_putreg(priv, NRF52_SAADC_TASKS_START_OFFSET, 1);
+#endif
     }
 #endif
 
@@ -306,6 +511,9 @@ static int nrf52_adc_isr(int irq, void *context, void *arg)
 static int nrf52_adc_configure(struct nrf52_adc_s *priv)
 {
   int regval = 0;
+#ifdef CONFIG_NRF52_SAADC_CONTINUOUS
+  int i      = 0;
+#endif
 
   DEBUGASSERT(priv);
 
@@ -321,18 +529,19 @@ static int nrf52_adc_configure(struct nrf52_adc_s *priv)
 
   /* Configure sample rate */
 
-#if defined(CONFIG_NRF52_SAADC_TIMER)
-  /* Trigger from local timer */
+#ifdef CONFIG_NRF52_SAADC_TIMER
+  /* Sampling driven by the SAADC internal timer */
 
   regval = SAADC_SAMPLERATE_MODE_TIMERS;
   regval |= ((CONFIG_NRF52_SAADC_TIMER_CC & SAADC_SAMPLERATE_CC_MASK)
              << SAADC_SAMPLERATE_CC_SHIFT);
-#elif defined(CONFIG_NRF52_SAADC_TASK)
-  /* Trigger on SAMPLE tas */
+#else
+  /* Sampling driven by the SAMPLE task, written either by the CPU
+   * (NRF52_SAADC_TASK) or by a TIMER compare event over PPI
+   * (NRF52_SAADC_TIMER_PPI).
+   */
 
   regval = SAADC_SAMPLERATE_MODE_TASK;
-#else
-#  error SAADC trigger not selected
 #endif
 
   nrf52_adc_putreg(priv, NRF52_SAADC_SAMPLERATE_OFFSET, regval);
@@ -341,17 +550,28 @@ static int nrf52_adc_configure(struct nrf52_adc_s *priv)
 
 #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).
+   * Each buffer holds CONTINUOUS_BUFLEN scans of all enabled channels.
    */
 
-  priv->next = 0;
+  priv->next   = 0;
+  priv->dmalen = priv->chan_len * CONFIG_NRF52_SAADC_CONTINUOUS_BUFLEN;
+
+  DEBUGASSERT(priv->dmalen <= SAADC_MAXCNT_MASK);
+
+  /* Samples are interleaved scan by scan, so the channel index simply
+   * cycles.  Build the map once instead of per interrupt.
+   */
+
+  for (i = 0; i < priv->dmalen; i += 1)
+    {
+      priv->chanmap[i] = i % priv->chan_len;
+    }
 
   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);
+  nrf52_adc_putreg(priv, NRF52_SAADC_MAXCNT_OFFSET, priv->dmalen);
 
   /* Auto-restart the SAADC: END event -> START task via PPI */
 
@@ -365,6 +585,7 @@ static int nrf52_adc_configure(struct nrf52_adc_s *priv)
   nrf52_adc_putreg(priv, NRF52_SAADC_PTR_OFFSET, regval);
 
   regval = priv->chan_len;
+  DEBUGASSERT(regval <= SAADC_MAXCNT_MASK);
   nrf52_adc_putreg(priv, NRF52_SAADC_MAXCNT_OFFSET, regval);
 #endif
 
@@ -862,6 +1083,17 @@ static int nrf52_adc_setup(struct adc_dev_s *dev)
         }
     }
 
+#ifdef CONFIG_NRF52_SAADC_TIMER_PPI
+  /* Acquire the TIMER and PPI channel that drive TASKS_SAMPLE */
+
+  ret = nrf52_adc_ppi_setup(priv);
+  if (ret < 0)
+    {
+      aerr("ERROR: nrf52_adc_ppi_setup failed: %d\n", ret);
+      goto errout;
+    }
+#endif
+
   /* Enable ADC */
 
   nrf52_adc_putreg(priv, NRF52_SAADC_ENABLE_OFFSET, 1);
@@ -889,6 +1121,13 @@ static int nrf52_adc_setup(struct adc_dev_s *dev)
   up_enable_irq(priv->irq);
 
 errout:
+#ifdef CONFIG_NRF52_SAADC_TIMER_PPI
+  if (ret < 0)
+    {
+      nrf52_adc_ppi_shutdown(priv);
+    }
+#endif
+
   return ret;
 }
 
@@ -908,6 +1147,15 @@ static void nrf52_adc_shutdown(struct adc_dev_s *dev)
   DEBUGASSERT(dev);
   DEBUGASSERT(priv);
 
+#ifdef CONFIG_NRF52_SAADC_TIMER_PPI
+  /* Stop the trigger TIMER before halting the SAADC */
+
+  if (priv->tim != NULL)
+    {
+      NRF52_TIM_STOP(priv->tim);
+    }
+#endif
+
 #ifdef CONFIG_NRF52_SAADC_CONTINUOUS
   /* Stop the auto-restart PPI channel so STOP actually halts the SAADC */
 
@@ -925,6 +1173,12 @@ static void nrf52_adc_shutdown(struct adc_dev_s *dev)
   /* Disable SAADC */
 
   nrf52_adc_putreg(priv, NRF52_SAADC_ENABLE_OFFSET, 0);
+
+#ifdef CONFIG_NRF52_SAADC_TIMER_PPI
+  /* Release the trigger TIMER and its PPI channel */
+
+  nrf52_adc_ppi_shutdown(priv);
+#endif
 }
 
 /****************************************************************************
@@ -985,28 +1239,30 @@ 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.
+          /* Enable the auto-restart PPI channel so the SAADC is restarted
+           * from its own END event without the CPU in the loop.
            */
 
           nrf52_ppi_channel_enable(CONFIG_NRF52_SAADC_CONTINUOUS_PPI_CH,
                                    true);
+#endif
 
-          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);
 
-          /* Trigger first sample */
+          /* Trigger sampling.  With NRF52_SAADC_TIMER_PPI the TIMER
+           * compare event drives TASKS_SAMPLE over PPI; otherwise the
+           * first conversion is kicked here.
+           */
 
+#ifdef CONFIG_NRF52_SAADC_TIMER_PPI
+          ret = nrf52_adc_ppi_start(priv);
+          if (ret < 0)
+            {
+              aerr("ERROR: failed to start SAADC PPI timer: %d\n", ret);
+            }
+#else
           nrf52_adc_putreg(priv, NRF52_SAADC_TASKS_SAMPLE_OFFSET, 1);
 #endif
         }

Reply via email to