Register forum user name Search FAQ

Gammon Forum

Notice: Any messages purporting to come from this site telling you that your password has expired, or that you need to verify your details, confirm your email, resolve issues, making threats, or asking for money, are spam. We do not email users with any such messages. If you have lost your password you can obtain a new one by using the password reset link.
 Entire forum ➜ Electronics ➜ Microprocessors ➜ Buttons, I2C, interrupts and port-expanders

Buttons, I2C, interrupts and port-expanders

Postings by administrators only.

Refresh page


Pages: 1 2  3  

Posted by Nick Gammon   Australia  (23,121 posts)  Bio   Forum Administrator
Date Sat 19 Feb 2011 05:40 AM (UTC)

Amended on Mon 09 Mar 2015 08:34 PM (UTC) by Nick Gammon

Message
An interesting problem in getting microprocessors to work in a nice, responsive way is to get them to handle user input properly. For example, in the Adventure game I am working on, I have a row of buttons for the player to press. Unfortunately, sometimes the key-press isn't registered, so it was time to get into interrupts, and port expanders.

There are a few problems with handling button (key) presses:


  • You may miss the press altogether, particularly if you use "polling" to check for them. Polling is when you periodically see if the key is down by checking if the pin is high, or low, or whatever indicates a key-press.

  • Keys bounce, because they are implemented with springs and bits of metal. If you are not careful then the bouncing will be interpreted as multiple key-presses.

  • If you have quite a few buttons (say, 16), then you use up most or all of your input pins on your processor, particularly if you use one pin per button. Even if you use a keyboard "matrix" (where you have 4 rows and 4 columns) then you still need 8 pins (4 for the rows and 4 for the columns). Whilst this is better than 16 pins, it is still a lot.

  • Keyboard matrices are fiddly to wire up, and fiddly to test (you have to power each column and then test each row). Also they suffer from "phantom" key-presses, unless you wire diodes in as well. And of course, they don't lend themselves to interrupts because you need to scan the rows and columns when they are pressed, not later on.

  • It is fiddly having wires going everywhere to handle all the buttons.


The solution to most of these problems is to use an I/O port expander, like the MCP23017, and interrupts. The port expander gives you 16 inputs, but only uses 2 pins on the Arduino (SDA and SCL), plus ground and power. Using interrupts lets you react to button presses even if you are doing something else when it is pressed. To do this we make use of the MCP23017's ability to generate an interrupt signal, and use one more wire to connect to one of the pins of the Arduino which can generate processor interrupts.

Wiring


My wiring for this project is as follows:



This only has a single switch shown, but I actually wired 16 switches from pins 1 to 8, and 21 to 28, all between the pin and ground.

These are the pinouts for the device:



Configure the MCP23017


There are five major registers of interest on the MCP23017, so let's look at how they are set up to make this work:

First we configure the expander to disable sequential mode, so that when we address a register it just toggles between the "A" and "B" registers. We also enable interrupt mirroring, so we only need to test one interrupt line rather than two.


  // expander configuration register
  expanderWriteBoth (IOCON, 0b01100000); // mirror interrupts, use sequential mode


To save mucking around with resistors, and to be a bit safer if we misconfigure the MCP23017, let's enable pull-up resistors for the switch port(s):


  expanderWriteBoth (GPPUA, 0xFF);   // pull-up resistor for switches


Register GPPUA is the GPIO pull-up resistor register. By writing a 1 (to each bit) of that it turns on a pull-up. Thus the port will normally be a 1, until the switch pulls it to 0 when you press it (because that grounds it).

The expanderWriteBoth function makes use of the ability of the MCP23017 to toggle registers, so that by writing twice, it writes to port A, and then port B.

Next, we'll reverse the polarity of that port because it will be 1 when not pressed, and 0 when pressed. To make things look more logical, we reverse it, so we read a 1 when the switch is closed.


 expanderWriteBoth (IOPOLA, 0xFF);  // invert polarity


Finally we enable interrupts for those pins:


  expanderWriteBoth (GPINTENA, 0xFF); // enable interrupts


Now when any pin changes value the MCP23017 will drive the interrupt pin for port B (pin 19) low.

To be on the safe side we will immediately read from the "interrupt capture" register. This clears any interrupt that might be currently present.



  // no interrupt yet
  keyPressed = false;

  // read from interrupt capture port to clear them
  expanderRead (INTCAPA);
  expanderRead (INTCAPB);


