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
}