I2C and momentary pushbuttons

Hi everyone, I’m new to the forum and new to the world of Arduino so go easy on me.

I’m using an EtherTen with two 8 channel relay shields stacked on to it. The shields use I2C to communicate with the EtherTen. So far it works fine with a light web interface that has been programmed into the sketch (see below).

/**
   OutputControllerRelay8x2.pde
*/

#include <SPI.h>
#include "Ethernet.h"
#include "WebServer.h"
#include "Wire.h"


#define SHIELD_1_I2C_ADDRESS  0x20  // 0x20 is the address with all jumpers removed
#define SHIELD_2_I2C_ADDRESS  0x21  // 0x21 is the address with a jumper on position A0

#define MAC_I2C_ADDRESS       0x50  // Microchip 24AA125E48 I2C ROM address

static uint8_t mac[] = { 0xFE, 0xDE, 0x8A, 0xAA, 0xFF, 0xB4 };

static uint8_t ip[] = { 192, 168, 2, 111 };

#define PREFIX "/control"  // This will be appended to the IP address as the URL
WebServer webserver(PREFIX, 80);

byte shield1BankA = 0; // Current status of all outputs on first shield, one bit per output
byte shield2BankA = 0; // Current status of all outputs on second shield, one bit per output

void serverCmd(WebServer &server, WebServer::ConnectionType type, char *url_tail, bool tail_complete)
{
  /* If we've received a POST request we need to process the submitted form values */
  if (type == WebServer::POST)
  {
    bool repeat;
    char name[20], value[16];
    do
    {
      /* readPOSTparam returns false when there are no more parameters
         to read from the input.  We pass in buffers for it to store
         the name and value strings along with the length of those
         buffers. */
      repeat = server.readPOSTparam( name, 20, value, 16);

      /* This is a standard string comparison function.  It returns 0
         when there's an exact match. */
      if (strcmp( name, "On" ) == 0)
      {
        setLatchChannelOn( atoi(value) );
      }

      if (strcmp( name, "Off" ) == 0)
      {
        setLatchChannelOff( atoi(value) );
      }

      if (strcmp( name, "AllOff" ) == 0)
      {
        sendRawValueToLatch1(0);
        sendRawValueToLatch2(0);
      }

    } while (repeat);

    // After procesing the POST data, tell the web browser to reload
    // the page using a GET method.
    server.httpSeeOther(PREFIX);
    return;
  }

  /* for a GET or HEAD, send the standard "it's all OK" headers */
  server.httpSuccess();

  /* Don't output the body for a HEAD request, only for GET */
  if (type == WebServer::GET)
  {
    /* store the HTML in program memory using the P macro */
    P(message) =
      "<html><head><title>16 Channel Test</title>"
      "<body style='background-color:powderblue;'>"
      "<form action='/control' method='POST'>"
      "<h1 style='color:red;font-family:comic sans ms;font-size:200%;text-align:center;'>16 Channel Test</h1><hr>"
      "<div style='float:left;width:60%;'>"
      "<p style='font-size:140%;'><button style='height:40px;width:75px' type='submit' name='On' value='1'>On</button><button style='height:40px;width:75px' type='submit' name='Off' value='1'>Off</button>1<button style='height:40px;width:75px' type='submit' name='On' value='2'>On</button><button style='height:40px;width:75px' type='submit' name='Off' value='2'>Off</button>2</p>"
      "<p style='font-size:140%;'><button style='height:40px;width:75px' type='submit' name='On' value='3'>On</button><button style='height:40px;width:75px' type='submit' name='Off' value='3'>Off</button>3<button style='height:40px;width:75px' type='submit' name='On' value='4'>On</button><button style='height:40px;width:75px' type='submit' name='Off' value='4'>Off</button>4</p>"
      "<p style='font-size:140%;'><button style='height:40px;width:75px' type='submit' name='On' value='5'>On</button><button style='height:40px;width:75px' type='submit' name='Off' value='5'>Off</button>5<button style='height:40px;width:75px' type='submit' name='On' value='6'>On</button><button style='height:40px;width:75px' type='submit' name='Off' value='6'>Off</button>6</p>"
      "<p style='font-size:140%;'><button style='height:40px;width:75px' type='submit' name='On' value='7'>On</button><button style='height:40px;width:75px' type='submit' name='Off' value='7'>Off</button>7<button style='height:40px;width:75px' type='submit' name='On' value='8'>On</button><button style='height:40px;width:75px' type='submit' name='Off' value='8'>Off</button>8</p>"
//rest of html removed due to post length

      "<p style='font-size:140%;text-align:center;'><button style='height:40px;width:75px' type='submit' name='AllOff' value='0'>Off</button>&nbsp&nbspTurn Everything Off</p></div>"
      "</form>"
      "</body></html>";

    server.printP(message);
  }
}

