Jump to content

Photo

SaveKey for Dummies!

SaveKey AtariVox Assembler

23 replies to this topic

#1 Thomas Jentzsch OFFLINE  

Thomas Jentzsch

    Thrust, Jammed, SWOOPS!, Boulder Dash, THREE·S, Star Castle

  • 23,972 posts
  • Always left from right here!
  • Location:Düsseldorf, Germany, Europe, Earth

Posted Sun Mar 6, 2016 3:25 AM

Hi,
I found no easy to use code for loading and storing on a SaveKey or AtariVox. Therefore I am presenting my code here.
 
All it takes are:

  • add the attached include file to your code's directory
  • define the three variables/constants at the beginning of the code
  • call WriteSaveKey for storing your score etc.
  • call ReadSaveKey for reading what you have stored

Notes:

  • if you have multiple scores to store (e.g. different game modes), you have to modify the address used in SetupSaveKey.
  • the X register is not used in any i2c subroutine.
  • after calling WriteSaveKey you must wait at least 5ms (~80 scan lines) before accessing the SaveKey again.
  • update i2c v2.3 attached, fixes unwanted noise issues when using an AtariVox
skBuffer    = <score RAM>       ; define the RAM address you want to store
SK_BYTES    = <n>               ; define how many bytes your want to store
SAVEKEY_ADR = $xxxx             ; ask Albert for a free slot! 
 
    include "i2c_v2.3.inc"      ; a highly optimized (for space) version   
    i2c_subs                    ; this makes the i2c macros of the include file known to the code 

;-------------------------------------------------------------------------------
WriteSaveKey SUBROUTINE         ; total cycles = 1923 (for 3 bytes)
;-------------------------------------------------------------------------------
; setup SaveKey:
    jsr     SetupSaveKey        ; 6+927
    bcc     .noSKfound          ; 2/3

; write high score:
    ldx     #SK_BYTES-1         ; 2 = 937   
.loopWriteSK
    lda     skBuffer,x          ; 4
    jsr     i2c_txbyte          ;6+296      transmit to EEPROM
    dex                         ; 2
    bpl     .loopWriteSK        ; 2/3=932

; stop write:
    jsr     i2c_stopwrite       ; 6+42=48   terminate write and commit to memory
.noSKfound
    rts                         ; 6

;-------------------------------------------------------------------------------
ReadSaveKey SUBROUTINE          ; total cycles = 2440 (for 3 bytes)
;-------------------------------------------------------------------------------
; setup SaveKey:
    jsr     SetupSaveKey        ;6+927
    bcc     .noSKfound          ; 2/3

; start read:
    jsr     i2c_stopwrite       ;6+42       end of "fake" write
    jsr     i2c_startread       ;6+284      Start signal and $a1 command byte

; read high score:
    ldx     #SK_BYTES-1         ; 2 = 1275
.loopReadSK
    jsr     i2c_rxbyte          ;6+333      read byte from EEPROM
    cmp     #$ff                ; 2         EEPROM slot empty? (we are assuming $ff for uninitialized space)
    bne     .skipEmptySK        ; 2/3        no, skip clear
    lda     #0                  ; 2         clear EEPROM slot
.skipEmptySK
    sta     skBuffer,x          ; 4
    dex                         ; 2
    bpl     .loopReadSK         ; 2/3=1061

; stop read:
    jsr     i2c_stopread        ;6+92=98    terminate read
.noSKfound
    rts                         ; 6

;------------------------------------------------------------------------------
SetupSaveKey SUBROUTINE         ; = 927
;------------------------------------------------------------------------------
; detect SaveKey:
    jsr     i2c_startwrite      ;6+312
    bne     .exitSK             ; 2/3

; setup address:
    clv                         ; 2
    lda     #>SAVEKEY_ADR       ; 2         upper byte of address
    jsr     i2c_txbyte          ;6+296
    lda     #<SAVEKEY_ADR       ; 2         lower byte offset
    jmp     i2c_txbyte          ;3+296      returns C==1

.exitSK
    clc
    rts

; 176 bytes in total (less if you inline the subroutines)

I hope this answers all questions. Else let me know. icon_smile.gif

