I improved the PLL calculator so that it acts numerically perfect now:
xinfreq = 20000000clkfreq = 333333333errfreq = 100000error = errfreq
for pppp = 0 to 15if pppp = 0 then post = 1 else post = pppp * 2for divd = 64 to 1 step -1Fpfd = round(xinfreq / divd)
mult = round(clkfreq * (post * divd) / xinfreq)
Fvco = round(xinfreq * mult / divd)
Fout = round(Fvco / post)
e = abs(Fout - clkfreq)
if (e <= error) and (Fpfd >= 250000) and (mult <= 1024) and (Fvco >= 99e6) and ((Fvco <= 201e6) or (Fvco <= clkfreq + errfreq)) then
result_divd = divd
result_mult = mult
result_post = post
result_pppp = (pppp-1) & 15 '%1111 = /1, %0000..%1110 = /2..30result_Fpfd = Fpfd
result_Fvco = Fvco
result_Fout = Fout
error = e
endifnextnextprint"XINFREQ: "; xinfreq, " (XI Input Frequency)"print"CLKFREQ: "; clkfreq, " (PLL Goal Frequency)"printprint"Divd: "; result_divd, "D field = "; result_divd-1print"Mult: "; result_mult, "M field = "; result_mult-1print"Post: "; result_post, "P field = "; result_pppp
printprint"setclk(%1_";
print right(bin(result_divd-1),6); "_";
print right(bin(result_mult-1),10); "_";
print right(bin(result_pppp),4); "_10_11, "; clkfreq; ")"printprint"Fpfd: "; result_Fpfd
print"Fvco: "; result_Fvco
printprint"Fout: "; result_Fout
print"Error: "; result_Fout - clkfreq
I've remembered that I wanted to make this into a runtime Pasm library function. Just completed transcribing it directly. Looking good so far. Next is to compact it down and comment with more than just the pseudo code.
Anyone care to do a Spin version? I struggle with lack of spin knowledge.
I did my own Spin2 version here and I already use it as part of my video driver. It works out the clock mode for a desired frequency or returns 0 if it could not meet the tolerance. The only annoying part is figuring out the bottom bits of the clock mode automatically. I sort of need to provide that in extra constants which may be duplicating some SPIN2 constants (and hence prone to inconsistency + mismatch) because I'm not sure this information can be accessed the way I wanted in the lower level object.
CON' clock source used below
#0, CLKSRC_XTAL, CLKSRC_XIN
' setup one of these based on your P2 HW input clock, ' this will only be used if the PLL settings get automatically computed (see code below)'CLKIN_HZ = _xtalfreq ' also only enable CLKSRC_XTAL below as CLKSRC'CLKIN_HZ = _xinfreq ' also only enable CLKSRC_XIN below as CLKSRC
CLKIN_HZ = 20000000' assume 20MHz crystal by default
CLKSRC = CLKSRC_XTAL ' enable this for crystal clock source (default)'CLKSRC = CLKSRC_XIN ' enable this for direct input clock source on XI (no crystal)' parameters used when automatically determining PLL settings
TOLERANCE_HZ = 500000' pixel clock accuracy will be constrained by this when no exact ratios are found
MAXVCO_HZ = 350000000' for safety, but you could try to overclock even higher at your own risk
MINVCO_HZ = 100000000
MINPLLIN_HZ = 500000' setting lower can find more PLL ratios but may begin to introduce more PLL jitterPUBcomputeClockMode(desiredHz) : mode | vco, finput, f, p, div, m, error, bestError
bestError := -1repeat p from0to30step2' compute the ideal VCO frequency f at this value of Pif p <> 0if desiredHz > MAXVCO_HZ/p ' test it like this to not overflow
quit
f := desiredHz * p
else
f := desiredHz
if f > MAXVCO_HZ
quit
' scan through D values, and find best M, retain best caserepeat div from1to64'compute the PLL input frequency from the crystal through the divider
finput := CLKIN_HZ/div
if finput < MINPLLIN_HZ ' input getting too low, and only gets lower so quit now
quit
' determine M value needed for this ideal VCO frequency and input frequency
m := f / finput
' check for the out of divider range caseif m +> 1024
quit
' zero is special and gets a second chanceif m == 0
m++
' compute the actual VCO frequency at this particular M, D setting
vco := finput * m
if vco +< MINVCO_HZ
quit
if vco +> MAXVCO_HZ
next
' compute the error and check next higher M value if possible, it may be closer
error := abs(f - vco)
if m < 1024and (vco + finput) +< MAXVCO_HZ
if error > abs(f - (vco + finput))
error := abs(f - (vco + finput))
m++
' retain best allowed frequency error and divider bits found so farif error +< bestError and error +< TOLERANCE_HZ+1
bestError := error
mode := ((div-1) << 18) + ((m-1) << 8) + (((p/2 - 1) & $f) << 4)
' quit whenever perfect match foundif bestError == 0
quit
if bestError == 0
quit
' final clock mode format is this #%0000_000E_DDDD_DDMM_MMMM_MMMM_PPPP_CCSSif mode
' also set 15 or 30pF capacitor loading based on input crystal frequency
mode |= (1<<24) ' enable PLLif (CLKSRC == CLKSRC_XTAL) ' enable oscillator and caps for crystal
mode |= (CLKIN_HZ < 16000000) ? %1111 : %1011else
mode |= %0111' don't enable oscillator
Here's what I've been using historically. It's been very useful for my testing. It uses only XMUL (For the M field) as an input variable so makes it a small routine. The other components are all constants.
'recalculate sysclock hertz using cordicqmul xtalmul, ##(XTALFREQ / XDIV / XDIVP) 'integer component of pre-divided crystal frequencymovpa, xtalmul
mulpa, ##round((float(XDIV*XDIVP)+0.5) * (float(XTALFREQ)/float(XDIV)/float(XDIVP) - float(XTALFREQ/XDIV/XDIVP)))
qdivpa, ##(XDIV * XDIVP) 'fractional component of pre-divided crystal frequencygetqx clk_freq 'result of integer componentgetqxpa'result of fractional componentadd clk_freq, pa'de-error the integer rounding
It doesn't compute the clock mode because XMUL is the only component to adjust. So it goes in the opposite direction of computing the sys-clock frequency from the given mode components. This is then needed for further computing timing parameters like for the serial pins for example.
Comments
I've remembered that I wanted to make this into a runtime Pasm library function. Just completed transcribing it directly. Looking good so far. Next is to compact it down and comment with more than just the pseudo code.
Anyone care to do a Spin version? I struggle with lack of spin knowledge.
CON ' clock source used below #0, CLKSRC_XTAL, CLKSRC_XIN ' setup one of these based on your P2 HW input clock, ' this will only be used if the PLL settings get automatically computed (see code below) 'CLKIN_HZ = _xtalfreq ' also only enable CLKSRC_XTAL below as CLKSRC 'CLKIN_HZ = _xinfreq ' also only enable CLKSRC_XIN below as CLKSRC CLKIN_HZ = 20000000 ' assume 20MHz crystal by default CLKSRC = CLKSRC_XTAL ' enable this for crystal clock source (default) 'CLKSRC = CLKSRC_XIN ' enable this for direct input clock source on XI (no crystal) ' parameters used when automatically determining PLL settings TOLERANCE_HZ = 500000 ' pixel clock accuracy will be constrained by this when no exact ratios are found MAXVCO_HZ = 350000000 ' for safety, but you could try to overclock even higher at your own risk MINVCO_HZ = 100000000 MINPLLIN_HZ = 500000 ' setting lower can find more PLL ratios but may begin to introduce more PLL jitter PUB computeClockMode(desiredHz) : mode | vco, finput, f, p, div, m, error, bestError bestError := -1 repeat p from 0 to 30 step 2 ' compute the ideal VCO frequency f at this value of P if p <> 0 if desiredHz > MAXVCO_HZ/p ' test it like this to not overflow quit f := desiredHz * p else f := desiredHz if f > MAXVCO_HZ quit ' scan through D values, and find best M, retain best case repeat div from 1 to 64 'compute the PLL input frequency from the crystal through the divider finput := CLKIN_HZ/div if finput < MINPLLIN_HZ ' input getting too low, and only gets lower so quit now quit ' determine M value needed for this ideal VCO frequency and input frequency m := f / finput ' check for the out of divider range case if m +> 1024 quit ' zero is special and gets a second chance if m == 0 m++ ' compute the actual VCO frequency at this particular M, D setting vco := finput * m if vco +< MINVCO_HZ quit if vco +> MAXVCO_HZ next ' compute the error and check next higher M value if possible, it may be closer error := abs(f - vco) if m < 1024 and (vco + finput) +< MAXVCO_HZ if error > abs(f - (vco + finput)) error := abs(f - (vco + finput)) m++ ' retain best allowed frequency error and divider bits found so far if error +< bestError and error +< TOLERANCE_HZ+1 bestError := error mode := ((div-1) << 18) + ((m-1) << 8) + (((p/2 - 1) & $f) << 4) ' quit whenever perfect match found if bestError == 0 quit if bestError == 0 quit ' final clock mode format is this #%0000_000E_DDDD_DDMM_MMMM_MMMM_PPPP_CCSS if mode ' also set 15 or 30pF capacitor loading based on input crystal frequency mode |= (1<<24) ' enable PLL if (CLKSRC == CLKSRC_XTAL) ' enable oscillator and caps for crystal mode |= (CLKIN_HZ < 16000000) ? %1111 : %1011 else mode |= %0111 ' don't enable oscillator
'recalculate sysclock hertz using cordic qmul xtalmul, ##(XTALFREQ / XDIV / XDIVP) 'integer component of pre-divided crystal frequency mov pa, xtalmul mul pa, ##round((float(XDIV*XDIVP)+0.5) * (float(XTALFREQ)/float(XDIV)/float(XDIVP) - float(XTALFREQ/XDIV/XDIVP))) qdiv pa, ##(XDIV * XDIVP) 'fractional component of pre-divided crystal frequency getqx clk_freq 'result of integer component getqx pa 'result of fractional component add clk_freq, pa 'de-error the integer rounding
It doesn't compute the clock mode because XMUL is the only component to adjust. So it goes in the opposite direction of computing the sys-clock frequency from the given mode components. This is then needed for further computing timing parameters like for the serial pins for example.