0

I got an Arduino Uno set up as an I2C master to an ATtiny85 slave, using the Wire (Master) and TinyWireS (Slave) libraries.

Communication works good. I have been able to keep the network running correctly for hours. However, I have not been able to let the master request more then a single byte from the slave.

Using the code below the master asks for 4 bytes from the slave, but in my serial console I get only an actual value for the first bytes. The next three bytes read 255.

How can I request multiple bytes from the slave, using the Wire library?


Master (Arduino Uno)

#include <Wire.h>

#define I2C_MASTER_ADDR 0x04
#define I2C_SLAVE_ADDR 0x05

int pollInterval = 700;//Milliseconds
unsigned long lastPoll = 0;


void setup() 
{
  Wire.begin(I2C_MASTER_ADDR);  // join i2c bus
  Serial.begin(115200); 
  Serial.println("Setup complete");
}

/*
 * The main loop
 */
int i = 0;

void loop() 
{

  //Writing to the slave
  if( (millis()-lastPoll) > pollInterval)
  {
    Wire.beginTransmission(I2C_SLAVE_ADDR);
    Wire.write(0x01);//Register to start at
    switch(i)
    {
      case 0:
        Wire.write(255);
        Wire.write(0);
        Wire.write(0);
        i++;
        break;
      case 1:
        Wire.write(0);
        Wire.write(255);
        Wire.write(0);
        i++;
        break;
      case 2:
        Wire.write(0);
        Wire.write(0);
        Wire.write(255);
        i = 0;
        break;
    }
    Wire.endTransmission();

    delay(1);//Dont let the slave panic

    //Set the register pointer back to 0x01, preparing for a read
    Wire.beginTransmission(I2C_SLAVE_ADDR);
    Wire.write(0x00);//Register to start at
    Wire.endTransmission();
    delay(1);//Dont let the slave panic

    //Get values from the three registers up from 0x01
    Wire.requestFrom(I2C_SLAVE_ADDR, 4);//Request N bytes
    while (Wire.available())
    {
      uint8_t next_byte = Wire.read();
      Serial.print(next_byte);Serial.print(" ");    
    }
    Serial.println("\n");

    lastPoll = millis();
  }//End if time to poll again

}//End loop

Slave - ATTiny85

#include <EEPROM.h>
#include <TinyWireS.h>
#include <Bounce2.h>
#include <WS2812.h>
#ifdef __AVR__ //Which will be true for ATtiny85
  #include <avr/power.h>
#endif

#define I2C_SLAVE_DEFAULT_ADDR 0x05
#define BUTTON_DEBOUNCE 5//Debounce milliseconds

#define NEOPIXEL_PIN  1
#define BUTTON_PIN    3

#define NUMPIXELS     1

/*
 * I2C Registers
 * 
 * Register map:
 * 0x00 - Button state
 * 0x01 - led value red
 * 0x02 - led value green
 * 0x03 - led value blue
 * 
 * Total size: 4
 */
const byte reg_size = 4;
volatile uint16_t i2c_regs[reg_size];

/*
 * Internal variables
 */
cRGB value;
volatile boolean led_needs_update = false;
volatile byte reg_position;

/*
 * Initialize instances/classes
 */
Bounce button = Bounce();
WS2812 led(NUMPIXELS);

void setup() 
{
  //Start I2C
  //uint8_t _device_addr = EEPROM_DATA::get_device_addr();
  TinyWireS.begin(I2C_SLAVE_DEFAULT_ADDR);
  TinyWireS.onReceive(i2cReceiveEvent);
  TinyWireS.onRequest(i2cRequestEvent);

  //Start Led
  led.setOutput(NEOPIXEL_PIN);
  value.b = 255; value.g = 0; value.r = 0;
  led.set_crgb_at(0, value); //Set value at LED found at index 0
  led.sync(); // Sends the value to the LED

  //Start Button
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  button.attach(BUTTON_PIN);
  button.interval(BUTTON_DEBOUNCE);

}

