2
\$\begingroup\$

I would like to programmatically send a PWM signal to one of the 16 output pins. One way to do this is to trigger an interrupt on timer reload and on an output compare event, and then to update the corresponding GPIO in the ISR, but this looks ugly. Is there a better way to do this?

\$\endgroup\$
7
  • \$\begingroup\$ Most microcontrollers (that I've dealt with) have hardware for doing PWM. All you have to do is set the type of PWM, duty cycle, and period using a few register bits. After that the PWM is seen on the given pin indefinitely until you stop it. If you want to use PWM on more than the pins with such dedicated hardware then explicit drive signals for each pin's state is how you must do it. \$\endgroup\$
    – sherrellbc
    Jun 19, 2014 at 13:19
  • \$\begingroup\$ Are you looking for a solution that does not involve external hardware? \$\endgroup\$
    – Tut
    Jun 19, 2014 at 13:28
  • \$\begingroup\$ @Tut If possible, yes. It would be trivial to do this with 16 external AND gates, I guess, or with multiplexor. \$\endgroup\$ Jun 19, 2014 at 13:55
  • 1
    \$\begingroup\$ I think you mean a decoder (PWM to the enable pin), but yes that is why I asked. \$\endgroup\$
    – Tut
    Jun 19, 2014 at 14:00
  • 1
    \$\begingroup\$ You might also try the STM32 Forum. There are some experts there that could probably answer this quite well. \$\endgroup\$
    – Tut
    Jun 19, 2014 at 14:08

1 Answer 1

4
\$\begingroup\$

In my opinion, the question is unclear, so here's what I understood, correct me if I'm wrong: you want to generate a PWM signal, but you want to programmatically switch the GPIO from which it is output, among a set of 16 pins. So maybe at one time you need it being output from PA0, but after a while you want to output it from PA1, and later from PA2, and so on up to PA15. (All GPIO pin numbers are just an example, of course). I'll answer assuming that my interpretation is correct.

The problem

In the STM32 series, you can't just connect an output from a peripheral to any pin of your liking. There may only be a few alternate function remaps available. Certainly you can't just switch a single signal to any of 16 different GPIOs. So, there's no "clean" solution to this problem. Anything you do will feel like a hack. You'll have to be pragmatic here, and despite thinking that any of the following solutions are "ugly", you'll have to choose based on objective requirements: cost, power consumption, CPU load, jitter, etc.

External Hardware

There's of course a solution involving external hardware, as mentioned above: maybe a decoder, in which you apply a single PWM signal in the input, and use 4 GPIOs from your MCU to select which one of 16 pins will output the signal in question. Another possibility is 16 AND gates: one input from every AND gate connected to the same GPIO generating the PWM signal, and then 16 GPIOs connected to the other 16 AND inputs. But I assume you don't care about a hardware solution.

Software only

Then there's a software solution, which you have already described: trigger an interrupt from the timer and set/reset GPIOs during the interrupt handler. I see nothing wrong with this, especially if you're dealing with low frequency signals. A couple hundred kHz should be doable without any optimizations, and a couple MHz might be doable if you programmed the registers directly rather than using the standard peripheral library. Of course, if you have hard real time requirements that might compete for CPU time with this interrupt handler; a high frequency PWM signal; low jitter requirement; lots of interrupts firing; and/or a high CPU load, this solution might not be acceptable. Still, I find nothing wrong with it otherwise. I'd use it without any objections if it didn't conflict with any other requirements.

Multiple timer peripherals

Finally, here's a solution using only the timer peripherals, but unfortunately it involves using many timers at once. Each timer has 4 different channels. Using all 4 channels of each of 4 different timers, you can meet the requirement of 16 GPIOs. An alternative that requires less timers is to selectively remap the timer pins. To illustrate, the following set of 16 GPIOs, plus TIM3 and TIM4, can be used:

  • PA6 - TIM3_CH1 (no remap)
  • PA7 - TIM3_CH2 (no remap)
  • PB0 - TIM3_CH3 (no remap)
  • PB1 - TIM3_CH4 (no remap)
  • PC6 - TIM3_CH1 (full remap)
  • PC7 - TIM3_CH2 (full remap)
  • PC8 - TIM3_CH3 (full remap)
  • PC9 - TIM3_CH4 (full remap)
  • PB6 - TIM4_CH1 (no remap)
  • PB7 - TIM4_CH2 (no remap)
  • PB8 - TIM4_CH3 (no remap)
  • PB9 - TIM4_CH4 (no remap)
  • PD12 - TIM4_CH1 (remap)
  • PD13 - TIM4_CH2 (remap)
  • PD14 - TIM4_CH3 (remap)
  • PD15 - TIM4_CH4 (remap)

Now, the way to do this is to configure TIM3 and TIM4 using the exact same time base unit configurations (which depend on your PWM signal frequency). Then, configure all 4 channels of both timers, again using an identical configuration for all channels.

At this point, you can disable all outputs by programing all 4 CCR registers of both TIM3 and TIM4 (either directly or using the TIM_SetCompareX(), with X = 1,...,4, standard peripheral library functions). Depending whether you want an inactive output to be low or high, you can set the CCR registers to either 0 or a value higher than the auto-reload register value configured in the time base unit.

Now you'll need to activate one of the 16 GPIOs you want. Depending on which GPIO you want to use, you may need to enable or disable the alternate function remap feature for that timer -- check the GPIO_PinRemapConfig() function in the standard peripheral library. Then, for the specific timer and channel that corresponds to the GPIO you want to use, you'll want to configure the CCR register (again using the TIM_SetCompareX() functions) to produce the exact duty cycle you want for your PWM signal.

When switching to another one of your 16 GPIOs, remember to set the CCR register previously in use to 0 or a high enough value (as explained two paragraphs above), which disables the GPIO currently in use, and then go through the procedure explained in the previous paragraph to enable a new one.

Efficiency: this solution uses only two timers (note that you'd have to use one anyway) and every channel on these two timers. It doesn't require external hardware, and it doesn't require firing an interrupt at the rate of your signal -- you initialize the timers once, and then do some further configurations every time you want to change from one GPIO to another. I've given this some thought and I don't think there exists any other solution that would use less resources than what I'm proposing.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.