Hello all,
Looking to create a network of Arduino's that each include inputs and outputs. Functionality of the system being that any input on any arduino can action an output on any other arduino.
This post is to outline my first thoughts and garner your opinions.
System comprises of one Master which will handle the I2C traffic, polling slaves, re-writing variables.
(forgot to include trigger line but simply one more buss line so slaves can tell master something has changed)
Basic flow to be:
- Slave button pressed (momentary)
- Local State Change Detection and change local binary variable
- Slave pulls Trigger line LOW momentarily
- Master polls Variables from all slaves in succession MasterReader
- Master compares results to find what has changed
- Master State change detection to change binary output variable (based on what has changed)
- Master writes output variables to all slaves in succession MasterWriter
- Slaves receive and change their Output variables and process action locally.
Using this approach I can break the programming (not my game) down in to manageable chunks.
Any thoughts of yours are appreciated = especially links to any code I can bastardise.
Your help appreciated
Adam
1. Your idea sounds good; schematic is good; the project could be implemented straightforward.
2. Skeleton Codes
(1) I2C Master UNO Codes (skeleton):
#include<Wire.h>
volatile bool flag1 = false;
volatile bool flag2 = false;
void setup()
{
Serial.begin(9600);
Wire.begin(0x08); //a provision for the Slave to access Now Master as Slave if needed
Wire.onReceive(receiveEvent);
Wire.onRequest(sendEvent);
}
void loop()
{
if(flag1 == true)
{
//take actions as needed
flag1 = false;
}
if(flag2 == true)
{
//take actions as needed
flag2 = false;
}
}
void receiveEvent(int howMany)
{
flag1 = true;
}
void sendEvent (int howMany)
{
falg2 = true;
}
(2) I2C Slave-1 NANO Codes (skeletons):
#include<Wire.h>
volatile bool flag1 = false;
volatile bool flag2 = false;
void setup()
{
Serial.begin(9600);
Wire.begin(0x09); //Slave-1 adress
Wire.onReceive(receiveEvent);
Wire.onRequest(sendEvent);
}
void loop()
{
if(flag1 == true)
{
//take actions as needed
flag1 = false;
}
if(flag2 == true)
{
//take actions as needed
flag2 = false;
}
}
void receiveEvent(int howMany)
{
flag1 = true;
}
void sendEvent (int howMany)
{
falg2 = true;
}
3. Connect only Master UNO and Slave-1 NANO using external 2.2k pull-up terminated I2C Bus.
4. Write codes based on the skeleton of Step-2 to implement the following task: Press Button B1_1; the closed condition will be relayed to Master; in response the Master will send command to Slave-1 to ignit led L1_1. Try your codes; if they are not working, place them here (with code tags: </>) in the Forum for corrections.
1 Like
Many thanks for your prompt and helpful reply;
Please find code I've written below - sticking point being how to pass BOOL variable values from the slave in the correct order? And then pull them out and update the variables in the master
MASTER
//------------------------------------------------
// Arduino UNO I2C Master Controller - by Adam Brackpool
//
// I2C Master Controller is used to control the I2C 'traffic' between any number of 'I/O' Slave Arduinos on the network
// Simple usage would be for more than 2 arduinos to pass variable information between each other - ie button presses and light LEDs
// I2C protocol is comprised of 2 wires, SDA and SCL, and includes no Arbitrage of buss line ownership.
// Therefore, this system employs a 3rd wire, denoted as triggerWire, which is connected to pin #6 on every arduino with a 10k pullup resistor to 5V
// When a Slave device has new information to pass, it pulls this line low, triggering the Master Controller to poll each Slave successively.
// The Master updates it bank of variables and then Writes these to all salves that require the information
//
// Consideration may be made to allocate an individual, unique trigger pin on the master to each slave so it only polls the Slave with new information
//
// Git Repo for project at:
// https://github.com/adds666/CamperControl
// All code is bastardised to some extent, credit is given in the git repo
// Slave1 has an I2C address of 0x09 - can I get this in to the code?
//------------------------------------------------ - //
#include<Wire.h>
volatile bool heaterOn = false;
volatile bool lightsOn = false;
const int triggerWire = 6; // '3rd' buss wire in the I2C buss system - Pulled high with 10k resistors and low by other arduinos to trigger 'Polling'
void setup()
{
Serial.begin(9600);
Wire.begin(0x08); //a provision for the Slave to access Now Master as Slave if needed
pinMode(triggerWire, INPUT);
}
void loop(){
if (digitalRead(triggerWire == LOW)) { // If the triggerWire is pulled LOW (by a Slave) instigate polling of data
pollSlaves();
}
}
void pollSlaves() {
Wire.requestFrom(0x09, 2); // request 2 bytes (number of Variables) from device address 0x09
while (Wire.available()) { // slave may send less than requested
// HOW DO I GET THE TWO BOOL VARIABLES FROM THE SLAVE IN TO THE VARIABLES HERE
char c = Wire.read(); // receive a byte as character
Serial.print(c); // print the character
}
}
SLAVE1
//------------------------------------------------
// Arduino UNO I2C_Slave1 - by Adam Brackpool
//
// I2C Slave1 is a slave device in an I2C network
// It monitors the button presses of two momentary close buttons and if pressed
// Changes the state of a variable which should then be passed to the master
// https://www.arduino.cc/en/Tutorial/StateChangeDetection
// In order to tell the master that it has new information, It pulls pin #6 LOW and triggers
// A 'polling' function in the master to retrieve the variables
//
// Git Repo for project at:
// https://github.com/adds666/CamperControl
// All code is bastardised to some extent, credit is given in the git repo
// Master has an I2C address of 0x08 - can I get this in to the code?
// Slave1 has an I2C address of 0x09 - can I get this in to the code?
//------------------------------------------------ - //
#include <Wire.h>
volatile bool heaterOn = false;
volatile bool lightsOn = false;
const int triggerWire = 6; // '3rd' buss wire in the I2C buss system - Pulled high with 10k resistors and low by other arduinos to trigger 'Polling'
const int heaterButton = 7; // momentary switch on 7, other side connected to ground
const int lightsButton = 8; // momentary button on 8, otherside connected to ground
int heaterButtonState = 0; // current state of the heaterButton
int lastHeaterButtonState = 0; // previous state of the heaterButton
int lightsButtonState = 0; // current state of the lightsButton
int lastLightsButtonState = 0; // previous state of the lightsButton
unsigned long previousMillis = 0; // will store last time triggerWire changed
const long interval = 200; // interval at which to pull triggerWire LOW - may need tweaking?
void setup() {
Wire.begin(0x09); // join i2c bus with address 0x09
Wire.onRequest(requestEvent); // register event
pinMode(heaterButton, INPUT);
pinMode(lightsButton, INPUT);
}
void loop() { // Look at all the buttons - act if one is pressed https://www.arduino.cc/en/Tutorial/StateChangeDetection
heaterButtonState = digitalRead(heaterButton); // Read the pushbutton input pin
if (heaterButtonState != lastHeaterButtonState) { // compare the heaterButtonState to its previous state
if (heaterButtonState == HIGH) { // If high then the button went from Off to On
heaterOn = !heaterOn; // Toggle Boolean variable
pollRequest(); // Request the Master retrieves variables via the triggerWire
}
}
delay(20); // for debounce on the button
lastHeaterButtonState = heaterButtonState;
lightsButtonState = digitalRead(lightsButton); // Read the pushbutton input pin
if (lightsButtonState != lastLightsButtonState) { // compare the lightsButtonState to its previous state
if (lightsButtonState == HIGH) { // If high then the button went from Off to On
lightsOn = !lightsOn; // Toggle Boolean variable
pollRequest(); // Request the Master retrieves variables via the triggerWire
}
}
delay(20); // for debounce on the button
lastLightsButtonState = lightsButtonState;
}
void pollRequest() {
digitalWrite(triggerWire, LOW);
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) { //
previousMillis = currentMillis;
digitalWrite(triggerWire, HIGH);
}
}
// function that executes whenever data is requested by master
// this function is registered as an event, see setup()
void requestEvent() {
Wire.write("hello "); // HOW DO I SEND THROUGH THE 2 BOOL VALUES OF THE VAR IN A SPECIFIC ORDER???
}
I sense some confusing here.
First of all, the MasterReader and MasterWriter are bad examples.
It is best to send and receive binary data. So you have to define what kind of data is requested and send by the Master.
Is that a single byte, or multiple bytes or is the length not defined and what kind of data is it ?
The option that a Slave can send something to the Master is very dangerous, that is bound to go wrong some day.
So I suggest not to use the onRequest and onReceive handler in the Master, and only Wire.begin() without Slave address.
The Master can poll a interrupt line or poll via I2C the Slaves, that is okay.
Can you tell what the difference is between polling the Slave continuously all the time, or only when the interrupt signal is active ? They might have the same response time, so there is not much to gain with a interrupt signal.
For the hardware, you have to calculate the combined value of all the pullup resistors for SDA and SCL.
Keep the wires short, for example 20 cm to each Slave.
Do not put SDA and SCL (next to each other) in a flat ribbon cable.
Many thanks for your reply @Koepel.
Do you have an example of sending binary data? Or links to explain binary data over sending the variables as Bool (or int as I've now discovered would be better with Wire.write).
Initially I'd like to only deal with binary on off variables.
Polling wise, I'd envisage the future state having a good number of slaves (maybe 10 or more), so thought it best to only poll on a slave command when something has changed.
Good tip on the proximity of SDA and SCL - thank you.
The most common way is to have a 'struct' with integers, bytes, floats, and whatever is needed. The Master and the Slave must use the same 'struct' or course.
An other option is that the Slave simulates registers, like the registers that sensors have. Some registers can be read-only or read-write. This requires a lot more programming.
Do you only want to send a 'bool' variable ? A single byte is the easiest for a 8-bit microcontroller.
Below is a framework for Master-Slave with I2C for a single byte.
Try to keep the 'onReceive' and 'onRequest' very short and do not call the Serial library from within those functions. They are interrupt functions and everything you do in there will make the I2C communication less reliable.
The next code has only light error checking, but that should be more than enough.
The Arduino Due can trigger a 'receiveEvent' call with the 'howMany' parameter being zero. To prevent problems, I have a check there to see if the number of received bytes is one. If you use more bytes, then you have to change that as well.
Master
byte data[2]; // received bytes from Slaves
void setup()
{
...
Wire.begin(); // Only Master mode
...
}
void loop()
{
...
// Example code, how to request data from two Slaves
for( int i = 0; i <2; i++) // requesting 2 Slaves for now
{
int n = Wire.requestFrom( i + 0x09, 1); // request 1 byte
if( n == 1) // check if the Slave did return the requested byte
{
data[i] = Wire.read();
}
else
{
// Something is wrong with the I2C communication with this Slave
Serial.print( "Something wrong with Slave ");
Serial.println( i + 0x09, HEX);
}
}
...
// Example code, how to send data to first Slaves
Wire.beginTransmission( 0x09); // To Slave 0x09
Wire.write( 0x01); // Data to send
Wire.endTransmission();
...
}
Slave
volatile byte dataIn;
volatile byte dataOut;
volatile bool dataReceived; // flag to notify that something is received.
void setup()
{
...
Wire.begin(0x09); // join i2c bus with address 0x09
Wire.onRequest( requestEvent); // register event
Wire.onReceive( receiveEvent);
...
}
void loop()
{
if( dataReceived)
{
Serial.print( "Data received: ");
Serial.println( dataIn, HEX);
dataReceived = false; // reset the flag
}
}
void requestEvent() // keep this function as short as possible
{
Wire.write( dataOut);
}
void receiveEvent( int howMany) // keep this function as short as possible
{
if( howMany == 1) // extra check prevents problems.
{
dataIn = Wire.read();
dataReceived = true; // notify the code in the loop() to do something with the data
}
}