Unusual Apple II

So I just picked up an Apple II Europlus off Instagram. The seller said it was brand new and never used, and honestly, the case is still in great shape. The motherboard’s way cleaner than my other Apple II, and it actually runs fine—the ROM’s working perfectly too. The only problem is the keyboard encoder, which I’ve heard is a pretty common issue on these Europlus machines.

The sticker itself said that it was Made In Ireland. But the motherboard and the PSU seems fishy. The Apple label on the motherboard barely visible, different compared to my previous collection that the label still sharp. I don’t know if it because of storage issues.

Faded Apple Computer Label

Power Supply

The power supply also rather suspicious, different with my other Apple II PSU. It was made in Hong Kong but it was “erased”.

The component inside the power supply itself feels cheap. Totally different with the Apple II power supply that you can find on the internet.

I don’t know the reason but I guess this one of Apple action to reduce the production price of this computer.

The Issue & Solution

This is the most common issue with the Europlus series which use the RF keyboard version. The issue is with the keyboard encoder and also with the key itself.

When you find this kind of keyboard, please do not disassemble, or you will find a hard-time to assembled it again.

The keyboard encoder also the most fail component in this series, also it is hard to find the replacement. It’s better to build a replacement for the encoder instead. You can find many Apple II keyboard encoder replacement sold on ebay or retro-shops. But I prefer to build it myself using microcontroller.

I found this open-source project to replace the Apple II keyboard encoder from Mel’s Vintage website. It use the ATMega328 which popularly used for Arduino Uno, so it easy to find the chip. The full schematic and code you can find on his website, but I will post the code that I used here (for my backup).

/*
  Apple II Datanetics Keyboard controller AY-5-3600 replacement
  By frank mels
  (c) 2021
 
  V1.1 update with improved key roll over by means of faster key scanning
  DEBOUNCE change from 8 to 1ms
  DATAHOLD change from 10 to 5ms
 
  Stand Alone ATmega328PU running from internal 8MHz oscillator
 
  J1 = DIP40 connection to Apple II keyboard PCB U5
  Keyboard column/line driver pins:
  J1  pin output Sig     pin input  Sig 
      14  PB0    XB0     25  PC2    Y2
      15  PB1    XB1     26  PC3    Y3
      16  PB2    XB2     27  PC4    Y4
      17  PB3    XB3     28  PC5    Y5
      18  PB4    XB4      2  PD0    Y6 
      19  PB5    XB5      3  PD1    Y7
       9  PB6    XB6      4  PD2    Y8
      10  PB7    Strobe   5  PD3    Y9
      23  PC0    Y0       6  PD4    NC 
      24  PC1    Y1      11  PD5    ANYKEYDOWN
                         12  PD6    CTRL  
                         13  PD7    Shift 
 
 
  PORTD maps to Arduino digital input pins pins Y6 to Y9 + CTRL + Shift + ANYKEYDOWN
  DDRD - The Port D Data Direction Register - read/write
  PORTD - The Port D Data Register - read/write
  PIND - The Port D Input Pins Register - read only
   
  PORTB maps to Arduino digital output pins XB1 to XB6 + strobe
  DDRB - The Port B Data Direction Register - read/write
  PORTB - The Port B Data Register - read/write
  PINB - The Port B Input Pins Register - read only
   
  PORTC maps to digital output pins digital input pins Y0 to Y5
  DDRC - The Port C Data Direction Register - read/write
  PORTC - The Port C Data Register - read/write
  PINC - The Port C Input Pins Register - read only
   
*/
 
#define DEBOUNCE  1      // debounce timing in ms
#define DATAREADY 10     // data ready timing in us
#define DATAHOLD  5      // data ready timing in ms
#define STROBE    50     // strobe timing in us
#define NCOLUMNS  9      // number of columns in scan
 
// functions
void vSendKey(unsigned char ucKey);
void vResetColumns(void);
void vWaitKey(unsigned char ucWaitType);
 
