Welcome to Hackster!
Hackster is a community dedicated to learning hardware, from beginner to pro. Join us, it's free!
Michael Cartwright
Published © MIT

6502 console interface using Arduino Uno to run WozMon

Failing to get your 65C51 to work? Use an Uno to simulate a serial peripheral interface port and get WozMon running.

IntermediateFull instructions provided4 hours735
6502 console interface using Arduino Uno to run WozMon

Things used in this project

Story

Read more

Code

Minimalist Serial Interface

Assembly x86
This is the minimum code required to initialize and use the serial interface. It echos keys back to the console.
; UNO emulator
UNODATA    = $EC
UNOSTATUS  = $ED

  .org $8000

start:
  sei              ; disable interrupts
  cld              ; clear decimal arithmetic mode.
  
  ldx #$ff         ; reset stack pointer
  txs
  
  sta UNOSTATUS    ; soft reset (value not important) 
  
  cli              ; enable interrupts

  lda #"O"
  sta UNODATA
  lda #"K"
  sta UNODATA
  lda #$0A       ; new line
  sta UNODATA
  
loopKey:
  lda UNOSTATUS
  and #$80
  beq loopKey   ; loop if no key available
  
  ldx UNODATA   ; get key from console

bufferFull:
  lda UNOSTATUS
  and #$40       
  bne bufferFull ; loop if write buffer full
  
  stx UNODATA    ; send character to console
  bra loopKey
  
irq:
  rti

nmi:
  rti

  .org $fffa
  .word nmi
  .word start
  .word irq

WozMon modified for Uno interface

Assembly x86
Wozniak's original monitor for the Apple 1 with my modifications to make it work via the Uno serial interface emulator.
;  The WOZ Monitor for the Apple 1
;  Written by Steve Wozniak in 1976

; Sample program:
; A9 20 AA 20 ED FF E8 8A C9 7F D0 F6 4C 00 FF

; Page 0 Variables


XAML            = $24           ;  Last "opened" location Low
XAMH            = $25           ;  Last "opened" location High
STL             = $26           ;  Store address Low
STH             = $27           ;  Store address High
L               = $28           ;  Hex value parsing Low
H               = $29           ;  Hex value parsing High
YSAV            = $2A           ;  Used to see if hex value is given
MODE            = $2B           ;  $00=XAM, $7F=STOR, $AE=BLOCK XAM


; Other Variables

IN              = $0200         ;  Input buffer to $027F
;KBD             = $D010         ;  PIA.A keyboard input
;KBDCR           = $D011         ;  PIA.A keyboard control register
;DSP             = $D012         ;  PIA.B display output register
;DSPCR           = $D013         ;  PIA.B display control register


UNODATA    = $EC                 ; UNO emulator data
UNOSTATUS  = $ED                 ; UNO emulator status

               .org $8000
                LDA #$20
loop:
                TAX
                JSR ECHO
                INX
                TXA
                CMP #$7F
                BNE loop
                JMP $FF00
                
               .org $FF00

RESET:          CLD             ; Clear decimal arithmetic mode.
                CLI
                STA UNOSTATUS   ; soft reset (value not important) 
                
                LDY #$7F        ; Mask for DSP data direction register.
                ;STY DSP         ; Set it up.
                ;LDA #$A7        ; KBD and DSP control register mask.
                ;STA KBDCR       ; Enable interrupts, set CA1, CB1, for
                ;STA DSPCR       ; positive edge sense/output mode.
                LDA  #$1B
                
NOTCR:          CMP #$08        ; Backspace key?
                BEQ BACKSPACE   ; Yes.
                CMP #$1B        ; ESC?
                BEQ ESCAPE      ; Yes.
                INY             ; Advance text index.
                BPL NEXTCHAR    ; Auto ESC if > 127.
ESCAPE:         LDA #$5C        ; "\".
                JSR ECHO        ; Output it.
GETLINE:        LDA #$0D        ; CR.
                JSR ECHO        ; Output it.
                LDY #$01        ; Initialize text index.
BACKSPACE:      DEY             ; Back up text index.
                BMI GETLINE     ; Beyond start of line, reinitialize.
                
