I was confident it would look pretty cool, yet once the case arrived (after some timely industrial action at customs and excise) my expectations were exceeded by far. It does in fact look damn amazing, this 3D printing thing and by extension Shapeways ability to turn home design into reality is brilliant.
As expected the surface on the final product is lightly rough to touch and has a non-gloss finish. It kind of reminds me of suede, if suede assumed a plasticky form. You can still see the layers where the object has been built up but that certainly doesn't detract from the looks of the AZ15 case. Invariably I couldn't wait to put it all together, and thus the beginning of the end phase ensued.
Firstly I gathered all the disparate parts together. Internally the case had the following items to be installed, a Raspberry Pi 2, the LeoStick and Converter board, a right-angled usb cable to connect to the LeoStick, a WIFI dongle, LEDs and the mode selector switch. The keyboard would sit nicely on the tray, with it's cable sliding through a gap provided at the upper right corner much, like on the original ZX81.
The Pi, USB cable and WIFI dongle were installed first. I was relived that this all fit exactly as planned, being that the AZ15 case was designed specifically to hide one of the USB port banks and thus give the appearance of one complete unit to the casual observer.
Next the LEDs were hot glued into position, the mode switch installed and the keyboard converter board were inserted. Working in the confined space was a little challenging but everything so far went in without to much difficultly. As I'd made the converter myself and therefore drilled the mounting holes by hand, the mounting points were designed to allow a little slip and slide for easy adjustment. The one thing I hadn't counted on was not being able to affix the bolt heads due to access issues at the far right corners, luckily for me the drill holes in the converter board are nice and tight, so the bolts screwed in and held the board firmly enough.
Time to plug in the brains of the keyboard ie. the LeoStick. It's all a tight little fit, leaving just enough room to smuggle in the cables coming directly from the keyboard. The keyboard was mounted with some doubled sided tape on the keyboard tray.
Flipping the case over, some handy dandy rubber feet are slotted into position to provide much needed non slipping action, necessary when typing on a keyboard of diminutive weight and size.
Now it was a simple matter of plonking the lid on, except that the plonking didn't quite plonk as one might have expected. Unfortunately I'd made the little tabs on the side of the lid slight to small, meaning that the lid slides about a little bit to much. The immediate solution was to use some tiny strips of double sided tape to hold the movable lid in position. At a later date I might opt to get the lid reprinted and permanently fix this relatively minor issue.
So that's it, time for some sitting back a marveling at my creation. I'm very pleased with the final look of the AZ15 case, and hopefully I've managed to do the original ZX81 design justice and possibly at least slightly amuse Rick Dickinson the creator of the ZX81's iconic looks.
▼
Friday, November 27, 2015
Monday, November 09, 2015
Final Case Design Shipped to Shapeways
AZ15 Deign Impression |
The case was designed using the modeler AC3D, I've been using this tool for various projects over a number of years (though never for a 3d printed model before), so am pretty comfortable using it. A Raspberry PI 2 / B+ model was sourced from Thingiverse to construct the case around. I can't locate the exact model I used now, however there are a number to chose from including this one by jayftee. It's always a bonus when somebody else has done the hard work of modeling and exacting object for you.
AZ15 Case Design, Side View |
AZ15 Case Design, Rear View |
From the outset I hadn't intended to clone a ZX81 case, the original being a design classic in its own right. What I'm after is something reminiscent of a classic 80s microcomputer but with a contemporary feel. A kind of modern clone. Hopefully within the next week or so I'll find out just how successful I've been.
Wednesday, November 04, 2015
Some More Arduino Code for the ZX81 Keyboard
I've made a fair number of changes to the ZX81 keyboard Arduino sketch. The most noticeable being that the code is now broken into multiple files / libraries; all feeling a little more C++ like. Of course real microcontroller programmers use C, but we wont discus that here.
In general the keyboard functions as earlier. The main physically tangible improvement being that the keyboard performs a whole lot better in ZX81 emulators. It's not perfect and it's definitely not ideal for playing real time games with, though It works as advertised when typing normally, ie. using the keyboard to program with.
Emulator comparability wise, the keyboard will work with sz81 and ZEsarUX. I'd recommend using sz81, as the keyboard still functions best with that particular emulator. I've also had a go at using Fuse with the keyboard, and to the extent that a ZX81 keyboard can be used on ZX Spectrum it functions okay, though it is missing those couple of crucial extra keys for proper Spectrum emulation.
When using the keyboard in standard mode as a regular USB input device in Linux or with a Raspberry Pi there are no issues, unfortunately, as with the emulators, games that require SDL libraries suffer from the "keys not being registered when you'd like them to be" syndrome. This issue I'm afraid may be down to problems with the Arduino USB Keyboard library and beyond my immediate control.
In general the keyboard functions as earlier. The main physically tangible improvement being that the keyboard performs a whole lot better in ZX81 emulators. It's not perfect and it's definitely not ideal for playing real time games with, though It works as advertised when typing normally, ie. using the keyboard to program with.
Emulator comparability wise, the keyboard will work with sz81 and ZEsarUX. I'd recommend using sz81, as the keyboard still functions best with that particular emulator. I've also had a go at using Fuse with the keyboard, and to the extent that a ZX81 keyboard can be used on ZX Spectrum it functions okay, though it is missing those couple of crucial extra keys for proper Spectrum emulation.
When using the keyboard in standard mode as a regular USB input device in Linux or with a Raspberry Pi there are no issues, unfortunately, as with the emulators, games that require SDL libraries suffer from the "keys not being registered when you'd like them to be" syndrome. This issue I'm afraid may be down to problems with the Arduino USB Keyboard library and beyond my immediate control.
Presented For Your Amusement, Code Caught in the Wild
Main Program (zx81usbkeyboard.ino)
As the name on the implies, here be the general program.
// **************************************************************************
// **** ZX81 USB Keyboard for Funtronics LeoStick (based on a Leonardo). ****
// **** zx81usbkeyboard.ino ****
// **************************************************************************
// ** David Stephenson 2015-11-03 **
// ** **
// ** Originally based on code by: **
// ** Dave Curran 2013-04-27 **
// ** Tony Smith 2014-02-15 **
// **********************************
#include "Arduino.h"
#include "zx81keyboard.h"
#include "zx81modestate.h"
// ************************
// *** Global Variables ***
// ************************
// Setup Global Variables for keyboard.
ZxKeyBoard MyKeyboard;
// Setup LeoStick pins for mode switch and indicator LEDs.
ModeState MyModeState(A5,A0,A1,A2);
// Setup LeoStick pins for Keyboard rows and columns.
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;
// Set Keyboard Mode.
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 == STANDARD){
// 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, eMyKeyMode);
if (bKeyPressed == KEY_RETURN) {
eMyKeyState = FUNCTION;
bKeyPressed = 0;
MyKeyboard.KeyDisable(bRow, bCol);
}
if (bKeyPressed == '9') {
eMyKeyState = GRAPHICS;
bKeyPressed = 0;
MyKeyboard.KeyDisable(bRow, bCol);
}
break;
case FUNCTION:
bKeyPressed = MyKeyboard.bKeyPress(bRow, bCol, NORMAL_SHIFTED, eMyKeyMode);
if (bKeyPressed == KEY_RETURN) {
eMyKeyState = NORMAL;
bKeyPressed = 0;
MyKeyboard.KeyDisable(bRow, bCol);
}
if (bKeyPressed == '9') {
eMyKeyState = GRAPHICS;
bKeyPressed = 0;
MyKeyboard.KeyDisable(bRow, bCol);
}
break;
case GRAPHICS:
bKeyPressed = MyKeyboard.bKeyPress(bRow, bCol, GRAPHICS_SHIFTED, eMyKeyMode);
if (bKeyPressed == KEY_RETURN) {
eMyKeyState = FUNCTION;
bKeyPressed = 0;
MyKeyboard.KeyDisable(bRow, bCol);
}
if (bKeyPressed == '9') {
eMyKeyState = NORMAL;
bKeyPressed = 0;
MyKeyboard.KeyDisable(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, eMyKeyMode);
}
if (bKeyPressed > 0 ) {
//Serial.write(bKeyPressed);
if (eMyKeyMode == EMULATOR){
if (bKeyPressed && boShifted) Keyboard.press(KEY_LEFT_SHIFT);
Keyboard.press(bKeyPressed);
// Many Emulators don't use standard OS keyboard routines and require a pause
// for key to register. There is some variance, but 100ms seems to cover most.
delay(DEBOUNCE_DELAY/2);
} else {
if (eMyKeyState == GRAPHICS && bKeyPressed > 96 && bKeyPressed < 123){
if (boShifted){
Keyboard.press(KEY_LEFT_ALT);
} else {
Keyboard.press(KEY_LEFT_CTRL);
}
}
Keyboard.press(bKeyPressed);
// Some audio feedback if not in EMULATOR mode.
// Kill the line if you hate beepy beep beeps.
tone(11, 31, 20);
}
Keyboard.releaseAll();
}
} else {
MyKeyboard.KeyPressReset(bRow, bCol);
}
}
pinMode(bRowPins[bRow], INPUT);
}
digitalWrite(bRowPins[SHIFT_ROW], LOW);
// Update LED panel and check for Mode change switch press.
MyModeState.SetState(eMyKeyState);
}
Keyboard Class (zx81keyboard.h)
Main guts of the keyboard setup and initial handling is in here.
// ***********************
// ** zx81keyboard.h **
// ** Class Definitions **
// ** For ZX81 keyboard **
// ***********************
enum KEYMODES {EMULATOR, STANDARD};
enum KEYSTATES {NORMAL, NORMAL_SHIFTED, GRAPHICS, GRAPHICS_SHIFTED, FUNCTION};
#define NUM_ROWS 8
#define NUM_COLS 5
#define SHIFT_COL 4
#define SHIFT_ROW 5
//#define FUNCTION_COL 4
//#define FUNCTION_ROW 6
//#define GRAPHICS_COL 3
//#define GRAPHICS_ROW 2
#define DEBOUNCE_DELAY 200 //debounce countdown value.
#define DEBOUNCE_DELAY_REPEAT 600
// Defines a single key and its 4 possible Major KEYSTATES: NORMAL, NORMAL_SHIFTED, GRAPHICS, GRAPHICS_SHIFTED
// Returns 5 KEYSTATES, adds FUNCTION: Caps Lock).
class ZxKey {
private:
// Standard Keyboard Characters for Normal PC Use
byte bNormal; // Standard ZX keyboard only in lower case. EMULATOR KEYMODE should use bNormal only.
// 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 should be used with CTL characters. Numbers = F1 - F10 etc
byte bGraphicsShifted; // Standard ZX keyboard but should be used with ALT characters. Numbers = F11 - F12, 5 = home, 6 PGUP etc
// Monitor Keys last press / activity.
short iDebounceCount = DEBOUNCE_DELAY;
short iDebounceCountHighest = iDebounceCount;
byte KeyValue(KEYSTATES eKeystate) {
// Get Keyboard character for correct Key state.
byte bKeyValue = 0;
switch (eKeystate) {
case FUNCTION:
bKeyValue = bNormal;
// Adjust for Capital Letters
if (bKeyValue > 96 && bKeyValue < 123){
bKeyValue = bKeyValue - 32;
}
break;
case NORMAL_SHIFTED:
bKeyValue = bNormalShifted;
break;
case GRAPHICS:
bKeyValue = bGraphics;
break;
case GRAPHICS_SHIFTED:
bKeyValue = bGraphicsShifted;
break;
default:
// Standard normal and Emulator mode keyboard.
// Shift key modifier used later to select functions etc in a ZX81 Emulator.
bKeyValue = bNormal;
break;
}
return bKeyValue;
}
public:
ZxKey (byte, byte, byte, byte);
byte KeyPressDetected(KEYSTATES eKeystate, KEYMODES eKeymode){
byte bKeyValue = 0;
iDebounceCount--;
if (iDebounceCount == 0){
switch (iDebounceCountHighest) {
case DEBOUNCE_DELAY:
iDebounceCount = DEBOUNCE_DELAY_REPEAT;
iDebounceCountHighest = iDebounceCount;
break;
case DEBOUNCE_DELAY_REPEAT:
if (eKeymode == EMULATOR){
iDebounceCount = DEBOUNCE_DELAY;
} else {
iDebounceCount = DEBOUNCE_DELAY / 2;
}
break;
default:
iDebounceCount = DEBOUNCE_DELAY;
break;
}
bKeyValue = KeyValue(eKeystate);
}
return bKeyValue;
}
void KeyPressReset(){
iDebounceCount = DEBOUNCE_DELAY;
iDebounceCountHighest = iDebounceCount;
}
void KeyDisable(){
iDebounceCount = -1;
}
};
// ZxKey constructor
ZxKey::ZxKey (byte Normal, byte NormalShifted, byte Graphics, byte GraphicsShifted){
bNormal = Normal;
bNormalShifted = NormalShifted;
bGraphics = Graphics;
bGraphicsShifted = GraphicsShifted;
}
// 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, KEYMODES eKeymode){
return keyMap[bRow][bCol].KeyPressDetected(eKeystate, eKeymode);
}
void KeyPressReset(byte bRow, byte bCol){;
keyMap[bRow][bCol].KeyPressReset();
}
void KeyDisable(byte bRow, byte bCol){;
keyMap[bRow][bCol].KeyDisable();
}
};
Mode Switch & LED Class (zx81modestate.h)
This ones still a work in progress, I'm wanting to add some more functionality to the Mode selection button at some point. Works well enough for the moment.
// *****************************
// ** zx81modestate.h **
// ** Class Definitions **
// ** For Mode Switch & LEDs **
// *****************************
// 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(DEBOUNCE_DELAY_REPEAT);
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);
}