Using Array Data to control I/O's

Hi, this is my first post. I’ve been experimenting with the Arduino for about a year now so I am as amateur as they come. I’ve fumbled my way through several successful projects though, and now I want to use two Arduino’s to communicate over I2C to relay the status of an industrial sized air compressor. The wires, nuts, and bolts side of this I’ve worked out already but the programming is giving me some… headaches!

After many hours and experimenting with various code, I’ve finally gotten the two Arduino’s to talk over I2C. I’m using a MasterWrite / SlaveRead configuration. 10 digital inputs from the compressor are being monitored by the MasterWrite which will then send an array of 1’s and 0’s (Inputs: TRUE or FALSE) which are then transmitted to the SlaveRead for processing.

I am able to receive the array and using the serial monitor off of the SlaveRead I can see the 1’s and 0’s change when their input is pulled high or low (true or false) on the MasterWrite. (10k pull-down resistors for 0V = False (0), 5V when True (1)).

What I am having trouble with is making the SlaveRead check a specific index entry and update various digital outputs according to the index’s value, which I thought would be simple since they were 1’s and 0’s. However, I cannot seem to find the right commands to make the SlaveRead process a request to make a subsequent digitalWrite. For several hours now, I’ve tried if and for statements, placed them in the main loop and within the receiveEvent loop, I’ve tried changing byte to int, I’ve tried to change all the array data from byte to int, but nothing seems to work. I’ve been checking over the web as well and I see some extensive documentation on data transfer over I2C, especially by nickgammon (and several others) which is why I’ve made it as far as I have. However, I still haven’t found a way to make this happen.

Essentially, what I want to happen is this: the compressor is connected to the digital inputs (pins 2 - 11) of the MasterWrite Arduino and will change from low to high depending on if a status or alarm is true or false (Compressor Running, Compressor Loaded, Over Temp, Oil Filter Pressure too high, etc. )). I then transmit these signals as an array which is read by the SlaveRead Arduino. On the SlaveRead, I want to be able to control an alarm, warning beacon, LCD message, or some other similar warning so I am aware of the current status of the compressor is. I’ve got a good handle on LCD’s and basics so I will determine later what an array index will control.

I feel like if someone could give me a little push over this hump, I will be able to run with my own momentum and finish the project. I certainly don’t like things handed to me, I like to know the WHY. I have concluded that in this case, if I know the WHAT, I will be able to understand the WHY behind it.

I will do my best to adhere to the forum guidelines and policies.

I apologize for being long-winded, I just want to be sure I communicate all that I can think of.

Here is the code for the MasterWrite:

#include <Wire.h>

const byte compSTS = 2;    // (Switch "S")  Start Switch Position
const byte compRUN = 3;    // (TB11:23)     Compressor Running (HM / Hour Meter)
const byte compCOM = 4;    // (TB11:24)     Compressor Loaded (LHM / Load Hour Meter)
const byte compESP = 5;    // (TB11:4)      E-Stop Pressed
const byte compMOL = 6;    // (S1)(TB11:5)  Motor Overload
const byte compEDT = 7;    // (S2)(TB11:7)  Excess Discharge Temperature >230*F
const byte compDBF = 8;    // (S4)(TB11:8)  Drive Belt Failure
const byte compOFC = 9;    // (S5)(TB11:9   Diff. Pres. Switch: Oil Filter Cartridge >18 psi
const byte compOSC = 10;   // (S6)(TB11:10) Diff. Pres. Wsitch: Oil Separator Cartridge >14.5 psi
const byte compAFP = 11;   // (S7)(TB11:11) Air Filter Pres. Diff. >0.8 psi

int STcompSTS;       // Declare variable for input state CIOS[0]
int STcompRUN;       // Declare variable for input state CIOS[1]
int STcompCOM;       // Declare variable for input state CIOS[2]
int STcompESP;       // Declare variable for input state CIOS[3]
int STcompMOL;       // Declare variable for input state CIOS[4]
int STcompEDT;       // Declare variable for input state CIOS[5]
int STcompDBF;       // Declare variable for input state CIOS[6]
int STcompOFC;       // Declare variable for input state CIOS[7]
int STcompOSC;       // Declare variable for input state CIOS[8]
int STcompAFP;       // Declare variable for input state CIOS[9]

