]> git.sur5r.net Git - cc65/commitdiff
Added Inkwell lightpen drivers for the C64 and the C128.
authorGreg King <gregdk@users.sf.net>
Wed, 29 May 2013 23:48:45 +0000 (19:48 -0400)
committerGreg King <gregdk@users.sf.net>
Thu, 30 May 2013 00:02:29 +0000 (20:02 -0400)
They use the mouse driver programming interface.

Added a test program for lightpen drivers.  Now, it knows about only those two drivers; it will need updating when others are added.

asminc/c128.inc
asminc/c64.inc
include/c128.h
include/c64.h
libsrc/c128/mou/c128-inkwell.s [new file with mode: 0644]
libsrc/c64/mou/c64-inkwell.s [new file with mode: 0644]
testcode/lib/pen-test.c [new file with mode: 0644]

index 8ec72a1728416ea0e449b404868d58c41e190d77..1fb8b397c91601910bc5bb36b390a5d029e6678b 100644 (file)
@@ -1,5 +1,5 @@
 ;
-; C64 generic definitions. Stolen from Elite128
+; C128 generic definitions. Stolen from Elite128
 ;
 
 
@@ -32,7 +32,7 @@ FETVEC          := $2AA         ; Vector patch location for FETCH
 STASH           := $2AF         ; Stash routine in RAM
 STAVEC          := $2B9         ; Vector patch location for STASH
 PALFLAG         := $A03         ; $FF=PAL, $00=NTSC
-INIT_STATUS     := $A04         ; Flag: Reset/NMI Status
+INIT_STATUS     := $A04         ; Flags: Reset/Restore initiation status
 FKEY_LEN        := $1000        ; Function key lengths
 FKEY_TEXT       := $100A        ; Function key texts
 
@@ -102,6 +102,9 @@ VIC_CTRL2       := $D016
 
 VIC_HLINE       := $D012
 
+VIC_LPEN_X      := $D013
+VIC_LPEN_Y      := $D014
+
 VIC_VIDEO_ADR   := $D018
 
 VIC_IRR         := $D019        ; Interrupt request register
index 6823403c5fb4752af4f8c957974ed3d46b7e14dc..f5dbcf549ffd8cedff67b2b6591b43c901adcef3 100644 (file)
@@ -95,6 +95,9 @@ VIC_CTRL2       := $D016
 
 VIC_HLINE       := $D012
 
+VIC_LPEN_X      := $D013
+VIC_LPEN_Y      := $D014
+
 VIC_VIDEO_ADR   := $D018
 
 VIC_IRR         := $D019        ; Interrupt request register
@@ -207,4 +210,3 @@ TP_FAST         = $80           ; Switch Rossmoeller TurboProcess to fast mode
 
 RAMONLY         = $F8           ; (~(LORAM | HIRAM | IOEN)) & $FF
 
-
index 0b005bce0ea34d7775021a28132af6813524ae2c..2799f8e39da43bf74dcdc7b26645f5b7df4dc36f 100644 (file)
@@ -6,7 +6,7 @@
 /*                                                                           */
 /*                                                                           */
 /*                                                                           */
-/* (C) 1998-2009, Ullrich von Bassewitz                                      */
+/* (C) 1998-2013, Ullrich von Bassewitz                                      */
 /*                Roemerstrasse 52                                           */
 /*                D-70794 Filderstadt                                        */
 /* EMail:         uz@cc65.org                                                */
@@ -131,6 +131,7 @@ extern void c128_ptvjoy_joy[];
 extern void c128_stdjoy_joy[];  /* Referred to by joy_static_stddrv[] */
 extern void c128_1351_mous[];   /* Referred to by mouse_static_stddrv[] */
 extern void c128_joy_mou[];
+extern void c128_nkwll40_mou[];
 extern void c128_pot_mou[];
 extern void c128_swlink_ser[];
 extern void c128_vdc_tgi[];     /* Referred to by tgi_static_stddrv[] */
index 0a4ebaf971c5dc9082f0d930235a8814ae7d777d..adf3840b93a0a9c0f35dc6b240d991b6fd129839 100644 (file)
@@ -2,11 +2,11 @@
 /*                                                                           */
 /*                                   c64.h                                   */
 /*                                                                           */
-/*                      C64 system specific definitions                      */
+/*                      C64 system-specific definitions                      */
 /*                                                                           */
 /*                                                                           */
 /*                                                                           */
-/* (C) 1998-2010 Ullrich von Bassewitz                                       */
+/* (C) 1998-2013 Ullrich von Bassewitz                                       */
 /*               Roemerstrasse 52                                            */
 /*               D-70794 Filderstadt                                         */
 /* EMail:        uz@cc65.org                                                 */
@@ -145,6 +145,7 @@ extern void c64_ptvjoy_joy[];
 extern void c64_stdjoy_joy[];           /* Referred to by joy_static_stddrv[] */
 extern void c64_1351_mou[];             /* Referred to by mouse_static_stddrv[] */
 extern void c64_joy_mou[];
+extern void c64_inkwell_mou[];
 extern void c64_pot_mou[];
 extern void c64_swlink_ser[];
 extern void c64_hi_tgi[];               /* Referred to by tgi_static_stddrv[] */
