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,120 posts)  Bio   Forum Administrator
Date Reply #30 on Mon 03 Sep 2012 11:28 PM (UTC)

Amended on Mon 03 Sep 2012 11:52 PM (UTC) by Nick Gammon

Message
Without seeing more details (like code) it is hard to say. The device may respond in 125 nS but handling an interrupt on the processor is more likely to take around 5 uS from start to finish. Plus it depends what you are doing in the ISR.

- Nick Gammon

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

Posted by Rossco   (5 posts)  Bio
Date Reply #31 on Wed 05 Sep 2012 07:47 PM (UTC)
Message
Hi,

I don't think the interrupt has any impact on the MCP chips as the INT registers are not read/cleared until all the time critical interrupts have occured. With th eball travelling approximately 30 m/s and travelling 400 mm between sensor arrays there is about 13000 ms between interrupt triggers at the arduino input.

The interrupt code is:

//Interrupt Service Routine Code for Timer1 Input Capture
ISR(TIMER1_CAPT_vect){
  if( bit_is_set(TCCR1B ,ICES1)){ // was first was rising edge detected ?
    TCNT1 = 0; // reset the counter
  }
  else if((! bit_is_set(TCCR1B ,ICES1)) && (EdgeCount==1)){ // was first falling edge detected ?
    time1=ICR1; // record time between 1st and 2nd array
  }
  else if((! bit_is_set(TCCR1B ,ICES1)) && (EdgeCount==3)){ // was second falling edge detected ?
    time2=ICR1; // record time between 2nd and 3rd array
  }
++EdgeCount;
TCCR1B ^= _BV(ICES1); // toggle bit value to trigger on the other edge
}



The main loop waits until the ball has passed all 4 sensor arrays and then reads the INTF values from each chip. as stated in my previous post, the results do not match those expected. Any ideas?

the EdgeCount and time variables in the interrupt routine are volatile and read with interrupts turned off (in functions edge_count() and final_timex, rather than main loop) as I believe is standard practice.



