Archive for November, 2020

Ascii Keyboard project

November 30, 2020

When we put together the ZRT-80 it was complete luck that there was an unassembled kit for an ascii keyboard to just use for that project. Plenty of people aren’t so lucky and have to resort to extremely expensive vintage offerings or converters that use pretty outdated keyboards as the base anyhow. I can’t exactly throw stones because I’m still using an older keyboard every day and the matrix I chose for this project is not really great quality, but at least it was free. Well, at least it was free to me. The archer 227-1020 keyboard is not exactly cheap these days if you want to do an exact clone of this project. EDIT: this is a Coleco Adam keyboard. In fact, to interface with my matrix you need a nearly unobtainable 2.5mm spacing flat flex socket that’s exactly 21 positions wide. Almost no one makes those connectors in that spacing any more and only one that I could find that wide.

Even if you don’t want to spend an unreasonable amount of money on a hard to use and poor feeling keyboard, this project can help with other options. We live in the golden age of custom keyboards! standardized switch shapes and footprints, custom printed and molded keycaps, diy frames and 3d printed cases, you could do everything that people do to make custom USB keyboards and replace the controller with mine here and you have an extremely high quality parallel keyboard for any old ’70s or ’80s system that needs it. If you’re doing your own matrix and can lay it out however you want then you might look at the 2376 encoder chip which (with minimal extra parts) can serve as a one-chip solution parallel keyboard.

Once I had obtained the connector for the key matrix I had to decide on a strategy for talking to the keyboard and the computer/terminal. I needed 21 pins for the matrix, 8 data bits, and a strobe line. I read somewhere that slower computers have a busy line so they can keep the keyboard from sending more data until the computer is ready so we can also add that. I’m up to 31 pins and I don’t even have any LED indicators or aux switch inputs/outputs. At that big of a pin count my microcontroller options are annoyingly expensive so we’re back to the old atmega328 and associated chips to reduce the pin count. Above you can see 2x 74hc138 chips wired together as a 4-to-16 matrix scanner, a 74hc595 as a SPI to parallel output chip for driving the bus to the computer, a 74hc597 as a SPI to parallel input for reading the matrix, and a 74hc86 xor gate for selectable inversions of the strobe and busy lines. I’m pretty proud of this setup but it needed a little rework after first contact with the matrix.

Here is my brilliant attempt to scan the key matrix. These ‘138 chips output a high logic level on all the outputs except the one that’s selected. These days chips tend to go into high-z mode on the lines that aren’t being used but in some applications driving outputs like this would reduce the need for pull up resistors and cut down on the part count of the design. I used one of the positive enable lines and one of the negative enable lines to add a fourth address line to these two chips, making it act like one big one. This meant that I just needed one extra pin on the arduino and I could double my scanning output. This worked, but if you look up at the matrix layout above we have a problem. When you press and hold one key while pressing another it is possible to interfere with the scanning and give the voltage somewhere else to escape. On this matrix the modifier keys, control and shift, are in the matrix, but not on any shared rows. This means they can be scanned by the matrix and if you do it right they will not interfere with any other keys. I did it wrong. Holding shift and A for example connects two of the outputs of the ‘138s together, inevitably connecting a high output to a low output whenever one of those rows is scanned. I then had to reverse course.

Here is my revised (cut down) strobe circuit, now only scanning the 8 columns. This also shows my use of XOR gates for selectable inversion of the strobe and busy lines. With this configuration you can run the keyboard with the busy line unconnected and the pull up will make it think that the connected computer is always ready for more data. If the computer needs inverse polarity of either or both of those lines just short those jumpers and you can invert the signals in hardware. If you want a really general purpose keyboard I guess you can break those out to toggle switches, but usually something like this would be paired with a specific computer or terminal and they can be set once (also possible to do in software, but I find it annoying to have to recompile some software every time I want to change a little setting like that).

Here is the new 597 chained input section. This can read up to 16 lines of the matrix all latched in at once and using hardware SPI too! The 595 adds 8 output pins and only costs one more pin on the arduino as they are the same bus. The chainable output on the 595 is left floating because we don’t need to drive another chip (although we could) and the 597s are tied together with theirs. This would have been a perfect time to use SIP resistor packages but I didn’t have any of the right size convenient. I can use the unused input lines to detect if there’s a problem with the hardware or the reading routine for the chips. The top 3 lines are always wired to 5v through their pull up resistors, the unused chainable input is wired to ground. That means if I read anything other than high logic signals on the lines that are supposedly unconnected I have an issue, and if I read three or more bytes and the additional bytes are anything other than low logic signals I also have an issue.

The hardware was the easy part, the software is where things are more interesting. Driving the ‘138 scanner is trivial, just put the address on the bus. Converting from a byte to individual lines though is nice and compact: We iterate over the entire array of output pins, writing the result of bit shifting and checking if the resulting number is even or odd, basically if the least significant bit is 0 or 1. The bit shifting just puts that bit in the position to drive the output of that modulus check.

  //set address line
  address += 1;
  if (address > 7)
    address = 0;
  for (int i = 0; i < sizeof(addr_pin) / sizeof(addr_pin[0]); ++i)
    digitalWrite(addr_pin[i], (address >> i) % 2);
  delay(1);

Then we have to read from the ‘597s. We take that data into a 2-byte wide variable and similarly check if the bit at a given position is a one or a zero. In this case a zero means the key is being detected as down. we then update a current working copy of all the key statuses (if they are pressed or released)

  SPI.beginTransaction(set597);
  digitalWrite (ss, LOW);
  digitalWrite (ss, HIGH); //Latch inputs
  //read entire row of keys
  temp = SPI.transfer(0);
  temp += (SPI.transfer(0) << 8);
  SPI.endTransaction();
  for (int i = 0; i < 16; i++) //iterate through each bit of the 16 lines we just read
  {
    if (!((temp >> i) % 2))
    {
      key_status[i][address] = 1;
    }
    else
    {
      key_status[i][address] = 0;
    }
  }

The next part is a bit hard to follow. We iterate through the whole matrix (8×16) checking the key statuses versus what they were last time we scanned them. If they move from a 0 to a 1 the key must have been just pressed, if it went from a 1 to a 0 it must have been released. On key down, if the key is shift or control and nothing else is complicating things we move into either control or shift mode. In these modes the keyboard keys are fully remapped to send different codes, all definable in tables. On key release if you are releasing the shift or control keys we cancel that mode unless some lock has been set. This keyboard has a lock key so I decided to make shift lock and control lock each work as if you are holding down those keys all the time. The lock status is indicated by the state of two LEDs that can be wired to the top of a future enclosure. If a key goes down and you are in either shift or control mode then we check to see if it’s the lock key and either set or un-set the lock mode. The last thing we do after checking each key is to update the other table of key statuses so we can detect the next time it goes up or down properly.

  for (int i = 0; i < 8; i++)
  {
    for (int j = 0; j < 16; j++)
    {
      if (prev_key_status[j][i] && !key_status[j][i]) //key went up
      {
        if (!controled && !shifted)
        {
          //do nothing
        }
        if (i == 0 && j == 13 && !shift_locked)//hardcoded shift
          shifted = 0; //only stop shifting if the lock isn't set
        if (i == 0 && j == 12 && !control_locked)//hardcoded control
          controled = 0; //only stop controling if the lock isn't set
      }
      if (!prev_key_status[j][i] && key_status[j][i]) //key went down
      {
        if (controled)
        {
          if (i == 0 && j == 14 && !control_locked)//hardcoded lock
          {
            control_locked = 1; //set control lock
            digitalWrite (ctrl_lock, control_locked);
            Serial.println("set control lock");
          }
          else if (i == 0 && j == 14 && control_locked)//hardcoded lock
          {
            control_locked = 0; //un-set control lock
            digitalWrite (ctrl_lock, control_locked);
            Serial.println("un-set control lock");
          }
          else
          {
            HandleKey(j, i, 0, 1);//controlled key
          }
        }
        else if (shifted)
        {
          if (i == 0 && j == 14 && !shift_locked)//hardcoded lock
          {
            shift_locked = 1; //set shift lock
            digitalWrite (shift_lock, shift_locked);
            Serial.println("set shift lock");
          }
          else if (i == 0 && j == 14 && shift_locked)//hardcoded lock
          {
            shift_locked = 0; //un-set shift lock
            digitalWrite (shift_lock, shift_locked);
            Serial.println("un-set shift lock");
          }
          else
          {
            HandleKey(j, i, 1, 0);//shifted key
          }
        }
        else if (!controled && !shifted)
        {
          // modifier keys handled state triggered now, not edge triggered
          HandleKey(j, i, 0, 0);//normal key
        }
      }
      prev_key_status[j][i] = key_status[j][i];
    }
  }

I still had some edge cases based on how the matrix was scanned so I had to add some checks outside of the key edge detection logic, I added some state dependent logic. I may be able to go entirely to state based logic but for now this works. Depending on what order I scan that far left column it is possible to detect two keys going down in the same scan window and act on them in whichever order I want, but that’s probably too far in the weeds for a matrix that scans so fast.

  //this handles edge cases where you unlock while not holding down the modifier key
  if (!key_status[13][0] && !shift_locked)
    shifted = 0;
  if (!key_status[12][0] && !control_locked)
    controled = 0;
  //this handles edge cases where you unlock while holding down the other modifier key
  if (key_status[12][0] && !key_status[13][0] && !shift_locked) //holding control and not holding shift and not shift locked
    controled = 1;
  if (key_status[13][0] && !key_status[12][0] && !control_locked) //holding shift and not holding control and not control locked
    shifted = 1;

The HandleKey function basically replaced my debug statements, taking all the data I used to print out and handling it sanely.

void HandleKey(int j, int i, boolean shift, boolean control)
{
  if (!shift && !control)
  {
    if (key_scancode[j][i] > 0x7f)
      HandleMacro(key_scancode[j][i]);
    else
      SendKey(key_scancode[j][i]);
  }
  if (shift)
  {
    if (shifted_scancode[j][i] > 0x7f)
      HandleMacro(shifted_scancode[j][i]);
    else
      SendKey(shifted_scancode[j][i]);
  }
  if (control)
  {
    if (controled_scancode[j][i] > 0x7f)
      HandleMacro(controled_scancode[j][i]);
    else
      SendKey(controled_scancode[j][i]);
  }
}

The next function is something I’m pretty happy with. Macros can be any character from 0x80 to 0xFF, I started counting backward from 0xFF because it’s easy to see all the undefined 0xFF key behavior in the scancode tables. This means that in my current implementation you cannot define extended ascii characters except through single character macro sequences which is a bit cumbersome to read but I don’t expect to come up often, if at all. I don’t actually know how various terminals and computers will respond to getting 8-bit extended ascii sequences over a parallel keyboard port, the 2376 controller chip won’t generate them so maybe they’re just passed on to the operating system or program? maybe they’re filtered out? maybe they just ignore the high bit entirely.

These macros can be useful for talking to a specific terminal and automating escape or control sequences. They can also be useful for talking through the terminal transparently and talking to the computer operating system or program to execute a specific function that could take multiple keys.

void HandleMacro(int macro)
{
  switch (macro) {
    case 0xff:
      Serial.println("do nothing");
      break;
    case 0xfe:
      Serial.println("up"); //from zrt-80 manual
      SendKey(0x1b); //ESC
      SendKey(0x5b); //[
      //SendKey(0x31); //1 might be unneeded, defaults to 1
      SendKey(0x41); //A
      break;
    case 0xfd:
      Serial.println("down"); //from zrt-80 manual
      SendKey(0x1b); //ESC
      SendKey(0x5b); //[
      //SendKey(0x31); //1 might be unneeded, defaults to 1
      SendKey(0x42); //B
      break;
    case 0xfc:
      Serial.println("left"); //from zrt-80 manual
      SendKey(0x1b); //ESC
      SendKey(0x5b); //[
      //SendKey(0x31); //1 might be unneeded, defaults to 1
      SendKey(0x44); //D
      break;
    case 0xfb:
      Serial.println("right"); //from zrt-80 manual
      SendKey(0x1b); //ESC
      SendKey(0x5b); //[
      //SendKey(0x31); //1 might be unneeded, defaults to 1
      SendKey(0x43); //C
      break;
    case 0xfa:
      Serial.println("wtf? no. stop."); //you are mucking with control and shift at the same time, stop it!
      break;
    case 0xf9:
      Serial.println("Erase in Display"); //from wikipedia ANSI_escape_code
      SendKey(0x1b); //ESC
      SendKey(0x5b); //[
      SendKey(0x32); //2
      SendKey(0x4a); //J
      break;
    case 0xf8:
      Serial.println("home"); //from zrt-80 manual
      SendKey(0x1b); //ESC
      SendKey(0x5b); //[
      SendKey(0x48); //H
      break;
    case 0xf7:
      Serial.println("F1"); //from wikipedia ANSI_escape_code
      SendKey(0x1b); //ESC
      SendKey(0x5b); //[
      SendKey(0x31); //1
      SendKey(0x50); //P
      break;
    case 0xf6:
      Serial.println("F2"); //from wikipedia ANSI_escape_code
      SendKey(0x1b); //ESC
      SendKey(0x5b); //[
      SendKey(0x31); //1
      SendKey(0x51); //Q
      break;
    case 0xf5:
      Serial.println("F3"); //from wikipedia ANSI_escape_code
      SendKey(0x1b); //ESC
      SendKey(0x5b); //[
      SendKey(0x31); //1
      SendKey(0x52); //R
      break;
    case 0xf4:
      Serial.println("F4"); //from wikipedia ANSI_escape_code
      SendKey(0x1b); //ESC
      SendKey(0x5b); //[
      SendKey(0x31); //1
      SendKey(0x53); //S
      break;
    case 0xf3:
      Serial.println("normal charset"); //from zrt-80 manual
      SendKey(0x1b); //ESC
      SendKey(0x5b); //[
      SendKey(0x67); //g
      break;
    case 0xf2:
      Serial.println("alt charset"); //from zrt-80 manual
      SendKey(0x1b); //ESC
      SendKey(0x5b); //[
      SendKey(0x66); //f
      break;
    case 0xf1:
      Serial.println("clear display"); //from zrt-80 manual
      SendKey(0x1b); //ESC
      SendKey(0x5b); //[
      SendKey(0x45); //E
      break;
    case 0xf0:
      Serial.println("clear"); //spare
      SendKey(0x1b); //ESC
      SendKey(0x5b); //[
      SendKey(0x32); //2
      SendKey(0x4a); //J
      break;
    default:
      Serial.println("undefined macro");
      break;
  }
}

The last part is how I deviate a little from the 2376 I’m modeling my timing on. That controller holds a state for as long as you are holding down that key. With my multi-key sequence macros I can’t do that. I took the information on the world’s fastest typist (216WPM), converted that to characters per minute (1080) and then to the maximum amount of time the whole transaction needs to take place (55.5ms). That helped me set the timing of the keyboard pulses so as to mimic someone typing faster than the world’s fastest typist hopefully to allow for the whole macro to be sent out before someone tries to hit the next key (although for very long macros that time would have to be further reduced to get that effect).

void SendKey(byte code)
{
  //set bits
  SPI.beginTransaction(set595);
  digitalWrite (ss, LOW);
  //set output bits
  SPI.transfer(code);
  digitalWrite (ss, HIGH);
  SPI.endTransaction();
  Serial.println(code, HEX);
  //settle 4.4ms + 88cycles @ 50khz = 6.16ms
  delayMicroseconds(6160);
  //set strobe
  digitalWrite (strobe, HIGH);
  //settle 0.5us + 30ms (way exceed human typing speed)
  delay(30);
  //look for busy signal
  while (!digitalRead(busy)) { //blocks on busy signal being low
    Serial.println("blocking for busy line");
  }
  //un-set strobe
  digitalWrite (strobe, LOW);
  //settle for more than 0.5us
  delayMicroseconds(1);
}

The keys I chose for the macro sequences came from the manual for the ZRT-80 (which this will probably hook up to), the manual for the ADM-3a (a pretty standard terminal), and the wikipedia entry on escape sequences. I will probably want to tune the macros to the application, but for now these will do. To edit the codes that each key sends just edit the three tables near the top of the code, 0x00 indicates no key is present in the matrix (or to send the null byte), 0xff indicates that a function has not been assigned to that key in that mode, anything 0x7f or less just gets sent right out, anything above that gets handled by the macro function to send one or more keys in sequence. I wired the output exactly like a parallel port that printers hook up to so if I were to get a dot matrix printer I may be able to make this act like a teletype in loopback mode. Now I just have to model up and print an enclosure. Either that or I can just buy a metal keyboard case and cut a big hole for the keys to stick out.

c64DTV power on reset circuit

November 27, 2020

I wanted to get this project all documented at once, but that’s a pretty tall order with how many moving parts it has. I also have issues with being able to track down all the information I want to include in a blog entry months after I did the project. I’m going to start with the latest aspect of this project I’ve finished and work backward through parts that I can document.

This is the issue I have. With my ps/2 keyboard when I turn on the c64DTV I get a “V” on the screen. As I type I get some characters, but not all, and a bunch of “V”s included until some point where it snaps out of it and is ok. The easiest option for this is to press the reset button after the device is powered on and then the keyboard operates properly. I recessed my reset button a little too far to be comfortable so now I want to have an automatic option. The standard option to get around this slight flaw in the c64DTV implementation is a keyboard twister. The problem I have is that even though it fixed the initialization function the keypresses were much slower, there was lag, it got confused if you type too fast. I don’t need the german translation functions or macros offered by that device so I designed my own solution.

I started by looking up “power on reset circuit” and came across the one for the apple 2. The problem is in order to simulate pushing the reset button I wanted to wait some amount of time, then press the button for a different amount of time, then release it for the duration of the power being applied. This circuit simply generates a high pulse for a given amount of time. In order to generate a pulse after some time I need something more complicated.

This circuit has two of those pulse generators that have slightly different periods. I combined them with a 2n7000 to get an open drain output and only have the reset pulse enabled when the output of side A is low and B is high. I did that by grounding the 2n7000 through the A half of the 556 so the pulse on the B side is not causing a reset until the pulse on the A side is done.

prototype circuit installed in the c64dtv
compact module, removable if needed
Fixed!

Upgrading a cloned logic analyzer

November 26, 2020

In my last post the logic analyzer I built so many years ago was actually useful. I did have some problems though. Due to it not being in a case or protected at all it wound up not having all 8 functioning channels. For this project it wasn’t a big deal, but for the future I want to fix that. While I’m fixing that I might as well make an interesting upgrade: double the channels! The first hack was centered around cloning an existing device to use its software, that is no longer desirable. Pulseview is now plenty good for using on a regular basis and I have no need of any proprietary software. That being said, I still (had to?) write a cloned eeprom to this board to make it identify as what I wanted.

The one I chose is the Braintechnology USB-LPS, this device is auto-detected in Pulseview with 16 channels and therefore can do what I want. I tossed out the shield that I had made so many years ago and decided to use the onboard eeprom spot since I no longer want to be able to switch between USB identifiers.

The chip I used happens to be a 24LC16B, since I’m not trying to get around the Saleae copy protection I don’t have to worry about 16bit addressing. The formatting I determined from this site ended up being C0 (tells the board to use the eeprom’s following VID/PID/DID) D0 16 98 04 84 23 00. I don’t know what DID I should use but it doesn’t seem to matter. Note that the addressing is backward-endian so every other byte is swapped. These bytes need to be placed at address 0x00 and it will detect in windows properly.

I had assumed that the software would just sort itself out because of the USB Vid/Pid setup. That was not the case for me. I got the device show up as unidentified until I installed the Braintechnology USB-LPS driver. That went well but still resulted in it not showing up in Pulseview. I got the error “fx2lafw: Failed to open potential device with VID:PID 16d0:0498: LIBUSB_ERROR_NOT_SUPPORTED”. To fix it I had to run Zadig and force the device to use WinUSB driver. I find myself having to use that program a lot lately, still not really sure what the usb drivers it switches out are for.

The next step is input protection. For the last one I cloned the input stage of the Saleae directly, but I don’t think that is as important this time. Based on the above table I decided to go with 100R inline resistors and 74HC245 buffer chips (I don’t need the bidirectionality, but the layout of the 244 is just annoying). I’m socketing them so I can try out HC, HCT, and other families (or none). This time around I’m not limiting myself to the same footprint as the breakout since I need 16 channels plus a couple grounds. I think I’m going to omit the use of a power header like the USBee, I never used them before and usb ports can’t source that much power anyway.

Since my original design I replaced the cable that had cheap chinese micro grabbers hardwired to bits of ethernet cable with a higher quality one. The new one has 9 pins, 8 for data and one for ground, it is color coded in the resistor color order and terminates in female 0.1″ crimps so I can use brand name Tektronix micro grabbers or plug into board pins directly. The newest replacement uses the color scheme that Pulseview assigns to each of the 16 channels. 0-7 are black through purple and 8-15 are grey through green. This means that each of the standard rainbow colors are used and even reused, but I think it can be obvious the order it goes in. That is except for needing to have ground on different cables; the colors get even more confusing because black is used as a data color twice by Pulseview.

still need to crimp the second 8 conductor cable

VCP200 reverse engineering

November 26, 2020

Some years ago I got a few VCP200 Speech recognition chips. Even though they are very limited in what they can do I was motivated by the simple interface to try to find a use for them. I eventually built the circuit available in the April 1991 edition of Radio Electronics to make use of the chip (with minor modifications). After finding some soldering faults and understanding more of what the amplification and clipping sections were doing I actually got it to work! My next plan for the three chips I have now is to draw up a design for the circuit to drive them using either through hole or surface mount components and have some slightly more robust modules than the one I hand soldered together.

But this post is not about that. When I watched the EEVblog episode on this chip it interested me greatly that it was not an ASIC, but probably a microcontroller with mask ROM inside. Having done a lot of work with MAME and dumping whatever I can get my hands on this seemed like an interesting challenge. After asking around it seemed that it may be possible to electrically dump the contents of the chip using a diagnostic mode Motorola included. Now comes the challenge of building the circuit to get those modes working and eventually figure out if the data I want comes out somewhere undocumented.

That’s the problem, the documentation says that this chip, the MC6804J2 has four modes of operation, and none of the documentation for any of them indicates that it can be used to read out the internal program or data memory. Based on some previous reverse engineering efforts on similar Motorola parts we started by looking at Non-User Mode, but that ended up being a dead-end (for now). For starters the documentation says that ports A, B, and C are used to interface to external instructions, but on the J2 chip we use there’s only port B and half of port A available at all! In addition the non-user mode interface is described in no documents I can find for this chip, but we may be able to eventually infer what the interface is based on other similar Motorola chips with more surviving documentation.

Based on the schematics for these test circuits I built something similar. What I made used the reset circuit and PA6/PA7 drive circuit from the functional test (which is self-check from above), but changed the crystal to an oscillator and made a whole bunch of pin configuration jumper settable. Before we move on to my design, take a look at the circuit for the ROM verify mode. This chip has a hardware CRC circuit inside and the ROMs, when properly authored, will be able to pass a CRC check using this circuit. That means that at very least the ROM data is coming out on one or more of those pins, XORed or otherwise garbled, and that at least one pin is clocking all those bits into this circuit which can verify that the CRC passes or fails.

As you can see I have adopted my usual use of massive piles of 0.1″ header and jumpers to configure things. Here I have pin headers that can be connected to a logic analyzer (port B only) as well as jumpers to enable pull ups on any available port pin. For the non-port pins: /IRQ is floating, as is EXTAL, XTAL goes to the output of my 1.000MHZ oscillator can, MDS and TIMER are both pulled up with 1K resistors. The reset circuit pins are pulled up with 4.7K resistors as are all of port B. Port A pins 6 and 7 are pulled up with 10K resistors, and pins 4 and 5 are pulled up with 1K resistors. Now for the taking of the data.

For this we used sigrok (the pulseview GUI). I had previously built a clone logic analyzer, but really the only important part about it is that it’s compatible with this open source software stack that’s pretty easy to use. For this dump we connected to pins PB0 (D7), PB2 (D6), PB3 (D4), PB5 (D2), PB6 (D1), and PB7 (D0). The other pins didn’t seem to do anything interesting and I was having an issue with some channels of the logic analyzer (soon to be overhauled). You can see that PB7 is the master bit clock, and that makes sense because in the rom verify crc circuit it is clocking the latches. PB6 is not used in that circuit but seems to be a pulse happening once every 12 clock cycles, our frame clock. PB5 seems to be the address of memory being represented in those 12 bits of data (each address is sent twice before moving on). PB3 and PB2 look random, probably the CRC values. PB0 looks like our serial program data! Here is the explanation for the above captured data:

one frame clock every 12 bits
one cycle of data every 24 bits
we get the address twice
we get 0xff for the first data byte (not real)
we get the real data for the second data byte
also, the data is delayed by 24 bits (1 address cycle)
Example as seen in the capture pic:
1 00000000000 D1, PB7 frame clock
1 00000111010 D2, PB5 reverse is (010 1110 0000) (0x2e0 address)
1 10000000011 D7, PB0 reverse is (11 00000000 1) (0x00 data from 0x2df)
1 00000000000 (frame clock)
1 10000111010 reverse is (010 1110 0001) (0x2e1 address)
1 11111111111 reverse is (11 11111111 1) (0xff data not real)
1 00000000000 (frame clock)
1 10000111010 reverse is (010 1110 0001) (0x2e1 address)
1 11010011011 reverse is (11 01100101 1) (0x65 data from 0x2e0)
1 00000000000 (frame clock)
1 01000111010 reverse is (010 1110 0010) (0x2e2 address)
1 11111111111 reverse is (11 11111111 1) (0xff data not real)

The python code that does all this parsing is based around the raw binary logic data that Pulseview can export. This keeps the filesizes nice and small while still being pretty easy to parse. That python code was able to generate a binary file representing the entirety of the program memory on the chip.

Now that we have a big block of everything in program memory it needs to be disassembled. You can do this by hand, but that’s infinitely tedious, and since there was no disassembler for the 6804 readily available we had to write one. The first pass was my super easy method of decoding every byte. I just checked one table to see what instruction decoded to and printed the contents of that table entry. Then I checked another table to see if it was at more than one byte, if so I grabbed another byte and printed it verbatim. Then I checked that table again to see if it was three bytes long, if so I grabbed the last byte and printed it, otherwise I moved on. There’s no re-checking or anything so if you get off by one byte somewhere you may never get lined up again and you’re reading data as opcodes and everything is all screwy. Frank had an improvement to my disassembler that made it a little smarter in handling each op code and formatting them in a way that matches standard disassembly syntax better.

Inside the ROM we see key areas described in the datasheet memory map. The User Restart Vector and the User /IRQ Vector are the same because in the VCP200 datasheet the /IRQ line of the microcontroller is tied to Vcc. The self-test ROM Reset Vector goes to AF6 which is interesting since the region starts at AE0. It turns out that AE0 is used for a subroutine that gets called later. The self-test /IRQ line goes to some sort of subroutine in the middle of the self test region so that makes sense, whenever that line is triggered then the program can verify it happened based on where it is in the code. The bottom of the self-test ROM actually contains some ascii data saying “MC6804J2 – ATX” which must mean that this is the self-test code specific to that processor variant, confirming what we suspected (except now we know it’s probably the HMOS variant). In the preliminary reverse engineering of the program memory it looks like the VCP200 code looks at three bytes of the data ROM that we don’t have. It also doesn’t look like the self-test code look at it at all. I am unclear what part of this processor checks the data ROM for integrity, if any.

It’s possible the data ROM is somewhere in the data we’ve already captured, it’s also possible it’s accessible in non-user mode, or through some part of the functional test program that we’ve started reverse engineering. If we emulate the processor, then send it known streams of pulses representing speech data and see what the real chip does versus the emulated chip we may even be able to reverse engineer those three bytes it reads out of the data ROM. It should be a tedious, but simple task of designing test sequences that cause the program to read those data bytes and analyzing what it does after to determine what those bytes were. That is beyond my current level of ambition though.

Vtech Whiz Kid reverse engineering

November 21, 2020

In an effort to free up space and get some low hanging fruit projects done fast I took on this one. This is a small learning computer from the 1980s. It uses a custom segment LCD for display, an optical barcode card reader and cartridges for data, a regular speaker for sound, and a membrane keyboard for input. I decided to go fairly in depth reverse engineering the hardware even though the software (and even the architecture of the processor) is still a bit of a mystery.

The cart is a pretty good place to start. I generated an entire schematic of this but the highlights are that it’s a 4-bit interface and the jumpers can pick from different pinouts of ROM chips. J7, J8, J9, and J10 pick which 4 bits are sent to the card edge, since the most common ROM chips are 8 bits and this machine is apparently 4 bits then they only need half of the ROM. The other half of the mask ROM is probably a different program and would be sold with the jumpers in the other configuration for that expansion pack. J11 and J12 swap which pin is A11 and which is an unused chip select pin, this allows a TMS4732 like is present right now or a more standard 2732 to be used, see my ROM pinouts for the slight differences. The other interesting thing is the use of a PNP NPN pair of transistors to cut power to the chip unless the chip select is asserted. This is clearly to cut down on power consumption of a battery powered toy, but thinking back on it now, why don’t ROM chips do this internally to save power? There’s also an unpopulated one for if a 2732 is fitted.

The next part I reverse engineered was the barcode reader. It uses an LM358 to amplify and digitize the signal from the optical sensor so the data can be read from the cards. There is only one track so you have to pull the card at just the right rate, but I was never able to do it consistently. It’s possible the part values have drifted far enough that the pot onboard couldn’t bring the signal back in spec, or maybe I’m just bad at it.

The screen is quite primitive, it’s an LCD segment display with some segments to create animations (like the game and watch series) but also some starbursts for character display. I managed to get a picture of the whole thing lit up due to some sort of bug when I had the keyboard disconnected. This should help if anyone in MAME can get a virtual set of segments drawn up. Higher quality versions of the back transparency and the rest of these images (as well as the cards and rom dump) are hosted at the internet archive.

Next I generated a schematic for the entire mainboard. This took a while but was not that difficult, it is only a one sided pcb with a lot of jumpers. The main takeaway is that the processor (which has to have internal ROM for the default game) also has the ability to drive the LCD directly. It has a parallel bus for reading the keyboard and the ROM from the cart, and the power button on the key matrix is a soft power switch at the hardware level.

My pin numbering scheme is not that inscrutable, is it?

The last thing of importance is the main processor itself. It is labeled T7826 with a date code of the 36th week of 1985. It can drive the LCD directly, it seems to have a 4-bit data bus and at least 11 bits of address space. The oscillator is either internal or an RC oscillator on pin 73. It uses a QFP92 package. From my research this is likely a Toshiba tlcs-42, 46a, or 47 series 4-bit microprocessor but I’ve never seen a datasheet for one in a package grater than 80 pins. I went one step further though and sent it in for decapping, having learned just about all I needed to know from the working machine. There hasn’t been a conclusion yet regarding the ROM bits or anything else, but maybe someone out there can verify my pinout findings or the processor architecture from the die.

Simple tool for reverse engineering key matrices

November 11, 2020

I decided to put together a tool for hooking up an arbitrary key matrix to an arduino and decode it by scanning it and reading the buttons pushed out the serial port. The gimmick I have added beyond most key decoders is I don’t care which are rows and which are columns. It was also pointed out to me that based on the information that we get over the serial port you can tell if the matrix has embedded diodes or not.

#define Start_pin          26
#define Stop_pin           49

void setup() {
  //start serial connection
  Serial.begin(115200);
  //set all pins as inputs to be read
  for (int i = Start_pin; i < (Stop_pin + 1); i++)
  {
    pinMode(i, INPUT_PULLUP);
  }
}

void loop() {
  //iterate through pins, outputting on one at a time
  for (int i = Start_pin; i < (Stop_pin + 1); i++)
  {
    pinMode(i, OUTPUT);
    digitalWrite(i, 0);
    //delay(10);  //settling time?
    //iterate throughall pins reading them as inputs
    for (int j = Start_pin; j < (Stop_pin + 1); j++)
    {
      //if the output pin is not the same pin being read and there is a connection
      if (!digitalRead(j) && i != j)
      {
        //print connection
        Serial.print("found connection: ");
        Serial.print(i);
        Serial.print(" and ");
        Serial.println(j);
      }
    }
    //set pin back to input before moving on to the next pin
    pinMode(i, INPUT_PULLUP);

  }
}

That’s all my code. I built it for use on an arduino mega so you get as many pins as possible, but it should work on anything. I set one pin at a time as an output and read all the rest in. This means that with a 21 pin matrix it could be a 20×1, 19×2, 18×3, … 12×9, or 11×10 matrix and I will still be able to decode that without knowing what it was beforehand. Since I scan both directions you will get a message like this with no diode across the switch:

found connection: 28 and 31
found connection: 31 and 28
found connection: 28 and 31
found connection: 31 and 28
found connection: 28 and 31
found connection: 31 and 28

Or this if there is a diode:

found connection: 28 and 31
found connection: 28 and 31
found connection: 28 and 31

One possible enhancement would be to use analog pins where possible and set a threshold for inputs to detect a crappy matrix that ends up as high resistance, but if that’s the case you have other problems (and I don’t have that many analog pins to use).

Ithica IA-1010 rev 1.3 2716 mod and enhancement

November 8, 2020

After the success of getting our Poly-88 up and running I wanted to clean up some hacks and simplify things. One of those hacks was to replace the 2708 monitor ROM with a stack of sockets bodge wired together in such a way to adapt a 2716 to the pinout on the board. After reading in the manual that there are instructions for converting the board to use a 2716 I wanted to do that so the card sits flatter in the card cage. This will explain how to make just the switch to the 2716 without increasing the monitor ROM size (power and chip select lines), how to expand the monitor ROM (and make it switchable), and how to quickly enable and disable the ROM entirely.

That’s the instructions for swapping the 2708 for a 2716. I notice a few things right away that I knew would be there, like the different power supplies and an enable line. I also notice something mucking with A10 (the highest address available on a 2716 and not available at all on a 2708. That tells me that this mod will map the entirety of the 2716 into memory and we will lose that 0x400 worth of RAM that we had on the other side of the monitor ROM. Initially this was not what I wanted, but I decided that I would like to make this switchable and I think I know how to do it.

In the instructions above it says to cut IC23’s connection to A10 and ground it. The trace for that is on the top of the board so I pulled the socket and cut the trace. I will make it switchable in a bit, but the instructions are not clear where A10 actually goes on this board. Looking at another line in those instructions you can see we’re supposed to attach IC25 pin 11 to one of the 2716 pins, I verified that this used to be connected to IC23’s pin 12. It seems that the 2708 -> 2716 size mapping increase is caused by these two things. I just need to be able to pick between grounding A10 on the 2716 while tying the board’s A10 line to IC23, and tying the board’s A10 line to the 2716 (IC36) while grounding IC23’s pin. I chose a 5 pin header and two jumpers to accomplish this. The pinout goes GND-IC23-IC25-IC36-GND and by moving two jumpers I can swap the size of the monitor ROM this board recognizes. I also cannot connect both IC23 and IC36 to IC25 simultaneously because the jumpers share that physical pin.

I went with right angle header because I knew I wanted low profile, I didn’t want wires or headers sticking outside of the card edges, and I had them handy. You also see a three pin header hooked to a different breakout that used to hold a wire link jumper. I did this so I could remove the monitor ROM entirely if I wanted. Look:

This section says that for using the monitor ROM you connect J and H, but if you don’t need it connect J and K. That’s fine and all, but look at the top. Revision 2 of the board had the benefit of being laid out so I could put pin headers and use shorting bars to quickly jump between configurations. This is an earlier revision so I don’t get that nicety. Now I have it.

vias used as pass through holes, no electrical contact is made

Here you can see all my yellow wires for this mod. You can also see one lone black wire connecting ground to an unused pin on the front panel header that allows me to power the LEDs I used on my bargraph substitute for a front panel. You can also see some red wires that were installed before I got the board. I assume that’s to fix compatibility with the expandoram or something but I didn’t like how they were done soldering directly to chips. I understand that the board cuts I made (you can see some in this picture) are permanent changes to the traces on the board and are not as easily undone as desoldering from chip legs, but I don’t think anyone will need to put this back to using a 2708. Having a programmer that can burn 2708 chips is odd enough these days, but if you do have some chips to flash I think you would want to install them into a board that you really don’t want to cut up (for historical reasons). 2716 equivalent EEPROM chips are also super convenient so you don’t have to cook them for every change. I also find that the chips will need replacing more often than the board will need rewiring, so swapability is important.