diff --git a/libsrc/c128/mou/c128-inkwell.s b/libsrc/c128/mou/c128-inkwell.s
new file mode 100644 (file)
index 0000000..5625213
--- /dev/null
@@ -0,0 +1,552 @@
+;
+; Driver for the Inkwell Systems 170-C and 184-C lightpens.
+;
+; 2013-05-18, Greg King
+;
+
+        .include        "zeropage.inc"
+        .include        "mouse-kernel.inc"
+        .include        "c128.inc"
+
+        .macpack        generic
+        .macpack        cbm
+
+; ------------------------------------------------------------------------
+; Header. Includes jump table.
+
+.segment        "JUMPTABLE"
+
+HEADER:
+
+; Driver signature
+
+        .byte   $6d, $6f, $75           ; ASCII "mou"
+        .byte   MOUSE_API_VERSION       ; Mouse driver API version number
+
+; Jump table
+
+        .addr   INSTALL
+        .addr   UNINSTALL
+        .addr   HIDE
+        .addr   SHOW
+        .addr   SETBOX
+        .addr   GETBOX
+        .addr   MOVE
+        .addr   BUTTONS
+        .addr   POS
+        .addr   INFO
+        .addr   IOCTL
+        .addr   IRQ
+
+; Mouse driver flags
+
+        .byte   MOUSE_FLAG_EARLY_IRQ
+
+; Callback table, set by the kernel before INSTALL is called.
+
+CHIDE:  jmp     $0000                   ; Hide the cursor
+CSHOW:  jmp     $0000                   ; Show the cursor
+CMOVEX: jmp     $0000                   ; Move the cursor to X co-ord.
+CMOVEY: jmp     $0000                   ; Move the cursor to Y co-ord.
+
+
+;----------------------------------------------------------------------------
+; Constants
+
+; This driver is for the 40-column screen.
+
+XSIZE   = 40
+YSIZE   = 25
+
+SCREEN_HEIGHT   = YSIZE * 8
+SCREEN_WIDTH    = XSIZE * 8
+SCREEN_ZONE     = YSIZE / 4 * XSIZE
+
+SCREEN          := $0400
+COLOR_RAM       := $D800
+
+PRIMM           := $FF7D                ; Print immediate
+
+
+;----------------------------------------------------------------------------
+; Global variables. The bounding box values are sorted so that they can be
+; written with the least effort in the SETBOX and GETBOX routines; so, don't
+; re-order them.
+
+.bss
+
+Vars:
+XMin:           .res    2               ; X1 value of bounding box
+YMin:           .res    2               ; Y1 value of bounding box
+XMax:           .res    2               ; X2 value of bounding box
+YMax:           .res    2               ; Y2 value of bounding box
+Buttons:        .res    1               ; Button status bits
+
+XPos:           .res    2               ; Current lightpen position, X
+YPos:           .res    2               ; Current lightpen position, Y
+
+OldPenX:        .res    1               ; Old HW-counter values
+OldPenY:        .res    1
+
+INIT_save:      .res    1
+
+.data
+
+; Start with an average offset.
+
+XOffset:        .byte   48 / 2          ; Calibration offset, for X position
+
+.rodata
+
+; Default values for above variables
+; (We use ".proc" because we want to define both a label and a scope.)
+
+.proc   DefVars
+        .word   0                       ; XMin
+        .word   0                       ; YMin
+        .word   SCREEN_WIDTH - 1        ; XMax
+        .word   SCREEN_HEIGHT - 1       ; YMax
+        .byte   %00000000               ; Buttons
+.endproc
+
+.proc   Command1
+        scrcode "Adjust by clicking on line."
+.endproc
+.proc   Command2
+        scrcode "Finish by clicking off box."
+.endproc
+
+
+.code
+
+;----------------------------------------------------------------------------
+; INSTALL routine. Is called after the driver is loaded into memory. If
+; possible, check if the hardware is present.
+; Must return a MOUSE_ERR_xx code in .XA.
+
+INSTALL:
+
+; Disable the BASIC interpreter's interrupt-driven sprite-motion code.
+; That allows direct access to the VIC-IIe's sprite registers.
+
+        lda     INIT_STATUS
+        sta     INIT_save
+        lda     #%11000000
+        sta     INIT_STATUS
+
+        bit     MODE
+        bpl     @L7                     ; Already 40 columns
+        jsr     PRIMM
+        .byte   $1B, 'x'                ; Toggle the screen-width mode
+        .byte   $0E, $00                ; Switch to lower-case chars.
+
+; Initiate variables. Just copy the default stuff over.
+
+@L7:    sei
+        ldx     #.sizeof (DefVars) - 1
+@L0:    lda     DefVars,x
+        sta     Vars,x
+        dex
+        bpl     @L0
+
+        ldx     VIC_LPEN_X
+        ldy     VIC_LPEN_Y
+        stx     OldPenX
+        sty     OldPenY
+        cli
+
+; There is a delay between when the VIC sends its signal, and when the display
+; shows that signal.  There is another delay between the display and when
+; the lightpen says that it saw that signal. Each display and pen is different.
+; Therefore, this driver must be calibrated to them.  A white box is painted on
+; the screen; and, a line is drawn down the middle of it.  When the user clicks
+; on that line, the difference between its position and where the VIC thinks
+; that the pen is pointing becomes an offset that is subtracted from what the
+; VIC sees.
+
+        lda     VIC_BG_COLOR0
+        ldx     #6                      ; Blue screen
+        stx     VIC_BG_COLOR0
+        pha
+        jsr     CLRSCR
+
+        ldy     #.sizeof (Command2) - 1
+@L2:    lda     Command2,y
+        sta     SCREEN + SCREEN_ZONE * 2 + XSIZE * 3 + (XSIZE - .sizeof (Command2)) / 2,y
+        lda     #15                     ; Light gray text
+        sta     COLOR_RAM + SCREEN_ZONE * 2 + XSIZE * 3 + (XSIZE - .sizeof (Command1)) / 2,y
+        dey
+        bpl     @L2
+        ldy     #.sizeof (Command1) - 1
+@L1:    lda     Command1,y
+        sta     SCREEN + SCREEN_ZONE * 2 + XSIZE * 1 + (XSIZE - .sizeof (Command1)) / 2,y
+        lda     #15                     ; Light gray text
+        sta     COLOR_RAM + SCREEN_ZONE * 2 + XSIZE * 1 + (XSIZE - .sizeof (Command1)) / 2,y
+        dey
+        bpl     @L1
+
+        ldx     #SCREEN_ZONE
+@L3:    lda     #$80 | $20              ; Reversed space screen-code
+        sta     SCREEN + SCREEN_ZONE - 1,x
+        lda     #15                     ; Light gray box
+        sta     COLOR_RAM + SCREEN_ZONE - 1,x
+        dex
+        bnz     @L3
+
+        ldy     #$80 | $5d              ; Reversed vertical-bar screen-code
+        .repeat 4, L
+        sty     SCREEN + SCREEN_ZONE + (L + 1) * XSIZE + XSIZE / 2
+        .endrep
+
+        lda     VIC_SPR0_COLOR
+        ldx     #12                     ; Medium gray pointer
+        stx     VIC_SPR0_COLOR
+        pha
+        jsr     SHOW
+
+        lda     #<(SCREEN_HEIGHT / 4 / 2)
+        ldx     #>(SCREEN_HEIGHT / 4 / 2)
+        jsr     PutCursor
+
+; Wait for the main button to be released.
+
+@L4:    lda     Buttons
+        bnz     @L4
+
+; Wait for the main button to be pressed.
+
+@L5:    lda     Buttons
+        bze     @L5
+
+; Find out if the pen is on or off the box.
+
+        ldy     YPos
+        ldx     YPos+1
+        txa
+        cpy     #<(YSIZE / 4 * 1 * 8)
+        sbc     #>(YSIZE / 4 * 1 * 8)
+        bmi     @L6                     ; Above box
+        txa
+        cpy     #<(YSIZE / 4 * 2 * 8)
+        sbc     #>(YSIZE / 4 * 2 * 8)
+        bpl     @L6                     ; Below box
+
+; The pen is on the box; adjust the offset.
+
+        lda     OldPenX
+        sub     #(XSIZE * 8 / 2 + 8/2) / 2
+        sta     XOffset
+        sta     OldPenX                 ; Make IRQ update X co-ordinate
+        jmp     @L4
+
+; Wait for the main button to be released.
+
+@L6:    lda     Buttons
+        bnz     @L6
+
+        lda     XOffset                 ; Tell test program about calibration
+        sta     $3ff
+
+        pla
+        sta     VIC_SPR0_COLOR
+        pla
+        sta     VIC_BG_COLOR0
+        jsr     CLRSCR
+
+; Be sure the lightpen cursor is invisible and at the default location.
+; It needs to be done here because the lightpen interrupt handler doesn't
+; set the lightpen position if it hasn't changed.
+
+        jsr     HIDE
+
+        lda     #<(SCREEN_HEIGHT / 2)
+        ldx     #>(SCREEN_HEIGHT / 2)
+PutCursor:
+        sei
+        jsr     MoveY
+        lda     #<(SCREEN_WIDTH / 2)
+        ldx     #>(SCREEN_WIDTH / 2)
+        jsr     MoveX
+        cli
+
+; Done, return zero.
+
+        lda     #MOUSE_ERR_OK
+        tax
+        rts
+
+;----------------------------------------------------------------------------
+; UNINSTALL routine. Is called before the driver is removed from memory.
+; No return code required (the driver is removed from memory on return).
+
+UNINSTALL:
+        jsr     HIDE                    ; Hide cursor on exit
+        lda     INIT_save
+        sta     INIT_STATUS
+        rts
+
+;----------------------------------------------------------------------------
+; HIDE routine. Is called to hide the lightpen pointer. The mouse kernel manages
+; a counter for calls to show/hide, and the driver entry point is called only
+; if the mouse is currently visible, and should get hidden. For most drivers,
+; no special action is required besides hiding the lightpen cursor.
+; No return code required.
+
+HIDE:   sei
+        jsr     CHIDE
+        cli
+        rts
+
+;----------------------------------------------------------------------------
+; SHOW routine. Is called to show the lightpen pointer. The mouse kernel manages
+; a counter for calls to show/hide, and the driver entry point is called only
+; if the mouse is currently hidden, and should become visible. For most drivers,
+; no special action is required besides enabling the lightpen cursor.
+; No return code required.
+
+SHOW:   sei
+        jsr     CSHOW
+        cli
+        rts
+
+;----------------------------------------------------------------------------
+; SETBOX: Set the lightpen bounding box. The parameters are passed as they come
+; from the C program, that is, a pointer to a mouse_box struct in .XA.
+; No checks are done if the lightpen is currently inside the box, that is the job
+; of the caller. It is not necessary to validate the parameters; trust the
+; caller; and, save some code here. No return code required.
+
+SETBOX: sta     ptr1
+        stx     ptr1+1                  ; Save data pointer
+
+        ldy     #.sizeof (MOUSE_BOX) - 1
+        sei
+
+@L1:    lda     (ptr1),y
+        sta     XMin,y
+        dey
+        bpl     @L1
+
+        cli
+        rts
+
+;----------------------------------------------------------------------------
+; GETBOX: Return the lightpen bounding box. The parameters are passed as they
+; come from the C program, that is, a pointer to a mouse_box struct in .XA.
+
+GETBOX: sta     ptr1
+        stx     ptr1+1                  ; Save data pointer
+
+        ldy     #.sizeof (MOUSE_BOX) - 1
+@L1:    lda     XMin,y
+        sta     (ptr1),y
+        dey
+        bpl     @L1
+        rts
+
+;----------------------------------------------------------------------------
+; MOVE: Move the mouse to a new position. The position is passed as it comes
+; from the C program, that is: X on the stack and Y in .XA. The C wrapper will
+; remove the parameter from the stack on return.
+; No checks are done if the new position is valid (within the bounding box or
+; the screen). No return code required.
+;
+
+MOVE:   sei                             ; No interrupts
+        jsr     MoveY
+
+        ldy     #$01
+        lda     (sp),y
+        tax
+        dey
+        lda     (sp),y
+        jsr     MoveX                   ; Move the cursor
+
+        cli                             ; Allow interrupts
+        rts
+
+;----------------------------------------------------------------------------
+; BUTTONS: Return the button mask in .XA.
+
+BUTTONS:
+        lda     Buttons
+        ldx     #>0
+
+; Make the lightpen buttons look like a 1351 mouse.
+
+        asl     a
+        asl     SID_ADConv2             ; PotY
+        rol     a
+        eor     #MOUSE_BTN_RIGHT
+        and     #MOUSE_BTN_LEFT | MOUSE_BTN_RIGHT
+        rts
+
+;----------------------------------------------------------------------------
+; POS: Return the lightpen position in the MOUSE_POS struct pointed to by ptr1.
+; No return code required.
+
+POS:    ldy     #MOUSE_POS::XCOORD      ; Structure offset
+
+        sei                             ; Disable interrupts
+        lda     XPos                    ; Transfer the position
+        sta     (ptr1),y
+        lda     XPos+1
+        iny
+        sta     (ptr1),y
+        lda     YPos
+        iny
+        sta     (ptr1),y
+        lda     YPos+1
+        cli                             ; Enable interrupts
+
+        iny
+        sta     (ptr1),y                ; Store last byte
+        rts
+
+;----------------------------------------------------------------------------
+; INFO: Returns lightpen position and current button mask in the MOUSE_INFO
+; struct pointed to by ptr1. No return code required.
+;
+; We're cheating here, to keep the code smaller: The first fields of the
+; mouse_info struct are identical to the mouse_pos struct; so, we'll just
+; call _mouse_pos to initiate the struct pointer, and fill the position
+; fields.
+
+INFO:   jsr     POS
+
+; Fill in the button state
+
+        jsr     BUTTONS                 ; Will not touch ptr1
+        ldy     #MOUSE_INFO::BUTTONS
+        sta     (ptr1),y
+        rts
+
+;----------------------------------------------------------------------------
+; IOCTL: Driver-defined entry point. The wrapper will pass a pointer to ioctl-
+; specific data in ptr1, and the ioctl code in .A.
+; Must return an error code in .XA.
+;
+
+IOCTL:  lda     #<MOUSE_ERR_INV_IOCTL     ; We don't support ioctls, for now
+        ldx     #>MOUSE_ERR_INV_IOCTL
+        rts
+
+;----------------------------------------------------------------------------
+; IRQ: IRQ handler entry point. Called as a subroutine, but in the IRQ context
+; (so, be careful). The routine MUST return carry set if the interrupt has been
+; 'handled' -- which means that the interrupt source is gone. Otherwise, it
+; MUST return carry clear.
+;
+
+IRQ:
+
+; Record the state of the buttons.
+; Try to avoid crosstalk between the keyboard and the lightpen.
+
+        ldy     #%00000000              ; Set ports A and B to input
+        sty     CIA1_DDRB
+        sty     CIA1_DDRA               ; Keyboard won't look like buttons
+        lda     CIA1_PRB                ; Read Control-Port 1
+        dec     CIA1_DDRA               ; Set port A back to output
+        eor     #%11111111              ; Bit goes up when button goes down
+        sta     Buttons
+        bze     @L0
+        lda     #%11101111              ; (Don't change bit that feeds VIC-II)
+        sta     CIA1_DDRB               ; Buttons won't look like keyboard
+        sty     CIA1_PRB                ; Set "all keys pushed"
+
+; Read the VIC-II lightpen registers.
+
+@L0:    lda     VIC_LPEN_Y
+        cmp     OldPenY
+
+; Skip processing if nothing has changed.
+
+        beq     @SkipY
+        sta     OldPenY
+
+; Subtract the height of the top border, so that the lightpen co-ordinate
+; will match the TGI co-ordinate.
+
+        sub     #50
+        tay                             ; Remember low byte
+        ldx     #>0
+
+; Limit the Y co-ordinate to the bounding box.
+
+        txa
+        cpy     YMin
+        sbc     YMin+1
+        bpl     @L3
+        ldy     YMin
+        ldx     YMin+1
+        jmp     @L4
+
+@L3:    txa
+        cpy     YMax
+        sbc     YMax+1
+        bmi     @L4
+        ldy     YMax
+        ldx     YMax+1
+
+@L4:    tya
+        jsr     MoveY
+
+@SkipY: lda     VIC_LPEN_X
+        cmp     OldPenX
+
+; Skip processing if nothing has changed.
+
+        beq     @SkipX
+        sta     OldPenX
+
+; Adjust the co-ordinate by the calibration offset.
+
+        sub     XOffset
+
+; Calculate the new X co-ordinate (--> .AY --> .XY).
+; The VIC-II register is eight bits; but, the screen co-ordinate is nine bits.
+; Therefore, the VIC-II number is doubled. Then, it points to every other pixel;
+; but, it can reach across the screen.
+
+        asl     a
+        tay                             ; Remember low byte
+        lda     #>0
+        rol     a
+        tax
+
+; Limit the X co-ordinate to the bounding box.
+
+        cpy     XMin
+        sbc     XMin+1
+        bpl     @L1
+        ldy     XMin
+        ldx     XMin+1
+        jmp     @L2
+
+@L1:    txa
+        cpy     XMax
+        sbc     XMax+1
+        bmi     @L2
+        ldy     XMax
+        ldx     XMax+1
+
+@L2:    tya
+        jsr     MoveX
+
+; Done
+
+@SkipX: clc                             ; Interrupt not "handled"
+        rts
+
+; Move the lightpen pointer to the new Y pos.
+
+MoveY:  sta     YPos
+        stx     YPos+1
+        jmp     CMOVEY
+
+; Move the lightpen pointer to the new X pos.
+
+MoveX:  sta     XPos
+        stx     XPos+1
+        jmp     CMOVEX
diff --git a/libsrc/c64/mou/c64-inkwell.s b/libsrc/c64/mou/c64-inkwell.s
new file mode 100644 (file)
index 0000000..8c869d6
--- /dev/null
@@ -0,0 +1,526 @@
+;
+; Driver for the Inkwell Systems 170-C and 184-C lightpens.
+;
+; 2013-05-16, Greg King
+;
+
+        .include        "zeropage.inc"
+        .include        "mouse-kernel.inc"
+        .include        "c64.inc"
+
+        .macpack        generic
+        .macpack        cbm
+
+; ------------------------------------------------------------------------
+; Header. Includes jump table.
+
+.segment        "JUMPTABLE"
+
+HEADER:
+
+; Driver signature
+
+        .byte   $6d, $6f, $75           ; ASCII "mou"
+        .byte   MOUSE_API_VERSION       ; Mouse driver API version number
+
+; Jump table
+
+        .addr   INSTALL
+        .addr   UNINSTALL
+        .addr   HIDE
+        .addr   SHOW
+        .addr   SETBOX
+        .addr   GETBOX
+        .addr   MOVE
+        .addr   BUTTONS
+        .addr   POS
+        .addr   INFO
+        .addr   IOCTL
+        .addr   IRQ
+
+; Mouse driver flags
+
+        .byte   MOUSE_FLAG_EARLY_IRQ
+
+; Callback table, set by the kernel before INSTALL is called.
+
+CHIDE:  jmp     $0000                   ; Hide the cursor
+CSHOW:  jmp     $0000                   ; Show the cursor
+CMOVEX: jmp     $0000                   ; Move the cursor to X co-ord.
+CMOVEY: jmp     $0000                   ; Move the cursor to Y co-ord.
+
+
+;----------------------------------------------------------------------------
+; Constants
+
+SCREEN_HEIGHT   = YSIZE * 8
+SCREEN_WIDTH    = XSIZE * 8
+SCREEN_ZONE     = YSIZE / 4 * XSIZE
+
+; This driver is for the standard 40-column screen.
+
+SCREEN          := $0400
+COLOR_RAM       := $D800
+
+
+;----------------------------------------------------------------------------
+; Global variables. The bounding box values are sorted so that they can be
+; written with the least effort in the SETBOX and GETBOX routines; so, don't
+; re-order them.
+
+.bss
+
+Vars:
+XMin:           .res    2               ; X1 value of bounding box
+YMin:           .res    2               ; Y1 value of bounding box
+XMax:           .res    2               ; X2 value of bounding box
+YMax:           .res    2               ; Y2 value of bounding box
+Buttons:        .res    1               ; Button status bits
+XPos:           .res    2               ; Current lightpen position, X
+YPos:           .res    2               ; Current lightpen position, Y
+
+OldPenX:        .res    1               ; Old HW-counter values
+OldPenY:        .res    1
+
+.data
+
+; Start with an average offset.
+
+XOffset:        .byte   48 / 2          ; Calibration offset, for X position
+
+.rodata
+
+; Default values for above variables
+; (We use ".proc" because we want to define both a label and a scope.)
+
+.proc   DefVars
+        .word   0                       ; XMin
+        .word   0                       ; YMin
+        .word   SCREEN_WIDTH - 1        ; XMax
+        .word   SCREEN_HEIGHT - 1       ; YMax
+        .byte   %00000000               ; Buttons
+.endproc
+
+.proc   Command1
+        scrcode "Adjust by clicking on line."
+.endproc
+.proc   Command2
+        scrcode "Finish by clicking off box."
+.endproc
+
+
+.code
+
+;----------------------------------------------------------------------------
+; INSTALL routine. Is called after the driver is loaded into memory. If
+; possible, check if the hardware is present.
+; Must return a MOUSE_ERR_xx code in .XA.
+
+INSTALL:
+
+; Initiate variables. Just copy the default stuff over.
+
+        sei
+        ldx     #.sizeof (DefVars) - 1
+@L0:    lda     DefVars,x
+        sta     Vars,x
+        dex
+        bpl     @L0
+
+        ldx     VIC_LPEN_X
+        ldy     VIC_LPEN_Y
+        stx     OldPenX
+        sty     OldPenY
+        cli
+
+; There is a delay between when the VIC sends its signal, and when the display
+; shows that signal.  There is another delay between the display and when
+; the lightpen says that it saw that signal. Each display and pen is different.
+; Therefore, this driver must be calibrated to them.  A white box is painted on
+; the screen; and, a line is drawn down the middle of it.  When the user clicks
+; on that line, the difference between its position and where the VIC thinks
+; that the pen is pointing becomes an offset that is subtracted from what the
+; VIC sees.
+
+        lda     VIC_BG_COLOR0
+        ldx     #6                      ; Blue screen
+        stx     VIC_BG_COLOR0
+        pha
+        jsr     CLRSCR
+
+        ldy     #.sizeof (Command2) - 1
+@L2:    lda     Command2,y
+        sta     SCREEN + SCREEN_ZONE * 2 + XSIZE * 3 + (XSIZE - .sizeof (Command2)) / 2,y
+        lda     #15                     ; Light gray text
+        sta     COLOR_RAM + SCREEN_ZONE * 2 + XSIZE * 3 + (XSIZE - .sizeof (Command1)) / 2,y
+        dey
+        bpl     @L2
+        ldy     #.sizeof (Command1) - 1
+@L1:    lda     Command1,y
+        sta     SCREEN + SCREEN_ZONE * 2 + XSIZE * 1 + (XSIZE - .sizeof (Command1)) / 2,y
+        lda     #15                     ; Light gray text
+        sta     COLOR_RAM + SCREEN_ZONE * 2 + XSIZE * 1 + (XSIZE - .sizeof (Command1)) / 2,y
+        dey
+        bpl     @L1
+
+        ldx     #SCREEN_ZONE
+@L3:    lda     #$80 | $20              ; Reversed space screen-code
+        sta     SCREEN + SCREEN_ZONE - 1,x
+        lda     #15                     ; Light gray box
+        sta     COLOR_RAM + SCREEN_ZONE - 1,x
+        dex
+        bnz     @L3
+
+        ldy     #$80 | $5d              ; Reversed vertical-bar screen-code
+        .repeat 4, L
+        sty     SCREEN + SCREEN_ZONE + (L + 1) * XSIZE + XSIZE / 2
+        .endrep
+
+        lda     VIC_SPR0_COLOR
+        ldx     #12                     ; Medium gray pointer
+        stx     VIC_SPR0_COLOR
+        pha
+        jsr     SHOW
+
+        lda     #<(SCREEN_HEIGHT / 4 / 2)
+        ldx     #>(SCREEN_HEIGHT / 4 / 2)
+        jsr     PutCursor
+
+; Wait for the main button to be released.
+
+@L4:    lda     Buttons
+        bnz     @L4
+
+; Wait for the main button to be pressed.
+
+@L5:    lda     Buttons
+        bze     @L5
+
+; Find out if the pen is on or off the box.
+
+        ldy     YPos
+        ldx     YPos+1
+        txa
+        cpy     #<(YSIZE / 4 * 1 * 8)
+        sbc     #>(YSIZE / 4 * 1 * 8)
+        bmi     @L6                     ; Above box
+        txa
+        cpy     #<(YSIZE / 4 * 2 * 8)
+        sbc     #>(YSIZE / 4 * 2 * 8)
+        bpl     @L6                     ; Below box
+
+; The pen is on the box; adjust the offset.
+
+        lda     OldPenX
+        sub     #(XSIZE * 8 / 2 + 8/2) / 2
+        sta     XOffset
+        sta     OldPenX                 ; Make IRQ update X co-ordinate
+        jmp     @L4
+
+; Wait for the main button to be released.
+
+@L6:    lda     Buttons
+        bnz     @L6
+
+        lda     XOffset                 ; Tell test program about calibration
+        sta     $3ff
+
+        pla
+        sta     VIC_SPR0_COLOR
+        pla
+        sta     VIC_BG_COLOR0
+        jsr     CLRSCR
+
+; Be sure the lightpen cursor is invisible and at the default location.
+; It needs to be done here because the lightpen interrupt handler doesn't
+; set the lightpen position if it hasn't changed.
+
+        jsr     HIDE
+
+        lda     #<(SCREEN_HEIGHT / 2)
+        ldx     #>(SCREEN_HEIGHT / 2)
+PutCursor:
+        sei
+        jsr     MoveY
+        lda     #<(SCREEN_WIDTH / 2)
+        ldx     #>(SCREEN_WIDTH / 2)
+        jsr     MoveX
+        cli
+
+; Done, return zero.
+
+        lda     #MOUSE_ERR_OK
+        tax
+        rts
+
+;----------------------------------------------------------------------------
+; UNINSTALL routine. Is called before the driver is removed from memory.
+; No return code required (the driver is removed from memory on return).
+
+UNINSTALL       := HIDE                 ; Hide cursor on exit
+
+;----------------------------------------------------------------------------
+; HIDE routine. Is called to hide the lightpen pointer. The mouse kernel manages
+; a counter for calls to show/hide, and the driver entry point is called only
+; if the mouse is currently visible, and should get hidden. For most drivers,
+; no special action is required besides hiding the lightpen cursor.
+; No return code required.
+
+HIDE:   sei
+        jsr     CHIDE
+        cli
+        rts
+
+;----------------------------------------------------------------------------
+; SHOW routine. Is called to show the lightpen pointer. The mouse kernel manages
+; a counter for calls to show/hide, and the driver entry point is called only
+; if the mouse is currently hidden, and should become visible. For most drivers,
+; no special action is required besides enabling the lightpen cursor.
+; No return code required.
+
+SHOW:   sei
+        jsr     CSHOW
+        cli
+        rts
+
+;----------------------------------------------------------------------------
+; SETBOX: Set the lightpen bounding box. The parameters are passed as they come
+; from the C program, that is, a pointer to a mouse_box struct in .XA.
+; No checks are done if the lightpen is currently inside the box, that is the job
+; of the caller. It is not necessary to validate the parameters; trust the
+; caller; and, save some code here. No return code required.
+
+SETBOX: sta     ptr1
+        stx     ptr1+1                  ; Save data pointer
+
+        ldy     #.sizeof (MOUSE_BOX) - 1
+        sei
+
+@L1:    lda     (ptr1),y
+        sta     XMin,y
+        dey
+        bpl     @L1
+
+        cli
+        rts
+
+;----------------------------------------------------------------------------
+; GETBOX: Return the lightpen bounding box. The parameters are passed as they
+; come from the C program, that is, a pointer to a mouse_box struct in .XA.
+
+GETBOX: sta     ptr1
+        stx     ptr1+1                  ; Save data pointer
+
+        ldy     #.sizeof (MOUSE_BOX) - 1
+@L1:    lda     XMin,y
+        sta     (ptr1),y
+        dey
+        bpl     @L1
+        rts
+
+;----------------------------------------------------------------------------
+; MOVE: Move the mouse to a new position. The position is passed as it comes
+; from the C program, that is: X on the stack and Y in .XA. The C wrapper will
+; remove the parameter from the stack on return.
+; No checks are done if the new position is valid (within the bounding box or
+; the screen). No return code required.
+;
+
+MOVE:   sei                             ; No interrupts
+        jsr     MoveY
+
+        ldy     #$01
+        lda     (sp),y
+        tax
+        dey
+        lda     (sp),y
+        jsr     MoveX                   ; Move the cursor
+
+        cli                             ; Allow interrupts
+        rts
+
+;----------------------------------------------------------------------------
+; BUTTONS: Return the button mask in .XA.
+
+BUTTONS:
+        lda     Buttons
+        ldx     #>0
+
+; Make the lightpen buttons look like a 1351 mouse.
+
+        asl     a
+        asl     SID_ADConv2             ; PotY
+        rol     a
+        eor     #MOUSE_BTN_RIGHT
+        and     #MOUSE_BTN_LEFT | MOUSE_BTN_RIGHT
+        rts
+
+;----------------------------------------------------------------------------
+; POS: Return the lightpen position in the MOUSE_POS struct pointed to by ptr1.
+; No return code required.
+
+POS:    ldy     #MOUSE_POS::XCOORD      ; Structure offset
+
+        sei                             ; Disable interrupts
+        lda     XPos                    ; Transfer the position
+        sta     (ptr1),y
+        lda     XPos+1
+        iny
+        sta     (ptr1),y
+        lda     YPos
+        iny
+        sta     (ptr1),y
+        lda     YPos+1
+        cli                             ; Enable interrupts
+
+        iny
+        sta     (ptr1),y                ; Store last byte
+        rts
+
+;----------------------------------------------------------------------------
+; INFO: Returns lightpen position and current button mask in the MOUSE_INFO
+; struct pointed to by ptr1. No return code required.
+;
+; We're cheating here, to keep the code smaller: The first fields of the
+; mouse_info struct are identical to the mouse_pos struct; so, we'll just
+; call _mouse_pos to initiate the struct pointer, and fill the position
+; fields.
+
+INFO:   jsr     POS
+
+; Fill in the button state
+
+        jsr     BUTTONS                 ; Will not touch ptr1
+        ldy     #MOUSE_INFO::BUTTONS
+        sta     (ptr1),y
+        rts
+
+;----------------------------------------------------------------------------
+; IOCTL: Driver-defined entry point. The wrapper will pass a pointer to ioctl-
+; specific data in ptr1, and the ioctl code in .A.
+; Must return an error code in .XA.
+;
+
+IOCTL:  lda     #<MOUSE_ERR_INV_IOCTL     ; We don't support ioctls, for now
+        ldx     #>MOUSE_ERR_INV_IOCTL
+        rts
+
+;----------------------------------------------------------------------------
+; IRQ: IRQ handler entry point. Called as a subroutine, but in the IRQ context
+; (so, be careful). The routine MUST return carry set if the interrupt has been
+; 'handled' -- which means that the interrupt source is gone. Otherwise, it
+; MUST return carry clear.
+;
+
+IRQ:
+
+; Record the state of the buttons.
+; Try to avoid crosstalk between the keyboard and the lightpen.
+
+        ldy     #%00000000              ; Set ports A and B to input
+        sty     CIA1_DDRB
+        sty     CIA1_DDRA               ; Keyboard won't look like buttons
+        lda     CIA1_PRB                ; Read Control-Port 1
+        dec     CIA1_DDRA               ; Set port A back to output
+        eor     #%11111111              ; Bit goes up when button goes down
+        sta     Buttons
+        bze     @L0
+        lda     #%11101111              ; (Don't change bit that feeds VIC-II)
+        sta     CIA1_DDRB               ; Buttons won't look like keyboard
+        sty     CIA1_PRB                ; Set "all keys pushed"
+
+; Read the VIC-II lightpen registers.
+
+@L0:    lda     VIC_LPEN_Y
+        cmp     OldPenY
+
+; Skip processing if nothing has changed.
+
+        beq     @SkipY
+        sta     OldPenY
+
+; Subtract the height of the top border, so that the lightpen co-ordinate
+; will match the TGI co-ordinate.
+
+        sub     #50
+        tay                             ; Remember low byte
+        ldx     #>0
+
+; Limit the Y co-ordinate to the bounding box.
+
+        txa
+        cpy     YMin
+        sbc     YMin+1
+        bpl     @L3
+        ldy     YMin
+        ldx     YMin+1
+        jmp     @L4
+
+@L3:    txa
+        cpy     YMax
+        sbc     YMax+1
+        bmi     @L4
+        ldy     YMax
+        ldx     YMax+1
+
+@L4:    tya
+        jsr     MoveY
+
+@SkipY: lda     VIC_LPEN_X
+        cmp     OldPenX
+
+; Skip processing if nothing has changed.
+
+        beq     @SkipX
+        sta     OldPenX
+
+; Adjust the co-ordinate by the calibration offset.
+
+        sub     XOffset
+
+; Calculate the new X co-ordinate (--> .AY --> .XY).
+; The VIC-II register is eight bits; but, the screen co-ordinate is nine bits.
+; Therefor, the VIC-II number is doubled. Then, it points to every other pixel;
+; but, it can reach across the screen.
+
+        asl     a
+        tay                             ; Remember low byte
+        lda     #>0
+        rol     a
+        tax
+
+; Limit the X co-ordinate to the bounding box.
+
+        cpy     XMin
+        sbc     XMin+1
+        bpl     @L1
+        ldy     XMin
+        ldx     XMin+1
+        jmp     @L2
+
+@L1:    txa
+        cpy     XMax
+        sbc     XMax+1
+        bmi     @L2
+        ldy     XMax
+        ldx     XMax+1
+
+@L2:    tya
+        jsr     MoveX
+
+; Done
+
+@SkipX: clc                             ; Interrupt not "handled"
+        rts
+
+; Move the lightpen pointer to the new Y pos.
+
+MoveY:  sta     YPos
+        stx     YPos+1
+        jmp     CMOVEY
+
+; Move the lightpen pointer to the new X pos.
+
+MoveX:  sta     XPos
+        stx     XPos+1
+        jmp     CMOVEX
diff --git a/testcode/lib/pen-test.c b/testcode/lib/pen-test.c
new file mode 100644 (file)
index 0000000..ed1d75a
--- /dev/null
@@ -0,0 +1,282 @@
+/*
+** Test program for lightpen drivers. Will work for the C64/C128.
+**
+** 2001-09-13, Ullrich von Bassewitz
+** 2013-05-24, Greg King
+**
+*/
+
+
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <mouse.h>
+#include <conio.h>
+#include <ctype.h>
+#include <dbg.h>
+
+#define max(a,b)  (((a) > (b)) ? (a) : (b))
+#define min(a,b)  (((a) < (b)) ? (a) : (b))
+
+
+
+/* Statically linked driver */
+#define DYN_DRV         0
+
+
+
+#ifdef __C64__
+#  if DYN_DRV
+#    define mouse_stddrv "c64-inkwell.mou"
+#  else
+#    define mouse_static_stddrv c64_inkwell_mou
+#  endif
+#endif
+
+#ifdef __C128__
+#  if DYN_DRV
+#    define mouse_stddrv "c128-inkwell.mou"
+#  else
+#    define mouse_static_stddrv c128_inkwell_mou
+#  endif
+#endif
+
+
+
+#if defined(__C64__) || defined(__C128__)
+
+/* Addresses of data for sprite 0 */
+#if defined(__C64__)
+#  define SPRITE0_DATA  ((unsigned char[64])0x0340)
+#  define SPRITE0_PTR   ((unsigned char *)0x07F8)
+#elif defined(__C128__)
+#  define SPRITE0_DATA  ((unsigned char[64])0x0E00)
+#  define SPRITE0_PTR   ((unsigned char *)0x07F8)
+#endif
+
+/* The lightpen sprite (an arrow) */
+static const unsigned char PenSprite[64] = {
+    0xFF, 0xFF, 0xFF,
+    0xFC, 0x00, 0x00,
+    0xF8, 0x00, 0x00,
+    0xFC, 0x00, 0x00,
+    0xDE, 0x00, 0x00,
+    0x8F, 0x00, 0x00,
+    0x87, 0x80, 0x00,
+    0x83, 0xC0, 0x00,
+    0x81, 0xE0, 0x00,
+    0x80, 0xF0, 0x00,
+    0x80, 0x78, 0x00,
+    0x80, 0x38, 0x00,
+    0x80, 0x00, 0x00,
+    0x80, 0x00, 0x00,
+    0x80, 0x00, 0x00,
+    0x80, 0x00, 0x00,
+    0x80, 0x00, 0x00,
+    0x80, 0x00, 0x00,
+    0x80, 0x00, 0x00,
+    0x80, 0x00, 0x00,
+    0x80, 0x00, 0x00
+};
+
+#endif  /* __C64__ or __C128__ */
+
+
+
+static void __fastcall__ CheckError (const char* S, unsigned char Error)
+{
+    if (Error != MOUSE_ERR_OK) {
+        cprintf ("%s: %s(%u)\r\n", S, mouse_geterrormsg (Error), Error);
+        exit (EXIT_FAILURE);
+    }
+}
+
+#if DYN_DRV
+static void DoWarning (void)
+/* Warn the user that a lightpen driver is needed for this program. */
+{
+    cprintf ("Warning: This program needs a lightpen\r\n"
+             "driver with the name\r\n"
+             "    %s\r\n"
+             "on disk! Press 'y' if you have it; or,\r\n"
+             "any other key to exit.\r\n", mouse_stddrv);
+    if (tolower (cgetc ()) != 'y') {
+        exit (EXIT_SUCCESS);
+    }
+    cprintf ("OK. Please wait patiently...\r\n");
+}
+#endif
+
+
+
+static void __fastcall__ ShowState (unsigned char Jailed, unsigned char Invisible)
+/* Display jail and cursor states. */
+{
+    cclearxy (0, 7, 40);
+    gotoxy (0, 7);
+    cprintf ("Lightpen cursor is %svisible%s.", Invisible? "in" : "", Jailed? " and jailed" : "");
+}
+
+
+
+int main (void)
+{
+    struct mouse_info info;
+    struct mouse_box full_box, small_box;
+    char C;
+    unsigned char width, height;
+    bool Invisible = false, Done = false, Jailed = false;
+
+    /* Only the VIC-II has a lightpen connection. */
+#ifdef __C128__
+    videomode (VIDEOMODE_40x25);
+#endif
+
+    /* Initiate the debugger. */
+    DbgInit (0);
+
+    /* Set dark-on-light colors.  Clear the screen. */
+#ifdef __CBM__
+    (void) bordercolor (COLOR_GRAY2);
+    (void) bgcolor (COLOR_WHITE);
+    (void) textcolor (COLOR_GRAY1);
+#else
+    (void) bordercolor (COLOR_BLUE);
+    (void) bgcolor (COLOR_WHITE);
+    (void) textcolor (COLOR_BLACK);
+#endif
+    cursor (0);
+    clrscr ();
+
+#if defined(__C64__) || defined(__C128__)
+    /* Copy the sprite data. */
+    memcpy (SPRITE0_DATA, PenSprite, sizeof PenSprite);
+
+    /* Set the VIC-II sprite pointer. */
+    *SPRITE0_PTR = (unsigned) SPRITE0_DATA / sizeof SPRITE0_DATA;
+
+    /* Set the color of sprite 0. */
+    VIC.spr0_color = COLOR_BLACK;
+#endif
+
+#if DYN_DRV
+    /* Output a warning about the driver that is needed. */
+    DoWarning ();
+
+    /* Load and install the lightpen driver. */
+    CheckError ("mouse_load_driver",
+                mouse_load_driver (&mouse_def_callbacks, mouse_stddrv));
+#else
+    /* Install the lightpen driver. */
+    CheckError ("mouse_install",
+                mouse_install (&mouse_def_callbacks, mouse_static_stddrv));
+#endif
+
+    /* Get the initial lightpen bounding box. */
+    mouse_getbox (&full_box);
+
+    screensize (&width, &height);
+
+top:
+    /* Print a help line. */
+    clrscr ();
+/*    revers (1); */
+    cputs (" d)ebug  h)ide   q)uit   s)how   j)ail   ");
+/*    revers (0); */
+
+    /* Put a cross at the center of the screen. */
+    gotoxy (width / 2 - 3, height / 2 - 1);
+    cprintf ("%3u,%3u\r\n%*s\xDB", width / 2 * 8 + 4, height / 2 * 8 + 4,
+             width / 2, "");
+
+    /* Custom driver debugging line */
+    cprintf ("\n\r\n Calibration X-offset = %u", (*(unsigned char *)0x3ff) * 2);
+
+    /* Expose the arrow. */
+    mouse_show ();
+    ShowState (Jailed, Invisible);
+
+    /* Test loop */
+    do {
+        /* Get the current lightpen co-ordinates and button states;
+        ** and, print them.
+        */
+        mouse_info (&info);
+        gotoxy (0, 2);
+        cprintf (" X  = %3d\r\n", info.pos.x);
+        cprintf (" Y  = %3d\r\n", info.pos.y);
+        cprintf (" B1 = %c\r\n", (info.buttons & MOUSE_BTN_LEFT) ? 0x5F : '^');
+        cprintf (" B2 = %c", (info.buttons & MOUSE_BTN_RIGHT) ? 0x5F : '^');
+
+        /* Handle user input. */
+        if (kbhit ()) {
+            cclearxy (1, 9, 23);
+            switch (tolower (C = cgetc ())) {
+                case 'd':
+                    BREAK();
+
+                    /* The debugger might have changed the colors.
+                    ** Restore them.
+                    */
+#ifdef __CBM__
+                    (void) bordercolor (COLOR_GRAY2);
+                    (void) bgcolor (COLOR_WHITE);
+                    (void) textcolor (COLOR_GRAY1);
+#else
+                    (void) bordercolor (COLOR_BLUE);
+                    (void) bgcolor (COLOR_WHITE);
+                    (void) textcolor (COLOR_BLACK);
+#endif
+                    goto top;
+
+                case 'h':
+                    mouse_hide ();
+                    ShowState (Jailed, ++Invisible);
+                    break;
+
+                case 'j':
+                    if (Jailed) {
+                        mouse_setbox (&full_box);
+                        Jailed = false;
+                    } else {
+                        small_box.minx = max (info.pos.x - 10, full_box.minx);
+                        small_box.miny = max (info.pos.y - 10, full_box.miny);
+                        small_box.maxx = min (info.pos.x + 10, full_box.maxx);
+                        small_box.maxy = min (info.pos.y + 10, full_box.maxy);
+                        mouse_setbox (&small_box);
+                        Jailed = true;
+                    }
+                    ShowState (Jailed, Invisible);
+                    break;
+
+                case 's':
+                    mouse_show ();
+                    if (Invisible) {
+                        ShowState (Jailed, --Invisible);
+                    }
+                    break;
+
+                case 'q':
+                    Done = true;
+                    break;
+
+                default:
+                    gotoxy (1, 9);
+                    cprintf ("Spurious character: $%02X", C);
+            }
+        }
+    } while (!Done);
+
+#if DYN_DRV
+    /* Uninstall and unload the lightpen driver. */
+    CheckError ("mouse_unload", mouse_unload ());
+#else
+    /* Uninstall the lightpen driver. */
+    CheckError ("mouse_uninstall", mouse_uninstall ());
+#endif
+
+    /* Say goodbye. */
+    cputsxy (0, 16, "Goodbye!");
+    return EXIT_SUCCESS;
+}