Close
0%
0%

ROM Disassembly - AlphaSmart Pro

Wherein I disassemble the ROM from a vintage typewriter-thing

Similar projects worth following
This logs my disassembly journey of this vintage device.
Some have expressed curiosity about my process of reversing ROMs, so maybe this journal will provide some insight.
It's not that hard, and a lot of fun! It's also educational to see how folks think about product construction. And every now and then you find a piece of buried treasure.

Why do I do this?  I don't know.  I've been impelled to disassemble physical things since I was a child, much to the consternation of my family during gifting events.  But eventually I was able to put things back together again.  Now I disassemble logical things.

I think that my enjoyment of disassembly is similar to how folks enjoy video games, or crossword.  You have to keep a large-ish number of unknowns in you mind (and also have good note taking skills to page out stuff).  The moment of revelation when a crucial piece of information resolves many unknowns at once is a gratifying experience.

I think it's a useful exercise in seeing how other folks create products, and also just to keep the mind active.  In Real Life™, I have disassembled OS components to figure out how undocumented internals worked, and those insights facilitated the creation of novel products.  However, these vintage products are usually small enough in scope that the exercise is less like day-job work and more like a pastime.

AlphaSmart001-20230301b.lst

Current disassembly listing; work in progress, will update periodically

plain - 306.99 kB - 03/01/2023 at 19:49

Download

Holes-001.png

for the curious; holes in the scancode map

Portable Network Graphics (PNG) - 5.54 kB - 02/25/2023 at 14:26

Preview

AlphaSmartPro-keyboard-002.jpg

Keyboard with AlphaSmart Pro scan codes overlaid!

JPEG Image - 287.21 kB - 02/22/2023 at 23:03

Preview

lt1173.pdf

DC-DC converter used to boost 3v to 5v, and also provides low battery indication.

Adobe Portable Document Format - 261.73 kB - 02/18/2023 at 17:47

Preview

AlphaSmart001.HEX

the ROM dump! courtesy of my friend [Eric]

hex - 22.51 kB - 02/15/2023 at 19:52

Download

