STM32 DMA PWM two channels - Page 1
Forum section: electronics / general

author: ali_asadzadeh
author_rank: Super Contributor
timestamp: March 13, 2021, 12:42:10 pm
content: Hi,I have two Data buffers, and I want to update two PWM channels with a frequency of 180KHz each for their PWM frequency , I want to update the PWM compare values with another timer of around 20KSps, so I mean I want to control the speed of the PWM update with another Timer, and I want to use DMA, the MCU is STM32F030KI have only found the HAL_TIM_PWM_Start_DMA in the HAL, how can this be done? can we control the DMA transfer with another timer? can it be done for two PWM channels?

author: oliviasmithh900
author_rank: Contributor
timestamp: March 13, 2021, 11:08:34 pm
content: For single PWM with fixed frequency, you can do something like this:HAL_TIM_PWM_Start(&htim4, TIM_CHANNEL_4);

author: DavidAlfa
author_rank: Super Contributor
timestamp: March 14, 2021, 05:24:14 pm
content: Check the attached STM32 IDE test project for the STM32F030K.Some resources:QuoteMy approach to this would be:QuoteHAL isn't clear about the timer interrupts. I tested in my STM342F411 and worked nice without them.Save and let cubeMX build the initial code.Check the STM32 HAL source files, as they usually have comments that will help further.Code:/Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_hal_tim.c/Drivers/STM32F0xx_HAL_Driver/Inc/stm32f0xx_hal_tim.hThen use "HAL_TIM_PWM_Start_DMA", the function parameters are as follow:Code:HAL_StatusTypeDef HAL_TIM_PWM_Start_DMA (TIM_HandleTypeDef * htim, uint32_t Channel, uint32_t * pData, uint16_t Length)Keep in mind that the pData is always a 32-bit pointer! So you must redeclare the pointer as (uint32_t*)&PWM_DATA.Length is the number of DMA word size! Half Word = 16 bit, so you must set the number of 16 bit values in the PWM data.You can set a single value, or a big array.Example:Code:// Data array where the PWM values are stored// Ensure it's aligned to 32bit, just in case.__attribute__((aligned(4))) volatile uint16_t PWM_CH1_Data = { 0, 50, 100, 150 }; // This channel uses 4 values// Start timer1 in PWM DMA mode// Selected channel = CH1// DMA source = PWM_CH1_Data// Length = 4HAL_TIM_PWM_Start_DMA(&htim1, TIM_CHANNEL_1, (uint32_t*)&PWM_CH1_Data, 4);Repeat for the second channel:Code:__attribute__((aligned(4))) volatile uint16_t PWM_CH2_Data = { 100, 250 }; // This channel uses 2 values// This doesn't work if done instantly after starting the first channel// HAL_TIM_PWM_Start_DMA(&htim1, TIM_CHANNEL_2, (uint32_t*)&PWM_CH2_Data, 2);// Edit: Apparently the PWM stays BUSY until the first PWM cycle is done. This fixes the second channel initializationwhile( HAL_TIM_PWM_Start_DMA(&htim1, TIM_CHANNEL_2, (uint32_t*)&PWM_CH2_Data, 2) == HAL_BUSY);At this point both PWM channels should be working at 180KHz, and updating the PWM value at 20KHz.Edit: There are strange behaviours using different DMA buffer sizes.Sizes = 2,4,8 work correctly. However size=6 fails and works with only 2 values.Also when you get close to 100% PWM it does the PWM cycle twice. In my current tests, with PWM period = 549, the higher value I can use is 547.Higher than that will duplicate that cycle. Not a single pwm cycle, but the repetition counter cycle, so in this case all the 9 pwm cycles.I don't understand why does this happen, the repetition counter clock source is the timer overflow, not the output compare.Check the last picture.First test:QuoteThe measurements have a small error due the analyzer 24MHz sampling rate causing some jittering.PWM = 5.542uS, 180180HzUpdate = 49.958uS, 20016HzThis is the PWM duty bug. The code that outputs those channels:Code:__attribute__((aligned(4))) volatile uint16_t PWM_CH1_Data = { 0, 540 };__attribute__((aligned(4))) volatile uint16_t PWM_CH2_Data = { 0, 100, 200, 540 };HAL_TIM_PWM_Start_DMA(&htim1, TIM_CHANNEL_1, (uint32_t*)&PWM_CH1_Data, 2);while (HAL_TIM_PWM_Start_DMA(&htim1, TIM_CHANNEL_2, (uint32_t*)&PWM_CH2_Data, 4) == HAL_BUSY){asm("nop");}