/**
*/

void setup()
{
  Wire.begin(); // Wake up I2C bus
  Serial.begin( 38400 );
  Serial.println("Test Control Output Controller starting up. v1.0, 16 channel relay shield");

  // setup the Ethernet library to talk to the Wiznet board
  Ethernet.begin(mac, ip);  // Use static address defined above
  //Ethernet.begin(mac);      // Use DHCP

  /* Register the default command (activated with the request of
     http://x.x.x.x/control */
  webserver.setDefaultCommand(&serverCmd);

  /* start the server to wait for connections */
  webserver.begin();

  /* Set up the Relay8 shields */
  initialiseShield(SHIELD_1_I2C_ADDRESS);
  sendRawValueToLatch1(0);  // If we don't do this, channel 6 turns on! I don't know why

  initialiseShield(SHIELD_2_I2C_ADDRESS);
  sendRawValueToLatch2(0);  // If we don't do this, channel 6 turns on! I don't know why

  Serial.println("Ready.");
}

/**
*/

void loop()
{
  // Process incoming connections one at a time forever
  webserver.processConnection();
}

/**
*/
void initialiseShield(int shieldAddress)
{
  // Set addressing style
  Wire.beginTransmission(shieldAddress);
  Wire.write(0x12);
  Wire.write(0x20); // use table 1.4 addressing
  Wire.endTransmission();

  // Set I/O bank A to outputs
  Wire.beginTransmission(shieldAddress);
  Wire.write(0x00); // IODIRA register
  Wire.write(0x00); // Set all of bank A to outputs
  Wire.endTransmission();
}

/**
*/

void toggleLatchChannel(byte channelId)
{
  if ( channelId >= 1 && channelId <= 8 )
  {
    byte shieldOutput = channelId;
    byte channelMask = 1 << (shieldOutput - 1);
    shield1BankA = shield1BankA ^ channelMask;
    sendRawValueToLatch1(shield1BankA);
  }
  else if ( channelId >= 9 && channelId <= 16 )
  {
    byte shieldOutput = channelId - 8;
    byte channelMask = 1 << (shieldOutput - 1);
    shield2BankA = shield2BankA ^ channelMask;
    sendRawValueToLatch2(shield2BankA);
  }
}

/**
*/
void setLatchChannelOn (byte channelId)
{

  if ( channelId >= 1 && channelId <= 8 )
  {
    byte shieldOutput = channelId;
    byte channelMask = 1 << (shieldOutput - 1);
    shield1BankA = shield1BankA | channelMask;
    sendRawValueToLatch1(shield1BankA);
  }
  else if ( channelId >= 9 && channelId <= 16 )
  {
    byte shieldOutput = channelId - 8;
    byte channelMask = 1 << (shieldOutput - 1);
    shield2BankA = shield2BankA | channelMask;
    sendRawValueToLatch2(shield2BankA);
  }
}


/**
*/
void setLatchChannelOff (byte channelId)
{
  if ( channelId >= 1 && channelId <= 8 )
  {
    byte shieldOutput = channelId;
    byte channelMask = 255 - ( 1 << (shieldOutput - 1));
    shield1BankA = shield1BankA & channelMask;
    sendRawValueToLatch1(shield1BankA);
  }
  else if ( channelId >= 9 && channelId <= 16 )
  {
    byte shieldOutput = channelId - 8;
    byte channelMask = 255 - ( 1 << (shieldOutput - 1));
    shield2BankA = shield2BankA & channelMask;
    sendRawValueToLatch2(shield2BankA);
  }
}

/**
*/
void sendRawValueToLatch1(byte rawValue)
{
  Wire.beginTransmission(SHIELD_1_I2C_ADDRESS);
  Wire.write(0x12);        // Select GPIOA
  Wire.write(rawValue);    // Send value to bank A
  shield1BankA = rawValue;
  Wire.endTransmission();
}

/**
*/
void sendRawValueToLatch2(byte rawValue)
{
  Wire.beginTransmission(SHIELD_2_I2C_ADDRESS);
  Wire.write(0x12);        // Select GPIOA
  Wire.write(rawValue);    // Send value to bank A
  shield2BankA = rawValue;
  Wire.endTransmission();
}

/**
   Required to read the MAC address ROM
*/
byte readRegister(byte r)
{
  unsigned char v;
  Wire.beginTransmission(MAC_I2C_ADDRESS);
  Wire.write(r);  // Register to read
  Wire.endTransmission();

  Wire.requestFrom(MAC_I2C_ADDRESS, 1); // Read a byte
  while (!Wire.available())
  {
    // Wait
  }
  v = Wire.read();
  return v;
}

Continued from above due to character limit.

The next stage is to use a number of the digital pins and activate the relay channels using momentary pushbuttons. I've been able to use the following code to have some success.