//       Count: 1  2  3  4  5  6  7  8  9  10
//  -->  Index: 0  1  2  3  4  5  6  7  8  9
byte CIOS[11] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};    // Array for I/O states

void setup() {
  Wire.begin();                // Start the I2C Bus as Master
 // Serial.begin(9600);
  pinMode(compSTS, INPUT);     // Set pin mode (ref. CIOS[0]; pin 2)
  pinMode(compRUN, INPUT);     // Set pin mode (ref. CIOS[1]; pin 3)
  pinMode(compCOM, INPUT);     // Set pin mode (ref. CIOS[2]; pin 4)
  pinMode(compESP, INPUT);     // Set pin mode (ref. CIOS[3]; pin 5)
  pinMode(compMOL, INPUT);      // Set pin mode (ref. CIOS[4]; pin 6)
  pinMode(compEDT, INPUT);     // Set pin mode (ref. CIOS[5]; pin 7)
  pinMode(compDBF, INPUT);     // Set pin mode (ref. CIOS[6]; pin 8)
  pinMode(compOFC, INPUT);     // Set pin mode (ref. CIOS[7]; pin 9)
  pinMode(compOSC, INPUT);     // Set pin mode (ref. CIOS[8]; pin 10)
  pinMode(compAFP, INPUT);     // Set pin mode (ref. CIOS[9]; pin 11)
}

void loop() {
  // Read the digital inputs, write their state to their variables,
  // assign 0's or 1's to their position in the array

  STcompSTS = digitalRead(compSTS);    // CIOS[0]
  if (STcompSTS == HIGH) {
    CIOS[0] = 1;
  }
  else if (STcompSTS == LOW) {
    CIOS[0] = 0;
  }

  STcompRUN = digitalRead(compRUN);    // CIOS[1]
  if (STcompRUN == HIGH) {
    CIOS[1] = 1;
  }
  else if (STcompRUN == LOW) {
    CIOS[1] = 0;
  }

  STcompCOM = digitalRead(compCOM);    // CIOS[2]
  if (STcompCOM == HIGH) {
    CIOS[2] = 1;
  }
  else if (STcompCOM == LOW) {
    CIOS[2] = 0;
  }

  STcompESP = digitalRead(compESP);    // CIOS[3]
  if (STcompESP == HIGH) {
    CIOS[3] = 1;
  }
  else if (STcompESP == LOW) {
    CIOS[3] = 0;
  }

  STcompMOL = digitalRead(compMOL);    // CIOS[4]
  if (STcompMOL == HIGH) {
    CIOS[4] = 1;
  }
  else if (STcompMOL == LOW) {
    CIOS[4] = 0;
  }

  STcompEDT = digitalRead(compEDT);    // CIOS[5]
  if (STcompEDT == HIGH) {
    CIOS[5] = 1;
  }
  else if (STcompEDT == LOW) {
    CIOS[5] = 0;
  }

  STcompDBF = digitalRead(compDBF);    // CIOS[6]
  if (STcompDBF == HIGH) {
    CIOS[6] = 1;
  }
  else if (STcompDBF == LOW) {
    CIOS[6] = 0;
  }

  STcompOFC = digitalRead(compOFC);    // CIOS[7]
  if (STcompOFC == HIGH) {
    CIOS[7] = 1;
  }
  else if (STcompOFC == LOW) {
    CIOS[7] = 0;
  }

  STcompOSC = digitalRead(compOSC);    // CIOS[8]
  if (STcompOSC == HIGH) {
    CIOS[8] = 1;
  }
  else if (STcompOSC == LOW) {
    CIOS[8] = 0;
  }

  STcompAFP = digitalRead(compAFP);    // CIOS[9]
  if (STcompAFP == HIGH) {
    CIOS[9] = 1;
  }
  else if (STcompAFP == LOW) {
    CIOS[9] = 0;
  }

  Wire.beginTransmission(9);
  Wire.write(CIOS, 11);
  Wire.endTransmission();
  delay(250);
}

