;ACME 0.97 ;!sl "ddrv.l" ; Name DuoDriver ; Purpose Input driver for mouse and joystick ; Author (c) Marco Baye, 1999 ; Licence Free software ; Changes: ; 23 Apr 1999 Release 2.20. Internal info: ; DuoDriver v2.20 by Mac Bacon 23 Apr 1999. Freeware! ; Somewhen Added self-calibration, forming release 3.00. Internal info: ; Mac Bacon:DuoDrv3,PD ; 21 Jul 1999 Used reverse subtraction, forming release 3.01. Internal info: ; Mac Bacon:DuoDrv3,PD ; 1 Aug 1999 Release 4.00. ; Both 128 and 64 versions ; Now supports overlay-sprite mouse pointer ; Binary includes sprites ; Released in GO64 8/1999 (without release number). ; 3 Aug 1999 Same source file for both 128 and 64 versions. Release 4.01. ; Apart from that, virtually identical to release 4.00. ; 04 Feb 2003 Beautified ; 05 Feb 2003 Added "SpriteLine" macro and made sprites inline ; 26 May 2005 Release 4.02. All changes since release 4.00 are source-only! ; The resulting binaries are identical to those of release 4.00 ; (which were included in GO64 magazine 8/1999) ; 26 Mar 2006 Release 4.03. Adjusted source to ACME 0.91 capabilities. ; 25 Nov 2007 Release 4.04. Adjusted source to ACME 0.94 capabilities. ; 7 Apr 2013 Slightly reformatted. ; 1 Jun 2014 Adjusted to experimental type system of ACME 0.95 ; This source code file uses conditional assembly ; to decide which version to produce (C64 or C128). ; Select type of binary to assemble (64 => c64, anything else => c128) !ifndef SYSTEM { !warn "Label SYSTEM not defined. Use -DSYSTEM=64 to build C64 version, -DSYSTEM=128 to build C128 version. Will now default to C64 version." SYSTEM = 64 } !if SYSTEM != 64 & SYSTEM != 128 { !serious "Please use either -DSYSTEM=64 or -DSYSTEM=128 when assembling this project." } ; --- Configurable values ; Start address, output file name and VIC location !if SYSTEM = 64 { * = $c000 !to "ddrv64.prg", cbm !addr VIC_Base = $d000 } !if SYSTEM = 128 { * = $0c00 !to "ddrv128.prg", cbm !addr VIC_Base = $11d6 ; Location of mirror registers } ; Pointer's maximum coordinates MaximumCoordinateX = 319 ; VIC value ; MaximumCoordinateX = 639 ; VDC value MaximumCoordinateY = 199 ; Maximum pixel step size ("speed") for joystick acceleration routine. MaxStep = $10 ; max $7f ; Distance before acceleration starts, in pixels. MaxTime = $04 ; max $7f ; Sprites to use for overlay pointer Sprite_A = 0 Sprite_B = 1 ; Coordinates of "pointer pixel" within pointer sprites; adjust these ; if you use different sprites. (0,0) is sprite's upper left pixel. Sprite_HotspotX = 1 Sprite_HotspotY = 1 ; address definitions !addr { ; Locations to store button states, $ff = pressed, $00 = not pressed. ; Mouse uses both buttons, joystick only uses "LeftButton". ; Location to store pointer's current character coordinates. !if SYSTEM == 64 { LeftButton = $a4 RightButton = $a5 CharX = $b3 CharY = $b4 tapebuf = $0340 spr_ptrs = 2040 } !if SYSTEM == 128 { LeftButton = $fa RightButton = $ff CharX = $9b CharY = $9c } ; Location to store pointer's current pixel coordinates. The driver ; code relies on having *four consecutive* bytes: ; x low, x high, y low, y high Coordinates = $fb ; $fb..$fe ; --- System constants ; Interrupt vector sys_iirq = $0314 ; I/O registers sid_pot = $d419 cia1_pra = $dc00 cia1_prb = $dc01 cia1_ddrb = $dc03 mmu_cr = $ff00 ; c128 only ; dummy value for self mod MODIFIED16 = $ffff };addr ; --- Label definitions ; New names for some precalculated values, only to improve ; readability. Don't change these. PointerXnow = Coordinates PointerYnow = Coordinates + 2 SpriteA_X = VIC_Base + 2 * Sprite_A SpriteA_Y = VIC_Base + 2 * Sprite_A + 1 SpriteB_X = VIC_Base + 2 * Sprite_B SpriteB_Y = VIC_Base + 2 * Sprite_B + 1 Sprites_OF = VIC_Base + 16 ; X Overflow ; The character "^" in the following calculation means "to the power ; of". It is ACME syntax - if your assembler cannot do this, you may ; want to use hardcoded values here instead of calculations. Sprites_Bitmask = 2 ^ Sprite_A + 2 ^ Sprite_B ;alternative: ; Sprites_Bitmask = 1 << Sprite_A | 1 << Sprite_B SpriteOffset_X = $18 - Sprite_HotspotX SpriteOffset_Y = $32 - Sprite_HotspotY ; In the sprite coordinate system, the graphics pixel (0,0) has the ; coordinates ($18,$32), so these are needed for converting. Blame the ; VIC. ; --- Entry point ; Because this routine is the first, the file can be BOOTed on a c128. ; Initialisation code, installs driver on IRQ vector. ; Fetch IRQ vector and write to end Init lda sys_iirq ldx sys_iirq + 1 sta mod16 stx mod16 + 1 ; Let IRQ vector point to driver code lda #Entry php sei sta sys_iirq stx sys_iirq + 1 plp !if SYSTEM == 128 { lda mmu_cr tay and #$fe ; activate I/O chips sta mmu_cr } ; Init mouse buttons lda #$11 sta cia1_prb !if SYSTEM == 128 { sty mmu_cr } !if SYSTEM == 64 { ; Copy sprites to tape buffer ldx #127 - lda Sprites, x sta tapebuf, x dex bpl - lda #Sprites_Bitmask ; Set sprite block pointers ldx #$0d stx spr_ptrs + Sprite_A inx stx spr_ptrs + Sprite_B ; Activate pointer sprites ora VIC_Base + 21 sta VIC_Base + 21 } rts ; --- Variables ; Pixel counter before accelerating JoyWaittime !byte 0 ; --- Main code Entry ; The driver consists of several distinct parts. To minimise ; performance wastage, you should remove all parts you don't need for ; the specific application. ; --- Part 0, initialisations ; Make sure decimal mode is off cld ; Set button states to "not pressed", so the other parts only have to ; deal with setting them to "pressed". lda #$00 sta LeftButton sta RightButton ; --- Part 1, handling mouse movements ; mouse x ldx #$00 ; 0 means "x stuff" jsr PotDelta ; Now signed x movement is in A/Y. Add to current x value. clc adc PointerXnow sta PointerXnow tya adc PointerXnow + 1 sta PointerXnow + 1 ; mouse y ldx #$01 ; 1 means "y stuff" jsr PotDelta ; Now signed y movement is in A/Y. Mouse and computer use different y ; directions, so don't add to, but subtract from current y value. ; This is a reverse subtraction - it might be harder to understand, ; but it is both faster and smaller than the usual way. clc sbc PointerYnow eor #$ff sta PointerYnow tya sbc PointerYnow + 1 eor #$ff sta PointerYnow + 1 ; --- Part 2, handling mouse buttons ; Prepare CIA by setting bits to input ldy #$11 sty cia1_ddrb ldx #$ff ; $ff means "pressed" lda #$10 ; check left button bit cia1_prb bne + stx LeftButton ; store state + lda #$01 ; check right button bit cia1_prb bne + stx RightButton ; store state + ; Reset CIA to normal state ldy #$00 sty cia1_ddrb ; --- Part 3, handling the joystick ; Fetch byte holding direction flags lda cia1_pra tax ; ...and remember it ; Check 'up' direction ror bcs ++ ; Subtract current step size from y value if needed. tay sec lda PointerYnow sbc JoyStepsize sta PointerYnow bcs + dec PointerYnow + 1 + tya ++ ; Check 'down' direction ror bcs ++ ; Add current step size to y value if needed. tay ;clc ; C is always clear here lda PointerYnow adc JoyStepsize sta PointerYnow bcc + inc PointerYnow + 1 + tya ++ ; Check 'left' direction ror bcs ++ ; Subtract current step size from x value if needed. tay sec lda PointerXnow sbc JoyStepsize sta PointerXnow bcs + dec PointerXnow + 1 + tya ++ ; Check 'right' direction ror bcs ++ ; Add current step size to x value if needed. tay ;clc ; C is always clear here lda PointerXnow adc JoyStepsize sta PointerXnow bcc + inc PointerXnow + 1 + tya ++ ; --- Part 4, handling joystick button ror bcs + lda #$ff ; $ff means "pressed" sta LeftButton + ; --- Part 5, joystick acceleration ; Restore joystick direction bits and check whether to set speed to ; zero. txa and #$0f ; Clear unneeded bits cmp #$0f ; Any direction bit ? bne + ; No direction was used, so reset speed and wait counter to normal. lda #$01 sta JoyStepsize lda #MaxTime sta JoyWaittime jmp Part5End + ; A direction bit was used, so check whether to accelerate: If speed ; is already maximum speed, don't accelerate. JoyStepsize = * + 1 lda #$00 ; (self-modifying) ; If the variable "JoyStepsize" would have been defined as a separate ; location (using "!byte"), it would have taken a byte of memory. By ; storing the value inside an LDA command's argument, we save that one ; byte. It might make a difference. :) cmp #MaxStep ; If speed is max., bcs Part5End ; don't accelerate. ; Speed isn't maximum yet. Check whether ; we have to wait before accelerating. dec JoyWaittime bpl Part5End ; Counter has underrun, so accelerate. inc JoyWaittime ; reset counter inc JoyStepsize ; increase speed Part5End ; --- Part 6, restrict coordinate range ; restrict x value ldx #$00 ; 0 means "x stuff" jsr Restrict ; restrict y value ldx #$02 ; 2 means "y stuff" jsr Restrict ; --- Part 7, positioning sprites ; Set sprites' x positions lda PointerXnow clc adc #SpriteOffset_X sta SpriteA_X ; set both sprites sta SpriteB_X lda Sprites_OF ; get x overflow bcs SetOF ldx PointerXnow + 1 bne SetOF and #Sprites_Bitmask XOR $ff bcc StoreOF ; C is clear here SetOF ora #Sprites_Bitmask StoreOF sta Sprites_OF ; set x overflow ; Set sprites' y positions lda PointerYnow clc adc #SpriteOffset_Y sta SpriteA_Y sta SpriteB_Y ; The y value's high byte is useless in this case. ; --- Part 8, making char coordinates ; Convert x coordinate. There are different "best" routines for ; different resolutions, so I've given the VIC and VDC routines. lda PointerXnow lsr lsr lsr ldx PointerXnow + 1 ;ora OrTable,x ; VDC only (see below for data table) beq + ; VIC only ora #$20 ; VIC only + sta CharX ; Convert y coordinate. lda PointerYnow lsr lsr lsr sta CharY ; --- Add further parts here ; Here you can add further routines, for example to use the button ; states to fake keypresses etc. ; --- The end ; The initialisation routine sets the argument to the address of the ; previous IRQ routine. mod16 = * + 1: jmp MODIFIED16 ; (self-modifying) ; This table is for part 8. ;OrTable !byte 0, 32, 64 ; VDC only ; --- "Restrict" subroutine PointerXmax !word MaximumCoordinateX PointerYmax !word MaximumCoordinateY ; "y" word must follow directly after "x" word in memory. Restrict ; Restrict internal coordinates to configured range. Entry conditions: ; X is direction handle (0 = x, 2 = y) lda PointerXnow + 1, x bmi SetTo0 cmp PointerXmax + 1, x bcc Eosr bne + lda PointerXmax, x cmp PointerXnow, x bcs Eosr + lda PointerXmax, x ldy PointerXmax + 1, x jmp DefCo SetTo0 lda #0 tay DefCo sta PointerXnow, x sty PointerXnow + 1, x Eosr rts ; --- "Pot" subroutine ; This routine computes the mouse movements and therefore contains the ; self-calibration stuff and the other improvements over the standard ; 1351 driver. PotMax !word 0 ; max. POTs yet plus 1 ! PotMin !word $ffff ; lowest POTs yet PotOld !word 0 ; old values PotWidth !word 0 ; interval width HalfPotWidth !word 0 ; half width ; (buffered for speed increase) ; The above variables are not really words: The first byte is the x ; value, the second byte is the y value respectively. ; Compute the signed distance of mouse movement. ; Entry conditions: X is direction handle (0 = x, 1 = y) ; Exit conditions: A/Y are signed distance (low/high) ; First, get new value and clear "recalculate signal width" flag. PotDelta lda sid_pot, x ldy #$00 ; Check whether new value is lower than lowest known. cmp PotMin, x bcs + ; Store new "lowest" und set "recalculate signal width" flag. sta PotMin, x ldy #$ff + ; Check whether new value is higher than highest known. cmp PotMax, x bcc + ; Set "recalculate signal width" flag and store new "highest". ldy #$ff pha ; Remember current value adc #$00 ; Add one (C is set) sta PotMax, x ; Value $ff (0 after adding) means that there is no mouse connected, ; so reset min/max in that case. beq ResetMM ; Stack is untidy... pla ; Restore current value + ; If flag is set, recalculate signal width. iny ; Check flag bne ++ tay ; Buffer current value. lda PotMax,x ; Get highest + 1 sec ; Subtract lowest sbc PotMin, x bcc + sta PotWidth, x ; Store signal lsr ; width and half signal sta HalfPotWidth, x ; width + tya ; Restore current value. ++ ; Calculate distance tay ; Buffer current value. sec sbc PotOld, x pha tya sta PotOld, x pla beq zero ; If not moved, exit. bcc minus ; Negative difference ; Positive difference: ; Check whether movement caused a value wrap-around. cmp HalfPotWidth, x bcc Decrease beq Decrease ; It did, so calculate "real" distance and jump to exit ;sec ; C is always set here sbc PotWidth, x ; Fix distance ; We now know that the (fixed) distance is really negative, so we ; finally wipe out that annoying bit 0 noise by incrementing the ; value. Increase ;clc ; C is always clear here adc #$01 beq zero ; If increasing gives zero, jump to zero handler. ldy #$ff ; Set up high byte for negative values. rts ; Negative difference: ; Check whether movement caused a value wrap-around. minus eor #$ff ; Complement ; If we would do a real negation (by adding "1"), then we would need ; to branch using BCC *and* BEQ. So the above way might be harder to ; understand, but it is both shorter *and* faster - which I like. :) cmp HalfPotWidth, x eor #$ff ; Restore value bcc Increase ; Movement caused a value wrap-around, so calculate "real" distance and exit. clc adc PotWidth, x ; Fix distance ; We now know that the (fixed) distance is really positive, so we ; finally wipe out that annoying bit 0 noise by decrementing the value. Decrease sec sbc #$01 ; No difference or positive difference; both need zero as the high byte. zero ldy #0 rts ; If there is no mouse, reset "lowest" ("highest" will have been reset ; already) and return zero. ResetMM tay ; Set Y to zero. pla ; Tidy stack lda #$ff ; Reset "lowest" sta PotMin, x tya ; Return with A/Y = 0 rts ; --- Include sprites ; Because the c64 version copies the sprite data into the tape buffer ; on initialisation, the data is included right here. ; In the c128 version, we skip memory until we reach $0e00 - this is ; where the sprites are stored by default. !if SYSTEM == 128 { !align $ffff, $e00, $0 } !macro SpriteLine .v { !by .v >> 16, (.v >> 8) & 255, .v & 255 } Sprites ; 765432107654321076543210 +SpriteLine %........................ +SpriteLine %.#...................... +SpriteLine %.##..................... +SpriteLine %.###.................... +SpriteLine %.####................... +SpriteLine %.#####.................. +SpriteLine %.######................. +SpriteLine %.#######................ +SpriteLine %.########............... +SpriteLine %.#########.............. +SpriteLine %.########............... +SpriteLine %.######................. +SpriteLine %.######................. +SpriteLine %.##..##................. +SpriteLine %.#....##................ +SpriteLine %......##................ +SpriteLine %.......##............... +SpriteLine %.......##............... +SpriteLine %........##.............. +SpriteLine %........##.............. +SpriteLine %........................ !byte 0 ; pad to 64-byte block ; 765432107654321076543210 +SpriteLine %##...................... +SpriteLine %###..................... +SpriteLine %####.................... +SpriteLine %#####................... +SpriteLine %######.................. +SpriteLine %#######................. +SpriteLine %########................ +SpriteLine %#########............... +SpriteLine %##########.............. +SpriteLine %###########............. +SpriteLine %###########............. +SpriteLine %#########............... +SpriteLine %########................ +SpriteLine %########................ +SpriteLine %###..####............... +SpriteLine %##...####............... +SpriteLine %......####.............. +SpriteLine %......####.............. +SpriteLine %.......####............. +SpriteLine %.......####............. +SpriteLine %........###.............