Set up Interrupt Service Routine


Now we are ready to configure the Arduino to react to interrupts on pin D2:


 // pin 19 of MCP23017 is plugged into D2 of the Arduino which is interrupt 0
  attachInterrupt(0, keypress, FALLING);


The interrupt service routine "keypress", which should be very brief, does this:


void keypress ()
{
  digitalWrite (ISR_INDICATOR, HIGH);  
  keyPressed = true;
}


This merely sets a flag which the main loop can test when it is ready. That tells us that we got an interrupt. The digitalWrite is for debugging, so I can see with the logic analyser when we hit this routine.

Test interrupts


Now let's hook up some debugging connections to the logic analyser and hit the button ...



I put a trigger on the switch being pressed, and immediately you notice the rather impressive switch bounce. That must be about 20 bounces (taking around 4 ms)! That's why you need to debounce. Notice that seemingly at the same instant, the "interrupt" signal is driven low, and the "ISR" (debug signal) is driven high. The interrupt doesn't bounce because it stays active until re-armed by reading the data from the "interrupt capture" register.

Now, zooming in on the exact moment, we can see how long the interrupt took to fire:



You can clearly see that from the moment the switch was pressed, to when the MCP23017 dropped the interrupt pin to low, was only 0.125 microseconds. That's only 125 nanoseconds! Very fast.

Then we see that only 7 microseconds after the keypress the Arduino interrupt routine also fired (we can tell because of the digitalWrite). So there was a very fast response there, even though it was busy doing something else.



Zooming out to get the bigger picture, we can see that the main loop didn't notice the keypress until an entire second had elapsed. No doubt because I had it beavering away doing this:


  // some important calculations here ...
  for (i = 0; i < 0x7FFFF; i++)
    {}


That is a loop of 524288 iterations, supposed to simulate the processor being busy (eg. updating your LCD screen).

You can also see another batch of switch bounces, circled. That switch certainly had a field day of it. That's 100 ms (1/10 of a second) after it was pressed! In fact, it looks like the second lot of bounces was when I released the switch. That would be right, because when it was released it would have jittered back and forwards a bit until settling down.



Once the main loop noticed the keypress it calls handleKeypress to find out what it was.



This does the following:


  • Drop the "ISR" pin (point "A") and sets the keyPressed flag back to false, so we can detect a future keypress.

  • For each port, reads from the MCP23017's "interrupt flag" register. This is so we know if this register was responsible for the interrupt or not. If we don't do this we may get an old value from a previous interrupt. These reads are not shown in the logic analyzer capture as I added them later on.

  • Then, for each port, if required, read from the MCP23017's "interrupt capture" register. The purpose of this register is to remember the state of the pins the moment the interrupt occurred, even if they have changed since then. This is just as well, because you can see that by the time we noticed the keypress the key had been released again. Notice that we read 0x01 which shows that the switch on pin 1 was closed.

    
      // Read port values, as required. Note that this re-arms the interrupts.
      if (expanderRead (INFTFA))
        {
        keyValue &= 0x00FF;
        keyValue |= expanderRead (INTCAPA) << 8;    // read value at time of interrupt
        }
      if (expanderRead (INFTFB))
        {
        keyValue &= 0xFF00;
        keyValue |= expanderRead (INTCAPB);        // port B is in low-order byte
        }
    


    [EDIT] Code changed from earlier version to only alter keyValue if you get an interrupt for that "side" (the A side or the B side).

  • The moment that the "interrupt capture" register is read the MCP23017 cancels the interrupt signal (raises it back to 1) at point "B". This effectively re-arms the interrupts for another time.

  • The code then turns on the on-board LED (point "C") thus giving a visual confirmation we noticed the key-press.



Example code


The full code in the test program was:


// Author: Nick Gammon
// Date: 19 February 2011

// Demonstration of an interrupt service routine connected to the MCP23017

#include <Wire.h>

// MCP23017 registers (everything except direction defaults to 0)

