Following Ben Eater’s 6502 project – part 4

Ben Eater’s exciting 6502 project has reached the next stage – adding a display. But before I could play along, I had some remedial work to do.

I’d achieved the previous stage of being able to get the LEDs to light up, but not reliably. Something was glitching and would cause the machine to go haywire.

Top side of one of the boards, with five rows of pins.

This isn’t entirely surprising on a breadboard project. My wiring wasn’t nearly as neat as Ben’s. (How does he do that? I suspect witchcraft.) There was almost certainly a dodgy connection somewhere.

Also, by the time I’d run data bus connections from the CPU to both the EEPROM and the 6522 VIA (plus address lines to the EEPROM and a few other places) I found there was no place to plug in the wires to the Arduino Mega, which is being used as a logic analyser.

I addressed that issue first with the creation of a couple of ‘pin extender’ boards. These are small pieces of stripboard into which I’ve soldered multiple rows of DuPont headers.

Underside of the board. Two rows of pins plug into the breadboard.

The first two rows are long headers that extend below the stripboard and plug into the breadboard (using two rows gives a firm, reliable connection).

The other three rows are normal headers that extend above the board but not below. The eliminate the chance of any nasty shorts, I put some duct tape on the bottom of the boards under these three rows.

That means I can now attach up to five wires to each of the pins on the 6502. The boards only cover the address and data pins, but in a future revision I might make them longer to handle nearly all of the 6502’s logic pins (ie, everything except power and GND, and maybe one or two of the pins that are rarely used, if ever).

Wrap it up

Having tall pins poking up above the chip opened up another possibility, too. Wire wrapping.

I’ve been considering wire wrapping as a possibility for the first real prototype of my own 6502 project (possibly the slowest-moving computer project in history). I already had the tool and suitable wire but have so far failed to find a reasonably priced source of wire-wrap compatible IC sockets (leave a comment if you know of one).

I put rows of long male headers in the breadboard alongside the EEPROM and input side of the 6522, too.

Wire wrapping is fiddly at first but, like any needlecraft skill, once you get the hang of it, it can be surprisingly fast.

I’m not saying the results are pretty, but they do work. Or, at least, they would have done if I hadn’t made a stupid mistake.

Bad connection

So, new code loaded on to the EEPROM, I fired up the Arduino, the serial terminal on my Alpha, the function gen and switched on the power to the breadboard. I held the reset button for a few seconds and watched as all the correct data – opcodes and operands – were fetched from the EEPROM. And I saw the 6502 switch into write mode and send data out on the bus.

Except that it didn’t.

Every time the 6502 wrote to the data bus, it was with the value $00. Every time. Here are some examples from the start of the code. This is the output seen on the Arduino’s serial monitor to which I’ve subsequently added comments.

1111111111111100 00000000 fffc r 00
1111111111111101 10000000 fffd r 80
1000000000000000 10101001 8000 r a9 ; lda #$ff
1000000000000001 11111111 8001 r ff
1000000000000010 10001101 8002 r 8d ; sta $6002
1000000000000011 00000010 8003 r 02
1000000000000100 01100000 8004 r 60
0110000000000010 00000000 6002 W 00 ; so why is it writing 0??
1000000000000101 10101001 8005 r a9 ; lda #$e0
1000000000000110 11100000 8006 r e0
1000000000000111 10001101 8007 r 8d ; sta $6003
1000000000001000 00000011 8008 r 03
1000000000001001 01100000 8009 r 60
0110000000000011 00000000 6003 W 00 ; writing 0 again!
1000000000001010 10101001 800a r a9 ; lda #$38
1000000000001011 00111000 800b r 38
1000000000001100 10001101 800c r 8d ; sta $6000
1000000000001101 00000000 800d r 00
1000000000001110 01100000 800e r 60
0110000000000000 00000000 6000 W 00 ; yup, 0 again

