SPCoast
Railroading on the Southern Pacific Coast

IOShield

From SPCoast

Jump to: navigation, search

This is a work in progress

In particular, the board design is incomplete because of unanswered questions.

Contents

[edit] Introduction

A simple I2C based 32-pin Arduino I/O Shield with monitoring LEDs on each pin (Active-LOW inputs, LED is on when pin is grounded, writing a "1" to the port pulls it to ground). Up to 4 boards can be used together, for a total of 128 i/o points, though a larger (1A @ 9v) external power supply could be used to power the I2C IO32 expander/shield stack if things are maxed out.

Note: Need to understand 4-pin Grove buckle system, but we need 5 pins to handle both I2C and /INT feedback...


IO32shield v1.5 pcb

[edit] Features

  • Chain-able/stackable (up to 4 nodes, 128 I/O points)

Shield or Daisy chained configurations

[edit] Cautions

The total current draw from "Vin" can become excessive if many LEDs are connected and driven - 256 LEDs * 5mA is 1.25A!

Each IOShield has its own onboard regulators for IO Port power and onboard LED feedback power which are fed from the Vin Arduino shield pin or the onboard power jack.

 Note: Need to protect power jack from injecting wrong polarity!

[edit] Schematic

IO32shield 1.5 schematic


[edit] Specification

This shield is based on the 8574 I2C IO Expander and the 74LS244 buffer/driver chip connected to a bank of LEDs that show the status of the I/O lines in real time. With 4 sets of chips on board, it provides a latched set of 32 IO points, with each point being software selectable to be either an input or an output. The individual points are reasonable protected from the environment, and can drive or sink 10 (???) mA each. By using active low inputs and outputs, the effects of environmental noise are reduced - remote sensors only need to ground an I/O point to register activity.


[edit] Communication Protocol

Simple I2C Reads and Writes:

int I2Cextender::read8(int i2caddr) {
    int _data = -1;
    Wire.requestFrom(i2caddr, 1);
    if(Wire.available()) {
      _data = Wire.receive();
    }
    return _data;
}

void I2Cextender::write8(int i2caddr, int data)
{ 
    Wire.beginTransmission(i2caddr);
    Wire.send(0xff & data);
    Wire.endTransmission();  
}

[edit] Hardware Installation

[edit] Programming

Library header:

/*
  I2Cexpander.h - Library for flashing Morse code.
  Created by John Plocher
  Released into the public domain.
*/
#ifndef I2Cextender_h
#define I2Cextender_h

#include "WProgram.h"

class I2Cextender {
public:
    I2Cextender(void);
    void init(int address, int type, int config);
    int get(void);
    void put(int data);
    int getSize(void);
    
    enum {
      B_UNKNOWN =  0,
      B8        =  8,
      B16       = 16,
      T_UNKNOWN =  0
    };
    enum {
      PCA9555   =  1,
      MCP23016  =  2,
      PCF8574   =  3,
      PCF8574A  =  4
    };
    enum {
      REGISTER_INPUT  =  0,
      REGISTER_OUTPUT =  2,
      REGISTER_INVERT =  4,
      REGISTER_CONFIG =  6
    };
    enum {
      base9555     = 0x20,
      base23016    = 0x20,
      base8574A    = 0x38,
      base8574     = 0x20
    };
    
 private:
    int _size;
    int _chip;
    int _address;
    int _config;
    
    void input8(int i2caddr, int dir);
    void input16(int i2caddr, int dir);
    void write8(int i2caddr, int data);
    int  read8(int i2caddr);
    void write16(int i2caddr, int data);
    int  read16(int i2caddr);
};

#endif // I2Cextender_h


I2C Library:

#include "WProgram.h"
#include "Wire.h"
#include "I2Cextender.h"


I2Cextender::I2Cextender() {
    _address = -1;
    _chip = -1;
    _config = -1;
}

void I2Cextender::init(int address, int chip, int config) {
    _address = address;
    _chip = chip;
    _config = config;
    switch (_chip) {
    case I2Cextender::PCA9555:
        _size = B16;
        input16(base9555 + _address, _config);
        break;
    case I2Cextender::MCP23016:
        _size = B16;
        input16(base23016 + _address, _config);
        break;
    case I2Cextender::PCF8574A:
        _size = B8;
        input8(base8574A + _address, _config);
        break;
    case I2Cextender::PCF8574:
        _size = B8;
        input8(base8574  + _address, _config);
        break;
    default:
        _size=0;
    }    
}    

int I2Cextender::getSize() {
    return _size;
}