#define IODIRA   0x00   // IO direction  (0 = output, 1 = input (Default))
#define IODIRB   0x01
#define IOPOLA   0x02   // IO polarity   (0 = normal, 1 = inverse)
#define IOPOLB   0x03
#define GPINTENA 0x04   // Interrupt on change (0 = disable, 1 = enable)
#define GPINTENB 0x05
#define DEFVALA  0x06   // Default comparison for interrupt on change (interrupts on opposite)
#define DEFVALB  0x07
#define INTCONA  0x08   // Interrupt control (0 = interrupt on change from previous, 1 = interrupt on change from DEFVAL)
#define INTCONB  0x09
#define IOCON    0x0A   // IO Configuration: bank/mirror/seqop/disslw/haen/odr/intpol/notimp
//#define IOCON 0x0B  // same as 0x0A
#define GPPUA    0x0C   // Pull-up resistor (0 = disabled, 1 = enabled)
#define GPPUB    0x0D
#define INFTFA   0x0E   // Interrupt flag (read only) : (0 = no interrupt, 1 = pin caused interrupt)
#define INFTFB   0x0F
#define INTCAPA  0x10   // Interrupt capture (read only) : value of GPIO at time of last interrupt
#define INTCAPB  0x11
#define GPIOA    0x12   // Port value. Write to change, read to obtain value
#define GPIOB    0x13
#define OLLATA   0x14   // Output latch. Write to latch output.
#define OLLATB   0x15


#define port 0x20  // MCP23017 is on I2C port 0x20

#define ISR_INDICATOR 12  // pin 12
#define ONBOARD_LED 13    // pin 13

volatile bool keyPressed;

// set register "reg" on expander to "data"
// for example, IO direction
void expanderWriteBoth (const byte reg, const byte data ) 
{
  Wire.beginTransmission (port);
  Wire.write (reg);
  Wire.write (data);  // port A
  Wire.write (data);  // port B
  Wire.endTransmission ();
} // end of expanderWrite

// read a byte from the expander
unsigned int expanderRead (const byte reg) 
{
  Wire.beginTransmission (port);
  Wire.write (reg);
  Wire.endTransmission ();
  Wire.requestFrom (port, 1);
  return Wire.read();
} // end of expanderRead

// interrupt service routine, called when pin D2 goes from 1 to 0
void keypress ()
{
  digitalWrite (ISR_INDICATOR, HIGH);  // debugging
  keyPressed = true;   // set flag so main loop knows
}  // end of keypress

void setup ()
{
  pinMode (ISR_INDICATOR, OUTPUT);  // for testing (ISR indicator)
  pinMode (ONBOARD_LED, OUTPUT);  // for onboard LED

  Wire.begin ();  
  Serial.begin (115200); 
  Serial.println ("Starting ..."); 

  // expander configuration register
  expanderWriteBoth (IOCON, 0b01100000); // mirror interrupts, disable sequential mode
 
  // enable pull-up on switches
  expanderWriteBoth (GPPUA, 0xFF);   // pull-up resistor for switch - both ports

  // invert polarity
  expanderWriteBoth (IOPOLA, 0xFF);  // invert polarity of signal - both ports
  
  // enable all interrupts
  expanderWriteBoth (GPINTENA, 0xFF); // enable interrupts - both ports
  
  // no interrupt yet
  keyPressed = false;

  // read from interrupt capture ports to clear them
  expanderRead (INTCAPA);
  expanderRead (INTCAPB);
  
  // pin 19 of MCP23017 is plugged into D2 of the Arduino which is interrupt 0
  attachInterrupt(0, keypress, FALLING);
  
}  // end of setup

// time we turned LED on
unsigned long time = 0;
unsigned int keyValue = 0;

// called from main loop when we know we had an interrupt
void handleKeypress ()
{
  
  delay (100);  // de-bounce before we re-enable interrupts
  
  keyPressed = false;  // ready for next time through the interrupt service routine
  digitalWrite (ISR_INDICATOR, LOW);  // debugging
  
  // Read port values, as required. Note that this re-arms the interrupts.
  if (expanderRead (INFTFA))
    {
    keyValue &= 0x00FF;
    keyValue |= expanderRead (INTCAPA) << 8;    // read value at time of interrupt
    }
  if (expanderRead (INFTFB))
    {
    keyValue &= 0xFF00;
    keyValue |= expanderRead (INTCAPB);        // port B is in low-order byte
    }
  
  Serial.println ("Button states");
  Serial.println ("0                   1");
  Serial.println ("0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5");
  
  // display which buttons were down at the time of the interrupt
  for (byte button = 0; button < 16; button++)
    {
    // this key down?
    if (keyValue & (1 << button))
      Serial.print ("1 ");
    else
      Serial.print (". ");
    
    } // end of for each button

  Serial.println ();
  
  // if a switch is now pressed, turn LED on  (key down event)
  if (keyValue)
    {
    time = millis ();  // remember when
    digitalWrite (ONBOARD_LED, HIGH);  // on-board LED
    }  // end if
  
}  // end of handleKeypress

