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