mandag den 19. september 2011

Using the PLL on dsPIC33F

Now this should be an easy one - using the PLL on the dsPIC. The oscillator (and PLL in particular) is very well documented it seems, but nevertheless it took some time for me to figure out.

The problem is - I am writing in assembly and all examples are in C. So first issue was to figure out how to set the configuration bits from assembly. Digging around a bit in the headers revealed the config macro, which seemed like exactly what I needed - just put it right after the include statement and you are good to go. Now I could just copy from the C examples, so I happily wrote:

config FOSCSEL, FNOSC_FRC
config FOSC, IOL1WAY_ON & FCKSM_CSECMD

which should boot in FRC mode but enable clock switching (reading in the manual that the PLL cannot be changed when running on the PLL). This way I could start slow, set up the PLL and then change to FRCPLL, just like the manual said. Only problem: It did not work - no matter what I did. After poking around in the debugger I found out that clock switching was not enabled. It turns out that the config above really didn't do anything - it was supposed to read:

config __FOSCSEL, FNOSC_FRC
config __FOSC, IOL1WAY_ON & FCKSM_CSECMD

So why didn't the assembler complain ? Because __FOSCSEL and __FOSC are "magic" labels that map to specific spots in the flash holding the init values for these registers. FOSCSEL and FOSC are just labels ending up randomly, but not illegal per se.

In my end application I need to be able to change between different clock speeds depending on a number of variables. Hence I decided to once and for all write a function once and for all to do this - source below. The general idea is that you can pass it the PLL settings and the oscillator mode (FNOSC_FRCPLL or FNOSC_PRIPLL) and then it will switch to FRC mode, do the PLL setup and then switch back. It is written from the C example in the manual. Actually the manual says that when the OSWEN bit clears everything should be fine and dandy, but the examples do as my code anyway. It is a recursive function to save space, but it works like a charm. It can even be used to switch back to FRC. The two macros are just helpers - you can pass registers or immediates in place of the first three arguments for setup_pll. Note that the CLKDIVL register will be computed from the PRE and POST arguments. But you need to use the offset-2 values as described in the manual. Also remember the two config lines above for things to work.

Example: setup_pll #145,#6,#0,#FNOSC_FRCPLL


With the hope of this being useful to someone else (as I wasted 10 hours on this) hereby the source released to the public domain:

.macro setup_pll FBDIV, PRE, POST, MODE
MOV \FBDIV, w0 ; PLLFBDIV
MOV \POST, w1 ; PLLPOST
MOV \PRE, w2 ; PLLPRE
MOV \MODE, w3 ; FNOSC designator
CALL _setup_pll
.endm

.macro setup_frc
CLR w3
CALL _setup_pll
.endm

.text
_setup_pll:
AND #7,w3
CP0 w3
BRA Z, frc

SL w1,#6,w1 ; Move to right bitfield
IOR w1,w2,w1 ; Construct bitfield
; Setup PLL control registers
MOV #CLKDIVL,w2
MOV.B w1, [w2]
MOV #PLLFBD, w2
MOV w0, [w2]

; recursive shift to FRC to ensure proper PLL handover
PUSH w3
CLR w3
CALL _setup_pll
POP w3

frc:
; Write NOSC
DISI #0x20
MOV #0x78, w0
MOV #0x9A, w1
MOV #OSCCONH, w2
MOV.B w0, [w2]
MOV.B w1, [w2]
MOV.B w3, [w2]
; Execute
MOV #0x46, w0
MOV #0x57, w1
MOV #OSCCONL, w2
MOV.B w0, [w2]
MOV.B w1, [w2]
BSET OSCCONL,#0

; Wait for COSC to take the value of w3
MOV #OSCCONH, w2
waitcosc:
MOV.B [w2],w0
LSR w0,#0x4,w0
CP w0,w3
BRA NZ, waitcosc

; skip PLL lock test if we changed to FRC
CP0 w3
BRA Z, end

; otherwise wait for PLL to lock
waitpll:
BTSS.B OSCCONL, #5
BRA waitpll
end:
RETURN



Ingen kommentarer:

Send en kommentar