void loop ()
{
  // was there an interrupt?
  if (keyPressed)
    handleKeypress ();

  // turn LED off after 500 ms 
 if (millis () > (time + 500) && time != 0)
   {
    digitalWrite (ONBOARD_LED, LOW);
    time = 0;
   }  // end if time up
 
}  // end of loop


Conclusion


The example presented shows how you can notice a key-press on up to 16 buttons, even if your code was doing some very CPU-intensive work.

All this is done with a minimal number of wires connected between where the buttons are and the Arduino (ground, +5V, SDA, SCL and the interrupt line).

You could handle more than 16 switches by using more than one MCP23017, see next post.

More information


More information about I2C is here:

http://www.gammon.com.au/forum/?id=10896

My article about hooking up a graphics LCD screen using I2C is here:

http://www.gammon.com.au/forum/?id=10940

Information about keyboard matrices, diodes and ghosting:

http://www.dribin.org/dave/keyboard/one_html/

Microchip's MCP23017 device:

http://www.microchip.com/wwwproducts/Devices.aspx?dDocName=en023499




[EDIT] Amended on 20th February 2011 to test all 16 switches, and have a more sophisticated key-press detection routine. This shows in the serial output which button has been pressed.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Nick Gammon   Australia  (23,121 posts)  Bio   Forum Administrator
Date Reply #1 on Sun 20 Feb 2011 05:05 AM (UTC)

Amended on Sun 20 Feb 2011 08:03 PM (UTC) by Nick Gammon

Message
Connecting multiple I/O expanders


After a query on the Arduino forum, I dug a bit deeper to see if I could connect more than one MCP23017 chip, to handle over 16 inputs. It turns out this is fairly simple. Changes were:


  • Configure the MCP23017 interrupt ports to be "open drain" which means they get pulled low on an interrupt, but are high-impedance when not on an interrupt. Effectively this means the interrupt ports can be shared.

  • Wired the MCP23017 interrupt pins together (that is connected pin 19 to pin 19).

  • Configured port D2 of the Arduino to have an internal pull-up resistor. This makes sure that D2 is high if neither MCP23017 is showing an interrupt.

  • Connected a second MCP23017, wiring up everything the same as for the first except that address A0 (pin 15) was wired to +5V rather than ground. That made it address 0x21 rather than 0x20, so they had different addresses. Plus connected pin 19 of the second one to pin 19 of the first one, as mentioned above.

  • Modified the code to accept a chip address in the functions expanderRead and expanderWriteBoth.

  • Modified the code to send set-up commands to both chips, and check for interrupts from both chips when the interrupt fired.


This all seems to work reliably, so in principle you should be able to have 64 inputs, by using four MCP23017 chips (for example, the Centipede shield).

Modified code to work with two MCP23017 chips is:


// Author: Nick Gammon
// Date: 20 February 2011

// Demonstration of an interrupt service routine connected to the MCP23017

#include <Wire.h>

// MCP23017 registers (everything except direction defaults to 0)

#define IODIRA   0x00   // IO direction  (0 = output, 1 = input (Default))
#define IODIRB   0x01
#define IOPOLA   0x02   // IO polarity   (0 = normal, 1 = inverse)
#define IOPOLB   0x03
#define GPINTENA 0x04   // Interrupt on change (0 = disable, 1 = enable)
#define GPINTENB 0x05
#define DEFVALA  0x06   // Default comparison for interrupt on change (interrupts on opposite)
#define DEFVALB  0x07
#define INTCONA  0x08   // Interrupt control (0 = interrupt on change from previous, 1 = interrupt on change from DEFVAL)
#define INTCONB  0x09
#define IOCON    0x0A   // IO Configuration: bank/mirror/seqop/disslw/haen/odr/intpol/notimp
//#define IOCON 0x0B  // same as 0x0A
#define GPPUA    0x0C   // Pull-up resistor (0 = disabled, 1 = enabled)
#define GPPUB    0x0D
#define INFTFA   0x0E   // Interrupt flag (read only) : (0 = no interrupt, 1 = pin caused interrupt)
#define INFTFB   0x0F
#define INTCAPA  0x10   // Interrupt capture (read only) : value of GPIO at time of last interrupt
#define INTCAPB  0x11
#define GPIOA    0x12   // Port value. Write to change, read to obtain value
#define GPIOB    0x13
#define OLLATA   0x14   // Output latch. Write to latch output.
#define OLLATB   0x15

