Best way for 2 way communication between 3 Dues?

Hello,

I'm working on a project that will have 3 Dues communicating together, or rather 1 master Due and 2 slave Dues, but I need 2 way communication between the slaves and the master. I need them to be able to grab master's attention and send data.

Looking around it seems like Multi Master I2C would be great except it doesn't work on Due since Wire is incomplete.
SPI might be a choice but I haven't found any examples showing slaves grabbing master's attention.
I'm thinking Serial might be my best bet since Due has 4 UART ports. My concern is that only one UART port can be active at one time and I might miss a signal from one of the slaves. Maybe a handshake setup to safeguard data?

Data transfer will be mainly integers.

Any suggestions? What has work best for you?

Thanks!
Artem.

FWIW, I am going down this same path.

This issue is that if you are a slave on a Due, the Wire.write seems to spin. I believe it is because of the internal status variable (SLAVE_IDLE,MASTER_IDLE etc) not having the status 'correct' for what is in fact a master write. Havn't had time (such a fleeting thing) to fully investigate!

Update: I believe the write works - it is the end_transmission that spins.

I have the full M3 I2c doc printed out and will be investigating - because I wanna get this working myself.

In a perfect world I will end up with a new library with a load if #IFDEFS that combine the ARM and standard Arduino code.

1st step is to see if I can get Multi master working.
2nd step is to see if I can get broadcast working - because this is such a cute feature
3rd step is to combine the libraries.

Big hurdle is that while I am a fair C programmer, and I can read and make sense of C++ I am not really a C++ programmer.

