Multi I/O Library (16 Inputs and Outputs)

Dear All, I just created this free library that allows you to have 16 inputs and/or outputs with 3 I/O pins and a few extra cheap chips.

Here's the download URL:
http://www.wusik.com/download/arduino/Multi_IO.zip

And here's the code: (header, there's no cpp file)

/*
 
 www.Wusik.com - Created by WilliamK @ Wusik Dot Com (c) 2010
 
*/

#ifndef MULTIIO_h
#define MULTIIO_h

#include <inttypes.h>
#include "HardwareSerial.h"

// 16 Inputs with only 3 pins on the Arduino using the following chip: 74HC165N //
// http://www.sparkfun.com/products/9519 //
#define BTploadPin 25      // Buttons Input C165N - Connects to Parallel load pin the 165
#define BTdataPin 27      // Buttons Input C165N - Connects to the Q7 pin the 165
#define BTclockPin 26      // Buttons Input C165N - Connects to the Clock pin the 165

// 16 Outputs with only 3 pins on the Arduino using the following chip: 74HC595 //
// http://www.sparkfun.com/products/733 //
#define LEDlatchPin 22      // LEDs Output C595N //
#define LEDclockPin 24      // LEDs Output C595N //
#define LEDdataPin 23      // LEDs Output C595N //

class Buttons
{
public:
    Buttons()
      {
            pinMode(BTploadPin, OUTPUT);
            pinMode(BTclockPin, OUTPUT);
            pinMode(BTdataPin, INPUT);
            digitalWrite(BTclockPin, LOW);
            digitalWrite(BTploadPin, HIGH);

            memset(Clicked,false,sizeof(Clicked));
            memset(prevHigh,false,sizeof(prevHigh));
      }

      bool Tick() // Returns True if something changed - check the Clicked[] variable and call Reset() after you do //
      {
            SomethingChanged = false;

            digitalWrite(BTploadPin, LOW);
            digitalWrite(BTploadPin, HIGH);
            for(int i = 0; i < 16; i++)
            {
                  tempButton = digitalRead(BTdataPin);
                  if (tempButton == LOW && prevHigh[15-i] == HIGH) { Clicked[15-i] = true; SomethingChanged = true; }
                  prevHigh[15-i] = tempButton;
                  digitalWrite(BTclockPin, HIGH);
                  digitalWrite(BTclockPin, LOW);
            }

            return SomethingChanged;
      }

      void Reset(void) { memset(Clicked,false,sizeof(Clicked)); }

      boolean Clicked[16];

private:
      boolean SomethingChanged;
      unsigned int tempButton;
      unsigned int prevHigh[16];
};

// ======================================================================================= //
class LEDs
{
public:
      LEDs()
      {
            pinMode(LEDlatchPin, OUTPUT);
            pinMode(LEDclockPin, OUTPUT);
            pinMode(LEDdataPin, OUTPUT);    

            Reset();
      }

      void Tick(void) // use the Value[] variable to turn outputs on and off //
      {
            digitalWrite(LEDlatchPin, LOW);
            digitalWrite(LEDdataPin, LOW);
            digitalWrite(LEDclockPin, LOW);
            for (int i=15; i>=0; i--)  
            {
              digitalWrite(LEDclockPin, LOW);
              if (Value[i]) digitalWrite(LEDdataPin, HIGH); else digitalWrite(LEDdataPin, LOW);
              digitalWrite(LEDclockPin, HIGH);
              digitalWrite(LEDdataPin, LOW);
            }
            digitalWrite(LEDclockPin, LOW);
            digitalWrite(LEDlatchPin, HIGH);
      }

      void Reset(void) { memset(Value,false,sizeof(Value)); }

      boolean Value[16];
};

#endif

Good to publish your code here, but some questions arise. It seems to me the lib is for the MEGA (based upon the defines) and unfortunately it has fixed pinnumbers. It would be great if I could define the three pins myself in a constructor.

Furthermore I think the names Buttons and Leds imply a purpose which doesn't need to be the case. iso a led I maybe want to connect relays or so. So a rename to something more generic would be appriciated.

I suggest to call the classes HC165N and HC595 and you could define the pinnumbers in the constructor, something like this:
(not tested)