#define chip1 0x20  // MCP23017 is on I2C address 0x20
#define chip2 0x21  // MCP23017 is on I2C address 0x21

volatile boolean keyPressed;

// set register "reg" on expander to "data"
// for example, IO direction
void expanderWriteBoth (const byte address, const byte reg, const byte data ) 
{
  Wire.beginTransmission (address);
  Wire.send (reg);
  Wire.send (data);  // port A
  Wire.send (data);  // port B
  Wire.endTransmission ();
} // end of expanderWrite

// read a byte from the expander
unsigned int expanderRead (const byte address, const byte reg) 
{
  Wire.beginTransmission (address);
  Wire.send (reg);
  Wire.endTransmission ();
  Wire.requestFrom (address, (byte) 1);
  return Wire.receive();
} // end of expanderRead

// interrupt service routine, called when pin D2 goes from 1 to 0
void keypress ()
{
  keyPressed = true;   // set flag so main loop knows
}  // end of keypress

void setup ()
{

  Wire.begin ();  
  Serial.begin (9600);  

  // expander configuration register
  expanderWriteBoth (chip1, IOCON, 0b01100100); // mirror interrupts, disable sequential mode, open drain
  expanderWriteBoth (chip2, IOCON, 0b01100100); // mirror interrupts, disable sequential mode, open drain
 
  // enable pull-up on switches
  expanderWriteBoth (chip1, GPPUA, 0xFF);   // pull-up resistor for switch - both ports
  expanderWriteBoth (chip2, GPPUA, 0xFF);   // pull-up resistor for switch - both ports

  // invert polarity
  expanderWriteBoth (chip1, IOPOLA, 0xFF);  // invert polarity of signal - both ports
  expanderWriteBoth (chip2, IOPOLA, 0xFF);  // invert polarity of signal - both ports
  
  // enable all interrupts
  expanderWriteBoth (chip1, GPINTENA, 0xFF); // enable interrupts - both ports
  expanderWriteBoth (chip2, GPINTENA, 0xFF); // enable interrupts - both ports
  
  // no interrupt yet
  keyPressed = false;

  // read from interrupt capture ports to clear them
  expanderRead (chip1, INTCAPA);
  expanderRead (chip2, INTCAPA);
  expanderRead (chip1, INTCAPB);
  expanderRead (chip2, INTCAPB);
  
  // pin 19 of MCP23017 is plugged into D2 of the Arduino which is interrupt 0
  pinMode (2, INPUT);      // make sure input
  digitalWrite (2, HIGH);  // enable pull-up as we have made the interrupt pins open drain
  
  attachInterrupt(0, keypress, FALLING);
  
}  // end of setup

void handleKeypress ()
{
  unsigned int keyValue1 = 0;
  unsigned int keyValue2 = 0;
  
  delay (100);  // de-bounce before we re-enable interrupts

  keyPressed = false;
  
  // check first chip
  if (expanderRead (chip1, INFTFA))
    keyValue1 |= expanderRead (chip1, INTCAPA) << 8;    // read value at time of interrupt
  if (expanderRead (chip1, INFTFB))
    keyValue1 |= expanderRead (chip1, INTCAPB);        // port B is in low-order byte

  // check second chip
  if (expanderRead (chip2, INFTFA))
    keyValue2 |= expanderRead (chip2, INTCAPA) << 8;    // read value at time of interrupt
  if (expanderRead (chip2, INFTFB))
    keyValue2 |= expanderRead (chip2, INTCAPB);        // port B is in low-order byte
  
  // show state of first 16 buttons
  for (byte button = 0; button < 16; button++)
    {
    // this key down?
    if (keyValue1 & (1 << button))
      {
      Serial.print ("Button ");
      Serial.print (button + 1, DEC);
      Serial.println (" now down");
      }  // end of if this bit changed
    
    } // end of for each button
  
  // show state of next 16 buttons
  for (byte button = 0; button < 16; button++)
    {
    // this key down?
    if (keyValue2 & (1 << button))
      {
      Serial.print ("Button ");
      Serial.print (button + 17, DEC);
      Serial.println (" now down");
      }  // end of if this bit changed
    
    } // end of for each button
  
}  // end of handleKeypress

