Table of Contents
Mouse Driver
By: TWW/Creators
This mouse driver was made to make a mouse routine which provide the maximum stability of the mouse while containing an easy syntax and having well documented features.
Port Interfacing and IO
The POTentiometers inside the mouse is connected to the SID chip at it's POT Registers located at memory address $d419 and $d41a. Only 1 mouse (or other analogue device) can be connected at one time due to only having two analogue inputs. Which Control Port is connected to the SID is decided by a 4066 analog switch located on the keyboard scan lines on CIA #1. To ensure valid input to the POT Registers on the SID, the lines must have been connected for a period of no less than 1.6 millisecond.
To decide if we will use Control Port #1 or Control Port #2, we use the two MSBs of $dd00 to select input like illustrated in the following code example:
.const ControlPort = 2 // Set CIA #1 Port A to Input lda #$ff sta $dc02 // Select port and enable RMB/LMB Lines lda $dc00 and #%00111111 .if (ControlPort == 2) { ora #%10010001 // #%10xxxxxx => Connect SID's AI ports to Control Port #2 (Gameport B / Joystick 2) and enable RMB/LMB Lines. } else { ora #%01010001 // #%01xxxxxx => Connect SID's AI ports to Control Port #1 (Gameport A / Joystick 1) and enable RMB/LMB Lines. } sta $dc00
It takes some cycles before the switch is made but this should not matter unless you modify (write to) $dc00 outside the Mouse Routine. We also enabled the two lines connected to LMB/RMB.
NOTE: If $dc00 is modified outside the Mouse Routine, it is imperative that the status of the bits and DDR are restores and sufficient time is allowed to pass before the mouse routine is called. |
---|
Potentiometer Values
The potentiometer values range from #%01000000 (#$40) to #%10111111 (#$bf) yielding a cyclic range span of #127 values. If no potentiometer is connected, reading the port will give #$ff which is a very simple way to detect if a potentiometer is present or not.
If we furtermore eliminate bit #7 byt a logical “and #$7f”, we get our values ranging from #%00000000 to #%01111111 (#$00 to #$7f).
The value will then be increasing or decreasing depending on which way you move the mouse and a the cyclic range is protected by a simple wrap around once you cross the minimum or maximum potentiometer value.
Naturally this range is too short to be used for a fullscreen pixel accurate mouse pointer routine so we need to make sure we handle wrap arounds in the correct way.
Other mouse routines uses something called “half delta” to calculate if the potentiometers have wrapped around. To account for the possible degraded feedback from the potentiometers, a “on the fly” calibration of the maximum and minimum potentiometer value is done and consequently the “half delta” is updated as well. It will not be incorporated into this mouse routine at this point as there are very few real mice left and even if there are, this behaviour must be further documented before implementing it here. It is trivial to incorporate though should the need arise.
NOTE: The Vertical potentiometer values are invertet and must be complemented before used in delta calculations. |
---|
The left and right mouse buttons are scanned in a simmular fashion one would check for a joystick firbutton.
Jitter Bit & Ignore Bit
Calculating the Movement
The way the potentiometers work leaves us with 4 different posibilities which we can handle without resulting to assumptions.
Posibilities:
- Positive movement, no wrap
- positive movement, wrap
- Negative movement. no wrap
- Negative movement, wrap
Example #1: Positive movement, no wrap
- Previous POT value: #$20
- Current POT value: #$30
- Current POT - Previous POT = #$10
Example #2: Positive movement, wrap
- Previous POT value: #$58
- Current POT value: #$08
- Maximum POT value: #$7f
- Maximum POT - Previous POT + Current POT = #$2f
Example #3: Negative movement, no wrap
- Previous POT value: #$50
- Current POT value: #$07
- Current POT - Previous POT = #$b71)
Example #4: Negative movement, wrap
- Previous POT value: #$08
- Current POT value: #$58
- Maximum POT value: #$7f
- #$00 - [Maximum POT - Current POT + Previous POT] = #$d12)
Alright, the next challenge is to reliably determine when the different scenarios occur. To do this, we need to look at Bit #6 of the previous and the current POT reading and by also reviewing the previous direction of movement, we can get the proper calculation done. Please review the following pseudo-code:
if NewPOT < #$40 { if OldPOT < #$40 { DeltaPOT = NewPOT - OldPOT } if OldPOT >= #$40 { if Direction = zero { DeltaPOT = 0 } if Direction = Negative { DeltaPOT = NewPOT - OldPOT } if Direction = Positive { DeltaPOT = 127 + NewPOT + OldPOT } } } if NewPOT >= #$40 { if OldPOT >= #$40 { DeltaPOT = NewPOT - OldPOT } if OldPOT < #$40 { if Direction = zero { DeltaPOT = 0 } if Direction = Negative { DeltaPOT = NewPOT - 127 - OldPOT } if Direction = Positive { DeltaPOT = NewPOT - OldPOT } } }
This means offcourse that the previous POT values and Movements must be stored to allow this comparison.
There is on problem with this approach, and this is inthe case where the last movement was zero. This has to be accounted for in the code. There are also some rare events where the negative movement is calculated to zero in which case we must also ensure that we don't take the two's complement of the value zero. All this is handled by simple BEQ's.
Limit Checking
To check if the mouse pointer is close to the edges, we first calculate the position into a virtual map which is 16 bits large. From there we center the mouse movement window into #$8000 and then add/subtract the signed movement. Finally, this value is converted to coordinates (and adjusted for any offset).
Why so comlicated? Well, if you test other mouse routines out there and move the pointer to the edge, and move id rapidly out of the legal area, you'll see how I didn't want this routine to work.
The Code
The following is the complete code for the mouse routine including a test suite. You'll need to make your own mouse pointer and in this case, a overlaid pointer is used. It's just a working version and it will need to be optimized for memory (has a lot of potential) and possible execution time. Furtermore it needs to be fitted with some initiation routine which let's you define the legal area and possible run-time port selection. Then the returned values must be defined and mos probably will be A, X and Y registers where Y = Y_POS (8 bit) and X:A = X_POS (16 bit).
.const Port = 1 .const CenterX = $8000 .const CenterY = $8000 .const RangeX = 320 .const RangeY = 200 .const OffsetX = 24 .const OffsetY = 50 .import source "lib2\IncludeFile.asm" :BASIC lda #$00 sta $d020 sta $d021 sei lda #$7f sta $dc0d sta $dd0d Begin: lda $d011 bpl *-3 lda $d011 bmi *-3 lda #30 cmp $d012 bne *-3 dec $d020 jsr mouse inc $d020 jsr Sprite jmp Begin mouse: //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Set up Ports lda #$ff sta $dc02 // Set CIA #1 Port A to Input lda $dc00 and #%00111111 .if (Port == 2) { ora #%10000000 // #%10xxxxxx => Connect SID Register POTs to Gameport B (Joy2) } else { ora #%01000000 // #%01xxxxxx => Connect SID Register POTs to Gameport A (Joy1) } sta $dc00 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Convert POTX Delta to Signed movement ldx #$00 // Set 16 bit Hi-Byte to zero lda $d419 // Read Horizontal POT and #%01111111 // Ignore MSB tay sta $02 // Check if current POT >= #$40 cmp #$40 // #$40 = cyclic range / 2 bcs NewPOT_Large // Branch if NewPot >= #$40 // Current POTX < #$40, compare with previous POTX lda PreviousPOTX cmp #$40 bcc NoWrap // Check if previous movement was zero, positive or negative lda PreviousDeltaX beq Done bmi NoWrap // Confirmed Wrap High -> Low; Positive Movement: [POTmax - POTold] + POTnew lda #$7f sec sbc PreviousPOTX clc adc $02 jmp Done NewPOT_Large: // Current POT >= #$40, compare with previous POTX lda PreviousPOTX cmp #$40 bcs NoWrap // Previous POTX < #$40 (Bit 7 Clear), POT changed from {#$00 <-> #$3f} to {#$40 <-> #$7f} // Check direction of last movement lda PreviousDeltaX beq Done bpl NoWrap // Confirmed Wrap Low -> High; Negative Movement: -([POTmax - POTnew] + POTold) lda #$7f sec sbc $02 clc adc PreviousPOTX beq Done // Complement Result eor #$ff clc adc #$01 dex jmp Done NoWrap: // Normal Signed Subtraction to find movement tya sec sbc PreviousPOTX // Compelement hi bits if negative bpl *+3 dex Done: sty PreviousPOTX sta PreviousDeltaX //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Update Virtual X Position !: clc adc VirtualPositionX sta VirtualPositionX txa adc VirtualPositionX+1 sta VirtualPositionX+1 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Check for limit X over/underflow // Are we below lower limit ($7f60) lda #>[CenterX-[RangeX/2]] // Hi-Byte cmp VirtualPositionX+1 bcc !+ // If PosHi <= $7f, check lobyte lda #<[CenterX-[RangeX/2]] // Lo-Byte cmp VirtualPositionX bcc !+ // If PosLo <= $60, Set minimumX lda #<[CenterX-[RangeX/2]] sta VirtualPositionX lda #>[CenterX-[RangeX/2]] sta VirtualPositionX+1 !: // Are we above higher limit ($80a0) lda VirtualPositionX+1 cmp #>[CenterX+[RangeX/2]]-1 // Hi-Byte bcc !+ // If $80 <= PosHi, Check Lo-Byte lda #<[CenterX+[RangeX/2]]-1 cmp VirtualPositionX bcs !+ // If PosLo > $a0, Set maximumX lda #<[CenterX+[RangeX/2]]-1 sta VirtualPositionX lda #>[CenterX+[RangeX/2]]-1 sta VirtualPositionX+1 !: //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Convert POTY Delta to Signed movement ldx #$00 // Set 16 bit Hi-Byte to zero lda $d41a // Read Horizontal POT eor #$ff adc #$01 and #%01111111 // Ignore MSB tay sta $02 // Check if current POT >= #$40 cmp #$40 // #$40 = cyclic range / 2 bcs NewPOT_LargeY // Branch if NewPot >= #$40 // Current POTX < #$40, compare with previous POTX lda PreviousPOTY cmp #$40 bcc NoWrapY // Check if previous movement was zero, positive or negative lda PreviousDeltaY beq DoneY bmi NoWrapY // Confirmed Wrap High -> Low; Positive Movement: [POTmax - POTold] + POTnew lda #$7f sec sbc PreviousPOTY clc adc $02 jmp DoneY NewPOT_LargeY: // Current POT >= #$40, compare with previous POTX lda PreviousPOTY cmp #$40 bcs NoWrapY // Previous POTX < #$40 (Bit 7 Clear), POT changed from {#$00 <-> #$3f} to {#$40 <-> #$7f} // Check direction of last movement lda PreviousDeltaY beq DoneY bpl NoWrapY // Confirmed Wrap Low -> High; Negative Movement: -([POTmax - POTnew] + POTold) lda #$7f sec sbc $02 clc adc PreviousPOTY beq DoneY // Complement Result eor #$ff clc adc #$01 dex jmp DoneY NoWrapY: // Normal Signed Subtraction to find movement tya sec sbc PreviousPOTY // Compelement hi bits if negative bpl *+3 dex DoneY: sty PreviousPOTY sta PreviousDeltaY //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Update Virtual Y Position !: clc adc VirtualPositionY sta VirtualPositionY txa adc VirtualPositionY+1 sta VirtualPositionY+1 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Check for limit Y over/underflow lda #>[CenterY-[RangeY/2]] cmp VirtualPositionY+1 bcc !+ lda #<[CenterY-[RangeY/2]] cmp VirtualPositionY bcc !+ lda #<[CenterY-[RangeY/2]] sta VirtualPositionY lda #>[CenterY-[RangeY/2]] sta VirtualPositionY+1 !: lda VirtualPositionY+1 cmp #>[CenterY+[RangeY/2]]-1 bcc !+ lda #<[CenterY+[RangeY/2]]-1 cmp VirtualPositionY bcs !+ lda #<[CenterY+[RangeY/2]]-1 sta VirtualPositionY lda #>[CenterY+[RangeY/2]]-1 sta VirtualPositionY+1 !: rts //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Show a sprite pointer Sprite: lda #$ff sta $d015 lda #%00000010 sta $d01c lda #$0f sta $d025 lda #$01 sta $d026 lda #$0b sta $d027 lda #$0c sta $d028 lda #[$2040/$40] sta $07f8 lda #[$2000/$40] sta $07f9 lda VirtualPositionX+1 sta $02 lda VirtualPositionX clc adc #OffsetX bcc !+ inc $02 !: adc #[RangeX/2] bcc !+ inc $02 !: sta $d000 sta $d002 lda $02 and #%00000001 beq !+ lda #%00000011 !: sta $d010 lda VirtualPositionY clc adc #OffsetY adc #[RangeY/2] sta $d001 sta $d003 rts //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Variables VirtualPositionX: .word CenterX VirtualPositionY: .word CenterY PreviousPOTX: .byte $00 PreviousPOTY: .byte $00 PreviousDeltaX: .byte $00 PreviousDeltaY: .byte $00 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .pc = $2000 "Sprite Pointer" .import binary "Sprites.raw"
Still has some issues to work out due to cases where you skipp past an entire half-POT and when the direction has changed since last scan. Really complicated stuff and really tricky compared to limiting movement to half-POT. To Be Continued…