@@ -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. |
@@ -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. |
@@ -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 | |||
@@ -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 |
@@ -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 |
@@ -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 <Arduino.h> | |||
#include <Wire.h> | |||
#include <Keyboard.h> | |||
#include <Mouse.h> |
@@ -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. |
@@ -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 |
@@ -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 <Wire.h> | |||
//keybrd library files | |||
//#include <objects_scancode.h> | |||
#include <Code_Sc.h> | |||
#include <Code_ScS.h> | |||
#include <Code_Shift.h> | |||
#include <StateLayers.h> | |||
//#include <Code_LayerLock.h> | |||
#include <Code_LayerHold.h> | |||
#include <Key_LayeredKeysArray.h> | |||
#include <RowPort_AVR_Optic.h> | |||
#include <ColPort_AVR.h> | |||
#include <Row.h> | |||
#include <Matrix.h> | |||
#include <Debug.h> | |||
// ================= 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(); | |||
} |
@@ -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=<br>Create keyboards with any configuration:<br>one-piece, split with I/O expander, single-layer, multiple-layer | |||
category=Device Control | |||
url=https://github.com/wolfv6/keybrd | |||
architectures=avr |
@@ -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 |
@@ -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(); | |||
} | |||
} |
@@ -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 |
@@ -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(); | |||
} | |||
} |
@@ -0,0 +1,32 @@ | |||
#ifndef CODE_LEDLOCK_H | |||
#define CODE_LEDLOCK_H | |||
#include <Arduino.h> | |||
#include <inttypes.h> | |||
#include <Code.h> | |||
#include <LED.h> | |||
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 |
@@ -0,0 +1,11 @@ | |||
#include "Code_LayerHold.h" | |||
void Code_LayerHold::press() | |||
{ | |||
refStateLayers.hold(layer); | |||
} | |||
void Code_LayerHold::release() | |||
{ | |||
refStateLayers.unhold(layer); | |||
} |
@@ -0,0 +1,21 @@ | |||
#ifndef CODE_LAYERHOLD_H | |||
#define CODE_LAYERHOLD_H | |||
#include <inttypes.h> | |||
#include <Code.h> | |||
#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 |
@@ -0,0 +1,10 @@ | |||
#include "Code_LayerLock.h" | |||
void Code_LayerLock::press() | |||
{ | |||
refStateLayers.lock(layer); | |||
} | |||
void Code_LayerLock::release() | |||
{ | |||
} |
@@ -0,0 +1,21 @@ | |||
#ifndef CODE_LAYERLOCK_H | |||
#define CODE_LAYERLOCK_H | |||
#include <inttypes.h> | |||
#include <Code.h> | |||
#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 |
@@ -0,0 +1,7 @@ | |||
#include "Code_LayeredCodeSc.h" | |||
void Code_LayeredCodeSc::press() | |||
{ | |||
layer = refStateLayers.getActiveLayer(); | |||
pressCode(); | |||
} |
@@ -0,0 +1,23 @@ | |||
#ifndef CODE_LAYEREDCODESC_H | |||
#define CODE_LAYEREDCODESC_H | |||
#include <Arduino.h> | |||
#include <inttypes.h> | |||
#include <Code_LayeredCodeScBase.h> | |||
#include <StateLayersInterface.h> | |||
/* 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 |
@@ -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(); | |||
} | |||
} |
@@ -0,0 +1,27 @@ | |||
#ifndef CODE_LAYEREDCODESCBASE_H | |||
#define CODE_LAYEREDCODESCBASE_H | |||
#include <Arduino.h> | |||
#include <inttypes.h> | |||
#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 |
@@ -0,0 +1,7 @@ | |||
#include "Code_LayeredScSc.h" | |||
void Code_LayeredScSc::press() | |||
{ | |||
layer = refStateLayers.getActiveLayer(); | |||
pressScancode(); | |||
} |
@@ -0,0 +1,22 @@ | |||
#ifndef CODE_LAYEREDSCSC_H | |||
#define CODE_LAYEREDSCSC_H | |||
#include <Arduino.h> | |||
#include <inttypes.h> | |||
#include <StateLayersInterface.h> | |||
#include <Code_LayeredScScBase.h> | |||
/* 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 |
@@ -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); | |||
} |
@@ -0,0 +1,26 @@ | |||
#ifndef CODE_LAYERED2SCANCODES_H | |||
#define CODE_LAYERED2SCANCODES_H | |||
#include <Arduino.h> | |||
#include <inttypes.h> | |||
#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 |
@@ -0,0 +1,14 @@ | |||
#ifndef CODE_NULL_H | |||
#define CODE_NULL_H | |||
#include <Arduino.h> | |||
#include <Code.h> | |||
/* 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 |
@@ -0,0 +1,11 @@ | |||
#include "Code_Sc.h" | |||
void Code_Sc::press() | |||
{ | |||
Keyboard.press(scancode); | |||
} | |||
void Code_Sc::release() | |||
{ | |||
Keyboard.release(scancode); | |||
} |
@@ -0,0 +1,19 @@ | |||
#ifndef CODE_SC_H | |||
#define CODE_SC_H | |||
#include <Arduino.h> | |||
#include <inttypes.h> | |||
#include <Code.h> | |||
/* 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 |
@@ -0,0 +1,13 @@ | |||
#include "Code_ScNS.h" | |||
void Code_ScNS::press() | |||
{ | |||
releaseAllShifts(); | |||
Keyboard.press(scancode); | |||
restoreAllShifts(); | |||
} | |||
void Code_ScNS::release() | |||
{ | |||
Keyboard.release(scancode); | |||
} |
@@ -0,0 +1,23 @@ | |||
#ifndef CODE_SCNS_H | |||
#define CODE_SCNS_H | |||
#include <Arduino.h> | |||
#include <inttypes.h> | |||
#include <Code_AutoShift.h> | |||
/* 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 |
@@ -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); | |||
} |
@@ -0,0 +1,22 @@ | |||
#ifndef CODE_ScS_H | |||
#define CODE_ScS_H | |||
#include <Arduino.h> | |||
#include <inttypes.h> | |||
#include <Code_AutoShift.h> | |||
/* 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 |
@@ -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); | |||
} | |||
} |
@@ -0,0 +1,23 @@ | |||
#ifndef CODE_SHIFT_H | |||
#define CODE_SHIFT_H | |||
#include <Arduino.h> | |||
#include <inttypes.h> | |||
#include <Code.h> | |||
/* 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 |
@@ -0,0 +1,11 @@ | |||
#include "ColPort.h" | |||
uint8_t ColPort::getColPins() | |||
{ | |||
return colPins; | |||
} | |||
uint8_t ColPort::getPortState() | |||
{ | |||
return portState; | |||
} |
@@ -0,0 +1,23 @@ | |||
#ifndef COLPORT_H | |||
#define COLPORT_H | |||
#include <Arduino.h> | |||
#include <inttypes.h> | |||
/* | |||
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 |
@@ -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; | |||
} |
@@ -0,0 +1,41 @@ | |||
#ifndef COLPORT_AVR_H | |||
#define COLPORT_AVR_H | |||
#include <Arduino.h> | |||
#include <inttypes.h> | |||
#include <ColPort.h> | |||
/* 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 |
@@ -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(); | |||
} |
@@ -0,0 +1,46 @@ | |||
#ifndef COLPORT_MCP23018_H | |||
#define COLPORT_MCP23018_H | |||
#include <Arduino.h> | |||
#include <inttypes.h> | |||
#include <Wire.h> | |||
#include <ColPort.h> | |||
#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 |
@@ -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(); | |||
} |
@@ -0,0 +1,44 @@ | |||
#ifndef COLPORT_PCA9655E_H | |||
#define COLPORT_PCA9655E_H | |||
#include <Arduino.h> | |||
#include <inttypes.h> | |||
#include <Wire.h> | |||
#include <ColPort.h> | |||
#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 |
@@ -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++; | |||
} |
@@ -0,0 +1,16 @@ | |||
#ifndef DEBUG_H | |||
#define DEBUG_H | |||
#include <Arduino.h> | |||
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 |
@@ -0,0 +1,49 @@ | |||
#ifndef IOEXPANDERPORT_H | |||
#define IOEXPANDERPORT_H | |||
#include <inttypes.h> | |||
/* 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 |
@@ -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 <Arduino.h> | |||
#include <inttypes.h> | |||
/* 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 |
@@ -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(); | |||
} |
@@ -0,0 +1,25 @@ | |||
#ifndef KEY_LAYEREDKEYSARRAY_H | |||
#define KEY_LAYEREDKEYSARRAY_H | |||
#include <Arduino.h> | |||
#include <inttypes.h> | |||
#include <StateLayersInterface.h> | |||
#include <Key.h> | |||
/* 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 |
@@ -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 |
@@ -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 | |||
} |
@@ -0,0 +1,23 @@ | |||
#ifndef LED_AVR_H | |||
#define LED_AVR_H | |||
#include <Arduino.h> | |||
#include <inttypes.h> | |||
#include <LED.h> | |||
/* 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 |
@@ -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(); | |||
} |
@@ -0,0 +1,35 @@ | |||
#ifndef LED_MCP23018_H | |||
#define LED_MCP23018_H | |||
#include <Arduino.h> | |||
#include <inttypes.h> | |||
#include <Wire.h> | |||
#include <LED.h> | |||
#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 |
@@ -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(); | |||
} |
@@ -0,0 +1,26 @@ | |||
#ifndef LED_PCA9655E_H | |||
#define LED_PCA9655E_H | |||
#include <Arduino.h> | |||
#include <inttypes.h> | |||
#include <Wire.h> | |||
#include <LED.h> | |||
#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 |
@@ -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); | |||
} | |||
} |
@@ -0,0 +1,41 @@ | |||
#ifndef MATRIX_H | |||
#define MATRIX_H | |||
#include <Arduino.h> | |||
#include <inttypes.h> | |||
#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 |
@@ -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; rowMask<rowEnd; rowMask<<=1, col++) //for each key in row | |||
{ | |||
//release before press avoids impossible key sequence | |||
if (rowMask & isFallingEdge) //if key was released | |||
{ | |||
ptrsKeys[col]->release(); | |||
} | |||
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. | |||
} |
@@ -0,0 +1,81 @@ | |||
#ifndef ROW_H | |||
#define ROW_H | |||
#include <Arduino.h> | |||
#include <inttypes.h> | |||
#include <Key.h> | |||
#include <RowPort.h> | |||
#include <ColPort.h> | |||
#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 |
@@ -0,0 +1,16 @@ | |||
#ifndef ROWPORT_H | |||
#define ROWPORT_H | |||
#include <Arduino.h> | |||
#include <inttypes.h> | |||
/* | |||
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 |
@@ -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 | |||
} |
@@ -0,0 +1,40 @@ | |||
#ifndef ROWPORT_AVR_OPTIC_H | |||
#define ROWPORT_AVR_OPTIC_H | |||
#include <Arduino.h> | |||
#include <inttypes.h> | |||
#include <RowPort.h> | |||
/* 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 |
@@ -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(); | |||
} |
@@ -0,0 +1,46 @@ | |||
#ifndef ROWPORT_MCP23018_H | |||
#define ROWPORT_MCP23018_H | |||
#include <Arduino.h> | |||
#include <inttypes.h> | |||
#include <Wire.h> | |||
#include <RowPort.h> | |||
#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 |
@@ -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(); | |||
} |
@@ -0,0 +1,48 @@ | |||
#ifndef ROWPORT_PCA9655E_H | |||
#define ROWPORT_PCA9655E_H | |||
#include <Arduino.h> | |||
#include <inttypes.h> | |||
#include <Wire.h> | |||
#include <RowPort.h> | |||
#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 |
@@ -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; | |||
} |
@@ -0,0 +1,25 @@ | |||
#ifndef LAYERSTATE_H | |||
#define LAYERSTATE_H | |||
#include <inttypes.h> | |||
#include <StateLayersInterface.h> | |||
//#include <LED.h> | |||
/* 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 |
@@ -0,0 +1,11 @@ | |||
#ifndef STATELAYERSINTERFACE_H | |||
#define STATELAYERSINTERFACE_H | |||
/* StateLayersInterface in an interface class | |||
*/ | |||
class StateLayersInterface | |||
{ | |||
public: | |||
virtual uint8_t getActiveLayer()=0; | |||
}; | |||
#endif |
@@ -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 <inttypes.h> | |||
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)); | |||
} | |||
}; |
@@ -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 <Code_Null.h> | |||
#include <Code_Sc.h> | |||
#include <Code_ScS.h> | |||
#include <Code_Shift.h> | |||
// ********** 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 |
@@ -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 <Code_ScNS.h> | |||
#include <Code_ScNS_00.h> | |||
// ********** 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); |
@@ -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 <RowPort_AVR_Optic.h> | |||
#include <ColPort_AVR.h> | |||
//Codes | |||
#include <Code_Sc.h> | |||
//Matrix | |||
#include <Row.h> | |||
#include <Matrix.h> | |||
// ============ 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(); | |||
} |
@@ -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 <RowPort_AVR_Optic.h> | |||
#include <ColPort_AVR.h> | |||
//Codes | |||
#include <Code_Sc.h> | |||
#include <StateLayers.h> | |||
#include <Code_LayerHold.h> | |||
#include <Key_LayeredKeysArray.h> | |||
//Matrix | |||
#include <Row.h> | |||
#include <Matrix.h> | |||
// ============ 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(); | |||
} |
@@ -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 <RowPort_AVR_Optic.h> | |||
#include <ColPort_AVR.h> | |||
//Codes | |||
#include <Code_Sc.h> | |||
#include <Code_ScS.h> | |||
#include <Code_Shift.h> | |||
#include <StateLayers.h> | |||
#include <Code_LayerHold.h> | |||
#include <Key_LayeredKeysArray.h> | |||
//Matrix | |||
#include <Row.h> | |||
#include <Matrix.h> | |||
// ============ 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(); | |||
} |
@@ -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 <RowPort_AVR_Optic.h> | |||
#include <ColPort_AVR.h> | |||
#include <IOExpanderPort.h> | |||
#include <RowPort_PCA9655E.h> | |||
#include <ColPort_PCA9655E.h> | |||
//Codes | |||
#include <Code_Sc.h> | |||
//Matrix | |||
#include <Row.h> | |||
#include <Matrix.h> | |||
// ============ 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(); | |||
} |
@@ -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") |
@@ -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. | |||
@@ -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") |
@@ -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 | |
@@ -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 | |
@@ -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 | |
@@ -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) | |||
@@ -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 |
@@ -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 |
@@ -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 <instantiations_ports.h> | |||
#include <instantiations_LEDs.h> | |||
#include <instantiations_codes.h> | |||
#include <instantiations_matrix.h> | |||
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 |