I puzzled over this for far too long, until I finally did what I should have done in the first place – head over to the forums on 6502.org. Very quickly, someone by the splendid moniker of CaptainCulry suggested, among other things: “Double check that the EEPROM is not enabled when you’re trying to address the 6522”.

Hmm. Enabled, eh? The /CE (chip enable) pin on the EEPROM has a direct connection to the decoder chip. I looked at that wire and… doh!

While doing all the rewiring (the wire wrapping and replacing the LEDs with the LCD display) I’d naturally removed a bunch of older wires. When I replaced the wire from the EEPROM to the decoder I’d accidentally connected it to GND instead of the correct pin (two holes over) of the decoder chip. That meant the EEPROM was always enabled and overriding the 6502’s attempts to put data on the bus. Moving that connection fixed the problem.

In my setup, there are a couple of deviations from Ben’s project. The first is that I’m now using my Rigol function generator as the clock signal, as I’m not sure I need single-stepping any more. It’s much easier to adjust the clock speed than cranking around the tiny knob on a pot!

The other is that I’m using a 20×4 display. In preparation for doing this part of the project, I looked at my collection of LCD displays and found that they all had I2C backpacks soldered on. While ordering a new 16×2 display from Amazon, I saw that 20×4 units were also very cheap, so I bought a couple, wondering if they could be a drop-in replacement. As you can see from the picture, that is, indeed, the case (this far, at least).

The code

The other change from Ben’s project is that I’m using Beebasm as my assembler. Translating from Ben’s code was fairly simple, one key alteration being that Beebasm uses the keyword OR instead of the pipe symbol for logical OR operations. Also, character literals need to be in single, not double, quotes – ie, lda #’H’ instead of lda #”H”.

Also, Ben’s code includes a routine to shift the cursor one space at the start, which I decided to omit (I figured he did that purely for teaching purposes). In it’s place, I’ve included the command $01 (or %00000001 if you prefer binary) which clears the screen and sets the internal memory pointer back to the start. Without this, if you run the code once, then hit the computer’s reset button, the screen doesn’t clear and characters restart from where they left off last time.

Here’s the Beebasm version of Ben’s code.

; Beebasm version of Ben Eater's code for the 6502 project.
; https://www.youtube.com/watch?v=FY3zTUaykVo
; This is the 'quick and dirty' way of writing 'Hello, world!'
; to an LCD screen.
;
; Compile with:
; beebasm -i hello-world.asm
;
; Write to EEPROM with:
; minipro -p AT28C256 -w hello-world.bin

PORTB = $6000
PORTA = $6001
DDRB = $6002
DDRA = $6003

E  = %10000000
RW = %01000000
RS = %00100000

ORG $8000

