Acorn Electron keyboard scanner, turns an old Acorn Electron into a USB keyboard.

Dependencies:   USBDevice mbed

main.cpp

Committer:
IH
Date:
2014-06-18
Revision:
2:9352b1232e6d
Parent:
1:84cd616cc684

File content as of revision 2:9352b1232e6d:

/*
 * This is a simple mbed program for driving 
 * an Acorn Electron keyboard from a KL25Z mbed
 * board.
 *
 * It is placed in the public domain by its author,
 * Ian Harvey. Note that there is NO WARRANTY.
 */
 
/* About the Electron keyboard -----------------

The keyboard itself has 14 'columns' and 4 'rows'.
The column being scanned is driven low and four row
bits are read out. The keyboard has diodes on the columns
and pull-ups on the rows, so there's no need to tri-state
un-driven columns or activate pullups. The break key
is wired separately and connects directly to ground
when preseed. Connections are as follows:

1.  Break key       PTE5
2.  Caps lock LED   PTE4
3.  Ground
4.  V+ for LED 
5.  Row 3           PTE3
6.  Row 2           PTE2
7.  Row 1           PTB11
8.  Row 0           PTB10
9.  Col 13          PTA1
10. Col 12          PTC7
11. Col 11          PTA2
12. Col 10          PTC0
13. Col 9           PTD4
14. Col 8           PTC3
15. Col 7           PTA12
16. Col 6           PTC4
17. Col 5           PTA4
18. Col 4           PTC5
19. Col 3           PTA5
20. Col 2           PTC6
21. Col 1           PTC8
22. Col 0           PTC10

Pin "1" (my numbering) is closest to the corner
of the board, and pin "22" is next to the space
bar. The connections of the keys follow the 
layout in 'hid_keys' below, so KEY_PERIOD is 
col 0 row 0, KEY_L is col 0 row 1, and so on.
We're using the following modifications:

"CAPS LK"  -> KEY_TAB
"[ ] COPY" -> KEY_OPEN_SQUARE
"DELETE" -> KEY_LEFT_ALT
": *" -> KEY_SINGLE_QUOTE

The firmware also treats the 'break' key as
'row 4' all on its own (mapped to KEY_BACKSPACE).

When 'DELETE' (the LEFT_ALT key) is pressed,
the keyboard map in 'alt_keys' is used, so
e.g. KEY_MINUS becomes KEY_EQUALS.

Keyboard layout
---------------

Note that the keycaps on the Electron keyboard
are considerably different to those on a regular
PC-style one: shift-6 is labelled '&' not '^',
shift-';' is labelled '+' not ':', and so on.
The code here *doesn't* change which key code
is sent when shift is pressed, so the characters
you get are those you'd get on a PC keyboard -
after some experimentation I personally preferred
this. If this isn't to your taste, creating a
custom keyboard layout on the PC/Pi may be the best
way to fix this.

I tend to use a 'US' keyboard layout with this
keyboard, so shift-3 generates a '#', not a
UK pound sign, and the single/double quotes are
next to the ';' key (labelled ': *' on the Electron).

---------------------------------------------- */

#include "mbed.h"
#include "USBKeyboard.h"
#include "hid_keys.h"

#define MAX_ROWS 5
#define MAX_COLS 14
#define REPORT_LEN 9
#define REPORT_ID_KEYBOARD 1

const uint8_t hid_keys[MAX_ROWS * MAX_COLS] = 
{
    KEY_PERIOD,     KEY_L,          KEY_O,      KEY_9,      KEY_BACKSPACE,
    KEY_SLASH,      KEY_SEMICOLON,  KEY_P,      KEY_0,      KEY_NONE,
    KEY_NONE,       KEY_SINGLE_QUOTE,KEY_UP_ARROW,   KEY_MINUS, KEY_NONE,
    KEY_LEFT_ALT,   KEY_ENTER,      KEY_DOWN_ARROW, KEY_LEFT_ARROW, KEY_NONE,

    KEY_SPACE,      KEY_NONE,       KEY_OPEN_SQUARE, KEY_RIGHT_ARROW, KEY_NONE,
    KEY_COMMA,      KEY_K,          KEY_I,      KEY_8,      KEY_NONE,
    KEY_M,          KEY_J,          KEY_U,      KEY_7,      KEY_NONE,
    KEY_N,          KEY_H,          KEY_Y,      KEY_6,      KEY_NONE,

    KEY_B,          KEY_G,          KEY_T,      KEY_5,      KEY_NONE,
    KEY_V,          KEY_F,          KEY_R,      KEY_4,      KEY_NONE,
    KEY_C,          KEY_D,          KEY_E,      KEY_3,      KEY_NONE,
    KEY_X,          KEY_S,          KEY_W,      KEY_2,      KEY_NONE,

    KEY_Z,          KEY_A,          KEY_Q,      KEY_1,      KEY_NONE,
    KEY_LEFT_SHIFT, KEY_LEFT_CTRL,  KEY_TAB,    KEY_ESC,    KEY_NONE,
};