author: ali_asadzadeh
author_rank: Super Contributor
timestamp: March 15, 2021, 07:26:40 am
content: Thanks DavidAlfa for sharing! Actually I want this to play audio, so is there a way to use another timer to make better control over PWM updates, since I need about 16Ksps updates,

author: DavidAlfa
author_rank: Super Contributor
timestamp: March 15, 2021, 07:49:44 am
content: Then just run and update the pwm at 16KHz?At 48MHZ and 2999 period you have it exact.Why 180KHz and 20KHz update?As you need to update the buffers, you can use a big buffer, and update halves of it each time.You have the half transfer and transfer complete callbacks.So if your buffer is 512 bytes, you write the first 256 after the half transfer, or the last 256 after the transfer complete.You can set a 256 byte temporal storage buffer and another DMA Channel in memory to memory mode, 32-bit word size, and trigger the DMA transfer when the callbacks is called. That will update the pwm buffer blazingly fast.Another thing, it seems the 030 uses the same die as the 050. The latter has i2s.You can try tricking the IDE by setting your device as the 050. It seems it also works at 64MHz without problem.

author: ali_asadzadeh
author_rank: Super Contributor
timestamp: March 15, 2021, 01:36:59 pm
content: DavidAlfa, thanks for the hints,Actually I have 16bit PCM data from a wave file, so I have separated the low byte and high bytes in different buffers, also I want to use 16KSPS audio files, and because 16bit PWM is not possible @ 16KSPs with 48MHz clock, I decided to use two PWM channels each with 8 bit resolution, and use another timer to pump data @ 16KSPS, so Now knowing what I want to know How should I set the PWM updates using the second timer?

author: DavidAlfa
author_rank: Super Contributor
timestamp: March 15, 2021, 01:42:00 pm
content: Why the use of the timer? If you set the PWM to 16KHz, no repetition counter, it will feed itself calling a DMA transfer after each PWM cycle.

author: ali_asadzadeh
author_rank: Super Contributor
timestamp: March 15, 2021, 03:34:41 pm
content: QuoteIt should have higher PWM frequency, so that the RC filter could make the signal, I think around 10 times the sample frequency, something like 180KHz PWM frequency would do a nice job.

author: DavidAlfa
author_rank: Super Contributor
timestamp: March 15, 2021, 03:39:01 pm
content: Then just use the repetition counter, dividing the PWM frequency to feed the DMA.At 48MHz you can do exactly 160KHz, use repetition counter to update every 10 pwm cycles.Everything is on the large post, did you read anything...?

author: ali_asadzadeh
author_rank: Super Contributor
timestamp: March 16, 2021, 06:50:56 am
content: Dear DavidAlfaYes, I got your points, I will use 160KHz then. thanks for the help.

author: DavidAlfa
author_rank: Super Contributor
timestamp: March 16, 2021, 09:57:54 am
content: I tried that circuit.I must say that the difference with and without the pwmH (with 1M resistor) is unoticeable.I lowered it further with 1k/256K. (256K = 470K+560K in parallel).And 47nF for extra-smoothing the test waveform. I don't see the point of so much PWM refining using 2 channels when there's so much noise.It needs an active filter for sure!I did a very optimistic waveform. 100 samples/wave:Repetition counter=0Repetition counter=9It seems that the DMA is always called at the end of every PWM cycle, but the PWM updates only at the repetition counter frequency.So the DMA sends 10 values, but the PWM takes only one. Not what I thought.Again, stm32 HAL bugs/issues/no documentation...I think HAL can't help you, you will need to do your own DMA setup.

author: ali_asadzadeh
author_rank: Super Contributor
timestamp: March 16, 2021, 03:00:01 pm
content: Try to put some wave file data to see what it can do, use audacity to generate a tone (a sin wave) @ 16KSPS and put those using two PWM channels.

author: DavidAlfa
author_rank: Super Contributor
timestamp: March 17, 2021, 12:17:39 am
content: You have more problems to worry about, since the characteristics of a RC filter... the amplitude it will change a lot between the frequencies.I got 3Vpp at 100Hz, but 1.5Vpp at 16KHz.I quickly made a 3rd order filter using this calculator, the output was very clean, althought there was some artifacts.http://sim.okawa-denshi.jp/en/Sallen3tool.phpRemoving the 1st RC filter removed the attenuation over frequency, but at higher frequencies and low PWM resolution, causes partially square output.I'm cheating, I'm using 160KHz sample rate. But check the last picture, you will see it does ok (in fact, better) at 16Ksps.100Hz1KHz10KHz16KHz16KHz160KHz PWM, 16Ksps