class HC165N
{
public:
   HC165N(int load, int data, int clock)   // byte would be large enough
  {
    _load = load;  // copy to internal vars
    _data = data;
    _clock = clock;

    pinMode(_load, OUTPUT);
    pinMode(_clock, OUTPUT);
    pinMode(_data, INPUT);

    digitalWrite(_clock, LOW);
    digitalWrite(_load, HIGH);

    Reset();  // should also reset prevHigh array I think
    // memset(prevHigh, false, sizeof(prevHigh));
}

etc

If you can define the pins during initialization it becomes possible to have multiple HC165N objects and therfore even more pins!

Similar for the HC595.

But a very good start for a library and I hope you will take time to enhance it and maybe even write a playground article how the lib is used and how to connect the IC's etc.

Rob

Thanks Rob, great feedback. Yes, I will make the changes and upload it again later next week.

How do I start a playground article?

I guess I could use Fritzing for the images?

Wk

How do I start a playground article?

You need to ask for a separate account - you cannot use the forum account - but you may use the same name and password

I guess I could use Fritzing for the images?

Yes sure

And when redoing the lin consider split current version in a .cpp and a .h file

Ah, yes, cpp and h file in the right way. I was too lazy yesterday. :-[

Wk

Looks much better, well done!

I still don't understand why you are using BT (button?) and LED prefixes as they assume a usage that not allways is true. On the other hand as these are private I will never have to "see" them in my code.

Ah, doh! indeed, sorry, I will fix that. :wink: (I was a bit tired yesterday when I did those...)

Wk

Another thing I changed is int to byte, as it uses less RAM and has the same effect. :sunglasses:

int takes 2 bytes while byte takes 1 byte. (as the name suggests)

Wk

Could some Moderator remove the code from the first post or let me modify it? As I have a new code now that I think is nearly finished. :wink:

Wk

Here's the official page on our site:

http://www.wusik.com/ww/open-wusik/arduino/download-files

And here are the files.

C165.h

/*
 
      www.Wusik.com - Created by WilliamK @ Wusik Dot Com (c) 2010

      8 to 16 Inputs with only 3 pins on the Arduino using the following chip: 74HC165N
      http://www.sparkfun.com/products/9519

      Typical Usage:

            Setup:

                  C165 Inputs = C165(25,27,26);

                  C165(      Connects to Parallel load pin the 165, 
                              Connects to the Q7 pin the 165,
                              Connects to the Clock pin the 165);      

            Loop:

                  if (Inputs.Tick()) // Returns True if something changed //
                  {
                        for (int xc=0; xc<16; xc++)
                        {
                              if (Inputs.isOn(xc))
                              {
                                    // Do Something //
                              }
                        }
                  }

*/

#ifndef C165_h
#define C165_h

#define NumberOfChips 2

#include <inttypes.h>
#include "HardwareSerial.h"

class C165
{
public:
    C165(byte _loadPin, byte _dataPin, byte _clockPin);
      boolean Tick(); 
      void Reset(void);
      boolean isOn(byte Pos);

private:
      byte loadPin;
      byte dataPin;
      byte clockPin;

      boolean Changed;
      boolean On[NumberOfChips*8];
      byte tempInputs;
      byte prevHigh[NumberOfChips*8];
};

#endif

C165.cpp

/*
 
      www.Wusik.com - Created by WilliamK @ Wusik Dot Com (c) 2010
      Check the C165.h file for instructions
 
*/

#include "WConstants.h"
#include "C165.h"

// ------------------------------------------------------------------------------------------- //
C165::C165(byte _loadPin, byte _dataPin, byte _clockPin)
{
      Changed = false;

      loadPin = _loadPin;
      dataPin = _dataPin;
      clockPin = _clockPin;

      pinMode(loadPin, OUTPUT);
      pinMode(clockPin, OUTPUT);
      pinMode(dataPin, INPUT);
      digitalWrite(clockPin, LOW);
      digitalWrite(loadPin, HIGH);

      Reset();            
}

// ------------------------------------------------------------------------------------------- //
boolean C165::Tick()
{
      Changed = false;

      digitalWrite(loadPin, LOW);
      digitalWrite(loadPin, HIGH);
      for(int i = 0; i < (NumberOfChips*8); i++)
      {
            tempInputs = digitalRead(dataPin);
            if (tempInputs == LOW && prevHigh[((NumberOfChips*8)-1)-i] == HIGH) { On[((NumberOfChips*8)-1)-i] = true; Changed = true; }
            prevHigh[((NumberOfChips*8)-1)-i] = tempInputs;
            digitalWrite(clockPin, HIGH);
            digitalWrite(clockPin, LOW);
      }

      return Changed;
}

// ------------------------------------------------------------------------------------------- //
void C165::Reset() 
{ 
      memset(On,false,sizeof(On)); 
      memset(prevHigh,false,sizeof(prevHigh));
}

// ------------------------------------------------------------------------------------------- //
boolean C165::isOn(byte Pos)
{
      if (On[Pos])
      {
            On[Pos] = false;
            return true;
      }

      return false;
}

C595.h

/*
 
      www.Wusik.com - Created by WilliamK @ Wusik Dot Com (c) 2010

      8 to 16 Outputs with only 3 pins on the Arduino using the following chip: 74HC595
      http://www.sparkfun.com/products/733

      Typical Usage:

            Setup:

                  C595 Outputs = C595(22,24,23);

                  C595(Latch Pin, Clock Pin, Data Pin)

            Loop:

                  Outputs.setOutput(0, true);
                  Outputs.setOutput(1, true);
                  Outputs.setOutput(2, false);
                  ...
                  Outputs.setOutput(15, false);
                  
                  Outputs.Tick();

*/

#ifndef C595_h
#define C595_h

#define NumberOfChips 2

#include <inttypes.h>
#include "HardwareSerial.h"

// ======================================================================================= //
class C595
{
public:
      C595(byte _latchPin, byte _clockPin, byte _dataPin);
      void Tick(void);
      void setOutput(byte Pos, boolean Value);
      void Reset(void);

private:
      byte latchPin;
      byte clockPin;
      byte dataPin;
      boolean Output[NumberOfChips*8];
};

#endif

C595.cpp

/*
 
      www.Wusik.com - Created by WilliamK @ Wusik Dot Com (c) 2010
      Check the C595.h file for instructions
 
*/

#include "WConstants.h"
#include "C595.h"

// ------------------------------------------------------------------------------------------- //
C595::C595(byte _latchPin, byte _clockPin, byte _dataPin)
{
      latchPin = _latchPin;
      clockPin = _clockPin;
      dataPin = _dataPin;

      pinMode(latchPin, OUTPUT);
      pinMode(clockPin, OUTPUT);
      pinMode(dataPin, OUTPUT);    

      Reset();
}

// ------------------------------------------------------------------------------------------- //
void C595::Tick()
{
      digitalWrite(latchPin, LOW);
      digitalWrite(dataPin, LOW);
      digitalWrite(clockPin, LOW);
      for (int i=15; i>=0; i--)  
      {
        digitalWrite(clockPin, LOW);
        if (Output[i]) digitalWrite(dataPin, HIGH); else digitalWrite(dataPin, LOW);
        digitalWrite(clockPin, HIGH);
        digitalWrite(dataPin, LOW);
      }
      digitalWrite(clockPin, LOW);
      digitalWrite(latchPin, HIGH);
}

// ------------------------------------------------------------------------------------------- //
void C595::Reset(void) 
{ 
      memset(Output,false,sizeof(Output)); 
}

// ------------------------------------------------------------------------------------------- //
void C595::setOutput(byte Pos, boolean Value)
{
      Output[Pos] = Value;
}

I'm trying to improve the code so it uses less memory. Please, could you guys take a look at the following thread? Thanks.

http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1293470091/0

think these first two LOW's can be ommitted,

  • clockpin low is also done in the begin of the for loop so not needed.
  • datapin is only relevant when clockpin gets high, so no need to init it too
void C595::Tick()
{
      digitalWrite(latchPin, LOW);
[glow]      // digitalWrite(dataPin, LOW);
      // digitalWrite(clockPin, LOW);
[/glow]      for (int i=15; i>=0; i--)
      {
        digitalWrite(clockPin, LOW);
        if (Output[i]) digitalWrite(dataPin, HIGH); else digitalWrite(dataPin, LOW);
        digitalWrite(clockPin, HIGH);
        digitalWrite(dataPin, LOW);
      }
      digitalWrite(clockPin, LOW);
      digitalWrite(latchPin, HIGH);
}

Furthermore I notice that you hardcode 15 in the loop while that should be NumberOfChips * 8 -1 ???

Furthermore I would change the boolean array to an unsigned int and use bitset bitclear code

(code not tested but to get an impression)

#define NumberOfChips 2
boolean Output[NumberOfChips*8];
==>
unsigned int Output;

void reset() 
{ 
  Output = 0; 
}

void setOutput(byte Pos, boolean Value)
{
      Value ? Output | (1UL << Pos) : Output & ~(1UL << Pos);
} 

// optional function
boolean getOutput(byte Pos)
{
  return Output & (1UL << Pos);
}

Thanks, I was already taking a look at bitRead and the whole gang. :wink: But thanks again. I already updated my C165 code to use bitRead and now it uses much less memory than before. I will post updated code soon.

Wk

Why can't I edit my posts above?!? :cry: (I want to update the code)

Wk

I still wonder if I could drive all my C165 and C595 with the same clock port?

Wk

I still want to know how I could improve the constructor and tick so the pin numbers doesn't need to be called twice and wouldn't use extra RAM.

Same for the other lib of course...

Then you need three local vars to hold the pinnumbers set by the constructor. It will use the same amount of memory I think but the calls to tick won't need parameters, so it becomes easier to use the library as the pins don't need to be supplied and the programmer doesn't need to remember the order of the pins. Note these three bytes should be declared in the .h file as private.

#include "WConstants.h"
#include "C595.h"

byte _latchPin;
byte _clockPin;
byte _dataPin;

C595::C595(byte latchPin, byte clockPin, byte dataPin)
{
        _latchPin = latchPin;
        _clockPin = clockPin;
        _dataPin = dataPin;

      pinMode(_latchPin, OUTPUT);
      pinMode(_clockPin, OUTPUT);
      pinMode(_dataPin, OUTPUT);

      reset();
}

void C595::tick()
{
      digitalWrite(_latchPin, LOW);
      for (int i=(BUS_WIDTH-1); i>=0; i--)
      {
        digitalWrite(_clockPin, LOW);
        if (bitRead(output, i)) digitalWrite(_dataPin, HIGH); else digitalWrite(_dataPin, LOW);
        digitalWrite(_clockPin, HIGH);
      }
      digitalWrite(_clockPin, LOW);
      digitalWrite(_latchPin, HIGH);
}

Q: what kind of license do you think of? I noticed the (c) in the header...

William, what do you think of a slightly different Tick()..

unsigned int C165::Tick()
{
      byte tempInputs;
       
        [glow]prevvalues = values; // remember the previous values;[/glow]

      digitalWrite(loadPin, LOW);
      digitalWrite(loadPin, HIGH);
      for(int i=(BUS_WIDTH-1); i >= 0; i--)
      {
            tempInputs = digitalRead(dataPin);
            if (tempInputs == LOW && bitRead(prevhigh,i) > 0) bitSet(values,i);
            bitWrite(prevhigh,i,tempInputs);

            digitalWrite(clockPin, HIGH);
            digitalWrite(clockPin, LOW);
      }

[glow]    // return all changed bits at once 
    // no bits changed == 0; otherwise > 0
    return values ^ prevvalues;       // X-or[/glow]
}

Oh, good idea, that's pretty cool. :sunglasses:

Also, I did some tests and using 3 small byte variables to hold the pin settings uses less memory than calling both constructor and tick with the pin values. And should also be faster, as a call with nothing to be parsed is faster, at least in real PC processors, so it should be the same for the Atmega too. (or not?)

Wk

I took the examples from the LCD library. :wink: Its using only 3 byte extra and works better. :sunglasses:

I will post the files again in a few minutes.

Wk

using 3 small byte variables to hold the pin settings uses less memory

OK,

should also be faster

Yep it is faster but only in the order of ~1 micro as it only has to fetch 3 bytes from memory and push them on the stack. So I think most apps won't notice the speed diff.

Its using only 3 byte extra and works better

classic memory for speed trade-off