Category Archives: Software

Apple II + Raspberry Pi = Apple II Pi

One of the most interesting computers to come to market in the past year is the Raspberry Pi. Of the many uses I’ve come up with for this diminutive device is to treat it as an upgrade to my Apple II, or, thought of another way, the Apple II as a crazy cool case for the Raspberry Pi (RPi). Now, for the past 20 years, people have speculated what the next Apple II could look like. The last issue of Juiced.GS, the only remaining Apple II periodical, had an interesting article about the Apple II nx (next, unix, ???). Of course, everyone has their own idea of what the next Apple II should look like, but there are a few obvious traits:  at least 32 bit CPU (6502 derivative preferred), some sort of unix OS, up-to-date graphics, modern connectivity, programmer focussed. Kind of sounds like the RPi, huh? For those that don’t know, the RPi has an ARM processor (who’s designers used the 6502 as a source of inspiration), has a fairly powerful GPU with HDMI output (and composite output for us retro types), USB 2.0, SD card storage, and 512 MB of main memory on the Model B (256 MB on the Model A).

If the RPi was to work with the Apple II, what would the interface be?  The RPi has a 26 pin GPIO header capable of driving many types of serial and parallel protocols. There were some early discussions about how an RPi could interface to an Apple II bus and control the peripherals at a low level. There was never a satisfactory solution given the low number of I/O pins and the difficulty programming them in real-time from Linux. So I settled on a different approach – use the TTL serial port available on the RPi GPIO header as the communications channel between the RPi and Apple II. The Apple II becomes a co-processor, of sorts, to the RPi. An additional benefit of a serial interface is that the IIc can partake in the upgrade.  The Super Serial Card in the Apple IIe would be the first implementation of the serial interface followed by a dedicated prototype card that would accept the RPi and connect to an on-board serial chip.

A protocol was developed to allow the Apple II to communicate at 115K  baud without interrupts, sending keyboard and mouse events to the RPi.  The RPi can insert a request command to the Apple II, which will be followed by the Apple II enacting on the request in it’s event loop. Currently the RPi can request memory reads, memory writes, and calls.  A small assembly language program runs on the Apple II while a small daemon runs on the RPi.  Both talk to each other over the serial port; the Linux daemon inserts input events into the Linux input subsystem as well as having a socket interface to accept requests to pass on to the Apple II from external programs.

The software was developed on an Apple IIc with a serial cable connected to a TTL->RS232 converter on the RPi’s GPIO serial pins. Merlin 8 was used to develop the code locally on the Apple II while the Linux daemon was written in user mode C (no kernel modules). Neither program’s source code is particularly attractive, but they get the job done. You can find the source code and Apple II disk image on GitHub: https://github.com/dschmenk/apple2pi.  Note that this code isn’t really tied to the RPi.  It will actually work with any modern Linux distribution on an x86.

Apple II Pi will work just fine using off-the-shelf parts you can buy from eBay (or parts you already have).  However, if you want to take it to the next level and integrate your Raspberry Pi into your Apple IIe case for the ultimate upgrade, here is what you’ll need:

apple2pi schematic

I used a ribbon cable to bring the GPIO header around to the prototype card so the RPi would be oriented in such a way that the ports would still be accessible with the case on.  Unfortunately, the RPi has ports on all side, so I sacrificed the composite video and analog audio access. Here are some pictures of my prototype board with a Model A Raspberry Pi:

Back of the board: 

Front of the board: 

There is one final software addition needed before the hardware will work.  In the source distribution is a program called a2serclk.  This will program the GPCLK pin to output the 1.8432 MHz clock signal needed for the 6551 baud rate generator.  Without this, the 6551 would require a physical crystal oscillator connected to the XTALI and XTALO pins.  Place a call to” /usr/local/bin/a2serclk” right before the call to “/usr/local/bin/a2pid –daemon” in the /etc/rc.local init script.

Here is a three part video series showing the development of the project:
Part 1
Part 2
Part 3
Part 4

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