From caa29dec631cab08d44568e5b3db683802df66ad Mon Sep 17 00:00:00 2001 From: wolfv6 Date: Wed, 31 Aug 2016 20:29:59 -0600 Subject: [PATCH] add MCP23S17 port files --- CONTRIBUTING.md | 9 +- doc/keybrd_library_developer_guide.md | 36 +++--- src/PortRead.h | 2 +- src/PortRead_MCP23S17.cpp | 23 ++++ src/PortRead_MCP23S17.h | 42 +++++++ src/PortWrite_MCP23S17.cpp | 50 +++++++++ src/PortWrite_MCP23S17.h | 46 ++++++++ src/Row.h | 3 +- src/Row_IOE.cpp | 12 ++ src/Row_IOE.h | 26 +++++ src/Scanner_ShiftRegs74HC165.h | 2 +- tutorials/breadboard_keyboard_supplies.ods | Bin 17759 -> 17514 bytes .../keybrd_1_breadboard.ino | 2 +- ...4b_split_keyboard_with_shift_registers.ino | 96 ++++++++++++++++ .../keybrd_4c_split_with_IOE.ino | 105 ++++++++++++++++++ .../tutorial_10_writing_IOE_Port_classes.md | 45 +++++--- ..._sharing_your_keybrd_extension_library.md} | 26 ++--- .../tutorial_8c_sharing_your_keybrd_sketch.md | 17 +++ unit_tests/MCP23S17_read/MCP23S17_read.ino | 42 +++++++ unit_tests/MCP23S17_write/MCP23S17_write.ino | 54 +++++++++ .../PortRead_MCP23S17/PortRead_MCP23S17.ino | 32 ++++++ .../PortWrite_MCP23S17/PortWrite_MCP23S17.ino | 35 ++++++ 22 files changed, 650 insertions(+), 55 deletions(-) create mode 100644 src/PortRead_MCP23S17.cpp create mode 100644 src/PortRead_MCP23S17.h create mode 100644 src/PortWrite_MCP23S17.cpp create mode 100644 src/PortWrite_MCP23S17.h create mode 100644 src/Row_IOE.cpp create mode 100644 src/Row_IOE.h create mode 100644 tutorials/keybrd_4b_split_keyboard_with_shift_registers/keybrd_4b_split_keyboard_with_shift_registers.ino create mode 100644 tutorials/keybrd_4c_split_with_IOE/keybrd_4c_split_with_IOE.ino rename tutorials/{tutorial_8b_creating_and_publishing_your_own_keybrd_extension_library.md => tutorial_8b_sharing_your_keybrd_extension_library.md} (77%) create mode 100644 tutorials/tutorial_8c_sharing_your_keybrd_sketch.md create mode 100644 unit_tests/MCP23S17_read/MCP23S17_read.ino create mode 100644 unit_tests/MCP23S17_write/MCP23S17_write.ino create mode 100644 unit_tests/PortRead_MCP23S17/PortRead_MCP23S17.ino create mode 100644 unit_tests/PortWrite_MCP23S17/PortWrite_MCP23S17.ino diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3d18462..4bbc30e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,8 +7,9 @@ Improvement suggestions We need to know what improvements to the keybrd library would help you create your keyboard design. Before requesting an improvement, please check [planned_features list](doc/planned_features.md) -Submit improvement suggestions to [GitHub issues](https://github.com/wolfv6/Keybrd/issues). -* The issue title should start with "suggestion:" followed by a descriptive title +Submit improvement suggestions to [GitHub issues](https://github.com/wolfv6/Keybrd/issues) +or [geekhack thread](https://geekhack.org/index.php?topic=83599.0). + * Provide a use case * Explain why the improvement is useful * Site other product examples where this improvement exists @@ -16,6 +17,8 @@ Submit improvement suggestions to [GitHub issues](https://github.com/wolfv6/Keyb Bug reports ----------- A bug report is the first step in making the keybrd library work the way it's supposed to work. +Submit bug reports to [GitHub issues](https://github.com/wolfv6/Keybrd/issues) +or [geekhack thread](https://geekhack.org/index.php?topic=83599.0). Please provide enough information so we can reproduce the bug behaviour! * Complete sketch (copy & paste, attachment, or a link to the code) @@ -52,7 +55,7 @@ A healthy project needs the perspective of many people. * Documentation - Suggest a clarification, simplification, correction, or other improvement. We need the perspective of people new to the project to see these things. Sometimes just changing a word or two makes a big difference. -* [Current user contributions](https://geekhack.org/index.php?topic=83599.0) highlights contributions that are needed for the keybrd project's current stage of development. +* [Current user contributions](https://geekhack.org/index.php?topic=83599.msg2223776#msg2223776) highlights contributions that are needed for the keybrd project's current stage of development. Text file documentation style guide: * Use Markdown with a .md suffix. diff --git a/doc/keybrd_library_developer_guide.md b/doc/keybrd_library_developer_guide.md index b912204..4506c49 100644 --- a/doc/keybrd_library_developer_guide.md +++ b/doc/keybrd_library_developer_guide.md @@ -27,7 +27,7 @@ Row_Ext::keyWasPressed() overrides Row::keyWasPressed() which is used to unstick Row_Ext_uC and Row_Ext_ShiftRegisters are a custom classes composed of stock keybrd library classes.
Row_Ext_uC uses Scanner_uC to scan the primary matrix.
-Row_Ext_ShiftRegisters uses Scanner_ShiftRegs74HC165 to scan the secondary matrix. +Row_Ext_ShiftRegisters uses Scanner_ShiftRegs74HC165 to scan the peripheral matrix. Class inheritance diagram ``` @@ -68,7 +68,7 @@ Keybrd library class inheritance diagram ``` ________ Row ___________ / | \ - Row_uC Row_ShiftRegisters Row_IOE (to be added) + Row_uC Row_ShiftRegisters Row_IOE (todo to be added) Scanner_uC Scanner_Port Scanner_ShiftRegs74HC165 @@ -78,7 +78,7 @@ Keybrd library class inheritance diagram PortWrite | - PortWrite_PCA9655E (one PortWrite class for each IOE type) + PortWrite_PCA9655E (one PortWrite class for each IOE type) PortRead | @@ -115,9 +115,9 @@ Keybrd library class inheritance diagram | | | | Code_LayeredScSc Code_LayeredCodeSc | - |__________________________________________ - \ \ \ \ - Code_Sc Code_Shift Code_AutoShift Code_LEDLock + |_________________________________________________________ + \ \ \ \ \ + Code_Sc Code_Shift Code_AutoShift Code_LEDLock Code_Null / \ Code_ScS Code_ScNS @@ -149,7 +149,7 @@ Dependency diagram of example multi-layer keyboard with layer LEDs ``` -Dependency diagram of example secondary matrix with shift registers +Dependency diagram of example peripheral matrix with shift registers ``` Row_ShiftRegisters[1..*] / \ \ @@ -159,17 +159,17 @@ Dependency diagram of example secondary matrix with shift registers ``` -Dependency diagram of example secondary matrix with I/O Expander and LEDs +Dependency diagram of example peripheral matrix with I/O Expander and LEDs ``` - ___ Row_IOE[1..*] _________ - / \ \ - _ Scanner_Port[1] _ Debouncer[1] Keys[1..*] __ - / | \ | \ - PortWrite[1] RowPin[1] PortRead[1] Code[1..*] Code_LEDLock[1..*] - \ / \ | - \ / ColPins[1..*] LED[1] - \ / - PortIOE[0..*] + _____ 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..*] ``` @@ -243,7 +243,7 @@ Following the style guide makes it easier for the next programmer to understand Trace of keybrd scan -------------------- Arduino does not have a debugger. -So here is the next best thing; 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. Refer to it like a table of contents while reading the keybrd library. diff --git a/src/PortRead.h b/src/PortRead.h index 8293045..9f3da80 100644 --- a/src/PortRead.h +++ b/src/PortRead.h @@ -13,7 +13,7 @@ Details are in config_key.h class PortRead { protected: - const uint8_t readPins; //bitwise pin configuration, 1 means read column + const uint8_t readPins; //bitwise pin configuration, 1 means read pin public: PortRead(const uint8_t readPins): readPins(readPins) {} virtual uint8_t read()=0; diff --git a/src/PortRead_MCP23S17.cpp b/src/PortRead_MCP23S17.cpp new file mode 100644 index 0000000..199bd64 --- /dev/null +++ b/src/PortRead_MCP23S17.cpp @@ -0,0 +1,23 @@ +#include "PortRead_MCP23S17.h" + +/* +PortRead_MCP23S17::begin() is not needed because port direction is already configured to input by default. +SPI bus is configured in PortWrite_MCP23S17::begin(). +*/ + +/* +returns port value +*/ +uint8_t PortRead_MCP23S17::read() +{ + uint8_t portState; //bit wise + + //slower clock + digitalWrite(SS, LOW); //enable Slave Select + SPI.transfer(port.ADDR << 1 | 1); //read command + SPI.transfer(port.num + 0x12); //register address to read data from + portState = SPI.transfer(0); //save the data (0 is dummy data to send) + digitalWrite(SS, HIGH); //disable Slave Select + + return portState; +} diff --git a/src/PortRead_MCP23S17.h b/src/PortRead_MCP23S17.h new file mode 100644 index 0000000..97ab958 --- /dev/null +++ b/src/PortRead_MCP23S17.h @@ -0,0 +1,42 @@ +#ifndef PORTREAD_MCP23S17_H +#define PORTREAD_MCP23S17_H +#include +#include +#include +#include +#include "PortIOE.h" + +/* One MCP23S17 I/O expander port connected to matrix columns. +MCP23S17 does not have internal pull-up resistors (PCA9535E does). + +Instantiation + ------------ +readPins parameter is port's bitwise pin configuration + 1=configure as input (for pins connected to column) + 0=configure as output (for LED or not connected to a column) + +Example instantiation for column port 0, with pins 2 and 3 connected to columns: + PortIOE port0(0, 0); + PortRead_MCP23S17 colPort0(port0, 2<<0 | 1<<3 ); +Example instantiation for column port 1, with pins 2 and 3 connected to columns: + PortIOE port1(1, 0); + PortRead_MCP23S17 colPort1(port1, 2<<0 | 1<<3 ); + +readPins are read from pin 0 on up. + +*/ +class PortRead_MCP23S17 : public PortRead +{ + private: + PortIOE& port; + public: +/* +todo not all PortRead_ classes need a readPins + move PortRead::readPins from PortRead to PortRead_PCA9655E + remove PortRead(0) initialization from this constructor +*/ + //The constructor initialization list is in .cpp + PortRead_MCP23S17(PortIOE& port) : PortRead(0), port(port) {} + virtual uint8_t read(); +}; +#endif diff --git a/src/PortWrite_MCP23S17.cpp b/src/PortWrite_MCP23S17.cpp new file mode 100644 index 0000000..73fd1c5 --- /dev/null +++ b/src/PortWrite_MCP23S17.cpp @@ -0,0 +1,50 @@ +#include "PortWrite_MCP23S17.h" + +void PortWrite_MCP23S17::writePort(const uint8_t registerAddr, const uint8_t data) +{ + //slower clock + //SPI.beginTransaction(SPISettings (SPI_CLOCK_DIV8, MSBFIRST, SPI_MODE0)); //control SPI bus todo move to begin() + digitalWrite(SS, LOW); //enable Slave Select + SPI.transfer(port.ADDR << 1); //write command + SPI.transfer(registerAddr); //register address to write data to + SPI.transfer(data); //data + digitalWrite(SS, HIGH); //disable Slave Select + //SPI.endTransaction(); //release the SPI bus +} + +/* +If PortRead_MCP23S17 is instantiated on the same port, do NOT use PortWrite_MCP23S17::begin(). +Otherwise readPins could be overwritten. +Output pins can be used for strobe pins and LEDs. + +SPI.endTransaction() is not called because keyboard only has one SPI device, so no need to release the SPI bus +*/ +void PortWrite_MCP23S17::begin() +{ + pinMode(SS, OUTPUT); //configure controller's Slave Select pin to output + digitalWrite(SS, HIGH); //disable Slave Select + SPI.begin(); + SPI.beginTransaction(SPISettings (SPI_CLOCK_DIV8, MSBFIRST, SPI_MODE0)); //control SPI bus + + writePort(port.num, 0); //configure port direction (port.num) to output (0) +} + +/* +pin is bitwise, where pin being strobed is 1. +strobe is HIGH or LOW (for active high or active low). +port.outputVal can be shared by LEDs. +The functions does not reset the other pins so that they can be used for LEDs. +*/ +void PortWrite_MCP23S17::write(const uint8_t pin, const bool strobe) +{ + if (strobe == LOW) //if active low + { + port.outputVal &= ~pin; //set pin output to low + } + else //if active high + { + port.outputVal |= pin; //set pin output to high + } + + writePort(port.num + 0x12, port.outputVal); //set GPIO port pins for stobe and LEDs +} diff --git a/src/PortWrite_MCP23S17.h b/src/PortWrite_MCP23S17.h new file mode 100644 index 0000000..7d2ea90 --- /dev/null +++ b/src/PortWrite_MCP23S17.h @@ -0,0 +1,46 @@ +#ifndef PORTWRITE_MCP23S17_H +#define PORTWRITE_MCP23S17_H +#include +#include +#include +#include +#include "PortIOE.h" + +/* One MCP23S17 I/O expander port connected to matrix rows. +MCP23S17 does not have internal pull-up resistors (PCA9535E does). + +begin() configures column port's configuration and output. +This should normally be called once in sketch's setup(). +If PortRead_MCP23S17 is instantiated on the same port, do NOT use PortWrite_MCP23S17::begin(). +Otherwise readPins could be overwritten. + +Instantiation + ------------ +Example instantiation for row port 0: + PortIOE port0(0, 0); + PortWrite_MCP23S17 rowPort0(port0); + +Example instantiation for row port 1: + PortIOE port1(1, 0); + PortWrite_MCP23S17 rowPort1(port1); + +Diode orientation + ---------------- +Diode orientation is explained in keybrd_library_user_guide.md > Diode orientation + +MCP23S17 data sheet + ---------------- + http://www.onsemi.com/pub_link/Collateral/MCP23S17-D.PDF +*/ + +class PortWrite_MCP23S17 : public PortWrite +{ + private: + PortIOE& port; + void writePort(const uint8_t registerAddr, const uint8_t data); + public: + PortWrite_MCP23S17(PortIOE& port) : port(port) {} + void begin(); + virtual void write(const uint8_t pin, const bool level); +}; +#endif diff --git a/src/Row.h b/src/Row.h index 7b70013..a0b797a 100644 --- a/src/Row.h +++ b/src/Row.h @@ -14,8 +14,7 @@ class Row Key *const *const ptrsKeys; //array of Key pointers virtual void keyWasPressed(); protected: - read_pins_t debounced; //bitwise state of keys after debouncing - // 1 means pressed, 0 means released + read_pins_t debounced; //bitwise state of keys after debouncing, 1=pressed, 0=released void send(const uint8_t readPinCount, const read_pins_t debouncedChanged); public: Row(Key* const ptrsKeys[]) : ptrsKeys(ptrsKeys), debounced(0) { } diff --git a/src/Row_IOE.cpp b/src/Row_IOE.cpp new file mode 100644 index 0000000..066623a --- /dev/null +++ b/src/Row_IOE.cpp @@ -0,0 +1,12 @@ +#include "Row_IOE.h" + +void Row_IOE::process() +{ + //these variables are all bitwise, one bit per key + uint8_t readState; //1 means pressed, 0 means released + uint8_t debouncedChanged; //1 means debounced changed + + readState = scanner.scan(); + debouncedChanged = debouncer.debounce(readState, debounced); + send(readPinCount, debouncedChanged); +} diff --git a/src/Row_IOE.h b/src/Row_IOE.h new file mode 100644 index 0000000..dd3b400 --- /dev/null +++ b/src/Row_IOE.h @@ -0,0 +1,26 @@ +#ifndef ROW_IOE_H +#define ROW_IOE_H + +#include +#include +#include +class PortWrite; +class PortRead; + +/* Row_IOE is a row connected to an Input/Output Expander. +Configuration and Instantiation instructions are in keybrd/src/Row_IOE.h +*/ +class Row_IOE : public Row +{ + private: + Scanner_Port scanner; + Debouncer_Samples debouncer; + const uint8_t readPinCount; //number of read pins + public: + Row_IOE(PortWrite& refPortWrite, const uint8_t strobePin, + PortRead& refPortRead, const uint8_t readPinCount, Key *const ptrsKeys[]) + : Row(ptrsKeys), scanner(refPortWrite, strobePin, refPortRead), + readPinCount(readPinCount) { } + void process(); +}; +#endif diff --git a/src/Scanner_ShiftRegs74HC165.h b/src/Scanner_ShiftRegs74HC165.h index 3f83f84..359f4b6 100644 --- a/src/Scanner_ShiftRegs74HC165.h +++ b/src/Scanner_ShiftRegs74HC165.h @@ -13,7 +13,7 @@ shift registers 74HC165 are Parallel-In-Serial-Out (PISO) Upto 4 shift registers can be in a daisy chained for a total of 32 read pins. For active low: -Shift register parallel input pins have 10k pull-down resistors powered +Shift register parallel input pins have 10k pull-up resistors powered Orient diodes with cathode (banded end) towards the write pins (row) Controller's MISO pin is connected to shift register's complementary serial output (/QH) pin Use these two lines in the sketch: diff --git a/tutorials/breadboard_keyboard_supplies.ods b/tutorials/breadboard_keyboard_supplies.ods index 6acdd3544d0f751f5a7a9fbcb26b460be1473eea..b185ce4466d7c429c30f477f1ea7b118db9c2e33 100644 GIT binary patch delta 14990 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 delta 15170 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 VIJp!7gr{hluoNxKhS=Zw{|^fo$mIY4 diff --git a/tutorials/keybrd_1_breadboard/keybrd_1_breadboard.ino b/tutorials/keybrd_1_breadboard/keybrd_1_breadboard.ino index ee185ec..398f3e9 100644 --- a/tutorials/keybrd_1_breadboard/keybrd_1_breadboard.ino +++ b/tutorials/keybrd_1_breadboard/keybrd_1_breadboard.ino @@ -32,7 +32,7 @@ Code_Sc s_c(KEY_C); Key* ptrsKeys_0[] = { &s_1, &s_a }; Row_uC row_0(0, readPins, READ_PIN_COUNT, ptrsKeys_0); -Key* ptrsKeys_1[] = { &s_b, &s_c }; +Key* ptrsKeys_1[] = { &s_b, &s_c }; Row_uC row_1(1, readPins, READ_PIN_COUNT, ptrsKeys_1); // ################### MAIN #################### diff --git a/tutorials/keybrd_4b_split_keyboard_with_shift_registers/keybrd_4b_split_keyboard_with_shift_registers.ino b/tutorials/keybrd_4b_split_keyboard_with_shift_registers/keybrd_4b_split_keyboard_with_shift_registers.ino new file mode 100644 index 0000000..5831af4 --- /dev/null +++ b/tutorials/keybrd_4b_split_keyboard_with_shift_registers/keybrd_4b_split_keyboard_with_shift_registers.ino @@ -0,0 +1,96 @@ +/* tutorial_4a_split_keyboard_with_shift_registers.ino +Tested on Teensy LC and two 74HC165 shift registers. + +The right matrix has 2 shift registers daisy chained. + + Layout Layout +| Left |**0** | Right |**0**|**1**|**2**|**3**|**4**|**5**|**6**|**7**| +|:-----:|-----| |:-----:|-----|-----|-----|-----|-----|-----|-----|-----| +| **0** | x | | **0** | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | +| **1** | y | | **1** | a | b | c | d | e | f | g | h | +*/ +// ################## GLOBAL ################### +// ================= INCLUDES ================== +#include +#include + +//Left matrix +#include + +//Right matrix +#include +#include + +// =============== CONFIGURATION =============== +ScanDelay scanDelay(9000); + +// =================== 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_e(KEY_E); +Code_Sc s_f(KEY_F); +Code_Sc s_g(KEY_G); + +Code_Sc s_x(KEY_X); +Code_Sc s_y(KEY_Y); + +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); + +// =============== LEFT MATRIX ================= +//set left matrix for active low +const bool Scanner_uC::STROBE_ON = LOW; +const bool Scanner_uC::STROBE_OFF = HIGH; + +//column pin +uint8_t readPins[] = {14}; +uint8_t READ_PIN_COUNT = sizeof(readPins)/sizeof(*readPins); + +//rows +Key* ptrsKeys_L0[] = { &s_x }; +Row_uC row_L0(0, readPins, READ_PIN_COUNT, ptrsKeys_L0); + +Key* ptrsKeys_L1[] = { &s_y }; +Row_uC row_L1(1, readPins, READ_PIN_COUNT, ptrsKeys_L1); + +// =============== RIGHT MATRIX ================ +//set matrix to active high +const bool Scanner_ShiftRegs74HC165::STROBE_ON = HIGH; +const bool Scanner_ShiftRegs74HC165::STROBE_OFF = LOW; + +//chip select pin +const uint8_t Scanner_ShiftRegs74HC165::SHIFT_LOAD = 10; + +//rows +Key* ptrsKeys_R0[] = { &s_6, &s_5, &s_4, &s_3, //shift regiser on right + &s_c, &s_d, &s_e, &s_f, + &s_2, &s_1, &s_0, &s_g, //shift regiser on left + &s_a, &s_b }; //unused input pins are grounded +Row_ShiftRegisters row_R0(0, sizeof(ptrsKeys_R0)/sizeof(*ptrsKeys_R0), ptrsKeys_R0); + +// ################### MAIN #################### +void setup() +{ + Keyboard.begin(); + SPI.begin(); + row_R0.begin(); +} + +void loop() +{ + //left matrix + row_L0.process(); + row_L1.process(); + + //right matrix + row_R0.process(); + + scanDelay.delay(); +} diff --git a/tutorials/keybrd_4c_split_with_IOE/keybrd_4c_split_with_IOE.ino b/tutorials/keybrd_4c_split_with_IOE/keybrd_4c_split_with_IOE.ino new file mode 100644 index 0000000..656f571 --- /dev/null +++ b/tutorials/keybrd_4c_split_with_IOE/keybrd_4c_split_with_IOE.ino @@ -0,0 +1,105 @@ +/* keybrd_4c_split_with_IOE_annotated.ino + +| Left | **0** | **1** | | Right | **0** | **1** | +|:-----:|-------|-------| |:-----:|-------|-------| +| **0** | a | b | | **0** | 1 | 2 | +| **1** | shift | c | | **1** | 3 | shift | + +| Left | **0** | **1** | | Right | **0** | **1** | todo +|:-----:|-------|-------| |:-----:|-------|-------| +| **1** | 1 | 2 | | **1** | 3 | 4 | +| **0** | a | b | | **0** | c | d | +*/ +// ################## GLOBAL ################### +// ================= INCLUDES ================== +#include +#include + +//left matrix +#include + +//right matrix +#include +#include +#include +#include + +// ============ SPEED CONFIGURATION ============ +ScanDelay scanDelay(9000); + +// ================ LEFT MATRIX ================ +// ---------------- ACTIVE STATE --------------- +const bool Scanner_uC::STROBE_ON = LOW; //active low +const bool Scanner_uC::STROBE_OFF = HIGH; + +// ------------------- PINS -------------------- +uint8_t readPins[] = {14, 15}; + +// ================ RIGHT MATRIX =============== +const bool Scanner_Port::STROBE_ON = HIGH; //active high +const bool Scanner_Port::STROBE_OFF = LOW; + +const uint8_t PortIOE::ADDR = 0x18; + +// ------------------ PORT 1 ------------------- +PortIOE port1_R(1, 0); +PortWrite_MCP23S17 portWrite1_R(port1_R); + +// ------------------ PORT 0 ------------------- +PortIOE port0_R(0, 0); +PortWrite_MCP23S17 portWrite0_R(port0_R); +PortRead_MCP23S17 portRead0_R(port0_R, 1<<0 | 1<<1 ); + +// =================== CODES =================== +Code_Sc s_shiftL(MODIFIERKEY_LEFT_SHIFT); +Code_Sc s_shiftR(MODIFIERKEY_RIGHT_SHIFT); + +Code_Sc s_a(KEY_A); +Code_Sc s_b(KEY_B); +Code_Sc s_c(KEY_C); +Code_Sc s_1(KEY_1); +Code_Sc s_2(KEY_2); +Code_Sc s_3(KEY_3); + +// =================== ROWS ==================== +// ---------------- LEFT ROWS ------------------ +Key* ptrsKeys_L0[] = { &s_a, &s_b }; +const uint8_t KEY_COUNT_L0 = sizeof(ptrsKeys_L0)/sizeof(*ptrsKeys_L0); +Row_uC row_L0(0, readPins, KEY_COUNT_L0, ptrsKeys_L0); + +Key* ptrsKeys_L1[] = { &s_c, &s_shiftL }; +const uint8_t KEY_COUNT_L1 = sizeof(ptrsKeys_L1)/sizeof(*ptrsKeys_L1); +Row_uC row_L1(1, readPins, KEY_COUNT_L1, ptrsKeys_L1); + +// ---------------- RIGHT ROWS ----------------- +Key* ptrsKeys_R0[] = { &s_1, &s_2 }; +const uint8_t KEY_COUNT_R0 = sizeof(ptrsKeys_R0)/sizeof(*ptrsKeys_R0); +Row_IOE row_R0(portWrite1_R, 1<<0, portRead0_R, KEY_COUNT_R0, ptrsKeys_R0); + +Key* ptrsKeys_R1[] = { &s_3, &s_shiftR }; +const uint8_t KEY_COUNT_R1 = sizeof(ptrsKeys_R1)/sizeof(*ptrsKeys_R1); +Row_IOE row_R1(portWrite1_R, 1<<1, portRead0_R, KEY_COUNT_R1, ptrsKeys_R1); + +// ################### MAIN #################### +void setup() +{ + Keyboard.begin(); + Wire.begin(); //Wire.begin() must be called before port begin() + portWrite1_R.begin(); + portRead0_R.begin(); +} + +void loop() +{ + //left matrix + row_L0.process(); + row_L1.process(); + + //right matrix + row_R0.process(); + row_R1.process(); + + scanDelay.delay(); + //debug.print_scans_per_second(); + //debug.print_microseconds_per_scan(); +} diff --git a/tutorials/tutorial_10_writing_IOE_Port_classes.md b/tutorials/tutorial_10_writing_IOE_Port_classes.md index 1c34dfc..c3304dc 100644 --- a/tutorials/tutorial_10_writing_IOE_Port_classes.md +++ b/tutorials/tutorial_10_writing_IOE_Port_classes.md @@ -1,19 +1,38 @@ -Tutorial 10 - writing IOE Port classes -=========================================== +Tutorial 10 - writing new IOE Port classes +========================================== Port classes are the keybrd library's interface to I/O expander ports. -To write your own Port class: +To write a new Port class: -1. Get a copy of the I/O expander datasheet. -2. Study other keybrd Port classes. - -For example, the keybrd_DH library uses these keybrd classes for its PCA9655E I/O expander: -* PortWrite_PCA9655E -* PortRead_PCA9655E -* LED_PCA9655E - -Debugging I/O expander code is hard because SPI or I2C protocol adds a level of indirection. -If you haven't written Arduino code for an I/O expander before, learn from an Arduiono I/O expander tutorial before attempting it here. +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). + Refer to the [Arduino SPI](https://www.arduino.cc/en/Reference/SPI) + or [Arduino Wire (I2C)](https://www.arduino.cc/en/Reference/Wire) library +3. Get familiar with your I/O expander. + 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. + Search for Arduino sketch examples containing your I/O expander + ([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/ + /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. + Debugging I/O expander code is hard because SPI or I2C protocol adds a level of indirection.
Creative Commons License
keybrd tutorial by Wolfram Volpi is licensed under a Creative Commons Attribution 4.0 International License.
Permissions beyond the scope of this license may be available at https://github.com/wolfv6/keybrd/issues/new. diff --git a/tutorials/tutorial_8b_creating_and_publishing_your_own_keybrd_extension_library.md b/tutorials/tutorial_8b_sharing_your_keybrd_extension_library.md similarity index 77% rename from tutorials/tutorial_8b_creating_and_publishing_your_own_keybrd_extension_library.md rename to tutorials/tutorial_8b_sharing_your_keybrd_extension_library.md index 86b6793..01d5161 100644 --- a/tutorials/tutorial_8b_creating_and_publishing_your_own_keybrd_extension_library.md +++ b/tutorials/tutorial_8b_sharing_your_keybrd_extension_library.md @@ -1,12 +1,12 @@ -Tutorial 8b - creating and publishing your own keybrd extension library -======================================================================= +Tutorial 8b - sharing your keybrd extension library +=================================================== Publishing and listing your keybrd extension library allows others to find and install your library. The keybrd extension library name should start with "keybrd_" so that it is easy for other people to find. There are two ways to publish and list an Arduino library. -Publishing anywhere with listing on Arduino Playground LibraryList ------------------------------------------------------------------- +Publish anywhere and list on Arduino Playground +----------------------------------------------- Publishing your keybrd extension library with the following directory structure makes it easy for others to understand. keybrd_MyKeyboard/ @@ -25,16 +25,13 @@ Publishing your keybrd extension library with the following directory structure instantiations_codes.h instantiations_rows.h -When your ready to list your keybrd extension library, go to the [Arduino Playground keybrd page](http://playground.arduino.cc/Main/keybrd). +When your ready to list your keybrd extension library, + add a link and short description of your keybrd extension library to the [Arduino Playground keybrd page](http://playground.arduino.cc/Main/keybrd) under "keybrd extension libraries". Arduino playground is a wiki. Links on how to edit the wiki are on the bottom left under "Participate". -You can also add a picture of a keyboard that uses your keybrd extension library. -Uploading files to the Playground is not allowed for standard users. -So if you want to add a picture, it will need to be hosted somewhere else. - -Publishing on GitHub with listing on Arduino Library-Manager and Arduino Playground LibraryList ------------------------------------------------------------------------------------------------ +Publish on GitHub and list on Arduino Library-Manager and Arduino Playground +---------------------------------------------------------------------------- The advantage of using GitHub is that users can submit pull requests. The advantage of using Arduino Library-Manager is that users can easily find and install your library through the Arduino IDE. @@ -78,14 +75,11 @@ Example library.properties file: Instructions for listing a library on Arduino Library Manager are at: https://github.com/arduino/Arduino/wiki/Library-Manager-FAQ -After it has been accepted into the Arduino IDE Library Manager, add your keybrd extension library to the [Arduino Playground keybrd page](http://playground.arduino.cc/Main/keybrd). +After it has been accepted into the Arduino IDE Library Manager, + add a link and short description of your keybrd extension library to the [Arduino Playground keybrd page](http://playground.arduino.cc/Main/keybrd) under "keybrd extension libraries". Arduino playground is a wiki. Links on how to edit the wiki are on the bottom left under "Participate". -You can also add a picture of a keyboard that uses your keybrd extension library. -Uploading files to the Playground is not allowed for standard users. -So if you want to add a picture, it will need to be hosted somewhere else. - To publish a new release of a library that is already listed on Arduino Library Manager 1. Update the version in your library.properties file: diff --git a/tutorials/tutorial_8c_sharing_your_keybrd_sketch.md b/tutorials/tutorial_8c_sharing_your_keybrd_sketch.md new file mode 100644 index 0000000..97fd1c5 --- /dev/null +++ b/tutorials/tutorial_8c_sharing_your_keybrd_sketch.md @@ -0,0 +1,17 @@ +Tutorial 8c - sharing your keybrd sketch +======================================== +keybrd sketches that use a keybrd extension library should be published in the extension library's examples directory. +keybrd sketches that do not use a keybrd extension library can be published anywhere. + +Publishing and listing your keybrd sketch allows others to find your sketch. + +Publish anywhere and list on Arduino Playground +----------------------------------------------- +Publish your sketch anywhere. Some free places are: +* GitHub repository +* [GitHub Gist](https://help.github.com/categories/gists/) +* [geekhack Making Stuff Together!](https://geekhack.org/index.php?board=117.0) + +Then add a link and short description of your keybrd sketch to the [Arduino Playground keybrd page](http://playground.arduino.cc/Main/keybrd) under "keybrd sketches". +Arduino playground is a wiki. +Links on how to edit the wiki are on the bottom left under "Participate". diff --git a/unit_tests/MCP23S17_read/MCP23S17_read.ino b/unit_tests/MCP23S17_read/MCP23S17_read.ino new file mode 100644 index 0000000..27911fd --- /dev/null +++ b/unit_tests/MCP23S17_read/MCP23S17_read.ino @@ -0,0 +1,42 @@ +/* this works +The setup is an MCP23S17 I/O expander on a Teensy LC controller. +MCP23S17 port B pins are alternately grounded and energized. +portBState is a bitwise reading of port B. +output is: 10101010 + +posted on http://arduino.stackexchange.com/questions/tagged/spi +http://arduino.stackexchange.com/questions/28792/reading-an-mcp23s17-i-o-expander-port-with-the-arduino-spi-library +*/ +#include + +const uint8_t ADDR = 0x20; //MCP23S17 address, all 3 ADDR pins are grounded +const uint8_t OPCODE_READ = (ADDR << 1 | 0x01); //MCP23S17 opcode read has LSB set + +const uint8_t IODIRB = 0x01; +const uint8_t GPIOB = 0x13; + +uint8_t portBState = 0; //bit wise + +void setup() +{ + Serial.begin(9600); + delay(1000); + + pinMode(SS, OUTPUT); //configure controller's Slave Select pin to output + digitalWrite(SS, HIGH); //disable Slave Select + SPI.begin(); + + //IODIRB register is already configured to input by default + + SPI.beginTransaction(SPISettings (SPI_CLOCK_DIV8, MSBFIRST, SPI_MODE0)); //gain control of SPI bus + digitalWrite(SS, LOW); //enable Slave Select + SPI.transfer(OPCODE_READ); //read command + SPI.transfer(GPIOB); //register address to read data from + portBState = SPI.transfer(0); //save the data (0 is dummy data to send) + digitalWrite(SS, HIGH); //disable Slave Select + SPI.endTransaction(); //release the SPI bus + + Serial.println(portBState, BIN); //should print 10101010 +} + +void loop() { } diff --git a/unit_tests/MCP23S17_write/MCP23S17_write.ino b/unit_tests/MCP23S17_write/MCP23S17_write.ino new file mode 100644 index 0000000..76dfc69 --- /dev/null +++ b/unit_tests/MCP23S17_write/MCP23S17_write.ino @@ -0,0 +1,54 @@ +/* this works with volt meter (MCP23S17 on 3.3v does not output enough power for LEDs) +LED lights w/o resistor, not light with 56 ohm resistor +blink LED on MCP23S17 port A pin +from Example 41.1 - Microchip MCP23017 with Arduino + http://tronixstuff.com/tutorials > chapter 41 + http://tronixstuff.com/2011/08/26/tutorial-maximising-your-arduinos-io-ports/ + John Boxall | CC by-sa-nc +from http://69.5.26.215/forum/?id=10945&page=3 #35 +modified to test MCP23S17 (SPI) using syntax from + http://arduino.stackexchange.com/questions/16348/how-do-you-use-spi-on-an-arduino + > + +SPISettings from http://arduino.stackexchange.com/questions/14191/mcp23s17-programming-iodirx-register-works-in-loop-but-not-in-setup +*/ +#include + +const uint8_t ADDR = 0x20; //MCP23S17 address, all ADDR pins are grounded +const uint8_t OPCODE_WRITE = (ADDR << 1); //MCP23S17 opcode write has LSB clear + +const uint8_t IODIRA = 0x00; //LEDs are on port A +const uint8_t GPIOA = 0x12; + +uint8_t LED_state = 0; //bit wise + +void IOEWrite(const uint8_t registerAddr, const uint8_t data) +{ + SPI.beginTransaction(SPISettings (SPI_CLOCK_DIV8, MSBFIRST, SPI_MODE0)); //slower clock + digitalWrite(SS, LOW); //enable Slave Select + SPI.transfer(OPCODE_WRITE); //write command + SPI.transfer(registerAddr); //register address to write data to + SPI.transfer(data); //data + digitalWrite(SS, HIGH); //disable Slave Select + SPI.endTransaction(); //release the SPI bus +} + +void setup() +{ + Serial.begin(9600); + + pinMode(SS, OUTPUT); //configure controller's Slave Select pin to output + digitalWrite(SS, HIGH); //disable Slave Select + SPI.begin(); + + IOEWrite(IODIRA, 0x00); //configure IODIRA register to output +} + +void loop() +{ + IOEWrite(GPIOA, LED_state); //set all GPIOA pins + + delay(2000); + //Serial.println(LED_state, BIN); //prints alternating 0 and 11111111 + LED_state = ~LED_state; //toggle LED on/off +} diff --git a/unit_tests/PortRead_MCP23S17/PortRead_MCP23S17.ino b/unit_tests/PortRead_MCP23S17/PortRead_MCP23S17.ino new file mode 100644 index 0000000..dd84376 --- /dev/null +++ b/unit_tests/PortRead_MCP23S17/PortRead_MCP23S17.ino @@ -0,0 +1,32 @@ +/* unit test for PortRead_MCP23S17 +The setup is an MCP23S17 I/O expander on a Teensy LC controller. +MCP23S17 port-B pins are alternately grounded and energized. +portBState is a bitwise reading of port B. +output is: 10101010 + +posted on http://arduino.stackexchange.com/questions/tagged/spi +http://arduino.stackexchange.com/questions/28792/reading-an-mcp23s17-i-o-expander-port-with-the-arduino-spi-library +*/ +#include "PortIOE.h" +#include "PortRead_MCP23S17.h" +#include "PortWrite_MCP23S17.h" + +const uint8_t PortIOE::ADDR = 0x20; //MCP23S17 address, all 3 ADDR pins are grounded +PortIOE portB(1, 0); + +PortRead_MCP23S17 portBRead(portB); +PortWrite_MCP23S17 portBWrite(portB); //PortBWrite needed for begin() + +void setup() +{ + uint8_t portBState; //bit wise + + delay(6000); + portBWrite.begin(); + + portBState = portBRead.read(); + Keyboard.print("portBState = "); + Keyboard.println(portBState, BIN); //should print 10101010 +} + +void loop() { } diff --git a/unit_tests/PortWrite_MCP23S17/PortWrite_MCP23S17.ino b/unit_tests/PortWrite_MCP23S17/PortWrite_MCP23S17.ino new file mode 100644 index 0000000..2901f5e --- /dev/null +++ b/unit_tests/PortWrite_MCP23S17/PortWrite_MCP23S17.ino @@ -0,0 +1,35 @@ +/* unit test for PortRead_MCP23S17 +The setup is an MCP23S17 I/O expander on a Teensy LC controller. +MCP23S17 port-A GPIO pins are not connected to anything. +Port-A GPIO-pin ouputs alternate between 0 and 3.3 volts. + +Use a volt meter to measure port-A GPIO-pin ouputs. +MCP23S17 on 3.3v does not output enough power to reliable light LEDs + LED lights w/o resistor + LED not light with 56 ohm resistor +*/ +#include "PortIOE.h" +#include "PortWrite_MCP23S17.h" + +const uint8_t PortIOE::ADDR = 0x20; //MCP23S17 address, all 3 ADDR pins are grounded +PortIOE portA(0, 0); + +PortWrite_MCP23S17 portAWrite(portA); //PortAWrite needed for begin() + +const uint8_t GPIOA = 0x12; //LEDs are on port A + +void setup() +{ + delay(6000); + portAWrite.begin(); + //Keyboard.print("start blinking"); +} + +void loop() +{ + portAWrite.write(~0, HIGH); //set all GPIOA pins HIGH + delay(2000); + + portAWrite.write(~0, LOW); //set all GPIOA pins LOW + delay(2000); +}