├── .DS_Store ├── .gitignore ├── .vscode └── extensions.json ├── test └── README ├── platformio.ini ├── src ├── Rotary.h ├── main.cpp ├── Adafruit_MCP23017.h ├── RotaryEncOverMCP.h ├── Rotary.cpp └── Adafruit_MCP23017.cpp ├── lib └── README └── include └── README /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fcoury/esp8266_mcp23017_oled96x64_rotary/master/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .vscode/.browse.c_cpp.db* 3 | .vscode/c_cpp_properties.json 4 | .vscode/launch.json 5 | .vscode/ipch 6 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "platformio.platformio-ide" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /test/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for PlatformIO Unit Testing and project tests. 3 | 4 | Unit Testing is a software testing method by which individual units of 5 | source code, sets of one or more MCU program modules together with associated 6 | control data, usage procedures, and operating procedures, are tested to 7 | determine whether they are fit for use. Unit testing finds problems early 8 | in the development cycle. 9 | 10 | More information about PlatformIO Unit Testing: 11 | - https://docs.platformio.org/page/plus/unit-testing.html 12 | -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [env:nodemcuv2] 12 | platform = espressif8266 13 | board = nodemcuv2 14 | framework = arduino 15 | lib_extra_dirs = ~/Documents/Arduino/libraries 16 | lib_deps = 17 | Wire 18 | moononournation/GFX Library for Arduino@^1.0.9 19 | tectiv3/Adafruit-SSD1331@0.0.0-alpha+sha.488737cb7a 20 | adafruit/Adafruit BusIO@^1.7.3 21 | lib_ignore = 22 | GFX Library for Arduino 23 | ArduinoSTL 24 | -------------------------------------------------------------------------------- /src/Rotary.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Rotary encoder library for Arduino. 3 | */ 4 | 5 | #ifndef rotary_h 6 | #define rotary_h 7 | 8 | #include "Arduino.h" 9 | 10 | // Enable this to emit codes twice per step. 11 | //#define HALF_STEP 12 | 13 | // Enable weak pullups 14 | #define ENABLE_PULLUPS 15 | 16 | // Values returned by 'process' 17 | // No complete step yet. 18 | #define DIR_NONE 0x0 19 | // Clockwise step. 20 | #define DIR_CW 0x10 21 | // Anti-clockwise step. 22 | #define DIR_CCW 0x20 23 | 24 | class Rotary 25 | { 26 | public: 27 | Rotary(char, char); 28 | // Process pin(s) 29 | unsigned char process(); 30 | unsigned char process(unsigned char pin1State, unsigned char pin2State); 31 | 32 | private: 33 | unsigned char state; 34 | unsigned char pin1; 35 | unsigned char pin2; 36 | }; 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /lib/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link into executable file. 4 | 5 | The source code of each library should be placed in a an own separate directory 6 | ("lib/your_library_name/[here are source files]"). 7 | 8 | For example, see a structure of the following two libraries `Foo` and `Bar`: 9 | 10 | |--lib 11 | | | 12 | | |--Bar 13 | | | |--docs 14 | | | |--examples 15 | | | |--src 16 | | | |- Bar.c 17 | | | |- Bar.h 18 | | | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html 19 | | | 20 | | |--Foo 21 | | | |- Foo.c 22 | | | |- Foo.h 23 | | | 24 | | |- README --> THIS FILE 25 | | 26 | |- platformio.ini 27 | |--src 28 | |- main.c 29 | 30 | and a contents of `src/main.c`: 31 | ``` 32 | #include 33 | #include 34 | 35 | int main (void) 36 | { 37 | ... 38 | } 39 | 40 | ``` 41 | 42 | PlatformIO Library Dependency Finder will find automatically dependent 43 | libraries scanning project source files. 44 | 45 | More information about PlatformIO Library Dependency Finder 46 | - https://docs.platformio.org/page/librarymanager/ldf.html 47 | -------------------------------------------------------------------------------- /include/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project header files. 3 | 4 | A header file is a file containing C declarations and macro definitions 5 | to be shared between several project source files. You request the use of a 6 | header file in your project source file (C, C++, etc) located in `src` folder 7 | by including it, with the C preprocessing directive `#include'. 8 | 9 | ```src/main.c 10 | 11 | #include "header.h" 12 | 13 | int main (void) 14 | { 15 | ... 16 | } 17 | ``` 18 | 19 | Including a header file produces the same results as copying the header file 20 | into each source file that needs it. Such copying would be time-consuming 21 | and error-prone. With a header file, the related declarations appear 22 | in only one place. If they need to be changed, they can be changed in one 23 | place, and programs that include the header file will automatically use the 24 | new version when next recompiled. The header file eliminates the labor of 25 | finding and changing all the copies as well as the risk that a failure to 26 | find one copy will result in inconsistencies within a program. 27 | 28 | In C, the usual convention is to give header files names that end with `.h'. 29 | It is most portable to use only letters, digits, dashes, and underscores in 30 | header file names, and at most one dot. 31 | 32 | Read more about using header files in official GCC documentation: 33 | 34 | * Include Syntax 35 | * Include Operation 36 | * Once-Only Headers 37 | * Computed Includes 38 | 39 | https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html 40 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #define OLED_SCK D5 11 | #define OLED_SDA D7 12 | #define OLED_RST D6 13 | #define OLED_DC D8 14 | #define OLED_CS D0 15 | 16 | #define BLACK 0x0000 17 | #define WHITE 0xFFFF 18 | 19 | void RotaryEncoderChanged(bool antiClock, int id); 20 | 21 | Adafruit_SSD1331 display = Adafruit_SSD1331(OLED_CS, OLED_DC, OLED_RST); 22 | Adafruit_MCP23017 mcp; 23 | Adafruit_MCP23017 *mcpr = &mcp; 24 | RotaryEncOverMCP rotaryEncoder = RotaryEncOverMCP(&mcp, 7, 6, &RotaryEncoderChanged); 25 | 26 | int value = 0; 27 | int curValue = -1; 28 | int presses = 0; 29 | int curPresses = -1; 30 | int butStatus = 0; 31 | int curButStatus = -1; 32 | 33 | void setup() 34 | { 35 | Serial.begin(9600); 36 | Serial.println("Initializing OLED..."); 37 | 38 | Wire.begin(D2, D1); 39 | Wire.beginTransmission(0x20); 40 | int res = Wire.endTransmission(); 41 | Serial.print("Result: "); 42 | Serial.println(res); 43 | 44 | mcp.begin(); 45 | display.begin(); 46 | Serial.println("Done!"); 47 | 48 | rotaryEncoder.init(); 49 | display.fillScreen(BLACK); 50 | 51 | // mcpr->pullUp(5, 1); 52 | mcpr->pinMode(5, INPUT); 53 | 54 | display.fillScreen(BLACK); 55 | display.setTextColor(WHITE); 56 | display.setTextSize(1); 57 | } 58 | 59 | void draw() 60 | { 61 | if (curValue != value || curPresses != presses) 62 | { 63 | Serial.println("Painting..."); 64 | 65 | display.setCursor(2, 1); 66 | display.print("Rotary "); 67 | if (curValue != value) 68 | { 69 | display.setTextColor(BLACK); 70 | display.print(curValue); 71 | curValue = value; 72 | display.setTextColor(WHITE); 73 | display.setCursor(2, 1); 74 | display.print("Rotary "); 75 | } 76 | display.print(value); 77 | 78 | display.setCursor(2, 11); 79 | display.print("Presses "); 80 | if (curPresses != presses) 81 | { 82 | display.setTextColor(BLACK); 83 | display.print(curPresses); 84 | curPresses = presses; 85 | display.setTextColor(WHITE); 86 | display.setCursor(2, 11); 87 | display.print("Presses "); 88 | } 89 | curPresses = presses; 90 | display.print(presses); 91 | } 92 | } 93 | 94 | void loop() 95 | { 96 | rotaryEncoder.poll(); 97 | int butStatus = mcpr->digitalRead(5); 98 | if (curButStatus != butStatus) 99 | { 100 | curButStatus = butStatus; 101 | if (butStatus == 0) 102 | { 103 | Serial.println("Button pressed"); 104 | presses++; 105 | } 106 | } 107 | 108 | draw(); 109 | } 110 | 111 | void RotaryEncoderChanged(bool antiClock, int id) 112 | { 113 | Serial.println("Encoder " + String(id) + ": " + (antiClock ? String("left") : String("right"))); 114 | value += antiClock ? -1 : 1; 115 | } 116 | -------------------------------------------------------------------------------- /src/Adafruit_MCP23017.h: -------------------------------------------------------------------------------- 1 | /*************************************************** 2 | This is a library for the MCP23017 i2c port expander 3 | 4 | These displays use I2C to communicate, 2 pins are required to 5 | interface 6 | Adafruit invests time and resources providing this open source code, 7 | please support Adafruit and open-source hardware by purchasing 8 | products from Adafruit! 9 | 10 | Written by Limor Fried/Ladyada for Adafruit Industries. 11 | BSD license, all text above must be included in any redistribution 12 | ****************************************************/ 13 | 14 | #ifndef _Adafruit_MCP23017_H_ 15 | #define _Adafruit_MCP23017_H_ 16 | 17 | // Don't forget the Wire library 18 | #ifndef ARDUINO_AVR_GEMMA 19 | //TinyWireM is now part of 20 | // Adafruit version of Wire Library, so this 21 | // will work with Adafruit ATtiny85's 22 | //But Arduino Gemma doesn't use that library 23 | // We do NOT want to include Wire if it's an arduino Gemma 24 | #include 25 | #else 26 | #include 27 | #define Wire TinyWireM 28 | #endif 29 | 30 | class Adafruit_MCP23017 31 | { 32 | public: 33 | void begin(uint8_t addr); 34 | void begin(void); 35 | 36 | void pinMode(uint8_t p, uint8_t d); 37 | void digitalWrite(uint8_t p, uint8_t d); 38 | void pullUp(uint8_t p, uint8_t d); 39 | uint8_t digitalRead(uint8_t p); 40 | 41 | void writeGPIOAB(uint16_t); 42 | uint16_t readGPIOAB(); 43 | uint8_t readGPIO(uint8_t b); 44 | 45 | void setupInterrupts(uint8_t mirroring, uint8_t open, uint8_t polarity); 46 | void setupInterruptPin(uint8_t p, uint8_t mode); 47 | uint8_t getLastInterruptPin(); 48 | uint8_t getLastInterruptPinValue(); 49 | 50 | uint16_t readINTCAPAB(); 51 | 52 | private: 53 | uint8_t i2caddr; 54 | 55 | uint8_t bitForPin(uint8_t pin); 56 | uint8_t regForPin(uint8_t pin, uint8_t portAaddr, uint8_t portBaddr); 57 | 58 | uint8_t readRegister(uint8_t addr); 59 | void writeRegister(uint8_t addr, uint8_t value); 60 | 61 | /** 62 | * Utility private method to update a register associated with a pin (whether port A/B) 63 | * reads its value, updates the particular bit, and writes its value. 64 | */ 65 | void updateRegisterBit(uint8_t p, uint8_t pValue, uint8_t portAaddr, uint8_t portBaddr); 66 | }; 67 | 68 | #define MCP23017_ADDRESS 0x20 69 | 70 | // registers 71 | #define MCP23017_IODIRA 0x00 72 | #define MCP23017_IPOLA 0x02 73 | #define MCP23017_GPINTENA 0x04 74 | #define MCP23017_DEFVALA 0x06 75 | #define MCP23017_INTCONA 0x08 76 | #define MCP23017_IOCONA 0x0A 77 | #define MCP23017_GPPUA 0x0C 78 | #define MCP23017_INTFA 0x0E 79 | #define MCP23017_INTCAPA 0x10 80 | #define MCP23017_GPIOA 0x12 81 | #define MCP23017_OLATA 0x14 82 | 83 | #define MCP23017_IODIRB 0x01 84 | #define MCP23017_IPOLB 0x03 85 | #define MCP23017_GPINTENB 0x05 86 | #define MCP23017_DEFVALB 0x07 87 | #define MCP23017_INTCONB 0x09 88 | #define MCP23017_IOCONB 0x0B 89 | #define MCP23017_GPPUB 0x0D 90 | #define MCP23017_INTFB 0x0F 91 | #define MCP23017_INTCAPB 0x11 92 | #define MCP23017_GPIOB 0x13 93 | #define MCP23017_OLATB 0x15 94 | 95 | #define MCP23017_INT_ERR 255 96 | 97 | #endif 98 | -------------------------------------------------------------------------------- /src/RotaryEncOverMCP.h: -------------------------------------------------------------------------------- 1 | /* 2 | * RotaryEncOverMCP.h 3 | * 4 | * Created on: 21.05.2018 5 | * Author: Maxi 6 | */ 7 | 8 | #ifndef SRC_ROTARYENCOVERMCP_H_ 9 | #define SRC_ROTARYENCOVERMCP_H_ 10 | 11 | /* Describes new objects based on the Rotary and Adafruit MCP23017 library */ 12 | #include 13 | #include "Rotary.h" 14 | 15 | /* function pointer definition */ 16 | typedef void (*rotaryActionFunc)(bool clockwise, int id); 17 | 18 | /* We describe an object in which we instantiate a rotary encoder 19 | * over an I2C expander. 20 | * It holds the information: 21 | * * to which MCP object it's connected 22 | * * which pin it is connected to 23 | * * what function to call when there is a change 24 | * */ 25 | class RotaryEncOverMCP 26 | { 27 | public: 28 | RotaryEncOverMCP(Adafruit_MCP23017 *mcp, byte pinA, byte pinB, rotaryActionFunc actionFunc = nullptr, int id = 0) 29 | : rot(pinA, pinB), mcp(mcp), 30 | pinA(pinA), pinB(pinB), 31 | actionFunc(actionFunc), id(id) 32 | { 33 | Serial.println("Inside here"); 34 | } 35 | 36 | /* Initialize object in the MCP */ 37 | void init() 38 | { 39 | if (mcp != nullptr) 40 | { 41 | mcp->pinMode(pinA, INPUT); 42 | mcp->pullUp(pinA, 0); //disable pullup on this pin 43 | mcp->setupInterruptPin(pinA, CHANGE); 44 | mcp->pinMode(pinB, INPUT); 45 | mcp->pullUp(pinB, 0); //disable pullup on this pin 46 | mcp->setupInterruptPin(pinB, CHANGE); 47 | } 48 | } 49 | 50 | /* On an interrupt, can be called with the value of the GPIOAB register (or INTCAP) */ 51 | void feedInput(uint16_t gpioAB) 52 | { 53 | // Serial.println(gpioAB, BIN); 54 | uint8_t pinValA = bitRead(gpioAB, pinA); 55 | uint8_t pinValB = bitRead(gpioAB, pinB); 56 | // if (pinValA != 1 || pinValB != 1) 57 | // { 58 | // Serial.print("Feed input: "); 59 | // Serial.print(pinValA); 60 | // Serial.print(" | "); 61 | // Serial.println(pinValB); 62 | // } 63 | uint8_t event = rot.process(pinValA, pinValB); 64 | // Serial.println(event); 65 | if (event == DIR_CW || event == DIR_CCW) 66 | { 67 | //clock wise or counter-clock wise 68 | bool clockwise = event == DIR_CW; 69 | //Call into action function if registered 70 | if (actionFunc) 71 | { 72 | actionFunc(clockwise, id); 73 | } 74 | } 75 | } 76 | 77 | /* Poll the encoder. Will cause an I2C transfer. */ 78 | void poll() 79 | { 80 | if (mcp != nullptr) 81 | { 82 | feedInput(mcp->readGPIOAB()); 83 | } 84 | } 85 | 86 | Adafruit_MCP23017 *getMCP() 87 | { 88 | return mcp; 89 | } 90 | 91 | private: 92 | Rotary rot; /* the rotary object which will be created*/ 93 | Adafruit_MCP23017 *mcp = nullptr; /* pointer the I2C GPIO expander it's connected to */ 94 | uint8_t pinA = 0; 95 | uint8_t pinB = 0; /* the pin numbers for output A and output B */ 96 | rotaryActionFunc actionFunc = nullptr; /* function pointer, will be called when there is an action happening */ 97 | int id = 0; /* optional ID for identification */ 98 | }; 99 | 100 | #endif /* SRC_ROTARYENCOVERMCP_H_ */ 101 | -------------------------------------------------------------------------------- /src/Rotary.cpp: -------------------------------------------------------------------------------- 1 | /* Rotary encoder handler for arduino. 2 | * 3 | * Copyright 2011 Ben Buxton. Licenced under the GNU GPL Version 3. 4 | * Contact: bb@cactii.net 5 | * 6 | */ 7 | 8 | #include "Arduino.h" 9 | #include "Rotary.h" 10 | 11 | /* 12 | * The below state table has, for each state (row), the new state 13 | * to set based on the next encoder output. From left to right in, 14 | * the table, the encoder outputs are 00, 01, 10, 11, and the value 15 | * in that position is the new state to set. 16 | */ 17 | 18 | #define R_START 0x0 19 | 20 | #ifdef HALF_STEP 21 | // Use the half-step state table (emits a code at 00 and 11) 22 | #define R_CCW_BEGIN 0x1 23 | #define R_CW_BEGIN 0x2 24 | #define R_START_M 0x3 25 | #define R_CW_BEGIN_M 0x4 26 | #define R_CCW_BEGIN_M 0x5 27 | const unsigned char ttable[6][4] = { 28 | // R_START (00) 29 | {R_START_M, R_CW_BEGIN, R_CCW_BEGIN, R_START}, 30 | // R_CCW_BEGIN 31 | {R_START_M | DIR_CCW, R_START, R_CCW_BEGIN, R_START}, 32 | // R_CW_BEGIN 33 | {R_START_M | DIR_CW, R_CW_BEGIN, R_START, R_START}, 34 | // R_START_M (11) 35 | {R_START_M, R_CCW_BEGIN_M, R_CW_BEGIN_M, R_START}, 36 | // R_CW_BEGIN_M 37 | {R_START_M, R_START_M, R_CW_BEGIN_M, R_START | DIR_CW}, 38 | // R_CCW_BEGIN_M 39 | {R_START_M, R_CCW_BEGIN_M, R_START_M, R_START | DIR_CCW}, 40 | }; 41 | #else 42 | // Use the full-step state table (emits a code at 00 only) 43 | #define R_CW_FINAL 0x1 44 | #define R_CW_BEGIN 0x2 45 | #define R_CW_NEXT 0x3 46 | #define R_CCW_BEGIN 0x4 47 | #define R_CCW_FINAL 0x5 48 | #define R_CCW_NEXT 0x6 49 | 50 | const unsigned char ttable[7][4] = { 51 | // R_START 52 | {R_START, R_CW_BEGIN, R_CCW_BEGIN, R_START}, 53 | // R_CW_FINAL 54 | {R_CW_NEXT, R_START, R_CW_FINAL, R_START | DIR_CW}, 55 | // R_CW_BEGIN 56 | {R_CW_NEXT, R_CW_BEGIN, R_START, R_START}, 57 | // R_CW_NEXT 58 | {R_CW_NEXT, R_CW_BEGIN, R_CW_FINAL, R_START}, 59 | // R_CCW_BEGIN 60 | {R_CCW_NEXT, R_START, R_CCW_BEGIN, R_START}, 61 | // R_CCW_FINAL 62 | {R_CCW_NEXT, R_CCW_FINAL, R_START, R_START | DIR_CCW}, 63 | // R_CCW_NEXT 64 | {R_CCW_NEXT, R_CCW_FINAL, R_CCW_BEGIN, R_START}, 65 | }; 66 | #endif 67 | 68 | /* 69 | * Constructor. Each arg is the pin number for each encoder contact. 70 | */ 71 | Rotary::Rotary(char _pin1, char _pin2) 72 | { 73 | Serial.println("Inside Rotary"); 74 | Serial.println(pin1); 75 | Serial.println(pin2); 76 | // Assign variables. 77 | pin1 = _pin1; 78 | pin2 = _pin2; 79 | Serial.println(pin1); 80 | Serial.println(pin2); 81 | // Set pins to input. 82 | // pinMode(pin1, INPUT); 83 | // pinMode(pin2, INPUT); 84 | Serial.println("After pins"); 85 | #ifdef ENABLE_PULLUPS 86 | Serial.println("Enabling pullups"); 87 | // digitalWrite(pin1, HIGH); 88 | // digitalWrite(pin2, HIGH); 89 | Serial.println("Done"); 90 | #endif 91 | Serial.println("Finished"); 92 | // Initialise state. 93 | state = R_START; 94 | } 95 | 96 | unsigned char Rotary::process() 97 | { 98 | return process(digitalRead(pin2), digitalRead(pin1)); 99 | } 100 | 101 | unsigned char Rotary::process(unsigned char pin1State, unsigned char pin2State) 102 | { 103 | // Grab state of input pins. 104 | unsigned char pinstate = (pin2State << 1) | pin1State; 105 | // Determine new state from the pins and state table. 106 | state = ttable[state & 0xf][pinstate]; 107 | // Return emit bits, ie the generated event. 108 | return state & 0x30; 109 | } 110 | -------------------------------------------------------------------------------- /src/Adafruit_MCP23017.cpp: -------------------------------------------------------------------------------- 1 | /*************************************************** 2 | This is a library for the MCP23017 i2c port expander 3 | 4 | These displays use I2C to communicate, 2 pins are required to 5 | interface 6 | Adafruit invests time and resources providing this open source code, 7 | please support Adafruit and open-source hardware by purchasing 8 | products from Adafruit! 9 | 10 | Written by Limor Fried/Ladyada for Adafruit Industries. 11 | BSD license, all text above must be included in any redistribution 12 | ****************************************************/ 13 | 14 | #ifdef __AVR 15 | #include 16 | #elif defined(ESP8266) 17 | #include 18 | #endif 19 | #include "Adafruit_MCP23017.h" 20 | 21 | #if ARDUINO >= 100 22 | #include "Arduino.h" 23 | #else 24 | #include "WProgram.h" 25 | #endif 26 | 27 | // minihelper to keep Arduino backward compatibility 28 | static inline void wiresend(uint8_t x) 29 | { 30 | #if ARDUINO >= 100 31 | Wire.write((uint8_t)x); 32 | #else 33 | Wire.send(x); 34 | #endif 35 | } 36 | 37 | static inline uint8_t wirerecv(void) 38 | { 39 | #if ARDUINO >= 100 40 | return Wire.read(); 41 | #else 42 | return Wire.receive(); 43 | #endif 44 | } 45 | 46 | /** 47 | * Bit number associated to a give Pin 48 | */ 49 | uint8_t Adafruit_MCP23017::bitForPin(uint8_t pin) 50 | { 51 | return pin % 8; 52 | } 53 | 54 | /** 55 | * Register address, port dependent, for a given PIN 56 | */ 57 | uint8_t Adafruit_MCP23017::regForPin(uint8_t pin, uint8_t portAaddr, uint8_t portBaddr) 58 | { 59 | return (pin < 8) ? portAaddr : portBaddr; 60 | } 61 | 62 | /** 63 | * Reads a given register 64 | */ 65 | uint8_t Adafruit_MCP23017::readRegister(uint8_t addr) 66 | { 67 | // read the current GPINTEN 68 | Wire.beginTransmission(MCP23017_ADDRESS | i2caddr); 69 | wiresend(addr); 70 | Wire.endTransmission(); 71 | Wire.requestFrom(MCP23017_ADDRESS | i2caddr, 1); 72 | return wirerecv(); 73 | } 74 | 75 | /** 76 | * Writes a given register 77 | */ 78 | void Adafruit_MCP23017::writeRegister(uint8_t regAddr, uint8_t regValue) 79 | { 80 | // Write the register 81 | Wire.beginTransmission(MCP23017_ADDRESS | i2caddr); 82 | wiresend(regAddr); 83 | wiresend(regValue); 84 | Wire.endTransmission(); 85 | } 86 | 87 | /** 88 | * Helper to update a single bit of an A/B register. 89 | * - Reads the current register value 90 | * - Writes the new register value 91 | */ 92 | void Adafruit_MCP23017::updateRegisterBit(uint8_t pin, uint8_t pValue, uint8_t portAaddr, uint8_t portBaddr) 93 | { 94 | uint8_t regValue; 95 | uint8_t regAddr = regForPin(pin, portAaddr, portBaddr); 96 | uint8_t bit = bitForPin(pin); 97 | regValue = readRegister(regAddr); 98 | 99 | // set the value for the particular bit 100 | bitWrite(regValue, bit, pValue); 101 | 102 | writeRegister(regAddr, regValue); 103 | } 104 | 105 | //////////////////////////////////////////////////////////////////////////////// 106 | 107 | /** 108 | * Initializes the MCP23017 given its HW selected address, see datasheet for Address selection. 109 | */ 110 | void Adafruit_MCP23017::begin(uint8_t addr) 111 | { 112 | if (addr > 7) 113 | { 114 | addr = 7; 115 | } 116 | i2caddr = addr; 117 | 118 | Wire.begin(); 119 | 120 | // set defaults! 121 | // all inputs on port A and B 122 | writeRegister(MCP23017_IODIRA, 0xff); 123 | writeRegister(MCP23017_IODIRB, 0xff); 124 | } 125 | 126 | /** 127 | * Initializes the default MCP23017, with 000 for the configurable part of the address 128 | */ 129 | void Adafruit_MCP23017::begin(void) 130 | { 131 | begin(0); 132 | } 133 | 134 | /** 135 | * Sets the pin mode to either INPUT or OUTPUT 136 | */ 137 | void Adafruit_MCP23017::pinMode(uint8_t p, uint8_t d) 138 | { 139 | updateRegisterBit(p, (d == INPUT), MCP23017_IODIRA, MCP23017_IODIRB); 140 | } 141 | 142 | /** 143 | * INTCAPA and INTCAPB capture the state of PORT A and PORT B at the moment an interrupt occured. 144 | * Reading this will also clear the interrupt. 145 | * Reads all 16 pins (port A and B) into a single 16 bits variable. 146 | */ 147 | uint16_t Adafruit_MCP23017::readINTCAPAB() 148 | { 149 | uint16_t ba = 0; 150 | uint8_t a; 151 | 152 | // read the current GPIO output latches 153 | Wire.beginTransmission(MCP23017_ADDRESS | i2caddr); 154 | wiresend(MCP23017_INTCAPA); 155 | Wire.endTransmission(); 156 | 157 | Wire.requestFrom(MCP23017_ADDRESS | i2caddr, 1); 158 | a = wirerecv(); 159 | 160 | Wire.beginTransmission(MCP23017_ADDRESS | i2caddr); 161 | wiresend(MCP23017_INTCAPB); 162 | Wire.endTransmission(); 163 | 164 | Wire.requestFrom(MCP23017_ADDRESS | i2caddr, 1); 165 | ba = wirerecv(); 166 | ba <<= 8u; 167 | ba |= a; 168 | 169 | return ba; 170 | } 171 | 172 | /** 173 | * Reads all 16 pins (port A and B) into a single 16 bits variable. 174 | */ 175 | uint16_t Adafruit_MCP23017::readGPIOAB() 176 | { 177 | uint16_t ba = 0; 178 | uint8_t a; 179 | 180 | // read the current GPIO output latches 181 | Wire.beginTransmission(MCP23017_ADDRESS | i2caddr); 182 | wiresend(MCP23017_GPIOA); 183 | Wire.endTransmission(); 184 | 185 | Wire.requestFrom(MCP23017_ADDRESS | i2caddr, 2); 186 | a = wirerecv(); 187 | ba = wirerecv(); 188 | ba <<= 8u; 189 | ba |= a; 190 | 191 | return ba; 192 | } 193 | 194 | /** 195 | * Read a single port, A or B, and return its current 8 bit value. 196 | * Parameter b should be 0 for GPIOA, and 1 for GPIOB. 197 | */ 198 | uint8_t Adafruit_MCP23017::readGPIO(uint8_t b) 199 | { 200 | 201 | // read the current GPIO output latches 202 | Wire.beginTransmission(MCP23017_ADDRESS | i2caddr); 203 | if (b == 0) 204 | wiresend(MCP23017_GPIOA); 205 | else 206 | { 207 | wiresend(MCP23017_GPIOB); 208 | } 209 | Wire.endTransmission(); 210 | 211 | Wire.requestFrom(MCP23017_ADDRESS | i2caddr, 1); 212 | return wirerecv(); 213 | } 214 | 215 | /** 216 | * Writes all the pins in one go. This method is very useful if you are implementing a multiplexed matrix and want to get a decent refresh rate. 217 | */ 218 | void Adafruit_MCP23017::writeGPIOAB(uint16_t ba) 219 | { 220 | Wire.beginTransmission(MCP23017_ADDRESS | i2caddr); 221 | wiresend(MCP23017_GPIOA); 222 | wiresend(ba & 0xFF); 223 | wiresend(ba >> 8); 224 | Wire.endTransmission(); 225 | } 226 | 227 | void Adafruit_MCP23017::digitalWrite(uint8_t pin, uint8_t d) 228 | { 229 | uint8_t gpio; 230 | uint8_t bit = bitForPin(pin); 231 | 232 | // read the current GPIO output latches 233 | uint8_t regAddr = regForPin(pin, MCP23017_OLATA, MCP23017_OLATB); 234 | gpio = readRegister(regAddr); 235 | 236 | // set the pin and direction 237 | bitWrite(gpio, bit, d); 238 | 239 | // write the new GPIO 240 | regAddr = regForPin(pin, MCP23017_GPIOA, MCP23017_GPIOB); 241 | writeRegister(regAddr, gpio); 242 | } 243 | 244 | void Adafruit_MCP23017::pullUp(uint8_t p, uint8_t d) 245 | { 246 | updateRegisterBit(p, d, MCP23017_GPPUA, MCP23017_GPPUB); 247 | } 248 | 249 | uint8_t Adafruit_MCP23017::digitalRead(uint8_t pin) 250 | { 251 | uint8_t bit = bitForPin(pin); 252 | uint8_t regAddr = regForPin(pin, MCP23017_GPIOA, MCP23017_GPIOB); 253 | return (readRegister(regAddr) >> bit) & 0x1; 254 | } 255 | 256 | /** 257 | * Configures the interrupt system. both port A and B are assigned the same configuration. 258 | * Mirroring will OR both INTA and INTB pins. 259 | * Opendrain will set the INT pin to value or open drain. 260 | * polarity will set LOW or HIGH on interrupt. 261 | * Default values after Power On Reset are: (false, false, LOW) 262 | * If you are connecting the INTA/B pin to arduino 2/3, you should configure the interupt handling as FALLING with 263 | * the default configuration. 264 | */ 265 | void Adafruit_MCP23017::setupInterrupts(uint8_t mirroring, uint8_t openDrain, uint8_t polarity) 266 | { 267 | // configure the port A 268 | uint8_t ioconfValue = readRegister(MCP23017_IOCONA); 269 | bitWrite(ioconfValue, 6, mirroring); 270 | bitWrite(ioconfValue, 2, openDrain); 271 | bitWrite(ioconfValue, 1, polarity); 272 | writeRegister(MCP23017_IOCONA, ioconfValue); 273 | 274 | // Configure the port B 275 | ioconfValue = readRegister(MCP23017_IOCONB); 276 | bitWrite(ioconfValue, 6, mirroring); 277 | bitWrite(ioconfValue, 2, openDrain); 278 | bitWrite(ioconfValue, 1, polarity); 279 | writeRegister(MCP23017_IOCONB, ioconfValue); 280 | } 281 | 282 | /** 283 | * Set's up a pin for interrupt. uses arduino MODEs: CHANGE, FALLING, RISING. 284 | * 285 | * Note that the interrupt condition finishes when you read the information about the port / value 286 | * that caused the interrupt or you read the port itself. Check the datasheet can be confusing. 287 | * 288 | */ 289 | void Adafruit_MCP23017::setupInterruptPin(uint8_t pin, uint8_t mode) 290 | { 291 | 292 | // set the pin interrupt control (0 means change, 1 means compare against given value); 293 | updateRegisterBit(pin, (mode != CHANGE), MCP23017_INTCONA, MCP23017_INTCONB); 294 | // if the mode is not CHANGE, we need to set up a default value, different value triggers interrupt 295 | 296 | // In a RISING interrupt the default value is 0, interrupt is triggered when the pin goes to 1. 297 | // In a FALLING interrupt the default value is 1, interrupt is triggered when pin goes to 0. 298 | updateRegisterBit(pin, (mode == FALLING), MCP23017_DEFVALA, MCP23017_DEFVALB); 299 | 300 | // enable the pin for interrupt 301 | updateRegisterBit(pin, HIGH, MCP23017_GPINTENA, MCP23017_GPINTENB); 302 | } 303 | 304 | uint8_t Adafruit_MCP23017::getLastInterruptPin() 305 | { 306 | uint8_t intf; 307 | 308 | // try port A 309 | intf = readRegister(MCP23017_INTFA); 310 | for (int i = 0; i < 8; i++) 311 | if (bitRead(intf, i)) 312 | return i; 313 | 314 | // try port B 315 | intf = readRegister(MCP23017_INTFB); 316 | for (int i = 0; i < 8; i++) 317 | if (bitRead(intf, i)) 318 | return i + 8; 319 | 320 | return MCP23017_INT_ERR; 321 | } 322 | uint8_t Adafruit_MCP23017::getLastInterruptPinValue() 323 | { 324 | uint8_t intPin = getLastInterruptPin(); 325 | if (intPin != MCP23017_INT_ERR) 326 | { 327 | uint8_t intcapreg = regForPin(intPin, MCP23017_INTCAPA, MCP23017_INTCAPB); 328 | uint8_t bit = bitForPin(intPin); 329 | return (readRegister(intcapreg) >> bit) & (0x01); 330 | } 331 | 332 | return MCP23017_INT_ERR; 333 | } 334 | --------------------------------------------------------------------------------