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?
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
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..............
/*
* 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
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;
}
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
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.
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
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.
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"
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.