if(edge_count() >= NUMBER_EDGES ){
  
  //these next defintions are made in the main loop rather than programme start as the should be cleared after each ball pass

  volatile unsigned int time1ms =0; //time registers store time values in microseconds and seconds for display and calculation
  volatile float time1s =0.0;
  volatile unsigned int time2ms=0;
  volatile float time2s=0.0;

  volatile unsigned int time1b;
  volatile unsigned int time2b;
  
  time1b=final_time1();
  time2b=final_time2();
  time1ms = time1b / 2;
  time1s= (float)time1ms / 1000000.0;
  time2ms= time2b / 2;
  time2s= (float)time2ms / 1000000.0;

  //These statements read the 8 bit INTA and INTB registers of each MCP23017 chip and combine and store these registers a 16bit vlaues
  chip1intA=expanderRead(chip1, INFTFA);
  chip1intB=expanderRead(chip1, INFTFB);
    chip1profile= (chip1intB << 8 | chip1intA); // this statement shifts the intB binary byte 8 bits to the left and combines it with the intA byte through OR logic to create a single 16 bit value.
  chip2intA=expanderRead(chip2, INFTFA);
  chip2intB=expanderRead(chip2, INFTFB);
    chip2profile= chip2intB << 8 | chip2intA;
  chip3intA=expanderRead(chip3, INFTFA);
  chip3intB=expanderRead(chip3, INFTFB);
    chip3profile= chip3intB << 8 | chip3intA;
  chip4intA=expanderRead(chip4, INFTFA);
  chip4intB=expanderRead(chip4, INFTFB);
    chip4profile= chip4intB << 8 | chip4intA;
  chip5intA=expanderRead(chip5, INFTFA);
  chip5intB=expanderRead(chip5, INFTFB);
    chip5profile= chip5intB << 8 | chip5intA;
  chip6intA=expanderRead(chip6, INFTFA);
  chip6intB=expanderRead(chip6, INFTFB);
    chip6profile= chip6intB << 8 | chip6intA;
    
array1profile = ((long)chip2profile << 16) | chip1profile;
array2profile = ((long)chip4profile << 16) | chip3profile;
    
Top

Posted by Nick Gammon   Australia  (23,120 posts)  Bio   Forum Administrator
Date Reply #32 on Wed 05 Sep 2012 09:04 PM (UTC)
Message


if(edge_count() >= NUMBER_EDGES ){
  
...

  volatile float time1s =0.0;
  volatile unsigned int time2ms=0;
  volatile float time2s=0.0;

  volatile unsigned int time1b;
  volatile unsigned int time2b;


I don't see the point of putting volatile variables here. Why are you doing that?

- Nick Gammon

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

Posted by Greete   (16 posts)  Bio
Date Reply #33 on Mon 22 Oct 2012 11:07 PM (UTC)

Amended on Mon 29 Oct 2012 01:17 AM (UTC) by Greete

Message
Hello Nick,
I finally solved to use your code for the MCP23S17 version.
I simply forget to address the chip!!!
Here's the working code if someone need it

UPDATE
For some unknow reason the MCP23s17 not works as his 23017 brother! The following code works with both chip but SPI version will drop the INT to LOW after some time (leave alone 3 min, press any button and...bang!), causing a freeze of the chip (that sometimes needs to force resetted!
I spent an entire week study the phenomena and tried at list 4 different libraries and this happened always (change chip won't help, tried 10 different ones and 3 processors).
If someone has any idea would be great!


#include <SPI.h>
#include "pins_arduino.h"

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

// Demonstration of an interrupt service routine connected to the MCP23017
// modded by Aleandro Greete for use with MCP23S17 (SPI version)

#include <Wire.h>

#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 SS   4  //I'm using ATMEGA 640P
#define chip1 0x25  // MCP23017 is on I2C address 0x25
#define INTused 0 
#define INTpin 10 //I'm using ATMEGA 640P, this pin has INT0 hardwired

volatile boolean keyPressed;

void expanderWriteBoth (const byte address, const byte reg, const byte data ) 
{
  if (SS > 0){
    digitalWrite(SS, LOW);
    SPI.transfer (address << 1);//this was missed!
    SPI.transfer (reg);
    SPI.transfer (data);
    SPI.transfer (data);
    digitalWrite(SS, HIGH);
  } 
  else {
    Wire.beginTransmission (address);
    Wire.write (reg);
    Wire.write (data);  // port A
    Wire.write (data);  // port B
    Wire.endTransmission ();
  }
}

unsigned int expanderRead (const byte address, const byte reg) 
{
  unsigned int data = 0;
  if (SS > 0){
    digitalWrite(SS, LOW);
    SPI.transfer ((address << 1) | 1);//this was missed!
    SPI.transfer (reg);
    data = SPI.transfer(0);
    digitalWrite(SS, HIGH);
  } 
  else {
    Wire.beginTransmission (address);
    Wire.write (reg);
    Wire.endTransmission ();
    Wire.requestFrom (address, (byte) 1);
    data = Wire.read();
  }
  return data;
}

void keypress ()
{
  keyPressed = true;   // set flag so main loop knows
}

void setup ()
{
  Serial.begin (38400); 
  if (SS > 0){
    SPI.begin ();
    SPI.setClockDivider(SPI_CLOCK_DIV8);
    digitalWrite(SS, HIGH);
  } 
  else {
    Wire.begin ();
  }

  // expander configuration register
  expanderWriteBoth (chip1, 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
  // invert polarity
  expanderWriteBoth (chip1, IOPOLA, 0xFF);  // invert polarity of signal - both ports
  // enable all interrupts
  expanderWriteBoth (chip1, GPINTENA, 0xFF); // enable interrupts - both ports

  keyPressed = false;

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

  pinMode (INTpin, INPUT);      // make sure input
  digitalWrite (10, HIGH);  // enable pull-up as we have made the interrupt pins open drain

  attachInterrupt(INTused, keypress, FALLING);

} 

void handleKeypress ()
{
  detachInterrupt(INTused);//protect from further interrupts 
  unsigned int keyValue1 = 0;
  delay (30);  // de-bounce before we re-enable interrupts
  if (expanderRead (chip1, INFTFA))
    keyValue1 |= expanderRead (chip1, INTCAPA);
  if (expanderRead (chip1, INFTFB))
    keyValue1 |= expanderRead (chip1, INTCAPB) << 8;        
  // 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

  }
  keyPressed = false;
  attachInterrupt(INTused, keypress, FALLING);
} 

void loop ()
{

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

}  // end of loop


I also added a detachInterrupt to protect the routine that read buttons from any further interrupts.
Thanks again for your lessons, I definetively learn more from this pages than arduino forum!
Top

Posted by Greete   (16 posts)  Bio
Date Reply #34 on Sat 27 Oct 2012 04:29 PM (UTC)
Message
Here's a version that uses Squential Operation Mode bit enabled. This seems to solve the problem I had in the past that simetimes the interrupt flag has not been correctly reset (read my old posts in this section).
Btw I experimented a lot with this chip and his SPI brother 23s17 and results are that interrupt section it's not working always as expected. For example, mcp23s17 configured in input for use the interrupts will always freeze down the INT port after some minute (checked with at list 10 different chips, various configuration and even different libraries), use it with no INT option will not cause any problem (looks like that only INT output pin it's affected).
In out mode it works always perfect, just find some difficulties when different chips use the HAEN option and share the same CS pin (better send HAEN to all chips before all).
The I2C version works (in input configuration) much better but find that Sequential Mode it's more reliable (at list as input with INT option). In out mode works as expect always (since no HAEN mode for this chip).
I find useful use a small button tied to ground engage hardware reset for this chips during configuration experiments since sometimes they just freeze up and works strangely, this just for experiments, not needed for applications.

Here's the code for use with Squential Operation mode bit
Thanks again for your lessons, Nick, it's always a great inspiration see your pages.

#include <Wire.h>
/*
Ths version has been modified for use with Squential Operation mode bit
This use slight less code
*/
#define I2C_SW1 0x27//USE THE CORRECT ADDRESS! this is 3 10k resistors tied to +
/* Multiple Interrupt defs and pins */
#define INT_DEBOUNCE_TIME 40
#define INT_COMMON_PIN 10
#define INT_COMMON_NUM 0
#define IODIR 0x00
#define IOPOL 0x02
#define GPINTEN 0x04
#define DEFVAL  0x06
#define INTCON  0x08
#define IOCON    0x0A   // IO Configuration: bank/mirror/seqop/disslw/haen/odr/intpol/notimp
#define GPPU  0x0C
#define INFTF  0x0E
#define INTCAP  0x10
#define GPIO  0x12
#define OLLAT  0x14

volatile byte keyPressed;

void setup(void)
{
#if defined DBG
  Serial.begin(38400);
  Serial.println("inited");
#endif
  Wire.begin(); // start I2C
  TWBR = ((F_CPU / 400000L) - 16) / 2;//I2C a 400Khz
  //-------------------------> SWITCH LOGIC CONFIGURATION
  //BANK,MIRROR,SEQOP,DISSLW,HAEN,ODR,INPOL,nused
  switchWrite(I2C_SW1,IOCON,0b01000000,2); // mirror interrupts, enable sequential mode, open drain
  // enable pull-up on switches
  switchWrite(I2C_SW1,GPPU,0xFF,2);   // pull-up resistor for switch - both ports
  // invert polarity
  switchWrite(I2C_SW1,IOPOL,0xFF,2);  // invert polarity of signal - both ports
  // enable all interrupts
  switchWrite(I2C_SW1,GPINTEN,0xFF,2); // enable interrupts - both ports
  switchWrite(I2C_SW1,IODIR,0b11111111,2); //port A = INPUT
  // read from interrupt capture ports to clear them
  switchRead(I2C_SW1,INTCAP);
  keyPressed = 0;
  // pin 19 of MCP23017 is plugged into D2 of the Arduino which is interrupt 0
  pinMode(INT_COMMON_PIN,INPUT);      // make sure input
  digitalWrite(INT_COMMON_PIN,HIGH);  // enable pull-up as we have made the interrupt pins open drain
  attachInterrupt(INT_COMMON_NUM,keypress,FALLING);
}


/**************++++++++++ LOOP ++++++++++*********************
 **************************************************************/
void loop(void)
{
  if (keyPressed){
    handleKeypress();
  }
}

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

void switchWrite(const byte address,const byte reg,const byte data,const byte times) 
{
  Wire.beginTransmission(address);
  Wire.write(reg);
  for (byte i=0;i<times;i++){
    Wire.write(data);
  }
  Wire.endTransmission();
}

// read a word from the expander
word switchRead(const byte address,const byte reg) 
{
  word _data = 0;
  Wire.beginTransmission(address);
  Wire.write(reg);
  Wire.endTransmission();
  Wire.requestFrom((int)address, 2);
  _data = Wire.read();
  _data |= Wire.read() << 8;
  return _data;
} 


void handleKeypress()
{
  word keyValue = 0b0000000000000000;//16 bit
  delay(INT_DEBOUNCE_TIME);  // de-bounce before we re-enable interrupts
  keyPressed = 0;
  // Read port values, as required. Note that this re-arms the interrupts.
  if (switchRead(I2C_SW1,INFTF)){
    keyValue = switchRead(I2C_SW1,INTCAP);
    if (keyValue > 0){
      for (byte key = 0; key < 16; key++)
      {
        if (keyValue & (1 << key))
        {
          Serial.print("Button ");
          Serial.print(key, DEC);
          Serial.println();
        }
      }
    }
  }
}  // end of handleKeypress
Top

Posted by Nick Gammon   Australia  (23,120 posts)  Bio   Forum Administrator
Date Reply #35 on Fri 11 Sep 2015 03:42 AM (UTC)

Amended on Fri 11 Sep 2015 08:14 PM (UTC) by Nick Gammon

Message
Example of writing to MCP23S17



// MCP23017 and MCP23S17 demo
// Author: Nick Gammon
// Date: 11 September 2015

#include <Wire.h>
#include <SPI.h>

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

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


const byte  DEVICE_ADDRESS = 0x20;  // MCP23017 is on I2C port 0x20

const byte ssPin = 10;   // slave select pin, if non-zero use SPI
const byte expanderPort = 0x20;

// set register "reg" on expander to "data"
// for example, IO direction
void expanderWrite (const byte reg, const byte data ) 
  {
  startSend ();
    doSend (reg);
    doSend (data);
  endSend ();
} // end of expanderWrite

// prepare for sending to MCP23017 
void startSend ()   
{
  
  if (ssPin)
    {
    digitalWrite (ssPin, LOW); 
    SPI.transfer (expanderPort << 1);  // note this is write mode
    }
  else
    Wire.beginTransmission (expanderPort);
  
}  // end of startSend

// send a byte via SPI or I2C
void doSend (const byte what)   
{
  if (ssPin)
    SPI.transfer (what);
  else
    Wire.write (what);
}  // end of doSend

// finish sending to MCP23017 
void endSend ()   
{
  if (ssPin)
    digitalWrite (ssPin, HIGH); 
  else
    Wire.endTransmission ();
 
}  // end of endSend

void setup ()
{
  
  if (ssPin)  // if we have an SS pin it is SPI mode
    {
    digitalWrite (ssPin, HIGH);
    SPI.begin ();
    pinMode (ssPin, OUTPUT);
    }
  else
    Wire.begin (DEVICE_ADDRESS);   

  // byte mode (not sequential)
  expanderWrite (IOCON, 0b00100000);
  
  // all pins as outputs
  expanderWrite (IODIRA, 0);
  expanderWrite (IODIRB, 0);
  
}  // end of setup

void loop ()
  {
  expanderWrite (GPIOA, 0xAA);
  delay (100);  
  expanderWrite (GPIOA, 0);
  delay (100);  
  }  // end of loop

- Nick Gammon

www.gammon.com.au, www.mushclient.com
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.


171,895 views.

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

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.