volatile unsigned long i;

void loop ()
{

  // some important calculations here ...
  for (i = 0; i < 0x7FFF; i++)
    {}
  
  // was there an interrupt?
  if (keyPressed)
    handleKeypress ();
 
}  // end of loop


This version doesn't have the debugging stuff in the earlier version. This makes it clearer what is really required to make it work.


Handling multiple button presses


The code above would only detect the first key-press if multiple ones were made in quick succession. If you really need to know that buttons 1 and 2 are held down together, you need to change:


 if (expanderRead (chip1, INFTFA))
    keyValue1 |= expanderRead (chip1, INTCAPA) << 8;    // read value at time of interrupt
  if (expanderRead (chip1, INFTFB))
    keyValue1 |= expanderRead (chip1, INTCAPB);        // port B is in low-order byte

  // check second chip
  if (expanderRead (chip2, INFTFA))
    keyValue2 |= expanderRead (chip2, INTCAPA) << 8;    // read value at time of interrupt
  if (expanderRead (chip2, INFTFB))
    keyValue2 |= expanderRead (chip2, INTCAPB);        // port B is in low-order byte


to:


  keyValue1 |= expanderRead (chip1, GPIOA) << 8;    // read value now
  keyValue1 |= expanderRead (chip1, GPIOB);         // port B is in low-order byte

  // check second chip
  keyValue2 |= expanderRead (chip2, GPIOA) << 8;    // read value now
  keyValue2 |= expanderRead (chip2, GPIOB);         // port B is in low-order byte


That reads the current value rather than the value at the time of the interrupt. Thus you would find what buttons are currently being held down. Of course, you need to react to the interrupt rather quickly in this case, or they may have let go of the button.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Greete   (16 posts)  Bio
Date Reply #2 on Thu 18 Aug 2011 07:28 PM (UTC)
Message
Hello,
thanks a lot for sharing those informations! Great work and help me a lot! I've realized the hardware and used the software you created and worked immediately. I just noticed that if I press 8 or more push button in the same time arduino will totally freeze and have to hardware reset. Any ideas?
Top

Posted by Nick Gammon   Australia  (23,121 posts)  Bio   Forum Administrator
Date Reply #3 on Thu 18 Aug 2011 09:58 PM (UTC)

Amended on Thu 18 Aug 2011 09:59 PM (UTC) by Nick Gammon

Message
Probably whatever it is you do when 8 buttons are pushed is the reason (for example, are you lighting LEDs?).

Each output port has a recommended current drain of 20 mA, and an absolute maximum of 40 mA. Plus there are limits for the whole device (200 mA from memory).

So if you have 8 LEDs, all drawing 30 mA (depending on your current limiting resistors) then that is within range for one port, but 8 x 30 is 240 mA which is too high. That might cause the processor to reset.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Greete   (16 posts)  Bio
Date Reply #4 on Fri 19 Aug 2011 10:14 PM (UTC)
Message
thanks for response!
I have no leds, just the switches!
Top

Posted by Nick Gammon   Australia  (23,121 posts)  Bio   Forum Administrator
Date Reply #5 on Fri 19 Aug 2011 11:53 PM (UTC)
Message
How are they wired up?

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Greete   (16 posts)  Bio
Date Reply #6 on Sun 21 Aug 2011 05:22 PM (UTC)
Message
hello Nick,
sorry delay but my internet connection was unavailable.
I have connected 16 switches, one for any port, with ground in the other side. I have followed your schematics as it is!
For soft, I'musing the first one in this page since I have only one chip.
Top

Posted by Nick Gammon   Australia  (23,121 posts)  Bio   Forum Administrator
Date Reply #7 on Tue 23 Aug 2011 01:38 AM (UTC)

Amended on Tue 23 Aug 2011 01:39 AM (UTC) by Nick Gammon

Message
I can't reproduce that. I hooked up my board again, and running a wire to each of the 16 pins to ground (one by one) I eventually got this:


Button 1 now down
Button 2 now down
Button 3 now down
Button 4 now down
Button 5 now down
Button 6 now down
Button 7 now down
Button 8 now down
Button 9 now down
Button 10 now down
Button 11 now down
Button 12 now down
Button 13 now down
Button 14 now down
Button 15 now down
Button 16 now down


There was no reset of the processor.

I had to change this:


