Trying to learn I2C (was RS485) to control things on my hobby railroad

Address 0 is the broadcast address with I2C. But not all I2C slave devices support this.

That's how most I2C devices do it.

But I'm not shure if @shauntherailroadguy is asking about I2C at all. I think he is still testing RS485.

1 Like

Unclear. The discussion started with RS485 but transitioned to I2C with code in post #36 for an I2C scenario.

Apologies, I am now testing I2C - looks to be far easier to implement for the pretty basic stuff I need

is there a way to change the title to prevent confusion?

I think, as the author of the thread, you can edit the title yourself.

Might also be worth adding to your initial post that the discussion has moved on to using I2C - possibly in bold - to catch other contributors eye.

will do, thanks! Sorry for the confusion too

Done, not as hard as I thought it would be!

That may be a big mistake.
I2C is much much more susceptible to noise then RS485 especially for wires longer than 10cm

1 Like

I can't agree with that. I'm using I2C for my model railroads for years. My last railroad was about 4,5x5m and the I2C lines was about 3m max. I used a multiplexer to limit the length ( about 2m in one direction and 3m in the other). I never had problems with that.
Noise is not so much a problem than the capacitance of the lines. You have to pay attention to that. And of course you can reduce the clock speed. This is not so much a problem in this usage.
Of course you can have much longer lines with RS485. It is designed for line lenght of more than 1km. You seldom will find that on a model railroad :wink: .

If you only want IO pins to set high or low or to read inputs, a nano as slave is a little bit overkill. There are I2C IO devices that can do that out of the box - so no need to programme a slave.

2 Likes

in a few cases I want to be able to drive steppers or servos, so maybe they are needed for that.

Having said that, I'm interested in what devices you are talking about, can you give me more details?

Now a question regarding powering the slave nanos. As I see it I think there are three options, plug the USB connection into a powered USB hub or charger, supply 5v to the VIN pin or supply 5v to the 5v pin

What is the best option, bearing in mind for the 28BYJ-48 stepper drivers I already would have a 5v supply

Never; Vin requires 7V or higher.

Things like below (I2C devices) can be used for general purpose digital IO; note that this is local and only to give you pointers to search for

You can also search for 74HC595 (output) or 74HC165 (input) shift registers; finding modules might make your life easier. These are not I2C.

Hi all

Once more I request your assistance. Over the last few days I managed to make a little network over I2c with a master with switches and an LED driving three Nanos with LED's, and one with a switch. The master could command the LEDs to light based on which switch was pressed and I even had feedback where the switch connected to the slave would light the LED connected to the master depending on the position of the switch.

I was feeling quite good about myself but the next slave has so far has me stumped. I have broken it out to try with a dedicated master sketch, and I beg your assistance to understand why this one won't work when I effectively copied one of the working sketches (apparently - maybe there is a detail I missed) and just added another LED argument.

The idea is that when you flip a toggle switch connected to the master via pins 11 and 12, it will light or turn off two LED's connected to the slave pins 2 and 3. I want them as individual actions and not and or.

In theory, the master will transmit values 'tal' of either 0 or 2, and 'ual' of 1 or 3'. When I open the serial monitor on the master, that is indeed what is being transmitted

The slave is then supposed to use an 'if' argument to light LED1 and LED 2 depending on the received values, which I thought would be straightforward.

However when I open the serial monitor for the slave, the only received value I can see is -1 no matter what the output from the master. On the other sketches I could see the received values changing, but for some reason this sketch is acting as if the master is not transmitting

This is the master sketch

// Include Arduino Wire library for I2C
#include <Wire.h>

// Define Slave I2C Address

#define SLAVE_ADDR12 12

bool buttonState4, buttonState5;

const int buttonPin4 = 11;
const int buttonPin5 = 12;
const int ledPin1 = 6;


int tal = 0;
int ual = 0;