This is the SlaveRead code:

#include <Wire.h>

byte CIOS[11];                         // Array for I/O states RECEIVED
const byte LedPin = 2;

void setup() {
  Wire.begin(9);                       // Join I2C with address #9 (arbitrarily chosen...)
  Wire.onReceive(receiveEvent);        // Calls function when Tx is received from Master
  Serial.begin(9600);                  // Start serial for output
  pinMode(2, OUTPUT);                  // LED output pin
}
void loop() {
  // Nothing to see here...
}

// function that executes whenever data is received from master
// this function is registered as an event, see setup()

void receiveEvent(byte howMany) {
  while (1 < Wire.available()) {
    byte CIOS = Wire.read();
    Serial.print(CIOS);
  }
  byte x = Wire.read();
  Serial.println(x);

  byte LedST = CIOS [0];                // Check for 1 or 0 at this index, write to LedST
    if (LedST == 1)  {                      // if it is a 1 (TRUE)
      digitalWrite(LedPin, HIGH); }    // turn on an LED
      else {                                     // or
      digitalWrite(LedPin, LOW); }    // Turn it off
    
}

On the Tx side you are populating the CIOS array with data and I presume that

  Wire.beginTransmission(9);
  Wire.write(CIOS, 11);
  Wire.endTransmission();

transmits it

On the Rx side you have a corresponding array but you do not use it. What you need to do is to have an index variable, let's call it arrayIndex and set it to zero. When a byte is received add it to the array and increment the index

  byte x = Wire.read();
  CIOS[arrayIndex] = x;
  arrayIndex++;  increment the index ready for next byte

When all of the data has been received the set of data will be in the array and you can do whatever you like with it.

PROBLEM How will you know which received byte is the start of a set of data and should go into CIOS[0] ? One way would be to add a marker to the start of the transmitted data, such as the number 255 which, when received, will indicate that arrayIndex should be reset to zero ready to receive a set of data. It would also be wise to add a trailing marker, perhaps 254, indicating the end of message.

for a start could you simplify the code in the master when reading the digital inputs?

CIOS[0]=digitalRead(compSTS);    // CIOS[0]

in the receiveEvent could you wait to receive 11 bytes then fill the array, e.g.

void receiveEvent(byte howMany) {
  if(howMany < 11) return;
  for (int i=0;i<11;i++)
  { 
    CIOS[i] = Wire.read();
    Serial.print(CIOS[i]);
  }     
}

Why bother with the array? Do you think that you have to assemble a data message before you send it?

Suppose that before reading the AC pins you send a start character then ‘0’ for 0 and ‘1’ for 1 for each pin read and then an end character?

What you should put in an array are the pin numbers. Then you can read each pin and write data in a loop.

Wire.beginTransmission(9);
Wire.write(startTxChar);

for ( byte index = 0; index < 10; index++)
{
if digitalRead(ACpin[index])
{
Wire.write(‘1’);
}
else
{
Wire.write(‘0’);
}
}

Wire.write(endTxChar);
Wire.endTransmission();

At the receive end, when it gets a start character, the next one will be the one you now light the led on 1 and turn off on 0.
The receive needs to keep track of what is read, any time there is not a start char followed by 10 data and an end char you have an error to deal with. Wire, like Serial, has no guarantee that data sent gets received correctly… you Need error check and handling.

UKHeliBob: On the Rx side you have a corresponding array but you do not use it. What you need to do is to have an index variable, let's call it arrayIndex and set it to zero. When a byte is received add it to the array and increment the index

  byte x = Wire.read();
  CIOS[arrayIndex] = x;
  arrayIndex++;  increment the index ready for next byte

What you're telling me is that without this section of code, the array will not populate with transmitted values, even though I'm able to see it on a serial monitor... correct? So I've been reading the serial side but failing to populate the array (CIOS) on the SlaveRead side... consequently there are no values to be read by other sections of the program.

