Category Archives: Software

QuickCam and the Apple II, Part II

Click here for Part I

Part II – The Software

The software turns out to be a bit easier once all the hardware is built.  The two main routines required are the read and write routines.  Writing a data byte to the QC involves writing the data to the output port then reading back the echo of the data from the QC to ensure it was received. Reading a data byte from QC involves bringing PCAck (Strobe) high, waiting for the QC to respond with CamRDY high, then reading the first nybble on the input data port.  Once the nybble has been read, deassert PCAck (Strobe), wait for CamRDY to go low, then read the second nybble from the input data port.  Because of the way the PC parallel port works, Nybble3 from the QC went through an inverter so the QC actually compliments Nybble3 so the PC would read the correct value right from the I/O port.  However, the APIC isn’t mapped like the PC parallel port, so this bit will show up inverted on the input data port.  The 6502 has to compliment Nybble3 to get the correct value.  One other issue mentioned in the hardware post has to be dealt with – the Output Strobe.  On the APIC, the Output Strobe connected to PCAck, has an automatic pulse duration that can be set with switches on the card. Because the actual duration of this pulse is dynamic, handshaking with the CamRDY signal, a way to keep re-triggering this signal so it doesn’t prematurely deactivate has to be developed.  As luck would have it, the maximum strobe pulse is 15 us, just long enough to check the status of PCAck and either re-trigger the strobe or read the data on the input port before the strobe de-asserts. Finally, reading the echo from a data write is the exact same code as reading a data byte, the two are combined and the data write routine falls right into the data read.  Here are the two most important routines with the slot*16 in the X register:

QCWRITE STA $C080,X  ; DATA OUT -> CMD
QCREAD  STA $C082,X  ; STROBE -> PCACK
        LSR $C084,X  ; ACK (BIT 0) <- CAMRDY
        BCC QCREAD
        LDA $C083,X  ; DATA IN <- HI NYBBLE
        AND #$F0     ; DO ENOUGH WORK SO HANDSHAKE CAN BE SKIPPED
        EOR #$88     ; COMPLIMENT NYBBLE3
        STA TMP
        LDA #$0F
        AND $C083,X  ; DATA IN <- LO NYBBLE
        EOR TMP      ; COMBINE LO AND HI NYBBLES
        RTS

This is the core of the code to read and write the QC.  The other important routine is the reset/init routine.  The original B/W QC would return a parameter to the GetVersion command of 0.  The later Color QC returned a non-zero value, plus an additional value.  Care must be taken to take this into account if code is to work on both.  Also, this code isn’t smart – if a QC isn’t attached, it will wait forever.

RESET   LDA #$FF
        STA $C085,X ; RESET QC
        JSR $FCA8   ; WAIT
        LDA #$17    ; GET_VER
        JSR QCWRITE
        CMP #$17    ; GOOD ECHO?
        BNE QCERR
        JSR QCREAD
        BEQ QCBW
        PHA
        JSR QCREAD ; COLOR QC HAS 2 PARMS
        PLA
QCBW    RTS

Once we can initialize the QC and read and write commands, time to read video frame data.  The QC can return pixel data in either 4BPP or 6BPP mode.  Due to the limitations of the Apple II graphics, 4BPP seems adequate so I don’t bother with 6BPP.  I wrote a very simple program to grab small video frames and put them on the Apple’s low resolution graphics screen.  By mapping the color blocks into a corresponding grey scale ramp and displaying the result on a monochrome monitor, live video that roughly resembles what the QC is looking at results.  I added controls to adjust the exposure value and contrast to that the best image can be obtained given the severe constraints of 4 bit pixels and a 40×48 graphics mode.  Finally, I added a high resolution capture command that would take a video frame and display it on the high res screen.  Now the high res screen only shows 1 bit pixels, so I had to employ a primitive error diffusion algorithm to get a respectable image.  I’ll let you be the judge of the results:

I added a small ordered dither to the pixel calculations to bring out the mid and low grey values.  Here is an image of some of my collection:

Finally, here is the code for the lo-res video/hi-res frame capture.  It isn’t really well documented, but is fairly short and simple.  Meant to be assembled by EDASM.

        ORG     $2000
