DAC and DMA in ArduinoIDE
Posted: Thu Feb 11, 2021 4:12 pm
Hey guys,
The help I got last time has got me up and running!
I'm building a little synth on the Blue Pill/STM32F103 (HID2.2 boot loader) with using Arduino IDE 1.8.13, on MacOS Mojave (10.14.6) and the stm32duino (STM Cores 1.9.0).
It works pretty well, but I'd love to get the DMA working for outputting to my DAC (12-bit MCP4921)! I've had a read around at some older posts, but I can only really find one that refers to a DAC and DMA (linked below), as opposed to quite a few ADC and DMA. I think I understand that I need to maybe disable/alter the way the Arduino API uses the HAL functions, but I'm not sure how (or if I even need to for a DAC).
viewtopic.php?t=442
I've currently got an interrupt that fires off at 44.1kHz. This handles my oscillator calculations, and then sending my 12-bit sample to my DAC over SPI. I've used a couple of low-level commands to reduce the overhead of the SPI sending in the interrupt, and I'm pretty certain I need to doing my oscillator calculations outside of the interrupt, but, it works (even with up to 8 separate oscillators).
My main focus right now is to have the DMA handle the DAC/SPI interaction. I'm not sure this will really give me much more overhead, but it feels like the correct way to do this... I'm not really looking to maintain portability, as my aim is to just develop this as is. (I was super close on the Arduino, but it was just too tight on power).
If someone could talk me through/point me in the right direction, I'd appreciate it!
Cheers,
Nick
The help I got last time has got me up and running!
I'm building a little synth on the Blue Pill/STM32F103 (HID2.2 boot loader) with using Arduino IDE 1.8.13, on MacOS Mojave (10.14.6) and the stm32duino (STM Cores 1.9.0).
It works pretty well, but I'd love to get the DMA working for outputting to my DAC (12-bit MCP4921)! I've had a read around at some older posts, but I can only really find one that refers to a DAC and DMA (linked below), as opposed to quite a few ADC and DMA. I think I understand that I need to maybe disable/alter the way the Arduino API uses the HAL functions, but I'm not sure how (or if I even need to for a DAC).
viewtopic.php?t=442
I've currently got an interrupt that fires off at 44.1kHz. This handles my oscillator calculations, and then sending my 12-bit sample to my DAC over SPI. I've used a couple of low-level commands to reduce the overhead of the SPI sending in the interrupt, and I'm pretty certain I need to doing my oscillator calculations outside of the interrupt, but, it works (even with up to 8 separate oscillators).
My main focus right now is to have the DMA handle the DAC/SPI interaction. I'm not sure this will really give me much more overhead, but it feels like the correct way to do this... I'm not really looking to maintain portability, as my aim is to just develop this as is. (I was super close on the Arduino, but it was just too tight on power).
If someone could talk me through/point me in the right direction, I'd appreciate it!
Cheers,
Nick
Code: Select all
/*
MCU - STM32F103
DAC - MCP4921
Test script for DAC output (12bit and 44.1kHz).
Plays a 440Hz tone
*/
#include "waveforms.h"
#include <SPI.h>
// -------------------------------- Hardware Definitions -------------------------------- //
#define LED PC13
#define slaveSelectPin PA4
// Other SPI 'default' SPI1 pins are :- MOSI1 = PA7 / SCLK1 = PA5
// ----------------------------- Software/Audio Definitions ----------------------------- //
#define sampleRate 44100 // 44.1kHz sampling rate
// final sample that goes tote DAC
uint16_t sample;
// the two bytes that go to the DAC over SPI
byte dacHigh;
byte dacLow;
// variables for oscillators
uint16_t phaseAcc = 0; // large number holds phase
int waveIndex = 0; // Index for wave lookup (the upper 8 bits of the accumulator)
uint16_t oscil = 8; // oscillator output
// holds frequency value used for oscillator in phase steps
// this is an integer proportial to Hertz in the following way:
// frequency = (FrequencyInHertz * 65536) / SampleRate, here sample rate is 44100
uint32_t frequency = 0;
// used for selecting the wavefrom from the wavetable.
int waveform = 0;
// Every 44.1kHz, calculate the oscillation frequency, map to the wavetable and output it via SPI.
void DACinterrupt()
{
phaseAcc = phaseAcc + frequency; // add in pith, the higher the number, the faster it rolls over, the more cycles per second
waveIndex = phaseAcc >> 8; // use top 8 bits as wavetable waveIndex
oscil = waveTable[(waveform*256)+waveIndex]; // get sample from wave table
sample = oscil << 8; // sample format for DAC is 12 bit, left justified
DACformat(sample);
SPItransmit(dacHigh, dacLow);
}
void setup()
{
// ----------------------------------------------------------------------------------- //
// ------------------------------------ SPI setup ------------------------------------ //
// ----------------------------------------------------------------------------------- //
// set the slaveSelectPin as an output:
pinMode(slaveSelectPin, OUTPUT);
// initialize SPI:
SPI.begin();
Serial.begin(9600);
Serial.println(F("~welcome to the world of tomorrow~"));
// ----------------------------------------------------------------------------------- //
// ------------------------- DAC sample rate interrupt setup ------------------------- //
// ----------------------------------------------------------------------------------- //
HardwareTimer *DACtimer = new HardwareTimer(TIM1);
DACtimer->pause();
DACtimer->setOverflow(sampleRate, HERTZ_FORMAT); // 44.1kHz
DACtimer->attachInterrupt(std::bind(DACinterrupt)); // bind argument to callback: When Update_IT_callback is called MyData will be given as argument
DACtimer->resume();
}
void loop()
{
// play a test tone at 440hz
testTone(440.0);
}
void testTone (int Freq) {
float frequencyInHertz = Freq; // use this to hold a frequency in hertz
// normally we wouldn't use floats, and just use integers,
// but it is convinient here to show freqency calculation
// using formula below, calculate correct oscillator frequency value
frequency = (frequencyInHertz * 65536.0) / sampleRate; //here sampleRate is 44100 (or 44.1kHz)
// wait 2 seconds
delay(2000);
}
void DACformat (uint16_t sample) {
// format/split sample for SPI port (4 config bits, followed by 12 audio bits)
dacHigh = sample >> 8;
dacHigh >>= 4;
dacHigh |= 0x30;
dacLow = sample >> 4;
}
void SPItransmit (int a, int b) {
// takes the Slave Select (SS or CS) pin low to select the DAC
digitalWrite(slaveSelectPin, LOW);
// send in the address and value via SPI:
SPI1->DR = (a);
while (!(SPI1->SR & SPI_SR_TXE)) {}; // Checks and waits if SPI is busy
SPI1->DR = (b);
while ( SPI1->SR & SPI_SR_BSY ) {}; // Checks and waits till SPI is done
// take the SS pin high to de-select the DAC:
digitalWrite(slaveSelectPin, HIGH);
}