MCP23017 (I2C 16 bits I/O expander) speed

Hi,

I have begin tests with an ESP32 and a MCP23017

And this seem very too slow for my needs on my firsts basics tests on it :frowning:
(the display of ONLY one RGB charlieplexed tower of 4x RGB leds that are turned at 90 degrees for each level to the next = 12 monochromatics leds)

Here is the code, I use :

#include <Wire.h>
#include <MCP23017.h>



#define I2C_SDA 21
#define I2C_SCL 22


hw_timer_t *My_timer = NULL;

int seconds = 0;
int frames = 0;

MCP23017 mcp;


void IRAM_ATTR timer_func()
{
  seconds++;
  Serial.print(seconds);
  Serial.print(" seconds (");
  Serial.print(frame);
  Serial.println(" fps)");
  frames=0;
}

void setup_timer() 
{
  seconds = 0;
  
  My_timer = timerBegin(0, 80, true);
  timerAttachInterrupt(My_timer, &timer_func, true);
  timerAlarmWrite(My_timer, 1000000, true);
  timerAlarmEnable(My_timer); //Just Enable
}


 
int search_i2c()
{
  byte error, address;
  int nDevices;
 
  Serial.println("Scanning ...");
 
  nDevices = 0;
  for(address = 1; address < 127; address++ )
  {
    // The i2c_scanner uses the return value of
    // the Write.endTransmisstion to see if
    // a device did acknowledge to the address.
    Wire.beginTransmission(address);
    error = Wire.endTransmission();
 
    if (error == 0)
    {
      Serial.print("I2C device found at address 0x");
      if (address<16)
        Serial.print("0");
      Serial.print(address,HEX);
      Serial.println("  :)");
 
      nDevices++;
    }
    else if (error==4)
    {
      Serial.print("Unknown error at address 0x");
      if (address<16)
        Serial.print("0");
      Serial.println(address,HEX);
    }    
  }
  if (nDevices == 0)
    Serial.println("No I2C devices found\n");
  else
    Serial.println("done\n");
 
  delay(5000);           // wait 5 seconds for next scan
  return nDevices;
}

void setup_i2c(int sda = I2C_SDA, int scl = I2C_SCL)
{
  int found = 0;
  
  Wire.begin();
 
  Serial.println("\nI2C Scanner");
  while ( (found = search_i2c() ) == 0 ) 
  {
      Serial.println("I2C device not found \n");
      delay(5000);
  }
}

void mcp_reset()
{
  // for ( int i = 0 ; i < 16 ; i++ )
  for ( int i = 8 ; i < 12 ; i++ )
  {
    mcp.pinMode(i, INPUT);
  }
}

 
void setup_mcp()
{
  mcp.begin( 7 );

  mcp_reset();
}


void mcp_set_pins(int low, int high)
{
    mcp.pinMode(low, OUTPUT);
    mcp.pinMode(high, OUTPUT);
    mcp.digitalWrite(low, LOW);
    mcp.digitalWrite(high, HIGH);

    // delay(1);
}

void mcp_stage( int pin0, int pin1 )
{
  /*
  Serial.print("mcp_stage(");
  Serial.print(pin0);
  Serial.print(",");
  Serial.print(pin1);
  Serial.println(")");
 */ 

  // mcp.digitalWrite(pin0, LOW);
  // mcp.digitalWrite(pin1, HIGH);  

  mcp_reset();
  mcp_set_pins( pin0, pin1);
}

void loop_mcp(int first)
{
  int nextpin[4] = { 1, 2, 3, 0};
  
  for ( int i = 0 ; i < 4 ; i++)
  {    
    for ( int j = 0 ; j < 4 ; j++)
    {
        if( i != nextpin[j] )
        {
          mcp_stage(i + first, nextpin[j] + first);
        }
    }
    // this seem needed to wait a little if we don't want a regular crash
    delay(1);
  }

}

void setup()
{
  Serial.begin(115200);
  setup_i2c();
  setup_mcp();
  setup_timer();
}

void loop() 
{
  // search_i2c();
  loop_mcp( 8 );
  frames++;
}

This give me only something between 11 and 12 refresh per second when I need something very more high like 50 or 100 Hz