// Read port values, as required. Note that this re-arms the interrupts.
  if (expanderRead (INFTFA))
    keyValue |= expanderRead (INTCAPA) << 8;    // read value at time of interrupt
  if (expanderRead (INFTFB))
    keyValue |= expanderRead (INTCAPB);        // port B is in low-order byte


to this:


 // Read port values, as required. Note that this re-arms the interrupts.
  if (expanderRead (INFTFA) || expanderRead (INFTFB))
    {
    keyValue |= expanderRead (INTCAPA) << 8;    // read value at time of interrupt
    keyValue |= expanderRead (INTCAPB);        // port B is in low-order byte
    }


Otherwise it didn't report the "B" buttons as being down if you connected on of the "A" buttons (because only the "A" button had the interrupt).

So if it isn't working maybe it is something electrical?

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Greete   (16 posts)  Bio
Date Reply #8 on Tue 23 Aug 2011 08:00 PM (UTC)
Message
Nick,
thanks a lot for your efforts! I have checked carefully the schematics and my breadboard and looks ok. I have measured power supply before and after the freeze and it's always around 5.04V (I'm using an external stabilized power supply, a switching module from ALPS).
The only differences is that I'm using pin 3 and interrupt 1 on the arduino 2009 instead of pin 2 and interrupt 0.
I've also included some decoupling capacitor (1x10uF and one 100nF very close the mcp chip supply pin).
Today I've tried to use the open drain command on mcp and pullup pin3 and results was much promises since it worked at my efforts to freeze it for several times but at the end it just stop working.
Pressing, one, two, three buttons in the same time will always work perfectly, pressing a bunch of push buttons 7 or more in the same time now sometims result in a complete freeze of the arduino.
At this point I'm think that something wrong with the arduino 2009 clone I'm using or the mcp chip is defective!

What I have not clearly understand is why you reset the internal interrupt of the mcp chip and don't just use the arduino interrupt to detect when chip goes low (as happen in ntx pcf8574 chips)
Thanks a lot for response and your time!
Top

Posted by Nick Gammon   Australia  (23,121 posts)  Bio   Forum Administrator
Date Reply #9 on Tue 23 Aug 2011 09:30 PM (UTC)
Message
Quote:

The only differences is that I'm using pin 3 and interrupt 1 on the arduino 2009 instead of pin 2 and interrupt 0.


Why? Is there more to this than exactly reproducing my circuit and code? Perhaps post your complete code in that case.

The capacitors are a good idea, won't do any harm.

Quote:

I have not clearly understand is why you reset the internal interrupt of the mcp chip and don't just use the arduino interrupt to detect when chip goes low ...


I do use the Arduino interrupt. The MCP23017 generates an interrupt on a pin change of any of the 16 ports. That interrupt is then detected by the Arduino. That interrupt tells it to enquire (via I2C) about the status of the pins.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Greete   (16 posts)  Bio
Date Reply #10 on Thu 25 Aug 2011 07:24 PM (UTC)
Message
Thanks Nick! I prolly finally found the problem and of course was my mistake. I've connected the interrupt pin 20 instead of 19 of the mcp! Unfortunatly I'm in vacation so I don't have a solder here to change the wire so I cannot verify if it's working.
There's a way to redirect MCP interrupts to this pin via software?

I cannot resist to bring my arduino clone in vacation so I've builded up a board with an Graphic LCD connected to a MCP23017 with your code (and working beautiful) and another MCP23017 with different address connected to 16 switches and a arduino clone board inserted in the board for study purposes.

Unfortunatly I cannot use interrupt 0 on arduino since the pin has not a stable connection so I changed these lines in your code:

pinMode (3, INPUT);      // make sure input
digitalWrite (3, HIGH);  // enable pull-up
  
attachInterrupt(1, keypress, FALLING);

Apart the interrupt changes on arduino and MCP (that was clearly my mistake) I followed your schematic as on graphic LCD (that as I told before works beautifully, just changed the mcp address on this last one)
Top

Posted by Nick Gammon   Australia  (23,121 posts)  Bio   Forum Administrator
Date Reply #11 on Thu 25 Aug 2011 11:57 PM (UTC)
Message
Greete said:

There's a way to redirect MCP interrupts to this pin via software?


I had "mirror interrupts" turned on, which according to the datasheet "the INT pins are internally connected".

So, either pin 19 or 20 should work.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Greete   (16 posts)  Bio
Date Reply #12 on Sat 27 Aug 2011 07:49 PM (UTC)

