torsdag den 22. september 2011

Using the dsPIC Output Compare as PWM

Todays project was an instance of (a lot of) RTFM answering the simple question:

How do I use the output compare as PWM and do an interrupt driven update of the value for each PWM cycle ?

I am still using my simple test circuit with a LED connected to RB7/RP7. The dsPIC uses a mechanism called Peripheral Pin Select (PPS) to map all peripherals but the IO ports to pins. Quite clever - once you get to know how and find the relevant manual sections. The PPS outputs are controlled by registers RPOR0-RPOR7 documented in the device manual (search is your friend), and each RPx pin has a bitfield selecting which output to route to the pin. The manual has the full list of possible outputs (in the manual for dsPIC33fj12gp201 it is on page 103 - search for RPnR), and to route OC1 to RP7 (my LED) you write 0x12 to RPOR3H. Then it is wise to lock the IOLOCK bit in order to not accidentally change this again. This requires waving the dead chicken to unlock OSCCONL first, so to do this you do:

; Set up RP7 for OC1
mov #RPOR3H, w2
mov #0x12, w0
mov.b w0,[w2]


; Lock OSCCONL
MOV #0x46, w0
MOV #0x57, w1
MOV #OSCCONL, w2
MOV.B w0, [w2]
MOV.B w1, [w2]
BSET OSCCONL,#6

Having established the physical connection between OC1 and the pin, it is now time to set up the timer and output compare. Later I would like to have an ADC reading synchronized with the PWM, so I have chosen Timer 3 to control the output compare. The key registers here are OC1CON (for the output compare setup), T3CON (for timer setup and start) and PR3 (for PWM period). OC1CON is configured for PWM output and timer 3 control by writing 0xE. Timer 3 is simply started by writing 0x8000 to T3CON:

; Set up output compare (timer3 based PWM)
MOV #0xE, W0
MOV W0, OC1CON

; Set up Timer3 to run with a 96 period
MOV #95,W0
MOV W0, PR3

MOV #0x8000, W0
MOV W0, T3CON

Now the PWM can be controlled by writing values into OC1RS. I wanted an interrupt routine to do this, counting up and down between 0-49152 so that the value >> 9 would sweep between 0-96 approx 4 times per second. To accomplish this I chose to have a direction value in one register, and the actual value in another. Then I enabled the Timer3 interrupt to drive this like this:

; Put start value into W3
MOV #0, W3
; Put direction in W4
MOV #1, W4

; Enable timer interrupt (IRQ 8)
; Priority is bit 0-2 in IPC2
MOV #1, W0
MOV W0, IPC2
; Enable flag is bit 8 in IEC0
BSET IEC0, #T3IE

Last bit to the puzzle is the interrupt routine. Here the assembler again invokes some magic, installing interrupt service routines by naming convention. The names can be found in the ASM30 manual and are relatively intuitively named. Only problem is that when the table mentiones e.g. _T3Interrupt, it really should be named __T3Interrupt - note the extra underscore. How very reader friendly. By default an untied interrupt will reset the processor so you find out the hard way if you misspelled something. Anyway; the interrupt routine looks like this (comments are for wimps in this case ;) :

__T3Interrupt:
LSR W3,#9,W2
MOV W2, OC1RS
ADD W4,W3,W3
MOV #49152, W2
CP W2,W3
BRA NZ, else
MOV #-1, W4
else:
CP0 W3
BRA NZ, endif
MOV #1, W4
endif:
BCLR IFS0, #T3IF
; Int flag is bit 8 in IFS0
RETFIE

And viola - a soft-blinking LED

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



lørdag den 17. september 2011

Getting stuff up and running

So I just started buildling a simple circuit from scratch using a dsPIC33fj12gp201. Actually the end goal is to build a class-D amplifier a few of these and a CPLD, but right now I am just learning along the way. I use MPLAB X (Right not beta, but it works on Mac OS X), and one thing I learned first is two manual steps needed after creating the mainasm file just to get things to build:

1) Change the include file in the top to read p33fj12gp201.inc instead of the ...xxxx.inc
2) Manually add the p33FJ12GP201.gld file to Linker files, otherwise the link step will fail

As the project wizard will ask for the specific device it could have inserted these, but that is not how it is for now (I will file this as a bug with Microchip but for now this is the workaround)

Next thing on the hardware was a simple blunder: Always remember to insert a pullup between MCLR* and Vcc - otherwise the circuit will not come (reliably) out of reset. 1K will do just fine. So now I have the obligatory Hello Blinking LED