commit 98b6060e7c9dc61d943cbbd51c7962f27951f217 Author: wolfv6 Date: Mon May 9 08:05:08 2016 -0600 initial commit keybrd version 0.3.0 diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..c8cb812 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Wolfram Volpi + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..f0ae1f3 --- /dev/null +++ b/README.md @@ -0,0 +1,44 @@ +keybrd library for creating custom-keyboard firmware +==================================================== +keybrd library is an open source library for creating custom-keyboard firmware. +The resulting keyboard firmware is compatible with standard USB keyboard drivers. + +keybrd library can support any keyboard configuration: +* one-piece +* split with I/O expander +* single-layer +* multiple-layer + +Multiple-layer keyboards can write symbols without using the shift key: + + ~ ! @ # $ % ^ & * () _ {} | < > : ? + +keybrd library leverages the Arduino environment to create keyboard firmware. +The Arduino development environment is free, and easy for novice programmers to setup and learn. + +The keybrd library has been tested on the Teensy 2.0 microcontroller, MCP23018 I/O expander, and PCA9655E I/O expander. + +> The public API should not be considered stable. +> Currently the keybrd library is limited to 8x8 matrices, which is enough for compact split keyboards. + +Example minimal keybrd sketch +----------------------------- +Here is a [minimal keybrd sketch](keybrd_single-layer_2/keybrd_single-layer_2.ino). + todo after teensy LC bb, copy and remove annotations from keybrd_single-layer_2_annotated.ino +The sketch has about 50 lines of code and runs a 4-key keyboard. +It runs on a breadboard with rows, columns, and diodes just like the big keyboards. +The sketch is small because the keybrd library takes care of the low-level details. + +The keybrd tutorial 1 shows how to make a breadboard keyboard. +The keybrd tutorials 2 and 3 show how to create custom keybrd firmware. + +Example complex keybrd sketch +----------------------------- +The DodoHand keybrd emulates the DataHand keyboard. +It has 72 keys, 4 layers, 2 sub-layers, 2 matrices, and is loaded with features. +The [DodoHand sketch](todo /sketch.cpp), and its instantiation files, contain about 800 lines of code. + +Support +------- +The [doc](doc) folder contains guides and tutorials. +Please ask a questions in [issues](https://github.com/wolfv6/Keybrd/issues) if something is not clear. diff --git a/doc/CHANGELOG.md b/doc/CHANGELOG.md new file mode 100644 index 0000000..7e425e3 --- /dev/null +++ b/doc/CHANGELOG.md @@ -0,0 +1,33 @@ +# Change Log +All notable changes to the keybrd project will be documented in this file. +This project adheres to [Semantic Versioning 2.0.0](http://semver.org/). + +Version 0.x.x is for initial development. The public API should not be considered stable. +Version 1.0.0 will be released when the public API is stable. + +## [Unreleased][unreleased] + +## [0.3.0] - 2016-05-09 +### Changed +* Restructured the project directory to conform to Arduino library manager specifications + * Moved keybrd_DH library extension (DodoHand) to its own repository + * Moved sketches to examples directory +* Replaced Key_Layered dependency on LayerManager with StateLayers class + +### Added +* tutorials + +## [0.2.0] - 2016-02-25 +### Added +* Port classes for micro-controllers and I/O expanders +* DH_2565 sketch with DataHand layout +* Sticky mouse button (SMB) for DataHand layout +* Supporting documentation + +## [0.1.0] - 2015-02-10 +### Added +* The library runs on Teensy 2.0 microcontroller and MCP23018 I/O expander +* Limited to 8x8 matrix, which is enough for compact or split keyboards +* First draft of supporting documentation +* Example keybrd sketches for single-layer, multi-layer, and DataHand layer schemes + diff --git a/doc/Teensy2_microcontroller.txt b/doc/Teensy2_microcontroller.txt new file mode 100644 index 0000000..9f61675 --- /dev/null +++ b/doc/Teensy2_microcontroller.txt @@ -0,0 +1,37 @@ + +Teensy 2.0 Pinout Diagram + ------------------------- +USB is on top in the diagram. +Inner columns are pin numbers, outer columns are port+bit pin name. +``` +ground GND USB VCC +5v power + B0 0 21 F0 + B1 1 20 F1 + B2 2 19 F4 + B3 3 18 F5 + B7 4 17 F6 +SCL D0 5 16 F7 +SDA D1 6 15 B6 + D2 7 14 B5 + D3 8 13 B4 + C6 9 12 D7 + C7 10 11 D6 Do not use pin D6 for scanning keyboard matrix + LED on pin D6 pulls voltage down and will always return low + +BOTTOM (USB on top, left to right) +PIN# port+bit function +23 D5 + VCC 5v power + GND ground + RST reset +22 D4 + +MID (below USB, left to right) +PIN# port+bit function +24 E6 +Ref +``` + +Teensy pin out with port names is on http://www.pjrc.com/teensy/td_digital.html +Identifying and naming ports is useful when instantiating RowPorts and ColPorts. +Keybrd library was tested on Teensy 2.0 diff --git a/doc/astyle_cpp b/doc/astyle_cpp new file mode 100644 index 0000000..d9dfa13 --- /dev/null +++ b/doc/astyle_cpp @@ -0,0 +1,11 @@ +# this file specifies style for keybrd C++ and Arduino sketch .ino files +# Artistic Style is a console application for formatting C++ and Java source code +# http://sourceforge.net/projects/astyle/files/ download +# http://astyle.sourceforge.net/astyle.html manual + +--style=allman +--indent-classes +--keep-one-line-blocks +--indent=spaces=4 +--convert-tabs +--max-code-length=100 diff --git a/doc/keybrd_library_developer_guide.md b/doc/keybrd_library_developer_guide.md new file mode 100644 index 0000000..7475d97 --- /dev/null +++ b/doc/keybrd_library_developer_guide.md @@ -0,0 +1,189 @@ +keybrd Library Developer's Guide +================================ +This guide contains diagrams, naming conventions, and a style guide. + +This guide will help you design custom classes to interface with the keybrd library. +The most common need for custom classes are: +* Port classes for micro controller or I/O expanders +* custom layer schemes for multi-layer keyboards +* experimental features + +## Who this guide is for +This guide is for the maintainers, developers, and anyone that wants to extend the keybrd library. +It is assumed the reader is familiar with C++ language including pointers, objects, classes, static class variables, composition, inheritance, polymorphism, and enum. +Some classes use bit manipulation. + +## Custom keybrd classes +Please refer to tutorial_7a_using_someone_else's_keybrd_extension_library.md + +## Class diagrams + +Keybrd library class inheritance diagram +``` + Matrix + + Row + + IOExpanderPort + + _______ RowPort _______ + / | \ + RowPort_AVR RowPort_MCP23018 RowPort_PCA9655E (one RowPort class for each type of IC) + + _______ ColPort _______ + / | \ + ColPort_AVR ColPort_MCP23018 ColPort_PCA9655E (one ColPort class for each type of IC) + + _____ LED ______ + / | \ + LED_AVR LED_MCP23018 LED_PCA9655E (one LED class for each type of IC) + + + StateLayersInterface + | + StateLayers + + + Key __ + | \ + | Key_LayeredKeysArray + | + Code + |_____________________ + | \ \ + | Code_LayerLock Code_LayerHold + | + |___________________________ + | \ \ + | Code_LayeredScScBase Code_LayeredCodeScBase + | | | + | Code_LayeredScSc Code_LayeredCodeSc + | + |__________________________________________ + \ \ \ \ + Code_Sc Code_Shift Code_AutoShift Code_LockLED + / | \ + Code_ScS Code_ScNS Code_ScNS_00 + +``` + +## Association diagrams + +single-layer Keybrd association diagram with LEDs +``` + keybrd[1] + | + matrix[1..*] + | + row[1..*]_____________________________ + | \ \ \ + rowPort[1] rowPin[1] colPort[1] keys[1] + | | + colPins[1..*] code[1..*] + | + LED[1] + +``` + +multi-layer Keybrd association diagram with LEDs and I/O Expander +``` + + keybrd[1] + | + matrix[1..*] + | stateLayers[1..*] + row[1..*]_________________________________________/__ | \ + | \ \ \ / \ | \ + rowPort[1] rowPin[1] colPort[1] keys[1] / code_layer[1..*] LED[0..*] + \ / \ | / / + \ / colPins[1..*] key[1..*] / + \ / | / + \ / code[1..*] / + \ / ______________________________________/ + IOExpanderPort[0..*] + +``` + +## Class naming conventions +Class names start with upper case letter. +Most derived-class names start with the base class name followed by "_" and a name e.g. +``` + Code + | + Code_Sc + +``` +This convention leads to class names that convey information about the classes inheritance. +Underscore delineates base class name and sub name. Capital letters delineate words. + +## Style guide +Following the style guide makes it easier for the next programmer to understand your code. +* For class names, see above section "Class Naming Conventions" +* For member names, use camelCase starting with lowercase letter. +* Use constants rather than macros, except for header guards. +* For constant names that could be macros, use ALL_CAPS_AND_UNDERSCORE. + * **ITEM_COUNT** is a constant number of items. + * **itemCount** is a variable number of items. +* Use header guards CLASS_NAME_H. +* Prefix pointer name with "ptr" e.g. ptrRow = &row +* Name arrays using the plural of elements e.g. Row* const = ptrsRows { &row0, &row1 }; +* Pass arrays using array notation rather than pointer notation. Use +``` + void printArray(char[] array); + not + void printArray( char* array); +``` +* In constructor's initialization list, use same names for fields and constructor parameters +* Do not use new or malloc (to make memory leaks impossible). +* If class has any non-[POD](http://en.wikipedia.org/wiki/Plain_old_data_structure) data members, [do not inline constructors and destructors](http://www.chromium.org/developers/coding-style/cpp-dos-and-donts). +* Document class interface in .h file, above the class declaration. +* Code should be self-documenting. + The only comments should be things that may need clarification. + A simple function with a good name needs no comment. + http://stackoverflow.com/questions/2198241/best-practice-for-c-function-commenting +* Code is automatically formated before being pushed to the keybrd repository + The options file doc/astyle_cpp specifies the format: + * Allman style indentation + * indent 4 spaces + * replace tabs with spaces + * maximum code width of 100 columns + +## keybrd sketches +keybrd sketch naming convention is described in the keybrd_library_user_guide "Sample keybrd sketches". + +The head of sketch should contain latest version of keybrd lib that sketch was test on: + + tested on keybrd v1.1 by wolfv6 + +## trace of keybrd scan +Arduino does not have a debugger; so here is a list of functions in the order that they are called. +Refer to it like a table of contents while reading the keybrd library. + +``` +Keybrd::scan() for each matrix + Matrix::scan() for each row + Row::process() + Row::scan() + RowPort_*::setActivePin*() strobe row on + for each col port + ColPort_*::read() read col port + RowPort_*::setActivePin*() strobe row off + Row::getRowState() for each col port + for each connected col pin + if key is pressed + set rowState bit + Row::debounce() debounce + Row::detectEdge() detect edge + Row::pressRelease() for each key in row + if rising edge + Key_*::press() scanCode->press() + Code_*::press() Keyboard.press(scancode) +``` + +## The Arduino libraries +The keybrd libraries compile on the Arduino IDE and make extensive use of the following [Arduino libraries](https://www.arduino.cc/en/Reference/Libraries): + + #include + #include + #include + #include diff --git a/doc/keybrd_library_user_guide.md b/doc/keybrd_library_user_guide.md new file mode 100644 index 0000000..158fde1 --- /dev/null +++ b/doc/keybrd_library_user_guide.md @@ -0,0 +1,220 @@ +keybrd Library User's Guide +=========================== +keybrd is an open source library for creating custom-keyboard firmware. +The resulting keyboard firmware is compatible with standard USB keyboard drivers. + +This guide shows how to +* set up the Arduino development environment +* install the keybrd library +* compile and load keybrd firmware + +The Arduino development environment is free and simple as possible. +Its easy for novice programmers to setup and learn. + +## Who this guide is for +This guide is for anyone who wants to use the keybrd library to develop custom-keyboard firmware. + +A reader with programming experience, but no C++ experience, would understand the tutorials well enough to modify existing keybrd sketches. +An experienced C++ programmer would be able to write original sketches and classes from scratch. + +The library is written in the C++ language and uses pointers, objects, classes, static class variables, composition, inheritance, and enum. + +## Microcontroller board requirements +The keybrd library works with Teensy and Arduino boards. + +[Teensy LC](https://www.pjrc.com/teensy/teensyLC.html) has 8K RAM, which is more than enough memory for any keyboard. + +keybrd has been tested on the DodoHand keyboard with Teensy 2.0 and PCA9655E I/O expander using the DH_2565 sketch. + +Teensy LC is preferred over the older Teensy 2.0 for it's larger memory capacity and lower price. + +## Getting started with Teensy, Arduino IDE, and keybrd +The Arduino IDE is used to edit and compile sketches, and then load them on to the microcontroller. + +Teensyduino is a software add-on for the Arduino IDE that allows it to compile to Teensy. + +[Teensy Getting Started](http://www.pjrc.com/teensy/first_use.html) is a good way to familiarize yourself with Teensy. +[Arduino Development Environment](http://arduino.cc/en/guide/Environment) is a brief description. + +The following steps create an Arduino development environment for keybrd sketches. + +### Install Arduino IDE and Teensyduino +Follow the install steps are modified from the [Teensyduino download page](https://www.pjrc.com/teensy/td_download.html) + +For Linux: + +1. Download and extract the Arduino software. + Move the extracted directory to /opt: + + $ sudo mv ~/Downloads/arduino-1.6.7 /opt/arduino-1.6.7 + +2. The "Linux udev rules" link is at top right of page. + Save the teensy.rules file in /etc/udev/rules.d/ + +3. "Teensyduino Files" installer links are at top of page. + Download the installer to your Downloads directory. + Make the installer executable: + + $ chmod 755 teensyduino.64bit + + Run the teensyduino installer and fill the form fields: + + Arduino location to install Teensyduino: /usr/local/bin/arduino-1.x.x + Libraries to Install: None + +4. Launch Arduino IDE from /opt/arduino-1.x.x/arduino + +### Setup Arduino IDE for compiling keybrd firmware +From the Arduino IDE tool bar, select: +* Tools > Board > Teensy LC (or whatever board you are using) +* Tools > USB Type > Keyboard + Mouse + Joystick + +optional: +* File > Preferences > Compiler warnings: All +* File > Preferences > check: Use external editor + +A Sketchbook is a folder that the Arduino IDE uses to store sketches and libraries. +The default location for [Arduino libraries](https://www.arduino.cc/en/Guide/Libraries) is in + + ~/Documents/Arduino/libraries/ + +### Download and unpack keybrd-master.zip into your Arduino directory +todo update after testing Arduino library manager +link from tutorial 7 ## Publishing + https://www.arduino.cc/en/Guide/Libraries + > Installing Additional Arduino Libraries + > Using the Library Manager + +Down load keybrd-master.zip from the [Download ZIP](https://github.com/wolfv6/keybrd) button. + +Unpack keybrd-master.zip into your Arduino directory on your system (default location is ~/Documents/Arduino/). + +### keybrd library and keybrd extension libraries +todo update after testing Arduino library manager + +The keybrd library contains the foundation classes for creating a keyboard firmware. +For emphasis, it is sometimes referred to as the "core keybrd library". + +keybrd extension libraries contain additional classes that extend the keyboard library. +keybrd extension library names are prefixed by "keybrd_". + +The Arduino IDE looks for libraries in Arduino/libraries/. +For example, the DodoHand keyboard requires that the core keybrd library and the keybrd_DH extension library be installed: +* Arduino/libraries/keybrd/ +* Arduino/libraries/keybrd_DH/ + +A keybrd extension library allows classes to be shared by multiple sketches without polluting the core keybrd library with classes that few other keyboards can use. + +### Compile and load keybrd sketch +If it isn't already plugged in, plug the USB cable into the computer and controller. + +> CAUTION: It is possible to loose control of your keyboard when running a keybrd sketch. +> If the keybrd sketch has a mouse object, it is also possible to loose control of your mouse. +> USB keyboard protocol is capable of spewing characters and mouse commands at up to 500 per second. +> Take the following precautions before uploading an untested keybrd sketch to a controller: +> * Save all files and close dangerous applications. +> * Park the cursor in an editor opened to a test file. +> That way you can immediately see if the controller starts spewing characters. +> * Be prepared to turn off the controller: +> turn off Teensy Loader's green "Auto" button and push Teensy's reset button +> if that fails, unplug Teensy USB + +Compile and load workflow: + + 1. Open a keybrd sketch in the Arduino IDE (for example Arduino/keybrds/firmware/keybrd_single-layer/keybrd_single-layer_1221_bb/keybrd_single-layer_1221_bb.ino) + 2. Prepare for loosing control of keyboard and mouse. + 3. On the Arduino IDE, click the Upload button. + 4. The Teensy boot loader window opens, you might need to press and release the tiny pushbutton on the Teensy circuit board. + +## Example keybrd sketches +Example keybrd sketches are in the keybrd_proj/keybrd/examples/ directory. +Extension libraries have their example sketches similarly located. + +The example sketch names use the following conventions. + + keybrd_extension_feature_version.ino + +where +* **keybrd** indicates a keybrd sketch +* **extension** is the extension library name e.g. DH, DualMode +* **feature** is distinguishing feature of keybrd sketch e.g. breadboard, LED, sound, Dvorak +* **version** is version number + +The first two fields are mandatory, the remaining fields are optional. + +## Diode orientation +The physical martix rows and columns on a keyboard can be in any direction or shape. +[diode](https://en.wikipedia.org/wiki/Diode) orientation is specified in [Matrix.h](todo) + +![Diode](images/120px-Diode_pinout_en_fr.svg.png) + +Diagram is of typical through-the-hole diode in same alignment as diode symbol. +Cross bar and band depict the cathode. + +## Troubleshooting check list +The following is a listing of items to check when a new keybrd sketch or keyboard is having trouble. + +Development-environment items to check: +* If the keyboard has an I/O expander, power cycle (replug the USB) after loading the HEX file. +* If the keybrd extension library directory name or location was changed, see section +[Populate Arduino/libraries with keybrd library symlinks](todo link) +* If compile error: 'KEY_A' was not declared in this scope + From the Arduino IDE tool bar, select: Tools > USB Type > Keyboard + Mouse + Joystick + +Sketch items to check: +* For each row, number of keys in Row should equal number of colPort pins. +In this example, row_0 has six colPort pins in ptrsColPorts, and six keys in ptrsKeys_0: +``` + ColPort_AVR colPortB(DDRB, PORTB, PINB, 1<<0 | 1<<1 | 1<<2 | 1<<3 ); + ColPort_AVR colPortD(DDRD, PORTD, PIND, 1<<2 | 1<<3 ); + + ColPort* const ptrsColPorts[] = { &colPortB, &colPortD }; + const uint8_t COL_PORT_COUNT = sizeof(ptrsColPorts)/sizeof(*ptrsColPorts); + + const Key* const ptrsKeys_0[] = { &k_00, &k_01, &k_02, &k_03, &k_04, &k_05 }; + Row row_0(ptrsKeys_0, &rowPortF, 1<<0, ptrsColPorts, COL_PORT_COUNT); +``` +* Some of the constructors take array-element-count arguments, make sure that the correct counts are passed to the constructors. Or use sizeof() like this example: +``` + Row* const ptrsRows[] = { &row0, &row1, &row2, &row3 }; + const uint8_t ROW_COUNT = sizeof(ptrsRows)/sizeof(*ptrsRows); + Matrix matrix(ptrsRows, ROW_COUNT, 1); +``` +* For multi-layered keyboards, the number of codes in each Key_Layered should equal the number of layers. + +Hardware items to check: +* Connections +* Diode orientation +* 5 volts across power and ground +* To validate keyboard hardware, modify the simple single-layer keybrd sketch from the tutorial. + +## Keybrd nomenclature +**[scancode](http://en.wikipedia.org/wiki/Scancode)** - +Is a 16-bit integer assigned to a key position on a keyboard. +The keyboard sends a scancode to the computer for every key press and release. + +**[Layers](http://deskthority.net/wiki/Layer)** - +are key bindings provided by the keyboard firmware. +The standard [IBM PC keyboard](http://en.wikipedia.org/wiki/IBM_PC_keyboard) has one layer. +Many compact keyboards have an additional [Fn layer](http://en.wikipedia.org/wiki/Fn_key). +The [Neo layout](http://neo-layout.org/index_en.html) has 6 layers. + +**Layer code** - is an integer assigned to a layer. + +**Layer scheme** - is a system for changing layers while typing. + A single-layer scheme does not change layers. + +**Layout** - is a grid of keys. Key caps are often labeled to show a keyboard's layout. + +**[Matrix](http://pcbheaven.com/wikipages/How_Key_Matrices_Works/)** - is a collection of switches connected by rows and columns. + +**[bounce](http://en.wikipedia.org/wiki/Switch#Contact_bounce)** - +Keyboard switches are made of moving contacts. +When the contacts close, they bounce apart one or more times before making steady contact. +A debouncer removes the bounce so that a key press is sent to the computer only once. + +**[Modifier key](http://en.wikipedia.org/wiki/Modifier_key)** - is a special key on a computer keyboard that temporarily modifies the normal action of another key when pressed together. By themselves, modifier keys usually do nothing; that is, pressing any of the Shift, Alt, or Ctrl keys alone does not trigger any action from the computer. + +**Sketch** - is the name that Arduino uses for a program + +**keybrd sketch** - is an Arduino sketch that uses the keybrd library to define a keyboard firmware. diff --git a/doc/planned_features.md b/doc/planned_features.md new file mode 100644 index 0000000..73a7bd5 --- /dev/null +++ b/doc/planned_features.md @@ -0,0 +1,22 @@ +PLANNED FEATURES is a view of where the keybrd project is headed. + +Top priority +============ +Add interrupts for I2C + +Med priority +============ +Add support for Teensy LC micro controller + +Add 16x16 matrix capability (currently limited to 8x8 matrices) + +Low priority +============ +Add matrix-to-layout transform array (to decouple matrix from layout) + +Change tutorial sketches from teensy 2.0 and PCA9655E-D IOE to Teensy LC and MCP23018 IOE + +Add more tutorials: + tutorial_5_LEDs.md + tutorial_6_mapping_matrix_to_layout.md + tutorial_9_active_high.md diff --git a/examples/keybrd_mapping_bb/keybrd_mapping_bb.ino b/examples/keybrd_mapping_bb/keybrd_mapping_bb.ino new file mode 100644 index 0000000..761f069 --- /dev/null +++ b/examples/keybrd_mapping_bb/keybrd_mapping_bb.ino @@ -0,0 +1,112 @@ +/* keybrd_mapping_bb.ino + +Runs on DodoHand hardware or breadboard, using the left matrix, first two rows and columns. +Uses the same variable naming convention as DH_2565. + +| Layout | **0** | **1** | +|:------:|-------|-------| +| **0** | a ! | b @ | +| **1** | fn | shift | +*/ +// ################# GLOBAL #################### +// ================ INCLUDES =================== +//Arudino library files +#include + +//keybrd library files +//#include +#include +#include + +#include +#include +//#include +#include + +#include + +#include +#include + +#include +#include + +#include + +// ================= DEBUG ===================== +Debug debug; + +// =========== SPEED CONFUGURATIONS ============ +const unsigned int Row::DELAY_MICROSECONDS = 1000; + +// =============== LEFT PORTS ================== +RowPort_AVR_Optic rowPortF_L(DDRF, PORTF); + +ColPort_AVR colPortB_L(DDRB, PORTB, PINB, 1<<0 | 1<<1 ); + +ColPort* const ptrsColPorts_L[] = { &colPortB_L }; +const uint8_t COL_PORT_L_COUNT = sizeof(ptrsColPorts_L)/sizeof(*ptrsColPorts_L); + +// ================= CODES ===================== +// -------------- LAYER CODES ------------------ +StateLayers stateLayer; +//Code_LayerLock l_alpha(0, stateLayer); +Code_LayerHold l_fn(1, stateLayer); + +// --------------- SHIFT CODE ------------------ +Code_Shift s_shift(MODIFIERKEY_LEFT_SHIFT); +Code_Shift *const ptrsShift[] = { &s_shift }; +Code_Shift *const *const Code_AutoShift::ptrsShifts = ptrsShift; +const uint8_t Code_AutoShift::shiftCount = sizeof(ptrsShifts)/sizeof(*ptrsShifts); + +// --------------- SCAN CODES ------------------ +Code_Sc s_a(KEY_A); +Code_Sc s_b(KEY_B); +Code_ScS s_exclamation(KEY_1); +Code_ScS s_at(KEY_2); + +StateLayersInterface& Key_LayeredKeysArray::refStateLayers = stateLayer; + +// ============== LEFT MATRIX ================== +// --------------- LEFT KEYS ------------------- +Key* const ptrsCodes_L00[] = { &s_a, &s_exclamation }; +Key_LayeredKeysArray k_L00(ptrsCodes_L00); + +Key* const ptrsCodes_L01[] = { &s_b, &s_at }; +Key_LayeredKeysArray k_L01(ptrsCodes_L01); + +// -------------- LEFT MAPPING ----------------- +// the mapping layout array consumes no additional SRAM +/* +Key* const ptrsLayout[2][2] = { { &k_L00, &k_L01 }, + { &l_fn, &s_shift } }; +*/ +Key* const ptrsLayout[2][2] = { { &k_L01, &k_L00 }, //swapped keys a-b + { &l_fn, &s_shift } }; + +// --------------- LEFT ROWS ------------------- +Key* const ptrsKeys_L0[] = { ptrsLayout[0][0], ptrsLayout[0][1] }; +Row row_L0(rowPortF_L, 1<<0, ptrsColPorts_L, COL_PORT_L_COUNT, ptrsKeys_L0); + +Key* const ptrsKeys_L1[] = { ptrsLayout[1][0], ptrsLayout[1][1] }; +Row row_L1(rowPortF_L, 1<<1, ptrsColPorts_L, COL_PORT_L_COUNT, ptrsKeys_L1); + +// -------------- LEFT MATRIX ------------------ +Row* const ptrsRows_L[] = { &row_L0, &row_L1 }; +const uint8_t ROW_L_COUNT = sizeof(ptrsRows_L)/sizeof(*ptrsRows_L); + +Matrix matrix_L(ptrsRows_L, ROW_L_COUNT, 1); + +// ################## MAIN ##################### +void setup() +{ + Keyboard.begin(); + delay(1000); //time for OS to detect USB before printing + Keyboard.print(F("keybrd_mapping_bb.ino, ")); + debug.print_free_RAM(); +} + +void loop() +{ + matrix_L.scan(); +} diff --git a/library.properties b/library.properties new file mode 100644 index 0000000..08978de --- /dev/null +++ b/library.properties @@ -0,0 +1,9 @@ +name=keybrd +version=0.3.0 +author=Wolfram Volpi +maintainer=Wolfram Volpi +sentence=A library for creating custom-keyboard firmware. +paragraph=
Create keyboards with any configuration:
one-piece, split with I/O expander, single-layer, multiple-layer +category=Device Control +url=https://github.com/wolfv6/keybrd +architectures=avr diff --git a/src/Code.h b/src/Code.h new file mode 100644 index 0000000..f543ac0 --- /dev/null +++ b/src/Code.h @@ -0,0 +1,11 @@ +#ifndef CODE_H +#define CODE_H +#include "Key.h" + +/* Code is an interface class +It's derived concrete classes send press and release USB scancodes to the computer. +*/ +class Code : public Key +{ +}; +#endif diff --git a/src/Code_AutoShift.cpp b/src/Code_AutoShift.cpp new file mode 100644 index 0000000..ff3714b --- /dev/null +++ b/src/Code_AutoShift.cpp @@ -0,0 +1,30 @@ +#include "Code_AutoShift.h" + +bool Code_AutoShift::isShifted() const +{ + //if a shift key is pressed return true else return false + for (uint8_t i = 0; i < shiftCount; i++) + { + if (ptrsShifts[i]->isPressed()) + { + return true; + } + } + return false; +} + +void Code_AutoShift::releaseAllShifts() const +{ + for (uint8_t i = 0; i < shiftCount; i++) + { + ptrsShifts[i]->unpress(); + } +} + +void Code_AutoShift::restoreAllShifts() const +{ + for (uint8_t i = 0; i < shiftCount; i++) + { + ptrsShifts[i]->restorePressed(); + } +} diff --git a/src/Code_AutoShift.h b/src/Code_AutoShift.h new file mode 100644 index 0000000..1706ebe --- /dev/null +++ b/src/Code_AutoShift.h @@ -0,0 +1,38 @@ +#ifndef CODE_AUTOSHIFT_H +#define CODE_AUTOSHIFT_H +#include "Code.h" +#include "Code_Shift.h" + +/* Code_AutoShift is an abstract base class for Codes that depend on automatic shifting. +Code_AutoShift can manage one or more shift keys. + +Example initialization: + const Code_Shift s_shift(MODIFIERKEY_LEFT_SHIFT); + const Code_Shift *const ptrsS[] = { &s_shift }; + const Code_Shift *const *const Code_AutoShift::ptrsShifts = ptrsS; + const uint8_t Code_AutoShift::shiftCount = sizeof(ptrsShifts)/sizeof(*ptrsShifts); +The two Code_Shift pointer arrays (ptrsShifts and ptrsS) must have distinct names. + +Automatic shifting is usful on multi-layered keyboards. +The shift state for Code_ScS and Code_ScNS are changed and restored: + Code_ScS object is a scancode shifted e.g. '%' in symbols layer + Code_ScNS object is a scancode not shifted e.g. '5' in numbers layer + +keyboards without Code_ScS and Code_ScNS can omit ptrsShifts[] array and +and place scancode MODIFIERKEY_LEFT_SHIFT directly in Code_Sc: + Code_Sc s_shift(MODIFIERKEY_LEFT_SHIFT); +*/ +class Code_AutoShift : public Code +{ + private: + static Code_Shift *const *const ptrsShifts; //array of Code_Shift pointers + static const uint8_t shiftCount; + protected: + bool isShifted() const; + void releaseAllShifts() const; + void restoreAllShifts() const; + public: + virtual void press()=0; + virtual void release()=0; +}; +#endif diff --git a/src/Code_LEDLock.cpp b/src/Code_LEDLock.cpp new file mode 100644 index 0000000..682dba4 --- /dev/null +++ b/src/Code_LEDLock.cpp @@ -0,0 +1,68 @@ +#include "Code_LEDLock.h" + +/* USB_LED_bit are codes from http://www.usb.org/developers/hidpage/HID1_11.pdf keyboard output report +*/ +Code_LEDLock::Code_LEDLock(const uint16_t scancode, LED& refLED) + : scancode(scancode), refLED(refLED) +{ + switch (scancode) //initilize USB_LED_bit for given scancode + { + case KEY_NUM_LOCK: + USB_LED_bit = 1<<0; + break; + case KEY_CAPS_LOCK: + USB_LED_bit = 1<<1; + break; + case KEY_SCROLL_LOCK: + USB_LED_bit = 1<<2; + break; + /* guessing at these case names: + case KEY_COMPOSE: //for separate accent keys + USB_LED_bit = 1<<3; break; + break; + case KEY_KANA: //for Japanese keyboards + USB_LED_bit = 1<<4; break; + break; + */ + } +} + +void Code_LEDLock::press() +{ + Keyboard.press(scancode); + updateLED(); +} + +void Code_LEDLock::release() +{ + Keyboard.release(scancode); +} + +/* updateLED() is a separate function from press() because Arduino boards may need a different implementation. +updateLED() has been tested on teensy 2.0. + The variable "keyboard_leds" is in /opt/arduino-1.6.7/hardware/teensy/avr/cores/usb_hid/usb.c + // 1=num lock, 2=caps lock, 4=scroll lock, 8=compose, 16=kana + https://forum.pjrc.com/threads/25368-How-do-I-receive-a-numlock-capslock-LED-signal-from-the-PC +updateLED() has NOT been tested on an Arduino board. + The word "keyboard_leds does not appear in "Arduino\hardware\arduino\cores\ + This shows how to hack KeyReport in Arduino: https://www.sparkfun.com/tutorials/337 +TMK firmware uses variable "usb_led" instead of "keyboard_leds" + http://deskthority.net/workshop-f7/how-to-build-your-very-own-keyboard-firmware-t7177.html >usb_led +*/ +void Code_LEDLock::updateLED() const +{ +/* KEY_SCROLL_LOCK is not working on Teensy2.0, it prints keyboard_leds=0, maybe Linux doesn't have it. +Here is the debug code: +Keyboard.print(F(" keyboard_leds=")); +Keyboard.print(keyboard_leds);//KEY_NUM_LOCK:1, KEY_CAPS_LOCK:2, KEY_SCROLL_LOCK:0 +Keyboard.print(" "); +*/ + if (keyboard_leds & USB_LED_bit) //if LED status bit is set + { + refLED.off(); //LED on/off seem inverted, but it works + } + else + { + refLED.on(); + } +} diff --git a/src/Code_LEDLock.h b/src/Code_LEDLock.h new file mode 100644 index 0000000..9187cc2 --- /dev/null +++ b/src/Code_LEDLock.h @@ -0,0 +1,32 @@ +#ifndef CODE_LEDLOCK_H +#define CODE_LEDLOCK_H +#include +#include +#include +#include + +extern volatile uint8_t keyboard_leds; + +/* Class Code_LEDLock turns LED on and off +scancode is KEY_CAPS_LOCK, KEY_SCROLL_LOCK, or KEY_NUM_LOCK + +In keybrd sketch, ports should be instantiated before Code_LEDLock is instantiated +because LED.off() needs ports to be configured by port constructor. + +If a key does not have an LED indictor light, use Code_S instead e.g.: + Code_S CapsLck(KEY_CAPS_LOCK); +*/ +class Code_LEDLock : public Code +{ + private: + const uint16_t scancode; + uint8_t USB_LED_bit; //codes used by keyboard output report + LED& refLED; + void updateLED() const; + + public: + Code_LEDLock(const uint16_t scancode, LED& refLED); + virtual void press(); + virtual void release(); +}; +#endif diff --git a/src/Code_LayerHold.cpp b/src/Code_LayerHold.cpp new file mode 100644 index 0000000..1923dbf --- /dev/null +++ b/src/Code_LayerHold.cpp @@ -0,0 +1,11 @@ +#include "Code_LayerHold.h" + +void Code_LayerHold::press() +{ + refStateLayers.hold(layer); +} + +void Code_LayerHold::release() +{ + refStateLayers.unhold(layer); +} diff --git a/src/Code_LayerHold.h b/src/Code_LayerHold.h new file mode 100644 index 0000000..69d4366 --- /dev/null +++ b/src/Code_LayerHold.h @@ -0,0 +1,21 @@ +#ifndef CODE_LAYERHOLD_H +#define CODE_LAYERHOLD_H + +#include +#include +#include "StateLayers.h" + +/* Code_LayerHold calls StateLayers when pressed to change activeLayer. +*/ +class Code_LayerHold : public Code +{ + private: + const uint8_t layer; + StateLayers& refStateLayers; + public: + Code_LayerHold(const uint8_t layer, StateLayers& refStateLayers) + : layer(layer), refStateLayers(refStateLayers) {} + virtual void press(); + virtual void release(); +}; +#endif diff --git a/src/Code_LayerLock.cpp b/src/Code_LayerLock.cpp new file mode 100644 index 0000000..d250768 --- /dev/null +++ b/src/Code_LayerLock.cpp @@ -0,0 +1,10 @@ +#include "Code_LayerLock.h" + +void Code_LayerLock::press() +{ + refStateLayers.lock(layer); +} + +void Code_LayerLock::release() +{ +} diff --git a/src/Code_LayerLock.h b/src/Code_LayerLock.h new file mode 100644 index 0000000..2605328 --- /dev/null +++ b/src/Code_LayerLock.h @@ -0,0 +1,21 @@ +#ifndef CODE_LAYERLOCK_H +#define CODE_LAYERLOCK_H + +#include +#include +#include "StateLayers.h" + +/* Code_LayerLock calls StateLayers when pressed to change activeLayer. +*/ +class Code_LayerLock : public Code +{ + private: + const uint8_t layer; + StateLayers& refStateLayers; + public: + Code_LayerLock(const uint8_t layer, StateLayers& refStateLayers) + : layer(layer), refStateLayers(refStateLayers) {} + virtual void press(); + virtual void release(); +}; +#endif diff --git a/src/Code_LayeredCodeSc.cpp b/src/Code_LayeredCodeSc.cpp new file mode 100644 index 0000000..cb03a09 --- /dev/null +++ b/src/Code_LayeredCodeSc.cpp @@ -0,0 +1,7 @@ +#include "Code_LayeredCodeSc.h" + +void Code_LayeredCodeSc::press() +{ + layer = refStateLayers.getActiveLayer(); + pressCode(); +} diff --git a/src/Code_LayeredCodeSc.h b/src/Code_LayeredCodeSc.h new file mode 100644 index 0000000..1773a5c --- /dev/null +++ b/src/Code_LayeredCodeSc.h @@ -0,0 +1,23 @@ +#ifndef CODE_LAYEREDCODESC_H +#define CODE_LAYEREDCODESC_H +#include +#include +#include +#include + +/* Class Code_LayeredCodeSc is a 2-layer code, one object for each layer e.g. + layer0: ms_up //mouse up + layer1: KEY_UP //up arrow +When the key is pressed, the active layer is retrieved from refStateLayers, + and the object for the active layer is sent to USB. +*/ +class Code_LayeredCodeSc : public Code_LayeredCodeScBase +{ + private: + static StateLayersInterface& refStateLayers; + public: + Code_LayeredCodeSc(Code& refCode0, const uint16_t scancode1) + : Code_LayeredCodeScBase(refCode0, scancode1, 0) { } + virtual void press(); +}; +#endif diff --git a/src/Code_LayeredCodeScBase.cpp b/src/Code_LayeredCodeScBase.cpp new file mode 100644 index 0000000..2fa300a --- /dev/null +++ b/src/Code_LayeredCodeScBase.cpp @@ -0,0 +1,25 @@ +#include "Code_LayeredCodeScBase.h" + +void Code_LayeredCodeScBase::pressCode() +{ + if (layer) + { + Keyboard.press(scancode1); + } + else + { + refCode0.press(); + } +} + +void Code_LayeredCodeScBase::release() +{ + if (layer) + { + Keyboard.release(scancode1); + } + else + { + refCode0.release(); + } +} diff --git a/src/Code_LayeredCodeScBase.h b/src/Code_LayeredCodeScBase.h new file mode 100644 index 0000000..9faf6d8 --- /dev/null +++ b/src/Code_LayeredCodeScBase.h @@ -0,0 +1,27 @@ +#ifndef CODE_LAYEREDCODESCBASE_H +#define CODE_LAYEREDCODESCBASE_H +#include +#include +#include "Code.h" + +/* Class Code_LayeredCodeScBase is a 2-layer code, one object for each layer e.g. + layer0: ms_up //mouse up + layer1: KEY_UP //up arrow +When the key is pressed, the active layer is retrieved from refStateLayers, + and the object for the active layer is sent to USB. +*/ +class Code_LayeredCodeScBase : public Code +{ + private: + Code& refCode0; + const uint16_t scancode1; + protected: + bool layer; + public: + Code_LayeredCodeScBase(Code& refCode0, const uint16_t scancode1, uint8_t layer): + refCode0(refCode0), scancode1(scancode1), layer(layer) { } + virtual void press()=0; + virtual void release(); + virtual void pressCode(); +}; +#endif diff --git a/src/Code_LayeredScSc.cpp b/src/Code_LayeredScSc.cpp new file mode 100644 index 0000000..8ab1fcd --- /dev/null +++ b/src/Code_LayeredScSc.cpp @@ -0,0 +1,7 @@ +#include "Code_LayeredScSc.h" + +void Code_LayeredScSc::press() +{ + layer = refStateLayers.getActiveLayer(); + pressScancode(); +} diff --git a/src/Code_LayeredScSc.h b/src/Code_LayeredScSc.h new file mode 100644 index 0000000..f4ee53a --- /dev/null +++ b/src/Code_LayeredScSc.h @@ -0,0 +1,22 @@ +#ifndef CODE_LAYEREDSCSC_H +#define CODE_LAYEREDSCSC_H +#include +#include +#include +#include + +/* Class Code_LayeredScSc is composed of two scancodes; "S" stands for Scancode. +layer is retreived from refStateLayers. + when layer=0, press sends scancode0 + when layer=1, press sends scancode1 +*/ +class Code_LayeredScSc : public Code_LayeredScScBase +{ + private: + static StateLayersInterface& refStateLayers; + public: + Code_LayeredScSc(const uint16_t scancode0, const uint16_t scancode1) + : Code_LayeredScScBase(scancode0, scancode1) { } + virtual void press(); +}; +#endif diff --git a/src/Code_LayeredScScBase.cpp b/src/Code_LayeredScScBase.cpp new file mode 100644 index 0000000..2ed5e04 --- /dev/null +++ b/src/Code_LayeredScScBase.cpp @@ -0,0 +1,20 @@ +#include "Code_LayeredScScBase.h" + +void Code_LayeredScScBase::pressScancode() +{ + if (layer) + { + scancode = scancode1; + } + else + { + scancode = scancode0; + } + + Keyboard.press(scancode); +} + +void Code_LayeredScScBase::release() +{ + Keyboard.release(scancode); +} diff --git a/src/Code_LayeredScScBase.h b/src/Code_LayeredScScBase.h new file mode 100644 index 0000000..132609d --- /dev/null +++ b/src/Code_LayeredScScBase.h @@ -0,0 +1,26 @@ +#ifndef CODE_LAYERED2SCANCODES_H +#define CODE_LAYERED2SCANCODES_H +#include +#include +#include "Code.h" + +/* Class Code_LayeredScScBase is an abstract base class. It is composed of two scancodes: + if layer=0, send scancode0 + if layer=1, send scancode1 +*/ +class Code_LayeredScScBase : public Code +{ + private: + const uint16_t scancode0; + const uint16_t scancode1; + uint16_t scancode; + protected: + bool layer; //0 or 1 + public: + Code_LayeredScScBase(const uint16_t scancode0, const uint16_t scancode1): + scancode0(scancode0), scancode1(scancode1), layer(0) { } + virtual void press()=0; + virtual void release(); + void pressScancode(); +}; +#endif diff --git a/src/Code_Null.h b/src/Code_Null.h new file mode 100644 index 0000000..903355c --- /dev/null +++ b/src/Code_Null.h @@ -0,0 +1,14 @@ +#ifndef CODE_NULL_H +#define CODE_NULL_H +#include +#include + +/* Class Code_Null doesn't do anything. It is usefull for blank codes. +*/ +class Code_Null: public Code +{ + public: + virtual void press() {}; + virtual void release() {}; +}; +#endif diff --git a/src/Code_Sc.cpp b/src/Code_Sc.cpp new file mode 100644 index 0000000..5824c6b --- /dev/null +++ b/src/Code_Sc.cpp @@ -0,0 +1,11 @@ +#include "Code_Sc.h" + +void Code_Sc::press() +{ + Keyboard.press(scancode); +} + +void Code_Sc::release() +{ + Keyboard.release(scancode); +} diff --git a/src/Code_Sc.h b/src/Code_Sc.h new file mode 100644 index 0000000..cd571a7 --- /dev/null +++ b/src/Code_Sc.h @@ -0,0 +1,19 @@ +#ifndef CODE_SC_H +#define CODE_SC_H +#include +#include +#include + +/* Class Code_Sc is composed of one scancode, which it sends when press() or release() is called. +"S" stands for Scancode. +*/ +class Code_Sc : public Code +{ + private: + const uint16_t scancode; + public: + Code_Sc(const uint16_t scancode): scancode(scancode) { } + virtual void press(); + virtual void release(); +}; +#endif diff --git a/src/Code_ScNS.cpp b/src/Code_ScNS.cpp new file mode 100644 index 0000000..27c963a --- /dev/null +++ b/src/Code_ScNS.cpp @@ -0,0 +1,13 @@ +#include "Code_ScNS.h" + +void Code_ScNS::press() +{ + releaseAllShifts(); + Keyboard.press(scancode); + restoreAllShifts(); +} + +void Code_ScNS::release() +{ + Keyboard.release(scancode); +} diff --git a/src/Code_ScNS.h b/src/Code_ScNS.h new file mode 100644 index 0000000..3162ae5 --- /dev/null +++ b/src/Code_ScNS.h @@ -0,0 +1,23 @@ +#ifndef CODE_SCNS_H +#define CODE_SCNS_H +#include +#include +#include + +/* Class Code_ScNS is composed of one scancode, which it sends when press() or release() is called. +autoShift insures that all MODIFIERKEY_SHIFTs are released. +Letters will still print as capital if CapsLck is on. +"ScNS" stands for Scancode Not Shifted. +If scancode is a letter, CapsLck will invert the case. +Normally this is not a problem because most layer schemes control letter case by shift. +*/ +class Code_ScNS: public Code_AutoShift +{ + private: + const uint16_t scancode; + public: + Code_ScNS(const uint16_t scancode): scancode(scancode) { } + virtual void press(); + virtual void release(); +}; +#endif diff --git a/src/Code_ScS.cpp b/src/Code_ScS.cpp new file mode 100644 index 0000000..a0e2eef --- /dev/null +++ b/src/Code_ScS.cpp @@ -0,0 +1,20 @@ +#include "Code_ScS.h" + +void Code_ScS::press() +{ + if (isShifted()) + { + Keyboard.press(scancode); + } + else + { + Keyboard.press(MODIFIERKEY_LEFT_SHIFT); //temporarily shift + Keyboard.press(scancode); + Keyboard.release(MODIFIERKEY_LEFT_SHIFT); //restore + } +} + +void Code_ScS::release() +{ + Keyboard.release(scancode); +} diff --git a/src/Code_ScS.h b/src/Code_ScS.h new file mode 100644 index 0000000..52ba356 --- /dev/null +++ b/src/Code_ScS.h @@ -0,0 +1,22 @@ +#ifndef CODE_ScS_H +#define CODE_ScS_H +#include +#include +#include + +/* Class Code_ScS contains one scancode, which it sends when press() or release() is called. +autoShift insures that MODIFIERKEY_LEFT_SHIFT is pressed. +"SS" stands for Scancode Shifted. +If scancode is a letter, CapsLck will invert the case. +Normally this is not a problem because most layer schemes control letter case by shift. +*/ +class Code_ScS: public Code_AutoShift +{ + private: + const uint16_t scancode; + public: + Code_ScS(const uint16_t scancode): scancode(scancode) { } + virtual void press(); + virtual void release(); +}; +#endif diff --git a/src/Code_Shift.cpp b/src/Code_Shift.cpp new file mode 100644 index 0000000..33d19a8 --- /dev/null +++ b/src/Code_Shift.cpp @@ -0,0 +1,34 @@ +#include "Code_Shift.h" + +void Code_Shift::press() +{ + Keyboard.press(scancode); + pressed = true; +} + +void Code_Shift::release() +{ + Keyboard.release(scancode); + pressed = false; +} + +bool Code_Shift::isPressed() +{ + return pressed; +} + +void Code_Shift::unpress() +{ + if (pressed) + { + Keyboard.release(scancode); + } +} + +void Code_Shift::restorePressed() +{ + if (pressed) + { + Keyboard.press(scancode); + } +} diff --git a/src/Code_Shift.h b/src/Code_Shift.h new file mode 100644 index 0000000..6280169 --- /dev/null +++ b/src/Code_Shift.h @@ -0,0 +1,23 @@ +#ifndef CODE_SHIFT_H +#define CODE_SHIFT_H +#include +#include +#include + +/* Class Code_Shift sends shift scancode for multi-layered keybrds. +Explanation in Code_AutoShift.h +*/ +class Code_Shift : public Code +{ + private: + bool pressed; //state of physical shift key, false means released + const uint16_t scancode; //MODIFIERKEY_LEFT_SHIFT or MODIFIERKEY_RIGHT_SHIFT + public: + Code_Shift(const uint16_t scancode) : pressed(false), scancode(scancode) {} + virtual void press(); + virtual void release(); + bool isPressed(); + void unpress(); + void restorePressed(); +}; +#endif diff --git a/src/ColPort.cpp b/src/ColPort.cpp new file mode 100644 index 0000000..015406e --- /dev/null +++ b/src/ColPort.cpp @@ -0,0 +1,11 @@ +#include "ColPort.h" + +uint8_t ColPort::getColPins() +{ + return colPins; +} + +uint8_t ColPort::getPortState() +{ + return portState; +} diff --git a/src/ColPort.h b/src/ColPort.h new file mode 100644 index 0000000..afa0043 --- /dev/null +++ b/src/ColPort.h @@ -0,0 +1,23 @@ +#ifndef COLPORT_H +#define COLPORT_H +#include +#include + +/* +ColPort is an abstract base class. +Port classes are the keybrd library's interface to microcontoller ports or I/O expander ports. +*/ +class ColPort +{ + protected: + const uint8_t colPins; //bitwise pin configuration, 1 means read column + uint8_t portState; //bitwise pin values, which is set in read() + public: + ColPort(const uint8_t colPins): colPins(colPins), portState(0) {} + + //read port and store it's pins values in portState + virtual void read()=0; + uint8_t getColPins(); + uint8_t getPortState(); +}; +#endif diff --git a/src/ColPort_AVR.cpp b/src/ColPort_AVR.cpp new file mode 100644 index 0000000..0d2d964 --- /dev/null +++ b/src/ColPort_AVR.cpp @@ -0,0 +1,17 @@ +#include "ColPort_AVR.h" + +/* +configures column port's DDRx and PORTx. +*/ +ColPort_AVR::ColPort_AVR(volatile unsigned char& DDRx, volatile unsigned char& PORTx, + volatile unsigned char& PINx, const uint8_t colPins) + : ColPort(colPins), DDR(DDRx = ~colPins), PORT(PORTx = colPins), PIN(PINx) +{} + +/* +Saves all port-pin values to portState. +*/ +void ColPort_AVR::read() +{ + portState = PIN; +} diff --git a/src/ColPort_AVR.h b/src/ColPort_AVR.h new file mode 100644 index 0000000..7269154 --- /dev/null +++ b/src/ColPort_AVR.h @@ -0,0 +1,41 @@ +#ifndef COLPORT_AVR_H +#define COLPORT_AVR_H +#include +#include +#include + +/* One AVR microcontroller port connected to matrix columns. + +Instantiation + ------------ +The constructor configures column's DDRx and PORTx. +The 'x' in parameters DDRx, PORTx, and PINx should all be the same letter. + +colPins is port's bitwise pin configuration + 1=configure as input (for pins connected to column) + 0=configure as output (for LED or not connected to a column) + +Example instantiation on column port B, with pins 2 and 3 connected to columns: + ColPort_AVR colPortB(DDRB, PORTB, PINB, 1<<2 | 1<<3 ); + +colPins are read from pin 0 on up. + +Diode orientation + ---------------- +Rows, columns, and diode orientation are explained in Matrix.h +*/ +class ColPort_AVR : public ColPort +{ + private: + const volatile unsigned char& DDR; //Data Direction Register, Direction: 0=Input + const volatile unsigned char& PORT; //PORT register + const volatile unsigned char& PIN; //PIN read register + public: + //The constructor initialization list is in .cpp + ColPort_AVR(volatile unsigned char& DDRx, volatile unsigned char& PORTx, + volatile unsigned char& PINx, const uint8_t colPins); + + //read port and store result in portState + virtual void read(); +}; +#endif diff --git a/src/ColPort_MCP23018.cpp b/src/ColPort_MCP23018.cpp new file mode 100644 index 0000000..2b5e35d --- /dev/null +++ b/src/ColPort_MCP23018.cpp @@ -0,0 +1,41 @@ +#include "ColPort_MCP23018.h" + +/* +configures column port's IODIR, GPIO, and GPPU. +*/ +ColPort_MCP23018::ColPort_MCP23018(IOExpanderPort& port, const uint8_t colPins) + : ColPort(colPins), port(port), IODIR(port.num), GPIO(port.num + 0x12), GPPU(port.num + 0x0C) +{} + +void ColPort_MCP23018::begin() +{ +//Wire.begin() should only be called once https://www.arduino.cc/en/Reference/WireBegin +#ifndef WIRE_BEGIN +#define WIRE_BEGIN + Wire.begin(); +#endif + + Wire.beginTransmission(port.ADDR); + Wire.write(IODIR); + Wire.write(colPins); //0=configure as output (for LED), 1=configure as input (for read) + Wire.endTransmission(); + + Wire.beginTransmission(port.ADDR); + Wire.write(GPPU); + Wire.write(colPins); //0=pull-up disabled (for LED), 1=pull-up enabled (for read) + Wire.endTransmission(); +} + +/* +Saves all port-pin values to portState. +*/ +void ColPort_MCP23018::read() +{ + Wire.beginTransmission(port.ADDR); + Wire.write(GPIO); //GPIO immediately before requestFrom + Wire.endTransmission(); + + Wire.requestFrom(port.ADDR, 1u); //request one byte from input port + + portState = Wire.read(); +} diff --git a/src/ColPort_MCP23018.h b/src/ColPort_MCP23018.h new file mode 100644 index 0000000..9333e03 --- /dev/null +++ b/src/ColPort_MCP23018.h @@ -0,0 +1,46 @@ +#ifndef COLPORT_MCP23018_H +#define COLPORT_MCP23018_H +#include +#include +#include +#include +#include "IOExpanderPort.h" + +/* One MCP23018 I/O expander port connected to matrix columns. + +Instantiation + ------------ +colPins parameter is port's bitwise pin configuration + 1=configure as input (for read) + 0=configure as output (for LED or not connected to a column) + +example instantiation for column port A, with pins 2 and 3 connected to columnss: + IOExpanderPort portA(0, ~0); + ColPort_MCP23018 colPortA(portA, 1<<2 | 1<<3 ); +example instantiation for column port B, with pins 2 and 3 connected to columns: + IOExpanderPort portB(1, ~0); + ColPort_MCP23018 colPortB(portB, 1<<2 | 1<<3 ); + +colPins are read from pin 0 on up. + +Diode orientation + ---------------- +Rows, columns, and diode orientation are explained in Matrix.h + +*/ +class ColPort_MCP23018 : public ColPort +{ + private: + IOExpanderPort& port; + const uint8_t IODIR; //Input/Ouput Direction register + const uint8_t GPIO; //General Purpose Input/Ouput register + const uint8_t GPPU; //General Purpose Pullup register + public: + //The constructor initialization list is in .cpp + ColPort_MCP23018(IOExpanderPort& port, const uint8_t colPins); + void begin(); + + //read port and store result in portState + virtual void read(); +}; +#endif diff --git a/src/ColPort_PCA9655E.cpp b/src/ColPort_PCA9655E.cpp new file mode 100644 index 0000000..4fbb564 --- /dev/null +++ b/src/ColPort_PCA9655E.cpp @@ -0,0 +1,37 @@ +#include "ColPort_PCA9655E.h" + +/* +configures column port's configuration, input, and pins. +*/ +ColPort_PCA9655E::ColPort_PCA9655E +(IOExpanderPort& port, const uint8_t colPins) + : ColPort(colPins), port(port), configurationByteCommand(port.num + 6), inputByteCommand(port.num) +{} + +void ColPort_PCA9655E::begin() +{ +//Wire.begin() should only be called once https://www.arduino.cc/en/Reference/WireBegin +#ifndef WIRE_BEGIN +#define WIRE_BEGIN + Wire.begin(); +#endif + + Wire.beginTransmission(port.ADDR); + Wire.write(configurationByteCommand); + Wire.write(colPins); //0=configure as output (for LED), 1=configure as input (for read) + Wire.endTransmission(); +} + +/* +Saves all port-pin values to portState. +*/ +void ColPort_PCA9655E::read() +{ + Wire.beginTransmission(port.ADDR); + Wire.write(inputByteCommand); //input immediately before requestFrom + Wire.endTransmission(false); //PCA9655E needs false to send a restart + + Wire.requestFrom(port.ADDR, 1u); //request one byte from input port + + portState = Wire.read(); +} diff --git a/src/ColPort_PCA9655E.h b/src/ColPort_PCA9655E.h new file mode 100644 index 0000000..faa8a1b --- /dev/null +++ b/src/ColPort_PCA9655E.h @@ -0,0 +1,44 @@ +#ifndef COLPORT_PCA9655E_H +#define COLPORT_PCA9655E_H +#include +#include +#include +#include +#include "IOExpanderPort.h" + +/* One PCA9655E I/O expander port connected to matrix columns. + +Instantiation + ------------ +colPins parameter is port's bitwise pin configuration + 1=configure as input (for pins connected to column) + 0=configure as output (for LED or not connected to a column) + +Example instantiation for column port 0, with pins 2 and 3 connected to columns: + IOExpanderPort port0(0, 0); + ColPort_PCA9655E colPort0(port0, 2<<0 | 1<<3 ); +Example instantiation for column port 1, with pins 2 and 3 connected to columns: + IOExpanderPort port1(1, 0); + ColPort_PCA9655E colPort1(port1, 2<<0 | 1<<3 ); + +colPins are read from pin 0 on up. + +Diode orientation + ---------------- +Rows, columns, and diode orientation are explained in Matrix.h +*/ +class ColPort_PCA9655E : public ColPort +{ + private: + IOExpanderPort& port; + const uint8_t configurationByteCommand; + const uint8_t inputByteCommand; + public: + //The constructor initialization list is in .cpp + ColPort_PCA9655E(IOExpanderPort& port, const uint8_t colPins); + void begin(); + + //read port and store result in portState + virtual void read(); +}; +#endif diff --git a/src/Debug.cpp b/src/Debug.cpp new file mode 100644 index 0000000..e2e558b --- /dev/null +++ b/src/Debug.cpp @@ -0,0 +1,32 @@ +#include "Debug.h" +#include "getFreeSRAM.h" + +void Debug::print_free_RAM() +{ + delay(1000); //give OS time to find USB + Keyboard.print(F("Free SRAM = ")); + Keyboard.println( getFreeSRAM() ); +} + +void Debug::print_microseconds_per_scan() +{ + if (millis() >= nextTime) + { + Keyboard.print(1000000/scanCount); //print microseconds per scan + Keyboard.write(','); + scanCount = 0; + nextTime = millis() + 1000; //print every second + } + scanCount++; +} +void Debug::print_scans_per_second() +{ + if (millis() >= nextTime) + { + Keyboard.print(scanCount); //print scans per second + Keyboard.write(','); + scanCount = 0; + nextTime = millis() + 1000; //print every second + } + scanCount++; +} diff --git a/src/Debug.h b/src/Debug.h new file mode 100644 index 0000000..f047f12 --- /dev/null +++ b/src/Debug.h @@ -0,0 +1,16 @@ +#ifndef DEBUG_H +#define DEBUG_H +#include + +class Debug +{ + private: + unsigned long nextTime = 0; + unsigned int scanCount = 0; + + public: + void print_free_RAM(); //print free SRAM, call this from setup() + void print_microseconds_per_scan(); //print microseconds per scan every second + void print_scans_per_second(); //print scans per second every second +}; +#endif diff --git a/src/IOExpanderPort.h b/src/IOExpanderPort.h new file mode 100644 index 0000000..c85e616 --- /dev/null +++ b/src/IOExpanderPort.h @@ -0,0 +1,49 @@ +#ifndef IOEXPANDERPORT_H +#define IOEXPANDERPORT_H +#include + +/* The pins of an IC's port can be split between RowPort, ColPort, and LED. + +IOExpanderPort contains outputVal, the value of a port's output register. +outputVal is used for port manipulation by classes RowPort and LED. +One port's outputVal can be shared by one RowPort object and multiple LED objects. + +IOExpanderPort is only used by I/O expander port classes. +AVR port classes do not need a similar class because PORTx is global in the Arduino library. + +Instantiation + ------------ +Example IOExpanderPort::ADDR initilization: + const uint8_t IOExpanderPort::ADDR = 0x18; +Be careful with the ADDR. +Table 6 in PCA9655E datasheet lists 8-bit versions of I2C addresses. +The Arduino Wire library uses 7-bit addresses throughout, so drop the low bit. +For example, I2C address with AD2=GND AD1=SCL AD0=SCL, + Table 6 lists 8-bit ADDR = 0x30 (b 00110000) + while Arduino uses 7-bit ADDR = 0x18 (b 00011000) +http://playground.arduino.cc/Main/WireLibraryDetailedReference +The PCA9655E data sheet is on http://www.onsemi.com/pub_link/Collateral/PCA9655E-D.PDF + +portNumber: If the I/O expander uses port letters, use 0 instead of A, use 1 instead of B. +outputVal: For pins that are connected to active low rows, set outputVal bit to 1. + Set all other outputVal bits to 0. + +Example instantiation for port0 with active low rows on all pins: + IOExpanderPort port0(0, ~0); + +Example instantiation for portA with active low rows on pins 0,1,2: + IOExpanderPort portA(0, 1<<0 | 1<<1 | 1<<2 ); + +Example instantiation for portB with active high rows on pins 0,1,2: + IOExpanderPort portB(1, 0); +*/ +struct IOExpanderPort +{ + static const uint8_t ADDR; //I2C address + const uint8_t num; //port number + uint8_t outputVal; //bitwise value of output register + + IOExpanderPort(const uint8_t portNumber, uint8_t outputVal) + : num(portNumber), outputVal(outputVal) {} +}; +#endif diff --git a/src/Key.h b/src/Key.h new file mode 100644 index 0000000..2034ab6 --- /dev/null +++ b/src/Key.h @@ -0,0 +1,18 @@ +//header-guard name needs to be uinique +//arduino\hardware\teensy\avr\cores\teensy\keylayouts.h has #define KEY_H +//Keypad library has a header file Key.h +#ifndef KEY_H_keybrd +#define KEY_H_keybrd + +#include +#include + +/* Key is an interface class +*/ +class Key +{ + public: + virtual void press()=0; //send scancode to USB for press + virtual void release()=0; //send scancode to USB for release +}; +#endif diff --git a/src/Key_LayeredKeysArray.cpp b/src/Key_LayeredKeysArray.cpp new file mode 100644 index 0000000..1670f93 --- /dev/null +++ b/src/Key_LayeredKeysArray.cpp @@ -0,0 +1,13 @@ +#include "Key_LayeredKeysArray.h" + +void Key_LayeredKeysArray::press() +{ + layer = refStateLayers.getActiveLayer(); + + ptrsKeys[layer]->press(); +} + +void Key_LayeredKeysArray::release() +{ + ptrsKeys[layer]->release(); +} diff --git a/src/Key_LayeredKeysArray.h b/src/Key_LayeredKeysArray.h new file mode 100644 index 0000000..b2b26f4 --- /dev/null +++ b/src/Key_LayeredKeysArray.h @@ -0,0 +1,25 @@ +#ifndef KEY_LAYEREDKEYSARRAY_H +#define KEY_LAYEREDKEYSARRAY_H +#include +#include +#include +#include + +/* Class Key_LayeredKeysArray contains an array of Key pointers, one pointer per layer. +Codes are a kind of Key, so the Key pointers can point to Codes as well. + +When the key is pressed, active layer is retreived from refStateLayers and +the Key object of the active layer is called. +*/ +class Key_LayeredKeysArray : public Key +{ + private: + Key *const *const ptrsKeys; //array of Key pointers, one Key per layer + uint8_t layer; //active layer when key was pressed + static StateLayersInterface& refStateLayers; + public: + Key_LayeredKeysArray(Key *const ptrsKeys[]): ptrsKeys(ptrsKeys) {} + virtual void press(); + virtual void release(); +}; +#endif diff --git a/src/LED.h b/src/LED.h new file mode 100644 index 0000000..f08e826 --- /dev/null +++ b/src/LED.h @@ -0,0 +1,21 @@ +#ifndef LED_H +#define LED_H + +/* LED is an abstract base class +Each LED object is an IC pin that is used to power an LED on and off. + +Connect the LED in series with the resistor: + Calculate current-limiting-resistor value (100 Ohms to 10k Ohms will work with 5 volts) + R = (Vs - Vf) / If + http://www.digikey.com/en/resources/conversion-calculators/conversion-calculator-led-series-resistor + Connect the LED's anode (the longer lead) to the AVR output pin (+) + Connect the LED's cathode to ground (-) +Never connect a LED directly from ground to power. Doing so would destroy the LED. +*/ +class LED +{ + public: + virtual void on()=0; + virtual void off()=0; +}; +#endif diff --git a/src/LED_AVR.cpp b/src/LED_AVR.cpp new file mode 100644 index 0000000..3550f87 --- /dev/null +++ b/src/LED_AVR.cpp @@ -0,0 +1,11 @@ +#include "LED_AVR.h" + +void LED_AVR::on() +{ + PORT |= pin; //set pin high +} + +void LED_AVR::off() +{ + PORT &= ~pin; //set pin low +} diff --git a/src/LED_AVR.h b/src/LED_AVR.h new file mode 100644 index 0000000..815eef1 --- /dev/null +++ b/src/LED_AVR.h @@ -0,0 +1,23 @@ +#ifndef LED_AVR_H +#define LED_AVR_H +#include +#include +#include + +/* A LED_AVR object is an AVR pin that is used to power an LED on and off. +DDRx Data Direction Register is configured as output in RowPort_AVR constructor. +*/ +class LED_AVR: public LED +{ + private: + volatile unsigned char& PORT; + const uint8_t pin; //bitwise pin to LED + + public: + LED_AVR(volatile unsigned char& PORTx, const uint8_t pin): PORT(PORTx), pin(pin) {} + + virtual void on(); + + virtual void off(); +}; +#endif diff --git a/src/LED_MCP23018.cpp b/src/LED_MCP23018.cpp new file mode 100644 index 0000000..ec3ee5a --- /dev/null +++ b/src/LED_MCP23018.cpp @@ -0,0 +1,17 @@ +#include "LED_MCP23018.h" + +void LED_MCP23018::on() +{ + Wire.beginTransmission(port.ADDR); + Wire.write(GPIO); + Wire.write(port.outputVal &= ~pin); //set pin low (sink) + Wire.endTransmission(); +} + +void LED_MCP23018::off() +{ + Wire.beginTransmission(port.ADDR); + Wire.write(GPIO); + Wire.write(port.outputVal |= pin); //set pin high (sink off) + Wire.endTransmission(); +} diff --git a/src/LED_MCP23018.h b/src/LED_MCP23018.h new file mode 100644 index 0000000..81e137c --- /dev/null +++ b/src/LED_MCP23018.h @@ -0,0 +1,35 @@ +#ifndef LED_MCP23018_H +#define LED_MCP23018_H +#include +#include +#include +#include +#include "IOExpanderPort.h" + +/* Class LED_MCP23018 uses a MCP23018 I/O expander pin to turn a LED on and off. + +Connect the LED in series with the resistor: + determin resistor value needed (Internet search: LED resistor value) + Connect the LED's (-) ground to the AVR output pin + connect LED's (+) to power +Never connect a LED directly from ground to power. Doing so would destroy the LED. + +MCP23018 ouput is open drain. The output acts like a switch to ground. +It cannot produce a high signal by itself. +*/ +class LED_MCP23018: public LED +{ + private: + IOExpanderPort& port; + const uint8_t GPIO; //General Purpose Input/Ouput register address + const uint8_t pin; //bitwise pin to LED + + public: + LED_MCP23018(IOExpanderPort& port, const uint8_t pin) + : port(port), GPIO(port.num + 0x12), pin(pin) {} + + virtual void on(); + + virtual void off(); +}; +#endif diff --git a/src/LED_PCA9655E.cpp b/src/LED_PCA9655E.cpp new file mode 100644 index 0000000..d0a15d0 --- /dev/null +++ b/src/LED_PCA9655E.cpp @@ -0,0 +1,17 @@ +#include "LED_PCA9655E.h" + +void LED_PCA9655E::on() +{ + Wire.beginTransmission(port.ADDR); + Wire.write(outputByteCommand); + Wire.write(port.outputVal |= pin); //set pin high + Wire.endTransmission(); +} + +void LED_PCA9655E::off() +{ + Wire.beginTransmission(port.ADDR); + Wire.write(outputByteCommand); + Wire.write(port.outputVal &= ~pin); //set pin low + Wire.endTransmission(); +} diff --git a/src/LED_PCA9655E.h b/src/LED_PCA9655E.h new file mode 100644 index 0000000..5af460f --- /dev/null +++ b/src/LED_PCA9655E.h @@ -0,0 +1,26 @@ +#ifndef LED_PCA9655E_H +#define LED_PCA9655E_H +#include +#include +#include +#include +#include "IOExpanderPort.h" + +/* A LED_PCA9655E object is an PCA9655E pin that is connected to an LED indicator light. +Input/Ouput Direction configuration is set to ouput in row_Port_PCA9655E.begin() and col_Port_PCA9655E.begin(). +*/ +class LED_PCA9655E: public LED +{ + private: + IOExpanderPort& port; + const uint8_t outputByteCommand; //General Purpose Input/Ouput register address + const uint8_t pin; //bitwise pin to LED + + public: + LED_PCA9655E(IOExpanderPort& port, const uint8_t pin) + : port(port), outputByteCommand(port.num + 2), pin(pin) {} + + virtual void on(); + virtual void off(); +}; +#endif diff --git a/src/Matrix.cpp b/src/Matrix.cpp new file mode 100644 index 0000000..a508e09 --- /dev/null +++ b/src/Matrix.cpp @@ -0,0 +1,12 @@ +#include "Matrix.h" + +/* +scan every row of matrix one time +*/ +void Matrix::scan() +{ + for (uint8_t i=0; i < rowCount; i++) + { + ptrsRows[i]->process(activeHigh); + } +} diff --git a/src/Matrix.h b/src/Matrix.h new file mode 100644 index 0000000..2906aa9 --- /dev/null +++ b/src/Matrix.h @@ -0,0 +1,41 @@ +#ifndef MATRIX_H +#define MATRIX_H +#include +#include +#include "Row.h" + +/* +Diode orientation + ---------------- +A keyboard's physically matrix is composed of rows and columns. +The rows and columns are physically connected to the keys. +The rows and columns are distinguishable by diode orientation (not horizontal/vertical). + +For active low diode orientation is: + cathodes on rows + anodes on columns + +For active high diode orientation is reversed: + anodes on rows + cathodes on columns + +Pull-down resistors + ------------------- +If Matrix uses active low, IC requires one pull-up resistor on each ColPort::colPins. +If Matrix uses active high, IC requires one pull-down resistor on each ColPort::colPins. +External pull-down resistors should have a value between 10k Ohms and 2.2k Ohms. +*/ +class Matrix +{ + private: + Row *const *const ptrsRows; //array of row pointers + const uint8_t rowCount; + const bool activeHigh; //logic level of strobe pin: 0=activeLow, 1=activeHigh + + public: + Matrix( Row *const ptrsRows[], const uint8_t rowCount, const bool activeHigh) + : ptrsRows(ptrsRows), rowCount(rowCount), activeHigh(activeHigh) {} + + void scan(); +}; +#endif diff --git a/src/Row.cpp b/src/Row.cpp new file mode 100644 index 0000000..a70a72e --- /dev/null +++ b/src/Row.cpp @@ -0,0 +1,204 @@ +#include "Row.h" +/* +scans the row and calls any newly pressed or released keys. +*/ +void Row::process(const bool activeHigh) +{ + //these variables are all bitwise, one bit per key + uint8_t rowState; //1 means pressed, 0 means released + uint16_t rowEnd; //1 bit marks positioned after last key of row + uint8_t newDebounced; //1 means pressed, 0 means released + uint8_t isFallingEdge; //1 means falling edge + uint8_t isRisingEdge; //1 means rising edge + + scan(activeHigh); //save column-port-pin values to portState + rowState = getRowState(rowEnd, activeHigh); + newDebounced = debounce(rowState); + detectEdge(newDebounced, isFallingEdge, isRisingEdge); + pressRelease(rowEnd, isFallingEdge, isRisingEdge); +} + +/* +Strobes the row and reads the columns. +Strobe is on for shortest possible time to preserve IR LED on DodoHand's optic switch. +*/ +void Row::scan(const bool activeHigh) +{ + //strobe row on + if (activeHigh) + { + refRowPort.setActivePinHigh(rowPin); + } + else //activeLow + { + refRowPort.setActivePinLow(rowPin); + } + + //read all the column ports + for (uint8_t i=0; i < colPortCount; i++) + { + ptrsColPorts[i]->read(); + } + + //strobe row off + if (activeHigh) + { + refRowPort.setActivePinLow(rowPin); + } + else //activeLow + { + refRowPort.setActivePinHigh(rowPin); + } +} + +/* +Copies column pins to rowState. Unused column pins are not copied. +Sets rowEnd and returns rowState. +rowEnd is bitwise, where 1 bit corrsiponds to place immediatly after last key of row. +rowEnd and rowMask are larger type than portMask so that they can not overflow. +*/ +uint8_t Row::getRowState(uint16_t& rowEnd, const bool activeHigh) +{ + uint16_t rowMask = 1; //bitwise, one col per bit, active col bit is 1 + uint8_t rowState = 0; //bitwise, one key per bit, 1 means key is pressed + + for (uint8_t i=0; i < colPortCount; i++) //for each col port + { + //bitwise colPins, 1 means pin is connected to column + uint8_t colPins = ptrsColPorts[i]->getColPins(); + + //bitwise colPortState, pin values where set in ColPort::read(), get them now + uint8_t colPortState = ptrsColPorts[i]->getPortState(); + + if (activeHigh) + { + colPortState = ~colPortState; + } + + for ( uint8_t portMask = 1; portMask > 0; portMask <<= 1 ) //shift portMask until overflow + { //for each pin of col port + if (portMask & colPins) //if pin is connected to column + { + if (portMask & ~colPortState) //if pin detected a key press + { + rowState |= rowMask; //set rowState bit for that key + } + + rowMask <<= 1; //shift rowMask to next key + } + } + } + + rowEnd = rowMask; + + return rowState; +} + +/* +Parameter rowState is bitwise, 1 means pressed, 0 means released. +Returns debounced rowState. + +Debounce uses multiple samples to debounces switch states, +where each sample contains the switch states for a row of keys, one bit per switch. + +Debounce uses Marty's debounce algorithm from + http://drmarty.blogspot.com.br/2009/05/best-switch-debounce-routine-ever.html +I2C and TWI protocals do not include any Packet Error Checking (PEC). +The goal of Marty's debounce routine is to reject spurious signals, +which is useful when conecting split keyboards with a cable using I2C or TWI. +Was tested on split keyboard with 3-meter long telephone wire to I/O expander + +Marty's debounce algorithm: + Periodically read samples and update the state when a number consecutive sample bits are equal. + +samples[SAMPLE_COUNT] is a ring buffer and samplesIndex is it's current write index. +SAMPLE_COUNT is #defined in Row.h +SAMPLE_COUNT is the number of consecutive equal samples needed to debounce. +It is a macro because it is used to define array size of samples[SAMPLE_COUNT] in Row. +SAMPLE_COUNT should be at lease 2. + +Multiple samples are for error correction on I2C I/O expander and shorten response time. +On keyboards without I/O expander, multiple samples only shorten response time. +Larger SAMPLE_COUNTs are more reliable but consume more memory, where + SAMPLE_COUNT*ROW_COUNT = bytes of memory consumed by keyboard +So don't make SAMPLE_COUNT too large, SAMPLE_COUNT = 4 is very reliable for I2C error correction. + +big SAMPLE_COUNT for fast response, small SAMPLE_COUNT to save memory +there is a way to define SAMPLE_COUNT in the sketch, but it's ugly + see http://forum.arduino.cc/index.php?topic=364843.0 > rely #2 +*/ +uint8_t Row::debounce(const uint8_t rowState) +{ + uint8_t all_1 = ~0; //bitwise + uint8_t all_0 = 0; //bitwise + + delayMicroseconds(DELAY_MICROSECONDS); //delay between Row scans to debounce key + + samples[samplesIndex] = rowState; //insert rowState into samples[] ring buffer + + if (++samplesIndex >= SAMPLE_COUNT) + { + samplesIndex = 0; //wrap samplesIndex to beginning of ring buffer + } + + for (uint8_t j = 0; j < SAMPLE_COUNT; j++) //traverse the sample[] ring buffer + { + all_1 &= samples[j]; //1 if all samples are 1 + all_0 |= samples[j]; //0 if all samples are 0 + } + + // update newDebounce if all the samples agree with one another + // if all samples=1 then newDebounced=1 + // elseif all samples=0 then newDebounced=0 + // else newDebounced=debounced i.e. no change + return all_1 | (all_0 & debounced); +} + +/* +Computes isFallingEdge and isRisingEdge. +All 3 parameters are bitwise. +*/ +void Row::detectEdge(uint8_t newDebounced, uint8_t& isFallingEdge, uint8_t& isRisingEdge) +{ + uint8_t debouncedChanged; //bitwise + + debouncedChanged = newDebounced xor debounced; + debounced = newDebounced; + + //bit=1 if last debounced changed from 1 to 0, else bit=0 + isFallingEdge = debouncedChanged & ~debounced; + + //bit=1 if last debounced changed from 0 to 1, else bit=0 + isRisingEdge = debouncedChanged & debounced; +} + +/* +calls key's press() or release() function if it was pressed or released. +All 3 parameters are bitwise. +*/ +void Row::pressRelease(const uint16_t rowEnd, const uint8_t isFallingEdge, + const uint8_t isRisingEdge) +{ + uint8_t rowMask; //bitwise, active col bit is 1 + uint8_t col; //index for ptrsKeys[col] array + + for (rowMask=1, col=0; rowMaskrelease(); + } + + if (rowMask & isRisingEdge) //if key was pressed + { + ptrsKeys[col]->press(); + keyWasPressed(); + } + } +} + +void Row::keyWasPressed() +{ + //empty in Row class. To unstick sticky keys, override keyWasPressed() in derived class. +} diff --git a/src/Row.h b/src/Row.h new file mode 100644 index 0000000..3f3817c --- /dev/null +++ b/src/Row.h @@ -0,0 +1,81 @@ +#ifndef ROW_H +#define ROW_H +#include +#include +#include +#include +#include + +#define SAMPLE_COUNT 4 //number of consecutive equal bits needed to change a debounced bit + +/* +Instantiation + ------------ +Example instantiation of a row: + RowPort_AVR rowPortF(DDRF, PORTF); + + ColPort_AVR colPortB(DDRB, PORTB, PINB, 1<<0 | 1<<1 | 1<<2 | 1<<3 ); + ColPort_AVR colPortD(DDRD, PORTD, PIND, 1<<2 | 1<<3 ); + + ColPort* const ptrsColPorts[] = { &colPortB, &colPortD }; + const uint8_t COL_PORTS_COUNT = sizeof(ptrsColPorts)/sizeof(*ptrsColPorts); + + const PROGMEM Key* const ptrsKeys_0[] = { &k_00, &k_01, &k_02, &k_03, &k_04, &k_05 }; + Row row_0(ptrsKeys_0, &rowPortF, 1<<0, ptrsColPorts, COL_PORTS_COUNT); + +Number of ColPort::colPins should equal number of keys in Row::ptrsKeys array + if a pin is missing, a key will be unresposive + if a Key pointer is missing, the keyboard will fail in an unprdictable way + +A keyboard with a faster scan rate is more resposive. +Follow these step to tune DELAY_MICROSECONDS for maximum scan rate within debounce times: + Initialize DELAY_MICROSECONDS in your sketch: + const unsigned int Row::DELAY_MICROSECONDS = 1000; + Add this to the sketche's loop() function: + keybrd.print_microseconds_per_scan(); + Compile and load the sketch into the microcontroller, which will print the actual microseconds_per_scan + Incrementaly adjust the DELAY_MICROSECONDS untill the printed microseconds_per_scan is near the switches bounce time +A switche's debounce time can be obtained from the switche's datasheet +Cherry MX has 5ms bounce time http://www.cherrycorp.com/english/switches/key/mx.htm +hasu measured Cherry MX bounce times .3ms to 1.4ms http://geekhack.org/index.php?topic=42385.0 +Tactile switch MJTP series bounce 10 ms http://www.apem.com/files/apem/brochures/MJTP_6MM.pdf +Optic switches 0 bounce time because optic doesn't bounce + +Slow-scan trick for debug message that print too fast + Keyboard.print(F("debug message")); +Change DELAY_MICROSECONDS to a large number like 10000 +That way printing debug messages is slowed to a managable rate +*/ +class Row +{ + private: + Key *const *const ptrsKeys; //array of Key pointers + + RowPort &refRowPort; //this row's IC port + const uint8_t rowPin; //bitwise, 1 indicates IC pin connected to this row + + ColPort *const *const ptrsColPorts; //array of column ports + const uint8_t colPortCount; + + static const unsigned int DELAY_MICROSECONDS; //delay between each Row scan for debouncing + uint8_t samples[SAMPLE_COUNT]; //bitwise, one bit per key, most resent readings + uint8_t samplesIndex; //samples[] current write index + uint8_t debounced; //bitwise, one bit per key, debounced value of readings + virtual void keyWasPressed(); + public: + Row( RowPort &refRowPort, const uint8_t rowPin, + ColPort *const ptrsColPorts[], const uint8_t colPortCount, + Key *const ptrsKeys[]) + : ptrsKeys(ptrsKeys), refRowPort(refRowPort), rowPin(rowPin), + ptrsColPorts(ptrsColPorts), colPortCount(colPortCount), + samplesIndex(0), debounced(0) { } + + Key* getPtrKey(uint8_t col) const; + void process(const bool activeHigh); + void scan(const bool activeHigh); + uint8_t getRowState(uint16_t& rowEnd, const bool activeHigh); + uint8_t debounce(const uint8_t rowState); //switch debouncer and I2C error correction + void detectEdge(uint8_t newDebounced, uint8_t& isFallingEdge, uint8_t& isRisingEdge); + void pressRelease(const uint16_t rowEnd, const uint8_t isFallingEdge, const uint8_t isRisingEdge); +}; +#endif diff --git a/src/RowPort.h b/src/RowPort.h new file mode 100644 index 0000000..b9f36c7 --- /dev/null +++ b/src/RowPort.h @@ -0,0 +1,16 @@ +#ifndef ROWPORT_H +#define ROWPORT_H +#include +#include + +/* +RowPort is an abstract base class. +Port classes are the keybrd library's interface to microcontoller ports or I/O expander ports. +*/ +class RowPort +{ + public: + virtual void setActivePinHigh(const uint8_t activePin)=0; + virtual void setActivePinLow(const uint8_t activePin)=0; +}; +#endif diff --git a/src/RowPort_AVR_Optic.cpp b/src/RowPort_AVR_Optic.cpp new file mode 100644 index 0000000..ee1f77d --- /dev/null +++ b/src/RowPort_AVR_Optic.cpp @@ -0,0 +1,34 @@ +#include "RowPort_AVR_Optic.h" + +/* +configures row port's DDRx and PORTx pins as output. +*/ +RowPort_AVR_Optic::RowPort_AVR_Optic +(volatile unsigned char& DDRx, volatile unsigned char& PORTx) + : DDR(DDRx = ~0), PORT(PORTx) +{} + +/* +activePin is a port mask, where active pin is 1. +*/ +void RowPort_AVR_Optic::setActivePinLow(const uint8_t activePin) +{ + PORT &= ~activePin; +} + +/* +activePin is port mask, where active pin is 1. + +The delayMicroseconds() is for DodoHand keyboard's optic switches. +Strobe needs to be turned on for >= 300µs before the columns are read. +During this time the state of the columns are settling into their actual values. +Seems to be necessary in order to allow the phototransistors to turn completely off. +(delay is not need for I/O expander because time between I2C Transmissions) +(Teensy2 ATMEGA32U4 16 MHz is a 0.0625 µs period) +*/ +void RowPort_AVR_Optic::setActivePinHigh(const uint8_t activePin) +{ + //strobe row on + PORT |= activePin; + delayMicroseconds(300); //wait for the column value to stabilize after strobe +} diff --git a/src/RowPort_AVR_Optic.h b/src/RowPort_AVR_Optic.h new file mode 100644 index 0000000..d68d6d7 --- /dev/null +++ b/src/RowPort_AVR_Optic.h @@ -0,0 +1,40 @@ +#ifndef ROWPORT_AVR_OPTIC_H +#define ROWPORT_AVR_OPTIC_H +#include +#include +#include + +/* One AVR microcontroller port connected to matrix rows. + +setActivePinHigh() has a delay to allow phototransistors time to sense strobe +(DodoHand has optic switches with phototransistors). + +Instantiation + ------------ +The constructor configures all pins of port as output (for strobe pins and LED). +The 'x' in parameters DDRx, PORTx, and PINx should all be the same letter. + +Example instantiation for row port F: + RowPort_AVR_Optic rowPortF(DDRF, PORTF); + +Diode orientation + ---------------- +Rows, columns, and diode orientation are explained in Matrix.h + +*/ +class RowPort_AVR_Optic : public RowPort +{ + private: + const volatile unsigned char& DDR; //Data Direction Register + + protected: + volatile unsigned char& PORT; //PORT register + + public: + //The constructor initialization list is in .cpp + RowPort_AVR_Optic(volatile unsigned char& DDRx, volatile unsigned char& PORTx); + + virtual void setActivePinLow(const uint8_t activePin); //activePin is a port mask + virtual void setActivePinHigh(const uint8_t activePin); +}; +#endif diff --git a/src/RowPort_MCP23018.cpp b/src/RowPort_MCP23018.cpp new file mode 100644 index 0000000..9e5b00c --- /dev/null +++ b/src/RowPort_MCP23018.cpp @@ -0,0 +1,46 @@ +#include "RowPort_MCP23018.h" + +/* +configures column port's IODIR, GPIO. +*/ +RowPort_MCP23018::RowPort_MCP23018(IOExpanderPort& port) + : port(port), IODIR(port.num), GPIO(port.num + 0x12) +{} + +void RowPort_MCP23018::begin() +{ +//Wire.begin() should only be called once https://www.arduino.cc/en/Reference/WireBegin +#ifndef WIRE_BEGIN +#define WIRE_BEGIN + Wire.begin(); +#endif + + Wire.beginTransmission(port.ADDR); + Wire.write(IODIR); + Wire.write(0); //0=configure as output (for strobe pins and LED pins) + Wire.endTransmission(); +} + +/* +sets activePin pin output to low. +activePin is port mask, where active-low pin is 1. +*/ +void RowPort_MCP23018::setActivePinLow(const uint8_t activePin) +{ + Wire.beginTransmission(port.ADDR); + Wire.write(GPIO); + Wire.write(port.outputVal &= ~activePin); + Wire.endTransmission(); +} + +/* +sets activePin pin output to high, does not reset the other pins because they might be used by LEDs. +activePin is port mask, where active-high pin is 1. +*/ +void RowPort_MCP23018::setActivePinHigh(const uint8_t activePin) +{ + Wire.beginTransmission(port.ADDR); + Wire.write(GPIO); + Wire.write(port.outputVal |= activePin); + Wire.endTransmission(); +} diff --git a/src/RowPort_MCP23018.h b/src/RowPort_MCP23018.h new file mode 100644 index 0000000..09dbcfc --- /dev/null +++ b/src/RowPort_MCP23018.h @@ -0,0 +1,46 @@ +#ifndef ROWPORT_MCP23018_H +#define ROWPORT_MCP23018_H +#include +#include +#include +#include +#include "IOExpanderPort.h" + +/* One MCP23018 I/O expander port connected to matrix rows. + +begin() configures column port's IODIR, GPIO. +This should normally be called only once. + +Instantiation + ------------ +Example instantiation for row port A: + IOExpanderPort portA(0, ~0); + RowPort_MCP23018 rowPortA(portA); +Example instantiation for row port B: + IOExpanderPort portB(1, ~0); + RowPort_MCP23018 rowPortB(portB); + +Diode orientation + ---------------- +Rows, columns, and diode orientation are explained in Matrix.h + +MCP23018 data sheet + ------------------ + http://ww1.microchip.com/downloads/en/DeviceDoc/22103a.pdf +*/ +class RowPort_MCP23018 : public RowPort +{ + private: + IOExpanderPort& port; + const uint8_t IODIR; //Input/Ouput Direction register address + const uint8_t GPIO; //General Purpose Input/Ouput register address + + public: + //The constructor initialization list is in .cpp + RowPort_MCP23018(IOExpanderPort& port); + void begin(); + + virtual void setActivePinLow(const uint8_t activePin); //activePin is a port mask + virtual void setActivePinHigh(const uint8_t activePin); +}; +#endif diff --git a/src/RowPort_PCA9655E.cpp b/src/RowPort_PCA9655E.cpp new file mode 100644 index 0000000..6f22e7a --- /dev/null +++ b/src/RowPort_PCA9655E.cpp @@ -0,0 +1,46 @@ +#include "RowPort_PCA9655E.h" + +/* +configures column port's configuration and output. +*/ +RowPort_PCA9655E::RowPort_PCA9655E(IOExpanderPort& port) + : port(port), configurationByteCommand(port.num + 6), outputByteCommand(port.num + 2) +{} + +void RowPort_PCA9655E::begin() +{ +//Wire.begin() should only be called once https://www.arduino.cc/en/Reference/WireBegin +#ifndef WIRE_BEGIN +#define WIRE_BEGIN + Wire.begin(); +#endif + + Wire.beginTransmission(port.ADDR); + Wire.write(configurationByteCommand); + Wire.write(0); //0=configure as output (for strobe pins and LED) + Wire.endTransmission(); +} + +/* +sets activePin pin output to low, does not reset the other pins because they might be used by LEDs. +activePin is port mask, where active pin is 1. +*/ +void RowPort_PCA9655E::setActivePinLow(const uint8_t activePin) +{ + Wire.beginTransmission(port.ADDR); + Wire.write(outputByteCommand); + Wire.write(port.outputVal &= ~activePin); + Wire.endTransmission(); +} + +/* +sets activePin pin output to high. +activePin is port mask, where active pin is 1. +*/ +void RowPort_PCA9655E::setActivePinHigh(const uint8_t activePin) +{ + Wire.beginTransmission(port.ADDR); + Wire.write(outputByteCommand); + Wire.write(port.outputVal |= activePin); + Wire.endTransmission(); +} diff --git a/src/RowPort_PCA9655E.h b/src/RowPort_PCA9655E.h new file mode 100644 index 0000000..32aa5f7 --- /dev/null +++ b/src/RowPort_PCA9655E.h @@ -0,0 +1,48 @@ +#ifndef ROWPORT_PCA9655E_H +#define ROWPORT_PCA9655E_H +#include +#include +#include +#include +#include "IOExpanderPort.h" + +/* One PCA9655E I/O expander port connected to matrix rows. + +begin() configures column port's configuration and output. +This should normally be called only once. + +Instantiation + ------------ +Example instantiation for row port 0: + IOExpanderPort port0(0, 0); + RowPort_PCA9655E rowPort0(port0); + +Example instantiation for row port 1: + IOExpanderPort port1(1, 0); + RowPort_PCA9655E rowPort1(port1); + +Diode orientation + ---------------- +Rows, columns, and diode orientation are explained in Matrix.h + +PCA9655E data sheet + ---------------- + http://www.onsemi.com/pub_link/Collateral/PCA9655E-D.PDF +*/ + +class RowPort_PCA9655E : public RowPort +{ + private: + IOExpanderPort& port; + const uint8_t configurationByteCommand; + const uint8_t outputByteCommand; + + public: + //The constructor initialization list is in .cpp + RowPort_PCA9655E(IOExpanderPort& port); + void begin(); + + virtual void setActivePinLow(const uint8_t activePin); //activePin is a port mask + virtual void setActivePinHigh(const uint8_t activePin); +}; +#endif diff --git a/src/StateLayers.cpp b/src/StateLayers.cpp new file mode 100644 index 0000000..8a34314 --- /dev/null +++ b/src/StateLayers.cpp @@ -0,0 +1,31 @@ +#include "StateLayers.h" + +void StateLayers::hold(const uint8_t layer) +{ + setActiveLayer(layer); +} + +void StateLayers::unhold(const uint8_t layer) +{ + if (layer == activeLayer); + { + setActiveLayer(lockedLayer); + } +} + +void StateLayers::lock(const uint8_t layer) +{ + setActiveLayer(layer); + lockedLayer = layer; +} + +//could set LED indicator lights in setActiveLayer() +void StateLayers::setActiveLayer(const uint8_t layer) +{ + activeLayer = layer; +} + +uint8_t StateLayers::getActiveLayer() +{ + return activeLayer; +} diff --git a/src/StateLayers.h b/src/StateLayers.h new file mode 100644 index 0000000..0405a5d --- /dev/null +++ b/src/StateLayers.h @@ -0,0 +1,25 @@ +#ifndef LAYERSTATE_H +#define LAYERSTATE_H + +#include +#include +//#include + +/* basic StateLayers for keyboard. +When pressed, Code_Layer objects call StateLayers functions lock() or hold(). +When pressed, Layered objects call StateLayers function getActiveLayer(). +*/ +class StateLayers : public StateLayersInterface +{ + protected: + uint8_t activeLayer; //currently active layer + uint8_t lockedLayer; //most recently pressed lock layer + virtual void setActiveLayer(const uint8_t layer); + public: + StateLayers() : activeLayer(0), lockedLayer(0) {} + virtual void hold(uint8_t layer); //set activeLayer + virtual void unhold(const uint8_t layer); //restore activeLayer to lockedLayer + virtual void lock(uint8_t layer); //set activeLayer and lock it + virtual uint8_t getActiveLayer(); +}; +#endif diff --git a/src/StateLayersInterface.h b/src/StateLayersInterface.h new file mode 100644 index 0000000..f2bc0e3 --- /dev/null +++ b/src/StateLayersInterface.h @@ -0,0 +1,11 @@ +#ifndef STATELAYERSINTERFACE_H +#define STATELAYERSINTERFACE_H + +/* StateLayersInterface in an interface class +*/ +class StateLayersInterface +{ + public: + virtual uint8_t getActiveLayer()=0; +}; +#endif diff --git a/src/getFreeSRAM.h b/src/getFreeSRAM.h new file mode 100644 index 0000000..f97fad9 --- /dev/null +++ b/src/getFreeSRAM.h @@ -0,0 +1,36 @@ +// getFreeSRAM.h copied from +// http://andybrown.me.uk/2011/01/01/debugging-avr-dynamic-memory-allocation/ +/* + * memdebug.h + * + * Created on: 15 Dec 2010 + * Author: Andy Brown + * + * Use without attribution is permitted provided that this + * header remains intact and that these terms and conditions + * are followed: + * + * http://andybrown.me.uk/ws/terms-and-conditions + */ + +#include + +extern unsigned int __bss_end; +extern unsigned int __heap_start; +extern void *__brkval; + +//measure and return amount of free SRAM +uint16_t getFreeSRAM() +{ + uint8_t newVariable; + // if heap is empty, use bss as start memory address + if ((uint16_t)__brkval == 0) + { + return (((uint16_t)&newVariable) - ((uint16_t)&__bss_end)); + } + // else use heap end as the start of the memory address + else + { + return (((uint16_t)&newVariable) - ((uint16_t)__brkval)); + } +}; diff --git a/src/objects_scancode.h b/src/objects_scancode.h new file mode 100644 index 0000000..ff2dd3d --- /dev/null +++ b/src/objects_scancode.h @@ -0,0 +1,179 @@ +/* Include this file in multiple-layer keybrd sketches. +This file instandiates Code objects. + +The scancode macros are defined in the top part of +Arduino\hardware\teensy\cores\teensy\keylayouts.h which is intended for use in "normal" programs. +This has been tested on teensy2.0. +*/ +#include +#include +#include +#include + +// ********** SCANCODES ********* +Code_Shift s_shift(MODIFIERKEY_LEFT_SHIFT); +Code_Shift s_shift_L(MODIFIERKEY_LEFT_SHIFT); +Code_Shift s_shift_R(MODIFIERKEY_RIGHT_SHIFT); + +Code_Sc s_ctrl(MODIFIERKEY_CTRL); +Code_Sc s_alt(MODIFIERKEY_ALT); +Code_Sc s_gui(MODIFIERKEY_GUI); +Code_Sc s_leftCtrl(MODIFIERKEY_LEFT_CTRL); +Code_Sc s_leftAlt(MODIFIERKEY_LEFT_ALT); +Code_Sc s_leftGUI(MODIFIERKEY_LEFT_GUI); +Code_Sc s_rightCtrl(MODIFIERKEY_RIGHT_CTRL); +Code_Sc s_rightAlt(MODIFIERKEY_RIGHT_ALT); +Code_Sc s_rightGUI(MODIFIERKEY_RIGHT_GUI); + +Code_Sc s_MVolumeInc(KEY_MEDIA_VOLUME_INC); +Code_Sc s_MVolumeDec(KEY_MEDIA_VOLUME_DEC); +Code_Sc s_MMute(KEY_MEDIA_MUTE); +Code_Sc s_MPlayPause(KEY_MEDIA_PLAY_PAUSE); +Code_Sc s_MNextTrack(KEY_MEDIA_NEXT_TRACK); +Code_Sc s_MPrevTrack(KEY_MEDIA_PREV_TRACK); +Code_Sc s_MStop(KEY_MEDIA_STOP); +Code_Sc s_MEject(KEY_MEDIA_EJECT); + +Code_Sc s_a(KEY_A); +Code_Sc s_b(KEY_B); +Code_Sc s_c(KEY_C); +Code_Sc s_d(KEY_D); +Code_Sc s_e(KEY_E); +Code_Sc s_f(KEY_F); +Code_Sc s_g(KEY_G); +Code_Sc s_h(KEY_H); +Code_Sc s_i(KEY_I); +Code_Sc s_j(KEY_J); +Code_Sc s_k(KEY_K); +Code_Sc s_l(KEY_L); +Code_Sc s_m(KEY_M); +Code_Sc s_n(KEY_N); +Code_Sc s_o(KEY_O); +Code_Sc s_p(KEY_P); +Code_Sc s_q(KEY_Q); +Code_Sc s_r(KEY_R); +Code_Sc s_s(KEY_S); +Code_Sc s_t(KEY_T); +Code_Sc s_u(KEY_U); +Code_Sc s_v(KEY_V); +Code_Sc s_w(KEY_W); +Code_Sc s_x(KEY_X); +Code_Sc s_y(KEY_Y); +Code_Sc s_z(KEY_Z); + +Code_Sc s_1(KEY_1); +Code_Sc s_2(KEY_2); +Code_Sc s_3(KEY_3); +Code_Sc s_4(KEY_4); +Code_Sc s_5(KEY_5); +Code_Sc s_6(KEY_6); +Code_Sc s_7(KEY_7); +Code_Sc s_8(KEY_8); +Code_Sc s_9(KEY_9); +Code_Sc s_0(KEY_0); + +Code_Sc s_enter(KEY_ENTER); +Code_Sc s_esc(KEY_ESC); +Code_Sc s_backspace(KEY_BACKSPACE); +Code_Sc s_tab(KEY_TAB); +Code_Sc s_space(KEY_SPACE); +Code_Sc s_minus(KEY_MINUS); +Code_Sc s_equal(KEY_EQUAL); +Code_Sc s_leftBracket(KEY_LEFT_BRACE); //[ ("brace" means curly bracket {}) +Code_Sc s_rightBracket(KEY_RIGHT_BRACE); //] +Code_Sc s_backslash(KEY_BACKSLASH); +Code_Sc s_semicolon(KEY_SEMICOLON); +Code_Sc s_quote(KEY_QUOTE); +Code_Sc s_graves(KEY_TILDE); //` +//Code_Sc s_graves(ASCII_60); //` +Code_Sc s_comma(KEY_COMMA); +Code_Sc s_period(KEY_PERIOD); +Code_Sc s_slash(KEY_SLASH); +Code_Sc s_capsLock(KEY_CAPS_LOCK); + +Code_Sc s_F1(KEY_F1); +Code_Sc s_F2(KEY_F2); +Code_Sc s_F3(KEY_F3); +Code_Sc s_F4(KEY_F4); +Code_Sc s_F5(KEY_F5); +Code_Sc s_F6(KEY_F6); +Code_Sc s_F7(KEY_F7); +Code_Sc s_F8(KEY_F8); +Code_Sc s_F9(KEY_F9); +Code_Sc s_F10(KEY_F10); +Code_Sc s_F11(KEY_F11); +Code_Sc s_F12(KEY_F12); + +Code_Sc s_printscreen(KEY_PRINTSCREEN); +Code_Sc s_scrollLock(KEY_SCROLL_LOCK); +Code_Sc s_pause(KEY_PAUSE); +Code_Sc s_insert(KEY_INSERT); +Code_Sc s_home(KEY_HOME); +Code_Sc s_pageUp(KEY_PAGE_UP); +Code_Sc s_delete(KEY_DELETE); +Code_Sc s_end(KEY_END); +Code_Sc s_pageDown(KEY_PAGE_DOWN); +Code_Sc s_right(KEY_RIGHT); //arrow +Code_Sc s_left(KEY_LEFT); +Code_Sc s_down(KEY_DOWN); +Code_Sc s_up(KEY_UP); +Code_Sc s_numLock(KEY_NUM_LOCK); + +Code_Sc s_padSlash(KEYPAD_SLASH); +Code_Sc s_padAsterix(KEYPAD_ASTERIX); +Code_Sc s_padMinus(KEYPAD_MINUS); +Code_Sc s_padPlus(KEYPAD_PLUS); +Code_Sc s_padEnter(KEYPAD_ENTER); +Code_Sc s_pad1(KEYPAD_1); +Code_Sc s_pad2(KEYPAD_2); +Code_Sc s_pad3(KEYPAD_3); +Code_Sc s_pad4(KEYPAD_4); +Code_Sc s_pad5(KEYPAD_5); +Code_Sc s_pad6(KEYPAD_6); +Code_Sc s_pad7(KEYPAD_7); +Code_Sc s_pad8(KEYPAD_8); +Code_Sc s_pad9(KEYPAD_9); +Code_Sc s_pad0(KEYPAD_0); +Code_Sc s_padPeriod(KEYPAD_PERIOD); + +Code_Sc s_menu(KEY_MENU); +Code_Sc s_F13(KEY_F13); +Code_Sc s_F14(KEY_F14); +Code_Sc s_F15(KEY_F15); +Code_Sc s_F16(KEY_F16); +Code_Sc s_F17(KEY_F17); +Code_Sc s_F18(KEY_F18); +Code_Sc s_F19(KEY_F19); +Code_Sc s_F20(KEY_F20); +Code_Sc s_F21(KEY_F21); +Code_Sc s_F22(KEY_F22); +Code_Sc s_F23(KEY_F23); +Code_Sc s_F24(KEY_F24); + +// ********** SCANCODES SHIFTED ********* +// shifted objects are named after ascii symbol names +Code_ScS s_exclamation(KEY_1); +Code_ScS s_at(KEY_2); +Code_ScS s_number(KEY_3); //# +Code_ScS s_dollar(KEY_4); +Code_ScS s_percent(KEY_5); +Code_ScS s_circumflex(KEY_6); //^ +Code_ScS s_ampersand(KEY_7); +Code_ScS s_asterix(KEY_8); +Code_ScS s_leftParen(KEY_9); //parenthesis +Code_ScS s_rightParen(KEY_0); + +Code_ScS s_underscore(KEY_MINUS); +Code_ScS s_plus(KEY_EQUAL); +Code_ScS s_leftBrace(KEY_LEFT_BRACE); //{ +Code_ScS s_rightBrace(KEY_RIGHT_BRACE); //} +Code_ScS s_vertBar(KEY_BACKSLASH); //| +Code_ScS s_colon(KEY_SEMICOLON); +Code_ScS s_doubleQuote(KEY_QUOTE); +Code_ScS s_tilde(KEY_TILDE); +Code_ScS s_lessThan(KEY_COMMA); +Code_ScS s_greaterThan(KEY_PERIOD); +Code_ScS s_question(KEY_SLASH); + +// ********** MISC CODES ********* +Code_Null code_null; //usefull for blank keys diff --git a/src/objects_scancodeNotShifted.h b/src/objects_scancodeNotShifted.h new file mode 100644 index 0000000..baa0bfe --- /dev/null +++ b/src/objects_scancodeNotShifted.h @@ -0,0 +1,39 @@ +/* This file instandiates Code_ScNS objects for multiple-layer keybrd sketches. +The scancode is always sent in the unshifted state regardless of shift key position. +Letters will still print as capital if CapsLck is on. + +If your uC is low on memory, copy needed lines rather than including the entire file. +With the Arduino IDE, objects consume memory if they are used or not. + +The scancode macros are defined in the top part of +Arduino\hardware\teensy\cores\teensy\keylayouts.h which is intended for use in "normal" programs. +This has been tested on teensy2.0. +*/ +#include +#include + +// ********** SCANCODES NOT SHIFTED ********* +Code_ScNS_00 sns_00; //double zero + +Code_ScNS sns_1(KEY_1); //could get similar effect with s_pad1 +Code_ScNS sns_2(KEY_2); +Code_ScNS sns_3(KEY_3); +Code_ScNS sns_4(KEY_4); +Code_ScNS sns_5(KEY_5); +Code_ScNS sns_6(KEY_6); +Code_ScNS sns_7(KEY_7); +Code_ScNS sns_8(KEY_8); +Code_ScNS sns_9(KEY_9); +Code_ScNS sns_0(KEY_0); + +Code_ScNS sns_minus(KEY_MINUS); //could get similar effect with s_padMinus +Code_ScNS sns_equal(KEY_EQUAL); +Code_ScNS sns_leftBracket(KEY_LEFT_BRACE); //[ ("brace" means curly bracket {}) +Code_ScNS sns_rightBracket(KEY_RIGHT_BRACE); //] +Code_ScNS sns_backslash(KEY_BACKSLASH); +Code_ScNS sns_semicolon(KEY_SEMICOLON); +Code_ScNS sns_quote(KEY_QUOTE); +Code_ScNS sns_tilde(KEY_TILDE); +Code_ScNS sns_comma(KEY_COMMA); +Code_ScNS sns_period(KEY_PERIOD); +Code_ScNS sns_slash(KEY_SLASH); diff --git a/tutorials/breadboard_keyboard_supplies.ods b/tutorials/breadboard_keyboard_supplies.ods new file mode 100644 index 0000000..292159b Binary files /dev/null and b/tutorials/breadboard_keyboard_supplies.ods differ diff --git a/tutorials/images/120px-Diode_pinout_en_fr.svg.png b/tutorials/images/120px-Diode_pinout_en_fr.svg.png new file mode 100644 index 0000000..db9ba1b Binary files /dev/null and b/tutorials/images/120px-Diode_pinout_en_fr.svg.png differ diff --git a/tutorials/images/breadboard_big.jpg b/tutorials/images/breadboard_big.jpg new file mode 100644 index 0000000..80beb1e Binary files /dev/null and b/tutorials/images/breadboard_big.jpg differ diff --git a/tutorials/images/breadboard_keyboard_2x2_labeled.jpg b/tutorials/images/breadboard_keyboard_2x2_labeled.jpg new file mode 100644 index 0000000..2fb3277 Binary files /dev/null and b/tutorials/images/breadboard_keyboard_2x2_labeled.jpg differ diff --git a/tutorials/keybrd_2_single-layer_annotated/keybrd_2_single-layer_annotated.ino b/tutorials/keybrd_2_single-layer_annotated/keybrd_2_single-layer_annotated.ino new file mode 100644 index 0000000..b219889 --- /dev/null +++ b/tutorials/keybrd_2_single-layer_annotated/keybrd_2_single-layer_annotated.ino @@ -0,0 +1,163 @@ +/* keybrd_single-layer_2_annotated.ino + +This sketch: + is a simple 1-layer keyboard + runs on the first two rows and columns of a breadboard keyboard + is annotated with a walk-through narrative + +This layout table shows how keys are arranged on the keyboard: + +| Layout | **0** | **1** | +|:------:|-------|-------| +| **0** | a | b | +| **1** | c | shift | + +The layout's row and column numbers are in the headers. +Each cell in the table's body represents a key. + +The sketch is annotated with a walk-through narrative enclosed in comment blocks. +Each comment block explains the next one or two lines of code. + +keybrd is instantiated under the "GLOBAL" heading. Most of the sketch is in global space. +keybrd runs at the end of this sketch, under the "MAIN" heading. +*/ +// ################## GLOBAL ################### +// ================= INCLUDES ================== +/* +The compiler copies #included files into the sketch. +All the includes in this sketch are to keybrd library classes. +*/ +//Ports +#include +#include + +//Codes +#include + +//Matrix +#include +#include + +// ============ SPEED CONFIGURATIONS ============ +/* +DELAY_MICROSECONDS specifies the amount of delay between row scans. +Keyboard switches are made of moving contacts. +When the contacts close, they bounce apart one or more times before making steady contact. +DELAY_MICROSECONDS gives the switches time to debounce. + +DELAY_MICROSECONDS is a static variable of class Row. +*/ +const unsigned int Row::DELAY_MICROSECONDS = 1000; + +// =================== PORTS =================== +/* +A micro-controller has one or more ports. Each port has one or more pins. +These pins are connected to the keyboard's rows and columns. + +The RowPort constructor parameters specify the port's registers. +*/ +RowPort_AVR_Optic rowPortF(DDRF, PORTF); + +/* +The ColPort constructor parameters specify the port's registers and the port pins to read: +A number to the right of "1<<" is the pin number to read. 1<<0 reads pin 0, and 1<<1 reads pin 1. +*/ +ColPort_AVR colPortB(DDRB, PORTB, PINB, 1<<0 | 1<<1 ); + +/* +ColPort pointers are placed in an array because some keyboards use multiple column ports. +This sketch only has one column port. + +sizeof() is used to compute the number of array elements. +This eliminates the risk of forgetting to update the count after adding or removing an element. +*/ +ColPort* const ptrsColPorts[] = { &colPortB }; +const uint8_t COL_PORT_COUNT = sizeof(ptrsColPorts)/sizeof(*ptrsColPorts); + +// =================== CODES =================== +/* +The CODES section instantiates four codes, one for each item in the layout. + +The Code_Sc constructor takes one scancode ("Sc" means "scancode"). +When Code_Sc is pressed, it sends its scancode. + +The Code object names in this sketch start with a "s_" prefix. +*/ +Code_Sc s_a(KEY_A); +Code_Sc s_b(KEY_B); +Code_Sc s_c(KEY_C); +Code_Sc s_shift(MODIFIERKEY_LEFT_SHIFT); + +// ================== MATRIX =================== +/* +The MATRIX section instantiates the components of the matrix: + Codes are grouped into rows. + Rows are grouped into a matrix. + +How the matrix works: + 1) The matrix scans one row at a time. + 2) If a row detects a key press, it notifies the code. + 3) The code sends its scancode. +*/ +// ------------------- ROWS -------------------- +/* +Here we group Code pointers into rows. +Codes are a kind of Key. Array ptrsKeys_0[] contains two pointers to Key objects. + +The Row constructor parameters are: + one rowPort + one row pin + an array of colPorts, and the number of colPorts + an array of Key pointers + +The Row objects names in this sketch start with a "row_" followed by a row number. +*/ +Key* const ptrsKeys_0[] = { &s_a, &s_b }; +Row row_0(rowPortF, 1<<0, ptrsColPorts, COL_PORT_COUNT, ptrsKeys_0); + +Key* const ptrsKeys_1[] = { &s_c, &s_shift }; +Row row_1(rowPortF, 1<<1, ptrsColPorts, COL_PORT_COUNT, ptrsKeys_1); + +/* +HOW ROW OBJECTS WORK +When a row is scanned, the row strobes the row pin, and the column ports read their column pins. +If a row detects a key press, it notifies the key which then sends its scancode. +*/ + +// ------------------ MATRIX ------------------- +/* +Here we group Row pointers into a matrix. +Array ptrsRows[] contains two pointers to Row objects. +*/ +Row* const ptrsRows[] = { &row_0, &row_1 }; +const uint8_t ROW_COUNT = sizeof(ptrsRows)/sizeof(*ptrsRows); + +/* +The Matrix constructor parameters are: + one array of Row pointers, and the number of rows + '0' for active low or '1' for active high +WARNING: the tutorial sketches all have '1' for active high to be compatible with DH. +The breadboard keyboard described in tutorial_1 is active low. +For active low, change the '1' to a '0': +*/ +Matrix matrix(ptrsRows, ROW_COUNT, 1); + +// ################### MAIN #################### +/* +Aruduino IDE copies Functions setup() and loop() into main(). + +setup() initialized the keybrd. +Keyboard.begin() should be called once to initialize. +*/ +void setup() +{ + Keyboard.begin(); +} + +/* +loop() continually scans the Matrix object. +*/ +void loop() +{ + matrix.scan(); +} diff --git a/tutorials/keybrd_3a_multi-layer_annotated/keybrd_3a_multi-layer_annotated.ino b/tutorials/keybrd_3a_multi-layer_annotated/keybrd_3a_multi-layer_annotated.ino new file mode 100644 index 0000000..800ec8d --- /dev/null +++ b/tutorials/keybrd_3a_multi-layer_annotated/keybrd_3a_multi-layer_annotated.ino @@ -0,0 +1,151 @@ +/* keybrd_3_multi-layer_annotated.ino + +This sketch: + is a simple 2-layer keyboard + runs on the first two rows and columns of a breadboard keyboard + is annotated with a walk-through narrative + +This layout table shows how keys are arranged on the keyboard: + +| Layout | **0** | **1** | +|:------:|-------|-------| +| **0** | a 1 | b 2 | +| **1** | fn | shift | + +The layout's row and column numbers are in the headers. +Each cell in the table's body represents a key. +The layered keys in row 0 have two layers; one character for each layer. +Letters 'a' and 'b' are on the normal layer. Numbers '1' and '2' are one the fn layer. +Holding the fn key down makes it the active layer. Releasing the fn key restores the normal layer. +*/ +// ################## GLOBAL ################### +// ================= INCLUDES ================== +//Ports +#include +#include + +//Codes +#include +#include +#include +#include + +//Matrix +#include +#include + +// ============ SPEED CONFIGURATIONS ============ +const unsigned int Row::DELAY_MICROSECONDS = 1000; + +// =================== PORTS =================== +RowPort_AVR_Optic rowPortF(DDRF, PORTF); + +ColPort_AVR colPortB(DDRB, PORTB, PINB, 1<<0 | 1<<1 ); + +ColPort* const ptrsColPorts[] = { &colPortB }; +const uint8_t COL_PORT_COUNT = sizeof(ptrsColPorts)/sizeof(*ptrsColPorts); + +// =================== CODES =================== +/* +The CODES section instantiates six codes, one for each item in the layout: + s_a s_1 s_b s_2 + l_fn s_shift +*/ +// ---------------- LAYER CODE ----------------- +/* +enum assings layer numbers to the layers. +*/ +enum layers { NORMAL, FN }; +/* +stateLayer keeps track of the active layer. The default layer number is 0. +*/ +StateLayers stateLayer; +/* +The Code_LayerHold constructor parameter specifies a layer number and the StateLayers it calls. +When l_fn is pressed, it tells stateLayer to change the active layer to 1. +When l_fn is released, it tells stateLayer to restore the normal layer. +*/ +Code_LayerHold l_fn(FN, stateLayer); + +// ---------------- SCAN CODES ----------------- +Code_Sc s_a(KEY_A); +Code_Sc s_b(KEY_B); +Code_Sc s_1(KEY_1); +Code_Sc s_2(KEY_2); +Code_Sc s_shift(MODIFIERKEY_LEFT_SHIFT); + +// ================== MATRIX =================== +/* +The MATRIX section instantiates the components of the matrix: + Codes are grouped into keys. + Keys are grouped into rows. + Rows are grouped into a matrix. +*/ +// ------------------- KEYS -------------------- +/* +Here we group Code pointers into keys. +Array ptrsCodes_00[] contains two pointers to Code objects. + +Key_LayeredKeysArray constructor parameters are: + one array of Code pointers + +Key_LayeredKeysArray objects are multi-layered - one Code object per layer. +Layer numbers are array indexes for the Key_LayeredKeysArray. +Defining layer numbers with enum insures that the layer numbers are a series starting at 0. + +The Key object names in this sketch start with a "k_" followed by matrix-row-column coordinates. +*/ +Key* const ptrsCodes_00[] = { &s_a, &s_1 }; +Key_LayeredKeysArray k_00(ptrsCodes_00); + +Key* const ptrsCodes_01[] = { &s_b, &s_2 }; +Key_LayeredKeysArray k_01(ptrsCodes_01); + +/* +Key_LayeredKeysArray has a static variable refStateLayers defined here. +It is a reference to stateLayer. +*/ +StateLayersInterface& Key_LayeredKeysArray::refStateLayers = stateLayer; + +/* +HOW LAYERED OBJECTS WORK +When a Key_LayeredKeysArray object is pressed, + it gets the active layer from stateLayer and then sends the scancode for the active layer. +*/ + +// ------------------- ROWS -------------------- +/* +Here we group Key pointers into rows. +Array ptrsKeys_0[] contains two pointers to Key_LayeredKeyArray objects. +*/ +Key* const ptrsKeys_0[] = { &k_00, &k_01 }; +Row row_0(rowPortF, 1<<0, ptrsColPorts, COL_PORT_COUNT, ptrsKeys_0); + +/* +Codes are a kind of Key that only have one layer. +So rows can contain multi-leyered a mix of keys and codes. +Array ptrsKeys_1[] contains two Code pointers. +*/ +Key* const ptrsKeys_1[] = { &l_fn, &s_shift }; +Row row_1(rowPortF, 1<<1, ptrsColPorts, COL_PORT_COUNT, ptrsKeys_1); + +// ------------------ MATRIX ------------------- +/* +Here we group Row pointers into a matrix. +Array ptrsRows[] contains two pointers to Row objects. +*/ +Row* const ptrsRows[] = { &row_0, &row_1 }; +const uint8_t ROW_COUNT = sizeof(ptrsRows)/sizeof(*ptrsRows); + +Matrix matrix(ptrsRows, ROW_COUNT, 1); + +// ################### MAIN #################### +void setup() +{ + Keyboard.begin(); +} + +void loop() +{ + matrix.scan(); +} diff --git a/tutorials/keybrd_3b_autoShift_annotated/keybrd_3b_autoShift_annotated.ino b/tutorials/keybrd_3b_autoShift_annotated/keybrd_3b_autoShift_annotated.ino new file mode 100644 index 0000000..fd32cf3 --- /dev/null +++ b/tutorials/keybrd_3b_autoShift_annotated/keybrd_3b_autoShift_annotated.ino @@ -0,0 +1,142 @@ +/* keybrd_3_autoShift_annotated.ino + +This sketch: + is a simple 2-layer keyboard with AutoShift + runs on the first two rows and columns of a breadboard keyboard + is annotated with a walk-through narrative + +This layout table shows how keys are arranged on the keyboard: + +| Layout | **0** | **1** | +|:------:|-------|-------| +| **0** | a ! | b @ | +| **1** | fn | shift | + +The layered keys in row 0 have two layers; one character for each layer. +Letters 'a' and 'b' are on the normal layer. Symbols '!' and '@' are one the fn layer. +Holding the fn key down makes it the active layer. Releasing the fn key restores the normal layer. +*/ +// ################## GLOBAL ################### +// ================= INCLUDES ================== +//Ports +#include +#include + +//Codes +#include +#include +#include +#include +#include +#include + +//Matrix +#include +#include + +// ============ SPEED CONFIGURATIONS ============ +const unsigned int Row::DELAY_MICROSECONDS = 1000; + +// =================== PORTS =================== +RowPort_AVR_Optic rowPortF(DDRF, PORTF); + +ColPort_AVR colPortB(DDRB, PORTB, PINB, 1<<0 | 1<<1 ); + +ColPort* const ptrsColPorts[] = { &colPortB }; +const uint8_t COL_PORT_COUNT = sizeof(ptrsColPorts)/sizeof(*ptrsColPorts); + +// =================== CODES =================== +/* +The CODES section instantiates six codes, one for each item in the layout: + s_a s_exclamation s_b s_at + l_fn s_shift +*/ +// ---------------- LAYER CODE ----------------- +enum layers { NORMAL, FN }; + +StateLayers stateLayer; +Code_LayerHold l_fn(FN, stateLayer); + +// ---------------- SCAN CODES ----------------- +/* +The Code_Sc constructor takes one scancode ("Sc" means "scancode"). +When Code_Sc is pressed, it sends its scancode. +*/ +Code_Sc s_a(KEY_A); +Code_Sc s_b(KEY_B); +/* +The Code_ScS constructor takes one scancode to be shifted ("ScS" means "scancode shifted"). +When Code_ScS is pressed, it calls Code_AutoShift before sending its scancode. +*/ +Code_ScS s_exclamation(KEY_1); +Code_ScS s_at(KEY_2); + +// ----------------- SHIFT CODE ---------------- +/* +The Code_Shift constructor takes one scancode. +*/ +Code_Shift s_shift(MODIFIERKEY_LEFT_SHIFT); + +/* +Code_Shift pointers are placed in an array because most keyboards have a left and right shift. +This sketch only has one shift code. +*/ +Code_Shift* const ptrsS[] = { &s_shift }; + +/* +Code_AutoShift is the base class of Codes_ScS (Codes_ScS is explained in the preceding section). +It has two static variables, ptrsShifts and shiftCount, which are defined here. +ptrsShifts is the array of Code_Shift pointers; one pointer for each shift key. +*/ +Code_Shift* const* const Code_AutoShift::ptrsShifts = ptrsS; +const uint8_t Code_AutoShift::shiftCount = sizeof(ptrsShifts)/sizeof(*ptrsShifts); + +/* +HOW AUTOSHIFT WORKS +When a modifier key is pressed, a standard keyboard driver will temporarily modify the normal action of another key when pressed together. + KEY_1 writes '1' + MODIFIERKEY_LEFT_SHIFT + KEY_1 writes '!' + + KEY_2 writes '2' + MODIFIERKEY_LEFT_SHIFT + KEY_2 writes '@' + +Code_ScS takes care of the MODIFIERKEY_LEFT_SHIFT automatically +When the user presses '!' or '@' on the fn layer: + Code_AutoShift checks the position of each shift key + Code_ScS sends MODIFIERKEY_LEFT_SHIFT scancode if needed + Code_ScS sends its scancode +*/ + +// ================== MATRIX =================== +// ------------------- KEYS -------------------- +Key* const ptrsCodes_00[] = { &s_a, &s_exclamation }; +Key_LayeredKeysArray k_00(ptrsCodes_00); + +Key* const ptrsCodes_01[] = { &s_b, &s_at }; +Key_LayeredKeysArray k_01(ptrsCodes_01); + +StateLayersInterface& Key_LayeredKeysArray::refStateLayers = stateLayer; + +// ------------------- ROWS -------------------- +Key* const ptrsKeys_0[] = { &k_00, &k_01 }; +Row row_0(rowPortF, 1<<0, ptrsColPorts, COL_PORT_COUNT, ptrsKeys_0); + +Key* const ptrsKeys_1[] = { &l_fn, &s_shift }; +Row row_1(rowPortF, 1<<1, ptrsColPorts, COL_PORT_COUNT, ptrsKeys_1); + +// ------------------ MATRIX ------------------- +Row* const ptrsRows[] = { &row_0, &row_1 }; +const uint8_t ROW_COUNT = sizeof(ptrsRows)/sizeof(*ptrsRows); + +Matrix matrix(ptrsRows, ROW_COUNT, 1); + +// ################### MAIN #################### +void setup() +{ + Keyboard.begin(); +} + +void loop() +{ + matrix.scan(); +} diff --git a/tutorials/keybrd_4_split_with_IOE_annotated/keybrd_4_split_with_IOE_annotated.ino b/tutorials/keybrd_4_split_with_IOE_annotated/keybrd_4_split_with_IOE_annotated.ino new file mode 100644 index 0000000..6130d68 --- /dev/null +++ b/tutorials/keybrd_4_split_with_IOE_annotated/keybrd_4_split_with_IOE_annotated.ino @@ -0,0 +1,171 @@ +/* keybrd_4_split_with_IOE_annotated.ino + +This sketch: + is a simple 1-layer keyboard + runs on two matrices of a breadboard keyboard + is annotated with a walk-through narrative + +This layout table shows left and right matrices: + +| Left | **0** | **1** | | Right | **0** | **1** | +|:-----:|-------|-------|-|:-----:|-------|-------| +| **0** | a | b | | **0** | 1 | 2 | +| **1** | shift | c | | **1** | 3 | shift | + +MARTIX NAMING CONVENTION +Since this keyboard has two matrices, we need a naming convention to distinguish the matrices. +Matrix IDs are the letters 'L' and 'R' (left and right). +Port object names and Port pointer array names end with matrix ID: + port1_R + rowPortF_L rowPort1_R + port0_R + colPortB_L colPort0_R + ptrsColPorts_L ptrsColPorts_R + COL_PORT_L_COUNT COL_PORT_R_COUNT + +Key pointer array names and Row objects names end with matrix ID and row number: + ptrsKeys_L0 ptrsKeys_R0 + row_L0 row_R0 + +Matrix object names end with matrix ID: + matrix_L matrix_R +*/ +// ################## GLOBAL ################### +// ================= INCLUDES ================== +//Ports +#include +#include +#include +#include +#include + +//Codes +#include + +//Matrix +#include +#include + +// ============ SPEED CONFIGURATIONS ============ +const unsigned int Row::DELAY_MICROSECONDS = 1000; + +// ================ LEFT PORTS ================= +/* +The left matrix is scanned by a micro-controller. +*/ +RowPort_AVR_Optic rowPortF_L(DDRF, PORTF); + +ColPort_AVR colPortB_L(DDRB, PORTB, PINB, 1<<0 | 1<<1 ); +ColPort* const ptrsColPorts_L[] = { &colPortB_L }; +const uint8_t COL_PORT_L_COUNT = sizeof(ptrsColPorts_L)/sizeof(*ptrsColPorts_L); + +// =============== RIGHT PORTS ================= +/* +The right matrix is scanned by an I/O expander. + +The micro-controller and I/O expander communicates via I2C bus. +Three hardware pins (AD0, AD1, AD2) are used to configure the I2C address of the I/O expander. +ADDR is a static variable of class IOExpanderPort. The I2C address of this I/O expander is 0x18. + +An I/O expander used on a matrix has two ports. Each port has eight pins. +One port is connected to the matrix's rows. The other port is connected to the matrix's columns. + +The IOExpanderPort constructor parameters specify the port number and initial output value. +I/O Expander and AVR have similar constructor parameters for RowPort and ColPort. +*/ +const uint8_t IOExpanderPort::ADDR = 0x18; + +/* +port1_R uses port 1 with an initial output value of 0. +*/ +IOExpanderPort port1_R(1, 0); + +/* +The RowPort_PCA9655E constructor parameter specifies the IOExpanderPort. +*/ +RowPort_PCA9655E rowPort1_R(port1_R); + +/* +port0_R uses port 0 with an initial output value of 0. +*/ +IOExpanderPort port0_R(0, 0); + +/* +The ColPort_PCA9655E constructor parameter specifies the IOExpanderPort and the port pins to read: +A number to the right of "1<<" is the pin number to read. 1<<0 reads pin 0, and 1<<1 reads pin 1. +*/ +ColPort_PCA9655E colPort0_R(port0_R, 1<<0 | 1<<1 ); + +/* +ColPort pointers are placed in an array because some keyboards use multiple column ports. +This sketch only has one column port. + +sizeof() is used to compute the number of array elements. +This eliminates the risk of forgetting to update the count after adding or removing an element. +*/ +ColPort* const ptrsColPorts_R[] = { &colPort0_R }; +const uint8_t COL_PORT_R_COUNT = sizeof(ptrsColPorts_R)/sizeof(*ptrsColPorts_R); + +// =================== CODES =================== +/* +Codes are not grouped into left and right because codes are independent of layout. + - a keyboard can have differnt layouts + - some codes may appear on both matrices +*/ +Code_Sc s_shiftL(MODIFIERKEY_LEFT_SHIFT); +Code_Sc s_shiftR(MODIFIERKEY_RIGHT_SHIFT); + +Code_Sc s_a(KEY_A); +Code_Sc s_b(KEY_B); +Code_Sc s_c(KEY_C); +Code_Sc s_1(KEY_1); +Code_Sc s_2(KEY_2); +Code_Sc s_3(KEY_3); + +// ================ LEFT MATRIX ================ +// ---------------- LEFT ROWS ------------------ +Key* const ptrsKeys_L0[] = { &s_a, &s_b }; +Row row_L0(rowPortF_L, 1<<0, ptrsColPorts_L, COL_PORT_L_COUNT, ptrsKeys_L0); + +Key* const ptrsKeys_L1[] = { &s_c, &s_shiftL }; +Row row_L1(rowPortF_L, 1<<1, ptrsColPorts_L, COL_PORT_L_COUNT, ptrsKeys_L1); + +// ---------------- LEFT MATRIX ---------------- +Row* const ptrsRows_L[] = { &row_L0, &row_L1 }; +const uint8_t ROW_L_COUNT = sizeof(ptrsRows_L)/sizeof(*ptrsRows_L); + +Matrix matrix_L(ptrsRows_L, ROW_L_COUNT, 1); + +// ================ RIGHT MATRIX =============== +// ---------------- RIGHT ROWS ----------------- +Key* const ptrsKeys_R0[] = { &s_1, &s_2 }; +Row row_R0(rowPort1_R, 1<<0, ptrsColPorts_R, COL_PORT_R_COUNT, ptrsKeys_R0); + +Key* const ptrsKeys_R1[] = { &s_3, &s_shiftR }; +Row row_R1(rowPort1_R, 1<<1, ptrsColPorts_R, COL_PORT_R_COUNT, ptrsKeys_R1); + +// ---------------- RIGHT MATRIX --------------- +Row* const ptrsRows_R[] = { &row_R0, &row_R1 }; +const uint8_t ROW_R_COUNT = sizeof(ptrsRows_R)/sizeof(*ptrsRows_R); + +Matrix matrix_R(ptrsRows_R, ROW_R_COUNT, 1); + +// ################### MAIN #################### +void setup() +{ +/* +Call begin() for I/O expander's rowPort and colPort. +*/ + rowPort1_R.begin(); + colPort0_R.begin(); + Keyboard.begin(); +} + +/* +loop() continually scans both Matrix objects. +*/ +void loop() +{ + matrix_L.scan(); + matrix_R.scan(); +} diff --git a/tutorials/tutorial_0_introduction.md b/tutorials/tutorial_0_introduction.md new file mode 100644 index 0000000..e62eeee --- /dev/null +++ b/tutorials/tutorial_0_introduction.md @@ -0,0 +1,25 @@ +Tutorial 0 - Introduction +========================= +The first two tutorials are intended to be read in sequence: + 1. Breadboard keyboard + 2. Single-layer keybrd +You can write a keyboard firmware after reading tutorial 2. +Topics covered in tutorial 2 apply to all keybrd sketches. +The remaining tutorials can be read in any sequence, and highlight topics that may or may not be useful to your keyboard design. + +The tutorials assume the reader: + * is familiar with C++ + * is new to Arduino, firmware, controllers, and the internal workings of keyboards + +Each tutorial presents an example sketch. +All the example sketches have 2 to 8 keys and run on a breadboard keyboard. + +todo all the tutorial sketches are tested on teensy 2.0 and PCA9655E-D IOE + in July, sketches will be changed to Teensy LC and MCP23018 IOE + +You will need a breadboard keyboard with a Teensy 2.0 controller to run the tutorial sketches. +If you use a different controller, you may have to change port classes. +If you already have a keyboard with an Arduino compatible controller, you can use that instead of a breadboard keyboard. + +[breadboard keyboard with 2 rows and 2 columns] +(images/breadboard_keyboard_2x2_labeled.jpg "2x2 breadboard keyboard") diff --git a/tutorials/tutorial_10_writing_your_own_port_classes.md b/tutorials/tutorial_10_writing_your_own_port_classes.md new file mode 100644 index 0000000..1ab9836 --- /dev/null +++ b/tutorials/tutorial_10_writing_your_own_port_classes.md @@ -0,0 +1,17 @@ +Tutorial 8 - writing your own port classes +========================================== +Port classes are the keybrd library's interface to microcontoller ports or I/O expander ports. + +To write your own port classes: + 1) Get a copy of the controller or I/O expander datasheet. + 2) Study keybrd port classes that use a similar IC. + 3) Consider looking for other open-source keyboard code that uses the same IC e.g. TMK keyboard firmware. + 4) Write your RowPort_* class to inherit from RowPort class. + 5) Write your ColPort_* class to inherit from ColPort class. + 6) Consider testing on a breadboard keyboard. + +Writing port classes is the most technically demanding task in the keybrd library. +If you have not read a controller datasheet or I/O expander datasheet before, + consider designing your keyboard around one of the controllers or I/O expanders + that already have port classes in the keybrd library. + diff --git a/tutorials/tutorial_1_breadboard_keyboard.md b/tutorials/tutorial_1_breadboard_keyboard.md new file mode 100644 index 0000000..4c6dc3d --- /dev/null +++ b/tutorials/tutorial_1_breadboard_keyboard.md @@ -0,0 +1,86 @@ +Tutorial 0 - breadboard keyboard +================================ +When you finish this tutorial you will have a working keyboard and understand how its key matrix works. + +[breadboard keyboard with 2 rows and 2 columns] +(images/breadboard_keyboard_2x2_labeled.jpg "2x2 breadboard keyboard") + +## Why a breadboard keyboard is useful +A breadboard is the easiest way to learn keyboard electronics. +Electronics are fickle, you won't get everything right the first time, there is a learning curve. +Breadboards make the learning electronics faster and fun. + +Breadboard keyboards have row-column matrices and diodes just like the big keyboards. +Compared to full size keyboards on PCBs, breadboard keyboards are easier to work with because: +* Parts can be reused in many different configurations +* A small keyboard is easier to trouble shoot +* Mistakes are easily corrected because no desoldering + +Breadboard keyboards are useful for: +* learning keyboard electronics - diodes, micro controllers, I/O expanders +* learning the firmware development process +* prototyping circuits before making a PCB +* testing firmware concepts before building the keyboard hardware + +## How a breadboard works +To understand the breadboard keyboard you will need to know the internal parts of a breadboard: +* terminal strip +* power rail + +These are explained in [How to Use a Breadboard](https://learn.sparkfun.com/tutorials/how-to-use-a-breadboard) + +## Breadboard keyboard starter kit +The parts needed to build all the Breadboard Keyboards in the keybrd tutorials are listed in [breadboard_keyboard_supplies.ods](breadboard_keyboard_supplies.ods). + +Wire cutters (or nail clippers) is the only required tool. +A multi-meter is useful for trouble shooting. + +## Building a basic breadboard keyboard +The basic breadboard has 4 switches and a microcontroller. + +[breadboard keyboard with 2 rows and 2 columns of keys] +(images/breadboard_keyboard_2x2_labeled.jpg "2x2 breadboard keyboard") + +The key matrix has two rows and two columns. +Breadboard power rails are repurposed as matrix rows. +Short bare wires connect switches into matrix columns. +Diodes connect switches to rows. + +The green rectangle on the right is the Teensy 2.0 microcontroller. +The matrix rows and columns connect to the microcontroller via jumper wires. + +Breadboard keyboard assembly instructions: + +1. Cut leads to length. + * tactile-switch-lead length 6 to 8 mm + * diodes 22 to 24 mm total end-to-end length, and save the cut offs, they will be used in steps 2 and 3 +2. Insert parts into the breadboard as shown in the picture. + * orient the switches such that the leads are on separate terminal strips + * orient diodes with cathode (banded end) towards the row (power strip) + * use the diode cut offs to connect switches into columns +3. Insert jumper wires connecting Teensy2 to the matrix rows and columns. + * follow pin connections table (below) and consult pinout diagram in + [Connecting Teensy 2.0 to a Keyboard](connecting_teensy2_to_keyboard.md) + +**Teensy 2.0 pin connections table** + +| Pin number | Row Column | +|------------|-------------| +| 21 | row_0 | todo this table might not match the sketches +| 20 | row_1 | +| 0 | col_0 | +| 1 | col_1 | + +## Compiling and loading the keyboard firmware +Follow the [keybrd Library User's Guide](todo /doc/keybrd_library_user_guide.md) to set up the Arduino environment and to compile and load keybrd firmware onto the keyboard's controller. + +## How a keyboard matrix works +Now that you have built your first breadboard keyboard, you can dig in and learn how it actually works. +This excellent article explains how the microcontroller, matrix, switches and diodes work together: +[How a Key Matrix Work](http://pcbheaven.com/wikipages/How_Key_Matrices_Works/) + +## Bigger breadboard keyboards +Sometimes its useful to prototype the full keyboard matrix before designing the PCB. +Several breadboards can be tied together into one. + +[big breadboard keyboard](breadboard_big.jpg "breadboard_big.jpg") diff --git a/tutorials/tutorial_2_single-layer_keyboard.md b/tutorials/tutorial_2_single-layer_keyboard.md new file mode 100644 index 0000000..dbe93b6 --- /dev/null +++ b/tutorials/tutorial_2_single-layer_keyboard.md @@ -0,0 +1,16 @@ +Tutorial 2 - single-layer keyboard +======================================= +This annotated sketch explains how the keybrd library works: +[keybrd_2_single-layer_annotated.ino](keybrd_proj/keybrd/examples/tutorials/keybrd_2_single-layer_annotated/keybrd_2_single-layer_annotated.ino) + +You can view the class definitions in the [keybrd library](keybrd/src/). + +After reading the sketch you will be to modify it to suite your own single-layer keyboard design. + +## Exercises +1) Add a third column to the breadboard and sketch. + +| Layout | **0** | **1** | **2** | +|:------:|-------|-------|-------| +| **0** | a | b | c | +| **1** | 1 | 2 | shift | diff --git a/tutorials/tutorial_3a_multi-layer_keyboard.md b/tutorials/tutorial_3a_multi-layer_keyboard.md new file mode 100644 index 0000000..7518dc7 --- /dev/null +++ b/tutorials/tutorial_3a_multi-layer_keyboard.md @@ -0,0 +1,66 @@ +Tutorial 3a - multi-layer keyboard +================================== +After reading this tutorial you will be able to be able to modify a multi-layer keybrd sketch to suite your own multi-layer keyboard design. + +## Multi-layer nomenclature +**[layers](http://deskthority.net/wiki/Layer)** are key bindings provided by the keyboard firmware. For example, +* The full-size [IBM PC keyboard](http://en.wikipedia.org/wiki/IBM_PC_keyboard) has one layer. +* Many compact keyboards have an additional [Fn layer](http://en.wikipedia.org/wiki/Fn_key). +* The [Neo layout](http://neo-layout.org/index_en.html) has 6 layers. + +**layer code** - is an integer used to identify a layer. + +**active layer** - is the layer currently used by the keyboard. + +**layer scheme** - is a system for changing layers while typing (a single-layer scheme does not change layers). + +## A simple multi-layer keybrd sketch +This annotated sketch demonstrates the multi-layer feature: +[keybrd_3_multi-layer_annotated.ino](keybrd_proj/keybrd/examples/tutorials/keybrd_3_multi-layer_annotated/keybrd_3_multi-layer_annotated.ino) + +## Layer scheme classes +The walkthrough example covered the most basic classes. +This section takes a general view of layer scheme classes. +You can view all the class definitions in the [keybrd library](keybrd/src/). + +### StateLayer +StateLayer object has an active layer. StateLayer keeps its active layer up to date. +There is only one StateLayer class: +* StateLayer + +### Layer +Layer objects control the active layer. For example, there could be one layer key for each layer. +When a Layer object is pressed, it tells StateLayer to change the active layer. +Example Layer classes include: +* Code_LayerHold +* Code_LayerLock + +### Multi-layered +Layered objects contain one scancode for each layer. +When a Layered object is pressed, it gets the active layer from StateLayer, and then sends the scancode of the active layer. +Example Layered classes include: +* Code_LayeredScSc +* Code_LayeredCodeSc +* Code_LayeredCodeCode +* Key_LayeredKeysArray + +## Single-layer Codes +Most Code objects only have one scancode or one layer code. +They do are not affected by the active layer. +Example single-layer Code classes include: +* Code_Sc +* Code_ScS +* Code_ScNS +* Code_Shift +* Code_LayerHold +* Code_LayerLock + +(Future version of keybrd library may change all Code classes to Key classes.) + +## Exercises +1) Modify the keybrd_3_multi-layer_annotated.ino sketch to use two Code_LayerLock objects. + +| Layout | **0** | **1** | +|:------:|--------|--------| +| **0** | a 1 | b 2 | +| **1** | layer0 | layer1 | diff --git a/tutorials/tutorial_3b_autoShift.md b/tutorials/tutorial_3b_autoShift.md new file mode 100644 index 0000000..8a138e9 --- /dev/null +++ b/tutorials/tutorial_3b_autoShift.md @@ -0,0 +1,23 @@ +Tutorial 3b - autoShift +======================= +After reading this tutorial your keyboard will be able to be able to automatically shifted characters. + +## AutoShift +Some mulit-layer keyboards have a symbols layer that writes symbols and numbers without using the shift key: + + ~ ! @ # $ % ^ & * () _ {} | < > ? 1 2 3 4 5 6 7 8 9 0 + +The keybrd library does this by automatically sending the MODIFIERKEY_SHIFT scancode. +This annotated sketch demonstrates the AutoShift feature: [keybrd_3_autoShift_annotated.ino](keybrd_proj/keybrd/examples/keybrd_3_autoShift_annotated/keybrd_3_autoShift_annotated.ino) + +Two keybrd classes use AutoShift: +* Code_ScS +* Code_ScNS + +## Exercises +1) Modify the keybrd_3_autoShift_annotated sketch to make a 3-layer keyboard with two Code_LayerHold objects. + +| Layout | **0** | **1** | +|:------:|-------|-------| +| **0** | a ! 6 | b @ 7 | +| **1** | sym | num | diff --git a/tutorials/tutorial_4_split_keyboard_with_IOE.md b/tutorials/tutorial_4_split_keyboard_with_IOE.md new file mode 100644 index 0000000..34594f1 --- /dev/null +++ b/tutorials/tutorial_4_split_keyboard_with_IOE.md @@ -0,0 +1,89 @@ +keybrd Tutorial 4 - split keyboard with I/O Expander +==================================================== +After reading this tutorial you will be able to be able to modify a 2-matrix keybrd sketch to suite your own split keyboard design. + +## Overview of split keyboard with I/O Expander +The breadboard in this picture models a split keyboard. +![breadboard keyboard with 2 rows and 4 columns of keys] +(images/breadboard_keyboard_2x4_labeled.jpg "2x5 breadboard keyboard") + +The right matrix is connected to a microcontroller. +The left matrix is connected to a I/O expander. + +There is a total of 4 matrix rows, each on a dedicated power rail. + +The microcontroller and I/O expander communicate by [I2C](http://en.wikipedia.org/wiki/I%C2%B2C) via 4 jumper wires: +* ground +* power +* Serial CLock input (SCL) +* Serial DAta I/O (SDA) + +The two resistors near the microcontroller pull-up voltage on the SCL and SDA pins. + +The I/O expander has a small notch on one end, which identifies the end with pin 1. +In the picture, pin 1 is on the right end. + +## Building a split keyboard with I/O Expander +The split keyboard is built on the Basic Breadboard Keyboard described in +tutorial_0_keybrd_breadboard.md > Building a Basic Breadboard Keyboard + +Follow these instructions to add a second matrix to the Basic Breadboard Keyboard: +4. Insert I2C jumper wires and pull-up resistors connecting to Teensy2. + * follow the I2C and pull-up resistors tables (below) and consult Teensy pinout diagram in + [Connecting Teensy 2.0 to a Keyboard](connecting_teensy2_to_keyboard.md) + +todo these tables might not match the sketch + +**Teensy 2.0 pin connections tables** + +| Pin Number | Row Column | +|------------|-------------| +| 21 | row_R0 | +| 20 | row_R1 | +| 0 | col_R0 | +| 1 | col_R1 | + +| Pin Number | I2C | +|------------|-------------| +| GND | ground | +| VCC | power | +| 5 | SCL | +| 6 | SDA | + +| Pin Number | 4.7K Ohms Pull-up Resistor | +|------------|-------------| +| 5 | VCC | +| 6 | VCC | + +5. Insert jumper wires to connect MCP23018 I/O expander + * follow pin connections tables (below) and consult pinout diagram in + [Connecting MCP23018 I/O Expander to a Keyboard](connecting_MCP23018_to_keyboard.md) + +**MCP23018 I/O expander pin connections tables** + +| Pin Number | Row Column | +|------------|-------------| +| 3 | row_L0 | +| 4 | row_L1 | +| 20 | col_L0 | +| 21 | col_L1 | +| 22 | col_L2 | + +| Pin Number | I2C | +|------------|-------------| +| 1 | ground | +| 11 | power | +| 12 | SCL | +| 13 | SDA | + +| Pin Number | Jump to Pin | +|------------|-------------| +| 11 | 16 | +| 1 | 15 | + +todo add capacitor + +## Sketch for split keyboard with I/O Expander +An annotated sketch for the split keyboard with I/O Expander is on +[keybrd_4_split_with_IOE_annotated.ino](keybrd_4_split_with_IOE_annotated/keybrd_4_split_with_IOE_annotated.ino) + diff --git a/tutorials/tutorial_7a_using_someone_else's_keybrd_extension_library.md b/tutorials/tutorial_7a_using_someone_else's_keybrd_extension_library.md new file mode 100644 index 0000000..ec53b26 --- /dev/null +++ b/tutorials/tutorial_7a_using_someone_else's_keybrd_extension_library.md @@ -0,0 +1,11 @@ +Tutorial 7a - using someone else's keybrd extension library +======================================================== +The keybrd library contains the foundation classes for creating a keyboard firmware. +keybrd extension libraries extend the main keyboard library. + +keybrd extension library names are prefixed by "keybrd_" and are listed in: +* [Arduino Playground](http://playground.arduino.cc/Main/InterfacingWithHardware#keyb) > find "keybrd" +* Arduino Library-Manager (Arduino IDE > Sketch > Include Library > Manage Libraries > Filter your search: keybrd) + +Instructions for installing a library are at: + http://www.arduino.cc/en/Guide/Libraries diff --git a/tutorials/tutorial_7b_creating_and_publishing_your_own_keybrd_extension_library.md b/tutorials/tutorial_7b_creating_and_publishing_your_own_keybrd_extension_library.md new file mode 100644 index 0000000..4e8a6c2 --- /dev/null +++ b/tutorials/tutorial_7b_creating_and_publishing_your_own_keybrd_extension_library.md @@ -0,0 +1,80 @@ +Tutorial 7b - creating and publishing your own keybrd extension library +======================================================================= +Listing your keybrd extension library allows others to find and install your library. +The keybrd extension library name should start with "keybrd_" so that it is easy for people to find. +The directory structure of the library depends on where it will be listed. + +## Publishing anywhere with listing on Arduino Playground LibraryList +Arduino Playground LibraryList can list a library with any directory structure. +The directory structure of your keybrd extension library can be as simple as: + + keybrd_MyKeyboard/ + class1.cpp + class1.h + class2.cpp + class2.h + .. + instantiations_codes.h + instantiations_ports.h + instantiations_matrix.h + doc/ + keybrd_MyKeyboard_guide + examples/ + keybrd_MyKeyboard1/ + keybrd_MyKeyboard1.ino + keybrd_MyKeyboard2/ + keybrd_MyKeyboard2.ino + +[Arduino playground](http://playground.arduino.cc/) is a wiki. +Instructions for listing a library on the Arduino playgound LibraryList are at: + http://playground.arduino.cc/Code/Library#Sharing + +Add your keybrd library to the Keyboard/Keypads sublist: + http://playground.arduino.cc/Main/InterfacingWithHardware#keyb + +## Publishing on GitHub with listing on Arduino Library-Manager and Arduino Playground LibraryList +The advantage of using GitHub is that users can submit pull requests. +The advantage of using Arduino Library-Manager is that users can install your library through the Arduino IDE. + +Arduino Library-Manager is particular about directory structures it accepts. +The directory structure of your keybrd extension library to look like this: + + keybrd_MyKeyboard/ + library.properties + doc/ + keybrd_MyKeyboard_guide + examples/ + keybrd_MyKeyboard1/ + keybrd_MyKeyboard1.ino + keybrd_MyKeyboard2/ + keybrd_MyKeyboard2.ino + src/ + class1.cpp + class1.h + class2.cpp + class2.h + .. + instantiations_codes.h + instantiations_ports.h + instantiations_matrix.h + +The library.properties file is described in + https://github.com/arduino/Arduino/wiki/Arduino-IDE-1.5:-Library-specification +Example library.properties file: + name=keybrd_MyKeyboard + version=1.2.3 + author=Me + maintainer=Me + sentence=An extension to the keybrd library for the My keyboard. + paragraph=This library demonstrates my feature. + category=Device Control + url=https://github.com/Me/keybrd_MyKeyboard + architectures=Teensy LC + +Instructions for listing a library on Arduino Library Manager are at: + https://github.com/arduino/Arduino/wiki/Library-Manager-FAQ + +After it has been accepted into the Arduino IDE Library Manager, add your library to the Arduino Playground LibraryList. +[Arduino playground](http://playground.arduino.cc/) is a wiki. +Sign in at http://playground.arduino.cc/Main/LibraryList and add keybrd libraries to Keyboard/Keypads sublist: + http://playground.arduino.cc/Main/InterfacingWithHardware#keyb diff --git a/tutorials/tutorial_8_breaking_up_a_sketch_into_smaller_files.md b/tutorials/tutorial_8_breaking_up_a_sketch_into_smaller_files.md new file mode 100644 index 0000000..9a0aac0 --- /dev/null +++ b/tutorials/tutorial_8_breaking_up_a_sketch_into_smaller_files.md @@ -0,0 +1,32 @@ +Tutorial 8 - breaking up a sketch into smaller files +==================================================== +A keybrd sketch can become quite lengthy, which can make it harder to navigate and understand. + +The keybrd_DH sketch has about 800 lines of code; 700 of which are for instantiating objects. +The object instantiations are grouped into four files located in the keybrd_DH library, and included in keybrd_DH.ino: + + // ========= OBJECT INSTANTIATIONS ============= + #include + #include + #include + #include + +Splitting your code into groups of instantiations also provides organizational and reusability benefits. + +Example 1. +You have three versions of LED indicators you are experimenting with: + instantiations_LEDs_1.h + instantiations_LEDs_2.h + instantiations_LEDs_3.h + +Example 2. +You use Colemak and want QWERTY users to to try your keyboard design. +So you publish your keybrd extension library with two versions of instantiations_matrix.h: + instantiations_matrix_QWERTY.h + instantiations_matrix_colemak.h + +Example 3. +Someone wants to try the layout in your keybrd extension library, but their controller and matrix are different. +So they replace two of your object instantiation files with their own: + instantiations_ports.h + instantiations_matrix.h