64 I/O Expander Shield for max 1024 I/O

leen15:
Can you check? It should be ok for me.

Hard to say... How does it compare to the parameters you used for laying it out?

One thing I notice is that they say 1:5 for silkscreen weight, which would be 20%. Your silkscreen looks a lot lighter than that. They also say a minimum height of 0.8 mm: it's hard to say, but some of your call outs, like capacitor numbers and address, bits seem like they may be smaller than that?

ShapeShifter:
Hard to say... How does it compare to the parameters you used for laying it out?

One thing I notice is that they say 1:5 for silkscreen weight, which would be 20%. Your silkscreen looks a lot lighter than that. They also say a minimum height of 0.8 mm: it's hard to say, but some of your call outs, like capacitor numbers and address, bits seem like they may be smaller than that?

Capacitor numbers and address are 0.8128 mm so it should be ok.

I already made some boards with ratio 10% and It's ok (see the last in attached image) so i hope that they accept my pcb files! ;D

Quite legible. Are they from the same board house? If not, the same rules may not apply.

ShapeShifter:
Quote legible. Are they from the same board house? If not, the same rules may not apply.

Yes, it's the same (3pcb).
It's the cheaper that i found in china with selection of multiple colors for solder mask.
Do you know others cheaper? :slight_smile:

It's been a while since I priced out sources. Their prices look reasonable -- although not for me, the shipping to the States is almost as much the boards! I hope your shipping is less expensive.

Yes, it's about 40$ for shipping, but there is no comparison with european board houses in total cost. 50 boards it's 80-90$ shipped to Italy!

Ok, last possibility for review on schematic and layout. :grin:

Tomorrow i'll send it for build! :smiling_imp:

PS: Difference between this version and the previous is that I removed power supply traces chip to chip, and took it directly from every cap near chip. :slight_smile:

leen15:
PS: Difference between this version and the previous is that I removed power supply traces chip to chip, and took it directly from every cap near chip. :slight_smile:

Good catch! That's the way it should be.

A good tip at this point is to use the "Show" command to highlight critical traces on the board, and see if they look reasonable without a lot of unnecessary rambling. On this board, I think that would include power, ground, and SCL/SDA.

ShapeShifter:
Good catch! That's the way it should be.

A good tip at this point is to use the "Show" command to highlight critical traces on the board, and see if they look reasonable without a lot of unnecessary rambling. On this board, I think that would include power, ground, and SCL/SDA.

Yes, this is what I'll do this afternoon before send for build! :slight_smile:

almost 2 weeks later last post, pcb finally arrived! :smiling_imp:

In a few days should arrive all components :grin:

Nice. I hope it works as well as it looks!

I hope too, or I will have 50 tiles to use for decorate a wall :stuck_out_tongue_closed_eyes:

50!?!?! :o

I'm not so brave... I would've placed a minimum order, proven the design, and then place the big order...

ShapeShifter:
50!?!?! :o

I'm not so brave... I would've placed a minimum order, proven the design, and then place the big order...

Most of price is shipping and tax fees.
So buy 5 or 50 it's maybe 10-15$ of difference on a total of 80 :wink:

And Why not, maybe i could sell them? :grinning:

Hey ShapeShifter, you are a wizard with code right? :grin:

I'm trying to modify centipede library (http://macetech.com/Centipede.zip) for use it with the new shield.

The idea is that normal user don't need to know what pin of what chip of what board need to active, but simple give in init:

  • number of attached shields
  • how many chips are used as OUTPUT
  • how many chips are used as INPUT
  • default value for OUT pins (es. sainsmart works with negative value)

And library automatically set all chips.
Then when read or write a pin, it should automatically detect what shield and what chip have to enable.

So, the initialize function should be like:

void SmartDomo_Exp::initialize(int countShields, int countOUT_MCP, int countIN_MCP, int defaultOutPortValue)
{
  // inizialize address pins
  pinMode(addrPIN0, OUTPUT);
  pinMode(addrPIN1, OUTPUT);
  pinMode(addrPIN2, OUTPUT);
  pinMode(addrPIN3, OUTPUT);

  // inizialize shields chips
  maxchip_forOUT = countOUT_MCP;
  maxchip_forIN = countOUT_MCP + countIN_MCP;
  int currentchip = 0;
  int i = 0;
  while (i < countShields) // cycle for set default values for every chip on connected shields
  {
    SelectShield(i);

    for (int j = 0; j < 4; j++) {

      if (currentchip < maxchip_forOUT)
      {
        portMode(i, 0b0000000000000000); // set all pins on chip to output
        portWrite(i, defaultOutPortValue);   // write default value for output
      }
      else if(currentchip >= maxchip_forOUT & currentchip < maxchip_forIN)
      {
        portMode(i, 0b1111111111111111); // set all pins on chip to input
      }
      else
      {
        // default configuration for not set chips
        CSDataArray[0] = 255;
        CSDataArray[1] = 255;

        WriteRegisters(j, 0x00, 2);

        CSDataArray[0] = 0;
        CSDataArray[1] = 0;

        for (int k = 2; k < 0x15; k+=2) {
          WriteRegisters(j, k, 2);
        }
      }

      currentchip++;

    }
    i++;
  }
}