void setup() {
 
  pinMode(buttonPin4, INPUT_PULLUP);
  pinMode(buttonPin5, INPUT_PULLUP);
  pinMode(ledPin1, OUTPUT);
  // Initialize I2C communications as Master
  Wire.begin();
}



void loop() {
 
  buttonState4 = digitalRead(buttonPin4);
  delay(10);
  if (buttonState4 == LOW) {
    tal = 1;

  } //else {
    
    if (buttonState4 == HIGH) {
    tal = 0;
  }
  buttonState5 = digitalRead(buttonPin5);
  delay(10);
  if (buttonState5 == LOW) {
    ual = 2;

  } //
  if (buttonState5 == HIGH) {
    ual = 3;

 
  }
  

  Wire.beginTransmission(SLAVE_ADDR12);
  Wire.write(tal);
  Wire.write(ual);
  Wire.endTransmission();


  {
    Wire.begin();
    Serial.begin(9600);

    Serial.println(F("-------------------------------------I am the Master"));
    delay(1000);
    
    Serial.print(F("tal : "));
    Serial.println(tal);
    Serial.print(F("ual : "));
    Serial.println(ual);

    
  }
}

here is the slave sketch

#include <Wire.h>

// Define Slave I2C Address
#define SLAVE_ADDR 12

// Define LED Pin
int ledPin1 = 2;
int ledPin2 = 3;

// Variable for received data
int rd;

void setup() {
  pinMode(ledPin1, OUTPUT);
  pinMode(ledPin2, OUTPUT);

  // Initialize I2C communications as Slave
   Wire.begin(SLAVE_ADDR);

   // Function to run when data received from master
  Wire.onReceive(receiveEvent);
  Serial.println("I2C Slave Demonstration");
 
  Serial.begin(9600);
  Serial.println("-------------------------------------I am Slave4");
  //Wire.onRequest(requestEvent);
 
}
void receiveEvent() {
  // read one character from the I2C
  rd = Wire.read();
  // Print value of incoming data
  Serial.println(rd);
  Wire.write(rd);
}
void loop() {
 
  delay(10);

   if (rd == 1) {
    // turn LED on:
    digitalWrite(ledPin1, HIGH);
  }
  if (rd = 2) {
    /*} else {
    // turn LED off:*/
    digitalWrite(ledPin1, LOW);
  }
  if (rd == 0) {
    // turn LED on:
    digitalWrite(ledPin2, HIGH);
  }
  if (rd = 3) {}

  digitalWrite(ledPin2, LOW);
}

void receiveEvent(int numBytes) {
  Wire.begin();        
  Serial.begin(9600); 
  Serial.println(F("---> recieved events"));
  Serial.print(numBytes);
  Serial.println(F("bytes recieved"));
  Serial.print(F("recieved value : "));
  rd = Wire.read();
  Wire.write(rd);
  Serial.println(rd);
  // Print value of incoming data

}

Am I misunderstanding the syntax, have I missed out a key line? It's probably a stupid error on my part but I just can't see it. If you can help resolve this I would be grateful

