Dvorak on the Tandy 102
Here’s my journal of what I did to remap the keyboard on my Tandy 102 to be dvorak. These notes are raw and uneditted, so they might be hard to read, but you can see basically what I did. I added some pictures just to make it more interesting.
At the risk of pushing my opinion too much, I think for a long time the Tandy 102 was one of the few machines that just got it right for a laptop. It comes pre-loaded with a suite of useful tools; it powers on instantly (no waiting for standby or hibernate); it is entirely solid-state, so you can drop it and mash it and it won’t break; it has a comfortable full-sized keyboard; it is light; and the battery life is phenomenal, and when you run out, you can replenish yourself with just four AA batteries. And the thing has built into a modem, RS-232, and expansion ports. Up until a few years ago, few Windows laptops could claim even half of these features at ten times the price! The only problem I had with it, until now, is that it didn’t support the dvorak key mapping. Here’s how I fixed it.
Remove Boot ROM
Remove boot ROM. Rom is of through-hole type; need to recover nondestructively. Note that pins are crimped on the bottom, so heating the board will not release the chip; crimps must be individually undone. Use ChipQuik desoldering alloy to re-alloy the solder compounds.
Undo crimps one at a time using hot soldering iron tip to push pins back into a straight position.
Use “tongs” soldering iron to heat most of the pins at once and rely on thermal mass to keep ChipQuik alloyed pins liquid.
Remove chip; recondition pins, clean board with soldering braid. Attach new socket. Verify removed chip works by inserting chip into socket and booting system. Good, it works.
Read out ROM
Read out ROM contents using ROM reader. Need to reconstruct pinout to determine what ROM type to use. Based on the adjacent chip (6264LP SRAM chip) one can infer that this ROM may have a pinout similar to a 27C256. Attempt to read chip. Found that all zero’s are returned from reader. Query if pinout is valid. Buzz-out pins and verify that pinout is correct for 27C256. Query whether ROM has a lock-out based on the Vpp pin, or if the programmer is doing something screwy with Vpp that could be locking out the chip. Oscilloscope traces reveal nothing unusual. Check to see how Vdd is doing. Noticed that Vdd does not rise properly on ROM reader (EMP-30). Hypothesize that current limiting is employed in the ROM reader and this mask ROM consumes an unusually large amount of current. Consider connecting test clips from ROM to voltage source to force ROM power-up (risky, could damage programmer and/or ROM if this is not correct). To limit risk, try jumpering from Vpp over to Vdd, such that internal current limits of ROM reader are still in place. Noted that Vdd rises a little higher, and return data is garbled but looking more alive. Heck, I’m impulsive tonight so connect supply onto Vdd to force ROM into power-up. Risk pays off! A complete ROM image appears in the ROM reader buffer. And there you go:
ROM:7F99 aBytesFree: .text "Bytes free" ROM:7F99 .db 0 ROM:7FA4 aTrs80Model100S:.text "TRS-80 Model 100 Software" ROM:7FA4 .db 0Dh ROM:7FA4 .db 0Ah ROM:7FA4 .text "Copr. 1983 Microsoft" ROM:7FA4 .db 0Dh ROM:7FA4 .db 0Ah
Hardware Analysis: Keyboard
Need some hardware information before continuing disassembly. Must determine keyboard controller hardware mappings.
Note keyboard ribbon cable maps partly to 8155 directly as well as 40H367 (infer that 40H367 is a low power CMOS variant of the 74xxx series; web search turns up nil for datasheets and Toshiba website does not document this series anymore). 40H367 are buffer/line drivers. Infer these are used to drive keyboard scan matrix.
Pin 9 on the 8085 AH (RST5.5) is connected to PC3 (pin 1 on 8155) which is also functional as the Port B interrupt. Noted that PB0/1 seems to have some connection to the keyboard; suspect that keyboard event generates interrupt.
Interrupt handler for RST5.5 originates at 0x2C. Code at 02C contains a jump to location 0xF5F9. This appears to be in main memory; infer that memory map has ROM on bottom since boot vector is at 0 and ROM is only 32k. Main memory map has not been confirmed through measurements.
Hypothesize that interrupt handler is copied to main memory at some point in time. Try to localize code segment in ROM by either finding copy routine in initialization sequence or identify based on unique instructions that access 8155 register space. Choose latter path; its more fun.
Need to determine location of 8155 to find in/outs to its chip enable location.
Searching for location: CE pin of 8155 maps to pin 9 of 40H011 gate. This should be a triple-3-input AND gate. Pin 9 is an “A” input. Not the pin we are looking for; search more. Also maps to pin 12 of 40H138 3-8 decoder. Excellent, pin 12 is “Y3_N” output of decoder. We have found a drive point.
Theorize that 40H138 decoder is fed addresses from 8085. Search for connectivity there. Noted use of 40H367 buffers to amplify address drive of 8085. This will complicate search; must be done in at least 2 phases.
Phase 1:
S0 maps to pin 3 M21 (40H367) — 1Y1, buffer of pin 2
S1 maps to pin 13 M21 (40H367) — 2Y2, buffer of pin 14
S2 maps to pin 7 M20 (40H367) — 1Y3, buffer of pin 6
EN3_N maps to pin 3 M17 (40H000 2-input NAND), output of NAND of pin 1 and pin 2
EN2_N maps to GND
EN1 maps to pin 13 M20 (40H367) — 2Y2, buffer of pin 14
Phase 2:
S0 maps to pin 3 M21 (40H367) — 1Y1, buffer of pin 2 — 8085 pin 25, A12
S1 maps to pin 13 M21 (40H367) — 2Y2, buffer of pin 14 — 8085 pin 26, A13
S2 maps to pin 7 M20 (40H367) — 1Y3, buffer of pin 6 — 8085 pin 27, A14
EN3_N maps to pin 3 M17 (40H000 2-input NAND), output of NAND of pin 1 and pin 2
EN2_N maps to GND
EN1 maps to pin 13 M20 (40H367) — 2Y2, buffer of pin 14 — 8155 pin 7, Io/M
Phase 3: determine NAND gate decoding
EN3_N maps to pin 3 M17 (40H000 2-input NAND), output of NAND of pin 1 and pin 2
pin 1 goes to pin 9 M20
pin 2 goes to pin 9 M20 — NAND gate is being used as an inverter!
pin 9 M20 is 1Y4, buffers pin 10 — 8085 pin 28, A15
(time to complete search: 45 minutes)
Conclusion: 8155 is driven by the following address map:
15 -> 0
1 0 1 1 x x x x x x x x x x x x x x x x
(I/O mode only)
That’s location 0xBXXXX
Refer to 8085 datasheet. Note that I/O ports are mapped only to top 8 bits, so I/O space is only 256 bytes large. Revise location opinion to location 0xBX.
Search for Keyboard Table Stub
Refer to disassembly of ROM. Search for instruction of type IN 0xBX. This is opcode 0xDB 0xBX. In particular, we are looking for stuff that tickles Port B of 8155, so refine search to 0xDB 0xB2 or 0xDB 0xBA.
Ida returns unidentified data segments with DB BA sequences. This is good. The fact that Ida did not encounter these during its initial walk through the code means that this was unreachable, which is consistent with a code block that is copied to memory and executed later on.
Found candidate function at 0x5359 in ROM space. Not sure where it gets copied into RAM yet. Makes a lot of calls to in/out on the 8155 on ports A and B, which are the keyboard ports.
Function is complicated and multi-tiered, still not obvious where mapping from key code to ASCII code happens.
Found candidate table at ROM location 7BF1:
ROM:7BF1 aZxcvbnmlasdfgh:.text "zxcvbnmlasdfghjkqwertyuiop[;" ROM:7BF1 .db 27h ROM:7BF1 .text ",./1234567890-=ZXCVBNMLASDFGHJKQWERTYUIOP]:" ROM:7BF1 .db 22h ROM:7BF1 .text "<>?!@#$%^&*()_+" ROM:7BF1 .db 0
That looks a heck of a lot like a keyboard mapping table to me. Tempted to modify and burn a ROM to test theory, but 27C256 parts aren’t in yet so I’ll keep on poking around at the code while I get progressively later for dinner :P
To confirm hypothesis, look for instruction motifs that could use 7BF1-ish area as a table lookup.
Not yielding any results.
Try searching from boot vector for any clues.
Noted that stack pointer is initialized to 0xFCC0. This supports ROM-low, RAM-high hypothesis. 831 bytes or so left for stack (assume it grows up???)
Handy reference found on web: http://satyap.csoft.net/8085crd.txt (instruction set ref).
8155: B8 is initialized to 0x43
8155: BA is initialised to 0xEC
8155: B9 is initialized to 0xFF
8155: BA is re-initialized to 0xED
Bingo. Code copying sequences found:
ROM:7DE7 loc_7DE7: ; CODE XREF: ROM:7D54j ROM:7DE7 ; ROM:7D5Ej ... ROM:7DE7 lxi sp, 0F5E6h ROM:7DEA call sub_7EE1 ROM:7DED mvi b, 90h ; 'É' ROM:7DEF lxi d, 0F5F0h ROM:7DF2 lxi h, 35Ah ROM:7DF5 call sub_2542 ROM:7DF8 call sub_7EC6 ROM:7DFB mvi a, 0Ch ROM:7DFD sta 0F930h ROM:7E00 mvi a, 64h ; 'd' ROM:7E02 sta 0F931h ROM:7E05 lxi h, 5B46h ROM:7E08 call sub_5A7C ROM:7E0B call sub_6C93 ROM:7E0E mvi b, 58h ; 'X' ROM:7E10 lxi d, 6BF1h ROM:7E13 lxi h, 0F962h ROM:7E16 call sub_3469 ROM:7E19 mvi b, 0D1h ; '-' ROM:7E1B xra a ROM:2542 sub_2542: ; CODE XREF: sub_1962+13j ROM:2542 ; sub_2542+5j ... ROM:2542 mov a, m ROM:2543 stax d ROM:2544 inx h ROM:2545 inx d ROM:2546 dcr b ROM:2547 jnz sub_2542 ROM:254A ret ROM:254A ; End of function sub_2542
Notes:
0x90 bytes at 0x35AH are copied to location 0xF5F0
cool! F5F9 is the interrupt handler we were looking for. Let’s check it out.
ouch, the interrupt handler is just this:
ROM:0363 ei ROM:0364 ret
Foiled! but not all is lost. Hypothesize that runt handler is installed while system initializes to prevent keyboard events from fouling up machine during initialization time. Thus, a secondary copy might exist somewhere. Query for other memory-copying routines.
Looking farther on–
0x58 bytes at 0x6BF1 is copied to 0xF962
This approach is not yielding fruit. Let’s try looking for instructions that touch 0x7BF1, our purported keyboard table lookup.
Here’s a locus of information:
ROM:718E loc_718E: ; CODE XREF: ROM:721Fj ROM:718E lxi h, 7C49h ROM:7191 rrc ROM:7192 jc loc_71B5 ROM:7195 lxi h, 7CA1h ROM:7198 rrc ROM:7199 jc loc_71B5 ROM:719C rrc ROM:719D jnc loc_71AE ROM:71A0 lxi h, 7BF1h ROM:71A3 dad b ROM:71A4 push d ROM:71A5 mov d, a ROM:71A6 call sub_7233 ROM:71A9 mov a, d ROM:71AA pop d ROM:71AB jz loc_71B7 ROM:71AE ROM:71AE loc_71AE: ; CODE XREF: ROM:719Dj ROM:71AE rrc ROM:71AF cc sub_722C ROM:71B2 lxi h, 7BF1h ROM:71B5 ROM:71B5 loc_71B5: ; CODE XREF: ROM:7192j ROM:71B5 ; ROM:7199j ROM:71B5 dad d ROM:71B6 ROM:71B6 loc_71B6: ; CODE XREF: ROM:7210j ROM:71B6 dad b ROM:71B7 ROM:71B7 loc_71B7: ; CODE XREF: ROM:71ABj ROM:71B7 ; ROM:7229j ROM:71B7 pop psw ROM:71B8 mov a, m ROM:71B9 jnc loc_71C1+1 ROM:71BC cpi 60h ; '`' ROM:71BE rnc ROM:71BF ani 3Fh
An interesting coincidece of a subfunction called here:
ROM:7233 sub_7233: ; CODE XREF: ROM:71A6p ROM:7233 mov a, m ROM:7234 mvi e, 6 ROM:7236 lxi h, 7CF9h ROM:7239 ROM:7239 loc_7239: ; CODE XREF: sub_7233+Bj ROM:7239 cmp m ROM:723A inx h ROM:723B rz ROM:723C inx h ROM:723D dcr e ROM:723E jp loc_7239 ROM:7241 ret
0x7CF9, called loaded above into the “h” register, contains the following data:
ROM:7CF9 numkeytbl: .db 6Dh ; m ROM:7CFA .db 30h ; 0 ROM:7CFB .db 6Ah ; j ROM:7CFC .db 31h ; 1 ROM:7CFD .db 6Bh ; k ROM:7CFE .db 32h ; 2 ROM:7CFF .db 6Ch ; l ROM:7D00 .db 33h ; 3 ROM:7D01 .db 75h ; u ROM:7D02 .db 34h ; 4 ROM:7D03 .db 69h ; i ROM:7D04 .db 35h ; 5 ROM:7D05 .db 6Fh ; o ROM:7D06 .db 36h ; 6 ROM:7D07 .db 1 ; ROM:7D08 .db 6 ; ROM:7D09 .db 14h ; ROM:7D0A .db 2 ; ROM:7D0B .db 20h ; ROM:7D0C .db 7Fh ; ROM:7D0D .db 9 ;
One will observe that the numeric keypad mode does a mapping of m=0, j=1, k=2, etc. So we must be really close. This also indicates that possibly getting the numeric keypad mode to work correctly with dvorak mappings could be a bit of a pain because they special case that computation.
Also, reviewing the code segment some more, one can see that the “h” register is getting the location of the mapping table, and then a series of “dad” (direct add, basically index adds of “h”) instructions are called. Then, memory is fetched off of derefrences from [h]. I think it is pretty safe to say this is where the mapping occurs.
Testing
Now, to test the theory, let’s patch the location and burn some ROMs.
Let’s figure out what the new table mapping should be. Basically, I think this table works as follows:
code qwerty dvorak ------------------------ 0 z ; 1 x q 2 c j
Thus, translating this:
qwerty: zxcvbnmlasdfghjkqwertyuiop[;',./1234567890-=ZXCVBNMLASDFGHJKQWERTYUIOP]:"<>?!@#$%^&*()_+ dvorak: ;qjkxbmnaoeuidht',.pyfgcrl\s-wvz1234567890[]:QJKXBMNAOEUIDHT"<>PYFGCRL=S_WVZ!@#$%^&*(){}
Now, patch it in with Hackman.
Burn EEPROM and install onto board.
Test. Looks like I swapped “m” and “n” and I just realized that by giving myself { and } symbols I’ve eliminated + and ?, which are useful for programming in BASIC. Re-patch ROM and install.
So did you ever get numeric keypad mode working w/ your new mapping? Doesn’t seem like it should be *that* hard…
Looks like I swapped “m” and “n” and I just realized that by giving myself { and } symbols I’ve eliminated + and ?, which are useful for programming in BASIC. Re-patch ROM and install.
Nice Thanx
Thanks. Thanks for typing this. It is always awesome to see someone give back to the public.
I loved the 102. I had the Kyocera KC85 (same as a Tandy model 100) in high school…carried it in the Radio Shack soft blue briefcase with the tape drive. Only kid in the place with a laptop.
Your website has some incredibly beneficial information on it. I find your approach helpful and stimulating. I question if your background is truly in security or writing. Just kidding. I truly do take pleasure in your material appreciate it.