int I2Cextender::get() {
    switch (_chip) {
    case I2Cextender::PCA9555:        return read16(base9555 + _address);
    case I2Cextender::MCP23016:       return read16(base23016 + _address);
    case I2Cextender::PCF8574A:       return read8(base8574A + _address);
    case I2Cextender::PCF8574:        return read8(base8574  + _address);
    default:                          return -1;
    }   
}

void I2Cextender::put(int data) {
    switch (_chip) {
    case I2Cextender::PCA9555:        write16(base9555 + _address, data); break;
    case I2Cextender::MCP23016:       write16(base23016 + _address, data);break;
    case I2Cextender::PCF8574A:       write8(base8574A + _address, data | _config);break;
    case I2Cextender::PCF8574:        write8(base8574  + _address, data | _config);break;
    default:                          break;
    }   
}

int I2Cextender::read8(int i2caddr) {
    int _data = -1;
    Wire.requestFrom(i2caddr, 1);
    if(Wire.available()) {
      _data = Wire.receive();
    }
    return _data;
}

void I2Cextender::write8(int i2caddr, int data)
{ 
    Wire.beginTransmission(i2caddr);
    Wire.send(0xff & data);
    Wire.endTransmission();  
}

void I2Cextender::input8(int i2caddr, int dir) {
}

void I2Cextender::input16(int i2caddr, int dir) {
  Wire.beginTransmission(i2caddr);
  Wire.send(REGISTER_CONFIG);
  Wire.send(0xff & dir);  // low byte
  Wire.send(dir >> 8);    // high byte
  Wire.endTransmission();  
}

void I2Cextender::write16(int i2caddr, int data) {
    Wire.beginTransmission(i2caddr);
    Wire.send(REGISTER_OUTPUT);
    Wire.send(0xff & data);  //  low byte
    Wire.send(data >> 8);    //  high byte
    Wire.endTransmission();  
}

int I2Cextender::read16(int i2caddr) {
  int data = 0;
  Wire.beginTransmission(i2caddr);
  Wire.send(REGISTER_INPUT);
  Wire.endTransmission();  
  // Wire.beginTransmission(i2caddr);
  Wire.requestFrom(i2caddr, 2);
  if(Wire.available()) {
    data = Wire.receive();
  }
  if(Wire.available()) {
    data |= (Wire.receive() << 8);
  }
  // Wire.endTransmission();  
  return data;
}

</syntaxhighlight>

[edit] Example

Demo Program:

<syntaxhighlight lang="Arduino">
/*
 * Demo of I2C Interface Library usage.
 *
 * This version simply walks thru the boards/ports and flashes the lights
 */
 
#include "Wire.h"
#include <I2Cextender.h>
 
#define INITVAL 0x0000  // Default to all outputs
#define NUMBOARDS 2    // How many boards (1..4)
#if ((NUMBOARDS < 1) || (NUMBOARDS > 4)) 
#error NUMBOARDS must be a value from 1 to 4
#endif
#define NUMPORTS (NUMBOARDS * 4)
 
I2Cextender m[NUMPORTS];
int i[4];
void setup()
{
    randomSeed(343446);  // Note that random() is a ferocious consumer of resources - use a lookup table for psuedorandom stuff is performance matters.
 
    for (int x = 0; x < NUMPORTS; x++) {
        m[x] = I2Cextender();
        i[x] = random(0,0xFFFF);
    }
   
    Wire.begin();
 
    switch (NUMBOARDS) {
        case 4:  m[15].init(7, I2Cextender::PCF8574A, INITVAL);
                 m[14].init(6, I2Cextender::PCF8574A, INITVAL);
                 m[13].init(7, I2Cextender::PCF8574,  INITVAL);
                 m[12].init(6, I2Cextender::PCF8574,  INITVAL);
         
        case 3:  m[11].init(5, I2Cextender::PCF8574A, INITVAL);
                 m[10].init(4, I2Cextender::PCF8574A, INITVAL);
                 m[ 9].init(5, I2Cextender::PCF8574,  INITVAL);
                 m[ 8].init(4, I2Cextender::PCF8574,  INITVAL);
         
        case 2:  m[ 7].init(3, I2Cextender::PCF8574A, INITVAL);
                 m[ 6].init(2, I2Cextender::PCF8574A, INITVAL);
                 m[ 5].init(3, I2Cextender::PCF8574,  INITVAL);
                 m[ 4].init(2, I2Cextender::PCF8574,  INITVAL);
         
        case 1:  m[ 3].init(1, I2Cextender::PCF8574A, INITVAL);
                 m[ 2].init(0, I2Cextender::PCF8574A, INITVAL);
                 m[ 1].init(1, I2Cextender::PCF8574,  INITVAL);
                 m[ 0].init(0, I2Cextender::PCF8574,  INITVAL);
    }
} 
 