Possible hurdle overcommer is that I can program in many dialects of assembler (some you will never heard of such a ICL 1900 Plan) plus VB & Cobol (anyone heard of that :slight_smile:

Stan

You could use SPI for the master to talk to two slaves, and have an "attention" pin
from each slave that causes an interrupt on the master (so two input pins on the master).

Whenever a slave interrupts the master it sets a flag to tell the main loop to send a
null SPI packet to that slave (in order to read the slave's "status" byte which would
indicate what the slave wants to communicate).

If every slave SPI package response starts with a status byte like this the interrupt
isn't needed really, you just arrange that the master polls each slave regularly to see
if it wants anything.

Its a pretty common convention for the first byte in an SPI transaction to be a
command from the master and the slave sends back its status automatically.

On the slave side whenever the "status" changes update the relevant SPI register so
its sent back beck transaction (but defer this till any current transaction has ended).
Optionally pulse an interrupt line.

You can replace SPI by I2C in the above, with care.

Well - I have some good news and some bad news - but still working on it.

I have modified a copy of the Due wire library and can now get a Due to be a slave and still perform the start/write/end transmission cycle.

(for my tests I am using a Due to a Mega - the mega does multi-master very goodly).

After my changes however, when the mega tries to send a message to the Due, the due immediately sends a Nack. Obviously I have still got a little work to do..............

Stan

Ok - I now have a Due and a Mega both issuing a wire.begin with an address happily sending date via a wire write back and forth!

I have modified the wire library and called it W2 and put it into the sketchbook folder. All my changes are commented with // SWG:

I have not tested the request from and on request as yet....

Please let me know how you get on!

If all this works, I may need some advice on checking this into the official Arduino Due code stream...

The next replies (because I cannot put it all into one reply are the library code!

Stan

I have 2 sketches as follows;

  1. Due Sketch.
#include <W2.h>
 
#define I2C_ADDRESS_OTHER 0x10
#define I2C_ADDRESS_ME 0x20
byte result=0;

void setup() {
   pinMode(13, OUTPUT);
  digitalWrite(13, HIGH);
  delay(7000);
  digitalWrite(13, LOW);
Serial.begin(9600);
// Wire.begin(I2C_ADDRESS_ME);
Wire.begin(I2C_ADDRESS_ME);
 Wire.onReceive(receiveI2C);  

}
 
void loop() {
 delay(5000);
 
Serial.println("About to begin transmission");
 Wire.beginTransmission(I2C_ADDRESS_OTHER);
 
Serial.println("About to Start Write");
 Wire.write("hello world from Due to Uno");

Serial.println("About to end transmission");
result=Wire.endTransmission(true);
Serial.println("Transmission ended");
   Serial.print(result,HEX);
  Serial.println(" ");
}
 
void receiveI2C(int howMany) {
 digitalWrite(13, HIGH);
  while (Wire.available() > 0) {
  char c = Wire.read();
 Serial.print(c);
 }
 Serial.println();
 digitalWrite(13, LOW);
}

Mega Sketch

#include <Wire.h>
 
#define I2C_ADDRESS_OTHER 0x20
#define I2C_ADDRESS_ME 0x10
byte result=0;
void setup() {
 Serial.begin(9600);
 delay(5000);
Serial.println("Starting");
 Wire.begin(I2C_ADDRESS_ME);
 Wire.onReceive(receiveI2C);
  pinMode(13, OUTPUT);
  digitalWrite(13, LOW);
}
 
void loop() {
 delay(1000);
 digitalWrite(13, HIGH);
 
Wire.beginTransmission(I2C_ADDRESS_OTHER);
Wire.write("hello world from UNO to DUE");
result=Wire.endTransmission();
  Serial.print(result,HEX);
 delay(100);
 digitalWrite(13, LOW);
 
}
 
void receiveI2C(int howMany) {
    Serial.print(howMany,HEX);
  Serial.println(" ");
  digitalWrite(13, HIGH);
  delay(10);
  digitalWrite(13,LOW);
  delay(10);
  digitalWrite(13,HIGH);
  
 while (Wire.available() > 0) {
  char c = Wire.read();
  Serial.print(c);
 }
 Serial.println();

  digitalWrite(13, LOW);
}

W2.h

/*
 * TwoWire.h - TWI/I2C library for Arduino Due
 * Copyright (c) 2011 Cristian Maglie <c.maglie@bug.st>.
 * All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#ifndef TwoWire_h
#define TwoWire_h

// Include Atmel CMSIS driver
#include <include/twi.h>

#include "Stream.h"
#include "variant.h"

#define BUFFER_LENGTH 32

class TwoWire : public Stream {
public:
	TwoWire(Twi *twi, void(*begin_cb)(void));
	void begin();
	void begin(uint8_t);
	void begin(int);
	void setClock(uint32_t);
	void beginTransmission(uint8_t);
	void beginTransmission(int);
	uint8_t endTransmission(void);
    uint8_t endTransmission(uint8_t);
	uint8_t requestFrom(uint8_t, uint8_t);
    uint8_t requestFrom(uint8_t, uint8_t, uint8_t);
	uint8_t requestFrom(int, int);
    uint8_t requestFrom(int, int, int);
	virtual size_t write(uint8_t);
	virtual size_t write(const uint8_t *, size_t);
	virtual int available(void);
	virtual int read(void);
	virtual int peek(void);
	virtual void flush(void);
	void onReceive(void(*)(int));
	void onRequest(void(*)(void));

    inline size_t write(unsigned long n) { return write((uint8_t)n); }
    inline size_t write(long n) { return write((uint8_t)n); }
    inline size_t write(unsigned int n) { return write((uint8_t)n); }
    inline size_t write(int n) { return write((uint8_t)n); }
    using Print::write;

	void onService(void);

private:
	// RX Buffer
	uint8_t rxBuffer[BUFFER_LENGTH];
	uint8_t rxBufferIndex;
	uint8_t rxBufferLength;

	// TX Buffer
	uint8_t txAddress;
	uint8_t txBuffer[BUFFER_LENGTH];
	uint8_t txBufferLength;

	// Service buffer
	uint8_t srvBuffer[BUFFER_LENGTH];
	uint8_t srvBufferIndex;
	uint8_t srvBufferLength;

	// Callback user functions
	void (*onRequestCallback)(void);
	void (*onReceiveCallback)(int);

	// Called before initialization
	void (*onBeginCallback)(void);

	// TWI instance
	Twi *twi;

	// TWI state
	enum TwoWireStatus {
		UNINITIALIZED,
		MASTER_IDLE,
		MASTER_SEND,
		MASTER_RECV,
		SLAVE_IDLE,
		SLAVE_RECV,
		SLAVE_SEND
	};
	TwoWireStatus status;
	//
	// SWG: If address provided on wire.begin, save it here
	//
    uint8_t slave_address;
	//

	// TWI clock frequency
	static const uint32_t TWI_CLOCK = 100000;
	uint32_t twiClock;

	// Timeouts (
	static const uint32_t RECV_TIMEOUT = 100000;
	static const uint32_t XMIT_TIMEOUT = 100000;
};

#if WIRE_INTERFACES_COUNT > 0
extern TwoWire Wire;
#endif
#if WIRE_INTERFACES_COUNT > 1
extern TwoWire Wire1;
#endif

#endif

I am unable to post the complete wire.cpp

The changes are as follows;

void TwoWire::begin(void) {
	if (onBeginCallback)
		onBeginCallback();

	// Disable PDC channel
	twi->TWI_PTCR = UART_PTCR_RXTDIS | UART_PTCR_TXTDIS;

	TWI_ConfigureMaster(twi, twiClock, VARIANT_MCK);
	status = MASTER_IDLE;
	//
	// SWG: If no address supplied, we are pure master
	//
	slave_address=0;
}

void TwoWire::begin(uint8_t address) {
	if (onBeginCallback)
		onBeginCallback();

	// Disable PDC channel
	twi->TWI_PTCR = UART_PTCR_RXTDIS | UART_PTCR_TXTDIS;

	TWI_ConfigureSlave(twi, address);
	status = SLAVE_IDLE;
	TWI_EnableIt(twi, TWI_IER_SVACC);
	//| TWI_IER_RXRDY | TWI_IER_TXRDY	| TWI_IER_TXCOMP);
	//
	// SWG: If slave address provided, save it for when we slip back into slave mode after wire.endtransmission
	//
	slave_address=address;
}
void TwoWire::beginTransmission(uint8_t address) {
	status = MASTER_SEND;

	// save address of target and empty buffer
	txAddress = address;
	txBufferLength = 0;
//
// SWG: We must configure for Master mode if we are sending data
//
   TWI_ConfigureMaster(twi, twiClock, VARIANT_MCK);
//
}
uint8_t TwoWire::endTransmission(uint8_t sendStop) {
	uint8_t error = 0;
	// transmit buffer (blocking)
	TWI_StartWrite(twi, txAddress, 0, 0, txBuffer[0]);
	if (!TWI_WaitByteSent(twi, XMIT_TIMEOUT))
		error = 2;	// error, got NACK on address transmit
	
	if (error == 0) {
		uint16_t sent = 1;
		while (sent < txBufferLength) {
			TWI_WriteByte(twi, txBuffer[sent++]);
			if (!TWI_WaitByteSent(twi, XMIT_TIMEOUT))
				error = 3;	// error, got NACK during data transmmit
		}
	}
	
	if (error == 0) {
		TWI_Stop(twi);
		if (!TWI_WaitTransferComplete(twi, XMIT_TIMEOUT))
			error = 4;	// error, finishing up
	}

	txBufferLength = 0;		// empty buffer
	status = MASTER_IDLE;
	
	//
	// SWG: If slave_address provided, go back to slave mode
	//
	if (slave_address != 0) {
	   TWI_ConfigureSlave(twi, slave_address);
	   	status = SLAVE_IDLE;
	   	TWI_DisableIt(twi, TWI_IDR_SVACC);
	    TWI_EnableIt(twi, TWI_IER_SVACC);
	}
	return error;
}

Stan, that looks like good work! Keep us updated. You can always post the whole modified file to pastebin.com and then share a link with us.

Mark, thanks for the advice. I'll give SPI a shot and see if I can set up the system. Do you have any good examples that I can follow?

Thanks!

Well if I haven't screwed this up, w2.cpp can be found at w2.cpp - Pastebin.com

Stan

Hey thanks! Can you upload the W2.h file too please? I'll give them a try.

Edit: I copied your posted one from the top.

Do I put the W2 library into hardware SAM or AVR? or Documents Libraries?

For the moment I just have it in my local libraries (Sketchbook)

On a Mac..... (Although my day job is a .net/sql server/ssrs developer, I have no idea where it goes in a PC (Sorry)

Documents/arduino/librarys/w2

In w2 put w2.cpp and w2.h

Didn't put w2.h into pastebin because I managed to post it all here.

Update: Reiterating the obvious - restart the IDE after updating the libraries.

Stan

artems:
I'm thinking Serial might be my best bet since Due has 4 UART ports. My concern is that only one UART port can be active at one time and I might miss a signal from one of the slaves. Maybe a handshake setup to safeguard data?

Hi Stan,

I'm working on a project with a Master Due, 2 Slave Dues, 2 IMU Sesnors and a Raspberry Pi - all inter connected through serial! The serial ports on the Due all have individual buffers which fill up on an interrupt basis. These buffers can be checked using

NumberOfBytesInBuffer0 = Serial.avaiable()
NumberOfBytesInBuffer1 = Serial1.avaiable()

etc.

This way you can check any serial port for data available. You're concern about missing data is fine as long as you are checking these buffers often enough and you're not sending more than 64bytes (Standard buffer size) at one time. I use a start an end character in all my transmissions to ensure I have a correct packet.

I.e. Slave sends: "StartCharacter, Command,Data1,Data2,EndCharacter**NewLine"

This way I can parse the buffer and ensure I have correct data - (however a checksum would be a more ideal solution but I haven't gotten around to it yet). With this method you can communicate on all serial ports at the same time. The only disadvantage is that the I2C library has Nak/Ack support and is possibly more robust in code (I found I2C hardware to be an issue).

I like serial because to me it's rather simple. Maybe this would be a fall back option if I2C lets you down.

Good Luck,

JW

Hi JW - for the moment I seem to have licked the multi-master issue.

Like you I am contemplating wrapping a small protocol around the packets.

Something like {sender},{Packet number},{Number packets} Payload,{checksum}

For the checksum I was thinking of just looting the same checksum routine used in GPS.

Having the sender is useful because with a whole lot of out of band stuff you don't always know where it came from.
Packet number and number packets would allow say a long message to be split.

(Just mulling over this)

I wanna check the ARM Spec - for multicast, if I can identify the packet (think I can) as a multicast packet, I would like to implement a onMulticastReceive callback.

Again I need to check the spec - to see if I can identify where the packet came from - that then saves a byte in the total packet :slight_smile:

Study, study, study - never ends

Stan

PS - the first step is to complete the testing for the mods I have done to ensure I haven't broken anything.....

Looks like having the Due and the Uno using Start Transmission, Write, end transmission works good.

Once I bring request from into the picture things go pear shaped.

This is going to take a bit longer - but unless anyone finds a problem with what I posted, it does provide a solution to multi master with a Due - and probably due to Due and Due to ATMEL or ATMEL to Due - providing the only transactions are as mentioned above;

start Transmission
Write
End Transmission

To be honest, you can fake the request from with a message to the 'slave' saying ' send me some data' - and the slave can respond with its start transmission, write and end transmission.

Stan

JWScotSat:

artems:
I'm thinking Serial might be my best bet since Due has 4 UART ports. My concern is that only one UART port can be active at one time and I might miss a signal from one of the slaves. Maybe a handshake setup to safeguard data?

Hi Stan,

I'm working on a project with a Master Due, 2 Slave Dues, 2 IMU Sesnors and a Raspberry Pi - all inter connected through serial! The serial ports on the Due all have individual buffers which fill up on an interrupt basis. These buffers can be checked using

NumberOfBytesInBuffer0 = Serial.avaiable()

NumberOfBytesInBuffer1 = Serial1.avaiable()




etc.

This way you can check any serial port for data available. You're concern about missing data is fine as long as you are checking these buffers often enough and you're not sending more than 64bytes (Standard buffer size) at one time. I use a start an end character in all my transmissions to ensure I have a correct packet.

I.e. Slave sends: "*StartCharacter*, Command,Data1,Data2,*EndCharacter**NewLine*"

This way I can parse the buffer and ensure I have correct data - (however a checksum would be a more ideal solution but I haven't gotten around to it yet). With this method you can communicate on all serial ports at the same time. The only disadvantage is that the I2C library has Nak/Ack support and is possibly more robust in code (I found I2C hardware to be an issue).

I like serial because to me it's rather simple. Maybe this would be a fall back option if I2C lets you down.

Good Luck,

JW

Hey JW, that does sound look a good solution. Any chance you can post some example code for your 5 way serial communication?

I've been working on getting i2c to work for what I needed it do and I had some minor success but I'm finding the DUE hardware to be fickle. If I can't get i2c to work to the level that I need it, I might switch to serial, especially based on your write up.
Thanks!

artems:
Hey JW, that does sound look a good solution. Any chance you can post some example code for your 5 way serial communication?

I've been working on getting i2c to work for what I needed it do and I had some minor success but I'm finding the DUE hardware to be fickle. If I can't get i2c to work to the level that I need it, I might switch to serial, especially based on your write up.
Thanks!

Hi Artems,

I found I2C to be very difficult once I moved from the bench to my project. It seems signal noise is a huge factor in I2C and I couldn't find a solution in the time scale I had.

If I had all the time in the world, I'd love to write a library to maintain all of the serial work I've done. However, once again my available time scales are limited. I came across a library that performs similar to what I'm doing with serial and is designed to be "easy"

Easy Transfer Library: EasyTransfer Arduino Library « The Mind of Bill Porter

Hopefully this will give you a good head start in the serial world.

A simple tip that might help to keep your mind straight is to name your serial ports with defines. i.e. :

#define SerialSlaveDue Serial1
#define SerialGyroSensor Serial2
#define SerialPC Serial //this allows to quickly switch from programming port serial to SerialUSB without going through your code

//These defines make serial calls more clear in you code. i.e:

SerialGyroSensor.begin(11200);

charbyte = SerialSlaveDue.read();

SerialPC.println("Printing to serial port of the PC");


//etc, etc.

Hope this helps,

JW