void loop() 
{
  button.update();

  if(led_needs_update)
  {
    led_update();
    led_needs_update = false;
  }

  if(button.fell())
  {
    i2c_regs[0x00] = true;
  }
  if(button.rose())
  {
    i2c_regs[0x00] = false;
  }

  // This needs to be here for the TinyWireS lib
  TinyWireS_stop_check();

}

/*
 * I2C Handelers
 */
void i2cReceiveEvent(uint8_t howMany)
{
    if (howMany < 1)
    {
        return;// Sanity-check
    }

    reg_position = TinyWireS.receive();
    howMany--;
    if (!howMany)
    {
        return;// This write was only to set the buffer for next read
    }

    while(howMany--)
    {
        //Store the recieved data in the currently selected register
        i2c_regs[reg_position] = TinyWireS.receive();

        //Proceed to the next register
        reg_position++;
        if (reg_position >= reg_size)
        {
            reg_position = 0;
        }
    }
    led_needs_update = true;
}//End i2cReceiveEvent()

void i2cRequestEvent()
{
    //Send the value on the current register position
    TinyWireS.send(i2c_regs[reg_position]);

    // Increment the reg position on each read, and loop back to zero
    reg_position++;
    if (reg_position >= reg_size)
    {
        reg_position = 0;
    } 
}//End i2cRequestEvent

/*
 * Helper functions
 */
void led_update()
{
  cRGB val;
  val.r = i2c_regs[0x01];
  val.g = i2c_regs[0x02];
  val.b = i2c_regs[0x03];
  led.set_crgb_at(0, val);
  led.sync(); // Sends the value to the LED
}
0

This is another case where the Arduino documentation is horribly incomplete to the point of being useless, and you must refer to the source code itself.

Presuming your TinyWire library works like the ordinary Arduino Wire, you need to use the two-argument form of the write method:

size_t TwoWire::write(const uint8_t *data, size_t quantity)

So your handler would probably look like this

void i2cRequestEvent()
{
    TinyWireS.write(i2c_regs, sizeof(i2c_regs));
}

However, you're also going to need to change your definition to an 8-bit type, rather than the 16-bit one you surprisingly had

volatile uint8_t i2c_regs[reg_size];

If you really do need to use a 16-bit type, then you'll need to either double the transfer size (since both processors are AVRs you won't hit any byte order issues) or first copy the values that you care about to a byte buffer.

  • Thanks for your answer. I updated uint16_t to uint8_t, my mistake. The TinyWireS uses the following write command, accepting only a single argument: void USI_TWI_S::send(uint8_t data){ // send it back to master usiTwiTransmitByte(data); } But as far as I understand that's actually normal I2C convention: the Wire library should request multiple bytes, one at a time. The slave responds with one byte to each request, jumping to the next register afterwards so the next requested byte comes from the next register. – Dennis Hunink Oct 11 '17 at 20:35
  • It may offer a single argument write, but that won't work for you. Check its source code and see if it also offers a two argument write - the normal Wire library offers both. If not, you'll probably have to modify the library. And no, i2c convention is to request multiple bytes at once, which is what you are doing. However, if you write a starting register in the preceding write, then you could I guess do distinct one-byte requests for each register in turn. – Chris Stratton Oct 11 '17 at 21:01
  • Hi @Chris, thanks again. I could only find a single argument write. In terms of performance, would making multiple one-byte requests slow things down (compared to a wire.request N bytes request)? Just figuring out my options, based on your answer/comment. – Dennis Hunink Oct 11 '17 at 21:26
  • Yes, multiple one byte requests would be slower. Also, it might yield values that aren't consistent with one another but come from different data points. You should be able to create a multi-byte write by comparing the source of the usual Wire library which has it with yours which apparently does not, and using that to write one - ie, figure out the differences between the two for the single byte case, and then apply those to the multi-byte code. – Chris Stratton Oct 11 '17 at 22:19
  • The master uses the 'normal' Wire library, but the TinyWireS is not normal at all. The master defines how many bytes are read. The slave can send a byte or not. If the slave does nothing, then the data will become 255. Could you try a requestEvent with four calls to TinyWireS.send with fixed numbers. For example TinyWireS.send('A') and 'B', and 'C' and 'D', or with numbers. – Jot Oct 11 '17 at 23:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.