DATAOUT EQU     $C080
STROBE  EQU     $C082
DATAIN  EQU     $C083
ACK     EQU     $C084
RESET   EQU     $C085
WAIT    EQU     $FCA8
ERR     EQU     $03
TMP     EQU     $06
HPIX    EQU     $06
LASTERR EQU     $07
*
* HARD-CODE SLOT 1 AND RESET
*
        LDX     #$10
        JSR     QCRESET
        BCC     GR
        RTS
*
* SET LORES GRAPHICS
*
GR      LDA     $C050
        LDA     $C056
        LDA     $C052
        LDA     $C054
*
* SET FRAME DIMENSIONS
*
        LDX     #$10
        LDA     #45
        JSR     QCLEFT
        LDA     #25
        JSR     QCTOP
        LDA     #20
        JSR     QCNUMH
        LDA     #48
        JSR     QCNUMV
*
* SET FRAME CONTRAST/EXPOSURE
*
SETCONT LDA     CONT
        JSR     QCCONT
SETEXP  LDA     EXP
        JSR     QCEXP
*
* GRAB FRAME INTO LORES GRAPHICS SCREEN
*
VIDEO   LDA     #$08
        JSR     QCFRAME
        LDA     #$00
        STA     LINE
*
* EVEN LINES
*
ELINE   JSR     $F847   ; GBASECALC
        LDY     #$00
EPIX    STA     STROBE,X
        LSR     ACK,X
        BCC     EPIX
        LDA     DATAIN,X
        AND     #$0F
        TAX
        LDA     GREYLO,X
        LDX     #$10
        STA     ($26),Y
        INY
        LDA     DATAIN,X
        AND     #$0F
        TAX
        LDA     GREYLO,X
        LDX     #$10
        STA     ($26),Y
        INY
        CPY     #40
        BNE     EPIX
*
* ODD LINES
*
OLINE   LDY     #$00
OPIX    STA     STROBE,X
        LSR     ACK,X
        BCC     OPIX
        LDA     DATAIN,X
        AND     #$0F
        TAX
        LDA     GREYHI,X
        LDX     #$10
        ORA     ($26),Y
        STA     ($26),Y
        INY
        LDA     DATAIN,X
        AND     #$0F
        TAX
        LDA     GREYHI,X
        LDX     #$10
        ORA     ($26),Y
        STA     ($26),Y
        INY
        CPY     #40
        BNE     OPIX
        INC     LINE
        LDA     LINE
        CMP     #24
        BEQ     EOFRM
        JMP     ELINE
EOFRM   JSR     QCEOF
*
* CHEK FOR KEYPRESS
*
        LDA     $C000
        BMI     KEYPRESS
        JMP     VIDEO
KEYPRESS STA    $C010
        CMP     #$D1    ; Q
        BEQ     DONE
        CMP     #$AB    ; +
        BNE     KP1
        INC     EXP
        JMP     SETEXP
KP1     CMP     #$AD    ; -
        BNE     KP2
        DEC     EXP
        JMP     SETEXP
KP2     CMP     #$BC    ; <
        BNE     KP3
        DEC     CONT
        JMP     SETCONT
KP3     CMP     #$BE    ; >
        BNE     KPEND
        INC     CONT
        JMP     SETCONT
KPEND   CMP     #$A0    ; SPACEBAR
        BEQ     HRCAP
        JMP     VIDEO
DONE    JMP     $FB39   ; TEXT
*
* HIRES FRAME CAPTURE
*
HRCAP   LDA     $C057
        LDA     $C055
        LDA     #20
        JSR     QCLEFT
        LDA     #25
        JSR     QCTOP
        LDA     #140
        JSR     QCNUMH
        LDA     #192
        JSR     QCNUMV
        LDA     #$00
        JSR     QCFRAME
        LDA     #$40
        STA     $E6
        STA     $27
        LDY     #$00
        STY     $26
        STY     LINE
        STY     LASTERR