Let me see if I understand this, because I've seen several examples of similarly structured code and I want to be sure I am comprehending what is going on. The first line byte x = Wire.read(); is assigning an "x" (an int?) as a temporary place to store each byte as it is read. It begins with index 0. The next line CIOS[arrayIndex] = x; tells the program that the index position, defined by arrayIndex in the array CIOS will be populated by "x" when "x" is read. arrayIndex++; increments the index position after said byte (0) is read, thereby sequentially placing each byte in its index position. Once the array has been populated, the values can then be read by other sections of code. Is this correct?

horace:
for a start could you simplify the code in the master when reading the digital inputs?

CIOS[0]=digitalRead(compSTS);    // CIOS[0]

in the receiveEvent could you wait to receive 11 bytes then fill the array, e.g.

void receiveEvent(byte howMany) {

if(howMany < 11) return;
  for (int i=0;i<11;i++)
  {
    CIOS[i] = Wire.read();
    Serial.print(CIOS[i]);
  }   
}

Inrteresting idea; the array would be populated with values of either HIGH or LOW, correct? So the reason I chose to use byte as the data type is so the data size would minimized. The physical run of communication cabling will be quite long, and I want to send the array frequently so that after error checking & handling I would have a fairly recent update (every 1 to 2 seconds or so) on the conditions of the compressor. This is why I didn’t use int or char, since those data types require more throughput to transmit over serial than a byte would.

byte x = Wire.read();

Reads a single byte (not an int) from the Wire interface

CIOS[arrayIndex] = x;

Puts the value of the x variable into the array at the current position

arrayIndex++;

Increments the variable that is being used as the index to the array ready to store the next byte when/if it is received.

I split the lines to make them easier to understand and debug, for instance you could print the value of x, but you can actually do all of this in one line of code

CIOS[arrayIndex++] = Wire.read();

Whether you think that is better or not is up to you.

The for loop provides an alternative way of achieving the same thing but all it effectively does is to increment the index and stop automatically when the limit is reached.

Whichever way you read the data I still think that you need start and end markers on the data so that you can synchronise the reading with the writing.

UKHeliBob:
Whichever way you read the data I still think that you need start and end markers on the data so that you can synchronise the reading with the writing.

I absolutely agree 100%. I will indeed integrate error checking & handling.

First, I want to assemble the write and read code for this, AND understand what is going on before I proceed with the next step. Baby steps… I find C++ to be a fascinating lanugage, but it is very new to me. I’m far more familiar with PLC ladder logic but the Arduino and C++ are a much lower cost alternative!

GoForSmoke: Why bother with the array? Do you think that you have to assemble a data message before you send it?

Yes, that's what I was thinking. If I send an array of 11 bytes, and receive 11 bytes, I know the array was transferred completely, as a way of checking for errors. You're showing that each one can be sent individually... so there would be no array at either end?

I do like the idea of sending a pin, the reading, then a character for each AC input as a single "section" of data. This ensures each and every input reading is sent correctly and can be checked for errors.

not_in_use: Yes, that's what I was thinking. If I send an array of 11 bytes, and receive 11 bytes, I know the array was transferred completely, as a way of checking for errors. You're showing that each one can be sent individually... so there would be no array at either end?

I do like the idea of sending a pin, the reading, then a character for each AC input as a single "section" of data. This ensures each and every input reading is sent correctly and can be checked for errors.

I don't think that you understand the nature of communication between devices by whatever means.

The arrays only exist in the computers, serial data is transmitted as bits at the baud rate (divide by 10 to get bytes/chars) and it is the job of the receiver to error check and assemble the incoming data.

At 9600 baud (960 bytes per second) it takes just over a milli to transmit a single char taken from the serial queue your command sends data to. In the time it takes to send the first char, Arduino can fill that queue with time left over. At the receive end, all the chars arrive one after the other but there is no guarantee that all the bits will be right especially with long wires.

Even at 250000 baud the Arduino -can- fill the 64 byte send queue faster than it can send 2 chars.

If you only transmit changes, how much less data will you need to transmit? How much doesn't change for a few to many milliseconds at a time? My guess is most of it.

