Help updating one timer with DMA triggered by a second timer - Page 1
Forum section: electronics / general

author: prosper
author_rank: Regular Contributor
timestamp: April 02, 2025, 03:43:38 pm
content: 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.Code:static void DMAConfig(const uint8_t* wav, size_t samples) // 'samples' is sizeof(wav)/sizeof(wav){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 wordDMA_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:Code: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 rateTIM3CountInit.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 transferLL_TIM_EnableCounter(TIM3);

author: wek
author_rank: Frequent Contributor
timestamp: April 03, 2025, 03:22:37 pm
content: > is this a viable approach?Yes, but details do matter.Which mcu?What are the symptoms?TIM1 without the rest of the whole stuff works as expected, i.e. outputting PWM?Read out and check/post content of TIM and DMA registers.> LL_TIM_EnableAllOutputs(TIM3); //do I need this? TIM3 doesn't need a physical output, just to initiate a DMA transferLook at source code what that function really does, and check in Reference Manual.JW

author: prosper
author_rank: Regular Contributor
timestamp: April 03, 2025, 07:10:41 pm
content: Understood. I was trying to confirm that my understanding of the core concept was accurate, not necessarily the specific implementation. Because if I understand the concepts correctly, then I'm confident that I'll eventually be able to sort of the implementation. Thanks for confirming that I'm not way out in the weedsFWIW, the code posted above is using the Puya library on the PY32F030, which is a 'port' (being generous here) of the STM32 HAL/LL (there's even still some STM copyright statements in the library, haha). I've also got something almost identical set up on an STM32F431, which is going to be one of my eventual targets. The timers are working, I get a 50% output on TIM1 OC channel, and if I manually update the CCR I can change the duty cycle as expected. Tim 3 is likewise function correctly. DMA, though, doesn't seem to trigger, ever. If I set up a 'transfer complete' ISR and enable it in the NVIC, it never gets there (i.e. I set a BP in the ISR and it never breaks). Timer ISRs DO work, though. So, yeah, probably just a matter of re-reading the RM and ensuring I'm doign the right things in the right order. Probably something simple I'm missing, like a peripheral clock somewhere that I should be enabling.I was confused about my approach, mostly because I read this page herehttps://vanhunteradams.com/Pico/DAC/DMA_DAC.html- where he uses two DMA channels, with the second channel updating the source address of the first channel. I don't understand why a second DMA is needed to update the address, when it's configured to auto increment of the source address already.

author: wek
author_rank: Frequent Contributor
timestamp: April 03, 2025, 07:55:30 pm
content: As I've said, details do matter. Read out the DMA registers, including status registers, and check. Often with DMA, the issue is that it cannot access certain memory areas, e.g.thisthisthisorthis. If the puya thing is ripoff of some of the STM32F030 there IMO shouldn't be such constraint (except maybe for the last one), but with chinesiums one never knows.Btw. there's no STM32F431 as far as I know.> I don't understand why a second DMA is needed to update the address, when it's configured to auto increment of the source address already.I haven't read that article and I don't intend to learn intricacies of the RpiJr, but also not everything what is on the web is necessarily correct or makes sense. Try contacting the author.LL is IMO waste of time, as it's mostly just renamed accesses to registers, so whenever debugging, you'd need to perform "reverse translation" to registers anyway. Yourmileageexperience may vary.JW

author: prosper
author_rank: Regular Contributor
timestamp: April 03, 2025, 08:17:19 pm
content: Quote from: wek on April 03, 2025, 07:55:30 pmSorry, meant G431.

author: langwadt
author_rank: Super Contributor
timestamp: April 03, 2025, 08:42:50 pm
content: if you are calculating each sample on the fly anyway you don't need DMA just use the timer update interrupt and stick the result in the CCR with auto-reload preload enabled

author: wek
author_rank: Frequent Contributor
timestamp: April 03, 2025, 08:51:17 pm
content: Will work, but DMA is likely be more efficient due to the relatively high interrupt overhead.So whether it's worth the hassle depends on circumstances.JW

author: langwadt
author_rank: Super Contributor
timestamp: April 03, 2025, 08:55:27 pm
content: Quote from: wek on April 03, 2025, 08:51:17 pm12 cycles in, 10 cycles out, if you don't use floats

author: prosper
author_rank: Regular Contributor
timestamp: April 04, 2025, 12:10:02 am
content: yeah, I can do it in a timer ISR. But the cordic unit in the 431 that i plan on using to produce the wavetable is really oriented to bulk calculations... setting it up for a single sample at a time is inefficient, but setting it loose on a whole array is a different story. And my thinking is that I can point DMA at that array, fire-and-forget style, while my processor is working on other things.Ultimately, it's going to be doing sound synthesis, maybe I'll take a crack at FM synth.

author: DavidAlfa
author_rank: Super Contributor
timestamp: April 06, 2025, 01:37:32 pm
content: Why two DMAs?Leave Timer 1 PWMing all the time with PWM DMA/IRQ disabled.Then use the Repetition counter feature for that, it'll trigger Timer Update IRQ every n repetitions.Enable Timer Update DMA channel, writing to CCR reg. Done!I did exactly this when playing with stereo PWM audio DAC:https://github.com/deividAlfa/STM32F411-Black-pill-USB-wav-player/It uses DMA half-transfer / transfer complete IRQs to update each half of the buffer.

author: langwadt
author_rank: Super Contributor
timestamp: April 06, 2025, 04:39:17 pm
content: Quote from: DavidAlfa on April 06, 2025, 01:37:32 pmare you sure about that? afair the updata irq fires every repetition

author: DavidAlfa
author_rank: Super Contributor
timestamp: April 06, 2025, 04:52:16 pm
content: AFAIK the PWM IRQ will always fire, but not the update IRQ.Maybe I didn't express myself correctly...The repetition counter is downcounter which decreases on each timer update event (When the timer overflows).Only when underflowing, the update event is allowed to fire.From the datasheet:And that's exactly what I'm doing here. Maybe it works differently in the G431 (I used a F411), but I don't think so.https://github.com/deividAlfa/STM32F411-Black-pill-USB-wav-player/blob/89f22aa9847f4635f8ae4c124ee728065d1ac914/Core/Src/pwmAudio.c#L27-L74- Timer 1 clock is 96MHz.- For 48KHz sample rate, I set the counter to 249 (+1=250) and the repetition counter to 7 (+1=8).- The timer generates 8 PWM cycles for each sample before triggering a DMA transfer.- I used unsigned 8-bit audio, reducing the level slighly in Audacity to prevent clipping, as the pwm can't reach 255.

author: prosper
author_rank: Regular Contributor
timestamp: April 07, 2025, 02:08:21 am
content: wow, this is excellent; exactly what I was looking for.I have it working right now: I'm decoding an ADPCM compressed file piece by piece into a chunk of SRAM, and playing it back over PWM. Excellent!Its a clean and elegant solution to PWM audio. I hadn't originally thought to use the repetition counter because I was trying to build it in such a way so that I could easily swap from PWM output to a DAC or SPI/I2S-sh, or even r2r on a gpio port. I had thought to make it so that it was nothing more than a simple write to an arbitrary memory address at a specific rate - and that memory address could easily be a transmit buffer, or a gpio ODR, or whatever else. But that can be a problem for future me to handle if/when I need to

author: DavidAlfa
author_rank: Super Contributor
timestamp: April 07, 2025, 04:53:25 am
content: I also tried I2S:https://github.com/deividAlfa/STM32F411-Black-pill-USB-WAV-MP3-player