(i kept in else the same code of centipede)

But i don't undestand how to detect pin, chip and shield when call for example "digialWrite" function, that in centipede library is:

void SmartDomo_Exp::digitalWrite(int pin, int level) {
  
  int port = pin >> 4;
  int subregister = (pin & 8) >> 3;

  int regpin = pin - ((port << 1) + subregister)*8;

  WriteRegisterPin(port, regpin, 0x12 + subregister, level);
  
}

How can i convert it for detect right shield (0-15) and right chip of the shield (0-3)?

The centipede function, passing a pin value of "120", detect that it need to send the command to chip 8, GPIO-B and pin 0, but i don't undestand how.. i'm not so expert with binary numbers. :stuck_out_tongue:

In my case i should detect that is shield 1, chip 3, GPIO-B, pin 0. How can i do it? :stuck_out_tongue_closed_eyes:

I attached WIP library.

PS: For semplicity with develop of library and use it, I assume that user have to use all OUTPUT chips consecutively and than all INPUT chips.

SmartDomo_Exp.cpp (5.5 KB)

SmartDomo_Exp.h (1.16 KB)

leen15:
Hey ShapeShifter, you are a wizard with code right? :grin:

Code? What is this code you are talking about? I don't understand... :smiling_imp:

How can i convert it for detect right shield (0-15) and right chip of the shield (0-3)?

The centipede function, passing a pin value of "120", detect that it need to send the command to chip 8, GPIO-B and pin 0, but i don't undestand how.. i'm not so expert with binary numbers. :stuck_out_tongue:

In my case i should detect that is shield 1, chip 3, GPIO-B, pin 0. How can i do it? :stuck_out_tongue_closed_eyes:

First off, number everything with zero based numbers, it will make the math MUCH easier. You have up to 1024 logical pins, numbered 0 through 1023:

  • 8 pins per port, 0 - 7
  • 2 ports per chip, 0=A, 1=B
  • 4 chips per board, 0 - 3
  • up to 16 boards, 0 - 15

Normally, you would use a lot of division and modulo (remainder after division) arithmetic to convert a logical pin number to a physical board/chip/pin address:

  • Take the logical pin number modulo 8 to get the physical pin number. Logical pin 120 becomes physical pin 0
  • Divide the logical pin number by 8 (the number of pins per port) then take modulo 2 to get the port number. Logical pin 120 becomes port 1 (B)
  • Divide the logical pin number by 16 (the number of pins per chip) then take modulo 4 to get the chip number. Logical pin 120 becomes chip 2
  • Divide the logical pin number by 64 (the number of pins per board) and the integer portion is your board number. Logical pin 120 becomes board 1

But in this case, since all of the factors are powers of 2, it becomes much easier. The lowest 3 bits (8 values) are the pin number, the next bit (two values) is the port number, the next 2 bits (4 values) are the chip number, and the remaining 4 bits (16 values) are the board number. That's a total of 3 + 1 + 2 + 4 = 10 bits, which gives you your logical pin number range of 0 to 1023. Using B for board, C for chip, P for port, and p for physical pin, the 10 bits of a logical pin number become:

   15 14 13 12 11 10  9  8  7  6  5  4  3  2  1  0
    0  0  0  0  0  0  B  B  B  B  C  C  P  p  p  p