GoForSmoke: I don't think that you understand the nature of communication between devices by whatever means.

The arrays only exist in the computers, serial data is transmitted as bits at the baud rate (divide by 10 to get bytes/chars) and it is the job of the receiver to error check and assemble the incoming data.

At 9600 baud (960 bytes per second) it takes just over a milli to transmit a single char taken from the serial queue your command sends data to. In the time it takes to send the first char, Arduino can fill that queue with time left over. At the receive end, all the chars arrive one after the other but there is no guarantee that all the bits will be right especially with long wires.

Even at 250000 baud the Arduino -can- fill the 64 byte send queue faster than it can send 2 chars.

If you only transmit changes, how much less data will you need to transmit? How much doesn't change for a few to many milliseconds at a time? My guess is most of it.

You're mostly correct, where my lack of understanding lies is how the code is structured in C++ to transmit and receive data. I've never attempted to use I2C, except for some basic tutorials you can find online using LCD displays and the MAX6675 thermocouple module, both of which I was able to get working by using copy + paste of code found online, combined with my application. So I began educating myself further with available info on the web, Arduino reference page, etc. and seeing if I could do it myself. I was reading about data types and I chose to use the form of bytes since it is smaller than int or char. It seemed natural to me to use 0's and 1's for logic of inputs. I thought that if the MasterWrite was continuously sending an array, which has all the data I'm looking for, it would be easier to check that all 11 bytes were received and handle errors with re-writes and timeouts since the only timeout which would occur would be if there was a power loss or damaged cable. A fast write / read cycle would ensure a quick signaling to me of a change in the status of the AC and that the MasterWrite unit is functioning. Since I don't know much about this (which I admitted in my opening post) and I see there are many different ways of approaching this, which one is best for me I haven't decided. I only chose to go the way I did because I had to start somewhere. I figured I would try to go this particular route since there's no telling how long it would have taken me to decide on the "best" way to go about it without trying something. I figured that if I find a better way to do it, I can always change it along the way or go back and change it later. I needed higher wisdom which is why I'm here.

Now you (guys) have shown me that there are alternative and possibly better ways of doing this and to be honest my head is spinning a bit because I'm just trying to get my mind wrapped around it. It is difficult for one to communicate effectively about something technical when they're learning about it.

If I choose to update the values only when there is a change in states, how would I know that the MasterWrite is still operating? That's one thing I need to know since the unit is pretty far away, in another building. I assume a request and response for that request every 500ms or so would be sufficient? How would you go about doing this?

I would suggest that you do that Playground project just to see how it works and add hardware-debounce caps across the switches only if it shows glitching. You will learn some and maybe take it further.


I suggest only reporting changes because in most cases I know most of the time there is no change. How often do any of your inputs change in one second? 1000x?

115200 baud is 11520 chars/second. You want to go faster to send 11000 bytes/sec. But if you only send 1000 or less then lower baud rate and longer wires or the chance to use cheap wifi to communicate becomes possible. Your software catches and reports events as they happen instead of reporting everything 1000x per second.

The MasterWrite could send a signal every 10ms with the Receive working as watchdog and make an alarm on overdue signal.

The as-and-when approach is asynchronous whereas the all-always approach is synchronous. Asynch-based code can handle synch or asynch data while Synch-based code generally cannot handle asynch data, it is like the difference between trains on tracks and cars on highways.

GoForSmoke: I would suggest that you do that Playground project just to see how it works and add hardware-debounce caps across the switches only if it shows glitching. You will learn some and maybe take it further.


I suggest only reporting changes because in most cases I know most of the time there is no change. How often do any of your inputs change in one second? 1000x?

115200 baud is 11520 chars/second. You want to go faster to send 11000 bytes/sec. But if you only send 1000 or less then lower baud rate and longer wires or the chance to use cheap wifi to communicate becomes possible. Your software catches and reports events as they happen instead of reporting everything 1000x per second.

The MasterWrite could send a signal every 10ms with the Receive working as watchdog and make an alarm on overdue signal.