I find 12 fps with the mcp_reset() call into mcp_stage() and 23 fps without
I find too 23 fps without tje mcp__set_pins(pin0, pin1)
And A VERY BIGGER frequency of refresh at 250 fps without mcp_reset() AND mcp_ set_pins() calls
(and I reset only 4 pins at the mcp_reset() call, not alls pins because in this case the fps is lowered to ONLY 4 fps ...)

It exist another MCP23017 package that is very more speed than the version that I have use from https://www.waveshare.com/wiki ?
(I use the mcp23017.h and . cpp files into the arduino directory of the MCP23017-IO-Expansion-Board-Demo-Code.7z archive)

I wonder how that code gives any refresh at all.

Why did you say " I wonder how that code gives any refresh at all" ?

This work but only at a very too slow rate
(the 2 very too slow calls are mcp.pinMode() and mcp.digitalWrite(), I have 250 fps when I comments them into mcp_reset() and mcp_set_pins() )

I don't see code where you transmit data to the expander ports. Please specify what makes up a frame in your project.

Sending data executes at I2C speed. If you observe lower data rates then this is due to your code.

Data is transmitted to the MCP23017 via mcp_reset() and mcp_ set_pins() calls
(they internally use mcp.pinMode() and mcp.digitalWrite() calls )

A frame is a complete refresh of alls 4 RGB leds of the tower
(I use the 8th, 9th, 10th and 11th pins output of the MCP23017, it's why first = 8 into the loop_mcp( int first) call )

I have now 23 fps with a better handling of the mcp.pinMode() and mcp.digitalWrite() calls, cf. only call them a strictly minimum times
=> the problem is at the mcp.pinMode() and mcp.digitalWrite() calls speed, it's now sure and certain ...

Here is the code

#include <Wire.h>
#include <MCP23017.h>



#define I2C_SDA 21
#define I2C_SCL 22

#define SLOW 1000

hw_timer_t *My_timer = NULL;

int seconds = 0;
int frames = 0;
int counter = 0;

MCP23017 mcp;


void IRAM_ATTR timer_func()
{
  seconds++;
  
  Serial.print(seconds);
  Serial.print("th second (");
  Serial.print(frames);
  Serial.print(" fps) [counter=");
  Serial.print(counter);
  Serial.println("]");

  frames=0;
  
  // counter++;
  // if ( counter > SLOW){ counter = 0; }

}

void setup_timer() 
{
  seconds = 0;
  
  My_timer = timerBegin(0, 80, true);
  timerAttachInterrupt(My_timer, &timer_func, true);
  timerAlarmWrite(My_timer, 1000000, true);
  timerAlarmEnable(My_timer); //Just Enable
}


 
int search_i2c()
{
  byte error, address;
  int nDevices;
 
  Serial.println("Scanning ...");
 
  nDevices = 0;
  for(address = 1; address < 127; address++ )
  {
    // The i2c_scanner uses the return value of
    // the Write.endTransmisstion to see if
    // a device did acknowledge to the address.
    Wire.beginTransmission(address);
    error = Wire.endTransmission();
 
    if (error == 0)
    {
      Serial.print("I2C device found at address 0x");
      if (address<16)
        Serial.print("0");
      Serial.print(address,HEX);
      Serial.println("  :)");
 
      nDevices++;
    }
    else if (error==4)
    {
      Serial.print("Unknown error at address 0x");
      if (address<16)
        Serial.print("0");
      Serial.println(address,HEX);
    }    
  }
  if (nDevices == 0)
    Serial.println("No I2C devices found\n");
  else
    Serial.println("done\n");
 
  delay(5000);           // wait 5 seconds for next scan
  return nDevices;
}

void setup_i2c(int sda = I2C_SDA, int scl = I2C_SCL)
{
  int found = 0;
  
  Wire.begin();
 
  Serial.println("\nI2C Scanner");
  while ( (found = search_i2c() ) == 0 ) 
  {
      Serial.println("I2C device not found \n");
      delay(5000);
  }
}

void mcp_reset(int first = 0 , int num = 16)
{
  // for ( int i = 0 ; i < 16 ; i++ )
  // for ( int i = 8 ; i < 12 ; i++ )
  for( int i = 0 ; i < num ; i++ )
  {
    mcp.pinMode(first + i, INPUT);
  }
}

void mcp_out(int first = 0, int num = 16)
{
  for ( int i = first ; i < num ; i++ )
  {
    mcp.pinMode(first + i, OUTPUT);
  }  
}

 
void setup_mcp()
{
  mcp.begin( 7 );

  mcp_reset();
}


void loop_mcp0() {

    Serial.println("lopp_mcp0");

 for ( int i = 0 ; i < 16 ; i++ )
  {
    mcp.pinMode(i, OUTPUT);
  }
    
    for(int i=0; i<16; i++)
    {
        mcp.digitalWrite(i, i%2 == 0 ? LOW : HIGH );
        delay(1000);
    }
    

    for(int i=0; i<16; i++)
    {
        mcp.digitalWrite(i, i%2 == 0 ? HIGH : LOW );
        delay(1000);
    }
}

 

void mcp_set_pins(int low, int high)
{
    mcp.pinMode(low, OUTPUT);
    mcp.pinMode(high, OUTPUT);
    mcp.digitalWrite(low, LOW);
    mcp.digitalWrite(high, HIGH);

    // delay(1);
}

void mcp_stage( int pin0, int pin1, int duree = 1000 )
{
  /*
  Serial.print("mcp_stage(");
  Serial.print(pin0);
  Serial.print(",");
  Serial.print(pin1);
  Serial.println(")");
 */ 

  // mcp_reset(8, 4);
  // mcp_set_pins( pin0, pin1);

  mcp.pinMode(pin1, OUTPUT);
  mcp.digitalWrite(pin1, HIGH);

  if ( duree ) delay(duree);

  mcp.pinMode(pin1, INPUT);
}

void loop_mcp(int first)
{
  int nextpin[4] = { 1, 2, 3, 0};
  
  for ( int i = 0 ; i < 4 ; i++)
  {    

    mcp.pinMode(i + first, OUTPUT);
    mcp.digitalWrite(i + first, LOW);
    
    for ( int j = 0 ; j < 4 ; j++)
    {
        if( i != nextpin[j] )  
        {
          mcp_stage(i + first, nextpin[j] + first, counter);
        }
        else
        {
          if( counter ) delay(counter);
        }

    }

     mcp.pinMode(i + first, INPUT);

    // this seem needed to wait a little (10 ms) if we don't want a regular crash
    delay(1);

   
  }
}

void setup()
{
  Serial.begin(115200);
  setup_i2c();
  setup_mcp();
  setup_timer();
}

void loop() 
{
  // search_i2c();
  loop_mcp( 8 );
  frames++;
}

The output is this :

I2C Scanner
Scanning ...
I2C device found at address 0x27  :)
done

