-
Notifications
You must be signed in to change notification settings - Fork 5
Arduino in Target mode
Not all Arduino boards support the I2C Target mode. The Arduino boards and compatible boards that support the I2C Target mode might have issues. Not every combination of Controller and Target might work.
The I2C bus is not the best bus to transfer data between processors. Serial/UART communication is easier and often better.
In Target mode the Arduino board can receive data or respond to a request.
If the Controller sends data:
Wire.beginTransmission( i2c_target_address);
Wire.write( data);
Wire.endTransmission();
then the Target can receive that data with the 'onReceive' handler.
void setup()
{
Wire.begin( i2c_target_address);
Wire.onReceive( receiveEvent);
}
void receiveEvent(int howMany)
{
byte data = Wire.read();
}
The receiveEvent() function is called from a interrupt routine, therefor it should be as short and as fast as possible.
Since the I2C bus uses packages of data, the code is easier when the packages have a fixed length, for example a 'struct'. In the receiveEvent() function a check can be done if the right amount of data was received.
// Suppose the Controller has send 4 bytes
void receiveEvent(int howMany)
{
byte data[4];
if( howMany == 4)
{
for( int i=0; i<4; i++)
{
data[i] = Wire.read();
}
}
}
To keep the receiveEvent() function as short as possible, the received data is often processed in the loop(). The data can be passed on to the loop() with a global buffer for the data and a 'bool' flag. Those variables should be 'volatile' to tell the compiler that the variables can be changed in a interrupt. The compiler will then adapt the optimizations for the code in the loop().
If the Controller requests data:
Wire.requestFrom( i2c_target_address, number_of_bytes);
then the Target runs the 'onRequest' handler.
void setup()
{
Wire.begin( i2c_target_address);
Wire.onRequest( requestEvent);
}
void requestEvent()
{
byte data = 0x55;
Wire.write( data);
}
The requestEvent() function is called from a interrupt routine, therefor it should be as short and as fast as possible.
While the requestEvent() function runs, the Target keeps the SCL line low to put the Controller on hold. That is called: clock pulse stretching.
The Controller calls Wire.requestFrom() to request a certain amount of bytes. The Target can return a number of bytes, but that does not have to be the same amount. It can be more or less than requested. The Controller can not even tell if it did receive the right amount of bytes. That is the result of the I2C protocol. The return value of Wire.requestFrom() is not the number of bytes that the Target has sent. The number is zero when there was a bus error.
I prefer to check if everything was okay, and then read all the data. The Controller still does not know that the Target has sent the right amount of bytes.
int n = Wire.requestFrom( i2c_target_address, number_of_bytes);
if( n == number_of_bytes)
{
// read the bytes
}
The Arduino as a I2C Target should respond as fast as possible. There are however libraries that turn off the interrupts for some time (Neopixel, FastLED, DHT, OneWire, and more) or use interrupts intensively themself (SoftwareSerial, VirtualWire, RadioHead in RH_ASK mode, and more). That will cause a problem if the there is a lot I2C communication.
When a Arduino board is set in Target mode, it can be a Controller as well. All the functions for the Controller can be used. However, two Controllers on the I2C bus is bound to go wrong. It is better to stay out of trouble and have only one Controller on the I2C bus.
It is demanding when a basic 16MHz Arduino board is set as a I2C Target. Even if the bus speed is only 100kHz and the onRequest and onReceive handlers are short and fast. When the Controller continuously pounds the Target with I2C sessions, then the code in the Target Arduino will not run properly anymore. A delay of a few milliseconds between the I2C sessions is enough for the Target to keep up.
When an Arduino is set in Target mode, it could emulate a sensor or a EEPROM. It would even be possible to insert an Arduino board between a Controller and a Target and be a person-in-the-middle. In almost all cases this will not work. The existing Controller must allow clock pulse stretching and there can be timing issues or many other problems.