It is assumed the reader is familiar with the C++ language including pointers, objects, classes, static class variables, composition, aggregation, inheritance, polymorphism, and enum. | It is assumed the reader is familiar with the C++ language including pointers, objects, classes, static class variables, composition, aggregation, inheritance, polymorphism, and enum. | ||||
Row, Scanner, and Debouncer classes use bit manipulation. | Row, Scanner, and Debouncer classes use bit manipulation. | ||||
Custom Row classes | |||||
------------------ | |||||
Row classes are central to the keybrd library. | |||||
Row is an abstract base class that allows flexibility in designing derived Row classes: | |||||
* Row functions can be overridden in a derived class | |||||
* choice of Debouncers | |||||
* choice of Scanners | |||||
This example illustrates the custom Row classes for a fictional keybrd_Ext extension library. | |||||
The keybrd_Ext library is for a split keyboard with a matrix on each hand and sticky keys. | |||||
Row_Ext::keyWasPressed() overrides Row::keyWasPressed() which is used to unstick sticky keys. | |||||
Row_Ext_uC and Row_Ext_ShiftRegisters are a custom classes composed of stock keybrd library classes.<br> | |||||
Row_Ext_uC uses Scanner_uC to scan the primary matrix.<br> | |||||
Row_Ext_ShiftRegisters uses Scanner_ShiftRegs74HC165 to scan the peripheral matrix. | |||||
Class inheritance diagram | |||||
``` | |||||
Row | |||||
| | |||||
Row_Ext (override Row::keyWasPressed() ) | |||||
/ \ | |||||
Row_Ext_uC Row_Ext_ShiftRegisters (inherit Row_Ext::keyWasPressed() ) | |||||
Scanner_uC Scanner_ShiftRegs74HC165 | |||||
``` | |||||
Dependency diagram | |||||
``` | |||||
________ Row_Ext_uC[1] ______________ | |||||
/ | \ | |||||
Scanner_uC[1] Debouncer_Samples[1] Key[1..*] | |||||
/ | | |||||
strobePin[1] Code[1..*] | |||||
_________ Row_Ext_ShiftRegisters[1] ________ | |||||
/ \ \ | |||||
Scanner_ShiftRegs74HC165[1] Debouncer_Samples[1] Key[1..*] | |||||
| | | |||||
strobePin[1] Code[1..*] | |||||
``` | |||||
Class inheritance diagrams | Class inheritance diagrams | ||||
-------------------------- | -------------------------- | ||||
Scanner_uC Scanner_IOE Scanner_ShiftRegsPISO | Scanner_uC Scanner_IOE Scanner_ShiftRegsPISO | ||||
PortIOE | |||||
PortIOE | |||||
PortWrite | |||||
| | |||||
PortWrite_PCA9655E (one PortWrite class for each IOE type) | |||||
PortWrite | |||||
/ \ | |||||
PortWrite_PCA9655E PortWrite_MCP23S17 (one PortWrite class for each IOE type) | |||||
PortRead | |||||
| | |||||
PortRead_PCA9655E (one PortRead class for each IOE type) | |||||
PortRead | |||||
/ \ | |||||
PortRead_PCA9655E PortRead_MCP23S17 (one PortRead class for each IOE type) | |||||
_ LED _ | _ LED _ | ||||
/ \ | / \ | ||||
Dependency diagram of example single-layer keyboard with LEDs | Dependency diagram of example single-layer keyboard with LEDs | ||||
``` | ``` | ||||
_ Row_uC[1..*] _ | |||||
/ | \ | |||||
Scanner_uC Debouncer Keys[1..*] __ | |||||
| \ | |||||
Code[1..*] Code_LEDLock[1..*] | |||||
| | |||||
LED_PinNumber[1] | |||||
____ Row ______ | |||||
/ | \ | |||||
Scanner_uC Debouncer Keys __ | |||||
| | \ | |||||
readPins Code Code_LEDLock | |||||
| | |||||
LED_PinNumber | |||||
``` | ``` | ||||
Dependency diagram of example multi-layer keyboard with layer LEDs | Dependency diagram of example multi-layer keyboard with layer LEDs | ||||
``` | ``` | ||||
LayerStates[1..*] | |||||
________ Row_uC[1..*] ___________/__ | \ | |||||
/ | \ / \ | \ | |||||
Scanner_uC[1] Debouncer[1] Keys[1..*] / Code_Layer[1..*] LED_PinNumber[0..*] | |||||
| / | |||||
Code[1..*] | |||||
LayerStates | |||||
___________ Row _______/__ | \ | |||||
/ / \ / \ | \ | |||||
Scanner_uC Debouncer Keys / Code_Layer LED_PinNumber | |||||
| \ / | |||||
readPins Code | |||||
``` | ``` | ||||
Dependency diagram of example peripheral matrix with shift registers | |||||
Dependency diagram of example shift registers Row | |||||
``` | ``` | ||||
Row_ShiftRegisters[1..*] | |||||
/ \ \ | |||||
RowScanner_ShiftRegisters Debouncer Keys[1..*] | |||||
| | |||||
Code[1..*] | |||||
_______ Row _______ | |||||
/ | \ | |||||
RowScanner_ShiftRegsPISO Debouncer Keys | |||||
| | |||||
Code | |||||
``` | ``` | ||||
Dependency diagram of example peripheral matrix with I/O Expander and LEDs | |||||
Dependency diagram of example I/O expander matrix with LEDs | |||||
``` | ``` | ||||
_____ Row_IOE[1..*] _________ | |||||
/ \ \ | |||||
__ Scanner_Port[1] __ Debouncer[1] Keys[1..*] __ | |||||
/ | \ | \ | |||||
PortWrite[1] strobePin[1] PortRead[1] Code[1..*] Code_LEDLock[1..*] | |||||
\ / \ | | |||||
\ / ColPins[1..*] LED[1] | |||||
\ / | |||||
PortIOE[0..*] | |||||
_________ Row _________ | |||||
/ \ \ | |||||
__ Scanner_IOE __ Debouncer Keys | |||||
/ | \ / \ | |||||
strobePin PortWrite PortRead Code Code_LEDLock | |||||
| \ / \ | | |||||
| PortIOE readPins LED | |||||
\___________________________/ \ | |||||
pin | |||||
``` | ``` | ||||
Arduino does not have a debugger. | Arduino does not have a debugger. | ||||
So here is a list of functions in the order that they are called. | So here is a list of functions in the order that they are called. | ||||
The trace is of a one-row single-layer keybrd scan. | The trace is of a one-row single-layer keybrd scan. | ||||
Refer to it like a table of contents while reading the keybrd library. | |||||
``` | ``` | ||||
loop() for each row | loop() for each row |
Key* ptrsKeys_0[] = { &s_a, &s_b }; | Key* ptrsKeys_0[] = { &s_a, &s_b }; | ||||
Row_uC row_0(0, readPins, READ_PIN_COUNT, ptrsKeys_0); | Row_uC row_0(0, readPins, READ_PIN_COUNT, ptrsKeys_0); | ||||
``` | ``` | ||||
* The scanner should have enough readPins to cover all the keys of the longest row. | |||||
(rows with fewer keys will have unused read pins) | |||||
* read_pins_t size in keybrd/src/config_keybrd.h file should cover all the read pins. | |||||
* Some of the constructors take array-element-count arguments, make sure that the correct counts are passed to the constructors. Or use sizeof() like the preceding example. | * Some of the constructors take array-element-count arguments, make sure that the correct counts are passed to the constructors. Or use sizeof() like the preceding example. | ||||
* For multi-layered keyboards, the number of codes in each Key_Layered should equal the number of layers. | * For multi-layered keyboards, the number of codes in each Key_Layered should equal the number of layers. | ||||
// ================ LEFT SCANNER =============== | // ================ LEFT SCANNER =============== | ||||
uint8_t readPins_L[] = {0, 1}; | uint8_t readPins_L[] = {0, 1}; | ||||
uint8_t readPinCount_L = sizeof(readPins_L)/sizeof(*readPins_L); | |||||
uint8_t READPIN_COUNT_L = sizeof(readPins_L)/sizeof(*readPins_L); | |||||
Scanner_uC scanner_L(HIGH, readPins_L, readPinCount_L); | |||||
Scanner_uC scanner_L(HIGH, readPins_L, READPIN_COUNT_L); | |||||
// =============== RIGHT SCANNER =============== | // =============== RIGHT SCANNER =============== | ||||
const uint8_t PortIOE::DEVICE_ADDR = 0x18; | const uint8_t PortIOE::DEVICE_ADDR = 0x18; |
#include "Debouncer_Not.h" | #include "Debouncer_Not.h" | ||||
/* debounce() sets debounced and returns debouncedChanged. All variables are bitwise. | |||||
/* debounce() sets debounced and returns debouncedChanged. | |||||
All parameters and variables are bitwise. | |||||
For parameters, 1 means pressed, 0 means released. | For parameters, 1 means pressed, 0 means released. | ||||
For return, 1 means debounced changed. | For return, 1 means debounced changed. | ||||
*/ | */ |
*/ | */ | ||||
#include "Debouncer_Samples.h" | #include "Debouncer_Samples.h" | ||||
/* debounce() sets debounced and returns debouncedChanged. All variables are bitwise. | |||||
/* debounce() sets debounced and returns debouncedChanged. | |||||
All parameters and variables are bitwise. | |||||
For parameters, 1 means pressed, 0 means released. | For parameters, 1 means pressed, 0 means released. | ||||
For return, 1 means debounced changed. | For return, 1 means debounced changed. | ||||
*/ | */ |
//PortIOE& port; | //PortIOE& port; | ||||
//const uint8_t outputByteCommand; //General Purpose Input/Ouput register address | //const uint8_t outputByteCommand; //General Purpose Input/Ouput register address | ||||
PortWrite_PCA9655E& refPort; | PortWrite_PCA9655E& refPort; | ||||
const uint8_t pin; //bitwise pin to LED | |||||
const uint8_t pin; //bitwise IOE pin to LED | |||||
public: | public: | ||||
LED_PCA9655E(PortWrite_PCA9655E& refPort, const uint8_t pin) | LED_PCA9655E(PortWrite_PCA9655E& refPort, const uint8_t pin) |
struct PortIOE | struct PortIOE | ||||
{ | { | ||||
static const uint8_t DEVICE_ADDR; | static const uint8_t DEVICE_ADDR; | ||||
const uint8_t num; //port number | |||||
const uint8_t num; //port identification number | |||||
uint8_t outputVal; //bitwise value of output register for LEDs | uint8_t outputVal; //bitwise value of output register for LEDs | ||||
PortIOE(const uint8_t portNumber) | PortIOE(const uint8_t portNumber) |
*/ | */ | ||||
uint8_t PortMCP23S17::transfer(const uint8_t command, const uint8_t registerAddr, const uint8_t data) | uint8_t PortMCP23S17::transfer(const uint8_t command, const uint8_t registerAddr, const uint8_t data) | ||||
{ | { | ||||
uint8_t portState; //bit wise | |||||
uint8_t portState; //bitwise | |||||
digitalWrite(SS, LOW); //enable Slave Select | digitalWrite(SS, LOW); //enable Slave Select | ||||
SPI.transfer(command); //write or read command | SPI.transfer(command); //write or read command |
{ | { | ||||
pullUp = readPins; | pullUp = readPins; | ||||
} | } | ||||
else | |||||
else //active high requires external pull-down resistors | |||||
{ | { | ||||
pullUp = 0; | pullUp = 0; | ||||
} | } |
/* | /* | ||||
send() calls key's press() or release() function if key was pressed or released. | send() calls key's press() or release() function if key was pressed or released. | ||||
Both parameters are bitwise. | |||||
Parameter debouncedChanged is bitwise. | |||||
*/ | */ | ||||
void Row::send(const uint8_t keyCount, const read_pins_t debouncedChanged) | void Row::send(const uint8_t keyCount, const read_pins_t debouncedChanged) | ||||
{ | { | ||||
read_pins_t isFallingEdge; //bitwise, 1 means falling edge | read_pins_t isFallingEdge; //bitwise, 1 means falling edge | ||||
read_pins_t isRisingEdge; //bitwise, 1 means rising edge | read_pins_t isRisingEdge; //bitwise, 1 means rising edge | ||||
read_pins_t readMask; //bitwise, active bit is 1 | |||||
read_pins_t readPosition; //bitwise, active bit is 1 | |||||
uint8_t i; //index for ptrsKeys[i] array | uint8_t i; //index for ptrsKeys[i] array | ||||
//bit=1 if last debounced changed from 1 to 0, else bit=0 | //bit=1 if last debounced changed from 1 to 0, else bit=0 | ||||
//bit=1 if last debounced changed from 0 to 1, else bit=0 | //bit=1 if last debounced changed from 0 to 1, else bit=0 | ||||
isRisingEdge = debouncedChanged & debounced; | isRisingEdge = debouncedChanged & debounced; | ||||
for (readMask=1, i=0; i < keyCount; readMask<<=1, i++) //for each key in row | |||||
for (readPosition=1, i=0; i < keyCount; readPosition<<=1, i++) //for each key in row | |||||
{ | { | ||||
//release before press avoids impossible key sequence | //release before press avoids impossible key sequence | ||||
if (readMask & isFallingEdge) //if key was released | |||||
if (readPosition & isFallingEdge) //if key was released | |||||
{ | { | ||||
ptrsKeys[i]->release(); | ptrsKeys[i]->release(); | ||||
} | } | ||||
if (readMask & isRisingEdge) //if key was pressed | |||||
if (readPosition & isRisingEdge) //if key was pressed | |||||
{ | { | ||||
ptrsKeys[i]->press(); | ptrsKeys[i]->press(); | ||||
keyWasPressed(); | keyWasPressed(); |
#include <Key.h> | #include <Key.h> | ||||
#include <ScannerInterface.h> | #include <ScannerInterface.h> | ||||
#include <Debouncer_Samples.h> | #include <Debouncer_Samples.h> | ||||
#include <Debouncer_Not.h> | |||||
/* | /* | ||||
strobePin has one of two formats: | strobePin has one of two formats: | ||||
* if refScanner a Scanner_uC, then strobePin is an Arduino pin number connected to this row | * if refScanner a Scanner_uC, then strobePin is an Arduino pin number connected to this row | ||||
* if refScanner a Scanner_IOE, then strobePin is bitwise, 1 indicating IC pin connected to this row | |||||
* otherwise strobePin is bitwise, 1 indicating an IC pin connected to this row | |||||
*/ | */ | ||||
class Row | class Row | ||||
{ | { | ||||
Key *const *const ptrsKeys; //array of Key pointers | Key *const *const ptrsKeys; //array of Key pointers | ||||
protected: | protected: | ||||
const uint8_t keyCount; //number of read pins | const uint8_t keyCount; //number of read pins | ||||
Debouncer_Samples debouncer; | |||||
//Debouncer_Samples debouncer; | |||||
Debouncer_Not debouncer; //todo | |||||
read_pins_t debounced; //bitwise state of keys after debouncing, 1=pressed, 0=released | read_pins_t debounced; //bitwise state of keys after debouncing, 1=pressed, 0=released | ||||
public: | public: | ||||
Row(ScannerInterface& refScanner, const uint8_t strobePin, | Row(ScannerInterface& refScanner, const uint8_t strobePin, |
} | } | ||||
/* scan() is called on every iteration of sketch loop(). | /* scan() is called on every iteration of sketch loop(). | ||||
strobePin is bitwise, 1 means that row pin is active. | |||||
scan() strobes the row's strobePin and retuns state of port's input pins. | scan() strobes the row's strobePin and retuns state of port's input pins. | ||||
Bitwise variables are 1 bit per key. | |||||
*/ | */ | ||||
read_pins_t Scanner_IOE::scan(const uint8_t strobePin) | read_pins_t Scanner_IOE::scan(const uint8_t strobePin) | ||||
{ | { | ||||
//strobe on | //strobe on | ||||
refPortWrite.write(strobePin, strobeOn); | refPortWrite.write(strobePin, strobeOn); | ||||
delayMicroseconds(3); //time to stabilize voltage | delayMicroseconds(3); //time to stabilize voltage | ||||
//delayMicroseconds(300); //todo | |||||
//read the port pins | //read the port pins | ||||
readState = refPortRead.read(); | readState = refPortRead.read(); |
Using smaller types on a 32-bit uC (Teensy LC) would accomplish nothing. | Using smaller types on a 32-bit uC (Teensy LC) would accomplish nothing. | ||||
*/ | */ | ||||
/* Use a read_pins_t size that covers the last 1 bit in bitwise Scanner_IOE::strobePin. | |||||
/* Use a read_pins_t size that covers all read pins of all Scanner objects i.e. | |||||
For Scanner_uC: read_pins_t bits >= Scanner_uC::readPinCount | |||||
For Scanner_ShiftRegsPISO: read_pins_t bits >= Scanner_ShiftRegsPISO::byte_count * 8 | |||||
(For Scanner_IOE: I/O expanders are assumed to have 8 bits per port or less) | |||||
*/ | */ | ||||
//typedef uint8_t read_pins_t; | |||||
typedef uint8_t read_pins_t; | |||||
//typedef uint16_t read_pins_t; | //typedef uint16_t read_pins_t; | ||||
typedef uint32_t read_pins_t; | |||||
//typedef uint32_t read_pins_t; | |||||
/* SAMPLE_COUNT_MACRO is used in Debouncer_Samples.h | /* SAMPLE_COUNT_MACRO is used in Debouncer_Samples.h | ||||
SAMPLE_COUNT_MACRO = 4 is very reliable for a keyboard. | SAMPLE_COUNT_MACRO = 4 is very reliable for a keyboard. |
| Layout | **0** | **1** | | | Layout | **0** | **1** | | ||||
|:------:|-------|-------| | |:------:|-------|-------| | ||||
| **0** | 1 | a | | |||||
| **1** | 2 | b | | |||||
| **0** | 1 | 2 | | |||||
| **1** | a | b | | |||||
*/ | */ | ||||
// ################## GLOBAL ################### | // ################## GLOBAL ################### | ||||
// ================= INCLUDES ================== | // ================= INCLUDES ================== | ||||
#include <ScanDelay.h> | |||||
#include <Code_Sc.h> | #include <Code_Sc.h> | ||||
#include <Row.h> | #include <Row.h> | ||||
#include <Scanner_uC.h> | #include <Scanner_uC.h> | ||||
#include <ScanDelay.h> | |||||
// ============ SPEED CONFIGURATION ============ | // ============ SPEED CONFIGURATION ============ | ||||
ScanDelay scanDelay(9000); | ScanDelay scanDelay(9000); | ||||
Code_Sc s_2(KEY_2); | Code_Sc s_2(KEY_2); | ||||
// =================== ROWS ==================== | // =================== ROWS ==================== | ||||
Key* ptrsKeys_0[] = { &s_1, &s_a }; | |||||
Key* ptrsKeys_0[] = { &s_1, &s_2 }; | |||||
uint8_t keyCount_0 = sizeof(ptrsKeys_0)/sizeof(*ptrsKeys_0); | uint8_t keyCount_0 = sizeof(ptrsKeys_0)/sizeof(*ptrsKeys_0); | ||||
Row row_0(scanner, 0, ptrsKeys_0, keyCount_0); | Row row_0(scanner, 0, ptrsKeys_0, keyCount_0); | ||||
Key* ptrsKeys_1[] = { &s_2, &s_b }; | |||||
Key* ptrsKeys_1[] = { &s_a, &s_b }; | |||||
uint8_t keyCount_1 = sizeof(ptrsKeys_1)/sizeof(*ptrsKeys_1); | uint8_t keyCount_1 = sizeof(ptrsKeys_1)/sizeof(*ptrsKeys_1); | ||||
Row row_1(scanner, 1, ptrsKeys_1, keyCount_1); | Row row_1(scanner, 1, ptrsKeys_1, keyCount_1); | ||||
This sketch: | This sketch: | ||||
is firmware for a simple 1-layer keyboard | is firmware for a simple 1-layer keyboard | ||||
runs on the first two rows and columns of a breadboard keyboard | |||||
runs on two rows and two columns of a breadboard keyboard | |||||
This layout table shows how keys are arranged on the keyboard: | This layout table shows how keys are arranged on the keyboard: | ||||
| Layout | **0** | **1** | | | Layout | **0** | **1** | | ||||
|:------:|-------|-------| | |:------:|-------|-------| | ||||
| **0** | shift | a | | |||||
| **1** | b | c | | |||||
| **0** | 1 | 2 | | |||||
| **1** | a | b | | |||||
The layout's row and column numbers are in the headers. | The layout's row and column numbers are in the headers. | ||||
Each cell in the table's body represents a key. | Each cell in the table's body represents a key. | ||||
The following sketch is annotated with a walk-through narrative enclosed in comment blocks. | The following sketch is annotated with a walk-through narrative enclosed in comment blocks. | ||||
Each comment block explains the next one or two lines of code. | |||||
Each comment block explains one or two lines of code after the comnent. | |||||
keybrd objects are instantiated under the "GLOBAL" heading. | keybrd objects are instantiated under the "GLOBAL" heading. | ||||
The keyboard runs at the end of the sketch, under the "MAIN" heading. | The keyboard runs at the end of the sketch, under the "MAIN" heading. | ||||
/* ================= INCLUDES ================== | /* ================= INCLUDES ================== | ||||
All the includes in this sketch are to keybrd library classes. | All the includes in this sketch are to keybrd library classes. | ||||
*/ | */ | ||||
#include <ScanDelay.h> | |||||
#include <Code_Sc.h> | #include <Code_Sc.h> | ||||
#include <Row_uC.h> | |||||
#include <Row.h> | |||||
#include <Scanner_uC.h> | |||||
#include <ScanDelay.h> | |||||
/* ============ SPEED CONFIGURATION ============ | /* ============ SPEED CONFIGURATION ============ | ||||
ScanDelay specifies microsecond between matrix scans. | |||||
Keyboard switches are made of moving contacts. | Keyboard switches are made of moving contacts. | ||||
When the contacts close, they bounce apart one or more times before making steady contact. | When the contacts close, they bounce apart one or more times before making steady contact. | ||||
ScanDelay gives the switches time to debounce. | ScanDelay gives the switches time to debounce. | ||||
ScanDelay specifies microsecond between matrix scans. | |||||
*/ | */ | ||||
ScanDelay scanDelay(9000); | ScanDelay scanDelay(9000); | ||||
/* ================ ACTIVE STATE =============== | |||||
The read pins detect which keys are pressed while a row is strobed. | |||||
STROBE_ON and STROBE_OFF define the logic levels for the strobe. | |||||
"Active low" means that if a switch is pressed (active), the read pin is low. | |||||
To make this sketch active low, STROBE_ON should be LOW (tutorial 6 coveres this in more detail). | |||||
*/ | |||||
const bool Scanner_uC::STROBE_ON = LOW; //set scanner for active low | |||||
const bool Scanner_uC::STROBE_OFF = HIGH; | |||||
/* ================= PINS ================= | |||||
Microcontroller 14 and 15 are connected to the matrix columns. | |||||
These readPins detect which keys are pressed while a row is strobed. | |||||
/* ================== SCANNER ================== | |||||
Microcontroller pins 14 and 15 are connected to the matrix columns. | |||||
sizeof() is used to compute the number of array elements. | sizeof() is used to compute the number of array elements. | ||||
This eliminates the risk of forgetting to update the count | |||||
This eliminates the risk of a programmer forgetting to update a count | |||||
after adding or removing an element from the array. | after adding or removing an element from the array. | ||||
*/ | */ | ||||
uint8_t readPins[] = {14, 15}; | uint8_t readPins[] = {14, 15}; | ||||
uint8_t READ_PIN_COUNT = sizeof(readPins)/sizeof(*readPins); | |||||
uint8_t readPinCount = sizeof(readPins)/sizeof(*readPins); | |||||
/* | |||||
The first parameter of the scanner constructor defines the logic level for the strobes. | |||||
"Active low" means that if a switch is pressed (active), the read pin is low. | |||||
The scanner uses readPins, readPinCount to read the colums. | |||||
*/ | |||||
Scanner_uC scanner(LOW, readPins, readPinCount); | |||||
/* HOW SCANNER OBJECTS WORK | |||||
The scanner object strobes a row. | |||||
If a key is pressed, the LOW strobe pulls that readPin LOW. | |||||
Then the scanner reads its readPins. | |||||
*/ | |||||
/* =================== CODES =================== | /* =================== CODES =================== | ||||
Four Codes are instantiated, one for each key in the layout. | Four Codes are instantiated, one for each key in the layout. | ||||
*/ | */ | ||||
Code_Sc s_a(KEY_A); | Code_Sc s_a(KEY_A); | ||||
Code_Sc s_b(KEY_B); | Code_Sc s_b(KEY_B); | ||||
Code_Sc s_c(KEY_C); | |||||
Code_Sc s_shift(MODIFIERKEY_LEFT_SHIFT); | |||||
Code_Sc s_1(KEY_1); | |||||
Code_Sc s_2(KEY_2); | |||||
/* =================== ROWS ==================== | /* =================== ROWS ==================== | ||||
Here we pack Code objects into Row objects. | Here we pack Code objects into Row objects. | ||||
The Row objects names in this sketch start with a "row_" followed by a row number. | The Row objects names in this sketch start with a "row_" followed by a row number. | ||||
Row_uC constructor has four parameters: | |||||
1) strobePin connected to the row. | |||||
2) readPins[] connected to the colums. | |||||
3) the number of readPins. | |||||
4) ptrsKeys[] containing all the Code objects of the row, one Code object per key. | |||||
Row constructor has four parameters: | |||||
1) scanner | |||||
2) strobePin connected to the row. | |||||
3) ptrsKeys[] containing all the Code objects of the row, one Code object per key. | |||||
4) the number of keys in the row. | |||||
*/ | */ | ||||
Key* ptrsKeys_0[] = { &s_shift, &s_a }; | |||||
Row_uC row_0(0, readPins, READ_PIN_COUNT, ptrsKeys_0); | |||||
Key* ptrsKeys_0[] = { &s_1, &s_2 }; | |||||
uint8_t keyCount_0 = sizeof(ptrsKeys_0)/sizeof(*ptrsKeys_0); | |||||
Row row_0(scanner, 0, ptrsKeys_0, keyCount_0); | |||||
Key* ptrsKeys_1[] = { &s_b, &s_c }; | |||||
Row_uC row_1(1, readPins, READ_PIN_COUNT, ptrsKeys_1); | |||||
Key* ptrsKeys_1[] = { &s_a, &s_b }; | |||||
uint8_t keyCount_1 = sizeof(ptrsKeys_1)/sizeof(*ptrsKeys_1); | |||||
Row row_1(scanner, 1, ptrsKeys_1, keyCount_1); | |||||
/* ################### MAIN #################### | /* ################### MAIN #################### | ||||
setup() is used to initialize the keyboard firmware. Keyboard.begin() should be called once. | setup() is used to initialize the keyboard firmware. Keyboard.begin() should be called once. | ||||
And when a key press is detected, the row sends the key's scancode. | And when a key press is detected, the row sends the key's scancode. | ||||
scanDelay creates time intervals between matrix scans. | scanDelay creates time intervals between matrix scans. | ||||
A debouncer uses this time interval to debounce key presses and releases. | |||||
The delay is needed so that the debouncer is not overwelmed. | |||||
*/ | */ | ||||
void loop() | void loop() | ||||
{ | { |
| **1** | fn | b 2 | | | **1** | fn | b 2 | | ||||
Each cell in the table's body represents a key. | Each cell in the table's body represents a key. | ||||
The layered keys in row 0 have two layers; one character for each layer. | |||||
The layered keys in column 1 have two layers; one character for each layer. | |||||
Letters 'a' and 'b' are on the normal layer. Numbers '1' and '2' are on the fn layer. | Letters 'a' and 'b' are on the normal layer. Numbers '1' and '2' are on the fn layer. | ||||
Holding the fn key down makes it the active layer. Releasing the fn key restores the normal layer. | Holding the fn key down makes it the active layer. Releasing the fn key restores the normal layer. | ||||
*/ | */ | ||||
#include <Key_LayeredKeysArray.h> | #include <Key_LayeredKeysArray.h> | ||||
//Matrix | //Matrix | ||||
#include <Row_uC.h> | |||||
#include <Row.h> | |||||
#include <Scanner_uC.h> | |||||
#include <ScanDelay.h> | #include <ScanDelay.h> | ||||
// ============ SPEED CONFIGURATION ============ | // ============ SPEED CONFIGURATION ============ | ||||
ScanDelay scanDelay(9000); | ScanDelay scanDelay(9000); | ||||
// ================ ACTIVE STATE =============== | |||||
const bool Scanner_uC::STROBE_ON = LOW; | |||||
const bool Scanner_uC::STROBE_OFF = HIGH; | |||||
// =================== PINS ==================== | |||||
// ================== SCANNER ================== | |||||
uint8_t readPins[] = {14, 15}; | uint8_t readPins[] = {14, 15}; | ||||
uint8_t READ_PIN_COUNT = sizeof(readPins)/sizeof(*readPins); | |||||
uint8_t readPinCount = sizeof(readPins)/sizeof(*readPins); | |||||
Scanner_uC scanner(LOW, readPins, readPinCount); | |||||
/* =================== CODES =================== | /* =================== CODES =================== | ||||
The CODES section instantiates six codes, one for each item in the layout. | The CODES section instantiates six codes, one for each item in the layout. | ||||
/* | /* | ||||
NORMAL=0 and FN=1. LayerState's default layer id is 0. | NORMAL=0 and FN=1. LayerState's default layer id is 0. | ||||
The Code_LayerHold constructor has two parameters: | The Code_LayerHold constructor has two parameters: | ||||
1) the layer that will be active while the key is held down. | |||||
2) a LayerState | |||||
1) the layer that will be active while the key is held down | |||||
2) a LayerState that will keep track of the active layer | |||||
When l_fn is pressed, it tells layerState to change the active layer to 1. | When l_fn is pressed, it tells layerState to change the active layer to 1. | ||||
When l_fn is released, it tells layerState that layer 1 is released, and layerState restores the default layer. | When l_fn is released, it tells layerState that layer 1 is released, and layerState restores the default layer. | ||||
*/ | */ | ||||
/* =================== KEYS ==================== | /* =================== KEYS ==================== | ||||
Here we pack Codes into keys. | Here we pack Codes into keys. | ||||
The Key_LayeredKeysArray constructor takes one array of Code pointers - one Code object per layer. | The Key_LayeredKeysArray constructor takes one array of Code pointers - one Code object per layer. | ||||
Key_LayeredKeysArray uses layer id numbers as array indexes. | |||||
Thus Key_LayeredKeysArray calls the Code corresponding to the active layer id. | |||||
The Key object names in this sketch start with a "k_" followed by row-column coordinates. | The Key object names in this sketch start with a "k_" followed by row-column coordinates. | ||||
*/ | */ | ||||
LayerStateInterface& Key_LayeredKeysArray::refLayerState = layerState; | LayerStateInterface& Key_LayeredKeysArray::refLayerState = layerState; | ||||
/* HOW LAYERED OBJECTS WORK | /* HOW LAYERED OBJECTS WORK | ||||
When a Key_LayeredKeysArray object is pressed, it gets the active layer id from layerState | |||||
It then uses the layer id as an array index to send the scancode for the active layer. | |||||
When a Key_LayeredKeysArray object is pressed, it gets the active layer id from layerState. | |||||
It then uses the layer id as an array index to call the Code of the active layer. | |||||
The Code object then sends its scancode over USB. | |||||
*/ | */ | ||||
/* =================== ROWS ==================== | /* =================== ROWS ==================== | ||||
Arrays ptrsKeys_0[] and ptrsKeys_1[] contain both Code pointers and Key pointers. | Arrays ptrsKeys_0[] and ptrsKeys_1[] contain both Code pointers and Key pointers. | ||||
*/ | */ | ||||
Key* const ptrsKeys_0[] = { &s_shift, &k_01 }; | Key* const ptrsKeys_0[] = { &s_shift, &k_01 }; | ||||
Row_uC row_0(0, readPins, READ_PIN_COUNT, ptrsKeys_0); | |||||
uint8_t keyCount_0 = sizeof(ptrsKeys_0)/sizeof(*ptrsKeys_0); | |||||
Row row_0(scanner, 0, ptrsKeys_0, keyCount_0); | |||||
Key* const ptrsKeys_1[] = { &l_fn, &k_11 }; | Key* const ptrsKeys_1[] = { &l_fn, &k_11 }; | ||||
Row_uC row_1(1, readPins, READ_PIN_COUNT, ptrsKeys_1); | |||||
uint8_t keyCount_1 = sizeof(ptrsKeys_1)/sizeof(*ptrsKeys_1); | |||||
Row row_1(scanner, 1, ptrsKeys_1, keyCount_1); | |||||
// ################### MAIN #################### | // ################### MAIN #################### | ||||
void setup() | void setup() |
/* keybrd_4c_split_with_IOE.ino | /* keybrd_4c_split_with_IOE.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** | | | Left | **0** | **1** | | Right | **0** | **1** | | ||||
|:-----:|-------|-------| |:-----:|-------|-------| | |||||
|:-----:|-------|-------|-|:-----:|-------|-------| | |||||
| **1** | 1 | 2 | | **1** | 3 | 4 | | | **1** | 1 | 2 | | **1** | 3 | 4 | | ||||
| **0** | a | b | | **0** | c | d | | | **0** | a | b | | **0** | c | d | | ||||
*/ | */ | ||||
ScanDelay scanDelay(9000); | ScanDelay scanDelay(9000); | ||||
// ================ LEFT SCANNER =============== | // ================ LEFT SCANNER =============== | ||||
/* | |||||
Left matrix rows work the same as the ones in keybrd_2_single-layer.ino | |||||
*/ | |||||
uint8_t readPins[] = {14, 15}; | uint8_t readPins[] = {14, 15}; | ||||
uint8_t readPinCount = sizeof(readPins)/sizeof(*readPins); | |||||
const uint8_t READPIN_COUNT = sizeof(readPins)/sizeof(*readPins); | |||||
Scanner_uC scanner_L(LOW, readPins, readPinCount); | |||||
Scanner_uC scanner_L(LOW, readPins, READPIN_COUNT); | |||||
// =============== RIGHT SCANNER =============== | // =============== RIGHT SCANNER =============== | ||||
const uint8_t PortIOE::DEVICE_ADDR = 0x20; //MCP23S17 address, all 3 ADDR pins are grounded | |||||
/* | |||||
The right matrix is scanned by an I/O expander. | |||||
The I/O expander device address is configured by hardware pins. | |||||
DEVICE_ADDR is a static variable of class PortIOE. | |||||
*/ | |||||
const uint8_t PortIOE::DEVICE_ADDR = 0x20; //MCP23S17 address with all 3 ADDR pins are grounded | |||||
todo explain port num and shift notation << | |||||
/* | |||||
port_B stobes the row while port_A reads the colums. | |||||
port_A is assigned port identification number 0. | |||||
port_A is assigned to portRead, which reads port_A pins 0 and 1. | |||||
"<<" (bit shift left) and "|" (or) are bitwise operators. | |||||
Pin numbers to be read are to the right of "1<<" and delimited by "|". | |||||
*/ | |||||
PortIOE port_A(0); | PortIOE port_A(0); | ||||
PortRead_MCP23S17 portRead_A(port_A, 1<<0 | 1<<1 ); | |||||
PortRead_MCP23S17 portRead(port_A, 1<<0 | 1<<1 ); | |||||
/* | |||||
port_B is assigned port identification number 1. | |||||
port_B is assigned to portWrite. | |||||
*/ | |||||
PortIOE port_B(1); | PortIOE port_B(1); | ||||
//PortWrite_MCP23S17 portWrite_B(port_B); //for LEDs | |||||
PortWrite_MCP23S17 portWrite_B(port_B); | |||||
//PortWrite_MCP23S17 portWrite(port_B); //for LEDs todo | |||||
PortWrite_MCP23S17 portWrite(port_B); | |||||
Scanner_IOE scanner_R(LOW, portWrite_B, portRead_A); | |||||
Scanner_IOE scanner_R(LOW, portWrite, portRead); | |||||
// =================== CODES =================== | // =================== CODES =================== | ||||
Code_Sc s_a(KEY_A); | Code_Sc s_a(KEY_A); | ||||
Code_Sc s_4(KEY_4); | Code_Sc s_4(KEY_4); | ||||
// =================== ROWS ==================== | // =================== ROWS ==================== | ||||
/* | |||||
Left row names contain the letter 'L', while right row names conatain the letter 'R'. | |||||
The first parameteer of a Row constructor specifies the scanner. | |||||
The second parameter of the Row constructor specifies the Row's strobePin. | |||||
strobePin has one of two formats: | |||||
* if refScanner a Scanner_uC, then strobePin is an Arduino pin number connected to this row | |||||
* otherwise strobePin is bitwise, 1 indicating an IC pin connected to this row | |||||
*/ | |||||
// ---------------- LEFT ROWS ------------------ | // ---------------- LEFT ROWS ------------------ | ||||
/* The left rows have a Scanner_uC and Arduino pin numbers to strobe. | |||||
*/ | |||||
Key* ptrsKeys_L0[] = { &s_1, &s_2 }; | Key* ptrsKeys_L0[] = { &s_1, &s_2 }; | ||||
const uint8_t KEY_COUNT_L0 = sizeof(ptrsKeys_L0)/sizeof(*ptrsKeys_L0); | const uint8_t KEY_COUNT_L0 = sizeof(ptrsKeys_L0)/sizeof(*ptrsKeys_L0); | ||||
Row row_L0(scanner_L, 0, ptrsKeys_L0, KEY_COUNT_L0); | Row row_L0(scanner_L, 0, ptrsKeys_L0, KEY_COUNT_L0); | ||||
Row row_L1(scanner_L, 1, ptrsKeys_L1, KEY_COUNT_L1); | Row row_L1(scanner_L, 1, ptrsKeys_L1, KEY_COUNT_L1); | ||||
// ---------------- RIGHT ROWS ----------------- | // ---------------- RIGHT ROWS ----------------- | ||||
/* | |||||
The right rows have a Scanner_IOE and pin bits to strobe. | |||||
*/ | |||||
Key* ptrsKeys_R0[] = { &s_3, &s_4 }; | Key* ptrsKeys_R0[] = { &s_3, &s_4 }; | ||||
const uint8_t KEY_COUNT_R0 = sizeof(ptrsKeys_R0)/sizeof(*ptrsKeys_R0); | const uint8_t KEY_COUNT_R0 = sizeof(ptrsKeys_R0)/sizeof(*ptrsKeys_R0); | ||||
Row row_R0(scanner_R, 1<<0, ptrsKeys_R0, KEY_COUNT_R0); | Row row_R0(scanner_R, 1<<0, ptrsKeys_R0, KEY_COUNT_R0); |
========================= | ========================= | ||||
The first two tutorials are intended to be read in sequence: | The first two tutorials are intended to be read in sequence: | ||||
* Tutorial 1 builds a breadboard keyboard and covers basic keyboard-hardware knowledge. | * Tutorial 1 builds a breadboard keyboard and covers basic keyboard-hardware knowledge. | ||||
* Tutorial 2 covers basic keybrd sketch knowledge needed to understand the remaining tutorials. | |||||
* Tutorial 2 covers basic keybrd-sketch knowledge needed to understand the remaining tutorials. | |||||
Tutorials from 3 up can be read in any order. | Tutorials from 3 up can be read in any order. | ||||
Tutorials 2 through 7 use the keyboard breadboard that was built in tutorial 1. | Tutorials 2 through 7 use the keyboard breadboard that was built in tutorial 1. |
========================================== | ========================================== | ||||
Port classes are the keybrd library's interface to I/O expander ports. | Port classes are the keybrd library's interface to I/O expander ports. | ||||
To write a new Port class: | |||||
Steps to writing a new port class: | |||||
1. Get a copy of the I/O expander's datasheet. | 1. Get a copy of the I/O expander's datasheet. | ||||
2. An I/O expander will use one of two communication protocols: [http://www.byteparadigm.com/applications/introduction-to-i2c-and-spi-protocols/](SPI or I2C). | 2. An I/O expander will use one of two communication protocols: [http://www.byteparadigm.com/applications/introduction-to-i2c-and-spi-protocols/](SPI or I2C). | ||||
Refer to the [Arduino SPI](https://www.arduino.cc/en/Reference/SPI) | Refer to the [Arduino SPI](https://www.arduino.cc/en/Reference/SPI) | ||||
or [Arduino Wire (I2C)](https://www.arduino.cc/en/Reference/Wire) library | or [Arduino Wire (I2C)](https://www.arduino.cc/en/Reference/Wire) library | ||||
3. Get familiar with your I/O expander. | 3. Get familiar with your I/O expander. | ||||
Different I/O expanders use different commands (a.k.a. operation codes). | |||||
* Different I/O expanders use different commands (a.k.a. operation codes). | |||||
Refer to your I/O expander's datasheet for read and write commands. | Refer to your I/O expander's datasheet for read and write commands. | ||||
Search for Arduino sketch examples containing your I/O expander | |||||
* Search for Arduino sketch examples containing your I/O expander | |||||
([sumotoy](https://github.com/sumotoy/gpio_expander) has a large gpio expander library). | ([sumotoy](https://github.com/sumotoy/gpio_expander) has a large gpio expander library). | ||||
Write very simple read and write examples for your I/O expander. | |||||
Simple SPI I/O expander examples: | |||||
todo link, pictures | |||||
/home/wolfv/Documents/Arduino/demo/IOE_MCP23S17_read/ todo internal pull-up resistors | |||||
/home/wolfv/Documents/Arduino/demo/IOE_MCP23S17_write/ | |||||
Simple I2C I/O expander examples: | |||||
todo link, pictures | |||||
read | |||||
write | |||||
4. Study other keybrd Port classes. | |||||
Port classes for SPI MCP23S17 I/O expander: | |||||
*todo | |||||
* | |||||
* | |||||
Port classes for I2C PCA9655E I/O expander: | |||||
* PortWrite_PCA9655E todo link | |||||
* PortRead_PCA9655E | |||||
* LED_PCA9655E | |||||
5. Write similar Port classes for your I/O expander. | |||||
4. Study a simple keybrd sketch that uses an I/O expander. | |||||
* [SPI I/O expander example sketch](keybrd_4c_split_with_IOE/keybrd_4c_split_with_IOE.ino) | |||||
* [I2C I/O expander example sketch](../examples/keybrd_PCA9655E/keybrd_PCA9655E.ino) | |||||
5. Study other keybrd port classes. | |||||
* SPI I/O expander port classes: PortMCP23S17 PortWrite_MCP23S17 PortRead_MCP23S17 | |||||
* I2C I/O expander port classes: PortWrite_PCA9655E PortRead_PCA9655E | |||||
6. Write the port classes for your I/O expander. | |||||
Debugging I/O expander code is hard because SPI or I2C protocol adds a level of indirection. | Debugging I/O expander code is hard because SPI or I2C protocol adds a level of indirection. | ||||
<br> | <br> |
In this tutorial, you will build a breadboard keyboard with 4 keys. | In this tutorial, you will build a breadboard keyboard with 4 keys. | ||||
The keyboad will be used in tutorials 2 through 7. | The keyboad will be used in tutorials 2 through 7. | ||||
When you finish this tutorial you will have a working keyboard and understand how a key matrix works. | |||||
When you finish this tutorial you will have a working keyboard and an understanding of how a key matrix works. | |||||
Why a solderless breadboard keyboard is useful | Why a solderless breadboard keyboard is useful | ||||
---------------------------------------------- | ---------------------------------------------- | ||||
* learning the firmware development workflow | * learning the firmware development workflow | ||||
* prototyping circuits before making a PCB | * prototyping circuits before making a PCB | ||||
Arduino simulation software is an alternative to breadboards; I haven't tried that. | |||||
Breadboard keyboard starter kit | Breadboard keyboard starter kit | ||||
------------------------------- | ------------------------------- | ||||
The parts needed to build the tutorial breadboard keyboards are listed in [breadboard_keyboard_supplies.ods](breadboard_keyboard_supplies.ods). | The parts needed to build the tutorial breadboard keyboards are listed in [breadboard_keyboard_supplies.ods](breadboard_keyboard_supplies.ods). | ||||
Switch-diode pairs, in series, connect rows to columns. | Switch-diode pairs, in series, connect rows to columns. | ||||
Tutorials 2 and 3 use the basic breadboard keyboard pictured above. | |||||
Tutorials 4, 5, and 6 will add more components to the basic breadboard keyboard. | |||||
Tutorials 2 and 3 use the same basic breadboard keyboard pictured above. | |||||
Tutorials 4, 5, and 6 add more components to the basic breadboard keyboard. | |||||
Positioning components as shown in the picture will provide space for those components. | Positioning components as shown in the picture will provide space for those components. | ||||
Breadboard keyboard assembly instructions: | Breadboard keyboard assembly instructions: | ||||
1. Bend and cut leads to fit breadboard. | |||||
* tactile-switch-lead | |||||
* diodes (save the cut offs for steps 2, 3, and tutorial 4) | |||||
1. Shape leads to fit breadboard. | |||||
* cut tactile-switch leads to length | |||||
* bend and cut diode leads (save the cut offs for steps 2, 3, and tutorial 4) | |||||
![bend diodes](keybrd_1_breadboard/diodes_bend_en_masse.JPG "bend diodes") | ![bend diodes](keybrd_1_breadboard/diodes_bend_en_masse.JPG "bend diodes") | ||||
* Teensy LC is on the left | * Teensy LC is on the left | ||||
* switch leads are oriented to connect diodes to columns (pictured below) | * switch leads are oriented to connect diodes to columns (pictured below) | ||||
* diode cut offs connect terminal strips into columns | * diode cut offs connect terminal strips into columns | ||||
* diodes connect switches to rows; orient diodes with cathode (banded end) towards the row (blue bus strip) | |||||
* diodes connect switches to rows; orient diodes with cathode (banded end) towards the row (blue bus) | |||||
![switch orientation](keybrd_1_breadboard/switch_orientation.JPG "switch orientation") | ![switch orientation](keybrd_1_breadboard/switch_orientation.JPG "switch orientation") | ||||
Compile and load the [keybrd_1_breadboard.ino](/tutorials/keybrd_1_breadboard/keybrd_1_breadboard.ino) sketch into the keyboard's controller. | Compile and load the [keybrd_1_breadboard.ino](/tutorials/keybrd_1_breadboard/keybrd_1_breadboard.ino) sketch into the keyboard's controller. | ||||
The operating system will take 1 to 6 seconds to recognize the USB keyboard. | The operating system will take 1 to 6 seconds to recognize the USB keyboard. | ||||
Then pressing the keys should type the characters 1, a, b, c. | |||||
Then pressing the keys should type the characters 1, 2, a, b. | |||||
Congratulations, you have a working keyboard. | |||||
How a key matrix works | How a key matrix works | ||||
---------------------- | ---------------------- | ||||
Congratulations, you have a working breadboard keyboard. | |||||
Now we fill in some details of how it all works. | Now we fill in some details of how it all works. | ||||
This excellent article explains how key matrix, diodes, and ghosting work: | This excellent article explains how key matrix, diodes, and ghosting work: | ||||
> Output pins power rows and input pins detect the power on columns. | > Output pins power rows and input pins detect the power on columns. | ||||
The keybrd library uses the word "strobe". | |||||
The keybrd library uses the word "strobe", which means powering one row for a very short time. | |||||
Strobe pins are output pins connected to rows. | Strobe pins are output pins connected to rows. | ||||
One row at a time is strobed for the purpose of reading input pins. | One row at a time is strobed for the purpose of reading input pins. | ||||
Exercises | Exercises | ||||
--------- | --------- | ||||
1) Read the three class definitions #included in the sketch. | |||||
1) Read the four class definitions #included in the sketch. | |||||
Classes are defined in the [keybrd library](../src/). | Classes are defined in the [keybrd library](../src/). | ||||
2) Add a third column to the breadboard keyboard and sketch. | |||||
2) Add a fifth key to the breadboard keyboard and sketch. | |||||
Not all rows have to have the same number of keys. | |||||
The scanner should have enough readPins to cover all the keys of the longest row. | |||||
| Layout |**0**|**1**|**2**| | | Layout |**0**|**1**|**2**| | ||||
|:------:|:---:|:---:|:---:| | |:------:|:---:|:---:|:---:| | ||||
| **0** | k | e | y | | |||||
| **1** | b | r | d | | |||||
| **0** | 1 | 2 | 3 | | |||||
| **1** | a | b | | |||||
<br> | <br> | ||||
<a rel="license" href="https://creativecommons.org/licenses/by/4.0/"><img alt="Creative Commons License" style="border-width:0" src="https://licensebuttons.net/l/by/4.0/88x31.png" /></a><br /><span xmlns:dct="http://purl.org/dc/terms/" property="dct:title">keybrd tutorial</span> by <a xmlns:cc="https://creativecommons.org/ns" href="https://github.com/wolfv6/keybrd" property="cc:attributionName" rel="cc:attributionURL">Wolfram Volpi</a> is licensed under a <a rel="license" href="https://creativecommons.org/licenses/by/4.0/">Creative Commons Attribution 4.0 International License</a>.<br />Permissions beyond the scope of this license may be available at <a xmlns:cc="https://creativecommons.org/ns" href="https://github.com/wolfv6/keybrd/issues/new" rel="cc:morePermissions">https://github.com/wolfv6/keybrd/issues/new</a>. | <a rel="license" href="https://creativecommons.org/licenses/by/4.0/"><img alt="Creative Commons License" style="border-width:0" src="https://licensebuttons.net/l/by/4.0/88x31.png" /></a><br /><span xmlns:dct="http://purl.org/dc/terms/" property="dct:title">keybrd tutorial</span> by <a xmlns:cc="https://creativecommons.org/ns" href="https://github.com/wolfv6/keybrd" property="cc:attributionName" rel="cc:attributionURL">Wolfram Volpi</a> is licensed under a <a rel="license" href="https://creativecommons.org/licenses/by/4.0/">Creative Commons Attribution 4.0 International License</a>.<br />Permissions beyond the scope of this license may be available at <a xmlns:cc="https://creativecommons.org/ns" href="https://github.com/wolfv6/keybrd/issues/new" rel="cc:morePermissions">https://github.com/wolfv6/keybrd/issues/new</a>. |
![basic breadboard keyboard](keybrd_1_breadboard/breadboard_keyboard_2x2.JPG "basic breadboard keyboard") | ![basic breadboard keyboard](keybrd_1_breadboard/breadboard_keyboard_2x2.JPG "basic breadboard keyboard") | ||||
Read the sketch annotations to understand how multi-layer keyboards work. | |||||
The sketch annotations explain how multi-layer keyboards work. | |||||
The sketch uses three layer-scheme classes: | The sketch uses three layer-scheme classes: | ||||
* LayerState | * LayerState | ||||
* Code_LayerHold | * Code_LayerHold | ||||
The following pseudo code is of three keybrd library classes. | The following pseudo code is of three keybrd library classes. | ||||
It has just enough detail to show the internal workings of layer schemes. | It has just enough detail to show the internal workings of layer schemes. | ||||
**Key_Layer** objects change the active layer when pressed. | |||||
**Code_Layer** objects change the active layer when pressed. | |||||
The "layer" variable is a layer id number. | The "layer" variable is a layer id number. | ||||
When a Key_Layer object is pressed, it tells LayerState to update the active layer. | |||||
When a Code_Layer object is pressed, it tells LayerState to update the active layer. | |||||
``` | ``` | ||||
class Key_Layer | |||||
class Code_Layer | |||||
{ | { | ||||
int layer; | int layer; | ||||
LayerState& refLayerState; | LayerState& refLayerState; | ||||
``` | ``` | ||||
**Key_LayeredKeysArray** objects contain an array of keys, one key for each layer. | **Key_LayeredKeysArray** objects contain an array of keys, one key for each layer. | ||||
Key_LayeredKeysArray use layer ids as array indexes. | |||||
Key_LayeredKeysArray objects use layer ids as Key_LayeredKeysArray indexes. | |||||
When a Key_LayeredKeysArray object is pressed, it gets the active layer from LayerState, and sends the corresponding key. | When a Key_LayeredKeysArray object is pressed, it gets the active layer from LayerState, and sends the corresponding key. | ||||
``` | ``` | ||||
class Key_LayeredKeysArray | class Key_LayeredKeysArray | ||||
Dependency diagram | Dependency diagram | ||||
``` | ``` | ||||
+-----------+ | |||||
| Key_Layer | | |||||
+-----------+ | |||||
+------------+ | |||||
| Code_Layer | | |||||
+------------+ | |||||
| | | | ||||
|setActiveLayer() | |setActiveLayer() | ||||
| | | | ||||
There are several layer scheme-classes to choose from. | There are several layer scheme-classes to choose from. | ||||
You can view all the class definitions in the [keybrd library](../src/). | You can view all the class definitions in the [keybrd library](../src/). | ||||
Key_Layer classes include: | |||||
Code_Layer classes include: | |||||
* Code_LayerHold | * Code_LayerHold | ||||
* Code_LayerLock | * Code_LayerLock | ||||
* Key_LayeredKeysArray | * Key_LayeredKeysArray | ||||
* Code_LayeredScSc | * Code_LayeredScSc | ||||
* Code_LayeredCodeSc | * Code_LayeredCodeSc | ||||
* Code_LayeredCodeCode | |||||
The basic LayerState provided by the keybrd library is sufficient for implementing ordinary layer schemes. | The basic LayerState provided by the keybrd library is sufficient for implementing ordinary layer schemes. | ||||
For experimental layer schemes, you would need to create a custom LayerState class, and possibly Key_Layer and Key_Layered custom layer classes as well. | |||||
For experimental layer schemes, you would need to create a custom LayerState class, and possibly custom Code_Layer and Key_Layered classes as well. | |||||
Single-layer Codes | Single-layer Codes | ||||
------------------ | ------------------ |
keybrd Tutorial 4b - split keyboard with shift registers | |||||
======================================================== | |||||
When you finish this tutorial you will be able to be able to modify a split keybrd sketch with 10 to 24 keys on the peripheral hand. | |||||
Overview of split keyboard with shift registers | |||||
------------------------------------------------ | |||||
Only the right matrix is shown. The left matrix was omitted to reduce clutter. | |||||
The layout has 2 rows and 7 columns. | |||||
Electronically, the matrix only has one row. | |||||
Diodes are not needed because there is only one row. | |||||
The two black rectangles are shift registers daisy chained together. | |||||
Shift register details are in the SN74HC165N datasheet. | |||||
![breadboard keyboard with shift_registers](keybrd_4b_split_keyboard_with_shift_registers/shift_reg_front.JPG ) | |||||
Building a split breadboard keyboard with shift registers | |||||
--------------------------------------------------------- | |||||
Add components to the breadboard as shown in the picture. | |||||
Each shift register has a small notch on one end to identify pin 1. | |||||
In the picture, pin 1s are on the left end. | |||||
Shift registers are chained together by colored wires that lay flat on the breadboard. | |||||
Each shift register has 8 parallel input pins, 4 on each side. | |||||
There are 14 keys, so 2 of the input pins are unused. | |||||
Used input pins are connected to 10k pull-down resistor which are grounded (blue bus). | |||||
Unused input pins are grounded (blue bus). | |||||
A decoupling capacitor between the power and ground wires prevents power disturbance. | |||||
Switches are connected to power (red bus) and shift register input pins (jumpers). | |||||
I apologize for not having a schematic. This table should help you figure out the pictures: | |||||
``` | |||||
74HC165 left (lower half of breadboard) | |||||
NAME PIN# DESCRIPTION TO TEENSY LC PIN# CHAIN | |||||
SH/LD 1 shift or load input CS0 10 green wire | |||||
CLK 2 clock input SCK0 13 yellow wire | |||||
D4 3 parallel input blue bus | |||||
D5 4 parallel input blue bus | |||||
D6 5 parallel input blue bus | |||||
D7 6 parallel input blue bus | |||||
/QH 7 ~serial output | |||||
GND 8 ground gnd blue bus | |||||
74HC165 right (upper half of breadboard) | |||||
NAME PIN# DESCRIPTION TO TEENSY LC PIN# CHAIN | |||||
VCC 16 power pin 3.3V red wire | |||||
CLK INH 15 clock inhibit blue bus | |||||
D3 14 parallel input blue bus | |||||
D2 13 parallel input blue bus | |||||
D1 12 parallel input blue bus | |||||
D0 11 parallel input blue bus | |||||
SER 10 serial input blue wire to next QH | |||||
QH 9 serial output MISO0 12 blue wire to previous SER | |||||
``` | |||||
Sketch for split keyboard with shift registers | |||||
---------------------------------------------- | |||||
[keybrd_4b_split_keyboard_with_shift_registers.ino](keybrd_4b_split_keyboard_with_shift_registers/keybrd_4b_split_keyboard_with_shift_registers.ino) is a simple sketch with two shift registers. | |||||
The sketch will run on the above breadboard keyboard. | |||||
The sketch has code for both left and right matrix. | |||||
Notice that the left matrix is active low, while the right matrix is active high. | |||||
Exercises | |||||
--------- | |||||
1. Guess what happens if an unused input pin is not grounded? Try it. | |||||
2. Add a left matrix to Teensy. | |||||
There is room between Teensy and the shift registers for a 1-column matrix. | |||||
The bus strips are occupied by the right keys, so use terminal strips instead. | |||||
Other wise it is similar to the 2-column matrix in [tutorial_1_breadboard_keyboard.md](tutorial_1_breadboard_keyboard.md). | |||||
<br> | |||||
<a rel="license" href="https://creativecommons.org/licenses/by/4.0/"><img alt="Creative Commons License" style="border-width:0" src="https://licensebuttons.net/l/by/4.0/88x31.png" /></a><br /><span xmlns:dct="http://purl.org/dc/terms/" property="dct:title">keybrd tutorial</span> by <a xmlns:cc="https://creativecommons.org/ns" href="https://github.com/wolfv6/keybrd" property="cc:attributionName" rel="cc:attributionURL">Wolfram Volpi</a> is licensed under a <a rel="license" href="https://creativecommons.org/licenses/by/4.0/">Creative Commons Attribution 4.0 International License</a>.<br />Permissions beyond the scope of this license may be available at <a xmlns:cc="https://creativecommons.org/ns" href="https://github.com/wolfv6/keybrd/issues/new" rel="cc:morePermissions">https://github.com/wolfv6/keybrd/issues/new</a>. | |||||
## parts for breadboard_keyboard_supplies.ods | |||||
keybrd Tutorial 4 - split keyboard with I/O Expander | |||||
==================================================== | |||||
When you finish 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_2x5_labeled.jpg "2x5 breadboard keyboard") | |||||
The breadboard's four bus strips are used as rows. | |||||
Two rows (blue buses) are connected to the microcontroller. | |||||
Two rows (red buses) are connected to the I/O expander. | |||||
The I/O expander is a MCP23S17. | |||||
It has a small notch on one end, which identifies pin 1. | |||||
In the picture, pin 1 is on the right end. | |||||
The MCP23S17 communicates via SPI protocol, where Teensy LC is the master, and MCP23S17 is slave. | |||||
The Teensy LC and MCP23S17 are connected by 6 jumper wires: | |||||
|CONNECTION |Teensy LC|MCP23S17| | |||||
|:------------------:|---------|--------| | |||||
|ground | GND | VSS | | |||||
|power | 3.3v | VDD | | |||||
|Serial Clock | SCK0 | SCK | | |||||
|Master Out, Slave In| MOSI0 | SI | | |||||
|Master In, Slave Out| MISO0 | SO | | |||||
|Chip Select | CS0 | /CS | | |||||
A decoupling capacitor suppresses high-frequency noise from the power supply. | |||||
MCP23S17's I/O expander address is configured by hardware pins. | |||||
The MCP23S17 with all address pins grounded has an device address of 0x20. | |||||
The MCP23S17's /RESET pin is connected to VDD. | |||||
The MCP23S17 I/O expander has two ports. Each port has eight pins. | |||||
Port B is connected to the matrix's rows. Port A is connected to the matrix's columns. | |||||
Building a split keyboard with I/O Expander | |||||
------------------------------------------- | |||||
Starting with the basic breadboard keyboard described in [tutorial_1_breadboard_keyboard.md](tutorial_1_breadboard_keyboard.md), add parts as described above. | |||||
Refer to the MCP23S17 datasheet to locate its pins. | |||||
<!-- todo schematic with IOE power decoupling capacitor | |||||
This schematic was written by consulting the I/O expander's datasheet and using the ?? tool. --> | |||||
Sketch for split keyboard with I/O Expander | |||||
------------------------------------------- | |||||
The [keybrd_4c_split_with_IOE.ino](keybrd_4c_split_with_IOE/keybrd_4c_split_with_IOE.ino) | |||||
sketch explains how the I/O Expander works on a keyboard. | |||||
<a rel="license" href="https://creativecommons.org/licenses/by/4.0/"><img alt="Creative Commons License" style="border-width:0" src="https://licensebuttons.net/l/by/4.0/88x31.png" /></a><br /><span xmlns:dct="http://purl.org/dc/terms/" property="dct:title">keybrd tutorial</span> by <a xmlns:cc="https://creativecommons.org/ns" href="https://github.com/wolfv6/keybrd" property="cc:attributionName" rel="cc:attributionURL">Wolfram Volpi</a> is licensed under a <a rel="license" href="https://creativecommons.org/licenses/by/4.0/">Creative Commons Attribution 4.0 International License</a>.<br />Permissions beyond the scope of this license may be available at <a xmlns:cc="https://creativecommons.org/ns" href="https://github.com/wolfv6/keybrd/issues/new" rel="cc:morePermissions">https://github.com/wolfv6/keybrd/issues/new</a>. |
* flip the diodes so that the cathode (banded end) are towards the read pins | * flip the diodes so that the cathode (banded end) are towards the read pins | ||||
* swap the STROBE_ON and STROBE_OFF values | * swap the STROBE_ON and STROBE_OFF values | ||||
The red bus is grounded. | |||||
The pull-down resistors plug into the red bus and column read pins. | |||||
The pull-down resistors plug into ground (red bus) and column read pins. | |||||
The [keybrd_6_active_highsketch.ino](keybrd_6_active_high/keybrd_6_active_high.ino) is the tutorial 1 sketch with STROBE_ON and STROBE_OFF values swapped. | The [keybrd_6_active_highsketch.ino](keybrd_6_active_high/keybrd_6_active_high.ino) is the tutorial 1 sketch with STROBE_ON and STROBE_OFF values swapped. | ||||