Saturday, September 20, 2014

Make Your Own Coleco ADAM Audio/Video Cable

TV RF Computer Slide SwitchThe Coleco ADAM “Family Computer System” came with an RF switch box used to connect the ADAM to a TV set to channel 3 or 4 (depending on the position of the channel switch on the back of the memory console). This worked, but the image quality using an RF cable can often be fuzzy and the sound can be scratchy.

Near the place where one would connect the TV cable, there is another RCA connector labeled MONITOR that is a Composite Video out. This provides a much better video image, but does not include the audio output.

ADAM Console Back

There is a third connector on the back of the ADAM labeled AUX VIDEO that provides both a composite video signal and an audio signal. Unfortunately Coleco used a 7-pin circular DIN connector, which is hard to find.

This article describes how to create an Audio/Video cable that will allow a Coleco ADAM to be connected to a TV via the TV’s composite video and audio RCA connectors, rather than the antenna input.

What You Need

Prepare the RCA Connectors

Stripped AV CableFor my cable I used an old camera A/V cable I was not using anymore, but any cable with two male RCA connectors will work. If you do not have a cable like this you can spare, you can create your own using two RCA connectors. Cut the A/V cable at the end opposite the RCA connectors and strip the wires.

 

 

Connect the Cable to the DIN Connector

The following diagram describes the pin layout of the 7-pin DIN connector as looking at the back of the Coleco ADAM:

AV Connector Back of ADAMADAM Connector

Before connecting the ends of the A/V cable to the DIN connector, be sure to slide the outer cover of the DIN connector over the A/V cable. If you forget to do this step, you will hate yourself.

AV Cable with DIN Cover

Connect the center wire of the audio cable to pin 1 of the DIN connector. Connect the outer or ground wire of the audio cable to pin 2. Connect the center wire of the composite video cable to pin 3 of the DIN connector. Connect the outer or ground wire of the composite video cable to pin 4 of the DIN connector.

Coleco ADAM AV Cable

Slide the cover over the DIN connector.

Plug the DIN connector into the AUX VIDEO port on the back of the Coleco ADAM memory console and plug the audio and composite video RCA connectors into the TV.

 

Enjoy the sharp image and clear sound of your new connection.

Coleco ADAM

Tuesday, September 09, 2014

Arduino: Classic Joystick to USB Adaptor

If you grew up in the early 1980’s and were into video games, you probably had an Atari 2600, ColecoVision, or similar game console. The controllers or joysticks for each of these systems had a distinct feel that is different from today’s game consoles or PC game controllers. If you find yourself longing to plug your old ColecoVision or Atari 2600 joystick into your modern PC, but are not sure how to go about it, this article is for you. This article describes how to use an Arduino Leonardo (or similar) card to make your classic console joystick look like a USB keyboard.