author: ali_asadzadeh
author_rank: Super Contributor
timestamp: March 17, 2021, 06:53:44 am
content: what values did you use for the R and C?

author: DavidAlfa
author_rank: Super Contributor
timestamp: March 17, 2021, 08:06:36 am
content: Go to the calculator and simply put 16KHz into the FC field.That values.

author: tru
author_rank: Regular Contributor
timestamp: March 17, 2021, 09:15:49 am
content: Quote from: DavidAlfa on March 16, 2021, 09:57:54 amI don't think that is a bug (so can't blame HAL for that), the STM32 is doing as intended, i.e. the PWM is updating only when the repetition counter is zero. In your first post, you even said so:Quote from: DavidAlfaAlso, the cook book mentions an "update event" is triggered when the repetition counter is null (zero) & (when timer counter is restarted), else it doesn't:https://www.st.com/resource/en/application_note/dm00236305-generalpurpose-timer-cookbook-for-stm32-microcontrollers-stmicroelectronics.pdf

author: DavidAlfa
author_rank: Super Contributor
timestamp: March 17, 2021, 11:52:36 am
content: Yes, the PWM is doing fine.But the logical DMA behavior is send data when the repetition counter resets, at least by default.Actually the DMA is sending data after each pwm cycle, no matter what the repetition counter is doing.So the DMA seems to be triggered by the output compare flag, instead the update flag.That's less than useless.

