From 051c21726ba5038134b2326872c031d52342c6af Mon Sep 17 00:00:00 2001 From: Greg King Date: Wed, 29 May 2013 19:48:45 -0400 Subject: [PATCH] Added Inkwell lightpen drivers for the C64 and the C128. 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 | 7 +- asminc/c64.inc | 4 +- include/c128.h | 3 +- include/c64.h | 5 +- libsrc/c128/mou/c128-inkwell.s | 552 +++++++++++++++++++++++++++++++++ libsrc/c64/mou/c64-inkwell.s | 526 +++++++++++++++++++++++++++++++ testcode/lib/pen-test.c | 282 +++++++++++++++++ 7 files changed, 1373 insertions(+), 6 deletions(-) create mode 100644 libsrc/c128/mou/c128-inkwell.s create mode 100644 libsrc/c64/mou/c64-inkwell.s create mode 100644 testcode/lib/pen-test.c diff --git a/asminc/c128.inc b/asminc/c128.inc index 8ec72a172..1fb8b397c 100644 --- a/asminc/c128.inc +++ b/asminc/c128.inc @@ -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 diff --git a/asminc/c64.inc b/asminc/c64.inc index 6823403c5..f5dbcf549 100644 --- a/asminc/c64.inc +++ b/asminc/c64.inc @@ -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 - diff --git a/include/c128.h b/include/c128.h index 0b005bce0..2799f8e39 100644 --- a/include/c128.h +++ b/include/c128.h @@ -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[] */ diff --git a/include/c64.h b/include/c64.h index 0a4ebaf97..adf3840b9 100644 --- a/include/c64.h +++ b/include/c64.h @@ -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 index 000000000..5625213b4 --- /dev/null +++ b/libsrc/c128/mou/c128-inkwell.s @@ -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 + 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 index 000000000..8c869d68f --- /dev/null +++ b/libsrc/c64/mou/c64-inkwell.s @@ -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 + 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 index 000000000..ed1d75a2a --- /dev/null +++ b/testcode/lib/pen-test.c @@ -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 +#include +#include +#include +#include +#include +#include + +#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; +} -- 2.39.5