1th second (22 fps) [counter=0]
2th second (23 fps) [counter=0]
3th second (23 fps) [counter=0]
4th second (22 fps) [counter=0]
5th second (23 fps) [counter=0]
6th second (23 fps) [counter=0]
7th second (23 fps) [counter=0]
8th second (22 fps) [counter=0]
9th second (23 fps) [counter=0]
10th second (23 fps) [counter=0]
...

You may be better off without the mcp library.

I wonder how you want to drive a LED matrix in Charlieplex without additional drivers? The expander supports only a fraction of AVR output pin currents.

It's only a very little LED matrix of 4 levels of RGB leds
(a charlieplexed tower, so this need only 4 wires for to can sequentially handle 4 levels of RGB leds = 12 monochromatics leds)
[where only one red, green or blue component of only one RGB led is active at the same time, so this consume ONLY about 20 mA per tower]

=> I test to make something functional without to use of mcp23017.h and mcp23017.c files, so directly to command the MCP23017 by the Wire API, because the code source of the mcp23017.c file don't seem optimized, a lot of things are very very redondants into it ... :frowning: :frowning:
(more than 32000 fps without mcp.pinMode() and mcp.digitalWrite() calls vs only 24 fps with them ...)
[of course, no led is lighted without them but I have only test this for to see the difference => I have clearly see the difference, this is 32000/24 = more than 1000x more speed without them ...]

I plane to use 4 towers in // and don't want to use ALLS possibles OUTPUT pins of the ESP32, because 4 towers of 4 pins = 16 distincts INPUT/OUTPUT pins to handle in // :frowning: :frowning:
=> it's why I search to minimize this with the use of a MCP23017 that need only 2 wires for to handle the I2C data transmission but can handle 16 news INPUT/OUTPUT pins on return, so a gain of 8x less of wires for to command alls 4 towers in // from the ESP32 point of view ...
(+ one wire for the +5V and another for the GND but I think to aliment the MCP2307 by an external power, not the power given by the ESP32)

