From 48a3a41db15451cc1f32677cd0f94660e98cb89a Mon Sep 17 00:00:00 2001 From: wolfv6 Date: Tue, 21 Jun 2016 20:40:35 -0600 Subject: [PATCH] add config_keybrd.h with typedef read_pins_t, read_pins_mask_t --- doc/CHANGELOG.md | 11 +++++-- doc/planned_features.md | 6 ++-- src/DebouncerInterface.h | 4 ++- src/Debouncer_4Samples.cpp | 20 ++++++------ src/Debouncer_4Samples.h | 9 +++--- src/RowBase.cpp | 14 ++++----- src/RowBase.h | 9 +++--- src/RowScannerInterface.h | 3 +- src/RowScanner_PinsArray.cpp | 4 +-- src/RowScanner_PinsArray.h | 7 +++-- src/RowScanner_PinsBitwise.cpp | 4 +-- src/RowScanner_PinsBitwise.h | 5 +-- src/RowScanner_SPIShiftRegisters.cpp | 4 +-- src/RowScanner_SPIShiftRegisters.h | 5 +-- src/Row_IOE.cpp | 4 +-- src/Row_IOE.h | 4 +-- src/Row_ShiftRegisters.cpp | 4 +-- src/Row_ShiftRegisters.h | 6 ++-- src/Row_uC.cpp | 4 +-- src/Row_uC.h | 4 +-- src/config_keybrd.h | 35 +++++++++++++++++++++ tutorials/tutorial_1_breadboard_keyboard.md | 2 +- 22 files changed, 108 insertions(+), 60 deletions(-) create mode 100644 src/config_keybrd.h diff --git a/doc/CHANGELOG.md b/doc/CHANGELOG.md index 8670255..eda1a3a 100644 --- a/doc/CHANGELOG.md +++ b/doc/CHANGELOG.md @@ -7,10 +7,17 @@ keybrd version 1.0.0 will be released when the public API is stable. ## [Unreleased][unreleased] +## [0.3.2] - 2016-06-21 +### Added +config_keybrd.h for size configurations. +RowScanner_SPI-ShiftRegisters for compact split keyboards up to 32 keys per matrix. +LED_PinNumber for controlling indicator lights by pin number. + ## [0.3.2] - 2016-06-10 ### Changed -* Changed uC from scanning port arrays to scanning Arduino pins. - Thereby added support for Arduino boards, Teensy 3, and Teensy LC micro controllers. +* Changed uC from scanning port arrays to scanning Arduino pins, thereby adding support for: + Arduino boards, Teensy 3, and Teensy LC micro controllers + up to 31x31 matrix capability * Changed IOE from scanning port arrays to scanning single ports. * Moved scanner and debouncer into their own classes. diff --git a/doc/planned_features.md b/doc/planned_features.md index 6377b9e..d7cc2d3 100644 --- a/doc/planned_features.md +++ b/doc/planned_features.md @@ -2,16 +2,14 @@ planned_features is a view of where the keybrd project is headed. Top priority ============ -Add support for shift registers, for compact split keyboards up to 32 keys per matrix. +MCP23S18 I/O expander with Serial Peripheral Interface (SPI) Med priority ============ -Add 16x16 matrix capability (currently limited to 8x8 matrices) +Add matrix-to-layout mapping array (to decouple matrix from layout) Low priority ============ -Add matrix-to-layout mapping array (to decouple matrix from layout) - Update tutorials: * Currently tutorial sketches are obsolete and won't compile * Change tutorial sketches from teensy 2.0 and PCA9655E-D IOE to Teensy LC and MCP23018 IOE diff --git a/src/DebouncerInterface.h b/src/DebouncerInterface.h index aa7e251..dab6ecd 100644 --- a/src/DebouncerInterface.h +++ b/src/DebouncerInterface.h @@ -1,12 +1,14 @@ #ifndef DEBOUNCERINTERFACE_H #define DEBOUNCERINTERFACE_H +#include + /* DebouncerInterface is an interface class. debounce() takes rawSignal and returns debounced signal. Signals are bitwise. */ class DebouncerInterface { public: - virtual uint8_t debounce(const uint8_t rawSignal, uint8_t& debounced)=0; + virtual read_pins_t debounce(const read_pins_t rawSignal, read_pins_t& debounced)=0; }; #endif diff --git a/src/Debouncer_4Samples.cpp b/src/Debouncer_4Samples.cpp index a22a4f0..bd39017 100644 --- a/src/Debouncer_4Samples.cpp +++ b/src/Debouncer_4Samples.cpp @@ -4,10 +4,10 @@ where each sample contains the switch states for a row of switches, one bit per Debounce uses Dr. Marty's debounce algorithm from http://drmarty.blogspot.com.br/2009/05/best-switch-debounce-routine-ever.html -I2C and TWI protocols do not include any Packet Error Checking (PEC). +SPI, I2C, and TWI protocols do not include any Packet Error Checking (PEC). The goal of Marty's debounce routine is to reject spurious signals, -which is useful when connecting split keyboards with a cable using I2C or TWI. -Was tested on split keyboard with 3-meter long telephone wire to I/O expander +which is useful when connecting split keyboards with a cable using SPI, I2C, or TWI. +This class was tested on split keyboard with a 3-meter long telephone wire to I/O expander. Dr. Marty's debounce algorithm: Periodically read samples and update the state when a number consecutive sample bits are equal. @@ -23,11 +23,11 @@ There is a latency equal to SAMPLE_COUNT, between button press and debounced sig samples[SAMPLE_COUNT] is a ring buffer. samplesIndex is it's current write index. SAMPLE_COUNT is the number of consecutive equal samples needed to debounce. SAMPLE_COUNT is a macro because it defines samples[SAMPLE_COUNT] array size at compile time. -SAMPLE_COUNT should be at lease 1. +SAMPLE_COUNT is defined in config_keybrd.h and should be at lease 1. SAMPLE_COUNT = 4 is very reliable for a keyboard. -Keyboards with a long I2C wire or in environment with strong electromagnetic interference (EMI) -may need a larger SAMPLE_COUNT for reliability. +Split keyboards with a long connecting wire or in environment with +strong electromagnetic interference (EMI) may need a larger SAMPLE_COUNT for reliability. */ #include "Debouncer_4Samples.h" @@ -35,11 +35,11 @@ may need a larger SAMPLE_COUNT for reliability. For parameters, 1 means pressed, 0 means released. For return, 1 means debounced changed. */ -uint8_t Debouncer_4Samples::debounce(const uint8_t rawSignal, uint8_t& debounced) +read_pins_t Debouncer_4Samples::debounce(const read_pins_t rawSignal, read_pins_t& debounced) { - uint8_t previousDebounced; //bitwise, 1 means pressed, 0 means released - uint8_t all_1 = ~0; //bitwise - uint8_t all_0 = 0; //bitwise + read_pins_t previousDebounced; //bitwise, 1 means pressed, 0 means released + read_pins_t all_1 = ~0; //bitwise + read_pins_t all_0 = 0; //bitwise samples[samplesIndex] = rawSignal; //insert rawSignal into samples[] ring buffer diff --git a/src/Debouncer_4Samples.h b/src/Debouncer_4Samples.h index 9955582..4983eb6 100644 --- a/src/Debouncer_4Samples.h +++ b/src/Debouncer_4Samples.h @@ -2,21 +2,20 @@ #define DEBOUNCER_4SAMPLES_H #include #include +#include #include -#define SAMPLE_COUNT 4 //number of consecutive equal bits needed to change a debounced bit - /* Debouncer_4Samples -Configuration: #define SAMPLE_COUNT in this header file. +Configuration: #define SAMPLE_COUNT in config_keybrd.h */ class Debouncer_4Samples : public DebouncerInterface { private: - uint8_t samples[SAMPLE_COUNT]; //bitwise, one bit per key, most recent readings + read_pins_t samples[SAMPLE_COUNT]; //bitwise, one bit per key, most recent readings uint8_t samplesIndex; //samples[] current write index public: Debouncer_4Samples(): samplesIndex(0) {} - virtual uint8_t debounce(const uint8_t rawSignal, uint8_t& debounced); + virtual read_pins_t debounce(const read_pins_t rawSignal, read_pins_t& debounced); }; #endif diff --git a/src/RowBase.cpp b/src/RowBase.cpp index 2b12d23..6c6cd09 100644 --- a/src/RowBase.cpp +++ b/src/RowBase.cpp @@ -5,9 +5,9 @@ process() scans the row and calls any newly pressed or released keys. void RowBase::process() { //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 debouncedChanged; //1 means debounced changed + read_pins_t rowState; //1 means pressed, 0 means released + read_pins_mask_t rowEnd; //1 bit marks positioned after last key of row + read_pins_t debouncedChanged; //1 means debounced changed wait(); rowState = scan(rowEnd); @@ -56,11 +56,11 @@ pressRelease() calls key's press() or release() function if it was pressed or re Both parameters are bitwise. rowEnd bit marks positioned immediatly after last key of row. */ -void RowBase::pressRelease(const uint16_t rowEnd, const uint8_t debouncedChanged) +void RowBase::pressRelease(const read_pins_mask_t rowEnd, const read_pins_t debouncedChanged) { - uint8_t isFallingEdge; //1 means falling edge - uint8_t isRisingEdge; //1 means rising edge - uint16_t rowMask; //bitwise, active col bit is 1 (same type as rowEnd) + read_pins_t isFallingEdge; //bitwise, 1 means falling edge + read_pins_t isRisingEdge; //bitwise, 1 means rising edge + read_pins_t rowMask; //bitwise, active col bit is 1 (same type as rowEnd) uint8_t col; //index for ptrsKeys[col] array //bit=1 if last debounced changed from 1 to 0, else bit=0 diff --git a/src/RowBase.h b/src/RowBase.h index 6afc4ba..4ee9f45 100644 --- a/src/RowBase.h +++ b/src/RowBase.h @@ -2,6 +2,7 @@ #define ROWBASE_H #include #include +#include #include /* RowBase is an abstract base class. @@ -14,14 +15,14 @@ class RowBase virtual void keyWasPressed(); protected: - uint8_t debounced; //bitwise, 1 means pressed, 0 means released + read_pins_t debounced; //bitwise, 1 means pressed, 0 means released void wait(); - void pressRelease(const uint16_t rowEnd, const uint8_t debouncedChanged); + void pressRelease(const read_pins_mask_t rowEnd, const read_pins_t debouncedChanged); public: RowBase(Key *const ptrsKeys[]) : ptrsKeys(ptrsKeys), debounced(0) { } virtual void process(); - virtual uint8_t scan(uint16_t& rowEnd)=0; - virtual uint8_t debounce(const uint8_t rowState, uint8_t& debounced)=0; + virtual read_pins_t scan(read_pins_mask_t& rowEnd)=0; + virtual read_pins_t debounce(const read_pins_t rowState, read_pins_t& debounced)=0; }; #endif diff --git a/src/RowScannerInterface.h b/src/RowScannerInterface.h index bb1adcb..5a623b3 100644 --- a/src/RowScannerInterface.h +++ b/src/RowScannerInterface.h @@ -3,11 +3,12 @@ #include #include +#include class RowScannerInterface { public: - virtual uint8_t scan(uint16_t& rowEnd)=0; + virtual read_pins_t scan(read_pins_mask_t& rowEnd)=0; }; #endif diff --git a/src/RowScanner_PinsArray.cpp b/src/RowScanner_PinsArray.cpp index 55709c5..d8aa82e 100644 --- a/src/RowScanner_PinsArray.cpp +++ b/src/RowScanner_PinsArray.cpp @@ -39,9 +39,9 @@ https://www.arduino.cc/en/Reference/DigitalWrite https://www.arduino.cc/en/Reference/DigitalRead https://www.arduino.cc/en/Reference/Constants > Digital Pins modes: INPUT, INPUT_PULLUP, and OUTPUT */ -uint8_t RowScanner_PinsArray::scan(uint16_t& rowEnd) +read_pins_t RowScanner_PinsArray::scan(read_pins_mask_t& rowEnd) { - uint8_t rowState = 0; + read_pins_t rowState = 0; //bitwise rowEnd = 1; //strobe row on diff --git a/src/RowScanner_PinsArray.h b/src/RowScanner_PinsArray.h index d6b5dda..b02f055 100644 --- a/src/RowScanner_PinsArray.h +++ b/src/RowScanner_PinsArray.h @@ -2,11 +2,14 @@ #define ROWSCANNER_PINSARRAY_H #include #include +#include #include #include #include /* RowScanner_PinsArray class uses Arduino pin numbers (not port pin numbers). +The maximum keys per row is 31, because Arduino's largest type is 32 bits and rowEnd consumes the last bit. +Constructor is in RowScanner_PinsArray.cpp */ class RowScanner_PinsArray : public RowScannerInterface { @@ -18,8 +21,8 @@ class RowScanner_PinsArray : public RowScannerInterface public: RowScanner_PinsArray(const uint8_t strobePin, const uint8_t readPins[], const uint8_t READ_PIN_COUNT); - virtual uint8_t scan(uint16_t& rowEnd); - uint8_t getRowState(uint16_t& rowEnd); + virtual read_pins_t scan(read_pins_mask_t& rowEnd); + //read_pins_t getRowState(read_pins_mask_t& rowEnd); }; #endif diff --git a/src/RowScanner_PinsBitwise.cpp b/src/RowScanner_PinsBitwise.cpp index cc641b9..7a44890 100644 --- a/src/RowScanner_PinsBitwise.cpp +++ b/src/RowScanner_PinsBitwise.cpp @@ -3,7 +3,7 @@ Strobes the row and reads the columns. Sets rowEnd and returns rowState. */ -uint8_t RowScanner_PinsBitwise::scan(uint16_t& rowEnd) +read_pins_t RowScanner_PinsBitwise::scan(read_pins_mask_t& rowEnd) { //strobe row on if (activeHigh) @@ -39,7 +39,7 @@ rowEnd is a bitwise row mask, one col per bit, where active col bit is 1. At end of function, 1 bit marks place immediatly after last key of row. rowEnd is a larger type than portMask so that it can not overflow. */ -uint8_t RowScanner_PinsBitwise::getRowState(uint16_t& rowEnd) +uint8_t RowScanner_PinsBitwise::getRowState(read_pins_mask_t& rowEnd) { uint8_t rowState = 0; //bitwise, one key per bit, 1 means key is pressed uint8_t portMask; //bitwise, 1 bit is a colPortState position diff --git a/src/RowScanner_PinsBitwise.h b/src/RowScanner_PinsBitwise.h index f7a3fcf..6752f73 100644 --- a/src/RowScanner_PinsBitwise.h +++ b/src/RowScanner_PinsBitwise.h @@ -7,6 +7,7 @@ #include /* RowScanner_PinsBitwise uses bit manipulation to read all pins of one port. +The maximum keys per row is 8, because ports have a maximum of 8 pins each. */ class RowScanner_PinsBitwise : public RowScannerInterface { @@ -20,7 +21,7 @@ class RowScanner_PinsBitwise : public RowScannerInterface : refRowPort(refRowPort), strobePin(strobePin), refColPort(refColPort) {} static const bool activeHigh; //logic level of strobe pin: 0=activeLow, 1=activeHigh - virtual uint8_t scan(uint16_t& rowEnd); - uint8_t getRowState(uint16_t& rowEnd); + virtual read_pins_t scan(read_pins_mask_t& rowEnd); + uint8_t getRowState(read_pins_mask_t& rowEnd); }; #endif diff --git a/src/RowScanner_SPIShiftRegisters.cpp b/src/RowScanner_SPIShiftRegisters.cpp index 5adb215..7ade720 100644 --- a/src/RowScanner_SPIShiftRegisters.cpp +++ b/src/RowScanner_SPIShiftRegisters.cpp @@ -9,10 +9,10 @@ void RowScanner_SPIShiftRegisters::begin() /* Sets rowEnd and returns rowState. */ -uint8_t RowScanner_SPIShiftRegisters::scan(uint16_t& rowEnd) +read_pins_t RowScanner_SPIShiftRegisters::scan(read_pins_mask_t& rowEnd) { //todo rowEnd, rowState, return int size depend on BYTE_COUNT, like in send() - uint8_t rowState; + read_pins_t rowState = 0; digitalWrite(SS, LOW); digitalWrite(SS, HIGH); diff --git a/src/RowScanner_SPIShiftRegisters.h b/src/RowScanner_SPIShiftRegisters.h index c590cb4..38ac2cd 100644 --- a/src/RowScanner_SPIShiftRegisters.h +++ b/src/RowScanner_SPIShiftRegisters.h @@ -8,6 +8,7 @@ #include /* RowScanner_SPIShiftRegisters reads all shift registers in a daisy chain. +The maximum keys per row is 31, because Arduino's largest type is 32 bits and rowEnd consumes the last bit. //todo delete: Assumes only one row of shift registers is connected (no Slave Select). */ class RowScanner_SPIShiftRegisters : public RowScannerInterface @@ -17,9 +18,9 @@ class RowScanner_SPIShiftRegisters : public RowScannerInterface const uint8_t BYTE_COUNT; //number of shift registers const uint8_t KEY_COUNT; //number of keys in row public: - RowScanner_SPIShiftRegisters(const uint8_t SS, uint8_t BYTE_COUNT, uint16_t KEY_COUNT) + RowScanner_SPIShiftRegisters(const uint8_t SS, uint8_t BYTE_COUNT, uint8_t KEY_COUNT) : SS(SS), BYTE_COUNT(BYTE_COUNT), KEY_COUNT(KEY_COUNT) {} - virtual uint8_t scan(uint16_t& rowEnd); + virtual read_pins_t scan(read_pins_mask_t& rowEnd); void begin(); }; #endif diff --git a/src/Row_IOE.cpp b/src/Row_IOE.cpp index 67527b3..d8a168b 100644 --- a/src/Row_IOE.cpp +++ b/src/Row_IOE.cpp @@ -1,11 +1,11 @@ #include "Row_IOE.h" -uint8_t Row_IOE::scan(uint16_t& rowEnd) +read_pins_t Row_IOE::scan(read_pins_mask_t& rowEnd) { return scanner.scan(rowEnd); } -uint8_t Row_IOE::debounce(const uint8_t rowState, uint8_t& debounced) +read_pins_t Row_IOE::debounce(const read_pins_t rowState, read_pins_t& debounced) { return debouncer.debounce(rowState, debounced); } diff --git a/src/Row_IOE.h b/src/Row_IOE.h index 47c019d..ca147a9 100644 --- a/src/Row_IOE.h +++ b/src/Row_IOE.h @@ -40,7 +40,7 @@ class Row_IOE : public RowBase Row_IOE( RowPort& refRowPort, const uint8_t strobePin, ColPort& refColPort, Key *const ptrsKeys[]) : RowBase(ptrsKeys), scanner(refRowPort, strobePin, refColPort) { } - uint8_t scan(uint16_t& rowEnd); - uint8_t debounce(const uint8_t rowState, uint8_t& debounced); + read_pins_t scan(read_pins_mask_t& rowEnd); + read_pins_t debounce(const read_pins_t rowState, read_pins_t& debounced); }; #endif diff --git a/src/Row_ShiftRegisters.cpp b/src/Row_ShiftRegisters.cpp index 4d59a0c..46ad104 100644 --- a/src/Row_ShiftRegisters.cpp +++ b/src/Row_ShiftRegisters.cpp @@ -5,12 +5,12 @@ void Row_ShiftRegisters::begin() scanner.begin(); } -uint8_t Row_ShiftRegisters::scan(uint16_t& rowEnd) +read_pins_t Row_ShiftRegisters::scan(read_pins_mask_t& rowEnd) { return scanner.scan(rowEnd); } -uint8_t Row_ShiftRegisters::debounce(const uint8_t rowState, uint8_t& debounced) +read_pins_t Row_ShiftRegisters::debounce(const read_pins_t rowState, read_pins_t& debounced) { return debouncer.debounce(rowState, debounced); } diff --git a/src/Row_ShiftRegisters.h b/src/Row_ShiftRegisters.h index 51f9b11..777e1e7 100644 --- a/src/Row_ShiftRegisters.h +++ b/src/Row_ShiftRegisters.h @@ -29,10 +29,10 @@ class Row_ShiftRegisters : public RowBase RowScanner_SPIShiftRegisters scanner; Debouncer_4Samples debouncer; public: - Row_ShiftRegisters(const uint8_t SS, uint8_t BYTE_COUNT, Key *const ptrsKeys[], uint16_t KEY_COUNT) + Row_ShiftRegisters(const uint8_t SS, uint8_t BYTE_COUNT, Key *const ptrsKeys[], uint8_t KEY_COUNT) : RowBase(ptrsKeys), scanner(SS, BYTE_COUNT, KEY_COUNT) { } void begin(); - uint8_t scan(uint16_t& rowEnd); - uint8_t debounce(const uint8_t rowState, uint8_t& debounced); + read_pins_t scan(read_pins_mask_t& rowEnd); + read_pins_t debounce(const read_pins_t rowState, read_pins_t& debounced); }; #endif diff --git a/src/Row_uC.cpp b/src/Row_uC.cpp index a3c7ee7..9b3dc83 100644 --- a/src/Row_uC.cpp +++ b/src/Row_uC.cpp @@ -1,11 +1,11 @@ #include "Row_uC.h" -uint8_t Row_uC::scan(uint16_t& rowEnd) +read_pins_t Row_uC::scan(read_pins_mask_t& rowEnd) { return scanner.scan(rowEnd); } -uint8_t Row_uC::debounce(const uint8_t rowState, uint8_t& debounced) +read_pins_t Row_uC::debounce(const read_pins_t rowState, read_pins_t& debounced) { return debouncer.debounce(rowState, debounced); } diff --git a/src/Row_uC.h b/src/Row_uC.h index ca3e3cd..4777ffe 100644 --- a/src/Row_uC.h +++ b/src/Row_uC.h @@ -35,7 +35,7 @@ class Row_uC : public RowBase Row_uC(const uint8_t strobePin, const uint8_t readPins[], const uint8_t READ_PIN_COUNT, Key *const ptrsKeys[]) : RowBase(ptrsKeys), scanner(strobePin, readPins, READ_PIN_COUNT) { } - uint8_t scan(uint16_t& rowEnd); - uint8_t debounce(const uint8_t rowState, uint8_t& debounced); + read_pins_t scan(read_pins_mask_t& rowEnd); + read_pins_t debounce(const read_pins_t rowState, read_pins_t& debounced); }; #endif diff --git a/src/config_keybrd.h b/src/config_keybrd.h new file mode 100644 index 0000000..7fb5426 --- /dev/null +++ b/src/config_keybrd.h @@ -0,0 +1,35 @@ +#ifndef CONFIG_KEYBRD_H +#define CONFIG_KEYBRD_H +#include + +/* size of read_pins_t and read_pins_mask_t depends on the maximum number of pins scanned by RowScanner. +By default, read_pins_t and read_pins_mask_t are set to the largest type. +If your 8-bit AVR is running low on memory, using a smaller type saves SRAM. +Using smaller types on a 32-bit uC (Teensy LC) would accomplish nothing. +*/ + +/* Uncomment a typedef read_pins_t that covers all col pins of the RowScanner object with the most col pins i.e. + For RowScanner_PinsArray, RowScanner_PinsArray::READ_PIN_COUNT + For RowScanner_SPIShiftRegisters, RowScanner_SPIShiftRegisters::KEY_COUNT + For RowScanner_PinsBitwise, cover the last 1 bit in RowScanner_PinsBitwise::strobePin +*/ +typedef uint8_t read_pins_t; +//typedef uint16_t read_pins_t; +//typedef uint32_t read_pins_t; + +/* read_pins_mask_t is only used for rowEnd, which extends one bit beyond the last col pin. +uncomment typedef that covers one bit beyond the last col pin. +This could be the same typedef as read_pins_t, or the next larger typedef. +*/ +typedef uint8_t read_pins_mask_t; +//typedef uint16_t read_pins_mask_t; +//typedef uint32_t read_pins_mask_t; + +/* SAMPLE_COUNT = 4 is very reliable for a keyboard. +Split keyboards with a long connecting wire or in environment with +strong electromagnetic interference (EMI) may need a larger SAMPLE_COUNT for reliability. +SAMPLE_COUNT is used in Debouncer_Samples.h +*/ +#define SAMPLE_COUNT 4 //number of consecutive equal bits needed to change a debounced bit + +#endif diff --git a/tutorials/tutorial_1_breadboard_keyboard.md b/tutorials/tutorial_1_breadboard_keyboard.md index 0df7231..7649b91 100644 --- a/tutorials/tutorial_1_breadboard_keyboard.md +++ b/tutorials/tutorial_1_breadboard_keyboard.md @@ -7,8 +7,8 @@ All the tutorial example sketches run on breadboard keyboards that have 2 to 8 k Breadboard keyboards have row-column matrices and diodes just like the big keyboards. A breadboard is the easiest way to learn keyboard electronics. +A novice won't get everything right the first time. It's easy to get some detail wrong with electronics. -You won't get everything right the first time. There is a learning curve. Compared to PCBs, breadboard keyboards are easier to learn on because: * Mistakes are easily corrected; no soldering and desoldering