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.