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.
IIRC, STM32 DMA transfers run at 1/4 system clock. So, this will set a lower bound on precision.
Further, multiple overlapping DMA sequences could jitter each other. You may have enough flexibility to control this, and prevent it having any negative effect.
Also, depending on where in the cycle it is updated, some STM32 timers have a buffer register between an update being written, and it actually taking place. You have some control over the configuration of the timer, and maybe the device, so you may be able to prevent this causing any negative effect.
A further issue is ensuring the data is set up by the processor. Again, you may have enough control to prevent this causing any negative effect.
If the lower bound on precision is an issue, trying to building a prototype, and doing some measurements, might give you enough detailed information to prove it can be done. However, if it is accuracy but not very small periods of time, i.e. sub microsecond, it can likely be made to work.
Summary:
The DMA rate sets a lower bound on precision, and setting up data using the processor might also be a significant obstacle if it needs short durations. Other issues exist, which you'll need to resolve. Accuracy without tiny periods is likely doable.
Best Answer
A timer interrupt will have significant jitter, unless it is the only interrupt source/handler in the entire system. Clocked devices typically do not behave well when the clock is jittery. Thus, I would personally use a 50% PWM timer instead of a software-generated pulse train, when all I know about the other devices is that it's a "clocked device."
If there's some reason that it's OK for the clock to have jitter on the order of whatever your worst interrupt latency/jitter is, then you could go with either option, although the PWM clearly has less moving parts and thus is less likely to break. Software is not to be trusted if you can do it in hardware :-) (And I say this as a software engineer)