I designed this Classic Joystick to USB Keyboard Adapter with the ADAMEm ColecoVision and Coleco ADAM emulator in mind (http://www.komkon.org/~dekogel/adamem.html, which can be run in Microsoft Windows using Virtual ADAM http://www.sacnews.net/adamcomputer/downloads/). However it will work with most emulators and any game or program that can use the keyboard as input. I have also tested it with the following emulators:

Default Keyboard Mappings

By default this classic console joystick to USB keyboard adapter uses the following mappings (these are the default mappings used by the ADAMEm emulator):

Joystick Action Keyboard Mapping
Up Up Arrow Key
Down Down Arrow Key
Left Left Arrow Key
Right Right Arrow Key
Left Fire Button Left Alt Key
Right Fire Button Left Ctrl Key
Purple Fire Button Left Shift Key
Blue Fire Button z
Keypad 0 0
Keypad 1 1
Keypad 2 2
Keypad 3 3
Keypad 4 4
Keypad 5 5
Keypad 6 6
Keypad 7 7
Keypad 8 8
Keypad 9 9
Keypad * -
Keypad # =

These mappings can be changed by altering the Arduino sketch file provided (see the Keyboard Keys section).

Atari 2600 Joysticks

Atari 2600 JoystickThe Atari 2600 joysticks are the simplest of the controllers supporter by this adaptor. They have a D-Subminiature Female 9 Position connector. The following table describes the function of each pin:

Pin Number Description
1 Up
2 Down
3 Left
4 Right
6 Fire
8 Ground

ColecoVision Controllers

ColecoVision Joystick
ADAM Joystick
ColecoVision Controller
ADAM Controller
ColecoVision Super Action Controller
ColecoVision Super Action Controller

The ColecoVision and ADAM joysticks are much more complicated. In addition to the four direction control stick, they have two fire buttons (or 4 in the case of the Super Action Controller) and a 12 button keypad. Like the Atari 2600 joystick, they have a D-Subminiature Female 9 Position connector, but the pin outs have two different modes. When pin 8 is grounded, pins 1-4 are the four directions and pin 6 is the left fire button. When pin 5 is grounded, pins 1-4 are a keypad scan code and pin 6 is the right fire button. The table below lists the pin outs for the ColecoVision and ADAM controllers.

Pin Number Pin 8 Grounded Pin 5 Grounded
1 Up Bit 0
2 Down Bit 2
3 Left Bit 3
4 Right Bit 1
5 Keypad Mode Keypad Mode
6 Left Fire Right Fire
7 Spinner Spinner
8 Control Stick Mode Control Stick Mode
9 Spinner Spinner

The keypad scan codes are listed below:

Keypad Value
Bit 3 / Pin 3
Bit 2 / Pin 2
Bit 1 / Pin 4
Bit 0 / Pin 1
Decimal Value
Hex Value
1
0
0
1
0
2
2
2
1
0
0
0
8
8
3
0
0
1
1
3
3
4
1
1
0
1
13
D
5
1
1
0
0
12
C
6
0
0
0
1
1
1
7
1
0
1
0
10
A
8
1
1
1
0
14
E
9
0
1
0
0
4
4
0
0
1
0
1
5
5
*
0
1
1
0
6
6
#
1
0
0
1
9
9
Purple Fire Button
0
1
1
1
7
7
Blue Fire Button
1
0
1
1
11
B

The solution I have developed will support Atari 2600 joysticks, ColecoVision and ADAM controllers, and most of the ColecoVision Super Action Controllers. This solution does not currently support the Spinner located below the keypad on the Super Action Controllers.

What You Need

Hardware

The pins of the D-Sub Male 9 Position connector should be connected to the pins of the Arduino Leonardo or Arduino Micro as shown in the table below:

Arduino Pin Male D-Sub Pin Description
2 6 Fire Button
3 1 Up
4 2 Down
5 3 Left
6 4 Right
7 5 Keypad Mode
8 8 Joystick Mode
9 7 Spinner (not used)
10 9 Spinner (not used)

Classic Joystick to USB Adaptor

Software

The Arduino sketch file listed below should be compiled and uploaded into the Arduino Leonardo or Arduino Micro.


// ColecoVision / ADAM Joystick to PC Keyboard Converter
// for the Arduino Leonardo
// 2014-08-24
//----------------------------------------------------------------------------------

// Joystick Pins
const byte gcFirePin = 2;
const byte gcUpPin = 3;
const byte gcDownPin = 4;
const byte gcLeftPin = 5;
const byte gcRightPin = 6;
const byte gcModeAPin = 7;
const byte gcModeBPin = 8;
const byte gcFireMonitorPin = 13;
const byte gcBit0Pin = 3;
const byte gcBit2Pin = 4;
const byte gcBit3Pin = 5;
const byte gcBit1Pin = 6;

// Keyboard Keys
const char gcUpKey = KEY_UP_ARROW;
const char gcDownKey = KEY_DOWN_ARROW;
const char gcLeftKey = KEY_LEFT_ARROW;
const char gcRightKey = KEY_RIGHT_ARROW;
const char gcLeftFireKey = KEY_LEFT_ALT;
const char gcRightFireKey = KEY_LEFT_CTRL;
const char gcPurpleButtonKey = KEY_LEFT_SHIFT;
const char gcBlueButtonKey = 'z';
const char gcAsteriskKey = '-';
const char gcPoundKey = '=';

// Current Frame Joystick Status
byte gLeftFireButton = 0;
byte gRightFireButton = 0;
byte gUp = 0;
byte gDown = 0;
byte gLeft = 0;
byte gRight = 0;
char gNumPadValue = ' ';
byte gPurpleButton = 0;
byte gBlueButton = 0;
byte gBit0 = 0;
byte gBit1 = 0;
byte gBit2 = 0;
byte gBit3 = 0;

// Last Frame Joystick Status
byte gLastLeftFireButton = 0;
byte gLastRightFireButton = 0;
byte gLastUp = 0;
byte gLastDown = 0;
byte gLastLeft = 0;
byte gLastRight = 0;
char gLastNumPadValue = ' ';
byte gLastPurpleButton = 0;
byte gLastBlueButton = 0;

// Line Read Variables
const unsigned int gcThreashold = 4;
unsigned int gLeftFireCount = 0;
unsigned int gRightFireCount = 0;
unsigned int gUpCount = 0;
unsigned int gDownCount = 0;
unsigned int gLeftCount = 0;
unsigned int gRightCount = 0;
unsigned int gBit0Count = 0;
unsigned int gBit1Count = 0;
unsigned int gBit2Count = 0;
unsigned int gBit3Count = 0;

// Frame Variables
const int gcFrameLength = 10;
unsigned int gLoopsPerFrame = 0;
unsigned long gLastFrameStart = 0;

// Shows the status of the joystick.
void ShowJoystickStatus()
{
  // Debug Information
//  Serial.println(gLoopsPerFrame);

  digitalWrite(gcFireMonitorPin, gLeftFireButton | gRightFireButton);
}

void SendKeyboardStateToPc()
{
  if ((gLastNumPadValue != ' ') && (gLastNumPadValue != gNumPadValue))
  {
    Keyboard.release(gLastNumPadValue);
  }
  if ((gNumPadValue != ' ') && (gLastNumPadValue != gNumPadValue))
  {
    Keyboard.press(gNumPadValue);
  }
  
  SendLineStateToPc(gLastLeftFireButton, gLeftFireButton, gcLeftFireKey);
  SendLineStateToPc(gLastRightFireButton, gRightFireButton, gcRightFireKey);
  SendLineStateToPc(gLastUp, gUp, gcUpKey);
  SendLineStateToPc(gLastDown, gDown, gcDownKey);
  SendLineStateToPc(gLastLeft, gLeft, gcLeftKey);
  SendLineStateToPc(gLastRight, gRight, gcRightKey);
  SendLineStateToPc(gLastPurpleButton, gPurpleButton, gcPurpleButtonKey);
  SendLineStateToPc(gLastBlueButton, gBlueButton, gcBlueButtonKey);
}

void SendLineStateToPc(byte lastState, byte currentState, char keyUsedForLine)
{
  if ((lastState == 1) && (currentState == 0))
  {
    Keyboard.release(keyUsedForLine);
  }
  if ((lastState == 0) && (currentState == 1))
  {
    Keyboard.press(keyUsedForLine);
  }
}

unsigned int CheckJoystickLine(byte pin)
{
  return (digitalRead(pin) == LOW);
}

void CheckJoystickLines()
{
  const int cLineDelay = 50;
  
  // Put Joystick in Direction Mode
  digitalWrite(gcModeAPin, HIGH);
  digitalWrite(gcModeBPin, LOW);
  delayMicroseconds(cLineDelay);
  gLeftFireCount += CheckJoystickLine(gcFirePin);
  gUpCount += CheckJoystickLine(gcUpPin);
  gDownCount += CheckJoystickLine(gcDownPin);
  gLeftCount += CheckJoystickLine(gcLeftPin);
  gRightCount += CheckJoystickLine(gcRightPin);

  // Put Joystick in Keypad Mode
  digitalWrite(gcModeAPin, LOW);
  digitalWrite(gcModeBPin, HIGH);
  delayMicroseconds(cLineDelay);
  gRightFireCount += CheckJoystickLine(gcFirePin);
  gBit0Count += CheckJoystickLine(gcBit0Pin);
  gBit1Count += CheckJoystickLine(gcBit1Pin);
  gBit2Count += CheckJoystickLine(gcBit2Pin);
  gBit3Count += CheckJoystickLine(gcBit3Pin);
}

void ResetFrameVariables()
{
  // Copy Current Frame to Last Frame
  gLastNumPadValue = gNumPadValue;
  gLastLeftFireButton = gLeftFireButton;
  gLastRightFireButton = gRightFireButton;
  gLastUp = gUp;
  gLastDown = gDown;
  gLastLeft = gLeft;
  gLastRight = gRight;
  gLastPurpleButton = gPurpleButton;
  gLastBlueButton = gBlueButton;
  
  // Reset Frame Loop Counter
  gLoopsPerFrame = 0;
  
  // Reset Joysick State
  gLeftFireCount = 0;
  gLeftFireButton = 0;
  gRightFireCount = 0;
  gRightFireButton = 0;
  gUpCount = 0;
  gUp = 0;
  gDownCount = 0;
  gDown = 0;
  gLeftCount = 0;
  gLeft = 0;
  gRightCount = 0;
  gRight = 0;
  gBit0Count = 0;
  gBit0 = 0;
  gBit1Count = 0;
  gBit1 = 0;
  gBit2Count = 0;
  gBit2 = 0;
  gBit3Count = 0;
  gBit3 = 0;
  gNumPadValue = ' ';
  gPurpleButton = 0;
  gBlueButton = 0;
}

byte DetermineJoystickLineValue(unsigned int pressCount)
{
    return (pressCount >= gcThreashold);
}

void DetermineJoystickValues()
{
  const char cKeypadValueLookup[16] = {
    ' ', '6', '1', '3', '9', '0', gcAsteriskKey, ' ', 
    '2', gcPoundKey, '7', ' ', '5', '4', '8', ' '};
    
  gLeftFireButton = DetermineJoystickLineValue(gLeftFireCount);
  gRightFireButton = DetermineJoystickLineValue(gRightFireCount);

  gUp = DetermineJoystickLineValue(gUpCount);
  gDown = DetermineJoystickLineValue(gDownCount);
  gLeft = DetermineJoystickLineValue(gLeftCount);
  gRight = DetermineJoystickLineValue(gRightCount);
  
  gBit0 = DetermineJoystickLineValue(gBit0Count);
  gBit1 = DetermineJoystickLineValue(gBit1Count);
  gBit2 = DetermineJoystickLineValue(gBit2Count);
  gBit3 = DetermineJoystickLineValue(gBit3Count);
  
  int keypadCode = (gBit3 << 3) + (gBit2 << 2) + (gBit1 << 1) + gBit0;
  
  gNumPadValue = cKeypadValueLookup[keypadCode];
  
  // Check for SuperAction Controller Buttons.
  if (keypadCode == 7)
  {
    gPurpleButton = 1;
  }
  if (keypadCode == 11)
  {
    gBlueButton = 1;
  }
}

void setup()
{
  // Setup Serial Monitor
//  Serial.begin(19200);
  
  // Setup Joystick Pins
  pinMode(gcFirePin, INPUT_PULLUP);
  pinMode(gcUpPin, INPUT_PULLUP);
  pinMode(gcDownPin, INPUT_PULLUP);
  pinMode(gcLeftPin, INPUT_PULLUP);
  pinMode(gcRightPin, INPUT_PULLUP);
  pinMode(gcModeAPin, OUTPUT);
  pinMode(gcModeBPin, OUTPUT);
  pinMode(gcFireMonitorPin, OUTPUT);
}

void loop()
{
  unsigned long currentTime;
  
  currentTime = millis();
  if (currentTime >= (gLastFrameStart + gcFrameLength))
  {
    // Do Joystick Value Commit Logic
    DetermineJoystickValues();
    
    // Send Values to Monitor
    ShowJoystickStatus();
    
    // Send Keyboad State to the PC
    SendKeyboardStateToPc();
    
    // Reset Frame Variables
    ResetFrameVariables();
  
    // Time to start next frame
    gLastFrameStart = currentTime;
  }
  else
  {
    // Check the value of the input lines and make note of them.
    gLoopsPerFrame++;
    CheckJoystickLines();
  }
}


Setup Function

The setup function is used to initialize the Arduino pins. Pins 2-6 are setup as input pins with pull-up resistors. Pins 7 and 8 are setup as output pins to toggle between Keypad Mode and Controller Mode. Pin 13 is setup as an output pin. On most Arduino boards pin 13 is connected to an LED. This LED is used to monitor the left and right fire buttons.

Loop Function

The main loop function is setup to report the value of the various joystick elements as either keydown or keyup events at a rate defined by the gcFrameLength constant. The main loop will read the state of all input pins in both the Keypad and Controller modes as many times as possible within a frame. If the number of times a pin was low exceeds the gcThreashold value, the pin is considered low for that frame. This is how each input is debounced.

Debugging

The Arduino COM port can be used for debugging by uncommenting out the call to Serial.begin in the setup function and adding debug output to the ShowJoystickStatus function.

Possible Future Enhancements

  • Add support for the ColecoVision Super Action Controller’s Spinner. This would map to the X-axis of the mouse.
  • Add support for ColecoVision Expansion Module #2 (the driving module).
  • Add support for the ColecoVision Roller Controller.
  • Add support for the Atari 2600 Paddle Controllers.
  • Create a version that presents the classic console controller as USB Game Controller instead of a USB Keyboard.