One strategy
1) Decide on the resolution (the number of steps between off and 100% on. Go with 16. That’s plenty.
2) Decide on a PWM frequency. Go with 30Hz. It sounds odd, but the reason becomes apparent next.
3) Set a 1:8 prescaler on an 8-bit timer.
4) Create a label “Count” and set it to the resolution (16).
5) Create a label "Demand"
6) In the interrupt service routine for the timer interrupt:
6.1) Decrement Count
6.2) When Count becomes zero, set it back to the resolution value again (16).
6.3) If Demand > Count, set the PWM output bit. Otherwise, clear it.
There you have a single PWM output. Put a value from 0 to 16 into “Demand” and the PWM output will perform accordingly. If you want two outputs, you create two Demands and perform two comparisons. (You only need one Count.)
What happens is that (assuming a 4MHz clock), this will interrupt every 2568=2048us. Each time, the counter decrements by one. This happens 16 times meaning that each of your unique mark to space ratios is 2048us apart and that your PWM time is 2048 * 16 = 32768us, or 30.51Hz.
If your demanded PWM value is less that the PWM counter, then your PWM should be marking, otherwise, you should be spacing.
Of course 30Hz might be a bit lumpy. Swap your 4MHz crystal for a 20MHz one and suddenly you’re at 150Hz. Irritatingly audiable. Your options are to reduce your resolution (half the resolution = double the frequency) or reduce your total pulse time (half the pulse time = double the frequency).
The disadvantage of reducing the resolution is obvious. The disadvantage or reducing the pulse time is that it leaves you fewer free processor cycles for your “main” program. In the example above, your interrupt is only called every 2048 cycles. Assuming it costs 200 cycles (that’s generous) to process the interrupt, that leaves your main program 1948 cycles between interrupts.
You could come down to a 2:1 prescaler with a PWM resolution of 8 and a clock of 20MHz. that gives you a PWM frequency of 20/4/(2256*8)*1000000 = 1.22kHz. BUT there are only 512 operations between interrupts then and 200(ish) of hem are taken up servicing the interrupt itself.