if (rd = 2) {

WHOOPS !

if (rd = 3)

and again

Thanks, and I wish I had your eyes, you were quick!

I have just realized that I cannot have two different values of the same int.

How do I get the slave sketch to recognize two different parameters to control two different master outputs?

EDIT

Just trying to use my head rather than leaning on you guys, would a conditional argument like
'if pin 11is high and pin 12 low then output 1',

'if pin 11is high and pin 12 high then output 2',

if pin 11is low and pin 12 low then output 3'.

if pin 11is low and pin 12 high then output 4'

Is that an option or not?

That is certainly an option but there are almost certainly better ways to to it. I am not overly familiar with I2C but I will see what I can find

In the meantime I suspect that someone more knowledgeable than will reply

Others may correct me here, but a couple of random thoughts:

  1. If receiveEvent is an interrupt handler, then I thought I read somewhere that Serial.print inside an interrupt handler was a bad idea/doesn't work. Maybe someone can confirm/deny this.
  2. In the world of I2C speak, I wonder what would happen if you do a Wire.write inside a receiveEvent? I'm trying to figure out what would be seen on the I2C bus in this case.

I'm not into that while (Serial.available()) waiting for Strings String readString; when all you really need is a char. I feel like you're going twice around the world for a shortcut and I feel your frustration with the Serial comms stuff - that was me when first starting out. I was convinced it didn't work correctly and then one day I came across this tutorial by Legend of Arduino, @Robin2 :

You don't want a while loop. You also don't need Serial.flush();.

I think you should be fine just passing chars to your accessory boards. If you don't plan on typing commands into a Serial monitor all the time once everything is done, there's no need to use commands like "on" or "off" when a simple char sent from the controller to the accessory boards will do. If you are pressing buttons that are read by your controller Arduino, you'll want to look at the IDE example StateChangeDetection so that the controller Arduino only sends out a char message to one of the accessory Arduinos if the button state has changed, so you're not spamming the Serial comms when that control button is just idle.

I managed to get the sketch to read the data, but I have to say that I am more confused that before

I replaced this

void receiveEvents(int numBytes) {
  Wire.begin();
  Serial.begin(9600);
  Serial.println(F("---> recieved events"));
  Serial.print(numBytes);
  Serial.println(F("bytes recieved"));
  Serial.print(F("recieved value : "));
  rd = Wire.read();
  Wire.write(rd);
  Serial.println(rd);
  // Print value of incoming data

with this copied from another sketch

void receiveEvent(int numBytes) {
  Wire.begin();        
  Serial.begin(9600); 
  Serial.println(F("---> recieved events"));
  Serial.print(numBytes);
  Serial.println(F("bytes recieved"));
  Serial.print(F("recieved value : "));
  rd = Wire.read();
  Wire.write(rd);
  Serial.println(rd);
  // Print value of incoming data

The only thing that I can see different is that the non-working sketch has 'void receiveEvents(int numBytes) {' instead of ' void receiveEvent(int numBytes) {', the difference being the extra 'S'

So I undid the copy and paste and restored the sketch to the non- functioning state, then deleted the offending 'S'. It still didn't work!

I then recopied and pasted the apparently now identical text from the working sketch, and it started working fine....

So with identical (apparently, I can't determine the difference) code in the sketch, one works and the other doesn't. Same hardware, and everything.

Is there some formatting of text or something that isn't visible but has an effect? If anyone can shed light on this, or point out something I am missing I'd love to understand

Certainly.

EDIT: @alto777 got me onto the ezButton library. If it's buttons that are sending chars from a central controller to accessory boards, this library is a great option and has been reliable for me.

It might look like (relevant sections copied from a project I'm working on)

#include <ezButton.h>

const byte returnButton = 2;
const byte changeDifficultyButton = 4;
const byte helpMenuButton = 5;
const byte reprogramButton = 7;
const byte retrieveButton = 8;

// make some buttons
ezButton aHelpMenuButton(helpMenuButton);
ezButton aDifficultyButton(changeDifficultyButton);
ezButton aReturnButton(returnButton);
ezButton aReprogramButton(reprogramButton);
ezButton aRetrieveButton(retrieveButton);

void setup() {
  Serial.begin(115200);
  aHelpMenuButton.setDebounceTime(25);
  aDifficultyButton.setDebounceTime(25);
  aReturnButton.setDebounceTime(25);
  aReprogramButton.setDebounceTime(25);
  aRetrieveButton.setDebounceTime(25);
  gameControl = 0; // set initial game level
}

void loop() {
  checkSwitches();
  switch (gameControl) {
    case 0:
      terminal();
      break;
    case 1:
      edensHacksMenu();
      break;
    case 2:
      displayTerminalProgress();
      password.reset();
      currentLength = 0;
      discardIncomingSerial();  // this needed to get clean password attempt
      gameControl = 0;
      break;
    case 3:
      disconnectFromPortal();
      gameControl = 1;
      break;
    case 4:
      hackerMenu();
      break;
    case 5:
      robcoTunnel();
      break;
    case 7:  // intermediate between hackerMenu() and robcoTunnel();
      longFastScroll();
      gameControl = 5;
      break;
    case 8:  // use for edensHacksMenu() back to normal terminal
      longFastScroll();
      gameControl = 2;
      break;
    case 9:  // use for clearing screen before disconnect robCoPortal
      longFastScroll();
      gameControl = 3;
      break;
    case 10:
      journalMenu();
      break;
      case 20:

      break;
    case 99:
      reprogramJournalPassword();
      break;
  }
}

void checkSwitches() {
  aHelpMenuButton.loop();
  aDifficultyButton.loop();
  aReturnButton.loop();
  aReprogramButton.loop();
  aRetrieveButton.loop();
  if (aHelpMenuButton.isReleased()) {
    quickScroll();
    Serial.println(F(" [ OR ] - RESETS PASSWORD"));
    Serial.println(F("    ? - INQUIRY MODE"));
    Serial.println(F("    + - SAVIOR REQUEST"));
    delay(3000);
    quickScroll();
    displayTerminalProgress();
  }
  if (aDifficultyButton.isReleased()) {
    gameDifficulty += 1;
    switch (gameDifficulty) {
      case 1:
        password = Password("48273");
        break;
      case 2:
        password = Password("BACKDOOR");
        break;
      default:
        password = Password("SNAKE");
        break;
    }
    if (gameDifficulty > 2) {
      gameDifficulty = 0;
    }
    robcoChangePasswordProtocol();
  }
  if (aReturnButton.isReleased()) {
    if (gameControl == 1) {
      gameControl = 0;
    } else if (gameControl == 0) {
      gameControl = 1;
    }
  }
  if (aReprogramButton.isReleased()) {
    longFastScroll();  // form feed clears screen
    lastGameControl = gameControl;
    gameControl = 99;
    Serial.println(F("User, select a new Pipboy Passcode..."));
  }
  if (aRetrieveButton.isReleased()) {
    longFastScroll();
    Serial.println("Pipboy Passcode:\t");
    Serial.println(EEPROM.get(0, buffer));
    delay(3000);
    longFastScroll();
  }
}

If you were sort of following along in this example, your central controller would maybe be reading buttons all the time, my checkSwitches(); function. In other words, everything you do involving the switches would be similar to the contents of that function and might be your entire void loop();

Point is, this button library is working well enough for my needs and does all the state change detection for you in the background.

You're really spamming the receiver here. In the IDE, look at File > Examples > 02. Digital > StateChangeDetection.

Just discovered that it does too!

I put this into the master

if (buttonState4 == LOW && buttonState5 == HIGH) {
        tal = 0;
      }
    
    if (buttonState4 == HIGH && buttonState5 == LOW) {
        tal = 1;
      }
    
    //{
      if (buttonState4 == LOW && buttonState5 == LOW) {
        tal = 2;

      }  //
    
    //{
      if (buttonState4 == HIGH && buttonState5 == HIGH) {
        tal = 3;
      }

and this in the slave

 if (rd == 1) {
    // turn LED on:
    digitalWrite(ledPin1, HIGH);
    digitalWrite(ledPin2, HIGH);
  }
  if (rd == 2) {
    /*} else {
    // turn LED off:*/
    digitalWrite(ledPin1, LOW);
    digitalWrite(ledPin2, HIGH);
  }
  if (rd == 0) {
    // turn LED on:
    digitalWrite(ledPin1, HIGH);
    digitalWrite(ledPin2, LOW);
  }
  if (rd == 3) {

  digitalWrite(ledPin1, LOW);
  digitalWrite(ledPin2, LOW);

and now that the sketch is actually reading an input it worked straight away.

It is a bit clunky, and as long as the variables aren't too many it should be manageable. However there must be a better way, a way of sending discrete data values to control individual LED's and things independently