;****************************************************************************** ; Push puppet toy ; Controller firmware ; v0.0.5 - 09.05.07 ; (c) Pierre Coupard, 2006 ; ; Fosc = 4MHz ; ; Pin assignment: ; ; RA0/AN0 --> / ; RA1/AN1 --> / ; RA2/AN2 --> / ; RA3/AN3/CMP1 --> / ; RA5//MCLR/VPP --> Vpp ; RA6/OSC2/CLKOUT --> 4MHz crystal ; RA7/OSC1/CLKIN --> 4MHz crystal ; ; RB0/INT --> / ; RB1/RX/DT --> USART Rx (9600bps, 8 bits, No parity, 1 stop bit) ; RB2/TX/CK --> USART Tx (9600bps, 8 bits, No parity, 1 stop bit) ; RB3/CCP1 --> PWM signal for servo control ; RB4/PGM --> / ; RB5 --> / ; RB6/PGC --> ICSP programming clock ; RB7/PGD --> ICSP programming data ; ; This program is distributed under the terms of the GNU General Public License ; See the COPYING file for details ;****************************************************************************** LIST P=16F628, R=DEC ; Use PIC16F628 and decimal system #include "p16f628.inc" ERRORLEVEL 0,-302 ; Suppress bank selection messages __config _XT_OSC & _LVP_OFF & _WDT_OFF & _PWRTE_ON & _BODEN_ON ; RAM variables CBLOCK 0x20 ; Declare variable addresses starting at 0x20 ct_W ; Saved copy of W during interrupts ct_STATUS ; Saved copy of STATUS during interrupts ct_STATUS2 ; 2nd saved copy of STATUS during interrupts ct_PCLATH ; Saved copy of PCLATH during interrupts delayctr1 delayctr2 delayctr3 number1H ; Buffer to store... number1L ; ...a 16-bit number number2H ; Buffer to store... number2L ; ...a 16-bit number nibble ; Nibble to convert to... hexdigit ; ...ASCII hex digit pwmperiodH ; servo PWM period, MSB pwmperiodL ; servo PWM period, LSB upH ; servo "up" position pulse len, 16 bits, MSB upL ; servo "up" position pulse len, 16 bits, LSB tmr1svupH ; Pre-computed TMR1 start value based on "up" tmr1svupL ; position timing and PWM period ccpr1vupH ; Pre-computed CCPR1 value based on "up" ccpr1vupL ; position timing and PWM period downH ; servo "down" position pulse len, 16 bits, MSB downL ; servo "down" position pulse len, 16 bits, LSB tmr1svdownH ; Pre-computed TMR1 start value based on "down" tmr1svdownL ; position timing and PWM period ccpr1vdownH ; Pre-computed CCPR1 value based on "down" ccpr1vdownL ; position timing and PWM period tmr1startvalH ; TMR1 start value for current servo position tmr1startvalL ; and PWM period curset ; Current setting in the settings menu i ; Scratch variable j ; Scratch variable ENDC ; EEPROM data addresses EEpwmperiodH equ 0x0 EEpwmperiodL equ 0x1 EEupH equ 0x2 EEupL equ 0x3 EEdownH equ 0x4 EEdownL equ 0x5 ; Various useful definitions CR equ 0x0d LF equ 0x0a SERVO_PORT equ PORTB SERVO_LINE equ 3 ; Servo control line is RB3 PWMSETTING equ 0 UPSETTING equ 1 DOWNSETTING equ 2 INCSETTING equ 3 ; --------------- ; POWER-ON VECTOR ; --------------- ORG 0x0 ; RESET vector goto start ; ----------------- ; INTERRUPT HANDLER ; ----------------- ORG 0x4 ; Interrupt vector ; Save context movwf ct_W ; Save W swapf STATUS,W ; This does not affect STATUS movwf ct_STATUS ; Save STATUS movf PCLATH,W movwf ct_PCLATH ; Save PCLATH movf STATUS,W ; W <-- state of the bank bit bcf STATUS,RP0 ; Switch to bank 0 movwf ct_STATUS2 ; save W in bank 0, so we can go back ; Did we get a CCP compare match interrupt ? testint btfsc PIR1,CCP1IF goto ccpint ; Yes ; Did we get a TMR1 overflow interrupt ? btfsc PIR1,TMR1IF goto tmr1int ; Yes ; Restore context movf ct_STATUS2,W ; Recover the bank we entered the interrupt in movwf STATUS ; Switch to this bank movf ct_PCLATH,W movwf PCLATH ; Restore PCLATH swapf ct_STATUS,W movwf STATUS ; Restore STATUS swapf ct_W,F ; Restore W swapf ct_W,W ; use SWAP to keep STATUS unaffected retfie ccpint ; Set the servo control line low bcf SERVO_PORT,SERVO_LINE ; Clear interrupt flag bcf PIR1,CCP1IF goto testint tmr1int ; Set the servo control line high bsf SERVO_PORT,SERVO_LINE ; Disable the timer bcf T1CON,TMR1ON ; Write start value in TMR1 movf tmr1startvalL,W movwf TMR1L movf tmr1startvalH,W movwf TMR1H ; Enable the timer bsf T1CON,TMR1ON ; Clear interrupt flag bcf PIR1,TMR1IF goto testint ; ------------ ; MAIN PROGRAM ; ------------ start ; Set digital inputs and comparators on port A movlw 00000111b ; Comparators off movwf CMCON ; Initialize ports movlw 00000000b ; PortA: all pins low movwf PORTA movlw 00000100b ; PortB: RB2 (USART Tx) is high others are low movwf PORTB bsf STATUS,RP0 ; Bank 1 movlw 11111111b movwf TRISA ; portA: set all pins as input (unused) movlw 11110011b ; PortB: RB2 (USART Tx) and RB3 (servo control) movwf TRISB ; are output, others are input ; Set UART: baudrate = 9600, No Parity, 1 Stop Bit movlw 0x19 ; 0x19=9600 bps (0x0c=19200 bps) movwf SPBRG movlw 00100100b ; BRGH = high (2) movwf TXSTA ; enable async transmission, set BRGH bcf STATUS,RP0 ; Bank 0 movlw 10010000b ; enable async reception movwf RCSTA ; Settling delay for the UART clrf delayctr1 settle decfsz delayctr1,F goto settle ; Flush UART receive buffer movf RCREG,W movf RCREG,W movf RCREG,W ; Read servo PWM period in EEPROM movlw EEpwmperiodH call eeread movwf pwmperiodH movlw EEpwmperiodL call eeread movwf pwmperiodL ; Read servo duty cycle for the "up" position in EEPROM movlw EEupH call eeread movwf upH movlw EEupL call eeread movwf upL ; Read servo duty cycle for the "down" position in EEPROM movlw EEdownH call eeread movwf downH movlw EEdownL call eeread movwf downL ; Pre-compute timing values for the "up" position movlw upH call precompt ; Pre-compute timing values for the "down" position movlw downH call precompt ; Set timer 1 for the PWM generator: ; Set timer 1 in timer mode, internal clock, and set prescaler movlw 00000000b ; 1/1 --> 1us per count at Fosc = 4MHz movwf T1CON ; Set CCP in compare mode, software interrupt generator on TMR1 match movlw 00001010b ; Compare mode, clear output on match movwf CCP1CON ; Enable CCP1 compare match interrupt and TMR1 overflow interrupt movlw 00000101b bsf STATUS,RP0 ; Bank 1 movwf PIE1 bcf STATUS,RP0 ; Bank 0 ; Enable peripheral interrupts bsf INTCON,PEIE ; Enable general interrupts bsf INTCON,GIE main ; Wait 1 second to give a change to the user to send ENTER to get to ; the setup menu movlw 10d ; 1s... call delay ; ...delay ; Check whether a character is waiting in the UART receive buffer btfss PIR1,RCIF goto mainloop ; No: skip the setup menu ; Check that the received character is CR movf RCREG,W xorlw CR btfsc STATUS,Z ; Is it zero ? goto setupmenu ; Go to the setup menu if character is CR mainloop ; Wait for a character on the serial port call recvuart movwf j ; Save the received character ; Did we get 'u' or 'U'? xorlw 'u' btfsc STATUS,Z goto puppetup ; Raise the puppet movf j,W xorlw 'U' btfsc STATUS,Z goto puppetup ; Raise the puppet ; Did we get 'd' or 'D'? movf j,W xorlw 'd' btfsc STATUS,Z goto puppetdown ; Let the puppet slump movf j,W xorlw 'D' btfsc STATUS,Z goto puppetdown ; Let the puppet slump ; Any other received character is discarded goto mainloop puppetup ; Send the servo to the "up" position movlw upH call startservo goto servodelay puppetdown ; Send the servo to the "down" position movlw downH call startservo servodelay ; Wait for the servo to settle on the desired position movlw 10d ; 1s... call delay ; ...delay ; Stop the servo: in case it can't reach its position or must fight the ; puppet's spring all the time for some reason, this will prevent it ; from ruining itself call stopservo ; Send "OK" to the serial port, so the controller knows the puppet is up call okmsg call crlfmsg goto mainloop ; -------- ; Routines ; -------- ; Setup menu setupmenu ; curset <-- current value being set clrf curset ; Start with the... bsf curset,PWMSETTING ; ...PWM period setting menulp ; Send the servo PWM period on the serial port call pwmmsg movlw pwmperiodH call sendval btfsc curset,PWMSETTING call arrowmsg call crlfmsg ; Send the servo duty cycle for the "up" position on the serial port call upmsg call dutymsg movlw upH call sendval btfsc curset,UPSETTING call arrowmsg call crlfmsg ; Send the servo duty cycle for the "down" position on the serial port call downmsg call dutymsg movlw downH call sendval btfsc curset,DOWNSETTING call arrowmsg call crlfmsg ; Send the rest of the menu call menumsg ; Wait for a character on the serial port menusel call recvuart movwf j ; Save the received character ; Did we get '+' ? xorlw '+' btfsc STATUS,Z goto incset ; Did we get '-' ? movf j,W xorlw '-' btfsc STATUS,Z goto decset ; Did we get 'p' or 'P'? movf j,W xorlw 'p' btfsc STATUS,Z goto pwmset movf j,W xorlw 'P' btfsc STATUS,Z goto pwmset ; Did we get 'u' or 'U'? movf j,W xorlw 'u' btfsc STATUS,Z goto upset movf j,W xorlw 'U' btfsc STATUS,Z goto upset ; Did we get 'd' or 'D'? movf j,W xorlw 'd' btfsc STATUS,Z goto downset movf j,W xorlw 'D' btfsc STATUS,Z goto downset ; Did we get 's' or 'S'? movf j,W xorlw 's' btfsc STATUS,Z goto save movf j,W xorlw 'S' btfsc STATUS,Z goto save ; Did we get 'q' or 'Q'? movf j,W xorlw 'q' btfsc STATUS,Z goto endmenu movf j,W xorlw 'Q' btfsc STATUS,Z goto endmenu ; Any other character, just resend the menu goto menulp decset bcf curset,INCSETTING goto setstep incset bsf curset,INCSETTING ; Do we need to change the PWM setting? setstep btfsc curset,PWMSETTING goto steppwm ; Yes ; Increment/decrement the duty cycle of any of the 2 predefined servo ; positions by 6us, corresponding to 1.06 degrees of servo rotation... movlw 6d movwf number2L movlw 0d movwf number2H ; Point to H btfsc curset,UPSETTING movlw upH btfsc curset,DOWNSETTING movlw downH goto addsub ; ...unless it's the PWM setting, in which case increment/decrement the ; PWM period by steps of 1000us (1ms) steppwm movlw 0x03 movwf number2H movlw 0xe8 movwf number2L ; Point to pwmperiodH movlw pwmperiodH addsub ; Load the value to change in number1 movwf FSR movf INDF,W movwf number1H incf FSR,F movf INDF,W movwf number1L ; Add or subtract ? btfsc curset,INCSETTING goto addstep ; Subtract number2 from number1 call subtract16 goto chgset ; Add number2 to number1 addstep call add16 chgset ; Store the result in the correct location movf number1L,W movwf INDF decf FSR,F movf number1H,W movwf INDF ; Send the new value to the uart movf FSR,W ; Save FSR... movwf i ; ...in i call sendval movlw CR call senduart movf i,W ; Restore... movwf FSR ;...FSR ; Are we changing the PWM setting ? btfsc curset,PWMSETTING goto calcall ; Yes: go recalculate all the timings ; No: recalculate the timings for this servo position only movf FSR,W call precompt ; Point to H btfsc curset,UPSETTING movlw upH btfsc curset,DOWNSETTING movlw downH ; Set the new servo position call setservo goto menusel ; Recalculate the timings for both servo positions calcall movlw upH call precompt movlw downH call precompt goto menusel pwmset clrf curset bsf curset,PWMSETTING call stopservo goto menulp upset clrf curset bsf curset,UPSETTING movlw upH call startservo goto menulp downset clrf curset bsf curset,DOWNSETTING movlw downH call startservo goto menulp save ; Save the PWM period movf pwmperiodH,W movwf i movlw EEpwmperiodH call eewrite movf pwmperiodL,W movwf i movlw EEpwmperiodL call eewrite ; Save the "up" position pulse length movf upH,W movwf i movlw EEupH call eewrite movf upL,W movwf i movlw EEupL call eewrite ; Save the "down" position pulse length movf downH,W movwf i movlw EEdownH call eewrite movf downL,W movwf i movlw EEdownL call eewrite call savedmsg call crlfmsg goto menulp endmenu ; Send "Done" and return to the start of the main program call donemsg call crlfmsg goto main ; Pre-compute timer1 and ccp1 timings for a given servo position ; Inputs: W --> Address of H (assumes that the following variables ; follow in that order: L, tmr1svH, ; tmr1svL, ccpr1vH, ccpr1vL precompt movwf FSR ; Point to H ; Store 65535 in number1 movlw 0xff movwf number1H movwf number1L ; Recover value of PWM period and store it in number2 movf pwmperiodH,W movwf number2H movf pwmperiodL,W movwf number2L ; Subtract number2 (PWM period) from number1 (65535) call subtract16 ; Recover the timing value of and store it in number2 movf INDF,W movwf number2H incf FSR,F ; Point to L movf INDF,W movwf number2L ; Store the result of (65535-PWM period) in tmr1sv incf FSR,F ; Point to tmr1svH movf number1H,W movwf INDF incf FSR,F ; Point to tmr1svL movf number1L,W movwf INDF ; Add to (65535-PWM period) call add16 ; Store the result of (65535-PWM period+ incf FSR,F ; Point to ccp1vH movf number1H,W movwf INDF incf FSR,F ; Point to ccp1vL movf number1L,W movwf INDF return ; Start the servo, i.e. assume the servo is currently stopped, set all the ; timing values for one of the 2 predefined servo positions, and start the PWM ; signal generation. ; Inputs: W --> Address of H (assumes that the following variables ; follow in that order: L, tmr1svH, ; tmr1svL, ccpr1vH, ccpr1vL startservo ; Store tmr1sv in tmr1startval addlw 2 ; Point to... movwf FSR ; ...tmr1svH movf INDF,W movwf tmr1startvalH incf FSR,F ; Point to tmr1svL movf INDF,W movwf tmr1startvalL ; Store ccpr1v in CCPR1 incf FSR,F ; Point to ccpr1vH movf INDF,W movwf CCPR1H incf FSR,F ; Point to ccpr1vL movf INDF,W movwf CCPR1L ; Set TMR1 to 0xffff, ready to overflow and start a new duty cycle movlw 0xff movwf TMR1H movwf TMR1L ; Enable the timer bsf T1CON,TMR1ON return ; Set one of the 2 predefined servo positions, assuming the PWM signal is ; currently being generated ; Inputs: W --> Address of H (assumes that the following variables ; follow in that order: L, tmr1svH, ; tmr1svL, ccpr1vH, ccpr1vL setservo ; Store ccpr1v in CCPR1 addlw 4 ; Point to... movwf FSR ; ...ccpr1vH movf INDF,W movwf CCPR1H incf FSR,F ; Point to ccpr1vL movf INDF,W movwf CCPR1L return ; Stop the servo by killing the PWM signal stopservo ; Disable the timer bcf T1CON,TMR1ON return ; Pause for approximately W 1/10s of seconds ; Inputs: W --> delay in 1/10s of seconds delay movwf delayctr3 wt3 movlw 100d ; | This code lasts exactly 100001us: movwf delayctr2 ; | with Fosc = 4MHz wt2 movlw 239d ; | movwf delayctr1 ; | wt1 goto $+1 ; | decfsz delayctr1,F ; | goto wt1 ; | nop ; | decfsz delayctr2,F ; | goto wt2 ; v decfsz delayctr3,F goto wt3 return ; Receive character from the serial port. Don't return until a char is read ; Returns: W <-- received character recvuart btfss PIR1,RCIF ; Check for received data goto recvuart movf RCREG,W ; Save received data in W return ; Send character to serial port. Don't return until the char is sent ; Inputs: W --> character to send senduart movwf TXREG ; send data in W bsf STATUS,RP0 ; Bank 1 wtsent btfss TXSTA,TRMT ; (1) transmission is complete if hi goto wtsent bcf STATUS,RP0 ; Bank 0 return ; Send a 16 bit number in ASCII hex on the serial port ; Inputs: W --> address of the MSB of the number to convert sendval ; Copy number to send in buffer movwf FSR ; Point to MSB of the number movf INDF,W ; Copy MSB... movwf number1H ; ...to number1H buffer incf FSR,F ; Point to LSB of the number movf INDF,W ; Copy LSB... movwf number1L ; ...to number1L buffer ; Send "0x" movlw '0' call senduart movlw 'x' call senduart ; Convert and display bits 15-12 swapf number1H,W andlw 0xf movwf nibble call dgascii ; Convert and display bits 11-8 movf number1H,W andlw 0xf movwf nibble call dgascii ; Convert and display bits 7-4 swapf number1L,W andlw 0xf movwf nibble call dgascii ; Convert and display bits 3-0 movf number1L,W andlw 0xf movwf nibble call dgascii return ; Convert a value from 0 to 15 into ASCII hex and send it to the serial port ; Inputs: nibble --> value to convert and display dgascii movlw '0' movwf hexdigit hexcvrt movf nibble,F btfsc STATUS,Z ; Is the nibble null ? goto sendhex ; Yes: send the character to the serial port decf nibble,F incf hexdigit,F movf hexdigit,W xorlw ':' ; Has the hex digit passed 9 ? btfss STATUS,Z goto hexcvrt ; No: continue converting movlw 'A' ; hex digit is 10d == 'A' movwf hexdigit goto hexcvrt ; Continue converting sendhex movf hexdigit,W call senduart return ; Send "(P)WM period: " on the serial port pwmmsg movlw '(' call senduart movlw 'P' call senduart movlw ')' call senduart movlw 'W' call senduart movlw 'M' call senduart movlw ' ' call senduart movlw 'p' call senduart movlw 'e' call senduart movlw 'r' call senduart movlw 'i' call senduart movlw 'o' call senduart movlw 'd' call senduart movlw ':' call senduart movlw ' ' call senduart return ; Send "(U)p" on the serial port upmsg movlw '(' call senduart movlw 'U' call senduart movlw ')' call senduart movlw 'p' call senduart return ; Send "(D)own" on the serial port downmsg movlw '(' call senduart movlw 'D' call senduart movlw ')' call senduart movlw 'o' call senduart movlw 'w' call senduart movlw 'n' call senduart return ; Send " position duty cycle: " on the serial port dutymsg movlw ' ' call senduart movlw 'p' call senduart movlw 'o' call senduart movlw 's' call senduart movlw 'i' call senduart movlw 't' call senduart movlw 'i' call senduart movlw 'o' call senduart movlw 'n' call senduart movlw ' ' call senduart movlw 'd' call senduart movlw 'u' call senduart movlw 't' call senduart movlw 'y' call senduart movlw ' ' call senduart movlw 'c' call senduart movlw 'y' call senduart movlw 'c' call senduart movlw 'l' call senduart movlw 'e' call senduart movlw ':' call senduart movlw ' ' call senduart return ; Send "+/- to adjust\r\n(S)ave\r\n(D)isplay inputs\r\n(Q)uit\r\n" ; Send " position duty cycle: " on the serial port menumsg movlw '+' call senduart movlw '/' call senduart movlw '-' call senduart movlw ' ' call senduart movlw 't' call senduart movlw 'o' call senduart movlw ' ' call senduart movlw 'a' call senduart movlw 'd' call senduart movlw 'j' call senduart movlw 'u' call senduart movlw 's' call senduart movlw 't' call senduart call crlfmsg movlw '(' call senduart movlw 'S' call senduart movlw ')' call senduart movlw 'a' call senduart movlw 'v' call senduart movlw 'e' call senduart call crlfmsg movlw '(' call senduart movlw 'Q' call senduart movlw ')' call senduart movlw 'u' call senduart movlw 'i' call senduart movlw 't' call senduart call crlfmsg return ; Send ' <--' on the serial port arrowmsg movlw ' ' call senduart movlw '<' call senduart movlw '-' call senduart movlw '-' call senduart return ; Send 'Done' on the serial port donemsg movlw 'D' call senduart movlw 'o' call senduart movlw 'n' call senduart movlw 'e' call senduart return ; Send 'Saved' on the serial port savedmsg movlw 'S' call senduart movlw 'a' call senduart movlw 'v' call senduart movlw 'e' call senduart movlw 'd' call senduart return ; Send 'OK' on the serial port okmsg movlw 'O' call senduart movlw 'K' call senduart return ; Send CR/LF on the serial port crlfmsg movlw CR call senduart movlw LF call senduart return ; Read a byte from EEPROM ; Inputs: W --> address to read in EEPROM ; Outputs: W <-- byte read in EEPROM eeread bsf STATUS,RP0 ; Bank 1 movwf EEADR ; Address to read in EEPROM bsf EECON1,RD ; EE read movf EEDATA,W ; W <-- byte from EEPROM bcf STATUS,RP0 ; Bank 0 return ; Write a byte to EEPROM ; Inputs: W --> address to write in EEPROM ; i --> byte to write in EEPROM eewrite ; Load address and data bsf STATUS,RP0 ; Bank 1 movwf EEADR bcf STATUS,RP0 ; Bank 0 movf i,W bsf STATUS,RP0 ; Bank 1 movwf EEDATA ; Enable write bsf EECON1,WREN ; Do the magic EEPROM sequence bcf INTCON,GIE ; Disable interrupts movlw 0x55 movwf EECON2 movlw 0xAA movwf EECON2 bsf EECON1,WR ; Set WR bit, begin write ; Wait till the write cycle is done eewrwt btfsc EECON1,WR goto eewrwt ; Disable write, clear the write complete interrupt flag bit bcf EECON1,WREN bcf STATUS,RP0 ; Bank 0 bcf PIR1,EEIF ; Reenable interrupts bsf INTCON,GIE return ; Carry out a 16-bit addition ; Inputs: number1H/number1L --> number to add to ; number2H/number2L --> number to add ; Outputs: number1H/number1L --> result add16 movf number2L,W ; Add the LSB of number2... addwf number1L,F ; ...to the LSB of number1 btfsc STATUS,C ; Did the addition generate a CARRY ? incf number1H,F ; Yes: increment the MSB of number1 movf number2H,W ; Add the MSB of number2... addwf number1H,F ; ...to the MSB of number1 return ; Carry out a 16-bit subtract ; Inputs: number1H/number1L --> number to subtract from ; number2H/number2L --> number to subtract ; Outputs: number1H/number1L --> result subtract16 movf number2L,W ; Subtract the LSB of number2... subwf number1L,F ; ...from the LSB of number1 btfss STATUS,C ; Did the subtract generate a /BORROW ? decf number1H,F ; Yes: decrement the MSB of number1 movf number2H,W ; Subtract the MSB of number2... subwf number1H,F ; ...from the MSB of number1 return ; -------------------- ; EEPROM PRESET VALUES ; -------------------- org 0X2100 ; Base address of EEPROM for the programmer de 0x4e ; EEpwmperiodH de 0x20 ; EEpwmperiodL --> EEpwmperiod = 20000us = 20ms de 0x03 ; EEupH de 0xe8 ; EEupL --> EEupH = 1000us = 1ms de 0x07 ; EEdownH de 0xd0 ; EEdownL --> EEdown = 2000us = 2ms END