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