// Constants
//    7bit Ascii + bit 7 = 1         PC0   PC1   PC2   PC3   PC4   PC5   PD0   PD1   PD2   PD3
//    Ascii = Y + X *10 + Z * 50     Y0    Y1    Y2    Y3    Y4    Y5    Y6    Y7    Y8    Y9    X: Column, Y: Row, Z: 0= no shift; 1= shift; 2= CTRL; 3= Shift + CTRL
// constants                          PB2   PB3   PB4   PB5   PC0   PC1   PC2   PC3   PC4   PC5
const unsigned char cucAscii[200] = {0xad, 0xba, 0xb0, 0xb9, 0xb8, 0xb7, 0xb6, 0xb5, 0xb4, 0xb3, //PC0  '-'   , ':' , '0', '9', '8', '7', '6', '5', '4', '3'
                                     0xd0, 0xcf, 0xc9, 0xd5, 0xd9, 0xd4, 0xd2, 0xc5, 0xd7, 0xd1, //PC1  'P'   , 'O' , 'I', 'U', 'Y', 'T', 'R', 'E', 'W', 'Q'
                                     0x95, 0x88, 0xbb, 0xcc, 0xcb, 0xca, 0xc8, 0xc7, 0xc6, 0xc4, //PC2  right , left, ';', 'L', 'K', 'J', 'H', 'G', 'F', 'D'
                                     0xaf, 0xae, 0xac, 0xcd, 0xce, 0xc2, 0xd6, 0xc3, 0xd8, 0xda, //PC3  '/'   , '.' , ',', 'M', 'N', 'B', 'V', 'C', 'X', 'Z'
                                     0x8d, 0x00, 0x00, 0x00, 0xa0, 0xc1, 0x9b, 0xb1, 0xb2, 0xd3, //PC3  return,  '' ,  '',  '', ' ', 'A', esc, '1', '2', 'S'
                                   //--------------------- Ctrl -------------------------------
                                     0xad, 0xba, 0xb0, 0xb9, 0xb8, 0xb7, 0xb6, 0xb5, 0xb4, 0xb3, //PC0  '-'   , ':' , '0', '9', '8', '7', '6', '5', '4', '3'
                                     0x90, 0x8f, 0x89, 0x95, 0x99, 0x94, 0x92, 0x85, 0x97, 0x91, //PC1  'P'   , 'O' , 'I', 'U', 'Y', 'T', 'R', 'E', 'W', 'Q'
                                     0x95, 0x88, 0xbb, 0x8c, 0x8b, 0x8a, 0x88, 0x87, 0x86, 0x84, //PC2  right , left, ';', 'L', 'K', 'J', 'H', 'G', 'F', 'D'
                                     0xaf, 0xae, 0xac, 0xcd, 0x8e, 0x82, 0x96, 0x83, 0x98, 0x9a, //PC3  '/'   , '.' , ',', 'M', 'N', 'B', 'V', 'C', 'X', 'Z'
                                     0x8d, 0x00, 0x00, 0x00, 0xa0, 0x81, 0x9b, 0xb1, 0xb2, 0x93, //PC4  return,  '' ,  '',  '', ' ', 'A', esc, '!', '"', 'S'
                                   //--------------------- Shift ------------------------------
                                     0xbd, 0xaa, 0xb0, 0xa9, 0xa8, 0xa7, 0xa6, 0xa5, 0xa4, 0xa3, //PC0  '='   , '*' , '0', ')', '(', ''', '&', '%', '$', '#'
                                     0xc0, 0xcf, 0xc9, 0xd5, 0xd9, 0xd4, 0xd2, 0xc5, 0xd7, 0xd1, //PC1  '@'   , 'O' , 'I', 'U', 'Y', 'T', 'R', 'E', 'W', 'Q'
                                     0x95, 0x88, 0xab, 0xcc, 0xcb, 0xca, 0xc8, 0xc7, 0xc6, 0xc4, //PC2  right , left, '+', 'L', 'K', 'J', 'H', 'G', 'F', 'D'
                                     0xbf, 0xbe, 0xbc, 0xdd, 0xde, 0xc2, 0xd6, 0xc3, 0xd8, 0xda, //PC3  '?'   , '>' , '<', ']', '^', 'B', 'V', 'C', 'X', 'Z'
                                     0x8d, 0x00, 0x00, 0x00, 0xa0, 0xc1, 0x9b, 0xa1, 0xa2, 0xd3, //PC4  return,  '' ,  '',  '', ' ', 'A', esc, '!', '"', 'S'
                                   //--------------------- Ctrl + shift -----------------------
                                     0xad, 0xba, 0xb0, 0xb9, 0xb8, 0xb7, 0xb6, 0xb5, 0xb4, 0xb3, //PC0  '-'   , ':' , '0', '9', '8', '7', '6', '5', '4', '3'
                                     0x80, 0x8f, 0x89, 0x95, 0x99, 0x94, 0x92, 0x85, 0x97, 0x91, //PC1  'P'   , 'O' , 'I', 'U', 'Y', 'T', 'R', 'E', 'W', 'Q'
                                     0x95, 0x88, 0xbb, 0x8c, 0x8b, 0x8a, 0x88, 0x87, 0x86, 0x84, //PC2  right , left, ';', 'L', 'K', 'J', 'H', 'G', 'F', 'D'
                                     0xaf, 0xae, 0xac, 0x9d, 0x9e, 0x82, 0x96, 0x83, 0x98, 0x9a, //PC3  '/'   , '.' , ',', 'M', 'N', 'B', 'V', 'C', 'X', 'Z'                                     
                                     0x8d, 0x00, 0x00, 0x00, 0xa0, 0x81, 0x9b, 0xb1, 0xb2, 0x93};//PC4  return,  '' ,  '',  '', ' ', 'A', esc, '!', '"', 'S'
 
const unsigned char cucNRows = 10;
const unsigned char cucNCulomns = 5;
const unsigned char cucNStates = 4;
                                    
// Variables
unsigned char ucRow = 0;
unsigned char ucColumn = 0;
unsigned char ucState = 0;
unsigned char ucKey = 0;
unsigned char ucRep = 0;
unsigned char ucPinC = 0;
unsigned char ucDdrC = 0;
unsigned char ucPinD = 0;
unsigned char ucDdrD = 0;
long lRepStart = 500;
long lRepTime = 100;
 