const uint8_t alt_keys[MAX_ROWS * MAX_COLS] = 
{
    KEY_NONE,       KEY_NONE,       KEY_NONE,   KEY_NONE,   KEY_NONE,
    KEY_NONE,       KEY_NONE,       KEY_NONE,   KEY_NONE,   KEY_NONE,
    KEY_NONE,       KEY_NONE,       KEY_HASH_TILDE, KEY_EQUALS, KEY_NONE,
    KEY_NONE,       KEY_NONE,       KEY_NONE,   KEY_BACKTICK_TILDE,   KEY_NONE,

    KEY_NONE,       KEY_NONE,       KEY_CLOSE_SQUARE, KEY_BACKSLASH,   KEY_NONE,
    KEY_NONE,       KEY_NONE,       KEY_NONE,   KEY_NONE,   KEY_NONE,
    KEY_NONE,       KEY_NONE,       KEY_NONE,   KEY_NONE,   KEY_NONE,
    KEY_NONE,       KEY_NONE,       KEY_NONE,   KEY_NONE,   KEY_NONE,

    KEY_NONE,       KEY_NONE,       KEY_NONE,   KEY_NONE,   KEY_NONE,
    KEY_NONE,       KEY_NONE,       KEY_NONE,   KEY_NONE,   KEY_NONE,
    KEY_NONE,       KEY_NONE,       KEY_NONE,   KEY_NONE,   KEY_NONE,
    KEY_NONE,       KEY_NONE,       KEY_NONE,   KEY_NONE,   KEY_NONE,

    KEY_NONE,       KEY_NONE,       KEY_NONE,   KEY_NONE,   KEY_NONE,
    KEY_NONE,       KEY_NONE,       KEY_NONE,   KEY_NONE,   KEY_NONE,

};


BusOut leds(LED1, LED2, LED3);

BusOut scanCols(
    PTC10,PTC8, 
    PTC6, PTA5,   
    PTC5, PTA4,  
    PTC4, PTA12,
    PTC3, PTD4,   
    PTC0, PTA2,  
    PTC7, PTA1  
    );

BusIn inRows(
    PTB10,
    PTB11,
    PTE2,
    PTE3,
    PTE5 );

DigitalOut extLed(PTE4);

USBKeyboard kbd;

static int scanColumn(int col)
{
    int rowBits;
    // Drive output low to scan
    scanCols.write(0x3FFF ^ (1 << col));
    leds.write(col >> 1);
    wait(0.001);
    rowBits = inRows.read();
    scanCols.write(0x3FFF);
    // Inputs also active-low
    return rowBits ^ 0x1F;
}

static const uint8_t altKeys =
  MODIFIER_BIT(KEY_LEFT_ALT) | MODIFIER_BIT(KEY_RIGHT_ALT);
  
      
int main()
{
    // Setup
    inRows.mode(PullUp);
    extLed = 1;

    // Run loop
    while(1)
    {
        int col, ocount;
        HID_REPORT report;
        uint8_t keyIfAlt = KEY_NONE;
        
        report.data[0] = REPORT_ID_KEYBOARD;
        report.data[1] = 0; // modifiers
        report.data[2] = 0;
        ocount = 3;
        
        for (col=0; col < MAX_COLS; col++)
        {
            int row;
            int rowBits = scanColumn(col);
            if ( !rowBits )
              continue;
            
            for (row=0; row < MAX_ROWS; row++)
            {
              if ( rowBits & (1 << row) )
              {
                uint8_t key = hid_keys[col * MAX_ROWS + row];
                if ( IS_MODIFIER(key) )
                  report.data[1] |= MODIFIER_BIT(key);
                else if ( key != KEY_NONE )
                {
                  if ( ocount < REPORT_LEN )
                    report.data[ocount++] = key;
                }
                
                key = alt_keys[col * MAX_ROWS + row];
                if ( key != KEY_NONE )
                  keyIfAlt = key;
                //kbd.printf("c%dr%d ", col, row);
              }
            }
        }

        if ( (report.data[1] & altKeys) != 0 &&
             keyIfAlt != KEY_NONE
           )
        {
            report.data[3] = keyIfAlt;
            ocount = 4; // Zero out the rest
            report.data[1] &= ~altKeys; // And put alt key up
        }
        
        while( ocount < REPORT_LEN )
          report.data[ocount++] = KEY_NONE;
            
        report.length = REPORT_LEN;  
        kbd.send(&report);
    }
}