I know it's a bit late, but I've just got this sabe doubt. After looking around, I've come across this Microchip Doc that shows some examples.
First, we calculate \$\text{PR2}\$. From this formula,
$$ F_\text{PWM} = \dfrac{1}{(\text{PR2} + 1) \times 4 \times T_\text{OSC} \times \text{T2CKPS}} $$
we get
$$ \text{PR2} = \dfrac{1}{F_\text{PWM} \times 4 \times T_\text{OSC} \times \text{T2CKPS}} - 1 $$
where \$T_\text{OSC} = 1/F_\text{OSC}\$, and \$\text{T2CKPS}\$ is the Timer2 prescaler value (1, 4 or 16).
Therefore, if we want \$F_\text{PWM} = 20\text{kHz}\$, and choosing \$\text{T2CKPS} = 1\$, we get \$\text{PR2} = 249\$. We should choose higher values for \$\text{T2CKPS}\$ only if \$\text{PR2}\$ exceeds 8 bits (\$\text{PR2} \gt 255\$) for the given prescale.
Now we calculate the max PWM resolution for the given frequency:
$$ \text{max PWM resolution} = \log_2(\;\dfrac{F_\text{OSC}}{F_\text{PWM}}\;) $$
That gives us \$9.9658\$ bits (I know, it sounds weird, but we'll use it like that later).
Now, let's calculate the PWM duty cycle. It is specified by the 10-bit value \$\text{CCPRxL:DCxB1:DCxB0}\$, that is, \$\text{CCPRxL}\$ bits as the most significant part, and \$\text{DCxB1}\$ and \$\text{DCxB0}\$ (bits 5 and 4 of \$\text{CCPxCON}\$) the least significant bits. Let's call this value \$\text{DCxB9:DCxB0}\$, or simply \$\text{DCx}\$. (x is the CCP number)
In our case, since we have a max PWM resolution of \$9.9658\$ bits, the PWM duty cycle (that is, the value of \$\text{DCx}\$) must be a value between \$0\$ and \$2^{9.9658} - 1 = 999\$. So, if we want a duty cycle of 50%, \$\text{DCx} = 0.5 \times 999 = 499.5 \approx 500\$.
The formula given on the datasheet (also on the linked doc),
$$\text{duty cycle} = \text{DCx} \times T_\text{OSC} \times \text{T2CKPS}$$
gives us the pulse duration, in seconds. In our case, it's equal to \$25\text{ns}\$. Since \$T_\text{PWM} = 50\text{ns}\$, it's obvious that we have a 50% duty cycle.
That said, to calculate DCx in terms of duty cycle as \$r \in [0,1]\$, we do:
$$ \text{DCx} = \dfrac{r \times T_\text{PWM}}{T_\text{OSC} \times \text{T2CKPS}} = \dfrac{r \times F_\text{OSC}}{F_\text{PWM} \times \text{T2CKPS}} $$
Answering your other questions:
2) The resolution of your PWM pulse with period \$T_\text{PWM}\$ is
$$ \dfrac{T_\text{PWM}}{2^\text{max PWM res}} $$
3) Because CCPRxL, along with DCxB1 and DCxB0, determine the pulse duration. Setting CCPRxL with a higher value than \$2^\text{max PWM res} - 1\$ means a pulse duration higher than the PWM period, and therefore you'll get a flat \$V_{DD}\$ signal.
Best Answer
Main problem
You are setting the Compare Match Output pin in the wrong register. Replace
with
Of course, this assumes you have a 16MHz CPU frequency with no clock divide as you are using a prescaler of 8 with a TOP value of 100.
Other problems
This might be working, but it's not the best idea. There's lots I could go over, but let's just talk about this
AVR is 8-bit. Double is a 64-bit number with decimal places. Actually, (in GCC, at least), doubles are automatically replaced with Floats (single-precision). AVR doesn't have a floating-point unit (FPU) and any floating-point operations (which have 32-bit storage) will take an order of magnitude MORE clock cycles to complete than a typical 8-bit operation. Division is also not supported in AVR hardware, so the compiler emulates it in software. You are also doing all of this math in an interrupt service routine. That's ... not good.
OCR0B is an 8-bit register (max of 255, unsigned). Since you are using OCR0A as the timer TOP with a value of 99, you have a nice 0 - 99% duty cycle setup. So, just set OCR0B to whatever duty cycle you want. Why do (50 / 99 * 100) which equals 49.5 and will be chopped to 49 anyway? Just do this:
The "static" keyword means this variable is only available in the current file - it's like a local global. You should also use it on any local functions that aren't accessed outside of this .c file. The "volatile" keywords tells the compiler not to optimize the variable because it could be updated somewhere not expected (like in an ISR). You don't need volatile if you aren't changing the duty_cycle variable in an ISR. Better yet, why use a global variable at all when you can access the register directly?
If you don't like that, use a macro or static inline function:
Also, unless you plan on re-initializing the timer at some point while keeping other changes you've made to those registers, you don't need to do "|=" which is a "read-modify-write" operation. Just use "=". The "|=" means to set only the specified bit while leaving everything else alone, which is pointless during initialization when you want everything not specifically set to be 0.