Programming an EEPROM on a 6502 Microprocessor

Or, updating our firmware internally like a proper computer

The Kickstarter edition of the VIC-1112 IEEE-488 adapter has an EEPROM included on it to enable software updates or even general firmware hacking without removing the IC.  These devices are simple to program, but not quite as simple as just poke’ing ram.  The IC’s need some time to complete a write before they can be accessed again, and indicate their finished state by returning the data that was written to them.

Let’s explore the process of writing to an EEPROM using a generic 6502 system as a model, and expand on using a VIC-20 to do the work later.

I tested some simple write code using byte mode to simplify the code’s logic.  Since the device is only 8,192 bytes it should finish in a reasonable time in spite of not using page mode.

Here’s my first test, a simple program to write 256 bytes in a row to the EEPROM.

zpsrc   =   $42         ; zp source pointer
zpdst   =   zpsrc+2     ; zp pointer to EEPROM
eeprom  =   $A000       ; just to have it defined
code    =   $2100       ; etc.

        *=  $2000
prog    ldy #<code
        sty srcptr
        ldy #>code
        sty zpsrc+1
        ldy #<eeprom
        sty zpdst
        ldy #>eeprom
        sty zpdst+1

        ldy #$00
wloop   lda (zpsrc),y   ; get source
        sta (zpdst),y   ; tell EEPROM to write
cloop   cmp (zpdst),y   ; check to see if it's done
        bne cloop       ; nope, check again

        iny
        bne wloop
        rts             ; 256 bytes programmed.

This works well, but what if the EEPROM is faulty, or the code erroneously tries to program IO, open space, or ROM? What if the EEPROM is faulty and never responds with a success?  It fails badly by never moving forward. This is bad practice as the user waiting for the EEPROM is left completely in the dark when it fails.  Let’s add a timeout in the loop:

wloop   lda (zpsrc),y   ; get source
        sta (zpdst),y   ; tell EEPROM to write
        ldx #$00        ; prepare timeout and give EEPROM a little more time
cloop   cmp (zpdst),y   ; check to see if it's done
        beq wdone       ; nope, check again
        inx             ; count loops
        beq timeout     ; did we overflow? We failed!
        bne cloop       ; keep it as relocatable as we can

wdone   iny

The change is pretty straightforward:  the .X register is used as a timer since we can’t assume a generic system has a hardware timer available.  Datasheets indicate 10ms maximum is required for writing the EEPROM. Let’s allow twice that.  That requires the timeout to give up after a maximum of 20,000 cycles.

Let’s evaluate the loop’s minimal timing.

        sta (zpdst),y   ; doesn't count, this is where we start
        ldx #0          ; 2
cloop   cmp (zpdst),y   ; 5 (6 if page boundaries are crossed)
        beq wdone       ; 2 (exiting the loop doesnt count)
        inx             ; 2
        beq timeout     ; 2 (again, exiting doesnt count)
        bne cloop       ; 3

wdone   iny             ; we're out of the loop here.

The total count inside the loop is fourteen cycles. Considering it will loop a maximum of 256 times before giving up, that’s 3,584 cycles, or 3.5ms at 1MHz. We need to add another 16,340 cycles to the overall loop, which is 65 cycles per loop, minimum.  Since the loop exits when the write’s complete, waiting longer is harmless.

There’s a few ways to waste some time. NOP’ing it out is inefficient; requiring 22 NOPs. How about a nested loop?  We’re out of registers: .A is our value to compare, .X is the failure counter, and .Y is our index.  We’ll have to save and restore a register each time the loop runs, which is easiest to do with .A on NMOS 6502’s. We’ll use it for our nested loop counter.

Here’s our proposed nested loop:

        pha             ; 6     save .A for compare instruction
        lda #$00        ; 2      preset counter
dloop   clc             ; 2
        adc #$01        ; 2     count up
        bne dloop       ; 3     until overflow
        pla             ; 6     restore .A for compare instruction

This requires 8 cycles on entry, and 5 on exit. The exit may look off; it’s offset for the ‘bne dloop’ only taking two cycles instead of three when falling through.  The center is 8 cycles, occuring 256 times. That’s 2048 in all, or 2.048ms.  Adding the head and tail brings us to 2061 cycles in all.

