stm32h7/dac: add DMA stream mode with ioctl-driven double-buffering

Add DMA support for DAC output with configurable double-buffering
via ioctl interface:

- ANIOC_DAC_DMABUFF_INIT: Copy full buffer into DMA buffer (memcpy)
- ANIOC_DAC_DMA_START: Start DMA with optional half-transfer interrupts
- ANIOC_DAC_DMA_STOP: Stop DMA and timer
- ANIOC_DAC_DMA_GET_EVENT: Wait for half-transfer complete event
- ANIOC_DAC_DMA_WRITE_HBUF: Write half-buffer into DMA buffer
- ANIOC_DAC_INFO: Query DAC capabilities (resolution, DMA, buffer size)

Stream mode: when halfint=1, both HTIF and TCIF generate events
via a ring buffer and semaphore. User writes the completed half
while DMA fills the other half. TCIF indicates h=1, HTIF h=0.

DMA priority is configurable per-channel via Kconfig choice
(Low/Medium/High/VeryHigh), defaulting to Medium.

Signed-off-by: Andrey Sobol <andrey.sobol.nn@gmail.com>
This commit is contained in:
Andrey Sobol 2026-07-14 23:06:48 +03:00 committed by Alan C. Assis
parent 85041e1699
commit 9bf825fd4f
4 changed files with 267 additions and 53 deletions

View file

@ -101,6 +101,26 @@ config STM32_DAC1CH1_DMA_EXTERNAL
bool "DAC1CH1 DMA External Trigger"
default n
choice
prompt "DAC1CH1 DMA priority"
default STM32_DAC1CH1_DMA_PRIORITY_MEDIUM
---help---
DMA stream priority for DAC1 channel 1.
config STM32_DAC1CH1_DMA_PRIORITY_LOW
bool "Low"
config STM32_DAC1CH1_DMA_PRIORITY_MEDIUM
bool "Medium"
config STM32_DAC1CH1_DMA_PRIORITY_HIGH
bool "High"
config STM32_DAC1CH1_DMA_PRIORITY_VERYHIGH
bool "Very High"
endchoice
if STM32_HRTIM_DAC
config STM32_DAC1CH1_HRTIM_TRG1
@ -163,6 +183,26 @@ config STM32_DAC1CH2_DMA_EXTERNAL
bool "DAC1CH2 DMA External Trigger"
default n
choice
prompt "DAC1CH2 DMA priority"
default STM32_DAC1CH2_DMA_PRIORITY_MEDIUM
---help---
DMA stream priority for DAC1 channel 2.
config STM32_DAC1CH2_DMA_PRIORITY_LOW
bool "Low"
config STM32_DAC1CH2_DMA_PRIORITY_MEDIUM
bool "Medium"
config STM32_DAC1CH2_DMA_PRIORITY_HIGH
bool "High"
config STM32_DAC1CH2_DMA_PRIORITY_VERYHIGH
bool "Very High"
endchoice
if STM32_HRTIM_DAC
config STM32_DAC1CH2_HRTIM_TRG1

View file

