├── .gitignore ├── Keyboard.png ├── Keyboard.sch ├── Keyboard ├── Keyboard.ino └── types.h ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /Keyboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ast/keyboard/7318013d8224fbaa6625ca92622552b0a87d3fdc/Keyboard.png -------------------------------------------------------------------------------- /Keyboard/Keyboard.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "types.h" 4 | 5 | key_t keys[88]; 6 | 7 | #define NUM_BANKS 11 8 | 9 | // For scanning banks 10 | bank_t banks[NUM_BANKS]; 11 | bank_t prev_banks[NUM_BANKS]; 12 | 13 | void trigger(key_t *key, event_t event) { 14 | 15 | if(event == KEY_PRESSED) { 16 | key->state = KEY_IS_GOING_DOWN; 17 | 18 | } else if (event == KEY_DOWN) { 19 | key->state = KEY_IS_DOWN; 20 | usbMIDI.sendNoteOn(key->midi_note, 127- key->t, 1); 21 | key->t = 0; 22 | 23 | } else if (event == KEY_RELEASED) { 24 | key->state = KEY_IS_GOING_UP; 25 | 26 | } else if ( event == KEY_UP) { 27 | key->state = KEY_IS_UP; 28 | usbMIDI.sendNoteOff(key->midi_note, 127 - key->t, 1); 29 | key->t = 0; 30 | 31 | } 32 | 33 | } 34 | 35 | void setup() { 36 | DDRD = 0b11111111; // Set D0-D7 as outputs 37 | 38 | DDRC= 0b00000000; // Set D0-D7 as inputs 39 | PORTC = 0b11111111; // Enable pull up resistors 40 | 41 | DDRF = DDRF | 0b11111110; 42 | PORTF = PORTF | 0b00000001; 43 | 44 | // Init array 45 | memset(banks, 0xff, sizeof(prev_banks)); 46 | 47 | // Init keys 48 | for ( int key = 0; key < 88; key++) { 49 | keys[key].midi_note = 21 + key; 50 | keys[key].t = 0; 51 | } 52 | 53 | MsTimer2::set(1, timeout); // 1ms period 54 | MsTimer2::start(); 55 | } 56 | 57 | void timeout() { 58 | scan(); 59 | increment(); 60 | 61 | footpedal(); 62 | 63 | } 64 | 65 | void increment() { 66 | // Advance timers 67 | for(int key = 0; key < 88; key++) { 68 | state_t state = keys[key].state; 69 | if(state == KEY_IS_GOING_UP || state == KEY_IS_GOING_DOWN) { 70 | keys[key].t++; 71 | } 72 | } 73 | } 74 | 75 | void footpedal() { 76 | 77 | static int prev_val = 1; 78 | 79 | int val = PINF & 0b00000001; 80 | 81 | if(val != prev_val) { 82 | usbMIDI.sendControlChange(64, (val ? 1 : 64), 1); 83 | prev_val = val; 84 | } 85 | 86 | } 87 | 88 | void scan() { 89 | 90 | // Scan and store 91 | for(int bank = 0; bank < NUM_BANKS; bank++) { 92 | prev_banks[bank] = banks[bank]; // Store previous state so we can look for changes 93 | 94 | PORTD = bank * 2; // Selects bottom row 95 | delayMicroseconds(10); // Debounce 96 | banks[bank].bottom = PINC; 97 | 98 | PORTD = bank * 2 + 1; // Selects top row 99 | delayMicroseconds(10); // Debounce 100 | banks[bank].top = PINC; 101 | } 102 | 103 | // Process 104 | for(int bank = 0; bank < NUM_BANKS; bank++) { 105 | 106 | byte diff; 107 | 108 | // Check top switches and fire events 109 | diff = banks[bank].top ^ prev_banks[bank].top; 110 | if(diff) { 111 | for(int key = 0; key < 8; key++) { 112 | if(bitRead(diff, key)) { 113 | event_t event = bitRead(banks[bank].top, key) ? KEY_UP : KEY_PRESSED; 114 | trigger(&keys[bank * 8 + key], event); 115 | } 116 | } 117 | } 118 | 119 | // Check bottom switches and fire events 120 | diff = banks[bank].bottom ^ prev_banks[bank].bottom; 121 | if(diff) { 122 | for(int key = 0; key < 8; key++) { 123 | if(bitRead(diff, key)) { 124 | event_t event = bitRead(banks[bank].bottom, key) ? KEY_RELEASED : KEY_DOWN; 125 | trigger(&keys[bank * 8 + key], event); 126 | } 127 | } 128 | } 129 | 130 | } 131 | 132 | } 133 | 134 | void loop() { } // Idle 135 | -------------------------------------------------------------------------------- /Keyboard/types.h: -------------------------------------------------------------------------------- 1 | // Possible key states 2 | typedef enum { 3 | KEY_IS_UP, 4 | KEY_IS_DOWN, 5 | KEY_IS_GOING_UP, // We increment the timer in this state 6 | KEY_IS_GOING_DOWN, // We increment the timer in this state 7 | } state_t; 8 | 9 | // Possible events 10 | typedef enum { 11 | KEY_PRESSED, // Key is pressed 12 | KEY_DOWN, // Key reached bottom 13 | KEY_RELEASED, // Key was released 14 | KEY_UP, // Key reached top 15 | } event_t; 16 | 17 | typedef struct { 18 | char midi_note:8; 19 | state_t state:4; // Bit fields 20 | unsigned int t :7; // Lines up nicely to 16bits, t overflows at 4096 21 | } key_t; 22 | 23 | typedef struct { 24 | char top; 25 | char bottom; 26 | } bank_t; 27 | 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Albin Stigo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Update 2017-10-14 2 | 3 | I can't believe it's already been four years since I published this! 4 | 5 | This repo has generated considerable interest and I regularly receive 6 | email about it. **However, I do not longer actively work on this 7 | project**. I'm not able to help you with your keyboard project. 8 | 9 | 10 | I have stopped using the Teensy for my keyboard and switched to 11 | the [ucapps](http://www.ucapps.de/) project. Might publish something 12 | about that in the future. 13 | 14 | ## Other keyboard project that I know about 15 | 16 | These might be helpful. 17 | 18 | * https://bitbucket.org/ulph/fatar88lux 19 | * http://www.ucapps.de/ 20 | 21 | 22 | # Arduino clone midi keyboard 23 | 24 | A Teensy (Arduino clone) powered velocity sensitive USB MIDI keyboard 25 | with sustain pedal. Works with diode matrix keybeds. I used one from 26 | Fatar (also known as Studiologic). 27 | 28 | Using the teensy USB MIDI library it works as a class compliant USB 29 | MIDI device. 30 | 31 | I have only included the source and a very basic schematic here. Feel 32 | free to contact me if you have any questions. 33 | 34 | ## Circuit 35 | 36 | As you can see on 37 | the 38 | [keyboard matrix schematic](http://www.doepfer.de/DIY/Matrix_88.gif), 39 | the 88 keys are arranged in to two diode matrices. There are actually 40 | 176 switches in the matrix. The reason for this is that there are two 41 | switches under each key. When a key is pressed, one of the switches 42 | closes slightly before the other one. By measuring the time of flight 43 | between the switches we can derive the velocity. 44 | 45 | I've included a rudimentary schematic how I use 3 74ls138 46 | demultiplexers to address the rows. With this arrangement I use 8 + 5 47 | pins to scan the keybed. 48 | 49 | 50 | ## TODO / Bugs 51 | 52 | 53 | This is a bit of a hack but it works quite well for now. 54 | 55 | If you have any insights to these questions please let me know! 56 | 57 | * Is a scanning frequency of 1000Hz too low? I've heard somewhere 58 | commercial keyboard use 2000Hz. Perhaps someone can measure one with 59 | a scope? 60 | * Right now the velocity response is linear and quite low 61 | resolution. What is a good velocity response curve? 62 | * Is it a bad idea to join the two matrices? Perhaps it's a better 63 | idea to use a separate micro controller for each matrix and let them 64 | talk together using i2c? This approach would be more modular and 65 | allow for stacking a lot of keys (in an organ i.e..) 66 | * I'm using the Teensy USB MIDI library. I'm not sure how this 67 | interferes with the timing of the scanning interrupt routine. 68 | 69 | 70 | 71 | ## Resources 72 | 73 | 74 | * [Keyboard matrix schematic](http://www.doepfer.de/DIY/Matrix_88.gif) 75 | 76 | * [Fatar](http://www.fatar.com/) (Keyboard manufacturer) 77 | 78 | * [Teensy](http://www.pjrc.com/teensy/) (Arduino clone) 79 | 80 | * [74ls138 datasheet](http://ecee.colorado.edu/~mcclurel/sn74ls138rev5.pdf) 81 | --------------------------------------------------------------------------------