With this, our compare loop interior becomes quite efficient at wasting time.  The interior moves up from 14 cycles to 2075 cycles, and repeating 256 times gives a delay of 531,200 cycles, offering over a half second for the EEPROM to finish its write before the routine gives up on the EEPROM.  Perfect!

Lets see the entire routine:

zpsrc   =   $42         ; zp source pointer
zpdst   =   zpsrc+2     ; zp pointer to EEPROM
eeprom  =   $A000       ; just to have it defined
code    =   $2100       ; etc.

        *=  $2000
prog    ldy #<code
        sty srcptr
        ldy #>code
        sty zpsrc+1
        ldy #<eeprom
        sty zpdst
        ldy #>eeprom
        sty zpdst+1

        ldy #$00        ; preset our index
wloop   lda (zpsrc),y   ; get source
        sta (zpdst),y   ; tell EEPROM to write
        ldx #$00        ;  prepare timeout and give EEPROM a little more time
cloop   cmp (zpdst),y   ;  check to see if it's done
        beq wdone       ;  nope, check again
        pha             ;   save .A for compare instruction
        lda #$00        ;   preset delay counter
dloop   clc             ;
        adc #$01        ;   count up
        bne dloop       ;   until overflow
        pla             ;  restore .A for compare instruction
        inx             ;  increment our timer
        beq timeout     ;   did we out of time? Error out
        bne cloop       ; continue to next byte.

wdone   iny
        sta (zpdst),y   ; tell EEPROM to write
cloop   cmp (zpdst),y   ; check to see if it's done
        bne cloop       ; nope, check again

        iny
        bne wloop
        clc             ; indicate things are fine.
        bcc exit        ; and exit
timeout sec             ; indicate write error
exit    rts             ; 256 bytes programmed.

There are some considerations to be kept in mind when programming the EEPROM.  First, you obviously can’t be running code from the device while programming it.  Data read back isn’t valid until the write’s complete.  Second, if you’re streaming from a file to the EEPROM, you’ll have to ensure it’s not being used to load the data from mass storage.  There are also many other considerations, such as the program’s inability to program more than 256 bytes at a time.

Covering some of these as well as enabling write mode on the Kickstarter 1112 will be covered later on, as this was intended to be more of an intro post for programming EEPROMs on your system.

-David

A simple network card

or, let’s start yet another thing!

I did make a note about increasing the content, and technical difficulties combined with attention span errors caused me to back-burner re-reviewing a certain PDF file. 😉

Enter the Imagewriter II localtalk option card, and what I’ve learned about it so far. Let’s begin.

The device

The card is a network adapter for Apple’s Imagewriter II series printers, allowing a direct connection to an appletalk or localtalk network. This gives a lab the ability to share the printer with all systems on the network without a switchbox or other trickery, while also allowing faster data transfers into the printer: Normally the printer communicates at 9600bps while appletalk runs at 230.4kbps.

I’d had curiosity about the card for quite a few years and spotted one on our favorite second hand website for an inexpensive sum. To this, I thought to myslef “why not? It looks like it shouldnt be too bad to figure out.”

Pre-exam before arrival

When examining the board in any images I could find online I got hints of a few key IC’s that indicate that the network adapter is in fact an intelligent device. Clearly visible in online images are the z8530 one would expect since Appletalk is generally driven by the Zilog SCC, as well as 8k of sram, a 65c02, and a rom-sized IC with an Apple copyright date.

This gives me the impression that the card in general is in fact an independent SBC, and the copyrighted IC is likely a mask or OTP ROM that provides the network services for the printer. In fact, I’d seen images of these cards with a ceramic DIP and paper sticker over a circular indentation, indicating that it is in fact compatible with an EPROM.

Then I considered some requirements of the 6500 series microprocessor family: Ram at pages 0 and 1 (unless you like a gelding system), and ROM at the end of memory (unless you like a comatose computer). Given the SRAM was easily identifiable as a 6264 in the auction page I viewed and the EPROM socket had the same number of pins, I guessed that the firmware was 8k in size.

The other chips aside from the oscillator weren’t well photographed in the auction page so other guessing must be done to distract myself from the waiting process while the card made its journey to my hands. For example, what sort of memory map might this processor see?