SaveKey & AtariVox memory allocation list

Attached Files


Edited by Thomas Jentzsch, Wed Nov 2, 2016 1:10 PM.


#2 Lumi OFFLINE  

Lumi

    Star Raider

  • 82 posts

Posted Sun Mar 6, 2016 8:36 AM

This is brilliant! Thank you so much!


Edited by Lumi, Mon Mar 7, 2016 4:20 PM.


#3 gauauu OFFLINE  

gauauu

    Moonsweeper

  • 408 posts
  • Location:Illinois

Posted Sun Mar 6, 2016 2:11 PM

Cool, I was looking for documentation about this last night and couldn't find any.

How popular are these SaveKeys? I'm trying to decide if it's something I want to support in my game.

#4 Wickeycolumbus OFFLINE  

Wickeycolumbus

    Red Sea Crosser

  • 5,162 posts
  • Location:Michigan

Posted Sun Mar 6, 2016 2:29 PM

Cool!  Thanks for sharing that, Thomas.

 

Cool, I was looking for documentation about this last night and couldn't find any.

How popular are these SaveKeys? I'm trying to decide if it's something I want to support in my game.

 

Somewhat popular from what I can tell.  They are integrated into the AtariVox, and those are supported by a number of homebrews.   If you have room for it, you may as well add the extra feature, to give people a reason to own one.



#5 Thomas Jentzsch OFFLINE  

Thomas Jentzsch

    Thrust, Jammed, SWOOPS!, Boulder Dash, THREE·S, Star Castle

  • Topic Starter
  • 23,972 posts
  • Always left from right here!
  • Location:Düsseldorf, Germany, Europe, Earth

Posted Sun Mar 6, 2016 3:12 PM

Currently ~30 homebrew games support the SaveKey (see link in my first post).

#6 alex_79 OFFLINE  

alex_79

    Stargunner

  • 1,195 posts
  • Location:Italy

Posted Sun Mar 6, 2016 4:14 PM

Thank you for sharing that! :thumbsup:



#7 Lumi OFFLINE  

Lumi

    Star Raider

  • 82 posts

Posted Mon Mar 7, 2016 4:28 PM

By the way, just so nobody makes my mistake, ReadSaveKey will not return anything if it encounters $FF at any point. I didn't realize that initially and was confused.


Edited by Lumi, Mon Mar 7, 2016 4:34 PM.


#8 Thomas Jentzsch OFFLINE  

Thomas Jentzsch

    Thrust, Jammed, SWOOPS!, Boulder Dash, THREE·S, Star Castle

  • Topic Starter
  • 23,972 posts
  • Always left from right here!
  • Location:Düsseldorf, Germany, Europe, Earth

Posted Mon Mar 7, 2016 4:38 PM

I thought the comment would be sufficient, no?

#9 Lumi OFFLINE  

Lumi

    Star Raider

  • 82 posts

Posted Mon Mar 7, 2016 4:49 PM

Yes, I just didn't see that part initially. I brought it up just in case someone else misses it or doesn't read it.


Edited by Lumi, Mon Mar 7, 2016 4:49 PM.


#10 enthusi OFFLINE  

enthusi

    Dragonstomper

  • 533 posts
  • Location:Potsdam, Germany

Posted Tue Mar 8, 2016 9:02 AM

A little too late for me ;-)

However, I used your code from Threes as you know.

Thanks alot in general, this is not that well documented.

For the record, reading 2 bytes takes roughly 26 PAL lines including the Setup.



#11 Thomas Jentzsch OFFLINE  

Thomas Jentzsch

    Thrust, Jammed, SWOOPS!, Boulder Dash, THREE·S, Star Castle

  • Topic Starter
  • 23,972 posts
  • Always left from right here!
  • Location:Düsseldorf, Germany, Europe, Earth

Posted Tue Mar 8, 2016 9:10 AM

For the record, reading 2 bytes takes roughly 26 PAL lines including the Setup.

...and 26 NTSC lines too! icon_mrgreen.gif

#12 Jinroh OFFLINE  

Jinroh

    Dragonstomper

  • 697 posts
  • Catgirl Maid Lover