You can extract those bits by shifting and masking. (If you're not familiar with them, look up the C or C++ shift and bitwise AND operators.)

unsigned logicalPin;

// The logical pin is already in the LSB position, so just mask off the rest of it.
byte pin = (logicalPin & 0x07);

// Shift the port number to the LSB, then mask off the other bits
byte port = ((logicalPin >> 3) & 0x01);

// Shift the chip number to the LSB, then mask off the other bits
byte chip = ((logicalPin >> 4) & 0x03);

// Shift the board number to the LSB, then mask off the other bits
byte board = ((logicalPin >> 6) & 0x0f);

Having everything zero based like this makes it easy to use these values as indexes into arrays to act as lookup tables. For example, you could create a constant 4 element array of I2C addresses, and then index it by the chip number to get the I2C address of the chip.

// Define a constant lookup table to convert chip number 0-3 to an I2C address
const byte I2Caddress[] = (36, 37, 38, 39};

byte address = I2Caddress[chip];

However, if you are going to use a switch statement to convert the chip number to an I2C address, then you really don't have to spend the execution time shifting the address into place, just mask it off and use the appropriate case alternative constants:

// Mask off and decode the chip number bits
switch (logicalPin & 0x30)
{
  case 0x00:  address = 36;  break;
  case 0x10:  address = 37;  break;
  case 0x20:  address = 38;  break;
  case 0x30:  address = 39;  break;
}

You could do the same thing with the port number to translate that into a device register address, as well as translate the board number into the digital I/O.

// Mask off and convert the board number bits
switch (logicalPin & 0x03c0)
case
{
  case 0x0000: digitalWrite(a0Pin, low);  digitalWrite(a1pin, low);  digitalWrite(a2pin, low);  digitalWrite(a3pin, low);  break;
  case 0x0040: digitalWrite(a0Pin, low);  digitalWrite(a1pin, low);  digitalWrite(a2pin, low);  digitalWrite(a3pin, high); break;
  case 0x0080: digitalWrite(a0Pin, low);  digitalWrite(a1pin, low);  digitalWrite(a2pin, high); digitalWrite(a3pin, low);  break;
   ... and so on ...
  case 0x03c0: digitalWrite(a0Pin, high); digitalWrite(a1pin, high); digitalWrite(a2pin, high); digitalWrite(a3pin, high); break;

But it would be tedious to write out 16 case alternatives and the appropriate four digitalWrite statements for each one. You could create an array of 16 elements, where each element is a structure for holding the values of the four output bits, but there is an even simpler way. Since the four bits that identify a board are a straight binary sequence just like the four bits that are output to select the board, the whole board selection process can come down to:

// Decode the logical pin number to address bits and set the outputs
digitalWrite(a0pin, (logicalPin & 0x0040));
digitalWrite(a1pin, (logicalPin & 0x0080));
digitalWrite(a2pin, (logicalPin & 0x0100));
digitalWrite(a3pin, (logicalPin & 0x0200));

There is a lot here. If there is anything you don't understand, I would spend some time studying it, researching it, and asking questions until the whole thing makes sense. Once you understand it all, you can decide which is the best way to decode the values, and then you can decide how to write the rest of the library.

After your usually great explain, i edited library with some improvements. :stuck_out_tongue:

There are some things that need to have shield running for understanding if can work, and other things that is under wip (like digitalReadAll).

I added 2 different read/write function, because developing my home automation system i notice that it's more simple to control what pin of what board, instead that use it from pin X to pin Y on different shields (i need to know where every pin of every board have to be connected!).

Take a look and let me know! :smiley:

SmartDomo_Exp.cpp (8.25 KB)

SmartDomo_Exp.h (1.42 KB)

some comments on the lib

the lines do not need to be in the cpp file

#if defined(ARDUINO) && ARDUINO >= 100
#include "Arduino.h"
#else
#include "WProgram.h"
#endif

a lot of variables are int now that is a 16 bit signed int. If you look carefully you can replace a lot of them with uint8_t (8 bit unsigned int). That reduces the footprint of the library and prevents values below 0 (e.g. negative pinnumbers) e.g.
uint8_t addrPIN0 = 5;
uint8_t addrPIN1 = 6;

also in loops using 16 bits ints where a 8 bit int would be sufficient reduces footprint e.g.
for (uint8_t j = 0; j < 4; j++) {

idem in interfaces of functions

the return value of Wire.endTransmission(); should return 0 for success.
You can check that in the library and forward this return code the calling functions (user sketch) so it can detect errors.

int SmartDomo_Exp::WriteRegisters(int chip, int startregister, int quantity) {
  Wire.beginTransmission(CSAddress + chip);
  wiresend(startregister);
  for (int i = 0; i < quantity; i++) {
    wiresend(CSDataArray[i]);
  }

  return Wire.endTransmission();
}

The functions that use WriteRegister can use/forward the return value even further/

int SmartDomo_Exp::WriteRegisterPin(int chip, int regpin, int gpio_register, int level) {

  ReadRegisters(chip, gpio_register, 1); 
  
  if (level == 0) {
    CSDataArray[0] &= ~(1 << regpin);
  }
  else {
    CSDataArray[0] |= (1 << regpin);
  }
  
  return WriteRegisters(chip, gpio_register, 1);  // <<<<<<<
  
}

my 2 cents

Thanks a lot for your suggestions!

This is my first library and every help is very appreciated! ;D

Updated library with robtillaart suggestions and with completed functions digitalReadAll and digitalReadExpAll. :slight_smile:

I'm waiting for components for assembly board and compile+test library. I hope they'll arrive tomorrow! :grin:

SmartDomo_Exp.cpp (9.34 KB)

SmartDomo_Exp.h (1.59 KB)