Amended on Sat 27 Aug 2011 07:57 PM (UTC) by Greete

Message
I've made some other researches, I've Saleae Logic analyzer with me (same as yours I think), I've connected sda/scl and interrupt, played with buttons and when I pushed all together the interrupt goes low and remains low constantly so looks like it's mcp chip the problem. I'm not a big expert of the logic analyzer but I was able to correctly assign sda and scl to the saleae I2C analyzer module and I saved the session with data, so if you want take a look... .)

http://depositfiles.com/files/c2uz01lhd
Top

Posted by Nick Gammon   Australia  (23,121 posts)  Bio   Forum Administrator
Date Reply #13 on Sat 27 Aug 2011 09:21 PM (UTC)

Amended on Sat 27 Aug 2011 09:25 PM (UTC) by Nick Gammon

Message
Can't you just post the image on an image hosting site? I had to wait 60 seconds and then got a .rar file which I can't unzip on the Mac I am sitting at right now.

[EDIT] Also a couple of extra advertisement pages opened in my web browser, which is quite annoying.

Anyway, without seeing the image, the interrupt line will go low on an interrupt and stay low until you "tell" the chip you have seen the interrupt by reading from the appropriate ports. In the setup phase I did it like this:


// read from interrupt capture ports to clear them
  expanderRead (INTCAPA);
  expanderRead (INTCAPB);


and later on, during operation:


// Read port values, as required. Note that this re-arms the interrupts.
  if (expanderRead (INFTFA) || expanderRead (INFTFB))
    {
    keyValue |= expanderRead (INTCAPA) << 8;    // read value at time of interrupt
    keyValue |= expanderRead (INTCAPB);        // port B is in low-order byte
    }


Reading INTCAPA and INTCAPB is when the interrupts should be cleared. You could set a debugging digital line high (eg. D7) just before doing that and then low afterwards. Then check those lines with the logic analyzer and see if the interrupt line is changing in that interval.

I would be a bit surprised if you have a faulty chip. Many times these things come down to software problems.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Greete   (16 posts)  Bio
Date Reply #14 on Sat 27 Aug 2011 09:41 PM (UTC)

Amended on Sat 27 Aug 2011 10:02 PM (UTC) by Greete

Message
I got it!
dunno exact the reason and the solution is far from being ok but like this it works! I have added some extra check to verify if the interrupt is firing the arduino pin.


void handleKeypress ()
{
  int checkInterrupt = 0;// ADDEDD
  checkInterrupt = digitalRead(2);// ADDEDD
  if (checkInterrupt == LOW){// ADDEDD
    unsigned int keyValue1 = 0;
    delay (100);  // de-bounce before we re-enable interrupts
    keyPressed = false;
    // check first chip
    while(checkInterrupt == LOW){// ADDEDD
    checkInterrupt = digitalRead(2);// ADDEDD
      if (expanderRead (chip1, INFTFA))
        keyValue1 |= expanderRead (chip1, INTCAPA) << 8;    // read value at time of interrupt
      if (expanderRead (chip1, INFTFB))
        keyValue1 |= expanderRead (chip1, INTCAPB);        // port B is in low-order byte
    }
      // show state of first 16 buttons
      for (byte button = 0; button < 16; button++)
      {
        // this key down?
        if (keyValue1 & (1 << button))
        {
          Serial.print ("Button ");
          Serial.print (button + 1, DEC);
          Serial.println (" now down");
        }  // end of if this bit changed

      } // end of for each button
    
  }
}  // end of handleKeypress


As I tell, dunno why but no more freezes!

In the initialize section I added these lines but I dunno if necessary:

  expanderWrite (chip1,IODIRB,B11111111); //port B = INPUT
  expanderWrite (chip1,IODIRA,B11111111); //port A = INPUT


Sorry for my last post!

If you still need I can post the saleae session zipped in my site (that can be opened by mac)

[EDIT] I have changed the while loop because in the last mod it was returning 2 times the button value, now it's okey
Top

The dates and times for posts above are shown in Universal Co-ordinated Time (UTC).

To show them in your local time you can join the forum, and then set the 'time correction' field in your profile to the number of hours difference between your location and UTC time.


172,059 views.

This is page 1, subject is 3 pages long: 1 2  3  [Next page]

Postings by administrators only.

Refresh page

Go to topic:           Search the forum


[Go to top] top

Information and images on this site are licensed under the Creative Commons Attribution 3.0 Australia License unless stated otherwise.