Remember again the need for ram at the bottom of memory and rom at the top, and that they’re 8k in size? A likely solution for decoding this easily is by using a 3-8 demultiplexor, likely our friend the 74LS138. Bets are that it’s involved, so lets keep our scope to eight devices.

So far we have four devices. I could see a few chips in the original pictures, a trio of 74ls374 8-bit latches, and a 74ls245 bus transciever. That gives us four more, leaving us at six. Add the SCC and we’re at seven, and some possible control logic regarding controlling data flow to get to eight. No rule says all areas have to be used, however.

The arrival

The board arrived in great condition, and I proceeded to take some good pictures.  Here’s a top shot for reference:

ImageWriter II Localtalk Card

Imagewriter Localtalk Option

When the board arrived I spent no time giving it a good examination. The 65c02 is rated at 2mhz, and a 3.6864mhz crystal clocks the system. A small non-volatile memory IC was also found, adding another device. The remainder are all standard 7400-series logic IC’s, one of which is a 74ls138 as predicted.

Since I can’t remove the CPU as I did for the IIEasy Print board, I’ll have to use a less automated method of finding the memory map. Time to get the continuity checker out!

Mapping the network (card)

If the 74ls138 is indeed used as a memory mapper, a few assumptions could be made. The first is that it’s enabled continuously, and the second that it’s controlled by the CPU’s clock to ensure correct write timing on the SRAM and latches. A quick check on /g1 and /g2 show them to be grounded. G3, the active high input however was not grounded or tied high. An intelligent guess led me to the phase2 line on the CPU, and my beeper agreed. Next is the blocksize decoded by the LS138. The I3 input on the 138 was quickly traced to A15 on the 65c02, I2 to A14, and I1 to A13. Eight 8k blocks of memory. With a single CPU and a 74ls138 the memory map is decoded.

Now let’s follow some outputs. Given the address bit connections, O0 should lead to the RAM chip, and as expected it did. O7 to the rom? Correct. Now where’s that SCC? I parked one probe on my meter on the /cs pin on the SCC and walked my way across the remaining outputs on the ‘138. Connected to O5 on the ‘138, the SCC starts at $a000 and ends at $BFFF.

All but two of the other outputs are traced so far. The remainder are a bit more complex and will be covered in a future update. A detailed memory map is below.

U9: 74ls138
pin name dest
7 /o7 [ e0-ff ] u3:20, u3:22 (8k eprom /oe, /ce) [$e000-$ffff]
9 /o6 [ c0-df ] nc?
10 /o5 [ a0-bf ] u2:33 (scc /cs)
11 /o4 [ 80-9f ] u7:1 (ls374 /oe)
12 /o3 [ 60-7f ] u5:1 (ls374 /oe)
13 /o2 [ 40-5f ] u6:11 (ls374 /load)
14 /o1 [ 20-3f ] nc?
15 /o0 [ 00-1f ] u4:20, u4:22 (8k sram /oe, /ce)

But what about the printer?

The Imagewriter II isn’t incredibly well documented, but a SAMS manual does have something resembling a schematic, in which a port for an ‘optional device’ is pinned out. It has 26 pins like the localtalk card, and has vcc and gnd in the same locations.

The pins are pretty straightforward: eight ‘AD’ pins for a multiplexed address/data bus, an ALE line to latch the address during an access, a DE line to enable the data transfers, and other various control lines.

AD0-7 head straight for the bus transciever to keep the network card off the imagewriter’s ADbus unless there’s a valid transaction, and then head into the three ‘374 8-bit latches. Likely, the latches are used for Address, Data in, and Data out (respectively). There’s some twisty logic to be traced so that this can be worked out.

And the software?

The firmware on the card is still hidden as I’ve not taken time to dump its contents and examine it. This may or may not be covered later as well, but I do find it interesting to find out how the card communicates with the printer. Perhaps other communications cards could be created- perhaps a parallel interface or other networking option?

And then…

To be honest, I didn’t really intend on adding this to my Imagewriter II, since printers aren’t really my thing these days. I just find vintage hardware interesting, and enjoy the process of mapping simple systems to feed my imagination of what could have been done with them had we known what they really were made of.

(Proabaly) more later. 😉