; PWM vibrator driving code - version 1.02 ; (c) 2007..2009 Design-Design Technology - contact crem@desdes.com ; This code is an AtTiny15 replacement for a PIC 12C508 driving an Ann Summers ; vibrator. Written in about two hours, using the Zeus AVR assembler. ; Motor controlled by PORTB pin 0, high = ON ; A bi-colour LED + series resistor sits between PORTB pins 1 and 2 ; The switch is between GND and PORTB pin 3, with an external pull-up resistor ; pressed=low. ; Spot the ghastly last minute 'better give the user some feedback' LED hack for ; the switch press duration ;) ; Slight changes to rtf formatting 9/9/2008 ; Changes to port it to Zeus-ish and use macros 8/12/2008 ; Added a graphical option as an example 14/3/2009 ; Added "referenced" example setcpu "ATTINY15" ; Select the type of CPU CPU_Clk equ 1600000 ; Set the speed optVoltage equ 5.0 ; 5V0 for now. . . optProgSpeed equ 20 ; Slow optProgFlashSize equ 1024 ; 1K DriveFuses equ true ; Drive them fsBOD equ 0 ; Low fsClockSrc equ %00 ; Default clock fsRESET_DISABLE equ false ; Do not disable the reset line fsLockByte equ $00 ; Code protected ; These next two lines tell the AVR programmer to read the clock fuse and modify the code optCopyCalLD equ true ; Copy the calibration fuse as a LD instruction optCopyCalFuse equ 0 ; Use fuse zero ; Options - this adds a graphical option near the "assemble" button that the user can select before assembly. optionsize 0 ; We don't need extra room, but show the option in the Zeus panel rather than the options tab bAddIndexF optionbool 0,0,"Add index pulses? (they don't work very well)",false ; Give the user the choice ; Application Registers rSwitch reg r15 ; rSwTemp reg r14 ; include "e.asm" ; CPU equates ; Code starts here irqSpurious equ * ; Just restart if we ever have an exception Start wdr ; Don't bite us cli ; Disable interrupts optCalByteAddr ld al,#$80 ; Setup the oscillator. My device programmer reads the AVR's actual out OSCCAL,al ; fuse value and modifies the ld al,#XX instruction during programming ; Enable the watchdog ld al,#%000 0 1 111 ; Slow, uncritical watchdog out WDTCR,al ; out WDTCR,al ; ; Setup the pins rcall Setup_Pins ; Setup the I/O direction and default states ; Setup the switch clr rSwitch ; Clear it ; Application code. The switch simply moves up/down through ; these functions. ; When a 'mode' exits it returns the zero flag indicating a ; short/long press on the switch ; Speeds are approximately percentages, so 0 = motor off, >99 = ; full speed ; Times are approximately multiples of 10mS L1 rcall Mode_0 ; OFF beq L2 ; Next rjmp L_Last ; Prev ; Fixed speeds ------ L2 rcall Mode_1 ; Speed = 30% bne L1 ; L3 rcall Mode_2 ; Speed = 60% bne L2 ; L4 rcall Mode_3 ; Speed = 100% bne L3 ; ; Marker? mIndexMark(1) ; One marker pulse ; _ ; Pulses -> |_| | L5 mPulse(5,25,30,50,L4) ; MinSpeed,MaxSpeed,MinDuration,MaxDuration,JumpBack L6 mPulse(5,25,20,20,L5) ; MinSpeed,MaxSpeed,MinDuration,MaxDuration,JumpBack L7 mPulse(5,25,10,10,L6) ; MinSpeed,MaxSpeed,MinDuration,MaxDuration,JumpBack ; Stronger L8 mPulse(5,99,30,30,L7) ; MinSpeed,MaxSpeed,MinDuration,MaxDuration,JumpBack L9 mPulse(5,99,20,20,L8) ; MinSpeed,MaxSpeed,MinDuration,MaxDuration,JumpBack L10 mPulse(5,99,10,10,L9) ; MinSpeed,MaxSpeed,MinDuration,MaxDuration,JumpBack ; Marker mIndexMark(2) ; Two marker pulses ; Rising sawtooths -> /|/|/| L11 mSaw(5,99,1,6,L10) ; InitialSpeed,FinalSpeed,Dirn,Rate,JumpBack L12 mSaw(5,99,1,3,L11) ; InitialSpeed,FinalSpeed,Dirn,Rate,JumpBack L13 mSaw(5,99,1,2,L12) ; InitialSpeed,FinalSpeed,Dirn,Rate,JumpBack L14 mSaw(5,99,1,1,L13) ; InitialSpeed,FinalSpeed,Dirn,Rate,JumpBack ; Marker mIndexMark(3) ; Three marker pulses ; Falling sawtooths |\|\|\ L15 mSaw(99,5,-1,6,L14) ; InitialSpeed,FinalSpeed,Dirn,Rate,JumpBack L16 mSaw(99,5,-1,3,L15) ; InitialSpeed,FinalSpeed,Dirn,Rate,JumpBack L17 mSaw(99,5,-1,2,L16) ; InitialSpeed,FinalSpeed,Dirn,Rate,JumpBack L18 mSaw(99,5,-1,1,L17) ; InitialSpeed,FinalSpeed,Dirn,Rate,JumpBack ; Marker mIndexMark(4) ; Four marker pulses ; See-saw /\/\/\ L19 mSeeSaw(5,99,1,6,L18) ; InitialSpeed,FinalSpeed,Dirn,Rate,JumpBack L20 mSeeSaw(5,99,1,3,L19) ; InitialSpeed,FinalSpeed,Dirn,Rate,JumpBack L21 mSeeSaw(5,99,1,2,L20) ; InitialSpeed,FinalSpeed,Dirn,Rate,JumpBack L22 mSeeSaw(5,99,1,1,L21) ; InitialSpeed,FinalSpeed,Dirn,Rate,JumpBack ; Marker mIndexMark(5) ; Five marker pulses ; See-saw between limits /\/\/\ L23 mSeeSaw(15,50,1,6,L22) ; InitialSpeed,FinalSpeed,Dirn,Rate,JumpBack L24 mSeeSaw(15,50,1,3,L23) ; InitialSpeed,FinalSpeed,Dirn,Rate,JumpBack L25 mSeeSaw(15,50,1,2,L24) ; InitialSpeed,FinalSpeed,Dirn,Rate,JumpBack L26 mSeeSaw(15,50,1,1,L25) ; InitialSpeed,FinalSpeed,Dirn,Rate,JumpBack ; Marker mIndexMark(6) ; Six marker pulses ; See-saw between limits /\/\/\ L27 mSeeSaw(25,50,1,6,L26) ; InitialSpeed,FinalSpeed,Dirn,Rate,JumpBack L28 mSeeSaw(25,50,1,3,L27) ; InitialSpeed,FinalSpeed,Dirn,Rate,JumpBack L29 mSeeSaw(25,50,1,2,L28) ; InitialSpeed,FinalSpeed,Dirn,Rate,JumpBack L30 mSeeSaw(25,50,1,1,L29) ; InitialSpeed,FinalSpeed,Dirn,Rate,JumpBack ; Marker mIndexMark(7) ; Seven marker pulses ; See-saw between limits /\/\/\ L31 mSeeSaw(25,75,1,6,L30) ; InitialSpeed,FinalSpeed,Dirn,Rate,JumpBack L32 mSeeSaw(25,75,1,3,L31) ; InitialSpeed,FinalSpeed,Dirn,Rate,JumpBack L33 mSeeSaw(25,75,1,2,L32) ; InitialSpeed,FinalSpeed,Dirn,Rate,JumpBack L34 mSeeSaw(25,75,1,1,L33) ; InitialSpeed,FinalSpeed,Dirn,Rate,JumpBack ; Marker mIndexMark(8) ; Eight marker pulses ; _ _____ _ _____ ; Sequence of pulses -> |_| |_| |_| |_| | L35 mSeq(0,25,6,30,L34) ; SpaceSpeed,MarkSpeed,MinPeriod,MaxPeriod,JumpBack L36 mSeq(25,50,6,30,L35) ; SpaceSpeed,MarkSpeed,MinPeriod,MaxPeriod,JumpBack L37 mSeq(25,75,6,30,L36) ; SpaceSpeed,MarkSpeed,MinPeriod,MaxPeriod,JumpBack L38 mSeq(0,98,2,10,L37) ; SpaceSpeed,MarkSpeed,MinPeriod,MaxPeriod,JumpBack ; Marker mIndexMark(9) ; Nine marker pulses ; _ __ ___ ____ ______ ; Variable speed pulses -> |_| |__| |___| |____| |_____| | L39 mVari(15,99,7,70,L38) ; MinSpeed,MaxSpeed,MinPulseTime,RampRate,JumpBack L40 mVari(15,99,7,50,L39) ; MinSpeed,MaxSpeed,MinPulseTime,RampRate,JumpBack L_Last mVari(15,99,7,25,L40) ; MinSpeed,MaxSpeed,MinPulseTime,RampRate,JumpBack rjmp L1 ; Back to the start ; OFF, but emulate orange LED if referenced Mode_0 ; Only include this if it's used. Mode_0 rcall Test_Sw ; Pressed? bcs Rts ; Yes, skip ld al,#50 ; rcall Wait_n100uS ; rcall Motor_OFF ; ld al,#50 ; rcall Wait_n100uS ; rcall LED_RED ; Off but red rjmp Mode_0 ; Loop endif ; Low speed if referenced Mode_1 ; Only include this if it's used. Mode_1 rcall Test_Sw ; Pressed? bcs Rts ; Yes, skip rcall Motor_ON ; ld al,#30 ; rcall Wait_n100uS ; rcall Motor_OFF ; ld al,#70 ; rcall Wait_n100uS ; rjmp Mode_1 ; Loop endif ; Med speed if referenced Mode_2 ; Only include this if it's used. Mode_2 rcall Test_Sw ; Pressed? bcs Rts ; Yes, skip rcall Motor_ON ; ld al,#60 ; rcall Wait_n100uS ; rcall Motor_OFF ; ld al,#40 ; rcall Wait_n100uS ; rjmp Mode_2 ; Loop endif ; Full speed if referenced Mode_3 ; Only include this if it's used. Mode_3 rcall Test_Sw ; Pressed? bcs Rts ; Yes, skip rcall Motor_ON ; ld al,#100 ; rcall Wait_n100uS ; rjmp Mode_3 ; Loop endif if referenced Rts ; Only include this if it's used. Rts ret ; Done endif ; Sawtooth. rll=Min, rlh=Max rhl=Dirn, rhh=Rate if referenced Mode_Saw ; Only include this if it's used. Mode_Saw ld bl,rll ; Sawtooth value Mode_Saw_Lp ld bh,rhh ; Update rate Mode_Saw_Lp2 rcall Mode_Phase ; Do it bcs Rts ; Yes, skip ; Update phase add bl,rhl ; Step it cp bl,rlh ; Max? beq Mode_Saw ; Yes, go back to min rjmp Mode_Saw_Lp ; Loop endif ; See-Saw. rll=Min, rlh=Max rhl=Dirn, rhh=Rate if referenced Mode_SeeSaw ; Only include this if it's used. Mode_SeeSaw ld bl,rll ; Sawtooth value neg rhl ; Mode_SeeSawFlip neg rhl ; Flip the direction Mode_SeeSaw_Lp ld bh,rhh ; Update rate Mode_SeeSaw_Lp2 rcall Mode_Phase ; Do it bcs Rts ; Yes, skip ; Update phase add bl,rhl ; Step it cp bl,rlh ; Max? beq Mode_SeeSawFlip ; Yes, flip cp bl,rll ; Min? beq Mode_SeeSawFlip ; Yes, flip rjmp Mode_SeeSaw_Lp ; Loop endif ; Pulse. rll for rhl, then rlh for rhh if referenced Mode_Pulse ; Only include this if it's used. Mode_Pulse ld bl,rll ; First ld bh,rhl ; Time rcall Mode_Phase ; Do it bcs Rts ; Yes, skip ; Other mode ld bl,rlh ; Second ld bh,rhh ; Time rcall Mode_Phase ; Do it bcs Rts ; Yes, skip ; Other mode rjmp Mode_Pulse ; Loop endif ; VariPulse. rll for rhl, then rlh for rhl, then change rhl if referenced Mode_Vari ; Only include this if it's used. Mode_Vari ld cl,rhh ; Max Mode_VariLp ld bl,rll ; First ld bh,cl ; Time rcall Mode_Phase ; Do it bcs Rts ; Yes, skip ; Other mode ld bl,rlh ; Second ld bh,cl ; Time rcall Mode_Phase ; Do it bcs Rts ; Yes, skip ; Change the time dec cl ; cp cl,rhl ; Min? beq Mode_Vari ; Yes, reset ; Other mode rjmp Mode_VariLp ; Loop endif ; Do a phase. BL speed for BH time if referenced Mode_Phase ; Only include this if it's used. Mode_Phase rcall Test_Sw ; Pressed? bcs MP_X ; Yes, skip cp bl,#0 ; Never ON beq MP_1 ; rcall Motor_ON ; MP_1 ld al,bl ; rcall Wait_n100uS ; cp bl,#99 ; Never OFF? bcc MP_2 ; rcall Motor_OFF ; MP_2 ld al,#101 ; sub al,bl ; rcall Wait_n100uS ; ; Timer dec bh ; bne Mode_Phase ; MP_X ret ; Done endif ; A sequence. rll=min, rlh=max, rhl=short, rhh=long if referenced Mode_Seq ; Only include this if it's used. Mode_Seq ld bl,rll ; Off ld bh,rhl ; [short period] rcall Mode_Phase ; Do it bcs MS_X ; Yes, skip ld bl,rlh ; ON ld bh,rhl ; [short] rcall Mode_Phase ; Do it bcs MS_X ; Yes, skip ld bl,rll ; Off ld bh,rhl ; [short] rcall Mode_Phase ; Do it bcs MS_X ; Yes, skip ld bl,rlh ; On ld bh,rhh ; [long] rcall Mode_Phase ; Do it bcs MS_X ; Yes, skip rjmp Mode_Seq ; MS_X ret ; Done endif ; Wait a number of 100uS Wait_n100uS ld ah,#52 ; Set using 'scope Wait_Lp dec ah ; mS Loop bne Wait_Lp ; dec al ; Loop bne Wait_n100uS ; ret ; Done ; Test the switch. If pressed returns CF=1, Z=1 for short press, Z=0 for long. ; This routine also drives the LED GREEN/RED to indicate press duration. Test_Sw wdr ; Don't bite us clc ; Assume not pressed ld rSwTemp,al ; Save it sbis PINB,#3 ; Skip if not pressed rjmp TS_Pressed ; ; Not pressed, but was it pressed? and how long for? ld al,rSwitch ; Get it cp al,#70 ; Long? bcc TS_Long ; Yes, skip cp al,#5 ; Short? bcc TS_Short ; Yes, skip ld al,rSwTemp ; Restore ; Nope, not long enough to debounce... clr rSwitch ; Clear it clc ; Nothing ret ; Done ; Short press TS_Short clr rSwitch ; ld al,rSwTemp ; sec ; C + Z sez ; ret ; ; Long press TS_Long clr rSwitch ; ld al,rSwTemp ; sec ; C + NZ clz ; ret ; ; Pressed, inc the count upto a max of 255 TS_Pressed inc rSwitch ; Inc the count bne TSP_NC ; Not wrapped dec rSwitch ; Clip to 255 TSP_NC ld al,rSwitch ; Get it cp al,#70 ; Long? bcc TSS_Long ; Yes, skip cp al,#10 ; Short? bcc TSS_Short ; Yes, skip ; Clear the LED cbi PORTB,#1 ; LED OFF cbi PORTB,#2 ; LED OFF TS_X ld al,rSwTemp ; clc ; ret ; ; Show GREEN TSS_Short sbi PORTB,#1 ; cbi PORTB,#2 ; rjmp TS_X ; ; Show RED TSS_Long cbi PORTB,#1 ; sbi PORTB,#2 ; rjmp TS_X ; ; Motor ON. This drives the LED RED if the switch is NOT pressed. Motor_ON sbi PORTB,#0 ; Enable the driver LED_RED sbis PINB,#3 ; Skip if not pressed rjmp MO_X ; ; Show the motor state cbi PORTB,#1 ; LED := RED sbi PORTB,#2 ; MO_X ret ; ; Motor OFF. This drives the LED GREEN if the switch is NOT pressed. Motor_OFF cbi PORTB,#0 ; Disable the driver LED_GREEN sbis PINB,#3 ; Skip if not pressed rjmp MO_X ; sbi PORTB,#1 ; LED := GREEN cbi PORTB,#2 ; ret ; ; Setup the pins Setup_Pins ld al,#%111 00 0 0 0 ; All external pins start out low, except /reset out PORTB,al ; ld al,#%000 00 1 1 1 ; Set I/O out DDRB,al ; ret ; Done ; Index markers? This pulses the motor a number of times so the user ; can tell where they are in the setting list. ; This turned out to be confusing, so I disabled it. As a wise man once ; remarked: "Never confuse a woman who is on the point of orgasm... Ouch!" if bAddIndexF ; Do we want incdex markers? ; We do, so the macro calls a routine that pulses the motor a number of times mIndexMark macro(nPulses) ld cl,#nPulses ; rcall IndexMark ; mend else ; We don't so the macro is a dummy and does nothing... mIndexMark macro(nPulses) ; Just ignore the markers mend ; endif ; Handy macros from here on down... mPulse macro(MinSpeed,MaxSpeed,MinDuration,MaxDuration,JumpBack) ld rll,#MinSpeed ; Min speed ld rlh,#MaxSpeed ; Max speed ld rhl,#MinDuration ; Min duration ld rhh,#MaxDuration ; Max duration rcall Mode_Pulse ; Pulse bne JumpBack ; mend mSaw macro(InitialSpeed,FinalSpeed,Dirn,Rate,JumpBack) ld rll,#InitialSpeed ; Initial speed ld rlh,#FinalSpeed ; Final speed ld rhl,#Dirn ; Ramp direction ld rhh,#Rate ; Ramp rate rcall Mode_Saw ; Saw tooth bne JumpBack ; mend mSeeSaw macro(InitialSpeed,FinalSpeed,Dirn,Rate,JumpBack) ld rll,#InitialSpeed ; Initial speed ld rlh,#FinalSpeed ; Final speed ld rhl,#Dirn ; Ramp direction ld rhh,#Rate ; Ramp rate rcall Mode_SeeSaw ; SeeSaw bne JumpBack ; mend mSeq macro(SpaceSpeed,MarkSpeed,MinPeriod,MaxPeriod,JumpBack) ld rll,#SpaceSpeed ; 'Space' speed ld rlh,#MarkSpeed ; 'Mark' speed ld rhl,#MinPeriod ; Min period ld rhh,#MaxPeriod ; Max period rcall Mode_Seq ; Sequence bne JumpBack ; mend mVari macro(MinSpeed,MaxSpeed,MinPulseTime,RampRate,JumpBack) ld rll,#MinSpeed ; Min speed ld rlh,#MaxSpeed ; Max speed ld rhl,#MinPulseTime ; Min pulse time ld rhh,#RampRate ; Ramp rate rcall Mode_Vari ; Up the tempo bne JumpBack ; mend ; A longer delay, bit naff. ; ; Note the use of "referenced" here. This code isn't planted at all unless ; the label "LongWait" is used elsewhere in the code. ; "referenced" is useful in library code to minimise code size... ; if referenced LongWait ; Don't plant this unless it's used LongWait ld al,#0 ; rcall Wait_n100uS ; rcall Wait_n100uS ; rcall Wait_n100uS ; rcall Wait_n100uS ; rjmp Wait_n100uS ; endif ; Pulse the motor 'cl' times if referenced IndexMark ; Don't plant this unless it's used IndexMark rcall Motor_OFF ; Off rcall LongWait ; rcall Motor_ON ; rcall LongWait ; rcall Motor_OFF ; rcall LongWait ; rcall Motor_OFF ; dec cl ; Loop bne IndexMark ; ret ; Done endif ; Write the code out to a [shudder] intel hex file output_intel "c:\temp\vibe.hex",0,$400