User Tools

Site Tools


ctr_mouse_driver

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.

FIXME 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:

  1. Positive movement, no wrap
  2. positive movement, wrap
  3. Negative movement. no wrap
  4. 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…

1) , 2)
Two's Complement
ctr_mouse_driver.txt · Last modified: 2018/03/31 15:57 by 127.0.0.1