The as-and-when approach is asynchronous whereas the all-always approach is synchronous. Asynch-based code can handle synch or asynch data while Synch-based code generally cannot handle asynch data, it is like the difference between trains on tracks and cars on highways.

Let me just say I appreciate your help! I see your point about the data transfer rate... and I should know better when it comes to the math side of that. The reality is you're correct, the inputs don't update that fast. The best I would be looking for is 0.5 to 1 second write / read between updates so what you're suggesting makes perfect sense. I just laid out my reasoning for you to have a better idea what I was thinking at the time. Now, I'm going to work on writing some code so I can post it and get some criticism. Good, bad, indifferent... whatever, I'll take it!

It's easier to start with something that works. You can mess with it and look for ways to do it better once you know how it works, that will start you one square ahead. An old axiom in computing is that if you want to make a program, write it twice and throw the first one away. Using working code is a way to cheat that just a bit.

if all you are doing is sending 11 high/low states you could do this in two bytes, e.g.

 Wire.write(digitalRead(compSTS)) | (digitalRead(compRUN) << 1) | (digitalRead(compCOM) << 2)   .... );

compSTS is bit 0, compRUN is bit 1, compCOM is bit 2, etc
at the receiver use the & operator to mask out the bits
As an indicator you could clear the MSB of the first byte and set the MSB of the second byte
you could add an error check bit to each byte indicating if the LSB of the sum of the bits is 0 or 1

True and there’s 5 bits left over for parity and a start or end marker.
And 2000 bytes per second to transmit, receive and process.

horace: for a start could you simplify the code in the master when reading the digital inputs?

CIOS[0]=digitalRead(compSTS);    // CIOS[0]

I think I'll shift gears a bit now that I've gotten advice from you guys. I also did some more research and am going to try to approach this thing a little differently since some of the assumptions I have made appear to be... well, silly.

I'm going to throw away the idea of using an array; I was thinking that assembling the data was necessary and part of the process but now I understand differently. I also think that I'm going to use a different controller configuration: Master-Read/requestFrom and Slave-Write/onRequest setup. About once every second or so the Master will request information from the Slave. Updating faster is not necessary, but I would prefer it to be updated every second.

Question regarding simplification and my understanding of the code. Let's say I have this input on the SlaveWrite:

void loop()
{
  runstate = digitalRead(runswitch);
}

void requestEvent()
{
  Wire.beginTransmission(10);
  Wire.write(runstate);
  Wire.endTransmission();
}

When the MasterRead executes:

Wire.requestFrom(20);            // not concerned about specifics or error checking just yet
while (Wire.available())
{
  char c = Wire.read();
  Serial.print(c);
}

the data is read as a series of characters; the number/count of which is determined by the amount called for in Wire.requestFrom(20, XX) which XX is missing from above, for now. As it stands, it will read all characters sent (assuming all are received) regardless of how many are sent, then print the characters to the serial monitor.

As written above, the value I read on the serial monitor will be either HIGH or LOW.

Is this correct? Do I understand this correctly?

That while loop in your master sketch is likely going to run faster than multiple chars arrive, but depending on the rest of the code in the void loop() may do no harm at all.

These 2 tutorials teach all you need to catch inputs as they arrive without locking onto any set.

1) http://gammon.com.au/blink 2) http://gammon.com.au/serial

They work great for simple use and will let you keep adding things to your sketch at least until you fill the chip or run so much it slows down to a minimal acceptable speed.

They can help you avoid spaghetti code solutions.

They can show you how fast these little chips can go. I put an unsigned long variable to count passes through loop() with a timer to print the value and zero it once a second, on typical lightly loaded examples I was reading over 60K using the techniques shown in those tutorials.

Getting a feel for the speed of the chip and the speed of your comms will help you decide when to wait and when not, it's like pies coming down conveyor belts --- how many people does it take to make sure each one goes in a box and then on a cart kind of thing. Until you know the speed of the belts and workers you really don't know.

I2C is pretty fast but does have a buffer. With short messages and millis between them you don't have to be real fast but with long ones or back-to-back short ones you do. SPI can go faster yet.