;NEXTCHAR:      LDA KBDCR       ; Key ready?
;               BPL NEXTCHAR    ; Loop until ready.
;               LDA KBD         ; Load character. B7 should be 1.

NEXTCHAR:       LDA UNOSTATUS
                AND #$80
                BEQ NEXTCHAR    ; loop if no key available
                LDA UNODATA

                STA IN,Y        ; Add to text buffer.
                
                CMP #$1B        ; don't echo the $1B
                BEQ NOTCR
                
                JSR ECHO        ; Display character.
                CMP #$0D        ; CR?
                BNE NOTCR       ; No.
                LDY #$FF        ; Reset text index.
                LDA #$00        ; For XAM mode.
                TAX             ; 0->X.
SETBLOCK:       
                ASL
SETSTOR:        
                ASL             ; Leaves $7B if setting STOR mode.
                STA MODE        ; $00=XAM $74=STOR $B8=BLOK XAM
BLSKIP:         
                INY             ; Advance text index.
NEXTITEM:       
                LDA IN,Y        ; Get character.
                CMP #$0D        ; CR?
                BEQ GETLINE     ; Yes, done this line.
                CMP #'.'        ; "."?
                BCC BLSKIP      ; Skip delimiter.
                BEQ SETBLOCK    ; Set BLOCK XAM mode
                CMP #':'        ; ":"?
                BEQ SETSTOR     ; Yes. Set STOR mode.
                CMP #'R'        ; "R"?
                BEQ RUN         ; Yes. Run user program.
                STX L           ; $00-> L.
                STX H           ; and H.
                STY YSAV        ; Save Y for comparison.
NEXTHEX:        LDA IN,Y        ; Get character for hex test.
                EOR #$30        ; Map digits to $0-9.
                CMP #$0A        ; Digit?
                BCC DIG         ; Yes.
                ADC #$88        ; Map letter "A"-"F" to $FA-FF.
                CMP #$FA        ; Hex letter?
                BCC NOTHEX      ; No, character not hex.
DIG:            ASL
                ASL             ; Hex digit to MSD of A.
                ASL
                ASL
                LDX #$04        ; Shift count.
HEXSHIFT:       ASL             ; Hex digit left, MSB to carry.
                ROL L           ; Rotate into LSD.
                ROL H           ;  Rotate into MSDs.
                DEX             ; Done 4 shifts?
                BNE HEXSHIFT    ; No, loop.
                INY             ; Advance text index.
                BNE NEXTHEX     ; Always taken. Check next char for hex.
NOTHEX:         CPY YSAV        ; Check if L, H empty (no hex digits).
                BEQ ESCAPE      ; Yes, generate ESC sequence.
                BIT MODE        ; Test MODE byte.
                BVC NOTSTOR     ;  B6=0 STOR 1 for XAM & BLOCK XAM
                LDA L           ; LSDs of hex data.
                STA (STL,X)     ; Store at current store index.
                INC STL         ; Increment store index.
                BNE NEXTITEM    ; Get next item. (no carry).
                INC STH         ; Add carry to store index high order.
TONEXTITEM:     JMP NEXTITEM    ; Get next command item.
RUN:            JMP (XAML)      ; Run at current XAM index.
NOTSTOR:        BMI XAMNEXT     ; B7=0 for XAM, 1 for BLOCK XAM.
                LDX #$02        ; Byte count.
SETADR:         LDA L-1,X       ; Copy hex data to
                STA STL-1,X     ; store index.
                STA XAML-1,X    ; And to XAM index.
                DEX             ; Next of 2 bytes.
                BNE SETADR      ; Loop unless X=0.
NXTPRNT:        BNE PRDATA      ; NE means no address to print.
                LDA #$0D        ; CR.
                JSR ECHO        ; Output it.
                LDA XAMH        ; Examine index high-order byte.
                JSR PRBYTE      ; Output it in hex format.
                LDA XAML        ; Low-order examine index byte.
                JSR PRBYTE      ; Output it in hex format.
                LDA #':'        ; ":".
                JSR ECHO        ; Output it.
PRDATA:         LDA #' '        ; Blank.
                JSR ECHO        ; Output it.
                LDA (XAML,X)    ; Get data byte at examine index.
                JSR PRBYTE      ; Output it in hex format.
XAMNEXT:        STX MODE        ; 0->MODE (XAM mode).
                LDA XAML
                CMP L           ; Compare examine index to hex data.
                LDA XAMH
                SBC H
                BCS TONEXTITEM  ; Not less, so no more data to output.
                INC XAML
                BNE MOD8CHK     ; Increment examine index.
                INC XAMH
MOD8CHK:        LDA XAML        ; Check low-order examine index byte
                AND #$07        ; For MOD 8=0
                BPL NXTPRNT     ; Always taken.
PRBYTE:         PHA             ; Save A for LSD.
                LSR
                LSR
                LSR             ; MSD to LSD position.
                LSR
                JSR PRHEX       ; Output hex digit.
                PLA             ; Restore A.
PRHEX:          AND #$0F        ; Mask LSD for hex print.
                ORA #$30        ; Add "0".
                CMP #$3A        ; Digit?
                BCC ECHO        ; Yes, output it.
                ADC #$06        ; Add offset for letter.

;ECHO:          BIT DSP         ; bit (B7) cleared yet?
;               BMI ECHO        ; No, wait for display.
;               STA DSP         ; Output character. Sets DA.

ECHO:
                PHA
READY:          LDA UNOSTATUS
                AND #$40       
                BNE READY ; loop if write buffer full
                PLA

                STA UNODATA

                RTS             ; Return.

                ;BRK             ; unused
                ;BRK             ; unused

                .org $FFFA
; Interrupt Vectors

                .WORD $0F00     ; NMI
                .WORD RESET     ; RESET
                .WORD $0000     ; BRK/IRQ

Ardunio serial emulator for 65C02

Arduino
Running at a slow clock of 1kHz we can get a 65C02 to see the Ardunio Uno as an ACIA style serial peripheral interface chip.
#define D0     3
#define D1     4
#define D2     5
#define D3     6
#define D4     7
#define D5     8
#define D6     9
#define D7    10

#define A0    11
#define CS    12
#define RW    13
#define CLK   2

unsigned char circularKeyBuffer[256];
unsigned char readKeyPointer;
unsigned char writeKeyPointer;

unsigned char circularDisplayBuffer[256];
unsigned char readDisplayPointer;
unsigned char writeDisplayPointer;

boolean waitingForReset = true;

void setup() 
{
  waitingForReset = true;
  pinMode(A0,  INPUT);
  pinMode(CS,  INPUT);
  pinMode(RW,  INPUT);
  pinMode(CLK, INPUT);

  pinMode(D0, INPUT);
  pinMode(D1, INPUT);
  pinMode(D2, INPUT);
  pinMode(D3, INPUT);
  pinMode(D4, INPUT);
  pinMode(D5, INPUT);
  pinMode(D6, INPUT);
  pinMode(D7, INPUT);

  Serial.begin(9600);

  readKeyPointer = 0;
  writeKeyPointer = 0;

  readDisplayPointer = 0;
  writeDisplayPointer = 0;

  attachInterrupt(digitalPinToInterrupt(CLK), onClock, FALLING);
  Serial.println();
  Serial.println("Uno Ready.");
}

unsigned char readData()
{
  unsigned char data = 0;
  data |= ((digitalRead(D0) == HIGH) ? 0x01 : 0x00);
  data |= ((digitalRead(D1) == HIGH) ? 0x02 : 0x00);
  data |= ((digitalRead(D2) == HIGH) ? 0x04 : 0x00);
  data |= ((digitalRead(D3) == HIGH) ? 0x08 : 0x00);
  data |= ((digitalRead(D4) == HIGH) ? 0x10 : 0x00);
  data |= ((digitalRead(D5) == HIGH) ? 0x20 : 0x00);
  data |= ((digitalRead(D6) == HIGH) ? 0x40 : 0x00);
  data |= ((digitalRead(D7) == HIGH) ? 0x80 : 0x00);
  return data;
}

void writeData(unsigned char data)
{
  // switch data pins to output mode
  pinMode(D0, OUTPUT);
  pinMode(D1, OUTPUT);
  pinMode(D2, OUTPUT);
  pinMode(D3, OUTPUT);
  pinMode(D4, OUTPUT);
  pinMode(D5, OUTPUT);
  pinMode(D6, OUTPUT);
  pinMode(D7, OUTPUT);

  digitalWrite(D0, (data & 0x01) ? HIGH : LOW);
  digitalWrite(D1, (data & 0x02) ? HIGH : LOW);
  digitalWrite(D2, (data & 0x04) ? HIGH : LOW);
  digitalWrite(D3, (data & 0x08) ? HIGH : LOW);
  digitalWrite(D4, (data & 0x10) ? HIGH : LOW);
  digitalWrite(D5, (data & 0x20) ? HIGH : LOW);
  digitalWrite(D6, (data & 0x40) ? HIGH : LOW);
  digitalWrite(D7, (data & 0x80) ? HIGH : LOW);
    
  // we were writing so we need to not leave the pins in output mode beyond this cycle
  bool ioSelected;
  do
  {
    ioSelected = !digitalRead(CS);
  }
  while (ioSelected);

  // switch data pins to back to input mode
  pinMode(D0, INPUT);
  pinMode(D1, INPUT);
  pinMode(D2, INPUT);
  pinMode(D3, INPUT);
  pinMode(D4, INPUT);
  pinMode(D5, INPUT);
  pinMode(D6, INPUT);
  pinMode(D7, INPUT);
}

#define WRITEDATADELAY 750 // 750 / 1000us clock cycle at 1kHz puts us firmly in the middle of the 2nd part of the clock cycle for 'WRITE DATA'

void onClock()
{
  bool ioSelected = !digitalRead(CS);
  if (ioSelected)
  {
    bool          rwState = digitalRead(RW);
    unsigned char rs0     = digitalRead(A0) ? 1 : 0;
    unsigned char data    = 0;
      
    if (rs0 == 0) // data register
    {
      if (!waitingForReset)
      {
        if (rwState)
        {
          if (readKeyPointer == writeKeyPointer)
          {
            data = 0; // don't fail on empty buffer
          }
          else
          {
            // 6502 is reading key data
            data = circularKeyBuffer[readKeyPointer];
            readKeyPointer++;
          }
          writeData(data);
        }
        else
        {
          delayMicroseconds(WRITEDATADELAY);

          // 6502 is writing display data
          data = readData();
          circularDisplayBuffer[writeDisplayPointer] = data;
          writeDisplayPointer++;
        }
      }
    }
    else // status register (rs0 == 1)
    {
      if (rwState)
      {
        // 6502 is reading status register
        if (!waitingForReset)
        {
          // D7 set means there is a key available to be read
          data = (readKeyPointer != writeKeyPointer) ? 0x80 : 0x00;

          // D6 set means the display buffer is full
          unsigned char writeSlot = writeDisplayPointer+1;
          data |= (writeSlot == readDisplayPointer) ? 0x40 : 0x00;

          writeData(data);
        }
      }
      else 
      {
        // 6502 is writing status register (soft reset)
        readKeyPointer = 0;
        writeKeyPointer = 0;
        readDisplayPointer = 0;
        writeDisplayPointer = 0;
        waitingForReset = false;
      }
    }
  } // ioSelected
}


void loop() 
{
  if (Serial.available() > 0)
  {
    unsigned char writeSlot = writeKeyPointer+1;
    if (writeSlot == readKeyPointer)
    {
      Serial.println("Console key buffer overflow!");
    }
    else
    {
      unsigned char keyCharacter = Serial.read();
      circularKeyBuffer[writeKeyPointer] = keyCharacter;
      writeKeyPointer++;
    }
  }
  if (readDisplayPointer != writeDisplayPointer)
  {
    unsigned char displayCharacter = circularDisplayBuffer[readDisplayPointer];
    readDisplayPointer++;
    Serial.write((char)displayCharacter);
  }
}

Credits

Michael Cartwright
21 projects • 16 followers
Contact

Comments

Please log in or sign up to comment.