*
* FIRST SEVEN BITS OF HIRES WORD - EVEN LINES
*
EHLINE  JSR     QCPIXHI
        CLC
        ADC     #4
        JSR     PIXTST
        JSR     QCPIXLO
        SEC
        SBC     #1
        JSR     PIXTST
        JSR     QCPIXHI
        CLC
        ADC     #4
        JSR     PIXTST
        JSR     QCPIXLO
        SEC
        SBC     #1
        JSR     PIXTST
        JSR     QCPIXHI
        CLC
        ADC     #4
        JSR     PIXTST
        JSR     QCPIXLO
        SEC
        SBC     #1
        JSR     PIXTST
        JSR     QCPIXHI
        CLC
        ADC     #4
        JSR     PIXTST
        LDA     HPIX
        LSR
        STA     ($26),Y
        INY
*
* SECOND BYTE OF HIRES WORD
*
        JSR     QCPIXLO
        SEC
        SBC     #1
        JSR     PIXTST
        JSR     QCPIXHI
        CLC
        ADC     #4
        JSR     PIXTST
        JSR     QCPIXLO
        SEC
        SBC     #1
        JSR     PIXTST
        JSR     QCPIXHI
        CLC
        ADC     #4
        JSR     PIXTST
        JSR     QCPIXLO
        SEC
        SBC     #1
        JSR     PIXTST
        JSR     QCPIXHI
        CLC
        ADC     #4
        JSR     PIXTST
        JSR     QCPIXLO
        SEC
        SBC     #1
        JSR     PIXTST
        LDA     HPIX
        LSR
        STA     ($26),Y
        INY
        CPY     #40
        BEQ     NEXTEHLINE
        JMP     EHLINE
NEXTEHLINE JSR  $F504   ; INC HPOSN
        INC     LINE
        LDY     #$00
*
* FIRST SEVEN BITS OF HIRES WORD - ODD LINES
*
OHLINE  JSR     QCPIXHI
        SEC
        SBC     #4
        JSR     PIXTST
        JSR     QCPIXLO
        CLC
        ADC     #1
        JSR     PIXTST
        JSR     QCPIXHI
        SEC
        SBC     #4
        JSR     PIXTST
        JSR     QCPIXLO
        CLC
        ADC     #1
        JSR     PIXTST
        JSR     QCPIXHI
        SEC
        SBC     #4
        JSR     PIXTST
        JSR     QCPIXLO
        CLC
        ADC     #1
        JSR     PIXTST
        JSR     QCPIXHI
        SEC
        SBC     #4
        JSR     PIXTST
        LDA     HPIX
        LSR
        STA     ($26),Y
        INY
*
* SECOND BYTE OF HIRES WORD
*
        JSR     QCPIXLO
        CLC
        ADC     #1
        JSR     PIXTST
        JSR     QCPIXHI
        SEC
        SBC     #4
        JSR     PIXTST
        JSR     QCPIXLO
        CLC
        ADC     #1
        JSR     PIXTST
        JSR     QCPIXHI
        SEC
        SBC     #4
        JSR     PIXTST
        JSR     QCPIXLO
        CLC
        ADC     #1
        JSR     PIXTST
        JSR     QCPIXHI
        SEC
        SBC     #4
        JSR     PIXTST
        JSR     QCPIXLO
        CLC
        ADC     #1
        JSR     PIXTST
        LDA     HPIX
        LSR
        STA     ($26),Y
        INY
        CPY     #40
        BEQ     NEXTOHLINE
        JMP     OHLINE
NEXTOHLINE JSR  $F504   ; INC HPOSN
        INC     LINE
        LDA     LINE
        CMP     #192
        BEQ     EXIT
        LDY     #$00
        JMP     EHLINE
EXIT    JMP     QCEOF
*
* SIMPLE ERROR DIFFUSION
*
PIXTST  CLC
        ADC     LASTERR
        CMP     #$08
        BMI     PIXNEG
        ROR     HPIX
        SEC
        SBC     #$0F
        CMP     #$80
        ROR
        STA     LASTERR
        RTS
PIXNEG  LSR     HPIX
        CMP     #$80
        ROR
        STA     LASTERR
        RTS
