Category Archives: CCD Cameras

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

QuickCam and the Apple II

Part I – The Hardware

I finally got around to looking into connecting an old Connectix Greyscale QuickCam (QC) to an Apple II by way of the Apple Parallel Interface Card (APIC).  I had a couple of QuickCams lying around from my webcam astronomy days and bought some APICs off the ePay in anticipation of hooking them together someday.  I chose the APIC because it was the only parallel interface card I could find that implemented a full 8-bit input port.

Someday finally came.  I pulled out the programming specs on the QC and the APIC Installation and Operation Manual and went about figuring out the details of mapping the PC parallel port pinouts of the QC to the APIC.  This is the cable magic required to connect them together:

APIC Signal/Pin        QuickCam Signal/Pin
Strobe    15           PCAck     17
Ack       16           CamRDY    15
Input D7  18           Nybble3   11
Input D6  21           Nybble2   10
Input D5  19           Nybble1   12
Input D4  14           Nybble0   13
Input D3  25           Nybble3   11
Input D2   3           Nybble2   10
Input D1  17           Nybble1   12
Input D0   1           Nybble0   13
Output D7 13           Cmd7       9
Output D6 12           Cmd6       8
Output D5 11           Cmd5       7
Output D4 23           Cmd4       6
Output D3 22           Cmd3       5
Output D2  8           Cmd2       4
Output D1  6           Cmd1       3
Output D0  5           Cmd0       2
Aux Strobe 7           *Reset    16
GND       24,20,4,2    GND       18-25

There are a few things to take note of.  First, the output from the QC, Nybble3-0 is mapped to both to the most significant and the least significant nybble of the APICs 8 bit input port.  Since there are only enough I/O pins to implement the 4 bit communications protocol, I decided to map the one QC nybble to a full byte of input to the APIC.  The 6502 isn’t very good at shifting, so this alleviates the need to shift the nybble around for certain algorithms.  The second thing to note is the output strobe of the APIC connected to the PCAck of the QC.  This is one of the major handshake lines that works in conjunction with the CamRDY line for transferring data.  The host needs to hold this line high until the QC signals data is ready by raising CamRDY.  However, the APICs strobe is pulsed for a short duration, automatically returning to its previous state based upon switch settings on the APIC.  The strobe duration can be set using the first three switches on the APIC, from 1 us to 15 us.  Luckily, by maxing out the strobe duration to 15 us gives the 6502 just enough time to check the PCAck from the QC and re-triggering the strobe if it isn’t ready, or reading the data input port if it is – all before the strobe falls.  The second nybble from the QC quickly follows the first, so there was no need to check the handshaking signals again if the 6502 did enough work in-between nybble reads.  The last fortuitous connection was the Aux Strobe to *Reset.  The Aux Strobe isn’t really documented in the APIC manual except for the block diagram.  It delivers a short pulse whenever a certain address is accessed, and just happens to be active low, matching the requirements for the *Reset on the QC.  The APIC allows for the selection of active high or low for the regular strobe signal, thus matching the QCs PCAck signal.  The switch settings required on the APIC are thus:

      APIC SWITCH BLOCK
 1   2   3   4   5   6   7
ON  ON  ON  OFF ON  OFF OFF

As this is a fairly delicate connection that I didn’t want to have to re-solder, I wanted to get a DB-25 dual ended hood to create a professional, reliable dongle.  However, those parts are no longer made, so I had to buy two DB-25 hoods and take a hack saw to them to create the piece I wanted.  Ultimately, I ended up with this:

Finally, the QuickCam takes its power from the PS/2->AT keyboard connector since the parallel port doesn’t provide any.  I went through the parts bin, found an old +5V power adapter, an old keyboard cable to hack up, and along with a trip to Radio Shack for the power plug, created this:

The only lines required to connect are the +5V and ground.  Here is a link to a nice diagram showing which pins are power for the two connector types: http://www.bbdsoft.com/keyboard.html

Continue on to Connectix QuickCam and the Apple II, Part II – The Software