Sunday, 13 September 2015

A Fritzing Layout and Some LeoStick Keyboard Code

Thought I'd end this weekend with a little code and a Fritzing layout.

A Breadboard Layout

The Fritzing layout is obviously for a LeoStick, though there is nothing stopping the basic layout (or the code) from working on the Leonardo.

Fritzig Layout for ZX81 Keyboard and a LeoStick.

A Little Code to Make it Go

The Code below incorporates everything described in my previous post, Multiple Layers and Keyboard Mode Selection. I've tested keyboard in emulator mode on a LINUX box, also on a PI running Raspbian, both tests using the sz81 emulator, and the keyboard works to my immediate satisfaction. I'm also pretty happy with the keyboards functionality in normal PS2 mode.

That said, there are some issues with an other emulator I've tried, ZEsarUX, where not every key press is registered. I'm not sure why this is as yet, I'll keep working, hopefully some resolution will be come to hand.

 // **************************************************************************  
 // **** ZX81 USB Keyboard for Funtronics LeoStick (based on a Leonardo). ****  
 // **************************************************************************  
 // ** David Stephenson 2015-10-04  **  
 // **                              **  
 // ** Originally based on code by: **  
 // ** Dave Curran 2013-04-27       **  
 // ** Tony Smith 2014-02-15        **  
 // **********************************  
 enum KEYMODES {EMULATOR, STANDARD};  
 enum KEYSTATES {NORMAL, NORMAL_SHIFTED, FUNCTION, GRAPHICS, GRAPHICS_SHIFTED};  
 #define NUM_ROWS 8  
 #define NUM_COLS 5   
 #define SHIFT_COL 4  
 #define SHIFT_ROW 5  
 #define DEBOUNCE_VALUE 220  
 #define REPEAT_DELAY 660  
 // ***********************  
 // ** Class Definitions **  
 // ***********************  
 // Defines a single key and its 4 possible Major states.  
 class ZxKey {  
   private:  
     // Standard Keyboard for Normal PC Use  
     byte bNormal;      // Standard ZX keyboard only in lower case. EMULATOR KEYMODE uses only bNormal.  
     // Extra Modes for using keyboard as a normal(ish) PS2 / USB device.  
     byte bNormalShifted;  // Red Symbols & Words (replaced by Symbols)   
     byte bGraphics;      // Standard ZX keyboard but CTL characters. Number = F1 - F10 etc  
     byte bGraphicsShifted;  // Standard ZX keyboard but ALT characters. Number = F11 - F12, 5 = home, 6 PGUP etc   
     // Monitor Keys last press / activity.  
     int iDebounceCount;  
     int iDebounceCountHighest;  
   public:  
     ZxKey (byte, byte, byte, byte);  
     // Return Key Values for specific States.  
     byte getKeyNormal(){  
       return bNormal;  
     }  
     byte getKeyNormalShifted(){  
       return bNormalShifted;  
     }  
     byte getKeyGraphics(){  
       return bGraphics;  
     }  
     byte getKeyGraphicsShifted(){  
       return bGraphicsShifted;  
     }  
     int setDebounceCount(int DebounceCount){  
       // Set autorepeat value lower if DebounceCount == iDebounceCountHighest  
       // eg on first debounce set to REPEAT_DELAY on next repeat timing will be initial DEBOUNCE_VALUE / 2  
       if (iDebounceCountHighest == DebounceCount ){  
         iDebounceCount = DEBOUNCE_VALUE / 2;  
       } else {  
         iDebounceCount = DebounceCount;  
         iDebounceCountHighest = DebounceCount;  
       }  
       //iDebounceCount = DebounceCount;  
     }  
     int resetDebounceCount(){  
       iDebounceCount = DEBOUNCE_VALUE;  
       iDebounceCountHighest = 0;  
     }  
     // iDebounceCount counts down to 0 from DEBOUNCE_VALUE (or REPEAT_DELAY)  
     int iDecreaseDebounceCount(){  
       iDebounceCount--;  
     }  
     int getDebounceCount(){  
       return iDebounceCount;  
     }  
 };  
 // ZxKey constructor  
 ZxKey::ZxKey (byte Normal, byte NormalShifted, byte Graphics, byte GraphicsShifted){  
   bNormal = Normal;  
   bNormalShifted = NormalShifted;  
   bGraphics = Graphics;  
   bGraphicsShifted = GraphicsShifted;  
   iDebounceCount = DEBOUNCE_VALUE;  
 }  
 // Defines entire keyboard, includes ZxKey class.  
 class ZxKeyBoard {  
   private:  
   // Setup 4 versions of keyboard states into keyboard.  
   // Keyboard mapped for US, might need changing for other configurations eg. UK keyboard.  
   ZxKey keyMap[NUM_ROWS][NUM_COLS] =   
     {  
       {{'5',KEY_LEFT_ARROW,KEY_F5,KEY_HOME},{'4','%',KEY_F4,KEY_INSERT},{'3','#',KEY_F3,KEY_ESC},{'2','@',KEY_F2,KEY_F12},{'1','!',KEY_F1,KEY_F11}},  
       {{'t','_','t','t'},{'r','&','r','r'},{'e','^','e','e'},{'w','`','w','w'},{'q','~','q','q'}},  
       {{'6',KEY_DOWN_ARROW,KEY_F6,KEY_PAGE_DOWN},{'7',KEY_UP_ARROW,KEY_F7,KEY_PAGE_UP},{'8',KEY_RIGHT_ARROW,KEY_F8,KEY_END},{'9','9',KEY_F9,'9'},{'0',KEY_BACKSPACE,KEY_F10,'0'}},  
       {{'g','\\','g','g'},{'f','}','f','f'},{'d','{','d','d'},{'s',']','s','s'},{'a','[','a','a'}},  
       {{'y','|','y','y'},{'u','$','u','u'},{'i','(','i','i'},{'o',')','o','o'},{'p','"','p','p'}},  
       {{'v','/','v','v'},{'c','?','c','c'},{'x',';','x','x'},{'z',':','z','z'},{0,0,0,0}},  
       {{'h','\'','h','h'},{'j','-','j','j'},{'k','+','k','k'},{'l','=','l','l'},{KEY_RETURN,KEY_RETURN,KEY_RETURN,KEY_RETURN}},  
       {{'b','*','b','b'},{'n','<','n','n'},{'m','>','m','m'},{'.',',','.','.'},{' ',KEY_TAB,'£',' '}}  
     };  
   public:  
     // Return from ZxKey matching specified state.  
     byte bKeyPress(byte bRow, byte bCol, KEYSTATES eKeystate){  
       // Value of Pressed key to Return.  
       byte bPressedKey =0;  
       // Get Keyboard character for correct Key state.  
       switch (eKeystate) {  
         case FUNCTION:  
           bPressedKey = keyMap[bRow][bCol].getKeyNormal();  
           // Adjust for Capital Letters  
           if (bPressedKey > 96 && bPressedKey < 123){  
             bPressedKey = bPressedKey - 32;  
           }  
           break;  
         case NORMAL_SHIFTED:  
           bPressedKey = keyMap[bRow][bCol].getKeyNormalShifted();  
         break;  
         case GRAPHICS:  
           bPressedKey = keyMap[bRow][bCol].getKeyGraphics();  
         break;  
         case GRAPHICS_SHIFTED:  
           bPressedKey = keyMap[bRow][bCol].getKeyGraphicsShifted();  
         break;  
         default:  
           // Standard normal and Emulator mode keyboard.  
           // Shift key modifier used later to select functions etc in a ZX81 Emulator.  
           bPressedKey = keyMap[bRow][bCol].getKeyNormal();  
         break;  
       }  
       // Keep track of length of key presses before returning a value.  
       if ( keyMap[bRow][bCol].getDebounceCount() == 0 ){  
         // Set repeat rate if key held down  
         keyMap[bRow][bCol].setDebounceCount(REPEAT_DELAY);  
         return bPressedKey;  
       } else {  
         keyMap[bRow][bCol].iDecreaseDebounceCount();  
         return 0;  
       }  
     }  
     byte bKeyRelease(byte bRow, byte bCol){;  
       keyMap[bRow][bCol].resetDebounceCount();  
       return 0;  
     }  
     byte bKeyDisable(byte bRow, byte bCol){;  
       keyMap[bRow][bCol].setDebounceCount(-1);  
       return 0;  
     }  
 };  
 // Defines LED / Mode switch panel and its behaviour.  
 // Three LEDs are configured to report on keyboard mode and states.  
 //   
 // In STANDARD mode:  
 //    Left LED on = GRAPHICS state.  
 //    Middle LED on = FUNCTION state.  
 //    Right LED on = NORMAL state.  
 //  
 // In EMULATOR mode:  
 //    Left and Right LEDs = on.  
 class ModeState {  
   private:  
     KEYMODES eKeyboardMode = STANDARD;  
     KEYSTATES eKeyboardState = NORMAL;  
     byte bModeSwitchPin;  
     byte bGraphicsPin;  
     byte bFunctionPin;  
     byte bModePin;  
     bool boDebounce = false;  
     byte SetMode(){  
       if (digitalRead(bModeSwitchPin) == HIGH && boDebounce == false) {  
         Serial.println("high ");  
         if (eKeyboardMode == STANDARD){  
           eKeyboardMode = EMULATOR;  
         } else {  
           eKeyboardMode = STANDARD;  
         }  
         delay(REPEAT_DELAY);  
         boDebounce = true;  
       } else if (digitalRead(bModeSwitchPin) == LOW) {  
         boDebounce = false;  
       }  
     }  
   public:  
     ModeState (byte, byte, byte, byte);  
     byte SetState(KEYSTATES eKeystate){  
       eKeyboardState = eKeystate;  
       // Check and set mode if Mode switch is pressed  
       SetMode();  
       if (eKeyboardMode != EMULATOR){  
         switch (eKeyboardState) {  
           case FUNCTION:  
             digitalWrite(bGraphicsPin, LOW);  
             digitalWrite(bFunctionPin, HIGH);  
             digitalWrite(bModePin, LOW);  
             break;  
           case GRAPHICS:  
             digitalWrite(bGraphicsPin, HIGH);  
             digitalWrite(bFunctionPin, LOW);  
             digitalWrite(bModePin, LOW);  
             break;  
           default:  
             digitalWrite(bGraphicsPin, LOW);  
             digitalWrite(bFunctionPin, LOW);  
             digitalWrite(bModePin, HIGH);  
             eKeyboardState = NORMAL;  
             break;  
         }  
       } else {  
         digitalWrite(bGraphicsPin, HIGH);  
         digitalWrite(bFunctionPin, LOW);  
         digitalWrite(bModePin, HIGH);  
         eKeyboardState = NORMAL;  
       }  
     }  
     KEYMODES GetMode(){  
       return eKeyboardMode;  
     }  
     KEYSTATES GetState(){  
       return eKeyboardState;  
     }  
 };  
 // ModeState constructor.  
 ModeState::ModeState (byte ModeSwitchPin, byte GraphicsPin, byte FunctionPin, byte ModePin){  
   bModeSwitchPin = ModeSwitchPin;  
   bGraphicsPin = GraphicsPin;  
   bFunctionPin = FunctionPin;  
   bModePin = ModePin;  
   pinMode(bModeSwitchPin, INPUT);  
   pinMode(bGraphicsPin, OUTPUT);  
   pinMode(bFunctionPin, OUTPUT);  
   pinMode(bModePin, OUTPUT);  
 }  
 // ************************  
 // *** Global Variables ***  
 // ************************  
 // Setup Global Variables for keyboard.  
 ZxKeyBoard MyKeyboard;  
 // Setup mode switch and indicator LEDs.  
 ModeState MyModeState(A5, A0, A1, A2);  
 // Setup Keyboard row and column pins.  
 const byte bColPins[NUM_COLS] = {13, 12, 10, 9, 8};  
 const byte bRowPins[NUM_ROWS] = {7, 6, 5, 4, 3, 2, 1, 0};  
 // ******************  
 // *** Main Setup ***  
 // ******************  
 void setup() {  
   // Set all Keyboard pins as inputs and activate pull-ups.  
   for (byte bColCount = 0 ; bColCount < NUM_COLS ; bColCount++)  
   {  
     pinMode(bColPins[bColCount], INPUT);  
     digitalWrite(bColPins[bColCount], HIGH);  
   }  
   // Set all Keyboard pins as inputs.  
   for (byte bRowCount = 0 ; bRowCount < NUM_ROWS ; bRowCount++)  
   {  
     pinMode(bRowPins[bRowCount], INPUT);  
   }  
   // initialize control over the keyboard.  
   Serial.begin(9600);  
   Keyboard.begin();  
 }  
 // ************************  
 // *** Main loop        ***  
 // *** Lets get Busy-ah ***  
 //*************************  
 void loop() {  
   bool boShifted = false;  
   byte bKeyPressed = 0;  
   KEYMODES eMyKeyMode = MyModeState.GetMode();  
   KEYSTATES eMyKeyState = MyModeState.GetState();  
   // Check for the Shift key being pressed.  
   pinMode(bRowPins[SHIFT_ROW], OUTPUT);  
   if (digitalRead(bColPins[SHIFT_COL]) == LOW) boShifted = true;  
   pinMode(bRowPins[SHIFT_ROW], INPUT);  
   for (byte bRow = 0 ; bRow < NUM_ROWS ; bRow++)  
   {  
     // Run through the rows, turn them on.  
     pinMode(bRowPins[bRow], OUTPUT);  
     digitalWrite(bRowPins[bRow], LOW);  
     for (byte bCol = 0 ; bCol < NUM_COLS ; bCol++)  
     {   
       if (digitalRead(bColPins[bCol]) == LOW)  
       {  
         if (boShifted && eMyKeyMode != EMULATOR){  
           // Select correct Keyboard layout for current state For STANDARD mode.   
           // Shift alters the Current state selection. eg. gets Red Shift symbols in Normal State.   
           switch (eMyKeyState) {  
             case NORMAL:          
               bKeyPressed = MyKeyboard.bKeyPress(bRow, bCol, NORMAL_SHIFTED);                    
               if (bKeyPressed == KEY_RETURN) {   
                 eMyKeyState = FUNCTION;  
                 bKeyPressed = 0;  
                 MyKeyboard.bKeyDisable(bRow, bCol);  
               }  
               if (bKeyPressed == '9') {  
                 eMyKeyState = GRAPHICS;  
                 bKeyPressed = 0;  
                 MyKeyboard.bKeyDisable(bRow, bCol);  
               }        
               break;  
             case FUNCTION:  
               bKeyPressed = MyKeyboard.bKeyPress(bRow, bCol, NORMAL_SHIFTED);  
               if (bKeyPressed == KEY_RETURN) {   
                 eMyKeyState = NORMAL;  
                 bKeyPressed = 0;  
                 MyKeyboard.bKeyDisable(bRow, bCol);  
               }  
               if (bKeyPressed == '9') {  
                 eMyKeyState = GRAPHICS;  
                 bKeyPressed = 0;  
                 MyKeyboard.bKeyDisable(bRow, bCol);  
               }  
               break;  
             case GRAPHICS:  
               bKeyPressed = MyKeyboard.bKeyPress(bRow, bCol, GRAPHICS_SHIFTED);  
               if (bKeyPressed == KEY_RETURN) {   
                 eMyKeyState = FUNCTION;  
                 bKeyPressed = 0;  
                 MyKeyboard.bKeyDisable(bRow, bCol);  
               }  
               if (bKeyPressed == '9') {  
                 eMyKeyState = NORMAL;  
                 bKeyPressed = 0;  
                 MyKeyboard.bKeyDisable(bRow, bCol);  
               }  
               break;  
           }  
         } else {  
           // Emulator keyboard, the keyboard states are controlled by a ZX81 emulator.  
           // Keyboard mimics unaltered PS2 Keyboard presses as expected by an emulator.  
           bKeyPressed = MyKeyboard.bKeyPress(bRow, bCol, eMyKeyState);  
         }  
         if (bKeyPressed > 0 ) {  
           //Serial.write(bKeyPressed);  
           if (eMyKeyMode == EMULATOR && bKeyPressed && boShifted) Keyboard.press(KEY_LEFT_SHIFT);  
           if (eMyKeyMode != EMULATOR && eMyKeyState == GRAPHICS && bKeyPressed > 96 && bKeyPressed < 123){  
             if (boShifted){  
               Keyboard.press(KEY_LEFT_ALT);  
             } else {  
               Keyboard.press(KEY_LEFT_CTRL);  
             }  
           }  
           Keyboard.press(bKeyPressed);  
           Keyboard.releaseAll();  
           //tone(11, 31, 20);  
         }  
       } else {  
         MyKeyboard.bKeyRelease(bRow, bCol);  
       }  
     }  
     pinMode(bRowPins[bRow], INPUT);  
   }  
   digitalWrite(bRowPins[SHIFT_ROW], LOW);  
   // Update LED panel and check for Mode change switch press.  
   MyModeState.SetState(eMyKeyState);  
 }