Follow the link below for an example and code that uses a MCP23017 port expander to add 16 digital I/O ports to the Arduino via the I2C bus.
http://www.g7smy.co.uk/2015/01/rotary-encoders-in-colour-on-the-i2c-bus//* Sparkfun RGB pushbutton rotary encoder COM-10982 Circuit: 'component - rotary encoder - SF COM-10982.fzz' Modified by: Mark Kiehl Original source: "Rotary_Encoder_Demo" Mike Grusin, SparkFun Electronics https://github.com/sparkfun/LilyPad_MP3_Player/blob/master/Arduino/LilyPad%20MP3%20Player/Examples/Rotary_Encoder_Demo/Rotary_Encoder_Demo.ino ------------------------------------------------------------------------------ Rotary change: rotary encoder output 0 to 255; var rotary_counter_pos Short button press: Function A on/off (red LED on/off); var FnAstate (LOW/HIGH) Long button press: Function B on/off; var FnBstate (true/false) Very long button press (>2sec): Save rotary encoder value to EEPROM (restored on next reboot). ------------------------------------------------------------------------------ Rotary encoder pin A to digital pin 3* (interrupt 1) Rotary encoder pin B to analog pin 3 Rotary encoder pin C to ground Rotary encoder pin 1 (red cathode) to digital pin 5 Rotary encoder pin 2 (green cathode) to digital pin 9 Rotary encoder pin 3 (button) to digital pin 4 Rotary encoder pin 4 (blue cathode) to digital pin 6 Rotary encoder pin 5 (common anode) to VCC (3.3V or 5V) Rotary encoder pin A & C 0.1uF cap Rotary encoder pin B & C 0.1uF cap Weak pullup resistors are turned on in the setup for encoder pins A & B * pin uses interrupt and cannot be changed. Resources DIO 2 DIO ~3 rotary encoder pin A * DiO 4 rotary encoder button pin 3 DIO ~5 rotary encoder red cathode pin 1 DIO ~6 rotary encoder blue cathode pin 4 DIO 7 DIO ~9 rotary encoder green cathode pin 2 DIO ~10 DIO ~11 DIO 12 DIO 13 AIO 0 AIO 1 AIO 2 AIO 3 rotary encoder pin B AIO 4 AIO 5 ------------------------------------------------------------------------------ SparkFun P/N COM-10982 rotary encoder with RGB SparkFun P/N BOB-11722 breakout board ------------------------------------------------------------------------------ Because this is a COMMON ANODE DEVICE, the pushbutton requires an external 1K-10K pullDOWN resistor in order to be detected properly. Software debounce is implemented, but performance may be further improved by installing 0.1uF capacitors between A and ground , and B and ground. ------------------------------------------------------------------------------ */ // HOW IT WORKS // The I/O pins used by the rotary encoder hardware are set up to // automatically call interrupt functions (rotaryIRQ and buttonIRQ) // each time the rotary encoder changes states. // The rotaryIRQ function transparently maintains a counter that // increments or decrements by one for each detent ("click") of // the rotary encoder knob. This function also sets a flag // (rotary_change) to true whenever the counter changes. You can // check this flag in your main loop() code and perform an action // when the knob is turned. // The buttonIRQ function does the same thing for the pushbutton // built into the rotary encoder knob. It will set flags for // button_pressed and button_released that you can monitor in your // main loop() code. There is also a variable for button_downtime // which records how long the button was held down. // There is also code in the main loop() that keeps track // of whether the button is currently being held down and for // how long. This is useful for "hold button down for five seconds // to power off"-type situations, which cannot be handled by // interrupts alone because no interrupts will be called until // the button is actually released. // Uses the PinChangeInt library by Lex Talionis, // download from http://code.google.com/p/arduino-pinchangeint/ // Load the PinChangeInt (pin change interrupt) library #include#define ROT_A 3 // rotary encoder pin A #define ROT_B A3 // rotary encoder pin B #define ROT_SW 4 // rotary encoder pin puhbutton (pin 3) #define ROT_LEDR 5 // rotary encoder (RGB) red LED (pin 1) #define ROT_LEDB 6 // rotary encoder (RGB) blue LED (pin 4) #define ROT_LEDG 9 // rotary encoder (RGB green LED (pin 2) // RGB LED colors (for common anode LED, 0 is on, 1 is off) #define OFF B111 #define RED B110 #define GREEN B101 #define YELLOW B100 #define BLUE B011 #define PURPLE B010 #define CYAN B001 #define WHITE B000 #define COMMON_ANODE // Global variables that can be changed in interrupt routines //volatile int rotary_counter = 0; // current "position" of rotary encoder (increments CW) volatile int rotary_counter_pos = 125; volatile boolean rotary_change = false; // will turn true if rotary_counter has changed volatile boolean button_pressed = false; // will turn true if the button has been pushed volatile boolean button_released = false; // will turn true if the button has been released (sets button_downtime) volatile unsigned long button_downtime = 0L; // ms the button was pushed before release //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// // Function A and Function B // Set to whatever you want to do when the rotary encoder buttion // is pressed for a short or medium duration. byte FnAstate = LOW; boolean FnBstate = false; //////////////////////////////////////////////////////////////////////// // include the EEPROM library #include const byte addrEEPROM = 0; //////////////////////////////////////////////////////////////////////// #define DEBUG true void setup(){ // Set up all the I/O pins. Unused pins are commented out. pinMode(ROT_B, INPUT); digitalWrite(ROT_B, HIGH); // turn on weak pullup // pinMode(ROT_A, INPUT); digitalWrite(ROT_A, HIGH); // turn on weak pullup // pinMode(ROT_SW, INPUT); // The rotary switch is common anode with external 1k-10k pulldown, do not turn on pullup pinMode(ROT_LEDB, OUTPUT); pinMode(ROT_LEDG, OUTPUT); pinMode(ROT_LEDR, OUTPUT); // We use the standard external interrupt pin for the rotary, // but we'll use the pin change interrupt library for the button. attachInterrupt(1, rotaryIRQ, CHANGE); PCintPort::attachInterrupt(ROT_SW, &buttonIRQ, CHANGE); #if DEBUG // serial must be 11500 or faster! Serial.begin(115200); // Use serial for debugging while (!Serial) { ; // wait for serial port to connect. Needed for Leonardo only delay(1); } Serial.println("setup complete"); #endif // Get the last value of rotary_counter_pos saved to EEPROM and assign it to rotary_counter_pos. rotary_counter_pos = EEPROM.read(addrEEPROM); #if DEBUG Serial.print("rotary_counter_pos from EEPROm = "); Serial.println(rotary_counter_pos); Serial.println(" "); #endif blinkRotaryEncoderLED(RED); blinkRotaryEncoderLED(BLUE); blinkRotaryEncoderLED(GREEN); blinkRotaryEncoderLED(WHITE); blinkRotaryEncoderLED(YELLOW); blinkRotaryEncoderLED(PURPLE); } void buttonIRQ() { // Process rotary encoder button presses and releases, including // debouncing (extra "presses" from noisy switch contacts). // If button is pressed, the button_pressed flag is set to true. // (Manually set this to false after handling the change.) // If button is released, the button_released flag is set to true, // and button_downtime will contain the duration of the button // press in ms. (Set this to false after handling the change.) static boolean button_state = false; static unsigned long start, end; if ((PCintPort::pinState == HIGH) && (button_state == false)) // Button was up, but is currently being pressed down { // Discard button presses too close together (debounce) start = millis(); if (start > (end + 10)) // 10ms debounce timer { button_state = true; button_pressed = true; } } else if ((PCintPort::pinState == LOW) && (button_state == true)) // Button was down, but has just been released { // Discard button releases too close together (debounce) end = millis(); if (end > (start + 10)) // 10ms debounce timer { button_state = false; button_released = true; button_downtime = end - start; } } } void rotaryIRQ() { // Process input from the rotary encoder. // The rotary "position" is held in rotary_counter, increasing for CW rotation (changes by one per detent). // If the position changes, rotary_change will be set true. (You may manually set this to false after handling the change). // This function will automatically run when rotary encoder input A transitions in either direction (low to high or high to low) // By saving the state of the A and B pins through two interrupts, we'll determine the direction of rotation // int rotary_counter will be updated with the new value, and boolean rotary_change will be true if there was a value change // Based on concepts from Oleg at circuits@home (http://www.circuitsathome.com/mcu/rotary-encoder-interrupt-service-routine-for-avr-micros) // Unlike Oleg's original code, this code uses only one interrupt and has only two transition states; // it has less resolution but needs only one interrupt, is very smooth, and handles switchbounce well. static unsigned char rotary_state = 0; // current and previous encoder states rotary_state <<= 2; // remember previous state rotary_state |= (digitalRead(ROT_A) | (digitalRead(ROT_B) << 1)); // mask in current state rotary_state &= 0x0F; // zero upper nybble if (rotary_state == 0x09) // from 10 to 01, increment counter. Also try 0x06 if unreliable { rotary_counter_pos++; rotary_change = true; } else if (rotary_state == 0x03) // from 00 to 11, decrement counter. Also try 0x0C if unreliable { rotary_counter_pos--; rotary_change = true; } } void loop() { // "Static" variables are initalized once the first time // that loop runs, but they keep their values through // successive loops. static unsigned char x = -1; static boolean button_down = false; static unsigned long int button_down_start, button_down_time; // The rotary IRQ sets the flag rotary_counter to true // if the knob position has changed. We can use this flag // to do something in the main loop() each time there's // a change. We'll clear this flag when we're done, so // that we'll only do this if() once for each change. if (rotary_change) { // when knob rotated: rotary_change = false; // Clear flag #if DEBUG Serial.print("rotary_counter_pos: "); Serial.println(rotary_counter_pos,DEC); Serial.println(" "); #endif // Reset if value < 0 or > 255 if (rotary_counter_pos < 0) { rotary_counter_pos = 0; } else if (rotary_counter_pos > 255) { rotary_counter_pos = 255; } if (FnAstate == HIGH) { setColorRotaryEncoderLED(rotary_counter_pos, 0, 0); // red } } // rotary_change // The button IRQ also sets flags to true, one for // button_pressed, one for button_released. Like the rotary // flag, we'll clear these when we're done handling them. if (button_pressed) { button_pressed = false; // Clear flag // We'll set another flag saying the button is now down // this is so we can keep track of how long the button // is being held down. (We can't do this in interrupts, // because the button state is not changing). button_down = true; button_down_start = millis(); } // main button press section if (button_released) { button_released = false; // Clear flag // Clear our button-being-held-down flag button_down = false; if (button_downtime <= 250) { // Short button press // Alternate FnAstate HIGH/LOW & turn rotary encoder LED on/off // Set FnBstate false #if DEBUG Serial.println("short button press"); #endif if (FnAstate == HIGH) { FnAstate = LOW; setRotaryEncoderLED(OFF); } else { FnAstate = HIGH; setColorRotaryEncoderLED(rotary_counter_pos, 0, 0); // red } #if DEBUG Serial.print("rotary_counter_pos: "); Serial.println(rotary_counter_pos,DEC); Serial.print("FnAstate: "); Serial.println(FnAstate,DEC); Serial.println(" "); #endif } else if (button_downtime > 250 && button_downtime < 2000) { // Long button press #if DEBUG Serial.println("long button press"); #endif // Set FnBstate true/false if (FnBstate == true) { FnBstate = false; } else { FnBstate = true; } #if DEBUG Serial.print("FnBstate: "); Serial.println(FnBstate,DEC); Serial.println(" "); #endif } else { // Very long button press // Save rotary encoder value to EEPROM EEPROM.write(addrEEPROM, rotary_counter_pos); #if DEBUG Serial.println("long button press"); Serial.print("Saved to EEPROM rotary_counter_pos = "); Serial.println(rotary_counter_pos,DEC); Serial.println(" "); #endif byte myEEPROMval; myEEPROMval = EEPROM.read(addrEEPROM); blinkRotaryEncoderLED(BLUE); if (myEEPROMval == rotary_counter_pos) { } else { blinkRotaryEncoderLED(RED); } // reset the state of the rotary encoder LED if (FnAstate == HIGH) { setColorRotaryEncoderLED(0, 0, rotary_counter_pos); // blue } else { setRotaryEncoderLED(OFF); } } } if (FnBstate == true) { //do something blinkRotaryEncoderLED(WHITE); } //FnBstate } // loop //////////////////////////////////////////////////////////////////////// // Rotary encoder functions void setColorRotaryEncoderLED(int red, int green, int blue){ #ifdef COMMON_ANODE red = 255 - red; green = 255 - green; blue = 255 - blue; #endif analogWrite(ROT_LEDR, red); analogWrite(ROT_LEDG, green); analogWrite(ROT_LEDB, blue); } void setRotaryEncoderLED(unsigned char color) // Set RGB LED to one of eight colors (see #defines above) { // color values // red // green // blue // yellow // white // purple // off = -1 digitalWrite(ROT_LEDR,color & B001); digitalWrite(ROT_LEDG,color & B010); digitalWrite(ROT_LEDB,color & B100); } void blinkRotaryEncoderLED(unsigned char color){ // consumes 300 ms. // color values // red // green // blue // yellow // white // purple // off = -1 for(int i = 5; i > 0; i--){ digitalWrite(ROT_LEDR,color & B001); digitalWrite(ROT_LEDG,color & B010); digitalWrite(ROT_LEDB,color & B100); delay(30); digitalWrite(ROT_LEDR,OFF & B001); digitalWrite(ROT_LEDG,OFF & B010); digitalWrite(ROT_LEDB,OFF & B100); delay(30); } }
Do you need help developing or customizing a IoT product for your needs? Send me an email requesting a free one hour phone / web share consultation.
The information presented on this website is for the author's use only. Use of this information by anyone other than the author is offered as guidelines and non-professional advice only. No liability is assumed by the author or this web site.