2
votes

I've got an STM32F4, and I want to PWM a GPIO port that's been OR'd with a mask..

So, maybe we want to PWM 0b00100010 for awhile at 200khz, but then, 10khz later, we now want to PWM 0b00010001...then, 10kHz later, we want to PWM some other mask on the same GPIO.

My question is, how do you do this with DMA? I'm trying to trigger a DMA transfer that will set all the bits on a rising edge, and then another DMA transfer that will clear all the bits on a falling edge.

I haven't found a good way to do this, (at least with CubeMX and my limited experience with C & STM32's) as it looks like I only get a chance to do something on a rising edge.

One of my primary concerns is CPU time, because although I mention hundreds of kilohertz in the above example, I'd like to make this framework very robust in-so-far as it isn't going to be wasteful of CPU resources...That's why I like the DMA idea, since it's dedicated hardware doing the mindless lifting of a word here to a word there type of stuff, and the CPU can do other things like crunch numbers for a PID or something.

Edit For clarity : I have a set of 6 values that I could write to a GPIO. These are stored in an array. What I'm trying to do is set up a PWM timer to set the GPIO during the positive width of the PWM and then I want the GPIO to be set to 0b00000000 during the low period width if the pwm. So, I need to see when the rising edge is, quickly write to the gpio, then see when the falling edge is, and write 0 to the gpio.

2
what is 10kHz later.? Hz is not the time unit. DMA cant do any logical operations. It just transfers data from location A to B.0___________
Ok... 100us later, I want to apply the same 200khz pwm to different pins on the GPIO. So, we transfer the variable containing the mask to the GPIOx->BSRR or with DMA on the rising edge of the PWM, then on the falling edge, we set the GPIO back to zero... 100us layer, a different timer overflows or compares or something, and then the only change that drives is that now we're loading the DMA up with a different value on the rising edgetestname123
I already gave you the answer. My example was for 1 bit. it can be 16 as well. Otherwise use the timer generated PWM0___________

2 Answers

4
votes

Limited solution without DMA

STM32F4 controllers have 12 timers with up to 4 PWM channels each, 32 in total. Some of them can be synchronized to start together, e.g. you can have TIM1 starting TIM2, TIM3, TIM4 and TIM8 simultaneously. That's 20 synchronized PWM outputs. If it's not enough, you can form chains where a slave timer is a master to another, but it'd be quite tricky to keep all of them perfectly synchronized. Not so tricky, if an offset of a few clock cycles is acceptable.

There are several examples in the STM32CubeF4 library example projects section, from which you can puzzle together your setup, look in Projects/*_EVAL/Examples/TIM/*Synchro*.

General solution

A general purpose or an advanced timer (that's all of them except TIM6 and TIM7) can trigger a DMA transfer when the counter reaches the reload value (update event) and when the counter equals any of the compare values (capture/compare event).

The idea is to let DMA write the desired bit pattern to the low (set) half of BSRR on a compare event, and the same bits to the high (reset) half of BSRR on an update event.

There is a problem though, that DMA1 cannot access the AHB bus at all (see Fig. 1 or 2 in the Reference Manual), to which the GPIO registers are connected. Therefore we must use DMA2, and that leaves us with the advanced timers TIM1 or TIM8. Things are further complicated because DMA requests caused by update and compare events from these timers end up on different DMA streams (see Table 43 in the RM). To make it somewhat simpler, we can use DMA 2, Stream 6 or Stream 2, Channel 0, which combine events from 3 timer channels. Instead of using the update event, we can set the compare register on one timer channel to 0.

Set up the DMA stream of the selected timer to

  • channel 0
  • single transfer (no burst)
  • memory data size 16 bit
  • peripheral data size 16 bit
  • no memory increment
  • peripheral address increment
  • circular mode
  • memory to peripheral
  • peripheral flow controller: I don't know, experiment
  • number of data items 2
  • peripheral address GPIOx->BSRR
  • memory address points to the output bit pattern
  • direct mode
  • at last, enable the channel.

Now, set up the timer

  • set the prescaler and generate an update event if required
  • set the auto reload value to achieve the required frequency
  • set the compare value of Channel 1 to 0
  • set the compare value of Channel 2 to the required duty cycle
  • enable DMA request for both channels
  • enable compare output on both channels
  • enable the counter

This way you can control 16 pins with each timer, 32 if using both of them in master-slave mode.

To control even more pins (up to 64) at once, configure the additional DMA streams for channel 4 compare and timer update events, set the number of data items to 1, and use ((uint32_t)&GPIOx->BSRR)+2 as the peripheral address for the update stream.

Channels 2 and 4 can be used as regular PWM outputs, giving you 4 more pins. Maybe Channel 3 too.

You can still use TIM2, TIM3, TIM4, and TIM5 (each can be slaved to TIM1 or TIM8) for 16 more PWM outputs as described in the first part of my post. Maybe TIM9 and TIM12 too, for 4 more, if you can find a suitable master-slave setup.

That's 90 pins toggling at once. Watch out for total current limits.

0
votes

what PWM 0b00100010 means? PWM is a square wave with some duty ratio. it wil be very difficult to archive using DMA but you will need to have table with already calculated values. For example to have 2kHz PWM with 10% ratio you will need to have 10 samples one with bit set, nine with bit zeroed. You configure the timer to 20k / sec trigger mem-to-mem (GPIO has to be done this way) DMA transmission in the circular mode. On the pin you will have 2kHz 10% wave. The PWM resolution will be 10%. If you want to make it 0.5% you will need 200 samples table and DMA triggered 400k times per second.

IMO it is better to use timer and DMA to load new values to it (read about the burst DMA mode in the timer documentation in the Reference Manual)