const int buttonPin2 = 2;
int buttonState = 0;

void loop()
{
  // Process incoming connections one at a time forever
  webserver.processConnection(); // ===This code was already there===

    if (shield1BankA==1 && digitalRead(buttonPin2)==HIGH)
    {
    sendRawValueToLatch1(0);
    delay(2000);
    }
  else if (digitalRead(buttonPin2)==HIGH)
    {
    sendRawValueToLatch1(1);
    delay(1000);
    }
}

As you can hopefully see, switching the relay channel on works fine because I can just send the latch values depending on the channel (1,2,4,8,16,32,64,128). This issue is switching individual channels off because when using "sendRawValueToLatch1(0)" it switches any channel that is on, off (because of the 0 value).

Hopefully this makes sense, and hopefully someone can provide some guidance.

Thanks in advance.

I am not sure if I understand your problem. Does every channel of the relay have their own register? If not and all channels are in one byte which would make sense since 1 byte = 8 bit and 8 channels, would you not have to send 00000001 in order to turn the last relay on and then 00000000 to turn it off again?

Wh are you sending just 1 or 0?

Your relays status is saved in variable shield1BankA resp shield2BankA.

I am on thin ice, but I think you can use bitSet and bitClear.

Your code would be

    if (shield1BankA==1 && digitalRead(buttonPin2)==HIGH)
    {
    sendRawValueToLatch1(bitClear(shield1BankA,0));
    delay(2000);

You might have an error as both if and else if has buttonPin2==HIGH

MarkGoingToSpace:
I am not sure if I understand your problem. Does every channel of the relay have their own register? If not and all channels are in one byte which would make sense since 1 byte = 8 bit and 8 channels, would you not have to send 00000001 in order to turn the last relay on and then 00000000 to turn it off again?

Wh are you sending just 1 or 0?

Hi,

Channel 1 of the shield turns on by sending 1 (or 00000001). Channel 2 turns on with 2 (or 00000010). Channel 3 with 4 (or 00000100) and so on. When a 0 (zero) is sent, it turns off all the channels (as seen in the sendRawLatchValue1 line (used to reset all to zero upon reset). This is where I'm having the problems in trying to work out the value that is sent in the function setLatchChannelOff (which is what I believe I should be using).

Your button code should only be calling setLatchChannelOn() and setLatchChannelOff(). They
already provide the functionality you need.

MarkT:
Your button code should only be calling setLatchChannelOn() and setLatchChannelOff(). They
already provide the functionality you need.

Yes I have pondered this too but I'm having trouble working out what value to send to turn only the specific shield channel off.

keiranwyllie:
Yes I have pondered this too but I'm having trouble working out what value to send to turn only the specific shield channel off.

Code does it all for you. Just send the channel number, 1-8 for relays on first shield, or 9-16 for the second shield.

Gabriel_swe:
Code does it all for you. Just send the channel number, 1-8 for relays on first shield, or 9-16 for the second shield.

It works for one channel (well Channel 1) with the code below, however if another channel is turned on (whether via switch or web), then Channel 1 won’t turn off because the value of shield1BankA is not 1 anymore. It becomes the value that includes whatever other channel is on (say channel 8 is on then the value is 1000001).

if (shield1BankA == 1 && digitalRead(2) == HIGH)
  {
    setLatchChannelOff(1);
    delay(1000);
  }
  else if (digitalRead(2) == HIGH)
  {
    setLatchChannelOn(1);
    delay(1000);
  }
[code]

keiranwyllie:
It works for one channel (well Channel 1) with the code below, however if another channel is turned on (whether via switch or web), then Channel 1 won't turn off because the value of shield1BankA is not 1 anymore. It becomes the value that includes whatever other channel is on (say channel 8 is on then the value is 1000001).

if (shield1BankA == 1 && digitalRead(2) == HIGH)

{
    setLatchChannelOff(1);
    delay(1000);
  }
  else if (digitalRead(2) == HIGH)
  {
    setLatchChannelOn(1);
    delay(1000);
  }

[code]

Code has several functions you can use. setLatchChannelOn/Off has we already talked about, and it is not good in this situation as you need to write code to detect what state the relay has to use the right function.

Code also has a function, toggleLatchChannel(byte channelId), and I think it is just what you want to do. No need to know what state relay has. Just use toggleLatchChannel(1) and relay 1 will switch for every push.

Gabriel_swe:
Code has several functions you can use. setLatchChannelOn/Off has we already talked about, and it is not good in this situation as you need to write code to detect what state the relay has to use the right function.

Code also has a function, toggleLatchChannel(byte channelId), and I think it is just what you want to do. No need to know what state relay has. Just use toggleLatchChannel(1) and relay 1 will switch for every push.

Oh man, it was staring me right in the face the whole time. Thank you so much. Works like a charm.