Last active
December 18, 2025 20:01
-
-
Save FevenKitsune/20f83c94860b463daf75dfc50706f8f0 to your computer and use it in GitHub Desktop.
Dynamic LUT generation and Direct Memory Access non-blocking signal generation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #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