4x3 keypad connected using I2C

Hello, all.
I wanted to connect the keypad to arduino, but I run out of pins.
As the project was already using I2C communication, I decided to modify the connection and use one free GPIO set on MCP23017.
I used the interrupt ability of the MCP (that costed me 1 pin on Arduino) and the RESET function (another 1 pin) - I had a bad experience with power drops and MCP state.
The MCP address is set to 0x20 using 3 wires connected to GND.
Attached you can see the schematics, following is the code.
Should you have any improvements, please, do not hesitate to post it.

#include <Wire.h>

// basic I2C config (addresses)
#define MCP             0x20

// specific configuration for MCP23017 
#define MCPenable       9  // reset pin
#define MCPint          10  // reading interrupt

// indicator led
#define LEDpin          13  // just to check if alive

// runtime variables
unsigned long mRunLast = 0UL;         // Last time MCP read started
unsigned long mInt = 200UL;           // Interval for MCP read (interrupt clear)
unsigned long currentMillis = 0UL;    // main time-counting var


void setup() {
  // setting serial
  Serial.begin(115200);
  Serial.println("Init...");

  Wire.begin();  // setting I2C
  setupMCP();    // setting up MCP and interrupt

  pinMode(LEDpin, OUTPUT);
  digitalWrite(LEDpin, LOW);
  Serial.println("Init done. Starting...");
}

void loop() {
  currentMillis = millis();
  if (!digitalRead(MCPint) ) {
    digitalWrite(LEDpin, HIGH);
    if (currentMillis - mRunLast > mInt) getMCP();
  } 
  else {
    digitalWrite(LEDpin, LOW);
  }
}

void setupMCP() {
  // first we reset MCP
  pinMode(MCPint, INPUT);
  pinMode(MCPenable, OUTPUT);
  digitalWrite(MCPenable, LOW);
  delay(10);
  digitalWrite(MCPenable, HIGH);
  delay(10);
  I2Csend(MCP, 0x00, B11100000); // set of bank A to INPUTS for first 3 pins, outputs for everything else
  I2Csend(MCP, 0x02, B11111111); // invert inputs of bank A
  I2Csend(MCP, 0x04, B11100000); // enable interrupt for input pins of bank A
  I2Csend(MCP, 0x06, B00000000); // set the default values for INT comparision
  I2Csend(MCP, 0x08, B11100000); // compare to default instead of previous value
  I2Csend(MCP, 0x0C, B11100000); // set pullup ON for bank A for inputs
  I2Csend(MCP, 0x12, B00000000); // set output pins to LOW
}

void getMCP() {
  I2CsendSingle(MCP, 0x10); // Interrupt capture GPIO A
  Wire.requestFrom(MCP, 1); 
  byte inputs = Wire.read();

  I2CsendSingle(MCP, 0x12); // to avoid error, re-read current state of GPIO
  Wire.requestFrom(MCP, 1); 
  inputs = Wire.read();

  if (inputs != 0 && currentMillis - mRunLast >= mInt) {
    byte column = inputs >> 5;
    if (column == 4) column = 3;

    I2Csend(MCP, 0x04, B00000000); // temporary disable interrupt for input pins of bank A

    byte row = 0;
    byte error = 0;
    for (byte i = 0; i < 4; i++) {
      I2Csend(MCP, 0x12, 1 << (i + 1)); // disabling one output line at a time
      I2CsendSingle(MCP, 0x12);
      Wire.requestFrom(MCP, 1); 
      byte portA = Wire.read();
      if ((portA & B11100000) == 0) { 
        row = i;
        error++;
      }
    }
    if (error == 1) {
      byte output = 255;
      byte input = row * 10 + column;
      if (input < 10) output = input;
      if (input > 10 && input < 20) output = input - 10 + 3;
      if (input > 20 && input < 30) output = input - 20 + 6;
      if (input > 30) output = input - 30 + 9;
      if (output == 11) output = 0;

      Serial.print("Key: ");
      Serial.println(output);
    } else {
      Serial.println("Error captured :(");
    }
    I2Csend(MCP, 0x12, B00000000); // disabling one output line at a time
    I2Csend(MCP, 0x04, B11100000); // enable interrupt for input pins of bank A

    // now we have to find out, which row is selected
  }
  mRunLast = millis();
}

