STM32F4: Continuous DMA won't iterate, stuck on first SPI command
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
}