I've never really used the DMA peripheral before, and I'm trying to wrap my head around it. I've set things up, but it looks like it's not ever initiating a DMA transfer. I've probably just misconfigured the peripheral setup somewhere, but before I spend too much more time troubleshooting that I want to validate my approach and my assumptions.
My question is - am I thinking about this right? Assuming I configure things the way I intend to - is this a viable approach? I'm assuming that the TIM3 trigger and setting DMA to increment the memory address means that each time my TIM3 updates, the DMA will send the next byte in the array to TIM1, until all the bytes I've told the DMA to send have been sent.
implementation details:
I have an array of samples somewhere. For now, they're just a uint8 const, but eventually I'll be calculating them on-the-fly and so I want to conserve CPU cycles as much as possible.
I want to output the samples to a DAC at a specific output rate. For now, I'm using a PWM DAC, (but I want to design things in such a way as to enable me to easily use other types of DACs in the future.)
All I really need to do is set up a TIM1 to output PWM at some frequency much higher than my sample rate, and then shuffle bytes into the CCR register of this TIM1 at my sample rate. A simple RC lowpass filter on the output removes the PWM switching frequencies, and I get a usable signal out.
I set up a second timer (TIM3) running at my intended sample rate, and configured it to trigger a DMA transfer from memory to the CCR address of my PWM timer.
Relevant code is below. TIM1 is already configured for PWM output, and TIM3 is set up for a 16kHz update.
static void DMAConfig(const uint8_t* wav, size_t samples) // 'samples' is sizeof(wav)/sizeof(wav[1])
{
LL_DMA_InitTypeDef DMA_TIM3Reload ={0};
LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_DMA1);
LL_APB1_GRP2_EnableClock(LL_APB1_GRP2_PERIPH_SYSCFG);
DMA_TIM3Reload.PeriphOrM2MSrcAddress = (uint32_t) &(TIM1->CCR1);
DMA_TIM3Reload.MemoryOrM2MDstAddress = (uint32_t)wav;
DMA_TIM3Reload.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH;
DMA_TIM3Reload.Mode = LL_DMA_MODE_NORMAL;
DMA_TIM3Reload.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT;
DMA_TIM3Reload.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT;
DMA_TIM3Reload.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_WORD; //TODO - figure this out. I'm sending a byte at a time, not a word
DMA_TIM3Reload.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD;
DMA_TIM3Reload.NbData = samples;
DMA_TIM3Reload.Priority = LL_DMA_PRIORITY_MEDIUM;
LL_DMA_Init(DMA1,LL_DMA_CHANNEL_1,&DMA_TIM3Reload);
/* Remap TIM3 update to DMA channel 1 */
LL_SYSCFG_SetDMARemap_CH1(LL_SYSCFG_DMA_MAP_TIM3_UP);
LL_DMA_EnableIT_TC(DMA1,LL_DMA_CHANNEL_1);
LL_DMA_EnableChannel(DMA1,LL_DMA_CHANNEL_1);
NVIC_EnableIRQ(DMA1_Channel1_IRQn);
NVIC_SetPriority(DMA1_Channel1_IRQn,0);
}
TIM3 setup looks like:
LL_TIM_InitTypeDef TIM3CountInit = {0};
LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_TIM3);
TIM3CountInit.ClockDivision = LL_TIM_CLOCKDIVISION_DIV1;
TIM3CountInit.CounterMode = LL_TIM_COUNTERMODE_UP;
TIM3CountInit.Prescaler = 0;
TIM3CountInit.Autoreload = (uint32_t)TIM3ARR; // TIM3ARR is calculated to result in a 16kHz update rate
TIM3CountInit.RepetitionCounter = 0;
LL_TIM_Init(TIM3,&TIM3CountInit);
LL_TIM_EnableAllOutputs(TIM3); //do I need this? TIM3 doesn't need a physical output, just to initiate a DMA transfer
LL_TIM_EnableCounter(TIM3);