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

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 loc_71AE:                               ; CODE XREF: ROM:719Dj
ROM:71AE                 rrc
ROM:71AF                 cc      sub_722C
ROM:71B2                 lxi     h, 7BF1h
ROM:71B5 loc_71B5:                               ; CODE XREF: ROM:7192j
ROM:71B5                                         ; ROM:7199j
ROM:71B5                 dad     d
ROM:71B6 loc_71B6:                               ; CODE XREF: ROM:7210j
ROM:71B6                 dad     b
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 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.


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:


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.

Works great!

Chip Shots

I couldn’t resist taking some shots of the EEPROM die.

5 Responses to “Dvorak on the Tandy 102”

  1. C. Scott Ananian says:

    So did you ever get numeric keypad mode working w/ your new mapping? Doesn’t seem like it should be *that* hard…

  2. 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

  3. Joanna Treml says:

    Thanks. Thanks for typing this. It is always awesome to see someone give back to the public.

  4. Christopher Strong says:

    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.

  5. Daryl Pinena says:

    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.

Leave a Reply