unsigned char ucPORTB[9] =    {B01111110, B01111101, B01111011, B01110111, B01101111, B01011111, B00111111, B01111111, B01111111};
unsigned char ucPORTBDIR[9] = {B10000001, B10000010, B10000100, B10001000, B10010000, B10100000, B11000000, B10000000, B10000000};
 
// init
void setup() {
  // Serial.begin(9600);
   
  DDRB = DDRB | B11111111;                // all pins to output
  DDRC = DDRC & B11000000;                // pins 0-5 = input, bits 6-7 untouched
  DDRD = B00000000;                       // all bits = input 
 
  PORTB = B00000000;                      // all outputs to LOW
  PORTC = PORTC | B00111111;              // invoke pullups on input pins 0-5 , bits 6-7 untouched
  PORTD = B11111111;                      // invoke pullups for all input bits 0-7
 
}
 
//main
void loop() {
unsigned int uiKey = 0;
 
  vResetColumns();                            // reset columns
   
  vWaitKey(0);                                // wait for key press
  //PORTD |= B00100000;                       // Key Press = Any Key Down high
 
  ucState = (PIND & B11000000) >> 6;          // Get Shift/CTRL State
  
  // Scan keyboard
  for(ucColumn = 0;ucColumn < cucNCulomns;ucColumn++) {
     // set column drivers for scan steps
      DDRB = ucPORTBDIR[ucColumn];
      PORTB = ucPORTB[ucColumn];
      delay(DEBOUNCE);
     
      uiKey = (unsigned int)(~PINC & B00111111) | (unsigned int)((~PIND & B00001111) << 6);        // setup matrix input vector
 
      // detect key press row
      ucRow = 0;
      switch(uiKey) {
        case 1 :   ucRow = 10;
                   break;
        case 2:    ucRow = 9;
                   break;    
        case 4:    ucRow = 8;
                   break;
        case 8:    ucRow = 7;
                   break;
        case 16:   ucRow = 6;
                   break; 
        case 32:   ucRow = 5;
                   break;
        case 64:   ucRow = 4;
                   break; 
        case 128:  ucRow = 3;
                   break;
        case 256:  ucRow = 2;
                   break;
        case 512:  ucRow = 1;
                   break;               
    }
    if(ucRow>0) break;
  }
 
  if(ucRow>0) {                       // if row is valid send Ascii code to A2
    ucRow--;
    ucKey = cucAscii[ucRow + (ucColumn * 10) + (ucState * 50)];       // Calculated Ascii code for pressed key
    
    vSendKey(ucKey);                  // send key !
  }
 
  vResetColumns();                     // reset columns for next scan
  vWaitKey(1);                         // wait for key release
     
}
 
void vSendKey(unsigned char ucSendKey) {
 
    DDRB = B11111111;
    PORTB = ~ucSendKey & B01111111;    // prepare keyboard output with strobe bit = LOW
    delayMicroseconds(DATAREADY);      // wait until output buffers are stable 
     
    PORTB = ~ucSendKey | B10000000;    // send keyboard value with strobe bit = HIGH
    delayMicroseconds(STROBE);         // wait for Apple to latch key Ascii code
     
    PORTB = ~ucSendKey & B01111111;    // put strobe to LOW
    delay(DATAHOLD);                   // keep buffer until A2 has processed the key input 
    if(ucSendKey < 0xa0) delay(15);
     
}
 
void vResetColumns(void) {
  PORTB = B00000000;                      
  DDRB = B11111111;
  delay(DEBOUNCE);
}
 
void vWaitKey(unsigned char ucWaitType) {
  long lMillisStart = 0;
   
  switch(ucWaitType) {
    case 0: ucKey = 0;
            while(ucKey == 0) {
              ucKey = (~PINC & B00111111) | (~PIND & B00001111);        // check for key press, shift and CTRL not included
            }
            break;
    case 1: ucKey = 1;
            lMillisStart = millis();
            while(ucKey>0) {
              ucKey = (~PINC & B00111111) | (~PIND & B00001111);        // check for key press, shift and CTRL not included
              if(ucKey == 0) ucRep = 0;
              if((lRepStart < (millis()-lMillisStart)) && (ucRep == 0) && (ucKey > 0)) {
                ucRep = 1;
                ucKey = 0;
              }
              if((lRepTime < (millis()-lMillisStart)) && (ucRep == 1)) ucKey = 0;
            }
            break;
  }
}

Troubleshooting

At first, the encoder didn’t work. I thought it was a hardware issue with my Apple II, so I replaced all the TTL chips on the keyboard PCB. That didn’t fix the problem. I had scavenged the chips from an unused Arduino Uno board, which normally uses a 16 MHz crystal as an external clock. However, I hadn’t installed the crystal on this board. I needed to change the fuse bits to enable the internal clock instead of the external one — and that solved the issue!

Leave a Reply

Your email address will not be published. Required fields are marked *