@ -30,6 +30,7 @@
#include <sys/types.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <debug.h>
@ -125,6 +126,32 @@
# define HAVE_DMA 1
#endif
/* DMA priority macros from per-channel Kconfig choices */
#ifdef CONFIG_STM32_DAC1CH1_DMA
# if defined(CONFIG_STM32_DAC1CH1_DMA_PRIORITY_LOW)
# define DAC1CH1_DMA_PRI 0
# elif defined(CONFIG_STM32_DAC1CH1_DMA_PRIORITY_MEDIUM)
# define DAC1CH1_DMA_PRI 1
# elif defined(CONFIG_STM32_DAC1CH1_DMA_PRIORITY_HIGH)
# define DAC1CH1_DMA_PRI 2
# else
# define DAC1CH1_DMA_PRI 3
# endif
#endif
#ifdef CONFIG_STM32_DAC1CH2_DMA
# if defined(CONFIG_STM32_DAC1CH2_DMA_PRIORITY_LOW)
# define DAC1CH2_DMA_PRI 0
# elif defined(CONFIG_STM32_DAC1CH2_DMA_PRIORITY_MEDIUM)
# define DAC1CH2_DMA_PRI 1
# elif defined(CONFIG_STM32_DAC1CH2_DMA_PRIORITY_HIGH)
# define DAC1CH2_DMA_PRI 2
# else
# define DAC1CH2_DMA_PRI 3
# endif
#endif
/* Timer trigger selection for DAC1 channels.
* On STM32H7, TSEL[3:0] encoding (from stm32h7xx_ll_dac.h):
* 0001: TIM1_TRGO (DAC_CR_TSEL1_0)
@ -346,17 +373,23 @@ struct stm32_chan_s
uint32_t dro; /* Data output register address */
uint32_t tsel; /* CR trigger select value */
#ifdef HAVE_DMA
uint8_t hasdma : 1; /* True, this channel supports DMA */
uint8_t dma_active : 1; /* True, DMA transfer is running */
uint8_t timer; /* Timer number for DMA trigger */
uint16_t dmachan; /* DMA channel */
uint16_t buffer_len; /* DMA buffer length */
DMA_HANDLE dma; /* Allocated DMA channel */
uint32_t tbase; /* Timer base address */
uint32_t tfrequency; /* Desired timer frequency */
int result; /* DMA result */
uint16_t buffer_pos; /* Position in dmabuffer */
uint16_t *dmabuffer; /* DMA transfer buffer */
uint8_t hasdma : 1; /* True, this channel supports DMA */
uint8_t dma_active : 1; /* True, DMA transfer is running */
uint8_t halfint : 1; /* True, HT interrupt enabled */
uint8_t dma_priority : 2; /* DMA PL field (DMA_SCR_PRI*) */
uint8_t timer; /* Timer number for DMA trigger */
uint16_t dmachan; /* DMA channel */
uint16_t buffer_len; /* DMA buffer length */
DMA_HANDLE dma; /* Allocated DMA channel */
uint32_t tbase; /* Timer base address */
uint32_t tfrequency; /* Desired timer frequency */
int result; /* DMA result */
uint16_t buffer_pos; /* Position in dmabuffer */
uint16_t *dmabuffer; /* DMA transfer buffer */
sem_t dma_halfsem; /* Stream half-completion sem */
uint8_t dma_half_q[16]; /* Ring of completed half indices */
uint8_t dma_half_q_head;
uint8_t dma_half_q_tail;
#endif
};
@ -385,7 +418,7 @@ static int stm32_dac_ioctl(struct dac_dev_s *dev, int cmd,
#ifdef HAVE_DMA
static int stm32_dac_timinit(struct stm32_chan_s *chan);
static void stm32_dac_timstart(struct stm32_chan_s *chan);
static void stm32_dac_dma_start(struct stm32_chan_s *chan);
static void stm32_dac_dma_start(struct stm32_chan_s *chan, bool halfint);
static void stm32_dac_dma_stop(struct stm32_chan_s *chan);
static int stm32_dac_dmainit(struct stm32_chan_s *chan);
#endif
@ -459,6 +492,7 @@ static struct stm32_chan_s g_dac1ch1priv =
.timer = CONFIG_STM32_DAC1CH1_TIMER,
.tbase = DAC1_CH1_TIMER_BASE,
.tfrequency = CONFIG_STM32_DAC1CH1_TIMER_FREQUENCY,
.dma_priority = DAC1CH1_DMA_PRI,
#endif
};
@ -497,6 +531,7 @@ static struct stm32_chan_s g_dac1ch2priv =
.timer = CONFIG_STM32_DAC1CH2_TIMER,
.tbase = DAC1_CH2_TIMER_BASE,
.tfrequency = CONFIG_STM32_DAC1CH2_TIMER_FREQUENCY,
.dma_priority = DAC1CH2_DMA_PRI,
#endif
};
@ -629,6 +664,10 @@ static int stm32_dac_setup(struct dac_dev_s *dev)
#ifdef HAVE_DMA
chan->buffer_pos = 0;
chan->halfint = 0;
nxsem_init(&chan->dma_halfsem, 0, 0);
chan->dma_half_q_head = 0;
chan->dma_half_q_tail = 0;
#endif
return OK;
@ -644,6 +683,7 @@ static void stm32_dac_shutdown(struct dac_dev_s *dev)
#ifdef HAVE_DMA
stm32_dac_dma_stop(chan);
nxsem_destroy(&chan->dma_halfsem);
#endif
stm32_dac_modify_cr(chan, DAC_CR_EN(chan->ch), 0);
@ -667,28 +707,37 @@ static void stm32_dac_dmatxcallback(DMA_HANDLE handle, uint8_t isr,
void *arg)
{
struct stm32_chan_s *chan = (struct stm32_chan_s *)arg;
struct dac_dev_s *dev;
DEBUGASSERT(chan);
#ifdef CONFIG_STM32_DAC1CH1
if (chan->ch == 1)
dev = &g_dac1ch1dev;
else
#endif
#ifdef CONFIG_STM32_DAC1CH2
if (chan->ch == 2)
dev = &g_dac1ch2dev;
else
#endif
DEBUGPANIC();
DEBUGASSERT(dev->ad_priv == chan);
if (chan->result == -EBUSY)
{
chan->result = (isr & DMA_STREAM_TEIF_BIT) ? -EIO : OK;
dac_txdone(dev);
if (isr & DMA_STREAM_TEIF_BIT)
{
chan->result = -EIO;
}
else if (chan->halfint)
{
uint8_t h;
if (isr & DMA_STREAM_HTIF_BIT)
{
h = 0;
}
else
{
h = 1;
}
chan->dma_half_q[chan->dma_half_q_head] = h;
chan->dma_half_q_head =
(chan->dma_half_q_head + 1) & 15;
nxsem_post(&chan->dma_halfsem);
}
else
{
chan->result = OK;
}
}
}
#endif
@ -701,24 +750,9 @@ static int stm32_dac_send(struct dac_dev_s *dev, struct dac_msg_s *msg)
{
struct stm32_chan_s *chan = dev->ad_priv;
#ifdef HAVE_DMA
if (chan->hasdma)
{
chan->dmabuffer[chan->buffer_pos++] = (uint16_t)msg->am_data;
dac_txdone(dev);
if (chan->buffer_pos >= chan->buffer_len && !chan->dma_active)
{
stm32_dac_dma_start(chan);
}
}
else
#endif
{
stm32_dac_modify_cr(chan, 0, DAC_CR_EN(chan->ch));
putreg16(msg->am_data, chan->dro);
dac_txdone(dev);
}
stm32_dac_modify_cr(chan, 0, DAC_CR_EN(chan->ch));
putreg16(msg->am_data, chan->dro);
dac_txdone(dev);
return OK;
}
@ -729,7 +763,105 @@ static int stm32_dac_send(struct dac_dev_s *dev, struct dac_msg_s *msg)
static int stm32_dac_ioctl(struct dac_dev_s *dev, int cmd, unsigned long arg)
{
return -ENOTTY;
int ret = -ENOTTY;
struct stm32_chan_s *chan = dev->ad_priv;
switch (cmd)
{
#ifdef HAVE_DMA
case ANIOC_DAC_DMABUFF_INIT:
{
uint16_t *buffer = (uint16_t *)arg;
/* The caller is responsible for providing buffer with
* suitable length equal to CONFIG_STM32_DACxCHy_DMA_BUFFER_SIZE
*/
uint32_t len = chan->buffer_len * sizeof(uint16_t);
memcpy(chan->dmabuffer, buffer, len);
#ifdef CONFIG_ARMV7M_DCACHE
up_clean_dcache((uintptr_t)chan->dmabuffer,
(uintptr_t)chan->dmabuffer + len);
#endif
ret = OK;
}
break;
case ANIOC_DAC_DMA_START:
{
struct dac_dma_start_s *req =
(struct dac_dma_start_s *)arg;
chan->halfint = req->halfint;
stm32_dac_dma_start(chan, req->halfint);
ret = OK;
}
break;
case ANIOC_DAC_DMA_STOP:
stm32_dac_dma_stop(chan);
chan->halfint = 0;
ret = OK;
break;
case ANIOC_DAC_DMA_GET_EVENT:
{
struct dac_dma_event_s *req =
(struct dac_dma_event_s *)arg;
ret = nxsem_wait(&chan->dma_halfsem);
if (ret == OK)
{
req->half =
chan->dma_half_q[chan->dma_half_q_tail];
chan->dma_half_q_tail =
(chan->dma_half_q_tail + 1) & 15;
}
}
break;
case ANIOC_DAC_DMAHBUF_WRITE:
{
struct dac_dma_event_s *req =
(struct dac_dma_event_s *)arg;
uint32_t half_len = chan->buffer_len / 2;
uint16_t *dst = chan->dmabuffer + req->half * half_len;
memcpy(dst, req->buffer, half_len * sizeof(uint16_t));
#ifdef CONFIG_ARMV7M_DCACHE
up_clean_dcache((uintptr_t)dst,
(uintptr_t)dst + half_len * sizeof(uint16_t));
#endif
ret = OK;
}
break;
#endif
case ANIOC_DAC_INFO:
{
struct dac_info_s *info = (struct dac_info_s *)arg;
info->sample_bits = 12;
#ifdef HAVE_DMA
info->dma_enabled = chan->dma_active;
info->halfint_enabled = chan->halfint;
info->dma_buffer_size = chan->buffer_len;
info->dma_timer_frequency = chan->tfrequency;
#else
info->dma_enabled = false;
info->halfint_enabled = 0;
info->dma_buffer_size = 0;
info->dma_timer_frequency = 0;
#endif
ret = OK;
}
break;
default:
break;
}
return ret;
}
/****************************************************************************
@ -849,7 +981,7 @@ static void stm32_dac_timstart(struct stm32_chan_s *chan)
****************************************************************************/
#ifdef HAVE_DMA
static void stm32_dac_dma_start(struct stm32_chan_s *chan)
static void stm32_dac_dma_start(struct stm32_chan_s *chan, bool halfint)
{
if (chan->dma_active)
return;
@ -862,7 +994,7 @@ static void stm32_dac_dma_start(struct stm32_chan_s *chan)
chan->buffer_len * sizeof(uint16_t));
#endif
stm32_dmastart(chan->dma, stm32_dac_dmatxcallback, chan, false);
stm32_dmastart(chan->dma, stm32_dac_dmatxcallback, chan, halfint);
stm32_dac_modify_cr(chan, 0, DAC_CR_EN(chan->ch) |
DAC_CR_TEN(chan->ch) |
DAC_CR_DMAEN(chan->ch));
@ -908,7 +1040,8 @@ static int stm32_dac_dmainit(struct stm32_chan_s *chan)
dmacfg.paddr = chan->dro;
dmacfg.maddr = (uint32_t)chan->dmabuffer;
dmacfg.ndata = chan->buffer_len;
dmacfg.cfg1 = DAC_DMA_CONTROL_WORD;
dmacfg.cfg1 = DAC_DMA_CONTROL_WORD |
(chan->dma_priority << DMA_SCR_PL_SHIFT);
dmacfg.cfg2 = 0;
stm32_dmasetup(chan->dma, &dmacfg);
@ -1064,7 +1197,7 @@ static void stm32_dac_llops_writedro(struct stm32_dac_dev_s *dev,
static void stm32_dac_llops_startdma(struct stm32_dac_dev_s *dev)
{
struct stm32_chan_s *priv = (struct stm32_chan_s *)dev;
stm32_dac_dma_start(priv);
stm32_dac_dma_start(priv, false);
}
/****************************************************************************

View file

@ -39,6 +39,7 @@
#include <nuttx/mutex.h>
#include <nuttx/semaphore.h>
#include <nuttx/spi/spi.h>
#include <nuttx/analog/ioctl.h>
/****************************************************************************
* Pre-processor Definitions
@ -60,6 +61,26 @@
* Public Types
****************************************************************************/
struct dac_dma_start_s
{
uint8_t halfint; /* 0 = transfer complete only, 1 = half + transfer complete */
};
struct dac_dma_event_s
{
FAR uint16_t *buffer; /* data to write (WRITE_BUF) */
int half; /* 0 = first half, 1 = second half */
};
struct dac_info_s
{
uint8_t sample_bits; /* DAC resolution (12 bits) */
uint8_t dma_enabled; /* 1 if DMA is running */
uint8_t halfint_enabled; /* current HTIT state */
uint32_t dma_buffer_size; /* DMA buffer length in samples */
uint32_t dma_timer_frequency; /* timer output frequency in Hz */
};
begin_packed_struct struct dac_msg_s
{
uint8_t am_channel; /* The 8-bit DAC Channel */

View file

@ -65,9 +65,29 @@
* IN: None
* OUT: Number of samples
* waiting to be read */
#define ANIOC_DAC_DMABUFF_INIT _ANIOC(0x0007) /* Copy full buffer to DAC DMA
* IN: uint16_t * (buffer to copy)
* Caller must provide buffer of
* CONFIG_STM32_DACxCHy_DMA_BUFFER_SIZE
* OUT: None */
#define ANIOC_DAC_DMA_START _ANIOC(0x0008) /* Start DAC DMA transfer
* IN: struct dac_dma_start_s *
* OUT: None */
#define ANIOC_DAC_DMA_STOP _ANIOC(0x0009) /* Stop DAC DMA transfer
* IN: None
* OUT: None */
#define ANIOC_DAC_DMA_GET_EVENT _ANIOC(0x000a) /* Wait for half-transfer DMA event
* IN: None
* OUT: struct dac_dma_event_s * */
#define ANIOC_DAC_DMAHBUF_WRITE _ANIOC(0x000b) /* Write half-buffer to DMA
* IN: struct dac_dma_event_s *
* OUT: None */
#define ANIOC_DAC_INFO _ANIOC(0x000c) /* Get DAC info
* IN: None
* OUT: struct dac_info_s * */
#define AN_FIRST 0x0001 /* First common command */
#define AN_NCMDS 6 /* Number of common commands */
#define AN_NCMDS 12 /* Number of common commands */
/* User defined ioctl commands are also supported. These will be forwarded
* by the upper-half driver to the lower-half driver via the ioctl()