ArduinoCore-API icon indicating copy to clipboard operation
ArduinoCore-API copied to clipboard

Suggest adding I2C_writeAnything to Wire library

Open nickgammon opened this issue 8 years ago • 7 comments

I have the following small template library on my page about I2C ( http://www.gammon.com.au/i2c ):

template <typename T> unsigned int I2C_writeAnything (const T& value)
  {
  Wire.write((byte *) &value, sizeof (value));
  return sizeof (value);
  }  // end of I2C_writeAnything

template <typename T> unsigned int I2C_readAnything(T& value)
  {
    byte * p = (byte*) &value;
    unsigned int i;
    for (i = 0; i < sizeof value; i++)
          *p++ = Wire.read();
    return i;
  }  // end of I2C_readAnything

It has been suggested to me that I request that you include this in the standard Wire library (Wire.h). This lets you more easily send things like floats or structs via I2C. For example:

    float fnum = 42.666;
    Wire.beginTransmission (SLAVE_ADDRESS);
    I2C_writeAnything (fnum);
    Wire.endTransmission ();

Being a template function it won't add any bloat unless you actually use it.

Related request: https://github.com/arduino/Arduino/issues/3692

nickgammon avatar Aug 17 '15 21:08 nickgammon

A note must be added that only ONE write operation can be used inside of the onRequestEvent() callback. Since the current Wire library resets the twi_txBufferLength value inside ..\library\Wire\utility\twi.c during the onRequestEvent() callback.

uint8_t twi_transmit(const uint8_t* data, uint8_t length){
// stuff
twi_txBufferLength = length; 
// stuff
}

Which is call from ..\library\Wire.cpp


// must be called in:
// slave tx event callback
// or after beginTransmission(address)
size_t TwoWire::write(const uint8_t *data, size_t quantity)
{
  if(transmitting){
  // in master transmitter mode
    for(size_t i = 0; i < quantity; ++i){
      write(data[i]);
    }
  }else{
  // in slave send mode
    // reply to master
    twi_transmit(data, quantity);
  }
  return quantity;
}

The boolean transmitting is false while in slave mode.

Chuck.

stickbreaker avatar Aug 18 '15 23:08 stickbreaker

Quite right, which is why I changed I2C_writeAnything to do a single write and not byte-by-byte. But of course you need to alert the end-user to only call that (I2C_writeAnything) once in a request event. However the point of it in the first place is that it simplifies sending (and receiving) a struct.

nickgammon avatar Aug 19 '15 00:08 nickgammon

I like your coding, I was just recommending that the Single write restriction should be prominently marked, or the underlying code fixed. It is a sore point for me. I have customized Wire to support repeated calls to onRequestEvent() to allow Slave mode Arduino I2C to function like 24LCxx series EEPROMs, and added bus failure timeouts. But these fixes are deemed too confusing for novice usage.
Personally, I think that a more complex function is easier to use:

enum STAGE { twi_reStart,twi_start};
void onRequestEvent(STAGE stage){// just use a single byte Wire.write() to support unlimited lengths 
switch(stage){
  twi_start : ; // slave read bare, no register address set, Start from begining
  twi_reStart : ; // slave read, continue from prior register address
  }
}

this coding will support

Wire.begin(addr);
Wire.write(z);//register address
Wire.endTransmission(false); // set register address (z)
Wire.requestFrom(addr,x,false); // read from (z) for (x) bytes
Wire.requestFrom(addr,xmore,false); // read from (z+x) for (xmore) bytes
Wire.requestFrom(addr,xevenmore,true); // read from (z+x+xmore) for (xevenmore) bytes, and end transaction
//and now that a stop has been issued on the I2C buss
Wire.requestFrom(addr,x,false); // start reading from address (0), controlled by twi_start Case of onRequestevent()
Wire.requestFrom(addr,xmore,true);  // read from (0+x) for (xmore) bytes.

To me this added complexity makes it easier for a novice to effectively uses I2C slave mode to transfer more than one value.

Chuck.

stickbreaker avatar Aug 19 '15 00:08 stickbreaker

or the underlying code fixed

Quite possibly however that goes outside the scope of this particular change request. :)

nickgammon avatar Aug 19 '15 03:08 nickgammon

A note must be added that only ONE write operation can be used inside of the onRequestEvent()

I just discovered this. But this just further supports adding @nickgammon's template since it allows one to do:

  uint16_t temps[6];
...
  I2C_writeAnything(temps);

to very easily send 6 uint16_ts.

brianjmurrell avatar Jan 26 '18 15:01 brianjmurrell

It's also worth pointing out that there are 153 "code result" matches on GitHub of use of I2C_writeAnything(). It's very popular!

brianjmurrell avatar Jan 26 '18 15:01 brianjmurrell

Some of them use the older version which does multiple writes. Oh well. :(

My code for I2C_Anything is now on GitHub:

https://github.com/nickgammon/I2C_Anything

nickgammon avatar Jan 27 '18 01:01 nickgammon