Posted Tue Mar 8, 2016 11:25 AM

Awesome! Thanks so much for sharing Thomas!

 

I was thinking about poking around the SaveKey so this was a very nice bit of code to read. :D



#13 gauauu OFFLINE  

gauauu

    Moonsweeper

  • 408 posts
  • Location:Illinois

Posted Mon Mar 14, 2016 9:44 PM

;------------------------------------------------------------------------------
SetupSaveKey SUBROUTINE         ; = 833
;------------------------------------------------------------------------------
; detect SaveKey:
    jsr     i2c_startwrite      ;6+280
    bne     .exitSK             ; 2/3

; setup address:
    clv                         ; 2
    lda     #>SAVEKEY_ADR       ; 2         upper byte of address
    jsr     i2c_txbyte          ;6+264
    lda     #<SAVEKEY_ADR       ; 2         lower byte offset
    jmp     i2c_txbyte          ;3+264      returns C==1   ; <======================THIS LINE?

.exitSK
    clc
    rts


Is that jmp instruction just before .exitSK really a jmp? Or is that supposed to be a jsr?

Edit: Oh, I get it, after staring at it long enough.  Since i2c_txbyte will do the rts for you, you can use its rts to return from SetupSaveKey. Nevermind!


Edited by gauauu, Mon Mar 14, 2016 9:51 PM.


#14 Omegamatrix OFFLINE  

Omegamatrix

    Quadrunner

  • 6,233 posts
  • Location:Canada

Posted Mon Mar 14, 2016 10:30 PM

; setup address:
    clv                         ; 2
    lda     #>SAVEKEY_ADR       ; 2         upper byte of address
    jsr     i2c_txbyte          ;6+264
    lda     #<SAVEKEY_ADR       ; 2         lower byte offset
    jmp     i2c_txbyte          ;3+264      returns C==1

.exitSK
    clc
    rts

; 174 bytes in total (less if you inline the subroutines)

There is a small cycle optimization here. The first instruction of of i2c_txbyte is EOR #$FF. Since you are loading constants this can be applied ahead of time and that instruction skipped over saving 4 cycles. You might be able to save more too if the carry is known to be set or cleared.



#15 Thomas Jentzsch OFFLINE  

Thomas Jentzsch

    Thrust, Jammed, SWOOPS!, Boulder Dash, THREE·S, Star Castle

  • Topic Starter
  • 23,972 posts
  • Always left from right here!
  • Location:Düsseldorf, Germany, Europe, Earth

Posted Tue Mar 15, 2016 1:28 AM

Yes, there is room for optimization, cycles and ROM (e.g. inline and share SetupSaveKey, merging .noSKFound/.exitSK and the calls for i2c_stopwrite/read...). But I wanted to keep the code as simple and understandable as possible. 

 

The amount of cycles which can be saved is very small (maybe 1%). But if anyone needs to find a few extra bytes ROM, I can help optimizing.



#16 gauauu OFFLINE  

gauauu

    Moonsweeper

  • 408 posts
  • Location:Illinois

Posted Fri Apr 22, 2016 9:57 AM

Is the best way to reserve a slot still to fill out the main contact form on AtariAge? 

 

(Just making sure, I sent something there and hadn't heard back in awhile. I'm happy to wait longer if it everyone's just busy, but didn't want to be waiting forever if I'm doing it wrong)



#17 Thomas Jentzsch OFFLINE  

Thomas Jentzsch

    Thrust, Jammed, SWOOPS!, Boulder Dash, THREE·S, Star Castle

  • Topic Starter
  • 23,972 posts
  • Always left from right here!
  • Location:Düsseldorf, Germany, Europe, Earth

Posted Fri Apr 22, 2016 10:27 AM

Yup.



#18 Thomas Jentzsch OFFLINE  

Thomas Jentzsch

    Thrust, Jammed, SWOOPS!, Boulder Dash, THREE·S, Star Castle

  • Topic Starter
  • 23,972 posts
  • Always left from right here!
  • Location:Düsseldorf, Germany, Europe, Earth

Posted Wed Nov 2, 2016 12:58 PM

After 8 years, we found a small issue in the i2c_v2.2.inc, which caused some noise when using an AtariVox. Therefore I attached an updated version 2.3 above.

Note: This version requires 2 more bytes ROM and is slightly slower when writing a byte to SaveKey/AtariVox. The timing has been adjusted accordingly.

#19 Dionoid OFFLINE  

Dionoid

    Chopper Commander

  • 132 posts
  • Location:Leiden, Netherlands

Posted Mon Sep 17, 2018 12:02 PM

This library is very easy to integrate. Thanks, Thomas! 

 

Because storing a 3-byte highscore takes 25 scanlines, I had to turn the screen to black during a single frame at the end of a highscore game. Otherwise that frame would need more than 262 scanlines.

Are there any best practices on when/where to call 'jsr WriteSaveKey' ?



#20 Dionoid OFFLINE  

Dionoid

    Chopper Commander

  • 132 posts
  • Location:Leiden, Netherlands

Posted Mon Sep 17, 2018 12:40 PM

Wait... I might just be able to split-up the writing of the 3 highscore bytes into 3 successive frames.

 

[Edit]

Splitting worked for me! Writing of a single byte to the SaveKey takes 1292 cycles, for which I have just enough time during overscan.

Note that writing a single byte at a time is not optimal, because the transmit-start and transmit-end in i2c uses most of the cycles. But with 2600 programming sometimes you just need to do what works :-)


Edited by Dionoid, Wed Sep 19, 2018 9:09 AM.


#21 iesposta OFFLINE  

iesposta

    River Patroller

  • 3,888 posts
  • Retro-gaming w/my VCS
  • Location:Pennsylvania

Posted Sun Oct 7, 2018 11:51 AM

Although I haven't used my allocated space yet, you can write and test your game by saving to the "scratchpad" area, correct?

 

In batari Basic, is very simple to: include anything.asm

 and any needed variables/constants are defined or reused from the ones available depending on which "kernel" is used, and defined in the batari Basic way at the start of your code.



#22 Thomas Jentzsch OFFLINE  

Thomas Jentzsch

    Thrust, Jammed, SWOOPS!, Boulder Dash, THREE·S, Star Castle

  • Topic Starter
  • 23,972 posts
  • Always left from right here!
  • Location:Düsseldorf, Germany, Europe, Earth

Posted Sun Oct 7, 2018 11:54 AM

Although I haven't used my allocated space yet, you can write and test your game by saving to the "scratchpad" area, correct?

Yes.

Or any area you like to overwrite. :)

#23 Thomas Jentzsch OFFLINE  

Thomas Jentzsch

    Thrust, Jammed, SWOOPS!, Boulder Dash, THREE·S, Star Castle

  • Topic Starter
  • 23,972 posts
  • Always left from right here!
  • Location:Düsseldorf, Germany, Europe, Earth

Posted Sat Nov 10, 2018 2:52 AM

Wait... I might just be able to split-up the writing of the 3 highscore bytes into 3 successive frames.

 

[Edit]

Splitting worked for me! Writing of a single byte to the SaveKey takes 1292 cycles, for which I have just enough time during overscan.

Note that writing a single byte at a time is not optimal, because the transmit-start and transmit-end in i2c uses most of the cycles. But with 2600 programming sometimes you just need to do what works :-)

I am no expert of flash memory wear, but IMO it is always a good idea to minimize flash writes.

 

Therefore I am not sure if yours is the optimal way, because that way you are flashing the whole 64 bytes block three times instead of only once. But probably that is still more than fine.



#24 RevEng OFFLINE  

RevEng

    Bit Player

  • 5,169 posts
  • Location:bottom of the stack

Posted Sun Dec 9, 2018 6:30 PM

I wouldn't sweat more writes being traded off for stable and attractive frames.

The spec sheet for the 24LC256 EEPROM says it's good for more than a million write/erase cycles. Even if every developer used 3 writes per save, that still leaves over three hundred thousand saves per vox.





Also tagged with one or more of these keywords: SaveKey, AtariVox, Assembler

0 user(s) are browsing this forum

0 members, 0 guests, 0 anonymous users