;**************************************************************** ; PWM I/O CHIP * ;**************************************************************** ; ; 2/1/97 KStaton 1st coding ; 2/3/97 KStaton fixed tmp->var update ; 2/4/97 KStaton fixed high byte off by one ; measured 31uS overhead (1.5mS in = 1.531mS out) ; ; PINOUT ; ------------- ; hand 1 --|RA2 RA1|-- 18 CH10 ; data 2 --|RA3 RA0|-- 17 CH9 ; clk 3 --|RA4 osci|-- 16 ; 4 --|mclr* osco|-- 15 ; 5 --|gnd vcc|-- 14 ; CH1 6 --|RB0 RB7|-- 13 CH8 ; CH2 7 --|RB1 RB6|-- 12 CH7 ; CH3 8 --|RB2 RB5|-- 11 CH6 ; CH4 9 --|RB3 RB4|-- 10 CH5 ; ------------- ; ; This program implements an 8 channel pwm peripheral. ; ; microprocessor interface: ; ; clk + data input ; handshake output ; ; the input accepts clk-ed data msb 1st. ; hndshk is asserted when data rx. ; data is latched on the rising edge of clk. ; handshake is negated on falling edge of clk. ; when all 16 bits have been input, ; the value is copied to the addressed channel. ; ; ; pwm outputs: ; ; 10 (2 spare) ; single positive going pulses ; with one output active at a time and outputs ; follow each other in rotation, so any one ; output has a period that depends on the duration ; of the other outputs ; ;**************************************************************** INCL "p16c84.inc" ; ;VARIABLES ; user register files 0x0C..0x2F (36) ; chan equ 0x10 ;index = {0..9} -> channel 1..10 flags equ 0x11 ; ch_base equ 0x12 ch1H equ 0x12 ;indexH = base + (ch)*2 ch1L equ 0x13 ;indexL = base + (ch)*2 + 1 ch2H equ 0x14 ch2L equ 0x15 ch3H equ 0x16 ch3L equ 0x17 ch4H equ 0x18 ch4L equ 0x19 ch5H equ 0x1A ch5L equ 0x1B ch6H equ 0x1C ch6L equ 0x1D ch7H equ 0x1E ch7L equ 0x1F ch8H equ 0x20 ch8L equ 0x21 ch9H equ 0x22 ch9L equ 0x23 ch10H equ 0x24 ch10L equ 0x25 ; tmpL equ 0x26 ;read data here tmpH equ 0x27 ; times equ 0x28 ; w_tmp equ 0x29 ;isr context saved here status_tmp equ 0x2A ; scratch equ 0x2B ; scratch register ; ; PORTA INTERFACE BIT DEFS ; clk equ 4 data equ 3 hand equ 2 ; ; PORTA PWMout BIT DEFS ; ch10 equ 1 ch9 equ 0 ; ; PORTB PWMout BIT DEFS ; ch1 equ 0 ch2 equ 1 ch3 equ 2 ch4 equ 3 ch5 equ 4 ch6 equ 5 ch7 equ 6 ch8 equ 7 ; ; FLAGS BIT DEFS ; done_pw equ 0 lastbit equ 1 bitchg equ 2 ;**************************************************************** org 0 goto Start ; org 4 goto isr ; org 0x10 ;**************************************************************** ; subroutine called to assert pwm output * ;**************************************************************** pwmout_on ; assert output spec'd in ch var bcf STATUS,C rlf chan,0 ; chan*2 -> W addwf PCL ; ### BE SURE NO PCL OVFL ### ; done by org near beginning of page bsf PORTB,ch1 ; PCL+0 goto done_pwon bsf PORTB,ch2 ; PCL+2 goto done_pwon bsf PORTB,ch3 ; PCL+4 goto done_pwon bsf PORTB,ch4 goto done_pwon bsf PORTB,ch5 goto done_pwon bsf PORTB,ch6 goto done_pwon bsf PORTB,ch7 goto done_pwon bsf PORTB,ch8 goto done_pwon bsf PORTA,ch9 goto done_pwon bsf PORTA,ch10 goto done_pwon done_pwon return ;**************************************************************** ; subroutine called to negate pwm output * ;**************************************************************** pwmout_off ; negate output spec'd in ch var bcf STATUS,C rlf chan,0 ; chan*2 -> W addwf PCL ; ### BE SURE NO PCL OVFL ### ; done by org near beginning of page bcf PORTB,ch1 ; PCL+0 goto done_pwoff bcf PORTB,ch2 ; PCL+2 goto done_pwoff bcf PORTB,ch3 ; PCL+4 goto done_pwoff bcf PORTB,ch4 goto done_pwoff bcf PORTB,ch5 goto done_pwoff bcf PORTB,ch6 goto done_pwoff bcf PORTB,ch7 goto done_pwoff bcf PORTB,ch8 goto done_pwoff bcf PORTA,ch9 goto done_pwoff bcf PORTA,ch10 goto done_pwoff done_pwoff return ;**************************************************************** ; RESET starts here with initialization * ;**************************************************************** Start ;init i/o movlw 00000000B ;make port b outputs bcf STATUS,5 ;bank 0 movwf PORTB ;initially low level out bsf STATUS,5 ;bank 1 movwf TRISB bcf STATUS,5 ;bank 0 movwf PORTA ;3 outputs initially low bsf STATUS,5 ;bank 1 movlw 00011000B ; a={clk/data/handsk/ch10/ch9} movwf TRISA bcf OPTION,T0CS ; timer clk = internal clk bcf STATUS,5 ;bank 0 ;now init vars movlw 0x00 movwf chan ;current channel bsf flags,done_pw ;done_pw=true so new one starts up ; bsf flags,lastbit ;assume current data bit = 1 btfss PORTA,data ;current data bit bcf flags,lastbit ;correct current data bit ; bcf flags,bitchg ;no change ; 1000us = 0x03E8 ; 1500us = 0x05DC ; 2000us = 0x07D0 ; init position to center = 1500us ;high byte movlw 0x05 addlw 1 ;fix high byte off by 1 movwf ch1H movwf ch2H movwf ch3H movwf ch4H movwf ch5H movwf ch6H movwf ch7H movwf ch8H ;low byte movlw 0xDC movwf scratch comf scratch,0 ;fix low byte up counter movwf ch1L movwf ch2L movwf ch3L movwf ch4L movwf ch5L movwf ch6L movwf ch7L movwf ch8L bsf INTCON,GIE ; global int enable, but all off ; enable timer int in wait loop when done_pw processed ;**************************************************************** ;* MAIN loop * ;**************************************************************** ; ; this peripheral takes a data word and ; splits out a 4 bit address leaving a 11 bit data value. ; the 11 bit value is 8 bits for initial timer count and ; 3 bits for the number of times the timer overflows ; the 8 bit initial count value must be complemented since ; the timer is an up counter. do this when tmpL is transfered ; to channel variable. ; the 4 bit value for the number of interrupts must be incremented, ; since there must always be at least ONE interrupt. ; ; main loop ; calls getword repeatedly. ; ; timer isr runs in background timing pulse widths. ; ; main call getword ; copy tmp to channel variable spec in bits 15..12 movf tmpH,0 ; tmpH->W andlw 0xF0 ; 0xF0 & tmpH -> W movwf scratch swapf scratch ; put in low nibble bcf STATUS,C rlf scratch,0 ; scratch*2->W addlw ch_base movwf FSR ; load indirect pointer movf tmpH,0 ; tmpH->W andlw 0x0F ; 0x0F & tmpH -> W addlw 1 ; fix high byte off by one movwf INDF ; copy high incf FSR ; inc pointer movf tmpL,0 ; tmpL->W movwf scratch comf scratch,0 ; not tmpL -> W movwf INDF ; copy low ; DEBUG bsf PORTA,ch10 bcf PORTA,ch10 ; END DEBUG goto main ;**************************************************************** ;* GETWORD * ; * ; getword gets data over 2 wire serial link * ; using clk+data lines. * ; data is valid when clk is high (= 1). * ; a handshake line is asserted when data bit has been accepted. * ; the data word is complete when the data line toggles * ; twice while the clock line is static. * ; there are 2 idle loops waiting for the clk to go high * ; and 1 idle loop waiting for the clk to go low. all 3 of these* ; idle loops call wait, where the done_pw flag is checked for * ; completion of current pulse width. if this flag is set, * ; then the next channel is set up and the timer started and * ; interrupts enabled. * ;**************************************************************** getword bsf flags,lastbit ;assume current data bit = 1 btfss PORTA,data ;current data bit bcf flags,lastbit ;correct current data bit cll1 btfsc PORTA,clk ;wait for clk assertion goto getbit ;clk=1 ; has data changed? call datachg ;returns bitchg=1 if change btfsc flags,bitchg goto cldc1 ;bitchg=1 call wait ;bitchg=0 goto cll1 ;clk low loop 1 cldc1 bsf flags,lastbit ;assume current data bit = 1 btfss PORTA,data ;current data bit bcf flags,lastbit ;correct current data bit cll2 btfsc PORTA,clk ;wait for clk assertion goto getbit ;clk=1 ; has data changed? call datachg ;returns bitchg=1 if change btfsc flags,bitchg goto cldc2 ;bitchg=1 call wait ;bitchg=0 goto cll2 ;clk low loop 2 getbit bsf status,C ;assume data=1 btfss PORTA,data bcf status,C ;data=0 rlf tmpL ;rot L. C->b0, b7->C rlf tmpH ;got bit bsf PORTA,hand ;set handshake waitc btfss PORTA,clk ;wait for clk negate goto waitcl ;clk=0 call wait goto waitc ;clk high loop waitcl bcf PORTA,hand ;clk negated, so negate hanshake call wait goto getword ;get next bit of word cldc2 return ;GETWORD DONE ;**************************************************************** ; DATACHG * ; data change subroutine. * ; returns with BITCHG=1 if the data input has changed, * ; or BITCHG=0 if there has been no change on the data input. * ; the value of lastbit is set in GETWORD * ;**************************************************************** datachg btfsc flags,lastbit goto lb1 ;last was 1 goto lb0 ;last was 0 lb1 btfss PORTA,data goto chg10 bcf flags,bitchg ;no change so clr flag return lb0 btfsc PORTA,data goto chg01 bcf flags,bitchg ;no change so clr flag return chg10 bsf flags,bitchg ;1->0 so set flag return chg01 bsf flags,bitchg ;0->1 so set flag return ;**************************************************************** ;* WAIT subroutine * ;* * ;* called from GETWORD during idle time while waiting for the * ;* clock to change state. * ;* * ;* this is where the next pwm channel output is setup and * ;* started. * ;* * ;* if done_pw flag is set, * ;* then setup next channel, * ;* else return * ;**************************************************************** wait btfss flags,done_pw ;test done_pw goto ndone ;false ydone ; done_pw. ; pre increment channel variable... incf chan ;pre inc movlw 0x08 subwf chan,0 ; (f)-(W)->(W) btfsc STATUS,Z ;Z=1 -> W=0x0A clrf chan ;reset chan index to 0 ; set timer for next channel... bcf STATUS,C rlf chan,0 ;chan*2->W addlw ch_base movwf FSR movf indf,0 ;get variable value->W movwf times ;msb is # times timer rolls ; assert pwm output for next channel call pwmout_on ; start timer at correct count for first (partial) count incf FSR movf indf,0 ;get variable value->W movwf TMR0 ;lsb is initial timer count ; enable ints bsf INTCON,T0IE bcf flags,done_pw ;clear done_pw flag ndone return ;**************************************************************** ; INTERRUPT SERVICE ROUTINE * ; TIMER is only active interrupt * ; * ; decrement 'times' variable... * ; # times int must occur before done * ; * ; if not done, just return * ; if done, set output done_pw flag + negate pwm output specd in * ; channel var + stop timer ints, then return * ;**************************************************************** isr ; save status & wreg movwf w_tmp swapf status,0 ;result in w movwf status_tmp ; isr body bcf INTCON,T0IF ; clr timer int flag decfsz times goto isrx ;<>0 goto do_done ; =0 isrx goto isrdone do_done bsf flags,done_pw ;set done flag bcf INTCON,T0IE ; disable timer int call pwmout_off ; negate pwm output isrdone ; restore status & wreg (swap affects no status bits) swapf status_tmp,0 ;result in w movwf status swapf w_tmp,1 ;result in w_tmp swapf w_tmp,0 ;result in w retfie end