EXP     DB      96
CONT    DB      104
LINE    DB      $00
*
* QCAM PIXEL DATA -> LORES GREYSCALE
*
GREYHI  DB      $30,$60,$A0,$50,$40,$80,$20,$10
        DB      $00,$F0,$E0,$D0,$70,$B0,$90,$C0
GREYLO  DB      $03,$06,$0A,$05,$04,$08,$02,$01
        DB      $00,$0F,$0E,$0D,$07,$0B,$09,$0C
***************************
*
*    QUICKCAM ROUTINES
*
***************************
*
* RESET QUICKCAM
*
QCRESET STA     RESET,X
        STX     TMP
        LDA     #$FF
        JSR     WAIT
        LDX     TMP
        LDA     #$17    ; GET VERSION
        JSR     QCWRITE
        CMP     #$17
        BEQ     QCOK
        SEC
        RTS
QCOK    JSR     QCREAD
        BEQ     QCBW
        PHA
        JSR     QCREAD
        PLA
QCBW    LDA     #$19    ; SET CONTRAST
        JSR     QCWRITE
        LDA     #104
        JSR     QCWRITE
        LDA     #$1B    ; AUTO CALIBRATE
        JSR     QCWRITE
        JSR     QCWRITE ; DUMMY PARAMETER
QCCAL   LDA     #$7F
        JSR     WAIT
        LDA     #$21    ; GET OFFSET
        JSR     QCWRITE
        JSR     QCREAD
        CMP     #$FF
        BEQ     QCCAL
        CLC
        RTS
*
* SET CONTRAST IN ACCUM
*
QCCONT  PHA
        LDA     #$19    ; SET CONTRAST
        JSR     QCWRITE
        PLA
        JMP     QCWRITE
*
* SET EXPOSURE IN ACCUM
*
QCEXP   PHA
        LDA     #$0B    ; SET EXPOSURE
        JSR     QCWRITE
        PLA
        JMP     QCWRITE
*
* SET TOP OF FRAME
*
QCTOP   PHA
        LDA     #$0D
        JSR     QCWRITE
        PLA
        JMP     QCWRITE
*
* SET FRAME LEFT COORDINATE
*
QCLEFT  PHA
        LDA     #$0F
        JSR     QCWRITE
        PLA
        JMP     QCWRITE
*
* SET FRAME LINES TO TRANSFER
*
QCNUMV PHA
        LDA     #$11
        JSR     QCWRITE
        PLA
        JMP     QCWRITE
*
* SET FRAME PIXELS PER LINE
*
QCNUMH  PHA
        LDA     #$13
        JSR     QCWRITE
        PLA
        JMP     QCWRITE
*
* COMPLETE END OF FRAME
*
QCEOF   STA     STROBE,X
        RTS
*
* SEND VIDEO FRAME
*
QCFRAME PHA
        LDA     #$07
        JSR     QCWRITE
        PLA
*
* WRITE BYTE TO CAM, FALL THRU TO READ ECHO
*
QCWRITE STA     DATAOUT,X
*
* READ BYTE FROM CAM
*
QCREAD  STA     STROBE,X
        LSR     ACK,X
        BCC     QCREAD
        LDA     DATAIN,X
        AND     #$F0
        EOR     #$88
        STA     TMP
        LDA     #$0F
        AND     DATAIN,X
        EOR     TMP
        RTS
*
* READ HI NYBBLE FROM CAM
* - NYBBLE IS DUPLICATED IN HI & LO NYBBLE
*
QCREADHI STA    STROBE,X
        LSR     ACK,X
        BCC     QCREADHI
*
* READ LO NYBBLE FROM CAM
* - SAME AS HI NYBBLE W/O WAIT
*
QCREADLO LDA    DATAIN,X
        EOR     #$88
        RTS
*
* READ 4BPP AS B/W RAMP 0->F
*
QCPIXHI STA     STROBE,X
        LSR     ACK,X
        BCC     QCPIXHI
QCPIXLO LDA     DATAIN,X
        EOR     #$07
        CLC
        ADC     #$01
        AND     #$0F
        RTS