void I2Csend(byte I2Caddress, byte memAddr, byte memValue) {
  Wire.beginTransmission(I2Caddress);
  Wire.write(memAddr); // IODIRA register
  Wire.write(memValue); // set all of bank A to INPUTS
  Wire.endTransmission();
}

void I2CsendSingle(byte I2Caddress, byte memValue) {
  Wire.beginTransmission(I2Caddress);
  Wire.write(memValue); // set all of bank A to INPUTS
  Wire.endTransmission();
}

You need 4k7 pull up resistors on A4 & A5
The internal pull ups are not good enough.

Really? I am running multiple such configurations using MCP23017 and MCP23008 and I never used a pullup - why should I? Can you, kindly, provide some details?
And I forgot to mention 1 thing - this is the first time ever I am using Fritzing, so, please, have some patience :wink:

http://forum.arduino.cc/index.php/topic,188840.0.html

this is the first time ever I am using Fritzing,

Make it your last it is a true abortion and conveys no information to others.

Really? ...... I never used a pullup - why should I

Because you need them.

Thanks a lot!

I guess I am just happy my garden control device works - there is no pull-ups and the list of i2c devices is relatively big (however the physical bus length is quite short):

  • LCD
  • RTC DS1307
  • MCP23017
  • Light sensor
  • 4kb EEPROM

It works for few weeks outside now, however one never knows - I'll add the pull-ups just to be on the safe side.

Grumpy_Mike:
Make it your last it is a true abortion and conveys no information to others.

Not really sure what exactly do you mean by this, but I am not native speaker, so maybe I just misunderstood.

Grumpy_Mike:
DssCircuits.com is for sale | HugeDomains

Thanks, the graphs are very nice - I'll solder 1k5 instead of 4k7 to have very nice clock signal :wink:

Not really sure what exactly do you mean by this,

Fritzing is a very bad application. Do not use it. It does not produce anything useful for communication with others. It produces a physical layout diagram. It is only used by idiots.

I seem to get your avatar name now :slight_smile:
I do not want to disagree, just wondering why is the Fritzing output used on all the examples on Arduino.cc within learning section then?

I do not want to disagree,

Good :slight_smile:

just wondering why is the Fritzing output used on all the examples on Arduino.cc within learning section then?

Because they mistakenly believe that some one who just plugs wires into a board to a set pattern is learning something. This illusion is reinforced when some people manage to reproduce what they have done. The achievement factor might be good but learning has been absolutely zero.

In truth they have learned nothing except how to put wires in holes.

So if you present a circuit that is anything but trivial in this format an other person would have to convert this into a schematic before any useful analysis of the validity of the circuit could be made. So there is no need for a physical layout diagram. Learn to read and draw a schematic. Then you can learn something and also communicate with others.

Do not fall into the trap of thinking that everything on the learning section is perfect there are errors on there that have been pointed out years and years ago but they never get fixed.

Thanks for pointing this out.
This is funny - I draw the schematics in Fritzing first (kinda lame, but works) and than switched to the BB layout and re-connected everything again. It was quite big effort as the tool did not update the wiring in accordance with the schematics, nor did it checked for errors and omissions, so I had to check and re-check it multiple times.
For my purposes I still use EagleCAD, however as I wanted to share this so someone can improve my code, I felt like keeping the learning-section approach is expected.

however as I wanted to share this so someone can improve my code, I felt like keeping the learning-section approach is expected.

You would have been much better off posting the EagleCAD schematic in PDF format so everyone could read them.
People who only know Fritzing are not good enough to tell you anything useful about code or hardware.