View all 9 files

  • 20230225a - Displaying the Current File

    ziggurat2902/26/2023 at 17:17 0 comments

    sub_F374 has caught my interest because it seems to be painting the LCD with the currently selected file's contents according to several state variables that I have not fully reverse-engineered.

    The routine begins like this:

    F374             sub_F374:
    F374 BD F6 18        jsr     clearHomeLCD_F618 ; Clear LCD set cursor pos 0
    F377 FE 01 20        ldx     word_120        ; XXX file related ???
    F37A BD F3 D5        jsr     sub_F3D5
    F37D DF 4E           stx     word_4E         ; scratch; /this/ keyboard scan column mask (rectus)
    F37F FE 01 20        ldx     word_120        ; XXX file related ???
    ...

    word_120 has been vexing for me for some time.  It's file-related and next to other file-related variables, but I haven't been able to make it out yet what it does.  Maybe this will be an opportunity to find out.

    ...
    0120 ?? ??       word_120:       rmb 2      ; XXX file related ???
    0122 ?? ??       curfileIns_122: rmb 2      ; XXX file related (current file insert ptr???) .0
    0124 ?? ??       curfileEnd_124: rmb 2      ; XXX file related (current file ptr end char (one past last)???) .4
    0126 ?? ??       curFileRgnLast_126:rmb 2   ; this file; file region end addr (inclusive)
    0128 ?? ??       curFileRgnFirst_128:rmb 2  ; this file; file region start addr
    012A ?? ?? ?? ??+aFileState_12A: rmb $30    ; XXX an array of 8 3-word file structures
    ...

    These various file-related variables strike me as curious from a design perspective because they are essentially copies of stuff that is already in aFileState_12A and also in ROM in aFilePartitions_FB24, but just for the current file (as per thisFileNo_15D).  I'm not sure why this apparent redundancy is done, but maybe that will become clearer later.  (Or it may just be an oddity -- humans sometimes do inexplicable things.)

    The sub_F3D5 is called early, just after loading X with the word_120 value, presumably as a parameter:

    F3D5             sub_F3D5:
    F3D5 DF 44           stx     word_44         ; XXX scratch
    F3D7 86 28           ldaa    #40
    F3D9 97 48           staa    byte_48         ; XXX counter; 100/40
    F3DB 18 FE 01 24     ldy     curfileEnd_124  ; XXX file related (current file ptr end char (one past last)???) .4
    F3DF 18 DF 42        sty     word_42
    F3E2 BD EC 80        jsr     selectRAMpageForFile_EC80 ; select in the relevant RAM page based on file number (byte_15D)
    F3E5             loop_F3E5:
    F3E5 9C 42           cpx     word_42
    F3E7 27 2A           beq     loc_F413
    F3E9 A6 00           ldaa    0,x
    F3EB 81 0D           cmpa    #$D
    F3ED 27 1E           beq     loc_F40D
    F3EF 7A 00 48        dec     byte_48         ; XXX counter; 100/40
    F3F2 27 03           beq     loc_F3F7
    F3F4 08              inx
    F3F5 20 EE           bra     loop_F3E5
    F3F7             loc_F3F7:
    F3F7 BD F4 1A        jsr     sub_F41A
    F3FA 29 11           bvs     loc_F40D
    F3FC             loc_F3FC:
    F3FC 09              dex
    F3FD A6 00           ldaa    0,x
    F3FF BD F4 1A        jsr     sub_F41A
    F402 29 09           bvs     loc_F40D
    F404 9C 44           cpx     word_44         ; XXX scratch
    F406 26 F4           bne     loc_F3FC
    F408 DE 44           ldx     word_44         ; XXX scratch
    F40A C6 27           ldab    #$27 ; '''
    F40C 3A              abx
    F40D             loc_F40D:
    F40D BD EC 79        jsr     selectRAMPage0_EC79 ; select 32 KiB RAM page 0
    F410 08              inx
    F411 0A              clv
    F412 39              rts
    F413             loc_F413:
    F413 BD EC 79        jsr     selectRAMPage0_EC79 ; select 32 KiB RAM page 0
    F416 DE 44           ldx     word_44         ; XXX scratch
    F418 0B              sev
    F419 39              rts

    As annotated, the byte_48 location seemed to be a counter of sorts based upon cross-references and was loaded either with 100 or 40.  40 seems coincidental with the 40 columns of LCD.  I don't know what 100 is for.

    But the loop "loop_F3E5" is particularly interesting since it seems to be whizzing through a file (starting at word_120) and looking for a carriage return (0x0d).  If found, it will go one past that character and clear the V bit (a common technique in this firmware to indicate 'success') and leave.

    If it does not find in within the 40 chars, then it starts to look backwards (up to the point at word_120) for some sort of characterization provided by sub_F41A:

    F41A             sub_F41A:
    F41A 81 0D           cmpa    #$D
    F41C 27 0E           beq     loc_F42C
    F41E 81 09           cmpa    #9
    F420 27 0A           beq     loc_F42C
    F422 81 20           cmpa    #$20 ; ' '
    F424 27 06           beq     loc_F42C
    F426 81 2D           cmpa    #$2D ; '-'
    F428 27 02           beq     loc_F42C
    F42A 0A              clv
    F42B 39              rts
    F42C             loc_F42C:
    F42C 0B              sev
    F42D 39              rts

    Which is pretty clear to indicate 'tab, or cr, or space, or hyphen'.  In other words, traditional line breaking characters.

    So sub_F3D5...

    Read more »

  • 20230223a - Special Keys and File Ops

    ziggurat2902/25/2023 at 15:03 1 comment

    While revisiting key handler handleKeyScanCode_EE37 and back annotating scan codes into the code, I wind up with this:

    EE37             handleKeyScanCode_EE37:
    EE37 13 63 01 04     brclr   bCloverKeyDown_63 1 noclover_EE3F ; true if 'clover key down'
    EE3B 12 61 01 C9     brset   bAltKeyDown_61 1 optionclover_EE08 ; true if 'alt/option key down'
    EE3F             noclover_EE3F:
    EE3F 13 64 01 02     brclr   bShiftKeyDown_64 1 cont_EE45 ; true if 'shift key down'
    EE43 8A 80           oraa    #$80            ; set high bit in base scan code to mark as shifted
    EE45             cont_EE45:
    EE45 97 70           staa    scratch_70      ; save the scan code for later
    EE47 81 56           cmpa    #$56            ; 'Clear File' scan code
    EE49 27 DD           beq     doDeleteFile_EE28
    EE4B 81 2B           cmpa    #$2B            ; 'Delete' scan code
    EE4D 27 28           beq     doDeleteChar_EE77 ; delete a char in file?
    EE4F 81 35           cmpa    #$35            ; 'arrow Left' scan code
    EE51 27 2A           beq     doLeft_EE7D
    EE53 81 34           cmpa    #$34            ; 'arrow Right' scan code
    EE55 27 36           beq     doRight_EE8D
    EE57 81 37           cmpa    #$37            ; 'arrow Up' scan code
    EE59 27 40           beq     doUp_EE9B
    EE5B 81 15           cmpa    #$15            ; 'arrow Down' scan code
    EE5D 27 4A           beq     doDown_EEA9
    EE5F 81 66           cmpa    #$66            ; 'Home' scan code
    EE61 27 54           beq     doHome_EEB7
    EE63 81 75           cmpa    #$75            ; 'End' scan code
    EE65 27 55           beq     doEnd_EEBC
    EE67 81 2D           cmpa    #$2D            ; 'Caps lock' scan code
    EE69 27 56           beq     toggleCapLock_EEC1
    EE6B 7E EF 67        jmp     loc_EF67
    EE6E             loc_EE6E:
    EE6E 7E EE C9        jmp     normalChar_EEC9
    EE71 BD F0 13        jsr     sub_F013        ; XXX prepare to send (flags and prompt)
    EE74 7E EE E0        jmp     leave_EEE0
    ...

    Now things are much clearer!  The various 'do XXX' locations are usual small, e.g.:

    EEB7             doHome_EEB7:
    EEB7 BD F1 6B        jsr     sub_F16B
    EEBA 20 1E           bra     sub_EEDA

    So it is reasonable that sub_F16B does whatever the 'Home' key does (probably move the insert point to the first char position and update the display as appropriate).

    Others have modifiers:

    EEA9             doDown_EEA9:
    EEA9 13 63 01 05     brclr   bCloverKeyDown_63 1 doDownOnce_EEB2 ; true if 'clover key down'
    EEAD BD EF 3D        jsr     sub_EF3D        ; XXX clover-down whacks F0DF handler four times
    EEB0 20 28           bra     sub_EEDA
    EEB2             doDownOnce_EEB2:
    EEB2 BD F0 DF        jsr     sub_F0DF        ; XXX down arrow handler
    EEB5 20 23           bra     sub_EEDA

    So it seems that in this case that the cloverleaf modifier would go down a 'page' (of 4 LCD lines), whereas the unmodified version moves down a line.

    In all these mini-handlers, there is a jump to sub_EEDA, which is brief:

    EEDA             sub_EEDA:
    EEDA BD F3 74        jsr     sub_F374
    EEDD BD F2 56        jsr     sub_F256
    EEE0             leave_EEE0:
    EEE0 39              rts

    This routine has a lot (22) references throughout the code.  Taking a quick peek at sub_F374, it starts with a call to clearHomeLCD_F618, fiddles with file related stuff, sends chars to the screen, and then ends with the 'caps lock indicator' mentioned a couple days ago.  So this feels like maybe the 'update screen for file state' function.  One sub-method was interesting:

    F62D             sub_F62D:
    F62D 81 09           cmpa    #9              ; less than TAB?
    F62F 2D 19           blt     bogoChar_F64A
    F631 85 80           bita    #$80            ; high ASCII?
    F633 26 15           bne     bogoChar_F64A
    F635 81 5C           cmpa    #'\'            ; ascii backslash looks like Yen on LCD, so translate
    F637 27 15           beq     xlatbackslash_F64E
    F639 81 7E           cmpa    #'~'            ; ascii tilde looks like right arrow on LCD, so translate
    F63B 27 15           beq     xlattildeswinton_F652
    F63D 81 0D           cmpa    #$D
    F63F 27 15           beq     xlatcr_F656
    F641 81 09           cmpa    #9
    F643 27 15           beq     xlattab_F65A
    F645 16              tab
    F646             loc_F646:
    F646 BD F6 74        jsr     sendLCDbyteB_F674 ; send byte in B to LCD (w/ctrl as per 0x5b)
    F649 39              rts
    F64A             bogoChar_F64A:
    F64A C6 3F           ldab    #'?'            ; junk character maps to '?'
    F64C 20 F8           bra     loc_F646
    F64E             xlatbackslash_F64E:
    F64E C6 8F           ldab    #$8F            ; LCD backslash char
    F650 20 F4           bra     loc_F646
    F652             xlattildeswinton_F652:
    F652 C6 F3           ldab    #$F3            ; LCD infinity is an approximation of tilde
    F654 20 F0           bra     loc_F646
    F656             xlatcr_F656:
    F656 C6 7F           ldab    #$7F            ; LCD left arrow indicates hard CR
    F658 20 EC           bra     loc_F646
    F65A             xlattab_F65A:
    F65A C6 7E           ldab    #$7E            ; LCD right arrow indicates TAB
    F65C 20 E8           bra     loc_F646

    This clearly translates file ASCII into LCD characters.  Most of the LCD follows ASCII, but a few symbol positions have different glyphs, so...

    Read more »

  • 20230222c - Finishing Up Key Matrix

    ziggurat2902/24/2023 at 18:06 4 comments

    Understanding of all the elements of scanCodeStructs_F8C5 enabled me to map out the rest of the keys (i.e. the non-textual ones).  This was quite tedious and there were a few oddities.

    The cloverleaf (command, meta) key is ambiguous in the scan code table, and although it has a distinct scan code for the left and right variant, the fact that they both map the the 'left cloverleaf' ADB code makes it ambiguous as to which is which.  So I can only make a guess there.

    Similarly, the alt (option) key has the same kind of ambiguity -- there is a left and right variant, but I cannot associate scan code to physical key from the code alone.  The physical distribution of scan codes on the keyboard might usually give some clues (e.g., all the row X items are clustered in an area), but not this time, alas.

    (on the other hand, the Shift keys are differentiated)

    The return key is interesting.  There is a 'return' key in the usual place, and also an 'enter' key in the upper right.  On PS/2, these behave the same, but under ADB they are distinct key codes, with the 'enter' key mapping to 'numeric keypad enter'.  Who knows why?

    Lastly, the pause/printscreen key has a PS/2 scan code of 0x84, which is not valid on scan set 2.  If you're familiar with PS/2, you'll perhaps remember that key is treated strangely, so I'm expecting maybe there is some special code for that.  We'll find out.

    I annotated an image of the keyboard with the scan codes overlaid upon the keys.  Along the way I got an unexpected insight.  Because I'm not particularly savvy with the graphics tools, to label the keys with their scan codes I made a 'layer' containing 00 - 7F, and then cut out snippets of that and moved them on top of their keys.  Literally cut and paste.  (I wanted an opaque background to the text and couldn't figure out another way.)  The result is that I have left over a square with holes where are valid scan codes and remaining text for the invalid ones.  Since I serendipitously laid this out rectangularly, I can see some things that were not obvious before.  I had noted in the scanKbd_E5A0: that there was special handling for columns 15, 8, 9, and 6.  Now it is clearer why!

    • column 15 has two keys on it -- the two Shift keys
    • column 8 has one key on it -- the Ctrl key
    • column 9 has two keys on it -- the two Alt keys
    • column 6 is treated extra special to test for two possible keys (among the others there) -- the two cloverleaf keys

    So the special handling of these columns (generally containing only a specific modifier key) is to set a flag.  Here's the dispatch:

    ...
    E5CB 8C 80 00   cpx     #$8000
    E5CE 27 5C      beq     handleCol15_E62C ; col 15 only has the two Shift keys
    E5D0 8C 02 00   cpx     #$200
    E5D3 27 71      beq     handleCol9_E646 ; col 9 only has two Alt keys
    E5D5 8C 01 00   cpx     #$100
    E5D8 27 5F      beq     handleCol8_E639 ; col 8 only has Ctrl key
    E5DA 8C 00 40   cpx     #$40
    E5DD 27 76      beq     handleCol6_E655 ; col 6 has two cloverleaf keys (and some others)
    ...

    and here's a typical example of the handling:

    ...
    E62C             handleCol15_E62C:
    E62C 5D              tstb                    ; any col 15? (only shift keys here)
    E62D 26 05           bne     col15down_E634
    E62F 7F 00 64        clr     bShiftKeyDown_64 ; true if 'shift key down'
    E632 20 AB           bra     processKbdBits_E5DF
    E634             col15down_E634:
    E634 14 64 FF        bset    bShiftKeyDown_64 $FF ; true if 'shift key down'
    E637 20 A6           bra     processKbdBits_E5DF
    ...

    with col 6 being just slightly different since there are other keys on that column:

    ...
    E655             handleCol6_E655:
    E655 C5 0A           bitb    #$A             ; test only the cloverleaf keys; there are others on this col
    E657 26 0C           bne     cont_E665
    E659 7F 00 63        clr     bCloverKeyDown_63 ; true if 'clover key down'
    E65C 7E E5 DF        jmp     processKbdBits_E5DF
    ...

    So that was a handy find and now I've got more commenting and labeling to do...

  • 20230222b A Tale of Two Translations

    ziggurat2902/24/2023 at 15:06 0 comments

    There are several translation routines found during the file sending stuff that translate one character value into another one.  Some we understand, like this one:

    F4DC             sub_F4DC:
    F4DC 84 7F           anda    #$7F            ; reset high bit
    F4DE 16              tab
    F4DF 4F              clra
    F4E0 05              lsld
    F4E1 05              lsld
    F4E2 C3 F8 C5        addd    #scanCodeStructs_F8C5
    F4E5 8F              xgdx                    ; X is scan code struct for scan code originally in A
    F4E6 A6 01           ldaa    1,x
    F4E8 39              rts

    This is a straightforward translation of AlphSmart Pro scan code to the PS/2 scan code (since we have now figured out offset +1 is the PS/2 code).  This is probably used when the device is operating as a conventional keyboard (i.e., not 'standalone mode').

    This one's fancier, but we can see what's going on:

    F4B6             sub_F4B6:
    F4B6 84 7F           anda    #$7F            ; reset high bit
    F4B8 CE F8 C5        ldx     #scanCodeStructs_F8C5
    F4BB             loop_F4BB:
    F4BB 8C FA C5        cpx     #byte_FAC5      ; (end of array)
    F4BE 27 18           beq     notfound_F4D8
    F4C0 A1 02           cmpa    2,x             ; match either lower case
    F4C2 27 0A           beq     matchLower_F4CE
    F4C4 A1 03           cmpa    3,x             ; or upper case
    F4C6 27 0A           beq     matchUpper_F4D2
    F4C8 08              inx
    F4C9 08              inx
    F4CA 08              inx
    F4CB 08              inx                     ; next struct...
    F4CC 20 ED           bra     loop_F4BB
    F4CE             matchLower_F4CE:
    F4CE A6 01           ldaa    1,x
    F4D0 20 04           bra     cont_F4D6
    F4D2             matchUpper_F4D2:
    F4D2 A6 01           ldaa    1,x
    F4D4 8A 80           oraa    #$80
    F4D6             cont_F4D6:
    F4D6 0A              clv
    F4D7 39              rts
    F4D8             notfound_F4D8:
    F4D8 86 4A           ldaa    #$4A
    F4DA 0B              sev
    F4DB 39              rts

    Here a linear search is done through the scanCodeStructs_F8C5 trying to match either the upper or lower case ASCII version of the entry, and then plucking out the PS/2 scan code.  So this is an 'ASCII to PS/2' translation, and is probably used when sending files (which are in ASCII) over the keyboard port.

    This one also involved the scanCodeStructs_F8C5 array, but it uses offset +0 that I do not yet understand:

    F482             sub_F482:
    F482 85 80           bita    #$80            ; remember high bit status (using byte_71 scratch var) for later
    F484 27 05           beq     cont_F48B
    F486 14 71 FF        bset    byte_71 $FF
    F489 20 03           bra     cont_F48E
    F48B             cont_F48B:
    F48B 7F 00 71        clr     byte_71
    F48E             cont_F48E:
    F48E 84 7F           anda    #$7F            ; mask off high bit in key scan code
    F490 16              tab
    F491 4F              clra
    F492 05              lsld
    F493 05              lsld                    ; effectively scan code *= 4
    F494 C3 F8 C5        addd    #scanCodeStructs_F8C5
    F497 8F              xgdx                    ; X is now scan code structure pointer
    F498 A6 00           ldaa    0,x             ; F8C5[].0
    F49A 81 FF           cmpa    #$FF
    F49C 27 08           beq     invalidKey_F4A6
    F49E 13 71 01 02     brclr   byte_71 1 loc_F4A4 ; restore high bit state
    F4A2 8A 80           oraa    #$80
    F4A4             loc_F4A4:
    F4A4 0A              clv
    F4A5 39              rts
    F4A6             invalidKey_F4A6:
    F4A6 86 2A           ldaa    #$2A
    F4A8 0B              sev
    F4A9 39              rts

    This is an AlphaSmart Pro scan code to +0 converter.  It also takes care to preserve the high bit of the incoming code.  (The high bit so far seems to be a flag indicating that shift is active.)  We will see if I discover the mysteries of offset +0.

    The last one is peculiar in that it involves a different array, 'byte_FAC5':

    F4AA             sub_F4AA:
    F4AA 84 7F           anda    #$7F            ; reset high bit
    F4AC 80 20           suba    #' '            ; offset to space = 0
    F4AE CE FA C5        ldx     #byte_FAC5      ; index into here
    F4B1 16              tab
    F4B2 3A              abx
    F4B3 A6 00           ldaa    0,x
    F4B5 39              rts

    This array is 95 bytes.  95 = 127 - 32.  So it seems more than coincidental that this would map to the printable ASCII 20-7fh.  Unlike the scanCodeStructs_F8C5 array, this is not a structure, just a byte.

    On a hunch, I am guessing that this is for Apple keyboard ("ADB") support.  Like PS/2, this protocol is documented.  I marked off a few entries in the list denoting what ASCII they correspond to:

    FAC5 31   byte_FAC5:  fcb $31  ; 20h - space
    ...
    FAD5 1D               fcb $1D  ; 30h - '0'
    ...
    FAE6 80               fcb $80  ; 41h - 'A'
    ...
    FAFF 86               fcb $86  ; 5ah - 'Z'
    ...
    FB06 00               fcb 0    ; 61h - 'a'
    ...
    FB1F 06               fcb 6    ; 7ah - 'z'

    By checking against documentation these do correspond to the ADB key codes for these keys.  It's interesting to note that the high bit is set to indicate if the shift key should be down.  (e.g. difference between 'A' and 'a' and 'Z' and 'z' is just the high bit set for upper case.)

    So that seems...

    Read more »

  • 20230222a -- Hunting for PS/2 Stuff

    ziggurat2902/24/2023 at 14:09 0 comments

    Eager to get the remaining scan codes for the keyboard, I started thinking about exploring the keyboard emulation aspect.  The product apparently works in two modes:  'standalone', which is what I've considered so far, and 'keyboard emulation' which is when it is attached to a desktop machine.  The keyboard emulation provides for file sending, which has been discussed a bit, but also as a general purpose keyboard.  This means that the various non-printing keys (shift, ctrl, F-keys, etc) are sent to the desktop, so there must be native AlphaSmart Pro scan code to standard (PS/2, ADB) scan code conversion somewhere.  And since PS/2 and ADB are documented, I should be able to correlate.

    Knowing from prior experience with PS/2 that the first sequence when a PS/2 keyboard is attached is to receive a 'reset' command (0xff), and respond with an 'ack' (0xfa) and the 'passed self test' (0xaa), I did a textual search of #$AA in hopes of maybe finding such.  There was one reference with that text.

    Address   Function Instruction                                   
    -------   -------- -----------                                   
    XROM:E2B7 sub_E265 86 AA                       ldaa    #$AA

    to

    E2B4             loc_E2B4:
    E2B4 BD E2 BE        jsr     sub_E2BE
    E2B7 86 AA           ldaa    #$AA
    E2B9 BD F7 A0        jsr     sub_F7A0
    E2BC 39              rts

    Maybe this could be PS/2 emulation related?  I was in luck.  The sub_E2BE prior to that seems to be loading 0xFA:

    E2BE             sub_E2BE:
    E2BE 86 FA           ldaa    #$FA
    E2C0 BD F7 A0        jsr     sub_F7A0
    E2C3 39              rts

    and they both call sub_F7A0 after.  So that's probably the 'send PS/2 byte' function.  There's a bunch of I/O bit fiddling and delays based on 'send speed', so there will be a bit of work to do in there.  This shouldn't be too bad since PS/2 is a documented protocol.

    This led up to a chain that is clearly the PS/2 host command handler.  Most of the stuff is ignored, but some critical things like 'reset' and 'echo' and 'identify' and 'get scan code set' are important.

    E265             ps2HandleCommand_E265:
    E265 BD E2 FE        jsr     ps2GetByte_E2FE ; XXX ps2 get byte
    E268 29 49           bvs     leave_E2B3
    E26A 96 72           ldaa    byte_72
    E26C 81 FF           cmpa    #$FF            ; 'reset and self-test'
    E26E 27 44           beq     ps2ResetResponse_E2B4
    E270 81 FE           cmpa    #$FE            ; 'resend last byte'
    E272 27 49           beq     locret_E2BD     ; just ignore; no ACK needed
    E274 81 FD           cmpa    #$FD            ; 'set specific key to make only'
    E276 27 60           beq     ps2EatNack_E2D8 ; PS/2 eat-and-ack for ignored commands
    E278 81 FC           cmpa    #$FC            ; 'set specific key to make/release'
    E27A 27 5C           beq     ps2EatNack_E2D8 ; PS/2 eat-and-ack for ignored commands
    E27C 81 FB           cmpa    #$FB            ; 'set specific key to typematic/autorepeat only'
    E27E 27 58           beq     ps2EatNack_E2D8 ; PS/2 eat-and-ack for ignored commands
    E280 81 FA           cmpa    #$FA            ; 'set all keys to typematic/autorepeat/make/release'
    E282 27 2C           beq     ps2AckIgnore_E2B0 ; PS/2 just ack for ignored commands
    E284 81 F9           cmpa    #$F9            ; 'set all keys to make only'
    E286 27 28           beq     ps2AckIgnore_E2B0 ; PS/2 just ack for ignored commands
    E288 81 F8           cmpa    #$F8            ; 'set all keys to make/release'
    E28A 27 24           beq     ps2AckIgnore_E2B0 ; PS/2 just ack for ignored commands
    E28C 81 F7           cmpa    #$F7            ; 'set all keys to typematic/autorepeat only'
    E28E 27 20           beq     ps2AckIgnore_E2B0 ; PS/2 just ack for ignored commands
    E290 81 F6           cmpa    #$F6            ; 'set default parameters'
    E292 27 1C           beq     ps2AckIgnore_E2B0 ; PS/2 just ack for ignored commands
    E294 81 F5           cmpa    #$F5            ; 'disable scanning'
    E296 27 18           beq     ps2AckIgnore_E2B0 ; PS/2 just ack for ignored commands
    E298 81 F4           cmpa    #$F4            ; 'enable scanning'
    E29A 27 14           beq     ps2AckIgnore_E2B0 ; PS/2 just ack for ignored commands
    E29C 81 F3           cmpa    #$F3            ; 'set typematic rate and delay'
    E29E 27 38           beq     ps2EatNack_E2D8 ; PS/2 eat-and-ack for ignored commands
    E2A0 81 F2           cmpa    #$F2            ; 'identify keyboard'
    E2A2 27 26           beq     ps2IdentifyKeyboard_E2CA
    E2A4 81 F0           cmpa    #$F0            ; 'get/set current scan code set'
    E2A6 27 3A           beq     ps2scanCodeSet_E2E2
    E2A8 81 EE           cmpa    #$EE            ; 'echo'; used for liveness testing by controller
    E2AA 27 18           beq     ps2sendEcho_E2C4
    E2AC 81 ED           cmpa    #$ED            ; 'set LEDs'
    E2AE 27 28           beq     ps2EatNack_E2D8 ; PS/2 eat-and-ack for ignored commands
    E2B0...
    Read more »

  • 20230221c -- Indicators

    ziggurat2902/24/2023 at 02:37 0 comments

    The manual states that on the bottom right are a series of characters:

    SCOC█

    That correspond to shift, control, option, command, capslock.

    So those looked like interesting things to figure out some more scan code for the non-printable keys.

    I started with capslock since I have found the flag indicating it's state.  I found a snippet:

    F3C6             loc_F3C6:
    F3C6 13 A1 01 0A     brclr   bCapsLock_A1 1 leave_F3D4 ; flag; CAPS lock in effect
    F3CA C6 9F           ldab    #159            ; cursor at end of screen
    F3CC BD F7 60        jsr     commonSetCursorPosB_F760 ; common set cursor position (abs pos in B)
    F3CF C6 0F           ldab    #$F             ; solid block char
    F3D1 BD F6 74        jsr     sendLCDbyteB_F674 ; send byte in B to LCD (w/ctrl as per 0x5b)
    F3D4             leave_F3D4:
    F3D4 39              rts

    Straightforward, but no SCOC in sight.  I am inclined to conclude again that the manual is for a later version of this product than this v2.03 firmware.  So I won't be able to discern those scan codes yet.

    This snippet is still interesting, though, because it's part of a function that does other displaying stuff and accessing file contents.  So that routine sub_F374 might be the 'display current file' routine.

  • 20230221b -- File Inserting

    ziggurat2902/23/2023 at 23:25 0 comments

    Intrigued by the various file-related pointers seen in deleteThisFile_EB9B, I remember that the keyboard scan routine had a final case where the bulk of the keystrokes (the printable ones) are handled, and there was a scan code to ASCII routine before handing it off.

    EEC9             loc_EEC9:
    EEC9 96 70           ldaa    byte_70
    EECB BD F5 92        jsr     sub_F592
    EECE 29 10           bvs     leave_EEE0
    EED0 13 A1 01 03     brclr   bCapsLock_A1 1 loc_EED7 ; XXX CAPS lock flag?
    EED4 BD EF BF        jsr     sub_EFBF
    EED7             loc_EED7:
    EED7 BD F3 07        jsr     sub_F307
    ...

    so it seems plausible that sub_F307 would be 'stick it in the current file' to some extent.

    F307             ; insert char A onto current file; V set if fail, clear on success
    F307             insertCharToFile_F307:
    F307 FE 01 24        ldx     curfileEnd_124  ; XXX file related (current file ptr end char (one past last)???) .4
    F30A BC 01 26        cpx     thisFileRgnLast_126 ; this file; file region end addr (inclusive)
    F30D 27 42           beq     leaveFileFull_F351
    F30F FE 01 22        ldx     curfileIns_122  ; XXX file related (current file insertion point???) .0
    F312 BC 01 24        cpx     curfileEnd_124  ; XXX file related (current file ptr end char (one past last)???) .4
    F315 26 0C           bne     scootchFileUp_F323 ; if inserting into the middle, we have to scootch things up
    F317 FE 01 22        ldx     curfileIns_122  ; (did we really need to load it again?)
    F31A 36              psha
    F31B BD EC 80        jsr     selectRAMpageForFile_EC80 ; select in the relevant RAM page based on file number (byte_15D)
    F31E 32              pula
    F31F A7 00           staa    0,x
    F321 20 1F           bra     finishUp_F342
    F323             scootchFileUp_F323:
    F323 36              psha                    ; save char to insert for later
    F324 FE 01 22        ldx     curfileIns_122  ; (did we really need to load it again?)
    F327 DF 46           stx     scratch_46      ; save insert point in scratch var
    F329 FE 01 24        ldx     curfileEnd_124  ; XXX file related (current file ptr end char (one past last)???) .4
    F32C BD EC 80        jsr     selectRAMpageForFile_EC80 ; select in the relevant RAM page based on file number (byte_15D)
    F32F             loop_F32F:
    F32F EC 00           ldd     0,x             ; get two chars
    F331 ED 01           std     1,x             ; store back up one byte
    F333 09              dex
    F334 09              dex                     ; bump down twice since we're doing two chars at a time
    F335 9C 46           cpx     scratch_46      ; are we at the insertion point?
    F337 2A F6           bpl     loop_F32F       ; keep going if not
    F339 DE 46           ldx     scratch_46      ; get back the insertion point
    F33B A6 00           ldaa    0,x
    F33D A7 01           staa    1,x             ; do the last odd char
    F33F 32              pula                    ; get back char to insert
    F340 A7 00           staa    0,x             ; /now/ stick the char in
    F342             finishUp_F342:
    F342 BD EC 79        jsr     selectRAMPage0_EC79 ; select 32 KiB RAM page 0
    F345 FE 01 24        ldx     curfileEnd_124  ; XXX file related (current file ptr end char (one past last)???) .4
    F348 08              inx
    F349 FF 01 24        stx     curfileEnd_124  ; increment cur file end pointer
    F34C BD F2 06        jsr     sub_F206        ; XXX finish up after insert into file
    F34F 0A              clv                     ; clear V for success
    F350 39              rts
    F351             leaveFileFull_F351:
    F351 BD F3 56        jsr     showFileFull_F356 ; show File Full message
    F354 0B              sev                     ; set V for fail
    F355 39              rts

    So it seems that word_124 is the 'current end of file pointer' and that word_122 is the 'insertion location pointer'.  These will be the same if you're inserting at the end, and different if not.

    This pleases me a bit because I found the values at 120, 122, and 124 to be copies of the values in the fileState_12A array (array of three entries per file).  The ones at 120, 122, and 124 (and not in the same order) are for the currently selected file.  But we already know what is the currently selected file via thisFileNo_15D, so why bother having two copies?  This is one of life's little mysteries at this point, but it may become apparent later.  For example, I know there is an emergency 'undelete' facility, so maybe the copy in the fileState_12A array is used to restore the working copies in (120-124).  Just a guess -- have to dig it to find out for sure.

    I also need to do a bit more work on sub_F206 which seems to be a final cleanup post insert routine.  If you're appending, it does nothing, but otherwise does do some work.

  • 20230221a -- File Deleting/Erasing

    ziggurat2902/23/2023 at 18:43 0 comments

    Previously, while rummaging for scan codes, I was correlating known behaviour with key codes.  One of those keys was the Clear File key (0x56).  I forgot to post about that find, but it's still worthwhile to mention the file clearing operation itself.

    There's two flavors of file clearing:
    • a 'delete', which simple resets pointers and does not destroy data
    • an 'erase', that clears the data in addition to resetting the pointers

    These were easy to find because there are two different textual prompts in the image.

    The first prompts for doing a 'delete' of the current file.

    EEE1             promptAndDeleteFile_EEE1:
    EEE1 BD F6 18        jsr     clearHomeLCD_F618 ; Clear LCD set cursor pos 0
    EEE4 CE FD F6        ldx     #aAreYouSureYo_0 ; "Are you sure you want to delete"
    EEE7 BD F6 69        jsr     showText_F669   ; show nts text @ X
    EEEA BD F7 4E        jsr     setcpLine1_F74E ; set cursor Line 1
    EEED CE FE 16        ldx     #aAllTheDataInThisFile?YN ; "all the data in this file?(y/n)"
    EEF0 BD F6 69        jsr     showText_F669   ; show nts text @ X
    EEF3             waitKey_EEF3:
    EEF3 BD E5 A0        jsr     scanKbd_E5A0    ; do key scan and enqueue
    EEF6 BD EB EE        jsr     dequeueKbdScanCode_EBEE ; pull scan code A (rowno|colno) from keyboard buffer; V set if empty, clear if valid
    EEF9 29 F8           bvs     waitKey_EEF3
    EEFB 81 22           cmpa    #$22            ; scan code for 'y'
    EEFD 27 02           beq     delete_EF01     ; empty it if 'y' pressed
    EEFF 20 03           bra     leave_EF04      ; skip if anything else
    EF01             delete_EF01:
    EF01 BD EB 9B        jsr     deleteThisFile_EB9B ; delete file by resetting pointers (recoverable)
    EF04             leave_EF04:
    EF04 20 D4           bra     sub_EEDA

    The real deleting work is at deleteThisFile_EB9B, but first the erase version:

    EF06             promptAndEraseFile_EF06:
    EF06 BD F6 18        jsr     clearHomeLCD_F618 ; Clear LCD set cursor pos 0
    EF09 CE FE 36        ldx     #aAreYouSureYouWantToPermanently ; "Are you sure you want to PERMANENTLY"
    EF0C BD F6 69        jsr     showText_F669   ; show nts text @ X
    EF0F BD F7 4E        jsr     setcpLine1_F74E ; set cursor Line 1
    EF12 CE FE 5B        ldx     #aEraseAllTheDataInThisFile?YN ; "erase all the data in this file?(y/n)"
    EF15 BD F6 69        jsr     showText_F669   ; show nts text @ X
    EF18             waitKey_EF18:
    EF18 BD E5 A0        jsr     scanKbd_E5A0    ; do key scan and enqueue
    EF1B BD EB EE        jsr     dequeueKbdScanCode_EBEE ; pull scan code A (rowno|colno) from keyboard buffer; V set if empty, clear if valid
    EF1E 29 F8           bvs     waitKey_EF18
    EF20 81 22           cmpa    #$22            ; scan code for 'y' key
    EF22 26 18           bne     leave_EF3C      ; leave if any key other than 'y'
    EF24 FE 01 26        ldx     word_126 ; this file; file region end addr (inclusive)
    EF27 DF 4C           stx     byte_4C         ; /this/ keyboard scan column no (also scratch when erasing file)
    EF29 FE 01 28        ldx     word_128 ; this file; file region start addr
    EF2C BD EC 80        jsr     selectRAMpageForFile_EC80 ; select in the relevant RAM page based on file number (byte_15D)
    EF2F             loopEraseFile_EF2F:
    EF2F 6F 00           clr     0,x
    EF31 08              inx
    EF32 9C 4C           cpx     byte_4C         ; /this/ keyboard scan column no (also scratch when erasing file)
    EF34 26 F9           bne     loopEraseFile_EF2F
    EF36 BD EC 79        jsr     selectRAMPage0_EC79 ; select 32 KiB RAM page 0
    EF39 BD EB 9B        jsr     deleteThisFile_EB9B ; set pointers to empty file
    EF3C             leave_EF3C:
    EF3C 39              rts

    This is almost the same as promptAndDeleteFile_EEE1 except for the loop zeroing out memory from *word_128 to *word_126 (inclusive).  So those are probably the start and end region pointers for the current working file.

    While working on the keyboard stuff, there were some locations that seemed to be relevant for those routines, but I found them to be re-used for completely different purposes in other routines (such as these).  byte_4C is a case in point.  So I am re-interpreting these locations as being 'scratch' variables used for various things.  So long as two routines are never in the same call chain, and that it's not a 'static' variable, that's OK.

    OK, the actual 'deleting' is done via deleteThisFile_EB9B:

    EB9B             deleteThisFile_EB9B:
    EB9B FC 01 28        ldd     word_128 ; this file; file region start addr
    EB9E FD 01 20        std     word_120
    EBA1 FD 01 22        std     word_122
    EBA4 FD 01 24        std     word_124
    EBA7 86 01           ldaa    #1
    EBA9 97 6B           staa    word_6B
    EBAB 97 6C staa word_6B+...
    Read more »

  • 20230220c -- Revisiting Keyboard Scan Code Handling

    ziggurat2902/23/2023 at 14:55 2 comments

    Eager to mine some more scan codes, I am looking at handleKeyScanCode_EE37

    EE37 13 63 01 04     brclr   byte_63 1 loc_EE3F ; XXX flag? kbd (maybe option? maybe clover?)
    EE3B 12 61 01 C9     brset   byte_61 1 loc_EE08 ; XXX flag? kbd (any col 9) (maybe option? maybe clover?)
    EE3F             loc_EE3F:
    EE3F 13 64 01 02     brclr   byte_64 1 loc_EE45 ; XXX flag? kbd (any col 15)
    EE43 8A 80           oraa    #$80 ; 'Ç'
    EE3F 13 64 01 02     brclr   byte_64 1 loc_EE45 ; XXX flag? kbd (any col 15)
    EE43 8A 80           oraa    #$80 ; 'Ç'
    EE3F             loc_EE45:
    EE45 97 70           staa    byte_70
    EE47 81 56           cmpa    #$56
    EE49 27 DD           beq     handleClearFile_EE28
    EE4B 81 2B           cmpa    #$2B
    EE4D 27 28           beq     loc_EE77
    EE4F 81 35           cmpa    #$35
    EE51 27 2A           beq     loc_EE7D
    EE53 81 34           cmpa    #$34
    EE55 27 36           beq     loc_EE8D
    EE57 81 37           cmpa    #$37
    EE59 27 40           beq     loc_EE9B
    EE5B 81 15           cmpa    #$15
    EE5D 27 4A           beq     loc_EEA9
    EE5F 81 66           cmpa    #$66
    EE61 27 54           beq     loc_EEB7
    EE63 81 75           cmpa    #$75
    EE65 27 55           beq     loc_EEBC
    EE67 81 2D           cmpa    #$2D
    EE69 27 56           beq     loc_EEC1
    EE6B 7E EF 67        jmp     loc_EF67

    This handles various special keys out-of-band of regular keystroke processing.  The 'Clear File' key was figured out because the file clearing logic was found, so we know that is 0x56.  The others are not yet known.

    loc_EEC1 is interesting because it toggles the caps lock indicator found before:

    EEC1             sub_EEC1:
    EEC1 96 A1           ldaa    bCapsLock_A1    ; XXX CAPS lock flag?
    EEC3 88 01           eora    #1
    EEC5 97 A1           staa    bCapsLock_A1    ; XXX CAPS lock flag?
    EEC7 20 11           bra     sub_EEDA

     So scan code 0x2d is likely the caps lock key.

    loc_EF67 is interesting because it handles everything else, and is likely involved in key code to ASCII conversion for the bulk of the keyboard.

    EEC9             loc_EEC9:
    EEC9 96 70           ldaa    byte_70
    EECB BD F5 92        jsr     sub_F592
    EECE 29 10           bvs     leave_EEE0
    EED0 13 A1 01 03     brclr   bCapsLock_A1 1 loc_EED7 ; XXX CAPS lock flag?
    EED4 BD EF BF        jsr     sub_EFBF
    EED7             loc_EED7:
    EED7 BD F3 07        jsr     sub_F307
    ...

    The interesting thing to me is that the scan code was saved in byte_70 earlier, prior to doing a bunch of things, then restored at the start of this routine.  The value could be mutated in sub_F592, and then the capslock flag we saw earlier is checked and an optional transform sub_EFBF is performed if caps lock is on.  Then the final value is processed in sub_F307.  It's just a hunch, but it could be that sub_F592 transforms the scan code to ascii (exiting if it cannot), optionally converts to upper case if caps lock is on via sub_EFBF, and then sub_F307 processes the asii version of the character.

    The transform at sub_EFBF is fairly obviously a 'make upper case routine' just for the lower case [a-z] by exploiting ASCII's structure:

    EFBF             sub_EFBF:
    EFBF 81 61           cmpa    #'a'
    EFC1 25 06           bcs     leave_EFC9
    EFC3 81 7A           cmpa    #'z'
    EFC5 22 02           bhi     leave_EFC9
    EFC7 84 DF           anda    #$DF   ; goodbye, bit 5
    EFC9             leave_EFC9:
    EFC9 39              rts

    What this also means is that coming into sub_F592 is a scan code in A, and then when it returns, coming into sub_EFBF is ASCII in A (or else the make upper wouldn't work).  So sub_F592 /has/ to do scan code to ASCII translation.

    The routine sub_F592 masks off the high bit and then multiplies that value by 4, and offsets it into a large array of 4-byte structures at unk_F8C5.  We know that A coming in is the scan code, so this table seems to be some sort of scan code to something (ASCII?) translation.  Lo! and Behold, this array is 128 entries, which would be consistent with the mask of 0x7f.  It is also consistent with our scan code scheme of (row|col), since there are 16 columns and 8 rows.

    F592             sub_F592:
    F592 84 7F           anda    #$7F ;  ; remove high bit, if present
    F594 16              tab
    F595 4F              clra
    F596 05              lsld
    F597 05              lsld            ; effectively multiply by 4 (4 byte entries)
    F598 C3 F8 C5...
    Read more »

  • 20230220b -- Hardware Info Sync

    ziggurat2902/23/2023 at 14:32 0 comments

    It's probably worthwhile to summarize what hardware aspects I have gleaned thus far.

    Port A:
    b7  LCD EX
    b6  (not bonded on DIP40)
    b5  RAM Bank Select 1 (A16)
    b4  (not bonded on DIP40)
    b3  RAM Bank Select 0 (A15)
    b2  ??? 
    b1  Low Battery
    b0  ??? 

    Port D:
    b7  (not available with external memory)
    b6  (not available with external memory)
    b5  LCD data 7
    b4  LCD data 6
    b3  LCD data 5
    b2  LCD data 4
    b1  ??? 
    b0  ??? 

    Port B, C -- (used for external memory address/data bus)

    8000 write:  Keyboard columns
    8000 read:   Keyboard rows
    C000 write:  Keyboard columns

    8000 write:  b2 - LCD R/~W
    b1 - LCD OC2
    b0 - LCD OC1

    Inferred Key ScanCodes:
    The keyboard scan code is 8-bits, with high nybble being row number, and low nybble being column number.  The firmware largely works with scan codes directly.  This is a bit tricky to figure out the key matrix from software alone.  A few can be figured out because they correlate to prompts indicating what key is expected to be pressed.
    0x22    'y'     as per prompt y/n @ EF20
    0x5e    '1'     as per prompt 'send speed' @ F084
    0x5d    '2'     ...
    0x5c    '3'     ...
    0x51    '4'     ...
    0x56    'Clear File'    as per 'handleKeyScanCode' @ EE08
    0x4d    's'     as per change send speed at @ EE14

View all 37 project logs

Enjoy this project?

Share

Discussions

Augusto Baffa wrote 03/12/2023 at 23:02 point

Hey ... this is genius!!! I just got an alphasmart pro and was thinking of taking a look at the rom and... boom.. found your page :D.  I'm wondering if it's possible to change the rom to give it more applications than just remember some chars.... 

  Are you sure? yes | no

ziggurat29 wrote 03/13/2023 at 17:33 point

Totally. Should be very hackable. The ROM is socketed, so you just need a 2764 and party on that. If you don't want to mess with UV eraser and burner, you should be able to substitute an EEPROM equivalent to make your life a bit easier.
The disassembly is largely complete, missing comments for the various ADB routines. You should be able to cut-paste things like LCD and keyboard scanning, and PS/2 interface as 'drivers' for whatever project you have in mind (or just use them as a conceptual starting point if you want to write your own from scratch).
I've had to pause work on this project for the moment due to some exigent circumstances, but there's a lot for you to chew on in the various manuals and disassembly listing are in the 'files' area.

  Are you sure? yes | no

Eric Hertz wrote 02/16/2023 at 09:48 point

I have to comment on the picture before all else!

"No Disassemble! [AlphaSmart] is Alive!"

  Are you sure? yes | no

ziggurat29 wrote 02/16/2023 at 12:01 point

lol, yes, you get the reference! my intention is to put a word bubble "Yes, disassemble!" but I don't have quite the photoshop fu for that at the moment.  Seems more vector than raster
Edit: a friend suggested just using a clipart for word bubble graphic, so now I have addressed that crucial detail; lol!

  Are you sure? yes | no

Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates