Sharp X68000 USB Keyboard + Mouse adapter (real USB protocol)

Started by Zuofu, May 20, 2022, 02:45:22 PM

Previous topic - Next topic

Zuofu

Hi all,

I made one of these for my FM Towns, and so I've decided to port the code which is similar to the X68000. Since the X68000 supports both keyboard and mouse through the same port (sort of like a ADB Mac), a single adapter can support both peripherals simultaneously. You can either use a simple hub with a wired KB+Mouse or use a combo wireless dongle (like the common Logitech devices). The biggest improvement over the PS2 only adapters floating around is true USB device support, especially important for wireless devices which almost never support the PS2 protocol through a passive adapter.

Requirements and Amazon links to buy the components are in the comments in the code. Feel free to modify this to suit your needs.

// Sharp X68000 to USB keyboard and mouse converter
// by Zuofu
//
// Supports true USB protocol (even through hub), so all types of USB keyboards and mice may be used.
// Combo devices (e.g. keyboard/mice combos with a single wireless receiver) are supported.
// Note that the X68000 keyboard has many more keys than a standard US 104 key, so feel free to adjust keymap
// to suit your needs (e.g. I don't know if software uses XF1-6 keys in a way that their shifted mapping is awkward
//
// Provided free of charge and without warranty by Zuofu, you may use this code
// for either non-commercial or commercial purposes (e.g. sell on eBay), but please
// give credit.
//
// Requirements
// -Arduino Mini Pro (3.3V/8MHz): https://smile.amazon.com/dp/B004RF9LB8 (or equivalent)
// -Mini USB Host: https://smile.amazon.com/dp/B01EWW9R1E (or equivalent)
//  note: above USB host board should have USB VBUS->3.3V connection removed and replaced with a connection from VBUS to RAW for full 5V USB compatibility
// -Mini DIN-7 connector: https://smile.amazon.com/dp/B00PZSCGPY
// -Some way to program above Arduino (typically a USB->Serial adapter)
// -Add USB Host Shield 2.0 library to Arduino (Tools->Manage Libraries...)
//
//  SHARP X68000 PIN CONNECTIONS:
//  Mini-DIN            Mini Pro
//  -----------------------------
//  pin1  +5V          RAW
//  pin2  MOUSE        D5
//  pin3  RXD          D1 (TXO)
//  pin4  TXD          D0 (RXI) - note this is technically over-voltage, can put a diode/resistor in series
//  pin5  READY        N/C
//  pin6  REMOTE      N/C
//  pin7  GND          GND
// 
//  References:
//  https://ixsvr.dyndns.org/ps2ms68k
//  https://geekhack.org/index.php?topic=29060.0
//  Sharp X68000 Technical Databook (Blue book)

//Special keys default mapping:
//{FULL WIDTH} -> RIGHT ALT
//{HIRAGANA}  -> LEFT ALT
//{KANA}      -> PRINT SCREEN
//{ROMANJI}    -> SCROLL LOCK
//{CODE ENTRY} -> PAUSE/BREAK
//BREAK        -> F11
//COPY        -> F12
//UNDO        -> END
//CLR          -> NUM LOCK
//OPT1        -> MENU
//OPT2        -> RIGHT CTRL
//CTRL        -> LEFT CTRL

//The following requires holding down Left 'Windows/GUI'
//{SYMBOL INPUT} -> WIN + NUM /
//{REGISTER}    -> WIN + NUM *
//HELP          -> WIN + NUM -
//XFn            -> WIN + Fn

#include <hidboot.h>
#include <usbhub.h>
#include <SoftwareSerial.h>

// Satisfy IDE, which only needs to see the include statment in the ino.
#ifdef dobogusinclude
#include <spi4teensy3.h>
#endif
#include <SPI.h>

//Adjust this accordingly to your mouse DPI if you have issues, 4 is a good number for 300/600 modern mice, may need to increase
//if using a high-DPI 'gamer' mouse
#define MOUSE_DIVIDER 0x04

#define CTRL_SCAN 0x71
#define SHIFT_SCAN 0x70
#define HIRA_SCAN 0x56
#define WIDTH_SCAN 0x60
#define OPT1_SCAN 0x72
#define OPT2_SCAN 0x73

SoftwareSerial x68kSerMouse (4, 5); // RX, TX (RX unused)

//KEY CODE TO X68K---------0-----1-----2-----3-----4-----5-----6-----7-----8-----9-----A-----B-----C-----D-----E-----F
uint8_t keymapping[] = {0x00, 0x00, 0x00, 0x00, 0x1E, 0x2E, 0x2C, 0x20, 0x13, 0x21, 0x22, 0x23, 0x18, 0x24, 0x25, 0x26,  //0x
                        0x30, 0x2F, 0x19, 0x1A, 0x11, 0x14, 0x1F, 0x15, 0x17, 0x2D, 0x12, 0x2B, 0x16, 0x2A, 0x02, 0x03,  //1x
                        0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x1D, 0x01, 0x0F, 0x10, 0x35, 0x0D, 0x0C, 0x1C,  //2x
                        0x29, 0x0E, 0x00, 0x27, 0x28, 0x5F, 0x31, 0x32, 0x33, 0x5D, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,  //3x
                        0x69, 0x6A, 0x6B, 0x6C, 0x61, 0x62, 0x5A, 0x5B, 0x5C, 0x5E, 0x36, 0x38, 0x37, 0x3A, 0x39, 0x3D,  //4x
                        0x3B, 0x3E, 0x3C, 0x3F, 0x40, 0x41, 0x42, 0x46, 0x4E, 0x4B, 0x4C, 0x4D, 0x47, 0x48, 0x49, 0x43,  //5x
                        0x44, 0x45, 0x4F, 0x51, 0x00, 0x72, 0x4A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; //6x

//USB keycodes for 'alternative keys' (holding down left-GUI) and their corresponding X68000 mappings
//Note that arrays must be the same size
uint8_t altKeysUSB[]  = {0x54, 0x55, 0x56, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E};
uint8_t altKeyCodes[] = {0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59};

                       
uint8_t rxbyte, last_rxbyte;
uint8_t mouse_left, mouse_right;
uint8_t leftGUI = 0;
int16_t mouse_dx, mouse_dy;

class MouseRptParser : public MouseReportParser
{
  protected:
    void OnMouseMove(MOUSEINFO *mi);
    void OnLeftButtonUp(MOUSEINFO *mi);
    void OnLeftButtonDown(MOUSEINFO *mi);
    void OnRightButtonUp(MOUSEINFO *mi);
    void OnRightButtonDown(MOUSEINFO *mi);
    void OnMiddleButtonUp(MOUSEINFO *mi);
    void OnMiddleButtonDown(MOUSEINFO *mi);
};
void MouseRptParser::OnMouseMove(MOUSEINFO *mi)
{
  mouse_dx += (mi->dX)/MOUSE_DIVIDER;
  mouse_dy += (mi->dY)/MOUSE_DIVIDER;
};
void MouseRptParser::OnLeftButtonUp    (MOUSEINFO *mi)
{
  mouse_left = 0;
};
void MouseRptParser::OnLeftButtonDown    (MOUSEINFO *mi)
{
  mouse_left = 1;
};
void MouseRptParser::OnRightButtonUp    (MOUSEINFO *mi)
{
  mouse_right = 0;
};
void MouseRptParser::OnRightButtonDown    (MOUSEINFO *mi)
{
  mouse_right = 1;
};
void MouseRptParser::OnMiddleButtonUp    (MOUSEINFO *mi)
{
  //Middle mouse unused
};
void MouseRptParser::OnMiddleButtonDown    (MOUSEINFO *mi)
{
  //Middle mouse unused
};

class KbdRptParser : public KeyboardReportParser
{
  protected:
    void OnControlKeysChanged(uint8_t before, uint8_t after);
    void OnKeyDown    (uint8_t mod, uint8_t key);
    void OnKeyUp    (uint8_t mod, uint8_t key);
    void OnKeyPressed(uint8_t key);
};

void KbdRptParser::OnKeyDown(uint8_t mod, uint8_t key)
{
  if (leftGUI){
    //if the GUI key is down, check the list of 'alternative keys'
      for (int i = 0; i<sizeof(altKeysUSB); i++)
        if (key ==  altKeysUSB[i]){ //found in list, so send keycode and break
          Serial.write (altKeyCodes[i] & ~0x80);
          break;
        }
  }
  else if (key < sizeof(keymapping))
    Serial.write(keymapping[key] & ~0x80);
}

void KbdRptParser::OnControlKeysChanged(uint8_t before, uint8_t after) {

  MODIFIERKEYS beforeMod;
  *((uint8_t*)&beforeMod) = before;

  MODIFIERKEYS afterMod;
  *((uint8_t*)&afterMod) = after;

  if (beforeMod.bmLeftCtrl != afterMod.bmLeftCtrl) {
    if (!afterMod.bmLeftCtrl)
        Serial.write(CTRL_SCAN | 0x80);
    else
        Serial.write(CTRL_SCAN & ~0x80);

  }
  if (beforeMod.bmLeftShift != afterMod.bmLeftShift) {
    if (!afterMod.bmLeftShift)
        Serial.write(SHIFT_SCAN | 0x80);
    else
        Serial.write(SHIFT_SCAN & ~0x80);
  }
  if (beforeMod.bmLeftAlt != afterMod.bmLeftAlt) {
    if (!afterMod.bmLeftAlt)
        Serial.write(HIRA_SCAN | 0x80);
    else
        Serial.write(HIRA_SCAN & ~0x80);
  }
  if (beforeMod.bmLeftGUI != afterMod.bmLeftGUI) {
    leftGUI = afterMod.bmLeftGUI;
  }

  if (beforeMod.bmRightCtrl != afterMod.bmRightCtrl) {
    if (!afterMod.bmRightCtrl)
        Serial.write(OPT2_SCAN | 0x80);
    else
        Serial.write(OPT2_SCAN & ~0x80);
  }
  if (beforeMod.bmRightShift != afterMod.bmRightShift) {
    if (!afterMod.bmRightShift)
        Serial.write(SHIFT_SCAN | 0x80);
    else
        Serial.write(SHIFT_SCAN & ~0x80);
  }
  if (beforeMod.bmRightAlt != afterMod.bmRightAlt) {
    if (!afterMod.bmRightAlt)
        Serial.write(WIDTH_SCAN | 0x80);
    else
        Serial.write(WIDTH_SCAN & ~0x80);
  }
  if (beforeMod.bmRightGUI != afterMod.bmRightGUI) {
    //right Windows/GUI key unused, since my keyboard doesn't have one
  }

}

void KbdRptParser::OnKeyUp(uint8_t mod, uint8_t key)
{
  if (leftGUI){
    //if the GUI key is down, check the list of 'alternative keys'
      for (int i = 0; i<sizeof(altKeysUSB); i++)
        if (key ==  altKeysUSB[i]){ //found in list, so send keycode and break
          Serial.write (altKeyCodes[i] | 0x80);
          break;
        }
  }
  else if (key < sizeof(keymapping))
    Serial.write(keymapping[key] | 0x80);
}

void KbdRptParser::OnKeyPressed(uint8_t key)
{
  //This callback unused
};

USB        Usb;
USBHub    Hub(&Usb);

HIDBoot<USB_HID_PROTOCOL_KEYBOARD | USB_HID_PROTOCOL_MOUSE>  HidComposite(&Usb);
HIDBoot<USB_HID_PROTOCOL_KEYBOARD>                            HidKeyboard(&Usb);
HIDBoot<USB_HID_PROTOCOL_MOUSE>                              HidMouse(&Usb);

KbdRptParser KbdPrs;
MouseRptParser MousePrs;

void setup()
{
  Serial.begin(2400);
  #if !defined(__MIPSEL__)
    while (!Serial); // Wait for serial port to connect - used on Leonardo, Teensy and other boards with built-in USB CDC serial connection
  #endif
  x68kSerMouse.begin(4800);
 
  if (Usb.Init() == -1)
    //Serial.println("OSC did not start.");

  delay( 200 );

  HidComposite.SetReportParser(0, &KbdPrs);
  HidComposite.SetReportParser(1, &MousePrs);
  HidKeyboard.SetReportParser(0, &KbdPrs);
  HidMouse.SetReportParser(0, &MousePrs);
}

void loop()
{
  Usb.Task(); //Poll USB
 
  while (Serial.available() > 0) //Read from Keyboard serial port - to determine whether to poll mouse or not
  {
    last_rxbyte = rxbyte;
    rxbyte = Serial.read();
    if (rxbyte == 0x40 && last_rxbyte == 0x41) //MSCRTL Toggle H->L
    {
      uint8_t mouse_packet[3];
      uint8_t x_ovp, x_ovn, y_ovp, y_ovn = 0;

      //detect overflow conditions
      if (mouse_dx > 127)
        x_ovp = 1;
      if (mouse_dx < -128)
        x_ovn = 1;
      if (mouse_dy > 127)
        y_ovp = 1;
      if (mouse_dy < -128)
        y_ovp = 1;

      mouse_packet[0] = (y_ovn << 7) | (y_ovp << 6) | (x_ovn << 5) | (x_ovp << 4) | (mouse_right << 1) | mouse_left;
      mouse_packet[1] = mouse_dx;
      mouse_packet[2] = mouse_dy;
      mouse_dx = 0;
      mouse_dy = 0;
      for (int i=0; i < 3; i++)
        x68kSerMouse.write(mouse_packet[i]);
    }
  }
}


ajacocks

Do you have any pictures of the other side, showing how you soldered the USB host board?

Thanks!
- Alex

HIggy

Quote from: ajacocks on October 12, 2022, 01:54:22 AMDo you have any pictures of the other side, showing how you soldered the USB host board?

Thanks!
- Alex

Hi,

I am looking at this project now (I need  to post a Hi and my X68000 restoration!) There is some more info on his post about the FM Towns adaptor on this forum and extra info here:

https://geekhack.org/index.php?topic=80421.0

I have all the bits and programmed my Pro Mini.
Thus sounds a great device if I can get it to work.

HIggy

Quick update, I've got the adaptor working. I've not tried the Mouse feature yet.

I have taken photos of the wiring so I can do a guide.

It is all messy at the moment, I need to find or 3d print a project box.

HIggy

Here are some pics.
H.Pin = Header pin.
Mod H.Pin = Header pin where I have removed the plastic part so I can solder a wire to the lower section.

USB Host_1.jpg

The picture above is is what you do before you place the Pro Mini on top.

USB Host_2.jpg

USB Host_3.jpg 

I still can't get mouse to work. The Mouse takes the USB signals, they must work because the Keyboard works fine. Maybe pin '5' the output to the X68000 is not pin '5' on my Pro Mini? I can't see in the .ino code where the '5' is defined to check.
I could use a logic probe to see if anything is being sent by pin 5, but I've put it in a case now and it is difficult to attach a probe.
Hopefully @Zuofu will reply.

HIggy

Another update.

I have got the Mouse to work. The issue was the LED I added to reduce the signal voltage.
I've now used a Level Shifter I had in the drawer to correctly change the 5v logic TXD signal from the X68000 to the 3.3V Pro Mini.

aotta

Quote from: HIggy on February 27, 2023, 04:55:24 AMAnother update.

I have got the Mouse to work. The issue was the LED I added to reduce the signal voltage.
I've now used a Level Shifter I had in the drawer to correctly change the 5v logic TXD signal from the X68000 to the 3.3V Pro Mini.
I build the adapter following your picture and hints, and using the code posted by Zuofu, but only some keys works (and only with my "rapoo" wireless keyboard, i tested other two wireless keyboard but i got less or no responds), and seems like the buffer isn't read quick enough (i mean, i got the previous char i typed when i press a second buttons, and so on).
Did you made some adjustments to the code or you use it as is? Thank you for any suggestion!

EDIT: solved, no issue on code, changed cable and, most important, i understood the "block/scrl" usage! sorry for my incompetence on x68k matters! ;)

Sebastian77

Quote from: HIggy on February 12, 2023, 12:09:12 AMHere are some pics.
H.Pin = Header pin.
Mod H.Pin = Header pin where I have removed the plastic part so I can solder a wire to the lower section.

USB Host_1.jpg

The picture above is is what you do before you place the Pro Mini on top.

USB Host_2.jpg

USB Host_3.jpg 

I still can't get mouse to work. The Mouse takes the USB signals, they must work because the Keyboard works fine. Maybe pin '5' the output to the X68000 is not pin '5' on my Pro Mini? I can't see in the .ino code where the '5' is defined to check.
I could use a logic probe to see if anything is being sent by pin 5, but I've put it in a case now and it is difficult to attach a probe.
Hopefully @Zuofu will reply.

Hi HIggy, if I use a 5v Arduino instead of 3.3v do I have to do that "reset to 3.3v" wire?

Thanks!

HIggy

@Sebastian77 3.3v and 5v Arduino's run at different speeds. I kept to using a 3.3V model to ensure everything worked.

So sorry I can't really help, but if you   are trying it I guess it would be Reset to a 5V pin as you want all power the same voltage.

I would think the Arduino program would need modifying as 5v models are easy to get hold of so I would have thought they  would have used a 5v to start with.

Thanks


incrediblehark

Has anyone built one of these and used with the zuiki x68000 keyboard and mouse? Would be a pretty good replacement if so