-
20230225a - Displaying the Current File
02/26/2023 at 17:17 • 0 commentssub_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 seems to actually be 'find end of display line'. It does it by first hunting for a CR within 40 chars, and if not found, search backwards for a line-breaking character, and if not found, just give up and break it at 40 characters.
So if one was to compose a document with the word: "pneumonoultramicroscopicsilicovolcanoconiosis" (a specific lung disease caused by inhaling quartz dust), or refer to Lake Chargoggagoggmanchauggauggagoggchaubunagungamaugg (in Michigan), then you'd have to suffer a hard break in the middle of the word. But even if you don't suffer "hippopotomonstrosesquippedaliophobia" (fear of long words) you'll almost always still be OK with a 40-char line. Though for the mid 90's I think they really should have tried to splurge for 80. 80 looks more like a typewritten page would be. Though such a display would have been a tight fit in this unit. On the other hand, you can't index an 80x4 display with one byte, so there would be a price to pay in code.
Getting back to sub_F374, the logic is a loop over characters in the file, spewing them to the LCD, starting with the location word_120, until the logical end-of-line is found, then advancing to the next line and continue spewing. This happens until 4 lines are filled, or until the end-of-file is reached. So it seems that word_120 is 'file position at start of screen'. Annotated, the function now looks like this:
F374 ; re-paint LCD for current file state F374 displayCurFile_F374: F374 BD F6 18 jsr clearHomeLCD_F618 ; Clear LCD set cursor pos 0 F377 FE 01 20 ldx curfileStartOfScreen_120 ; XXX file related (current file screen start ptr???) .2 F37A BD F3 D5 jsr findNextLineStart_F3D5 ; find start of next display line considering breaking chars; X = cur file.2; V if no more lines; clear if next line start valid F37D DF 4E stx scratchw_4E ; save start of /next/ line F37F FE 01 20 ldx curfileStartOfScreen_120 ; XXX file related (current file screen start ptr???) .2 F382 86 01 ldaa #1 F384 97 4C staa scratchby_4C ; (used for tracking row) F386 loopLine_F386: F386 86 01 ldaa #1 F388 97 60 staa scratchby_60 ; (used for tracking col) F38A loopChar_F38A: F38A BC 01 22 cpx curfileIns_122 ; is this char at the insertion point? remember (row,col) for it F38D 26 08 bne cont_F397 ; nope; keep spewing chars F38F 96 4C ldaa scratchby_4C ; row F391 97 6C staa lcdcpRow1_6C ; 1-relative LCD cursor pos row F393 96 60 ldaa scratchby_60 ; col F395 97 6B staa lcdcpCol1_6B ; 1-relative LCD cursor pos col F397 cont_F397: F397 BC 01 24 cpx curfileEnd_124 ; finished entire file? leave F39A 27 2A beq showCapLockLeave_F3C6 F39C BD EC 80 jsr selectRAMpageForFile_EC80 ; select in the relevant RAM page based on file number (byte_15D) F39F A6 00 ldaa 0,x ; get char in file F3A1 BD EC 79 jsr selectRAMPage0_EC79 ; select 32 KiB RAM page 0 F3A4 BD F6 2D jsr xlatShowChar_F62D ; put it on the LCD (cp will advance automatically) F3A7 08 inx ; next char F3A8 7C 00 60 inc scratchby_60 ; next col F3AB 9C 4E cpx scratchw_4E ; is it now at the start of the next display line? F3AD 26 DB bne loopChar_F38A ; nope; keep spewing chars F3AF 96 4C ldaa scratchby_4C ; row F3B1 81 04 cmpa #4 ; was this the 4th (last) line? leave if so F3B3 27 11 beq showCapLockLeave_F3C6 F3B5 7C 00 4C inc scratchby_4C ; next row F3B8 3C pshx ; save file ptr while we fiddle with things F3B9 BD F4 2E jsr lcdSetCPatLineA ; set LCD cursor position at start of line A F3BC DE 4E ldx scratchw_4E ; get the last end-of-line and hunt for next end-of-line F3BE BD F3 D5 jsr findNextLineStart_F3D5 ; find start of next display line considering breaking chars; X = cur file.2; V if no more lines; clear if next line start valid F3C1 DF 4E stx scratchw_4E ; save the next end-of-line F3C3 38 pulx ; restore file ptr while we continue on the next line F3C4 20 C0 bra loopLine_F386 F3C6 showCapLockLeave_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
And that is how we do that!
-
20230223a - Special Keys and File Ops
02/25/2023 at 15:03 • 1 commentWhile 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 this fixes those up. Aside from understanding the code, the context is useful because it seems to suggest that sub_EEDA is in fact 'update display for current file'. The first routine sub_F374 being 'do the showing file part'. The second routine sub_F256 is briefer:
F256 sub_F256: F256 86 28 ldaa #40 ; (char per line) F258 D6 6C ldab word_6B+1 ; XXX 0x0101 upon file cleared; row, col? 1-relative F25A 5A decb F25B 3D mul F25C DB 6B addb word_6B ; XXX 0x0101 upon file cleared; row, col? 1-relative F25E 5A decb F25F 14 5B 01 bset lcdCtlLineState_5B 1 ; set OC2,OC1 to 11; 'set cursor address' F262 14 5B 02 bset lcdCtlLineState_5B 2 ; LCD control lines state in b1 = IOC2, b0 = IOC1 F265 BD F6 74 jsr sendLCDbyteB_F674 ; send byte in B to LCD (w/ctrl as per 0x5b) F268 15 5B 01 bclr lcdCtlLineState_5B 1 ; return OC2,1 to 10; 'data' F26B 39 rts
It fiddles with the word_6B and does a set cursor address, so this seems to suggest word_6B is in fact two bytes and they are the row/col (1-relative) screen position of the cursor. This will be useful to back annotate into the various other routines that use/manipulate these locations.
So, some more mysteries solved. Time to comment and propagate info throughout.
-
20230222c - Finishing Up Key Matrix
02/24/2023 at 18:06 • 4 commentsUnderstanding 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
02/24/2023 at 15:06 • 0 commentsThere 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 to solve one mystery. Hot on this ADB stuff, the new hypothesis is that the +0 entry is the ADB code. Cross-checking the entries above in the scanCodeStructs_F8C5 correlates once again, so I think this second mystery is solved as well. So in sum:
- F482 scanCodeToADB_F482: ; a table lookup
- F4AA xlatASCIItoADB_F4AA: ; a table lookup
- F4B6 xlatASCIItoPS2_F4B6: ; a linear search
- F4DC scanCodetoPS2_F4DC: ; a table lookup
and those are our 4 use-cases: ADB or PS/2, and file sending or keyboard emulation.
What is more interesting to me now is that there are many scan codes which are not valid ASCII (i.e. +2 and +3 are 0). These are doubtlessly the various function keys that I have not mapped out yet, so I should be able to use the PS/2 documentation to map out most of the remainder of the keyboard matrix.
-
20230222a -- Hunting for PS/2 Stuff
02/24/2023 at 14:09 • 0 commentsEager 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 ps2AckIgnore_E2B0: E2B0 BD E2 BE jsr ps2SendAck_E2BE ; PS/2 just ack for ignored commands E2B3 leave_E2B3: E2B3 39 rts
From this, we can also confirm that this device is emulating 'scan code set 2'. No surprise, as this is the most common, and the only one guaranteed to be supported, but it's still good to confirm.
The way this scan code set works is that there is a 'make' code (when key down), and a 'break' code (when key up). The 'make' code is usually a single byte, and the 'break' code has a prefix of 0xF0 followed by that same byte. There are a few keys that involve two-byte make codes (prefixed with 0xe0) and have three-byte break codes (prefixed with 0xe0, 0xf0).
Looking back at the scan code mapped structures at F8C5, there are byte values at offset +0 and +1 that I hadn't figured out yet. Making a spot check of a few keys and correlating that to PS/2 documentation for 'scan code set 2', it is strongly suggestive that offset +1 is the 'base' PS/2 code (i.e., without the 0xf0 or 0xe0 prefix, which is probably programmatically supplied).
FA75 0E fcb $E ; scan code 6c - 'E' FA76 24 fcb $24 ; <- PS/2 scan code for 'E'!!! FA77 65 fcb 'e' FA78 45 fcb 'E' FA79 0D fcb $D ; scan code 6d - 'W' FA7A 1D fcb $1D ; <- PS/2 scan code for 'W' FA7B 77 fcb 'w' FA7C 57 fcb 'W' FA7D 0C fcb $C ; scan code 6e - 'Q' FA7E 15 fcb $15 ; <- PS/2 scan code for 'Q' FA7F 71 fcb 'q' FA80 51 fcb 'Q'
So I think there's another mystery solved. That should put some light on all the references to the +1 offset, so I've got some commenting and labelling to do....
-
20230221c -- Indicators
02/24/2023 at 02:37 • 0 commentsThe 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
02/23/2023 at 23:25 • 0 commentsIntrigued 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
02/23/2023 at 18:43 • 0 commentsPreviously, 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+1 EBAD 39 rts
Pretty straightforward -- just resetting some pointers. Who knows what they actually do, but we can now at least mark those locations as being 'current file related'.
The word_6b is quite a curiousity. I marked it as a word based on access patterns, but here we seem to be accessing it byte-wise, putting a 1 in both locations when the file is cleared. What could be a (1,1) pair at file clearing? Maybe (line, col), though I'm not sure why this product would need that since there is no display for such or 'goto' capability. Maybe it's used to associate the location on the screen as to where the pointer for 'insert point' is located. And for some reason it is 1-relative. Food for future thought.
-
20230220c -- Revisiting Keyboard Scan Code Handling
02/23/2023 at 14:55 • 2 commentsEager 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 addd #unk_F8C5 ; XXX 128 4-byte structures F59B 8F xgdx ; X is now struct address for this scan code F59C 13 64 01 04 brclr byte_64 1 loc_F5A4 ; XXX flag? F5A0 A6 03 ldaa 3,x F5A2 20 02 bra cont_F5A6 F5A4 loc_F5A4: F5A4 A6 02 ldaa 2,x F5A6 cont_F5A6: F5A6 81 00 cmpa #0 F5A8 27 02 beq nochar_F5AC F5AA 0A clv F5AB 39 rts F5AC nochar_F5AC: F5AC 86 2A ldaa #$2A ; '*' F5AE 0B sev F5AF 39 rts
There is also a check of flag byte_64 which affects whether element at +2 or +3 is selected. This isn't the caps lock flag, but maybe it is the shift flag? Caps lock generally only affect alpha characters, whereas you still need to use shift to get to things above the numerics.
Since I had managed to figure out a few scan codes earlier by correlating with menu options, lets check those out for consistency. I (tediously) labelled all the entries in the array. We know (well, believe) that 'y' for 'yes' is 0x22. So, 0x22 * 4 + 0xF8C5 = 0xf94d
F94D 10 fcb $10 F94E 35 fcb $35 ; 5 F94F 79 fcb $79 ; y F950 59 fcb $59 ; Y
And element +2 is lower-case y, and +3 is upper-case 'Y'. This looks promising. Some of the others known:
code 0x5c, believed to be '3':
FA35 14 fcb $14 FA36 26 fcb $26 ; & FA37 33 fcb $33 ; 3 FA38 23 fcb $23 ; #
code 0x5d, believed to be '2':
FA39 13 fcb $13 FA3A 1E fcb $1E FA3B 32 fcb $32 ; 2 FA3C 40 fcb $40 ; @
code 0x5e, believed to be '1':
FA3D 12 fcb $12 FA3E 16 fcb $16 FA3F 31 fcb $31 ; 1 FA40 21 fcb $21 ; !
code 0x5e, believed to be '4':
FA09 15 fcb $15 FA0A 25 fcb $25 ; % FA0B 34 fcb $34 ; 4 FA0C 24 fcb $24 ; $
So this is checking out pretty good so far! And the upper case of the numerics corresponds to their shifted symbol on the keyboard.
OK, now I must tediously go through the entire table, and markup a printout of the keyboard. I will not be able to figure out all things, since this just covers stuff that is meant to translate to printable characters, but it will be a bit leg up. Plus, I don't know what elements +0 and +1 do yet
There are 80 physical keys on the AlphaSmart Pro, so most of these entries will be 'invalid' from the standpoint of the ASCI conversion.
-
20230220b -- Hardware Info Sync
02/23/2023 at 14:32 • 0 commentsIt'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 columns8000 write: b2 - LCD R/~W
b1 - LCD OC2
b0 - LCD OC1Inferred 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