.start
  lda #%11111111 ; Set all pins on port B to output
  sta DDRB

  lda #%11100000 ; Set top 3 pins on port A to output
  sta DDRA

  lda #%00111000 ; Set 8-bit mode; 2-line display; 5x8 font
  sta PORTB
  lda #0         ; Clear RS/RW/E bits
  sta PORTA
  lda #E         ; Set E bit to send instruction
  sta PORTA
  lda #0         ; Clear RS/RW/E bits
  sta PORTA

  lda #%00001110 ; Display on; cursor on; blink off
  sta PORTB
  lda #0         ; Clear RS/RW/E bits
  sta PORTA
  lda #E         ; Set E bit to send instruction
  sta PORTA
  lda #0         ; Clear RS/RW/E bits
  sta PORTA

  lda #%00000001 ; clear display, reset display memory
  sta PORTB
  lda #0         ; Clear RS/RW/E bits
  sta PORTA
  lda #E         ; Set E bit to send instruction
  sta PORTA
  lda #0         ; Clear RS/RW/E bits
  sta PORTA

  ; -- I decided to omit this bit --
  ; lda #%00000110 ; Increment and shift cursor; don't shift display
  ; sta PORTB
  ; lda #0         ; Clear RS/RW/E bits
  ; sta PORTA
  ; lda #E         ; Set E bit to send instruction
  ; sta PORTA
  ; lda #0         ; Clear RS/RW/E bits
  ; sta PORTA

  lda #'H'
  sta PORTB
  lda #RS         ; Set rs; Clear rw/E bits
  sta PORTA
  lda #(RS OR E)  ; Set E bit to send instruction
  sta PORTA
  lda #RS         ; Clear E bits
  sta PORTA

  lda #'e'
  sta PORTB
  lda #RS         ; Set rs; Clear rw/E bits
  sta PORTA
  lda #(RS OR E)  ; Set E bit to send instruction
  sta PORTA
  lda #RS         ; Clear E bits
  sta PORTA

  lda #'l'
  sta PORTB
  lda #RS         ; Set rs; Clear rw/E bits
  sta PORTA
  lda #(RS OR E)  ; Set E bit to send instruction
  sta PORTA
  lda #RS         ; Clear E bits
  sta PORTA

  lda #'l'
  sta PORTB
  lda #RS         ; Set rs; Clear rw/E bits
  sta PORTA
  lda #(RS OR E)  ; Set E bit to send instruction
  sta PORTA
  lda #RS         ; Clear E bits
  sta PORTA

  lda #'o'
  sta PORTB
  lda #RS         ; Set rs; Clear rw/E bits
  sta PORTA
  lda #(RS OR E)  ; Set E bit to send instruction
  sta PORTA
  lda #RS         ; Clear E bits
  sta PORTA

  lda #','
  sta PORTB
  lda #RS         ; Set rs; Clear rw/E bits
  sta PORTA
  lda #(RS OR E)  ; Set E bit to send instruction
  sta PORTA
  lda #RS         ; Clear E bits
  sta PORTA

  lda #' '
  sta PORTB
  lda #RS         ; Set rs; Clear rw/E bits
  sta PORTA
  lda #(RS OR E)  ; Set E bit to send instruction
  sta PORTA
  lda #RS         ; Clear E bits
  sta PORTA

  lda #'w'
  sta PORTB
  lda #RS         ; Set rs; Clear rw/E bits
  sta PORTA
  lda #(RS OR E)  ; Set E bit to send instruction
  sta PORTA
  lda #RS         ; Clear E bits
  sta PORTA

  lda #'o'
  sta PORTB
  lda #RS         ; Set rs; Clear rw/E bits
  sta PORTA
  lda #(RS OR E)  ; Set E bit to send instruction
  sta PORTA
  lda #RS         ; Clear E bits
  sta PORTA

  lda #'r'
  sta PORTB
  lda #RS         ; Set rs; Clear rw/E bits
  sta PORTA
  lda #(RS OR E)  ; Set E bit to send instruction
  sta PORTA
  lda #RS         ; Clear E bits
  sta PORTA

  lda #'l'
  sta PORTB
  lda #RS         ; Set rs; Clear rw/E bits
  sta PORTA
  lda #(RS OR E)  ; Set E bit to send instruction
  sta PORTA
  lda #RS         ; Clear E bits
  sta PORTA

  lda #'d'
  sta PORTB
  lda #RS         ; Set rs; Clear rw/E bits
  sta PORTA
  lda #(RS OR E)  ; Set E bit to send instruction
  sta PORTA
  lda #RS         ; Clear E bits
  sta PORTA

  lda #'!'
  sta PORTB
  lda #RS         ; Set rs; Clear rw/E bits
  sta PORTA
  lda #(RS OR E)  ; Set E bit to send instruction
  sta PORTA
  lda #RS         ; Clear E bits
  sta PORTA

.loop
  jmp loop

ORG $fffc
    equw start	; write $8000 to $fffc
    equw $0000	; fill last two bytes with null values to fill ROM

.end

SAVE "hello-world.bin", start, end

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.