#include "Code_Sc_LED.h" | |||||
void Code_Sc_LED::press() | |||||
{ | |||||
Keyboard.press(scancode); | |||||
refLED.on(); | |||||
} | |||||
void Code_Sc_LED::release() | |||||
{ | |||||
Keyboard.release(scancode); | |||||
refLED.off(); | |||||
} |
#ifndef CODE_SC_LED_H | |||||
#define CODE_SC_LED_H | |||||
#include <Arduino.h> | |||||
#include <inttypes.h> | |||||
#include <Code.h> | |||||
#include <LED.h> | |||||
/* Class Code_Sc_LED sends a scancode when press() or release() is called. | |||||
"S" stands for Scancode. | |||||
*/ | |||||
class Code_Sc_LED : public Code | |||||
{ | |||||
private: | |||||
const uint16_t scancode; | |||||
LED& refLED; | |||||
public: | |||||
Code_Sc_LED(const uint16_t scancode, LED& refLED): scancode(scancode), refLED(refLED) { } | |||||
virtual void press(); | |||||
virtual void release(); | |||||
}; | |||||
#endif |
../classes |
/* this works on Teensy LC 1*bb, active low and active high | |||||
MCP23018 is not working, MCP23018::begin() hangs, details in setup() | |||||
| Layout | **0** | **1** | | |||||
|:------:|-------|-------| | |||||
| **0** | a | b | | |||||
| **1** | c | d | | |||||
*/ | |||||
// ################## GLOBAL ################### | |||||
// ================= INCLUDES ================== | |||||
#include <Debug.h> | |||||
//LEDs | |||||
#include <LED_PinNumber.h> | |||||
#include "classes/Code_Sc_LED.h" //symlink: ln -s ../classes classes | |||||
//IOE Ports | |||||
#include "IOExpanderPort.h" | |||||
#include <RowPort_MCP23018.h> | |||||
#include <ColPort_MCP23018.h> | |||||
//Codes | |||||
#include <Code_Sc.h> | |||||
//Matrix | |||||
#include <Row_uC.h> | |||||
#include <SPI.h> | |||||
#include <Row_ShiftRegisters.h> | |||||
// =============== CONFIGURATION =============== | |||||
const unsigned int RowBase::DELAY_MICROSECONDS = 500; | |||||
//activeLow has diode cathode (band) on row | |||||
//activeHigh has diode cathode (band) on col, and pull down resistors on cols | |||||
//0=active low, 1= active high | |||||
const bool RowScanner_PinsArray::activeHigh = 0; | |||||
//const bool RowScanner_PinsBitwise::activeHigh = 0; | |||||
Debug debug; | |||||
// ================ LEFT PORTS ================= | |||||
uint8_t readPins[] = {14, 15}; | |||||
uint8_t READ_PIN_COUNT = sizeof(readPins)/sizeof(*readPins); | |||||
LED_PinNumber LED1(16); //Teensy LC pins 16, 17 are 20 ma | |||||
// ================ RIGHT PORTS ================ | |||||
/* | |||||
const uint8_t IOExpanderPort::ADDR = 0x20; | |||||
IOExpanderPort portA(0, 0); | |||||
RowPort_MCP23018 rowPort(portA); | |||||
IOExpanderPort portB(1, 0); | |||||
ColPort_MCP23018 colPort(portB, 1<<0 | 1<<1 ); | |||||
*/ | |||||
// =================== CODES =================== | |||||
Code_Sc s_a(KEY_A); | |||||
Code_Sc s_b(KEY_B); | |||||
Code_Sc s_c(KEY_C); | |||||
Code_Sc_LED s_d(KEY_D, LED1); | |||||
Code_Sc s_0(KEY_0); | |||||
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_z(KEY_Z); | |||||
// ================= LEFT ROWS ================= | |||||
Key* ptrsKeys_L0[] = { &s_a, &s_b }; | |||||
Row_uC row_L0(0, readPins, READ_PIN_COUNT, ptrsKeys_L0); | |||||
Key* ptrsKeys_L1[] = { &s_c, &s_d }; | |||||
Row_uC row_L1(1, readPins, READ_PIN_COUNT, ptrsKeys_L1); | |||||
// ================= RIGHT ROWS ================ | |||||
/* | |||||
Key* ptrsKeys_R[] = { &s_0, &s_z, &s_z, &s_z, | |||||
&s_4, &s_z, &s_z, &s_z, | |||||
&s_8, &s_z, &s_z, &s_z }; //the s_z are place holders and should not print | |||||
*/ | |||||
Key* ptrsKeys_R[] = { &s_0, &s_1, &s_2, &s_3, | |||||
&s_4, &s_5, &s_6, &s_7 }; //the most that 8-bit send() can handle | |||||
const uint8_t KEY_COUNT = sizeof(ptrsKeys_R)/sizeof(*ptrsKeys_R); | |||||
//Row_ShiftRegisters row_R(9, 2, ptrsKeys_R, KEY_COUNT); | |||||
Row_ShiftRegisters row_R(9, 1, ptrsKeys_R, KEY_COUNT); //1 byte | |||||
// ################### MAIN #################### | |||||
void setup() | |||||
{ | |||||
Keyboard.begin(); | |||||
SPI.begin(); | |||||
row_R.begin(); | |||||
//delay(1000); //time for OS to detect USB before printing | |||||
Keyboard.print(F("activeState.ino ")); | |||||
debug.print_free_RAM(); | |||||
//Wire.begin(); | |||||
/* Teensy LC on 1*bb 6/13/16 copied to: activeState_MCP23018_begin_hangs.no | |||||
RowPort_MCP23018::begin() hangs | |||||
ColPort_MCP23018::begin() sometimes hangs, sometimes prints after 6 seconds | |||||
PCA9655E::begin()s works on 4*bb | |||||
maybe hangs if IOE is not attached because endTransmission() waiting for confirmation?? | |||||
trouble shooting MCP23018::begin()s | |||||
checked wiring against datasheets | |||||
measured power and ground, 3.3 volts checks out | |||||
!! next things to check could take days: | |||||
test MCP23018 with Teensy 2.0, because it worked last year | |||||
set Teensy 2.0 to 3.3 volts and test again | |||||
try with PCA9655E instead of MCP23018 (works on 4*bb) | |||||
might be solder joints on LC (I soldered it), try using other Teensy LC | |||||
test MCP23018 on simple demo sketch /home/wolfv/Documents/Arduino/demo_keep/mcp23018_../ | |||||
test MCP23018 with signal analyzer | |||||
//rowPort.begin(); //this breaks sketch, does not print "activeState.ino ", kb unresponsive | |||||
//colPort.begin(RowScanner_PinsBitwise::activeHigh); //hanges for 6 seconds | |||||
Keyboard.println(F(" after Port.begin()")); | |||||
*/ | |||||
} | |||||
//uint16_t next = 0; | |||||
//elapsedMillis elapsed; | |||||
void loop() | |||||
{ | |||||
row_L0.process(); | |||||
row_L1.process(); | |||||
row_R.process(); | |||||
//row_R0.process(); | |||||
//row_R1.process(); | |||||
/* used this when debugging MCP23018::begin() hangs | |||||
if ( (next < 10) && (elapsed > 1000 * next) ) | |||||
{ | |||||
Keyboard.print(next); | |||||
Keyboard.print(F(" ")); | |||||
next++; | |||||
} | |||||
*/ | |||||
//delay(100); | |||||
//Keyboard.println(""); | |||||
} |
/* this works on Teensy LC 1*bb, active low and active high | |||||
MCP23018::begin() hangs, details in setup() | |||||
| Layout | **0** | **1** | | |||||
|:------:|-------|-------| | |||||
| **0** | a | b | | |||||
| **1** | c | d | | |||||
*/ | |||||
// ################## GLOBAL ################### | |||||
// ================= INCLUDES ================== | |||||
#include <Debug.h> | |||||
//IOE Ports | |||||
#include "IOExpanderPort.h" | |||||
#include <RowPort_MCP23018.h> | |||||
#include <ColPort_MCP23018.h> | |||||
//Codes | |||||
#include <Code_Sc.h> | |||||
//Matrix | |||||
#include <Row_uC.h> | |||||
#include <Row_IOE.h> | |||||
// =============== CONFIGURATION =============== | |||||
const unsigned int RowBase::DELAY_MICROSECONDS = 500; | |||||
//activeLow has diode cathode (band) on row | |||||
//activeHigh has diode cathode (band) on col, and pull down resistors on cols | |||||
//0=active low, 1= active high | |||||
const bool RowScanner_PinsArray::activeHigh = 0; | |||||
const bool RowScanner_PinsBitwise::activeHigh = 0; | |||||
Debug debug; | |||||
// ================= uC PINS ================= | |||||
uint8_t readPins[] = {14, 15}; | |||||
uint8_t READ_PIN_COUNT = sizeof(readPins)/sizeof(*readPins); | |||||
// ================ IOE PORTS ================ | |||||
const uint8_t IOExpanderPort::ADDR = 0x20; | |||||
IOExpanderPort portA(0, 0); | |||||
RowPort_MCP23018 rowPort(portA); | |||||
IOExpanderPort portB(1, 0); | |||||
ColPort_MCP23018 colPort(portB, 1<<0 | 1<<1 ); | |||||
// =================== CODES =================== | |||||
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_0(KEY_0); | |||||
Code_Sc s_1(KEY_1); | |||||
Code_Sc s_2(KEY_2); | |||||
Code_Sc s_3(KEY_3); | |||||
// ================= LEFT ROWS ================= | |||||
Key* ptrsKeys_L0[] = { &s_a, &s_b }; | |||||
Row_uC row_L0(0, readPins, READ_PIN_COUNT, ptrsKeys_L0); | |||||
Key* ptrsKeys_L1[] = { &s_c, &s_d }; | |||||
Row_uC row_L1(1, readPins, READ_PIN_COUNT, ptrsKeys_L1); | |||||
// ================= RIGHT ROWS ================ | |||||
Key* ptrsKeys_R0[] = { &s_0, &s_1 }; | |||||
Row_IOE row_R0(rowPort, 1<<0, colPort, ptrsKeys_R0); | |||||
Key* ptrsKeys_R1[] = { &s_2, &s_3 }; | |||||
Row_IOE row_R1(rowPort, 1<<1, colPort, ptrsKeys_R1); | |||||
// ################### MAIN #################### | |||||
void setup() | |||||
{ | |||||
Keyboard.begin(); | |||||
Wire.begin(); //Wire.begin() must be called before rowPort.begin() colPort.begin() | |||||
//delay(1000); //time for OS to detect USB before printing | |||||
Keyboard.print(F("activeState.ino ")); | |||||
debug.print_free_RAM(); | |||||
/* Teensy LC on 1*bb | |||||
RowPort_MCP23018::begin() hangs | |||||
ColPort_MCP23018::begin() sometimes hangs, sometimes prints after 6 seconds | |||||
PCA9655E::begin()s works on 4*bb | |||||
maybe hangs if IOE is not attached because endTransmission() waiting for confirmation?? | |||||
trouble shooting MCP23018::begin()s | |||||
checked wiring against datasheets | |||||
measured power and ground, 3.3 volts checks out | |||||
!! next things to check could take days: | |||||
test MCP23018 with Teensy 2.0, because it worked last year | |||||
set Teensy 2.0 to 3.3 volts and test again | |||||
try with PCA9655E instead of MCP23018 (works on 4*bb) | |||||
might be solder joints on LC (I soldered it), try using other Teensy LC | |||||
test MCP23018 on simple demo sketch /home/wolfv/Documents/Arduino/demo_keep/mcp23018_../ | |||||
test MCP23018 with signal analyzer | |||||
//rowPort.begin(); //this breaks sketch, does not print "activeState.ino ", kb unresponsive | |||||
//colPort.begin(RowScanner_PinsBitwise::activeHigh); //hanges for 6 seconds | |||||
Keyboard.println(F(" after Port.begin()")); | |||||
*/ | |||||
} | |||||
uint16_t next = 0; | |||||
elapsedMillis elapsed; | |||||
void loop() | |||||
{ | |||||
row_L0.process(); | |||||
row_L1.process(); | |||||
//row_R0.process(); | |||||
//row_R1.process(); | |||||
/* used this when debugging MCP23018::begin() hangs | |||||
if ( (next < 10) && (elapsed > 1000 * next) ) | |||||
{ | |||||
Keyboard.print(next); | |||||
Keyboard.print(F(" ")); | |||||
next++; | |||||
} | |||||
*/ | |||||
//delay(500); | |||||
//Keyboard.println(""); | |||||
} |
../classes |
/* this works on DH 4*bb, top-left buttons | |||||
demo RowScanner_PinsBitwise | |||||
| Left | **0** | **1** | | Right | **0** | **1** | | |||||
|:-----:|-------|-------| |:-----:|-------|-------| | |||||
| **0** | a | b | | **0** | 0 | 1 | | |||||
| **1** | c | d | | **1** | 2 | 3 | | |||||
*/ | |||||
// ################## GLOBAL ################### | |||||
// ================= INCLUDES ================== | |||||
#include <Debug.h> | |||||
//Ports | |||||
#include <RowPort_AVR_Optic.h> | |||||
#include <ColPort_AVR.h> | |||||
//LEDs | |||||
#include <LED_AVR.h> | |||||
#include <LED_PCA9655E.h> | |||||
#include "classes/Code_Sc_LED.h" //symlink: ln -s ../classes classes | |||||
//Codes | |||||
#include <Code_Sc.h> | |||||
//Matrix | |||||
#include <Row_IOE.h> | |||||
#include <Matrix.h> | |||||
// =============== CONFIGURATION =============== | |||||
const unsigned int RowBase::DELAY_MICROSECONDS = 500; | |||||
const bool RowScanner_PinsBitwise::activeHigh = 1; | |||||
Debug debug; | |||||
// ================ LEFT PORTS ================= | |||||
RowPort_AVR_Optic rowPort_L(DDRF, PORTF); | |||||
ColPort_AVR colPort_L(DDRB, PORTB, PINB, 1<<0 | 1<<1 ); | |||||
//LED | |||||
LED_AVR LED_L1(PORTB, 1<<6); //green | |||||
// ================ RIGHT PORTS ================ | |||||
#include <IOExpanderPort.h> | |||||
#include <RowPort_PCA9655E.h> | |||||
#include <ColPort_PCA9655E.h> | |||||
const uint8_t IOExpanderPort::ADDR = 0x18; | |||||
//row port | |||||
IOExpanderPort port1(1, 0); | |||||
RowPort_PCA9655E rowPort_R(port1); | |||||
//column and pin numbers on schematic_switch_matrix.png and schematic_pca9655_pin_assignments.png | |||||
//col port | |||||
IOExpanderPort port0(0, 0); | |||||
ColPort_PCA9655E colPort_R(port0, 1<<0 | 1<<1 ); | |||||
//LED | |||||
LED_PCA9655E LED_R1(port1, 1<<5); //blue | |||||
// =================== CODES =================== | |||||
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_LED s_d(KEY_D, LED_L1); | |||||
Code_Sc s_0(KEY_0); | |||||
Code_Sc s_1(KEY_1); | |||||
Code_Sc s_2(KEY_2); | |||||
//Code_Sc s_3(KEY_3); | |||||
Code_Sc_LED s_3(KEY_3, LED_R1); | |||||
// ================= LEFT ROWS ================= | |||||
Key* const ptrsKeys_L0[] = { &s_a, &s_b }; | |||||
Row_IOE row_L0(rowPort_L, 1<<0, colPort_L, ptrsKeys_L0); //strobe F0 | |||||
Key* const ptrsKeys_L1[] = { &s_c, &s_d }; | |||||
Row_IOE row_L1(rowPort_L, 1<<1, colPort_L, ptrsKeys_L1); //strobe F1 | |||||
// ================= RIGHT ROWS ================ | |||||
Key* ptrsKeys_R0[] = { &s_0, &s_1 }; | |||||
Row_IOE row_R0(rowPort_R, 1<<0, colPort_R, ptrsKeys_R0); | |||||
Key* ptrsKeys_R1[] = { &s_2, &s_3 }; | |||||
Row_IOE row_R1(rowPort_R, 1<<1, colPort_R, ptrsKeys_R1); | |||||
// ################### MAIN #################### | |||||
void setup() | |||||
{ | |||||
Keyboard.begin(); | |||||
Wire.begin(); | |||||
delay(1000); //time for OS to detect USB before printing | |||||
Keyboard.print(F("activeState_AVR.ino ")); | |||||
debug.print_free_RAM(); | |||||
rowPort_R.begin(); | |||||
colPort_R.begin(); | |||||
} | |||||
void loop() | |||||
{ | |||||
row_L0.process(); | |||||
row_L1.process(); | |||||
row_R0.process(); | |||||
row_R1.process(); | |||||
//delay(500); | |||||
//Keyboard.println(F("")); | |||||
} |
#include <inttypes.h> | #include <inttypes.h> | ||||
#include <Code.h> | #include <Code.h> | ||||
/* Class Code_Sc is composed of one scancode, which it sends when press() or release() is called. | |||||
/* Class Code_Sc_LED sends a scancode when press() or release() is called. | |||||
"S" stands for Scancode. | "S" stands for Scancode. | ||||
*/ | */ | ||||
class Code_Sc : public Code | class Code_Sc : public Code |
configures column port's IODIR, GPIO, and GPPU. | configures column port's IODIR, GPIO, and GPPU. | ||||
*/ | */ | ||||
ColPort_MCP23018::ColPort_MCP23018(IOExpanderPort& port, const uint8_t colPins) | 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) | |||||
: ColPort(colPins), port(port), IODIR(port.num), GPIO(port.num + 0x12), GPPU(port.num + 0x0C) | |||||
{} | {} | ||||
void ColPort_MCP23018::begin() | |||||
void ColPort_MCP23018::begin(uint8_t activeHigh) | |||||
{ | { | ||||
//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.beginTransmission(port.ADDR); | ||||
Wire.write(IODIR); | Wire.write(IODIR); | ||||
Wire.write(colPins); //0=configure as output (for LED), 1=configure as input (for read) | |||||
Wire.write(colPins); //0=configure as output (for LED), 1=configure as input (for read) | |||||
Wire.endTransmission(); | Wire.endTransmission(); | ||||
Wire.beginTransmission(port.ADDR); | Wire.beginTransmission(port.ADDR); | ||||
Wire.write(GPPU); | Wire.write(GPPU); | ||||
Wire.write(colPins); //0=pull-up disabled (for LED), 1=pull-up enabled (for read) | |||||
if (activeHigh) | |||||
{ | |||||
Wire.write(0); //0=pull-up disabled for activeHigh //todo not tested yet | |||||
} | |||||
else | |||||
{ | |||||
Wire.write(colPins); //0=pull-up disabled (for LED), 1=pull-up enabled (for read) | |||||
} | |||||
Wire.endTransmission(); | Wire.endTransmission(); | ||||
} | } | ||||
public: | public: | ||||
//The constructor initialization list is in .cpp | //The constructor initialization list is in .cpp | ||||
ColPort_MCP23018(IOExpanderPort& port, const uint8_t colPins); | ColPort_MCP23018(IOExpanderPort& port, const uint8_t colPins); | ||||
void begin(); | |||||
void begin(uint8_t activeHigh); | |||||
//read port and store result in portState | //read port and store result in portState | ||||
virtual void read(); | virtual void read(); |
void ColPort_PCA9655E::begin() | 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.beginTransmission(port.ADDR); | ||||
Wire.write(configurationByteCommand); | Wire.write(configurationByteCommand); | ||||
Wire.write(colPins); //0=configure as output (for LED), 1=configure as input (for read) | Wire.write(colPins); //0=configure as output (for LED), 1=configure as input (for read) |
#include "IOExpanderPort.h" | #include "IOExpanderPort.h" | ||||
/* One PCA9655E I/O expander port connected to matrix columns. | /* One PCA9655E I/O expander port connected to matrix columns. | ||||
PCA9655E does not have internal pull-up resistors (PCA9535E does). | |||||
Instantiation | Instantiation | ||||
------------ | ------------ |
public: | public: | ||||
LED_AVR(volatile unsigned char& PORTx, const uint8_t pin): PORT(PORTx), pin(pin) {} | LED_AVR(volatile unsigned char& PORTx, const uint8_t pin): PORT(PORTx), pin(pin) {} | ||||
virtual void on(); | virtual void on(); | ||||
virtual void off(); | virtual void off(); | ||||
}; | }; | ||||
#endif | #endif |
#include "LED_PinNumber.h" | |||||
void LED_PinNumber::on() | |||||
{ | |||||
digitalWrite(pin, HIGH); | |||||
} | |||||
void LED_PinNumber::off() | |||||
{ | |||||
digitalWrite(pin, LOW); | |||||
} |
#ifndef LED_PINNUMBER_H | |||||
#define LED_PINNUMBER_H | |||||
#include <Arduino.h> | |||||
#include <inttypes.h> | |||||
#include <LED.h> | |||||
/* A LED_PinNumber object is an Aduino pin that is used to power an LED on and off. | |||||
*/ | |||||
class LED_PinNumber: public LED | |||||
{ | |||||
private: | |||||
const uint8_t pin; | |||||
public: | |||||
LED_PinNumber(const uint8_t pin): pin(pin) | |||||
{ | |||||
pinMode(pin, OUTPUT); | |||||
} | |||||
virtual void on(); | |||||
virtual void off(); | |||||
}; | |||||
#endif |
For fastest response time, wait() should be placed before scan() or after pressRelease() | For fastest response time, wait() should be placed before scan() or after pressRelease() | ||||
(waiting between strobe and send would unnecessarily delay send). | (waiting between strobe and send would unnecessarily delay send). | ||||
DELAY_MICROSECONDS explained | |||||
---------------------------- | |||||
A keyboard with a faster scan rate responds faster. | A keyboard with a faster scan rate responds faster. | ||||
Follow these step to tune DELAY_MICROSECONDS for maximum scan rate for a given SAMPLE_COUNT: | Follow these step to tune DELAY_MICROSECONDS for maximum scan rate for a given SAMPLE_COUNT: | ||||
Initialize DELAY_MICROSECONDS in your sketch: | Initialize DELAY_MICROSECONDS in your sketch: | ||||
{ | { | ||||
uint8_t isFallingEdge; //1 means falling edge | uint8_t isFallingEdge; //1 means falling edge | ||||
uint8_t isRisingEdge; //1 means rising edge | uint8_t isRisingEdge; //1 means rising edge | ||||
uint8_t rowMask; //bitwise, active col bit is 1 | |||||
uint16_t rowMask; //bitwise, active col bit is 1 (same type as rowEnd) | |||||
uint8_t col; //index for ptrsKeys[col] array | uint8_t col; //index for ptrsKeys[col] 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 |
void RowPort_MCP23018::begin() | 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.beginTransmission(port.ADDR); | ||||
Wire.write(IODIR); | Wire.write(IODIR); | ||||
Wire.write(0); //0=configure as output (for strobe pins and LED pins) | Wire.write(0); //0=configure as output (for strobe pins and LED pins) |
void RowPort_PCA9655E::begin() | 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.beginTransmission(port.ADDR); | ||||
Wire.write(configurationByteCommand); | Wire.write(configurationByteCommand); | ||||
Wire.write(0); //0=configure as output (for strobe pins and LED) | Wire.write(0); //0=configure as output (for strobe pins and LED) |
Diode orientation | Diode orientation | ||||
---------------- | ---------------- | ||||
PCA9655E does not have internal pull-up resistors, external pull-down resistors are required. | |||||
Rows, columns, and diode orientation are explained in Matrix.h | Rows, columns, and diode orientation are explained in Matrix.h | ||||
PCA9655E data sheet | PCA9655E data sheet |
#include "RowScanner_PinsArray.h" | #include "RowScanner_PinsArray.h" | ||||
/* | |||||
Strobes the row and reads the columns. | |||||
/* constructor | |||||
*/ | |||||
RowScanner_PinsArray::RowScanner_PinsArray(const uint8_t strobePin, | |||||
const uint8_t readPins[], const uint8_t READ_PIN_COUNT) | |||||
: strobePin(strobePin), readPins(readPins), READ_PIN_COUNT(READ_PIN_COUNT) | |||||
{ | |||||
uint8_t mode; | |||||
//configure row | |||||
pinMode(strobePin, OUTPUT); | |||||
if (activeHigh) | |||||
{ | |||||
mode = INPUT; //requires external pull-down resistor | |||||
} | |||||
else | |||||
{ | |||||
mode = INPUT_PULLUP; //uses internal pull-up resistor | |||||
} | |||||
//configure cols | |||||
for (uint8_t i=0; i < READ_PIN_COUNT; i++) | |||||
{ | |||||
pinMode(readPins[i], mode); | |||||
} | |||||
} | |||||
/* scan() Strobes the row and reads the columns. | |||||
Sets rowEnd and returns rowState. | Sets rowEnd and returns rowState. | ||||
rowEnd is a bitwise row mask, one col per bit, where active col bit is 1. | 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. | 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. | rowEnd is a larger type than portMask so that it can not overflow. | ||||
https://www.arduino.cc/en/Tutorial/DigitalPins | |||||
https://www.arduino.cc/en/Reference/PinMode | |||||
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) | uint8_t RowScanner_PinsArray::scan(uint16_t& rowEnd) | ||||
{ | { |
#include <RowPort.h> | #include <RowPort.h> | ||||
#include <ColPort.h> | #include <ColPort.h> | ||||
/* RowScanner_PinsArray class uses Arduino pin numbers (no port name). | |||||
/* RowScanner_PinsArray class uses Arduino pin numbers (not port pin numbers). | |||||
*/ | */ | ||||
class RowScanner_PinsArray : public RowScannerInterface | class RowScanner_PinsArray : public RowScannerInterface | ||||
{ | { | ||||
const uint8_t READ_PIN_COUNT; //number of read pins | const uint8_t READ_PIN_COUNT; //number of read pins | ||||
public: | public: | ||||
RowScanner_PinsArray(const uint8_t strobePin, | RowScanner_PinsArray(const uint8_t strobePin, | ||||
const uint8_t readPins[], const uint8_t READ_PIN_COUNT) | |||||
: strobePin(strobePin), readPins(readPins), READ_PIN_COUNT(READ_PIN_COUNT) | |||||
{ | |||||
//row | |||||
pinMode(strobePin, OUTPUT); | |||||
//cols | |||||
for (uint8_t i=0; i < READ_PIN_COUNT; i++) | |||||
{ | |||||
pinMode(readPins[i], INPUT_PULLUP); | |||||
} | |||||
} | |||||
const uint8_t readPins[], const uint8_t READ_PIN_COUNT); | |||||
virtual uint8_t scan(uint16_t& rowEnd); | virtual uint8_t scan(uint16_t& rowEnd); | ||||
uint8_t getRowState(uint16_t& rowEnd); | uint8_t getRowState(uint16_t& rowEnd); | ||||
}; | }; |
rowEnd <<= 1; //shift rowEnd to next key | rowEnd <<= 1; //shift rowEnd to next key | ||||
} | } | ||||
} | } | ||||
//todo Keyboard.print(rowState); | |||||
return rowState; | return rowState; | ||||
} | } |
class RowScanner_PinsBitwise : public RowScannerInterface | class RowScanner_PinsBitwise : public RowScannerInterface | ||||
{ | { | ||||
private: | private: | ||||
static const bool activeHigh; //logic level of strobe pin: 0=activeLow, 1=activeHigh | |||||
RowPort& refRowPort; //this row's IC port | RowPort& refRowPort; //this row's IC port | ||||
const uint8_t strobePin; //bitwise, 1 indicates IC pin connected to this row | const uint8_t strobePin; //bitwise, 1 indicates IC pin connected to this row | ||||
ColPort& refColPort; | ColPort& refColPort; | ||||
ColPort& refColPort) | ColPort& refColPort) | ||||
: refRowPort(refRowPort), strobePin(strobePin), | : refRowPort(refRowPort), strobePin(strobePin), | ||||
refColPort(refColPort) {} | refColPort(refColPort) {} | ||||
static const bool activeHigh; //logic level of strobe pin: 0=activeLow, 1=activeHigh | |||||
virtual uint8_t scan(uint16_t& rowEnd); | virtual uint8_t scan(uint16_t& rowEnd); | ||||
uint8_t getRowState(uint16_t& rowEnd); | uint8_t getRowState(uint16_t& rowEnd); | ||||
}; | }; |
#include "RowScanner_SPIShiftRegisters.h" | |||||
void RowScanner_SPIShiftRegisters::begin() | |||||
{ | |||||
pinMode (SS, OUTPUT); | |||||
digitalWrite (SS, HIGH); | |||||
} | |||||
/* | |||||
Sets rowEnd and returns rowState. | |||||
*/ | |||||
uint8_t RowScanner_SPIShiftRegisters::scan(uint16_t& rowEnd) | |||||
{ | |||||
//todo rowEnd, rowState, return int size depend on BYTE_COUNT, like in send() | |||||
uint8_t rowState; | |||||
digitalWrite(SS, LOW); | |||||
digitalWrite(SS, HIGH); | |||||
SPI.transfer(&rowState, BYTE_COUNT); | |||||
rowEnd = 1 << KEY_COUNT; | |||||
return rowState; | |||||
} | |||||
#ifndef ROWSCANNER_SPI_SHIFTREGISTERS_H | |||||
#define ROWSCANNER_SPI_SHIFTREGISTERS_H | |||||
#include <Arduino.h> | |||||
#include <inttypes.h> | |||||
#include <SPI.h> | |||||
#include <RowScannerInterface.h> | |||||
#include <RowPort.h> | |||||
#include <ColPort.h> | |||||
/* RowScanner_SPIShiftRegisters reads all shift registers in a daisy chain. | |||||
//todo delete: Assumes only one row of shift registers is connected (no Slave Select). | |||||
*/ | |||||
class RowScanner_SPIShiftRegisters : public RowScannerInterface | |||||
{ | |||||
private: | |||||
const uint8_t SS; //Slave Select, pin on master | |||||
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) | |||||
: SS(SS), BYTE_COUNT(BYTE_COUNT), KEY_COUNT(KEY_COUNT) {} | |||||
virtual uint8_t scan(uint16_t& rowEnd); | |||||
void begin(); | |||||
}; | |||||
#endif |
/* Row_DH_IOE is a row connected to an Input/Output Expander. | /* Row_DH_IOE is a row connected to an Input/Output Expander. | ||||
Configuration | |||||
------------- | |||||
Define and initilize DELAY_MICROSECONDS in sketch. Detailed how to is in RowBase.cpp. | |||||
Instantiation | Instantiation | ||||
------------- | ------------- | ||||
Definition of DELAY_MICROSECONDS is explained in RowBase.cpp. | |||||
Definition of activeHigh is explained in RowScanner_Interface.h | |||||
Example instantiation of a row: | Example instantiation of a row: | ||||
const unsigned int RowBase::DELAY_MICROSECONDS = 1000; | |||||
const bool RowScanner_PinsArray::activeHigh = 0; | |||||
const uint8_t IOExpanderPort::ADDR = 0x18; | const uint8_t IOExpanderPort::ADDR = 0x18; | ||||
IOExpanderPort port1(1, 0); | IOExpanderPort port1(1, 0); |
#include "Row_ShiftRegisters.h" | |||||
void Row_ShiftRegisters::begin() | |||||
{ | |||||
scanner.begin(); | |||||
} | |||||
uint8_t Row_ShiftRegisters::scan(uint16_t& rowEnd) | |||||
{ | |||||
return scanner.scan(rowEnd); | |||||
} | |||||
uint8_t Row_ShiftRegisters::debounce(const uint8_t rowState, uint8_t& debounced) | |||||
{ | |||||
return debouncer.debounce(rowState, debounced); | |||||
} |
#ifndef ROW_SHIFTREGISTERS_H | |||||
#define ROW_SHIFTREGISTERS_H | |||||
#include <RowBase.h> | |||||
#include <RowScanner_SPIShiftRegisters.h> | |||||
#include <Debouncer_4Samples.h> | |||||
/* Row_DH_IOE is a row connected to an Input/Output Expander. | |||||
Instantiation | |||||
------------- | |||||
Definition of DELAY_MICROSECONDS is explained in RowBase.cpp. | |||||
Example instantiation of a row: | |||||
const unsigned int RowBase::DELAY_MICROSECONDS = 1000; | |||||
todo | |||||
Key* const ptrsKeys_0[] = { &k_00, &k_01, &k_02, &k_03, &k_04, &k_05 }; | |||||
Row_ShiftRegisters row_0(uint8_t BYTE_COUNT, ptrsKeys_0); | |||||
Number of pins in colPort0 should equal number of keys in ptrsKeys_0[] 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 | |||||
*/ | |||||
class Row_ShiftRegisters : public RowBase | |||||
{ | |||||
private: | |||||
RowScanner_SPIShiftRegisters scanner; | |||||
Debouncer_4Samples debouncer; | |||||
public: | |||||
Row_ShiftRegisters(const uint8_t SS, uint8_t BYTE_COUNT, Key *const ptrsKeys[], uint16_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); | |||||
}; | |||||
#endif |
#include <Debouncer_4Samples.h> | #include <Debouncer_4Samples.h> | ||||
/* Row_uC is a row connected to a micro controller. | /* Row_uC is a row connected to a micro controller. | ||||
Configuration | |||||
------------- | |||||
Define and initilize DELAY_MICROSECONDS in sketch. Detailed how to is in RowBase.cpp. | |||||
Instantiation | Instantiation | ||||
------------- | ------------- | ||||
Definition of DELAY_MICROSECONDS is explained in RowBase.cpp. | |||||
todo Definition of activeHigh is explained in RowScanner_Interface.h | |||||
Example instantiation of a row: | Example instantiation of a row: | ||||
const unsigned int RowBase::DELAY_MICROSECONDS = 1000; | |||||
const bool RowScanner_PinsArray::activeHigh = 0; | |||||
const uint8_t colPins[] = {0,1,2,3,7,8}; | const uint8_t colPins[] = {0,1,2,3,7,8}; | ||||
const uint8_t COL_PIN_COUNT = sizeof(colPins)/sizeof(*colPins); | const uint8_t COL_PIN_COUNT = sizeof(colPins)/sizeof(*colPins); | ||||
Breadboard keyboards have row-column matrices and diodes just like the big keyboards. | Breadboard keyboards have row-column matrices and diodes just like the big keyboards. | ||||
A breadboard is the easiest way to learn keyboard electronics. | A breadboard is the easiest way to learn keyboard electronics. | ||||
Electronics are fickle, and you 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. | There is a learning curve. | ||||
Compared to PCBs, breadboard keyboards are easier to learn on because: | Compared to PCBs, breadboard keyboards are easier to learn on because: | ||||
* Mistakes are easily corrected; no soldering and desoldering | * Mistakes are easily corrected; no soldering and desoldering | ||||
## Breadboard keyboard starter kit | ## Breadboard keyboard starter kit | ||||
The parts needed to build all the tutorial Breadboard Keyboards are listed in [breadboard_keyboard_supplies.ods](breadboard_keyboard_supplies.ods). | The parts needed to build all the tutorial Breadboard Keyboards are listed in [breadboard_keyboard_supplies.ods](breadboard_keyboard_supplies.ods). | ||||
Wire cutters (or nail clipper) is the only required tool. | |||||
A multi-meter is useful for trouble shooting. | |||||
You will need two tools: | |||||
* Wire cutters (or nail clipper) | |||||
* A multi-meter for trouble shooting | |||||
## How a breadboard works | ## How a breadboard works | ||||
To understand the breadboard keyboard you will need to know the internal parts of a breadboard: | To understand the breadboard keyboard you will need to know the internal parts of a breadboard: | ||||
* power rail | |||||
* bus strip | |||||
* terminal strip | * terminal strip | ||||
These are explained in [How to Use a Breadboard](https://learn.sparkfun.com/tutorials/how-to-use-a-breadboard) | These are explained in [How to Use a Breadboard](https://learn.sparkfun.com/tutorials/how-to-use-a-breadboard) | ||||
![breadboard keyboard with 2 rows and 2 columns](images/breadboard_keyboard_2x2_labeled.jpg "2x2 breadboard keyboard") | ![breadboard keyboard with 2 rows and 2 columns](images/breadboard_keyboard_2x2_labeled.jpg "2x2 breadboard keyboard") | ||||
The key matrix has two rows and two columns. | The key matrix has two rows and two columns. | ||||
Breadboard power rails are repurposed as matrix rows. | |||||
Breadboard bus strips are used as matrix rows. | |||||
Short bare wires connect terminal strips into matrix columns. | Short bare wires connect terminal strips into matrix columns. | ||||
Switch-diode pairs connect rows to columns. | Switch-diode pairs connect rows to columns. | ||||
* Teensy LC on the terminal strip labeled 1 | * Teensy LC on the terminal strip labeled 1 | ||||
* switch leads oriented so that they will connect diodes to columns | * switch leads oriented so that they will connect diodes to columns | ||||
* diode cut offs connect terminal strips into columns | * diode cut offs connect terminal strips into columns | ||||
* diodes are orient with cathode (banded end) towards the row (power strip) | |||||
* diodes are orient with cathode (banded end) towards the row (bus strip) | |||||
3. Insert jumper wires connecting Teensy2 to the matrix rows and columns. | 3. Insert jumper wires connecting Teensy2 to the matrix rows and columns. | ||||
* follow pin connections table (below) and consult pinout diagram in | * follow pin connections table (below) and consult pinout diagram in | ||||
[close-up pic shows switch way half out, to show lead orientation] | [close-up pic shows switch way half out, to show lead orientation] |
The breadboard in this picture models a split keyboard. | 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") | ![breadboard keyboard with 2 rows and 4 columns of keys](images/breadboard_keyboard_2x5_labeled.jpg "2x5 breadboard keyboard") | ||||
There is a total of 4 matrix rows, each on a breadboard power rail. | |||||
The right matrix is connected to a microcontroller. | |||||
The left matrix is connected to a I/O expander. | |||||
The breadboard has four bus strips used as rows. | |||||
Two rows connected to a microcontroller, and two rows connected to a I/O expander. | |||||
The I/O expander has a small notch on one end, which identifies the end with pin 1. | 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. | In the picture, pin 1 is on the right end. |