void loop() {
  on();
  delay(1000);
  off();
  cylon(8);
  off();
  walk(8);
  off();
  rand(64);
  off();
  delay(500);
}
 
void walk(int iterations) {
    for (int iter = 0; iter <= iterations; iter++) {
      for (int x = 0; x < NUMPORTS; x++) {
          for (int b = 0; b <= 7; b++) {
              m[x].put( ~(1 << b));
              delay(50);
          }
          m[x].put( 0xFFFF);
      }
    }
}
 
void cylon(int iterations) {
    for (int iter = 0; iter <= iterations; iter++) {
        for (int b = 0; b <= 7; b++) {
            for (int x = 0; x < NUMPORTS; x++) {
                m[x].put( ~(1 << b));
            }
            delay(50);
        }
        for (int b = 7; b >= 0; b--) {
            for (int x = 0; x < NUMPORTS; x++) {
                m[x].put( ~(1 << b));
            }
            delay(50);
        }
    }
}
 
void onoff(int iterations) {
    for (int iter = 0; iter <= iterations; iter++) {
        for (int x = 0; x < NUMPORTS; x++) {
            m[x].put(0);
        }
        delay(500);
        for (int x = 0; x < NUMPORTS; x++) {
            m[x].put(0xFFFF);
        }
        delay(500);
    }
}
 
void off() {
        for (int x = 0; x < NUMPORTS; x++) {
            m[x].put(0xFFFF);
        }
}
 
void on() {
        for (int x = 0; x < NUMPORTS; x++) {
            m[x].put(0x0000);
        }
}
 
void rand(int iterations) {
    for (int iter = 0; iter <= iterations; iter++) {
      int r = random(0,8);
      for (int x = 0; x < NUMPORTS; x++) {
          if (bitRead(i[x], r)) bitClear(i[x], r); else bitSet(i[x], r);
          m[x].put(i[x]);
      }
       
      delay(random(80,200));
    }
}

[edit] Bill of Materials (BOM) /parts list

(These are through hole parts, need to update with SMD parts!)

2x) NXP 771-PCF85AT3512 I/O expander
2x) NXP 771-PCF8574TD-T I/O expander
8x) TDK 810-C2012Y5V1H104Z-1 Multi-layer ceramic capacitors SMD 0805 0.10uF 50volts Y5V +80-20%
2x) TDK 810-C2012Y5V1C105Z-2 Multi-layer ceramic capacitors SMD 0805 1.0uF 16volts Y5V +80-20%
8x) FCI 649-68898-001LF Telecom & Ethernet Connectors HORIZONTAL 6P
2x) Bourns 652-CR0805JW-182ELF Thick Film Resistors - SMD 1.8K 5%
9x) Bourns 652-4605X-1LF-1.2K Resistor networks and arrays 5pins 1.2Kohms Bussed
4x) TI 595-SN74LS244DWR Buffer and Line Driver Tri-State Octal
1x) MChip 579-MCP1703-5002E/DB Low Dropout (LDO) regulator Low Iq 250mA LDO Vin 16V maxVout 5.0V
1x) Kingbrt 604-WP914CK4YDT LED circuit board indicators Yellow Diffused 588nm 4mcd
8x) Kingbrt 604-W934SB/4GD LED circuit board indicators T-1 QUAD-LEVEL GRN


[edit] Version Tracker

Revision Descriptions Release
v1.0 Initial prototype, used a non-Arduino form factor with a single IO expander per board, proved to be not high enough density date
v1.1 Didn't validate package sizes -vs- available parts, boards were unusable date
v1.2 Missing connection between 8574 and 74ls244, jumper pins impossible to reach when boards stacked date
v1.3 Experimented with daisy chained/ribbon cable board connections with a simple small shield. date
v1.4 Combo board, stackable and chainable, LED display in chunks. date
v1.5 Combo board, stackable and chainable, LED display in 8-bit rows. date

[edit] Original Idea

This board was originally produced to help me build control panels for my model railroad - I needed to connect many different buttons, switches and lights to an Arduino, and also connect the Arduino to a communications bus (Ethernet, CAN or Loconet), and I couldn't find anything that had both high IO point density AND visual feedback.

[edit] Resources

  • datasheets


[edit] See Also

Other related products and resources.

[edit] Licensing

This documentation is licensed under the Creative Commons Attribution-ShareAlike License 3.0 Source code and libraries are licensed under GPL/LGPL, see source code files for details. The Eagle CAD files are licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License