Archived
1
0

initial commit keybrd version 0.3.0

This commit is contained in:
wolfv6 2016-05-09 08:05:08 -06:00
commit 98b6060e7c
92 changed files with 3846 additions and 0 deletions

21
LICENSE.txt Normal file
View File

@ -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.

44
README.md Normal file
View File

@ -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.

33
doc/CHANGELOG.md Normal file
View File

@ -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

View File

@ -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

11
doc/astyle_cpp Normal file
View File

@ -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

View File

@ -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>

View File

@ -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.

22
doc/planned_features.md Normal file
View File

@ -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

View File

@ -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();
}

9
library.properties Normal file
View File

@ -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

11
src/Code.h Normal file
View File

@ -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

30
src/Code_AutoShift.cpp Normal file
View File

@ -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();
}
}

38
src/Code_AutoShift.h Normal file
View File

@ -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

68
src/Code_LEDLock.cpp Normal file
View File

@ -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();
}
}

32
src/Code_LEDLock.h Normal file
View File

@ -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

11
src/Code_LayerHold.cpp Normal file
View File

@ -0,0 +1,11 @@
#include "Code_LayerHold.h"
void Code_LayerHold::press()
{
refStateLayers.hold(layer);
}
void Code_LayerHold::release()
{
refStateLayers.unhold(layer);
}

21
src/Code_LayerHold.h Normal file
View File

@ -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

10
src/Code_LayerLock.cpp Normal file
View File

@ -0,0 +1,10 @@
#include "Code_LayerLock.h"
void Code_LayerLock::press()
{
refStateLayers.lock(layer);
}
void Code_LayerLock::release()
{
}

21
src/Code_LayerLock.h Normal file
View File

@ -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

View File

@ -0,0 +1,7 @@
#include "Code_LayeredCodeSc.h"
void Code_LayeredCodeSc::press()
{
layer = refStateLayers.getActiveLayer();
pressCode();
}

23
src/Code_LayeredCodeSc.h Normal file
View File

@ -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

View File

@ -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();
}
}

View File

@ -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

7
src/Code_LayeredScSc.cpp Normal file
View File

@ -0,0 +1,7 @@
#include "Code_LayeredScSc.h"
void Code_LayeredScSc::press()
{
layer = refStateLayers.getActiveLayer();
pressScancode();
}

22
src/Code_LayeredScSc.h Normal file
View File

@ -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

View File

@ -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);
}

View File

@ -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

14
src/Code_Null.h Normal file
View File

@ -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

11
src/Code_Sc.cpp Normal file
View File

@ -0,0 +1,11 @@
#include "Code_Sc.h"
void Code_Sc::press()
{
Keyboard.press(scancode);
}
void Code_Sc::release()
{
Keyboard.release(scancode);
}

19
src/Code_Sc.h Normal file
View File

@ -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

13
src/Code_ScNS.cpp Normal file
View File

@ -0,0 +1,13 @@
#include "Code_ScNS.h"
void Code_ScNS::press()
{
releaseAllShifts();
Keyboard.press(scancode);
restoreAllShifts();
}
void Code_ScNS::release()
{
Keyboard.release(scancode);
}

23
src/Code_ScNS.h Normal file
View File

@ -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

20
src/Code_ScS.cpp Normal file
View File

@ -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);
}

22
src/Code_ScS.h Normal file
View File

@ -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

34
src/Code_Shift.cpp Normal file
View File

@ -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);
}
}

23
src/Code_Shift.h Normal file
View File

@ -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

11
src/ColPort.cpp Normal file
View File

@ -0,0 +1,11 @@
#include "ColPort.h"
uint8_t ColPort::getColPins()
{
return colPins;
}
uint8_t ColPort::getPortState()
{
return portState;
}

23
src/ColPort.h Normal file
View File

@ -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

17
src/ColPort_AVR.cpp Normal file
View File

@ -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;
}

41
src/ColPort_AVR.h Normal file
View File

@ -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

41
src/ColPort_MCP23018.cpp Normal file
View File

@ -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();
}

46
src/ColPort_MCP23018.h Normal file
View File

@ -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

37
src/ColPort_PCA9655E.cpp Normal file
View File

@ -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();
}

44
src/ColPort_PCA9655E.h Normal file
View File

@ -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

32
src/Debug.cpp Normal file
View File

@ -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++;
}

16
src/Debug.h Normal file
View File

@ -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

49
src/IOExpanderPort.h Normal file
View File

@ -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

18
src/Key.h Normal file
View File

@ -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

View File

@ -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();
}

View File

@ -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

21
src/LED.h Normal file
View File

@ -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

11
src/LED_AVR.cpp Normal file
View File

@ -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
}

23
src/LED_AVR.h Normal file
View File

@ -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

17
src/LED_MCP23018.cpp Normal file
View File

@ -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();
}

35
src/LED_MCP23018.h Normal file
View File

@ -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

17
src/LED_PCA9655E.cpp Normal file
View File

@ -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();
}

26
src/LED_PCA9655E.h Normal file
View File

@ -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

12
src/Matrix.cpp Normal file
View File

@ -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);
}
}

41
src/Matrix.h Normal file
View File

@ -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

204
src/Row.cpp Normal file
View File

@ -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.
}

81
src/Row.h Normal file
View File

@ -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

16
src/RowPort.h Normal file
View File

@ -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

34
src/RowPort_AVR_Optic.cpp Normal file
View File

@ -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
}

40
src/RowPort_AVR_Optic.h Normal file
View File

@ -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

46
src/RowPort_MCP23018.cpp Normal file
View File

@ -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();
}

46
src/RowPort_MCP23018.h Normal file
View File

@ -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

46
src/RowPort_PCA9655E.cpp Normal file
View File

@ -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();
}

48
src/RowPort_PCA9655E.h Normal file
View File

@ -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

31
src/StateLayers.cpp Normal file
View File

@ -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;
}

25
src/StateLayers.h Normal file
View File

@ -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

View File

@ -0,0 +1,11 @@
#ifndef STATELAYERSINTERFACE_H
#define STATELAYERSINTERFACE_H
/* StateLayersInterface in an interface class
*/
class StateLayersInterface
{
public:
virtual uint8_t getActiveLayer()=0;
};
#endif

36
src/getFreeSRAM.h Normal file
View File

@ -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));
}
};

179
src/objects_scancode.h Normal file
View File

@ -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

View File

@ -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);

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 333 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 476 KiB

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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")

View File

@ -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.

View File

@ -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")

View File

@ -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 |

View File

@ -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 |

View File

@ -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 |

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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