Skip to content

Instantly share code, notes, and snippets.

@FevenKitsune
Last active December 18, 2025 20:01
Show Gist options
  • Select an option

  • Save FevenKitsune/20f83c94860b463daf75dfc50706f8f0 to your computer and use it in GitHub Desktop.

Select an option

Save FevenKitsune/20f83c94860b463daf75dfc50706f8f0 to your computer and use it in GitHub Desktop.
Dynamic LUT generation and Direct Memory Access non-blocking signal generation
#include <Arduino.h>
#define FREQUENCY 21000
const uint16_t sample_count = 1000000 / FREQUENCY; // Reorganization of the (48MHz / (sample_count * freq)) - 1 to compute array size.
uint16_t lut[sample_count];
// https://forum.arduino.cc/t/samd51-dac-using-dma-seems-too-fast/678418/4
typedef struct // DMAC descriptor structure
{
uint16_t btctrl;
uint16_t btcnt;
uint32_t srcaddr;
uint32_t dstaddr;
uint32_t descaddr;
} dmacdescriptor ;
volatile dmacdescriptor wrb[DMAC_CH_NUM] __attribute__ ((aligned (16))); // Write-back DMAC descriptors
dmacdescriptor descriptor_section[DMAC_CH_NUM] __attribute__ ((aligned (16))); // DMAC channel descriptors
dmacdescriptor descriptor __attribute__ ((aligned (16))); // Place holder descriptor
void setup()
{
// Generate 1-cycle sinusoidal LUT with the number of samples calculated to be the theoretical maximum.
for (uint16_t i = 0; i < sample_count; i++)
{
lut[i] = round((4095.0 / 2.0) * sin((2.0 * PI * 1.0 * float(i)) / sample_count) + (4095.0 / 2.0));
}
DMAC->BASEADDR.reg = (uint32_t)descriptor_section; // Specify the location of the descriptors
DMAC->WRBADDR.reg = (uint32_t)wrb; // Specify the location of the write back descriptors
DMAC->CTRL.reg = DMAC_CTRL_DMAENABLE | DMAC_CTRL_LVLEN(0xf); // Enable the DMAC peripheral
analogWriteResolution(12); // Set the DAC's resolution to 12-bits
analogWrite(A0, 0); // Initialise DAC0
DMAC->Channel[5].CHCTRLA.reg = DMAC_CHCTRLA_TRIGSRC(TC0_DMAC_ID_OVF) | // Set DMAC to trigger when TC0 timer overflows
DMAC_CHCTRLA_TRIGACT_BURST; // DMAC burst transfer
descriptor.descaddr = (uint32_t)&descriptor_section[5]; // Set up a circular descriptor
descriptor.srcaddr = (uint32_t)&lut[0] + sample_count * sizeof(uint16_t); // Read the current value in the sine table
descriptor.dstaddr = (uint32_t)&DAC->DATA[0].reg; // Copy it into the DAC data register
descriptor.btcnt = sample_count; // This takes the number of sine table entries = 1000 beats
descriptor.btctrl = DMAC_BTCTRL_BEATSIZE_HWORD | // Set the beat size to 16-bits (Half Word)
DMAC_BTCTRL_SRCINC | // Increment the source address every beat
DMAC_BTCTRL_VALID; // Flag the descriptor as valid
memcpy((void*)&descriptor_section[5], &descriptor, sizeof(dmacdescriptor)); // Copy to the channel 5 descriptor
GCLK->PCHCTRL[TC0_GCLK_ID].reg = GCLK_PCHCTRL_CHEN | // Enable perhipheral channel for TC0
GCLK_PCHCTRL_GEN_GCLK1; // Connect generic clock 1 at 48MHz
TC0->COUNT16.WAVE.reg = TC_WAVE_WAVEGEN_MFRQ; // Set TC0 to Match Frequency (MFRQ) mode
TC0->COUNT16.CC[0].reg = 47; // Set the sine wave frequency to 1kHz: (48MHz / (sample_count * freq)) - 1. 47 seems to be the magic number.
while (TC0->COUNT16.SYNCBUSY.bit.CC0); // Wait for synchronization
TC0->COUNT16.CTRLA.bit.ENABLE = 1; // Enable the TC0 timer
while (TC0->COUNT16.SYNCBUSY.bit.ENABLE); // Wait for synchronization
DMAC->Channel[5].CHCTRLA.bit.ENABLE = 1; // Enable DMAC on channel 5
}
void loop()
{
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment