;************************************************************************** ; FILE: picworks1.asm * ; CONTENTS: PICworks1 * ; COPYRIGHT: MadLab Ltd. 2006 * ; AUTHOR: James Hutchby * ; UPDATED: 03/07/06 * ;************************************************************************** list p=12F629 #include "p12f629.inc" __config _INTRC_OSC_NOCLKOUT & _WDT_ON & _MCLRE_OFF & _PWRTE_ON & _BODEN_OFF & _CP_OFF & _CPD_OFF __idlocs h'FE10' errorlevel -207,-302,-305,-306 ;************************************************************************** ; * ; Specification * ; * ;************************************************************************** ; power-up self-test - both LEDs flash twice ; test program echoes pushbuttons to low and high current outputs ; supply voltage 4.8V - 12V dc (4 x AA or 6 x AA) ; LEDs: ; connect to LO1 (anode) & GND (cathode) and LO2 (anode) & GND (cathode) ; 470R (typically) in series with LED ; pushbuttons/microswitches: ; connect to IN1 & GND and IN2 & GND ; no external pull-up resistors needed ; dc motors (uni-directional): ; connect to HI1 & COM and HI2 & COM ; 100n ceramic capacitor across motor tags to reduce noise ; relays: ; connect to HI1 & COM and/or HI2 & COM (if rated at supply voltage) ; servo motors: ; connect power to COM (+ve) and either GND (-ve), connect controls to ; LO1 (SERVO1) and LO2 (SERVO2) ; analogue sensor: ; connect sensor (LDR, variable resistor etc.) to COM, 100n (polyester) capacitor ; to either GND, 10R resistor to IN2, connect other three ends together ; variable resistor - connect to centre wiper and either end of track ; N.B. no analogue sensor on IN1 as GP3 is input only ; piezo speaker: ; connect to LO1 & GND, LO2 & GND, or LO1 & LO2 ; in third case piezo is driven in anti-phase and is louder than single phase ; 100R resistor in series with piezo ; piezo at fixed frequency ~2kHz, for other frequencies drive directly rather ; than via isr (disable interrupts to avoid glitches) ; if in-circuit programming is required then be careful not to drive LO1 or LO2 ; with too high a current which could lead to corruption during programming of ; the clock and data signals which share the same pins ; these pins already drive the on-board LEDs so remove R6 and R7 if necessary ;************************************************************************** ; * ; Port assignments * ; * ;************************************************************************** GPIO_IO equ b'001100' ; port I/O status ; inputs #define IN1 GPIO,3 ; in parallel with pushbutton #1 (S1) #define IN2 GPIO,2 ; in parallel with pushbutton #2 (S2) ; low current outputs #define LO1 GPIO,1 ; in parallel with LED #1 (L1) #define LO2 GPIO,0 ; in parallel with LED #2 (L2) ; high current outputs #define HI1 GPIO,5 #define HI2 GPIO,4 ;************************************************************************** ; * ; Constants and timings * ; * ;************************************************************************** ; conditional assembly switches MOTOR1 equ 0 ; non-zero if motor #1 connected to HI1 MOTOR2 equ 0 ; non-zero if motor #2 connected to HI2 SERVO1 equ 0 ; non-zero if servo #1 connected to LO1 SERVO2 equ 0 ; non-zero if servo #2 connected to LO2 ANALOGUE equ 0 ; non-zero if sensor connected to IN2 PIEZO1 equ 0 ; non-zero if piezo connected to LO1 PIEZO2 equ 0 ; non-zero if piezo connected to LO2 FACTORY equ 0 ; non-zero if factory default program USER equ 1 ; non-zero if user program ; (nominal) processor clock frequency in Hz CLOCK equ d'4000000' ; number of pwm steps PWM_STEPS equ d'64' ; motor acceleration ACCEL equ d'1' ; servo minimum and maximum pulse length in us (normally centred at 1.5ms) SERVO_MIN equ d'1000' ; nominal value SERVO_MAX equ d'2000' ; nominal value if SERVO_MAX/d'16' > h'ff' error "Servo maximum pulse length exceeds limit (4ms)" endif ; number of servo steps SERVO_STEPS equ (SERVO_MAX-SERVO_MIN)/d'16' ; capacitor discharge period in ms DISCHARGE equ d'10' ;************************************************************************** ; * ; File register usage * ; * ;************************************************************************** RAM set h'20' MAX set h'60' cblock RAM context:3 ; saved context flags ; various flags ticks ; clock ticks rand:2 ; random number count ; counter sensor ; sensor reading temp1, temp2 ; isr timers work1, work2 ; work registers speed1, speed2 ; motor speeds (pwm duty cycles), 0 (off) to 64 (fully on) accel1, accel2 ; instantaneous motor speeds servo1, servo2 ; servo positions, 0 (left) to SERVO_STEPS-1 (right) RAM_ endc if RAM_ > MAX error "File register usage overflow" endif ; flags PIEZO_ equ 0 ; set if piezo active ;************************************************************************** ; * ; Macros * ; * ;************************************************************************** routine macro label ; routine label endm tstw macro ; test w register iorlw 0 endm movff macro f1,f2 ; move file to file movfw f1 movwf f2 endm movlf macro n,f ; move literal to file movlw n movwf f endm ;-------------------------------------------------------------------------- ; reset and interrupt service routine vectors ;-------------------------------------------------------------------------- org 0 ; un-comment the following lines to trim the internal RC oscillator to 4MHz ; caution: some PIC programmers erase the calibration value at address 3ff ; leading to malfunction if the lines are un-commented ; bsf STATUS,RP0 ; call h'3ff' ; movwf OSCCAL goto main_entry org 4 goto isr ;************************************************************************** ; * ; Procedures * ; * ;************************************************************************** ;-------------------------------------------------------------------------- ; dc motor pwm control ;-------------------------------------------------------------------------- do_pwm macro if MOTOR1 ; motor #1 control movfw ticks ; pwm andlw h'3f' subwf accel1,w skpnz clrc skpc bcf HI1 skpnc bsf HI1 movfw accel1 ; accelerate subwf speed1,w movlw ACCEL skpz addwf accel1 movfw speed1 subwf accel1,w skpnc subwf accel1 endif if MOTOR2 ; motor #2 control movfw ticks ; pwm andlw h'3f' subwf accel2,w skpnz clrc skpc bcf HI2 skpnc bsf HI2 movfw accel2 ; accelerate subwf speed2,w movlw ACCEL skpz addwf accel2 movfw speed2 subwf accel2,w skpnc subwf accel2 endif endm ;-------------------------------------------------------------------------- ; servo motor control ;-------------------------------------------------------------------------- do_servo macro movfw ticks ; execute every ~16ms andlw b'111111' bnz dos2 if SERVO1 ; check within range movlw SERVO_STEPS-1 subwf servo1,w skpnc subwf servo1 endif if SERVO2 movlw SERVO_STEPS-1 subwf servo2,w skpnc subwf servo2 endif if SERVO1 ; determine control pulse lengths movfw servo1 addlw SERVO_MIN/d'16' movwf temp1 else clrf temp1 endif if SERVO2 movfw servo2 addlw SERVO_MIN/d'16' movwf temp2 else clrf temp2 endif if SERVO1 ; output control pulses bsf LO1 endif if SERVO2 bsf LO2 endif dos1 clrwdt ; [4] if SERVO1 ; servo #1 tstf temp1 ; [4] skpz ; [8/4] decf temp1 ; [0/4] skpnz ; [8/4] bcf LO1 ; [0/4] else nop ; [4] nop ; [4] nop ; [4] nop ; [4] nop ; [4] endif if SERVO2 ; servo #2 tstf temp2 ; [4] skpz ; [8/4] decf temp2 ; [0/4] skpnz ; [8/4] bcf LO2 ; [0/4] else nop ; [4] nop ; [4] nop ; [4] nop ; [4] nop ; [4] endif movfw temp1 ; loop until both pulses finished [4] iorwf temp2,w ; [4] bnz dos1 ; [12] ; 64 cycles per iteration = ~16us dos2 endm ;-------------------------------------------------------------------------- ; piezo speaker ;-------------------------------------------------------------------------- do_piezo macro if PIEZO1 ; toggle output LO1 movlw 1<<1 btfsc flags,PIEZO_ xorwf GPIO endif if PIEZO2 ; toggle output LO2 movlw 1<<0 btfsc flags,PIEZO_ xorwf GPIO endif endm ;-------------------------------------------------------------------------- ; interrupt service routine (called every ~250us) ;-------------------------------------------------------------------------- routine isr movwf context+0 ; save context swapf STATUS,w clrf STATUS ; bank 0 movwf context+1 movff PCLATH,context+2 clrf PCLATH ; page 0 incf ticks ; increment ticks if MOTOR1 || MOTOR2 ; dc motor pwm control do_pwm endif if SERVO1 || SERVO2 ; servo motor control do_servo endif if PIEZO1 || PIEZO2 ; piezo speaker do_piezo endif bcf INTCON,T0IF ; clear interrupt movff context+2,PCLATH ; restore context swapf context+1,w movwf STATUS swapf context+0 swapf context+0,w retfie ;-------------------------------------------------------------------------- ; waits, fed with the (approx) wait in milliseconds in the wreg ;-------------------------------------------------------------------------- routine wait_ms movwf work1 wait1 movlf CLOCK/(d'1000'*d'20'),work2 wait2 clrwdt ; [4] nop ; [4] decfsz work2 ; [4] goto wait2 ; [8] decfsz work1 goto wait1 return ;-------------------------------------------------------------------------- ; waits, fed with the (approx) wait in seconds in the wreg ;-------------------------------------------------------------------------- routine wait_s movwf count wait3 movlw d'250' call wait_ms movlw d'250' call wait_ms movlw d'250' call wait_ms movlw d'250' call wait_ms decfsz count goto wait3 return ;-------------------------------------------------------------------------- ; writes the EEPROM with w reg ;-------------------------------------------------------------------------- write_EEPROM macro addr bsf STATUS,RP0 movwf EEDATA movlf addr,EEADR call write1 bcf STATUS,RP0 endm write1 bcf INTCON,GIE ; disable global interrupts bsf EECON1,WREN movlf h'55',EECON2 movlf h'aa',EECON2 bsf EECON1,WR write2 clrwdt btfsc EECON1,WR goto write2 bcf EECON1,WREN bsf INTCON,GIE ; enable global interrupts return ;-------------------------------------------------------------------------- ; reads the EEPROM into w reg ;-------------------------------------------------------------------------- read_EEPROM macro addr bsf STATUS,RP0 movlf addr,EEADR bsf EECON1,RD movfw EEDATA bcf STATUS,RP0 endm ;-------------------------------------------------------------------------- ; generates a pseudo random number, returns the number in w reg (0 to 255) ;-------------------------------------------------------------------------- routine get_random movfw rand+0 iorwf rand+1,w bnz getr1 movfw TMR0 ; seed generator movwf rand+1 xorlw h'ff' movwf rand+0 getr1 rlf rand+0,w ; calculate next in sequence xorwf rand+0,w movwf work1 ; msb <= Q15 ^ Q14 swapf rand+1,w btfsc rand+0,4 xorlw h'80' ; msb <= Q12 ^ Q3 xorwf work1 rlf work1 rlf rand+1 rlf rand+0 ; << 1 + (Q15 ^ Q14 ^ Q12 ^ Q3) movfw rand+1 ; w <= random number return ;-------------------------------------------------------------------------- ; samples the analogue sensor, returns the result in w reg (1 to 255, or ; 0 if timeout) ;-------------------------------------------------------------------------- if ANALOGUE routine sample_sensor bcf IN2 ; discharge the capacitor bsf STATUS,RP0 movlf GPIO_IO&~(1<<2),TRISIO bcf STATUS,RP0 movlw DISCHARGE call wait_ms clrwdt bcf INTCON,GIE ; interrupts off bsf STATUS,RP0 ; weak pull-ups disabled bsf OPTION_REG,7 bcf STATUS,RP0 clrf work1 bsf STATUS,RP0 ; charge the capacitor movlf GPIO_IO,TRISIO bcf STATUS,RP0 samp1 incf work1 ; [4] bz samp2 ; branch if timeout [8] ; DELAY = ~10 for LDR (with C = 100n) ; DELAY = ~5 for 47k variable resistor ; experiment to find the most suitable value for the sensor in use DELAY set d'10' variable i = DELAY while i > 0 nop ; [4] i -= 1 endw btfss IN2 ; charged ? [4] goto samp1 ; loop if not [8] samp2 bsf STATUS,RP0 ; weak pull-ups enabled bcf OPTION_REG,7 bcf STATUS,RP0 bsf INTCON,GIE ; interrupts on movfw work1 return endif ;-------------------------------------------------------------------------- ; waits for a change in sensor reading (used with an LDR functions as a ; simple proximity/motion sensor) ;-------------------------------------------------------------------------- if ANALOGUE routine sensor_change ; ~b'11', ~b'1' = more sensitive, ~b'1111', ~b'11111' = less sensitive LDR_MASK equ ~b'111' call sample_sensor movwf sensor chan1 movlw 1 call wait_ms call sample_sensor xorwf sensor,w xorwf sensor andlw LDR_MASK bz chan1 return endif ;-------------------------------------------------------------------------- ; short beep (200ms at ~2kHz) ;-------------------------------------------------------------------------- routine beep if PIEZO1 bsf LO1 endif if PIEZO2 bcf LO2 endif bsf flags,PIEZO_ movlw d'200' call wait_ms bcf flags,PIEZO_ if PIEZO1 bcf LO1 endif if PIEZO2 bcf LO2 endif return ;-------------------------------------------------------------------------- ; main entry point ;-------------------------------------------------------------------------- routine main_entry bcf STATUS,RP0 clrf GPIO ; initialise port bsf STATUS,RP0 movlf GPIO_IO,TRISIO bcf STATUS,RP0 bsf STATUS,RP0 ; prescale WDT 1:128, movlw b'00001111' ; weak pull-ups enabled movwf OPTION_REG movlf GPIO_IO,WPU bcf STATUS,RP0 bcf INTCON,GIE ; interrupts off movlf b'111',CMCON ; comparator off clrf PCLATH movfw STATUS ; wake-up from sleep ? andlw (1<