Skip to main content
Graduate
July 5, 2025
Question

STM32F4: Continuous DMA won't iterate, stuck on first SPI command

  • July 5, 2025
  • 1 reply
  • 495 views

I'm working on a DMA routine for my SPI DAC, and I've loaded up a buffer with the commands to make a sine wave. But when I check it with the oscilloscope, it's just stuck at 2.05V, which is actually the very first command in my sequence.

I've spent hours on it, trying everything from physically wiring LDAC low to fiddling with different write commands, and still no luck.

I'm really hoping this is something super simple I'm missing, because I'm still pretty new to embedded and C.

// AD5668_DMA.cpp
#include "AD5668_DMA.h"
#include "stm32f4xx.h"

// AD5668 “write input & update N” command base
static constexpr uint8_t CMD_WU = 0x30;

AD5668_DMA::AD5668_DMA(uint8_t csPin, uint8_t dacChan, bool circular)
 : _csPin(csPin), _dacChan(dacChan & 0x0F),
 _samples(0), _bytes(0), _buf(nullptr), _circ(circular)
{
 pinMode(_csPin, OUTPUT);
 digitalWrite(_csPin, HIGH);
}

void AD5668_DMA::begin(const uint16_t* wavetable, uint16_t size) {
 _samples = size;
 _bytes = size * 4; // 4 bytes per sample
 delete[] _buf;
 _buf = new uint8_t[_bytes];
 packFrames(wavetable);

 // 1) Enable DMA2 clock
 RCC->AHB1ENR |= RCC_AHB1ENR_DMA2EN;

 // 2) Disable stream for config
 DMA2_Stream3->CR &= ~DMA_SxCR_EN;
 while (DMA2_Stream3->CR & DMA_SxCR_EN);

 // 3) Point to SPI1->DR & our buffer
 DMA2_Stream3->PAR = (uint32_t)&SPI1->DR;
 DMA2_Stream3->M0AR = (uint32_t)_buf;
 DMA2_Stream3->NDTR = _bytes;

 // 4) Build CR for 8‑bit, mem→periph, incr, high‑prio, optional circ
 uint32_t cr = 
 (3 << DMA_SxCR_CHSEL_Pos) | // Channel 3
 DMA_SxCR_DIR_0 | // Mem→Periph
 DMA_SxCR_MINC | // Mem inc
 /* MSIZE=0, PSIZE=0 ⇒ 8‑bit */ 
 DMA_SxCR_PL_1; // High prio

 if (_circ) cr |= DMA_SxCR_CIRC;

 DMA2_Stream3->CR = cr;

 // 5) Enable TX‑DMA on SPI1
 SPI1->CR2 |= SPI_CR2_TXDMAEN;
}

void AD5668_DMA::start() {
 if (!_buf) return;
 digitalWrite(_csPin, LOW);
 delayMicroseconds(1);
 DMA2_Stream3->CR |= DMA_SxCR_EN;
}

void AD5668_DMA::stop() {
 DMA2_Stream3->CR &= ~DMA_SxCR_EN;
 while (DMA2_Stream3->CR & DMA_SxCR_EN);
 digitalWrite(_csPin, HIGH);
}

void AD5668_DMA::packFrames(const uint16_t* wavetable) {
 for (uint16_t i = 0; i < _samples; i++) {
 uint16_t v = wavetable[i];
 uint8_t b1 = CMD_WU | _dacChan;
 uint8_t b2 = (_dacChan << 4) | (v >> 12);
 uint8_t b3 = (v >> 4) & 0xFF;
 uint8_t b4 = ((v << 4) & 0xF0) | 0x0F;

 uint32_t idx = i * 4;
 _buf[idx + 0] = b1;
 _buf[idx + 1] = b2;
 _buf[idx + 2] = b3;
 _buf[idx + 3] = b4;
 }
}

 

// Command definition list
#define WRITE_INPUT_REGISTER 0
#define UPDATE_OUTPUT_REGISTER 1
#define WRITE_INPUT_REGISTER_UPDATE_ALL 2
#define WRITE_INPUT_REGISTER_UPDATE_N 3
#define POWER_DOWN_UP_DAC 4
#define LOAD_CLEAR_CODE_REGISTER 5
#define LOAD_LDAC_REGISTER 6
#define RESET_POWER_ON 7
#define SETUP_INTERNAL_REF 8

 

// main.ino
#include <Arduino.h>
#include <SPI.h>
#include "AD5668_DMA.h"
#include "AD5668.h"

#define CS_PIN PB0
#define CLR_PIN PB1
#define LDAC_PIN PB10
#define WAVETABLE_SIZE 256

// circular=true for continuous sine
AD5668_DMA dmaDac(CS_PIN, /*chan=*/0, /*circ=*/true);
AD5668 dac(CS_PIN, CLR_PIN, LDAC_PIN);

uint16_t waveform[WAVETABLE_SIZE];

void generateSine() {
 for (int i = 0; i < WAVETABLE_SIZE; i++) {
 float a = 2*PI * i / WAVETABLE_SIZE;
 waveform[i] = (uint16_t)((sin(a) + 1.0) * 32767.5);
 }
}

void setup() {
 // 1) SPI (8‑bit default)
 SPI.begin();
 SPI.beginTransaction(SPISettings(8000000, MSBFIRST, SPI_MODE1));

 // 2) AD5668 init
 dac.init();
 dac.enableInternalRef();
 dac.powerDAC_Normal(B11111111);

 // 3) Build LUT & DMA
 generateSine();
 dmaDac.begin(waveform, WAVETABLE_SIZE);

 // 4) Start continuous stream
 dmaDac.start();
}

void loop() {
 // nothing — hardware is running
}
    This topic has been closed for replies.

    1 reply

    Super User
    July 5, 2025

    The STM32F4 SPI can't drive the CS pin high and low between bytes. Even if it could, you're using 8-bit words and the chip needs 16-bit words. Use a logic analyzer to see what's happening on the lines.

    fufu930Author
    Graduate
    July 5, 2025

    Ouch, I'm not even flipping CS (SYNC), just only once before the first message. My SPI transfers are 8-bit long and the whole message is 32-bit, so that part seems alright. I can find libraries for that chip and they do in fact set CS HIGH after the 4 x 8-bit SPI transfers.
    As per datasheet:5668-sync.png
    So now I must find a way to control CS based on DMA transfer count with handlers and move everything to a 48 kHz timer to send one command per tick.

    Edit: And also reduce the buffer size to the message size (32 bits) and on each TC interrupt set CS high and set the next consecutive M0AR address?

    Super User
    July 5, 2025

    Yep, you understand what needs to happen.

    Set CS high when the SPI is done, not when TC is raised. TC only indicates the DMA has transferred all data. You can take a look at HAL_SPI_TransmitReceive_DMA fow how this is done correctly.

     

    Newer chip families handle this better. On F4, options are fewer. Synchronizing a PWM signal to act as CS can work if done correctly.