author: DavidAlfa
author_rank: Super Contributor
timestamp: March 19, 2021, 02:21:49 am
content: OK, I finally understood the DMA behavior.Each DMA has a very specific handler, so you can't use different DMAs and trigger them with the same source (I thought it was more flexible).In this case we have:- Timer update DMA- PWM ch1 DMA- PWM ch2 DMA- PWM ch3 DMA- PWM ch4 DMA- Timer update triggers when the counter resets. This is what repetition counter works with!- Pwm channels DMA always trigger the DMA at the Output compare event (When the pwm goes low).- If you put too high pwm values, OC flag is not set. Doesn't explain why it fails if the value is "Timer Period-1".- It will always call the dma after each PWM cycleSo this is not what we need.However, by default HAL doesn't let you use the Update DMA to feed other timer registers.But, since the DMA can reach any other base peripheral register, I could "hack" the HAL by using the Timer update DMA, and send the data to the PWM ch1.(hacking = uhh I don't want to mess too much at low level)Now, it updates every 10 PWM cycles @ 160KHz PWM. The sound its pretty nice, what you would expect from 16Ksps 8bit audio (8KHz bandwidth).Taking photos of the analog scope with fast moving signals proved to be a mess, the camera and the scope update frequency cause a lot of trouble.I ended using a special filtering mode that seems to work better. 2mS/division, 10 divisions wide.Due the persistence, you can see the previous wave sample points, it's cool.I tried audacity, but it can't correctly export to 8bit 16KHz wav files. It always sets it to 22KHz no matter what you do.I found a great alternative, it was even better. Wavosaur (https://www.wavosaur.com/)It can export raw binaries, so no wav headers, only pure audio data.Then I just copy the hex values from HXD, copy into a text editor, and replace all the spaces with ",0x".And it's ready to insert into a C array.Try this, I think it may work in the STM32F030.

author: ali_asadzadeh
author_rank: Super Contributor
timestamp: March 19, 2021, 05:23:35 am
content: DavidAlfa, thanks for the update, would you please send the last code update, can we use tow DMA channels for two PWM channels? even with a slight delay?

author: DavidAlfa
author_rank: Super Contributor
timestamp: March 19, 2021, 09:49:53 am
content: It's there, in the last post, check the Zip file!It plays a ~0.5 second audio in a loop (stm32f030 has only 32K flash)Yes you can do it with 2 different pwm timers.The delay is avoided by stopping the timer and clearing the counter after starting it with the HAL function, simple as:"__HAL_TIM_Disable(&htim1)"."__HAL_TIM_SetCounter(&htim1, 0)"So you init both timers and DMA, stopping them.After finishing the setup, start the timers:"__HAL_TIM_Enable(&htim1)""__HAL_TIM_Enable(&htim2)"The delay between the channels will be few nanoseconds, that's perfectly fine.

author: ali_asadzadeh
author_rank: Super Contributor
timestamp: March 19, 2021, 08:36:08 pm
content: thanks DavidAlfa

author: DavidAlfa
author_rank: Super Contributor
timestamp: March 19, 2021, 11:51:22 pm
content: I added USB OTG, now actually playing wav music at 32Ksps from a USB drive.I was bored... Had to do something

author: ali_asadzadeh
author_rank: Super Contributor
timestamp: March 20, 2021, 06:58:26 am
content: that's great, is the sound quality satisfying?

author: DavidAlfa
author_rank: Super Contributor
timestamp: March 20, 2021, 10:23:22 am
content: I'm using an old phone speaker for testing...I have usb headphones so I can't plug them to the analog output.But clearly, much better than 16ksps, as the bandwidth went from 8 to 16Khz.16 vs 8 bit is not so noticeable, even playing from the computer.What kind of audio quality are you expecting from a PWM output?It's ok for playing simple sounds, warnings, user interaction... But I wouldn't use that for music.I strongly suggest going for the stm32f050 and use a i2s DAC... Or hacking the 030 by programming it like a 050, as it seems they use the same die.I tried different PWM buffer size, it worked well using at least 256 PWM samples (So the DMA is called every half of that value).Going lower than that caused some crackling noises, caused by the USB and filesystem overhead not being fast enough.The pwm dma needs 16 bit values, always.I've tried all possible ways trying to feed the pwm dma with 8bit data, doesn't work.So I can't directly transfer USB data to the pwm buffer, I have to use an auxiliar 8 bit buffer and then manually copy the values to the 16-bit pwm buffer, so the 50% of the pwm buffer is wasted memory.Given that, using an external 16bit dac will use the same memory but fully use the 16bit, and output a much better signal.

author: DavidAlfa
author_rank: Super Contributor
timestamp: March 20, 2021, 12:09:08 pm
content: I connected it to the computer line input and recorded few seconds, I'm suprised how nice it sounds for the crappy setup it isThe background noise is audible due the 8 bit precision, but there's no buzzing, crackling or other weird noises.At 16ksps you can clearly notice that the bandwidth is heavily compressed, 32ksps the improvement is huge, quality similar to similar to FM radio (15KHz BW), and at 48ksps the highs sound a little better.It's not a hi-fi sound card, but it was nice little project to spend my spare time on.

author: ali_asadzadeh
author_rank: Super Contributor
timestamp: March 20, 2021, 09:02:15 pm
content: It sounds great!

author: DavidAlfa
author_rank: Super Contributor
timestamp: March 21, 2021, 12:57:45 am
content: I got it working in Burst mode. Hence, now it's doing stereo with the same resources.In this mode, when the timer triggers the DMA, it does it multiple times, and uses a virtual register (DMAR) that will redirect the data to register offset set in DCR.In this case , burst=2, offset=CCR1, so it will update 2 channels at once (CCR1 + CCR2).Only 3 lines of code, however getting it configured correctly took a few triesCode:/* Configure timer in DMA burst mode** Timer start offset = CCR1* DMA trigger source = Timer Update* DMA source = PCMdata[]* Burst count = 2*/ Total data length = PCMsize (it's a #define here)HAL_TIM_DMABurst_MultiWriteStart(&htim1, TIM_DMABASE_CCR1, TIM_DMA_UPDATE, (uint32_t*)&PCMdata, TIM_DMABURSTLENGTH_2TRANSFERS, PCMsize);// Start PWM channelsHAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_2);But I have bad news: STM32 F0 and F1 series don't have it... only single transfer.It seems it does! AN4776 said that... but it's wrong.

author: DavidAlfa
author_rank: Super Contributor
timestamp: March 21, 2021, 05:21:28 am
content: I finally added WAV parsing, it can recognize and play 16/32/48KHz mono/stereo 8-bit WAV files.I uploaded the sample project here:https://github.com/deividAlfa/STM32F411-Black-pill-USB-wav-player

author: ali_asadzadeh
author_rank: Super Contributor
timestamp: March 21, 2021, 09:36:12 am
content: Thanks for sharing

author: ozcar
author_rank: Frequent Contributor
timestamp: March 21, 2021, 08:47:21 pm
content: Quote from: DavidAlfa on March 21, 2021, 12:57:45 amDon't have burst mode? I've not done much with F1s, and nothing at all with an F0, but from a quick peek at the reference manuals, burst mode DMA is supported for some of the timers.

author: DavidAlfa
author_rank: Super Contributor
timestamp: March 21, 2021, 10:03:04 pm
content: Quote from: ozcar on March 21, 2021, 08:47:21 pmYou're right!I checked both STM32F0xx and STM32F10x series reference manuals.TIM1 has DMAR/DCR registers, clearly stating the DMA burst capabilities.What I posted was based on the AN4776 application note, which might refer to other F1 series.Or just BS to trick you into buying a more expensive part?Quote