This is very close to being a cross-post, which is against forum rules. @Yannoo could at the least have posted a link to their other topic, which explains about the charlieplexed towers.

@Yannoo is using an esp32, not an AVR, so the source/sink current of the MCP pins is an improvement over the esp pin which is only around 12mA. Also the esp outputs only 3.3V, so not enough to drive green and especially blue LEDs (some voltage required for the series resistors). So I recommended the MCP and recommended to drive it at 5V with a level shifter between it and the esp.

Yes, looking at your code and knowing that library, this is not a surprise. But I am sure that with some optimisation, a significantly higher rate can be achieved.

First, try adding is to setup_i2c():

Wire.setClock(400000);

I am not certain this will work correctly with the i²c level shifter module, assuming you have used the commonly available module from eBay etc. There are better level shifters which should be able to achieve higher speeds if the common modules cannot.

If the above does work, you could try

Wire.setClock(1000000);

I use an ESP32 shield like this for the 3.3V to 5V conversion and this work now very well on 5V :slight_smile:
(any problem with green or blue colors, alls combinaisons work)

ESP32 devellopement board

What makes you think this board converts the 3.3V pin signals to 5V? I see nothing on that page that describes that feature.

Remember what I said in your other topic: if you power the MCP module with 5V and the pull-up resistors on the SSL/SDA pins are connected to 5V, the esp could be damaged.

Hi,
Can you please post a circuit diagram of your project, including power supplies, use a pen(cil) and paper and show an image of your project , as well, would be fine.
Please do not use a Fritzy picture

Thanks.. Tom... :smiley: :+1: :coffee: :australia:

@paulRB

A lot of thanks , this work now at 56 fps after the simple addition of a Wire.setClock(400000) call at the end of my setup_i2c() ... and work perfectly at 72 fps with Wire.setClock(1000000) :slight_smile: :slight_smile:

=> alls 4 levels on my RGB leds tower are now lighted with a little attenued white color, you have quasi totaly resolved my problem :slight_smile: :slight_smile:

But my MCP23017 don't seem to want work on 3.3V, only on 5V :frowning: :frowning:
(I think to test a very short time with 2 towers instead only one, but think to add before a transistor array for to can isolate the +5V of towers from the 3.3V / 5V of the ESP32 shield)
[with a lot of chance, this can perhaps give me the possibilty to aliment the MCP23017 on 3.3V instead 5V ... but on alls test that I have make without any tower, it don"t seem to work on 3.3V, only on 5V]

Correct, these will be an inefficient way to set the pin modes and write to the pins of the MCP. The Arduino will communicate to the MCP chip to change each pin individually.

The library also has functions
mcp.portMode() and mcp.writePort() which will be much more efficient. With these functions, the Arduino will only need to communicate with the MCP once to set the pin modes or to write to 8 pins at the same time.

@PaulRB

Thanks, this is exactly what I want
=> I test with the very more efficient globals mcp.portMode() and mcp.writePort() calls instead individuals mcp.pinMode() and mcp.digitalWrite() calls
(but this need certainly a relative big chunk of changes for to make the transition => I'm come back here after to have something that work using the very more efficient mcp.portMode() and mcp.writePort() calls)

This is an error, the parameter should be 0x27.

This work exactly the same with mcp.begin(7) or mcp.begin(0x27) ...

This don't work with no argument to mcp.begin() and I have used the value 7 because finded it on an example and that this have worked with it :smiley: :smiley:

But 0x27 is of course the good value to use because this is the value returned by the search_i2c() call :smiley: :smiley:

That's a bad example because I2C addresses below 8 are reserved for special purpose.

Where that function does not return any address :frowning:

It's exact, the search_i2c() call "only" return the number of founded i2c devices

But the search_i2c() call display too the address of each i2c device found :

    if (error == 0)
    {
      Serial.print("I2C device found at address 0x");
      if (address<16)
        Serial.print("0");
      Serial.print(address,HEX);
      Serial.println("  :)");
 
      nDevices++;
    }

And the search_i2c() call display this
(but only when I reset the ESP32 => I don't understand why)

Scanning ...
I2C device found at address 0x27  :)
done