1

I've been working on a project for the last few days and spent a LOT of time googling, reading and testing. I'm kinda running out of ideas now and instead of bumping my head to the wall I thought posting my issue here in the hopes someone might be able to point me towards the right direction

I've got an Arduino UNO programmed as I2C Master and an ATtiny85 as an I2C slave. An single Adafruit Neopixel LED is attached to the slave.

When running the two sketches below everything actually works perfect.....for about a minute or so. While during correct operations the Neopixel LED is actually changing color correctly and the serial monitor on the Uno shows the correctly updated R-G-B values, things change after about 1 minute of running. The LED keeps blinking, which tells me the ATtiny is not frozen. The UNO keeps sending I2C and is receiving I2C data...but the data is constant. The updated RGB that the Uno (master) has been sending to the ATtiny slave are somehow not handled (correctly) by the slave.

Any thoughts are more then appreciated. I'm totally lost and do not know which directions to go from here so any pointers would be more then welcome.

The Master - Arduino Uno -

#include <Wire.h>

#define I2C_MASTER_ADDR 0x04
#define I2C_SLAVE_ADDR 0x05

/*
* Software config
*/
int pollInterval = 700;//Milliseconds

//Demo's
#define demoLedInterval 150//In milliseconds
boolean demoRequest = false;

/*
* Internal variables
*/
unsigned long lastPoll = 0;

int demoLedState = 0;
unsigned long lastDemoLedUpdate = 0;

/*
* Setup function
*/
void setup() 
{
 Wire.begin(I2C_MASTER_ADDR);  // join i2c bus (address optional for master)
 Serial.begin(115200);         // start serial for output
 Serial.println("Setup complete");
}

/*
* The main loop
*/
int i =0; 
void loop() 
{

 /*
  * Poll the slave 
  */
 if( (millis()-lastPoll) > pollInterval)
 {
   /*
    * 
    */
   int red=0; int green=0; int blue=0;

     if(i==0)
     {
       Serial.println("Write data to slave - set led red");
       Wire.beginTransmission(I2C_SLAVE_ADDR);
       Wire.write(0xC2);//Command
       Wire.write(0x00);
       Wire.write(255);//Data
       Wire.endTransmission();
       Wire.beginTransmission(I2C_SLAVE_ADDR);
       Wire.write(0xC3);//Command
       Wire.write(0x00);
       Wire.write(0);//Data
       Wire.endTransmission();
       Wire.beginTransmission(I2C_SLAVE_ADDR);
       Wire.write(0xC4);//Command
       Wire.write(0x00);
       Wire.write(0);//Data
       Wire.endTransmission();
     }
     if(i==1)
     {
       Serial.println("Write data to slave - set led green");
       Wire.beginTransmission(I2C_SLAVE_ADDR);
       Wire.write(0xC2);//Command
       Wire.write(0x00);
       Wire.write(0);//Data
       Wire.endTransmission();
       Wire.beginTransmission(I2C_SLAVE_ADDR);
       Wire.write(0xC3);//Command
       Wire.write(0x00);
       Wire.write(255);//Data
       Wire.endTransmission();
       Wire.beginTransmission(I2C_SLAVE_ADDR);
       Wire.write(0xC4);//Command
       Wire.write(0x00);
       Wire.write(0);//Data
       Wire.endTransmission();
     }
     if(i==2)
     {
       Serial.println("Write data to slave - set led blue");
       Wire.beginTransmission(I2C_SLAVE_ADDR);
       Wire.write(0xC2);//Command
       Wire.write(0x00);
       Wire.write(0);//Data
       Wire.endTransmission();
       Wire.beginTransmission(I2C_SLAVE_ADDR);
       Wire.write(0xC3);//Command
       Wire.write(0x00);
       Wire.write(0);//Data
       Wire.endTransmission();
       Wire.beginTransmission(I2C_SLAVE_ADDR);
       Wire.write(0xC4);//Command
       Wire.write(0x00);
       Wire.write(255);//Data
       Wire.endTransmission();
     }
     
     
   i++; if(i>2) i =0;

   /*
    * General request (without data). Should return something though
    */
   Serial.println("General request for slave");
   Wire.requestFrom(I2C_SLAVE_ADDR, 7);//Request N bytes, N=byte_len
   delay(10);//Give the slave time to answer
   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

The Slave code - ATtiny 85 - Main file

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

#define I2C_SLAVE_DEFAULT_ADDR 0x05

#include "namespaces.h"//Include at last to make sure all libs are present

//Software config
#define BUTTON_DEBOUNCE 5//Debounce milliseconds

//Hardware config
#define NEOPIXEL_PIN  1
#define BUTTON_PIN    3
#define DS2401_PIN    4

#define NUMPIXELS     1

/*
 * Initialize instances/classes
 */
Bounce button = Bounce();
Adafruit_NeoPixel neopixel = Adafruit_NeoPixel(NUMPIXELS, NEOPIXEL_PIN, NEO_GRB + NEO_KHZ800);
OneWire ds(DS2401_PIN);

void setup() 
{
  uint32_t _now = millis();
  
  //Start I2C
  uint8_t _device_addr = EEPROM_DATA::get_device_addr();
  //TinyWireS.begin(_device_id);
  TinyWireS.begin(I2C_SLAVE_DEFAULT_ADDR);
  TinyWireS.onReceive(I2C::receiveEvent);
  TinyWireS.onRequest(I2C::requestEvent);

  //Start NeoPixel
  neopixel.begin();
  neopixel.setPixelColor(NUMPIXELS-1, neopixel.Color(255, 255, 255) );
  neopixel.show();

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

}


unsigned long lastBlink = 0;
boolean lastBlinkState;

void loop() 
{

  
  button.update();

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

  if(button.fell())
  {
    VARS::button_state = HIGH;
  }
  if(button.rose())
  {
    VARS::button_state = LOW;
  }

  //testblink
  if ( (millis()-lastBlink) > 600)
  {
    if(lastBlinkState)
    {
      neopixel.setPixelColor(NUMPIXELS-1, 0,0,0 );
      neopixel.show();
      lastBlinkState = false;
    }
    else
    {
      led_update();
      lastBlinkState = true;
    }
    lastBlink = millis();
  }
  
  // This needs to be here for the TinyWireS lib
  TinyWireS_stop_check();
}

/*
 * Helper functions
 */
void led_update()
{
  neopixel.setPixelColor(NUMPIXELS-1, 
    VARS::led_red,
    VARS::led_green,
    VARS::led_blue
  );
  neopixel.show();
}

The Slave code - ATtiny 85 - namespaces.h

namespace VARS
{
  //These are available in a I2C response
  volatile boolean button_state = 0;
  volatile int led_red = 0;
  volatile int led_green = 0;
  volatile int led_blue = 0;
  uint8_t sn_family;
  uint8_t sn_bit1;
  //uint64_t serial_number = 0;
  //int I2C_response_max_length = 4;//Count the above

  //These are internal
  volatile boolean led_needs_update = false;
  int update_interval     = 300;
  unsigned long last_update = 0;
}


//===========================================================================

namespace EEPROM_DATA 
{
  uint8_t default_device_addr   =     I2C_SLAVE_DEFAULT_ADDR;
  uint8_t this_device_address   =     0x05;
  
  bool device_addr_valid(uint8_t addr) 
  { 
    return (addr > 0x04 && addr < 0x7E); 
  }
  bool store_device_addr(uint8_t new_device_addr) 
  {
    if (!device_addr_valid(new_device_addr)) return false;//cancel if out of bounds. Assume corrupt data.
    EEPROM.write(EEPROM_DATA::this_device_address, new_device_addr);//Set new i2c address into EEPROM
    return true;
  }
  uint8_t get_device_addr() 
  {
    uint8_t _addr = EEPROM.read(EEPROM_DATA::this_device_address);
    if (!device_addr_valid(_addr)) 
    {
      _addr = EEPROM_DATA::default_device_addr;
      store_device_addr(_addr); //update EEPROM with a valid/default device id.
    }
    return _addr;
  }
}

//===========================================================================

namespace COMMANDS 
{
  enum {
    assign_new_device_addr  =     0xC1,
    set_led_red             =     0xC2,
    set_led_green           =     0xC3,
    set_led_blue            =     0xC4
  };
  bool do_command(uint8_t cmd, uint16_t cmd_data) 
  {
    //interpret command.
    switch (cmd) 
    {
      //set new i2c slave adress. 
      //ATTENTION: After command is performed, the chip will need a hard reset.
      case COMMANDS::assign_new_device_addr:
        uint8_t new_device_addr;
        new_device_addr = uint8_t(cmd_data & 0x007F);//ensure the address is 7-bit.
        return EEPROM_DATA::store_device_addr(new_device_addr);//return true if saved, false if invalid address.
        break;
  
      case COMMANDS::set_led_red:
        //if (cmd_data > 0x00FF) break;//ignore out of range data. Assume corrupt packet.
        VARS::led_red = int(cmd_data);
        VARS::led_needs_update = true;
        break;
  
      case COMMANDS::set_led_green:
        //if (cmd_data > 0x00FF) break;//ignore out of range data. Assume corrupt packet.
        VARS::led_green = int(cmd_data);
        VARS::led_needs_update = true;
        break;
  
      case COMMANDS::set_led_blue:
        //if (cmd_data > 0x00FF) break;//ignore out of range data. Assume corrupt packet.
        VARS::led_blue = int(cmd_data);
        VARS::led_needs_update = true;
        break;
    }
  }
}

//===========================================================================
int pointer = 0;
namespace I2C{

  //Gets called when the ATtiny receives a general i2c data request.
  //Tx_stop is sent after end of call, so all bytes need to be sent before leaving.
  void requestEvent()
  {
    
//The number of bytes requested should NOT exceed the number of bytes returned
    TinyWireS.send( VARS::button_state );
    TinyWireS.send( VARS::led_red );
    TinyWireS.send( VARS::led_green );
    TinyWireS.send( VARS::led_blue );
    
  }

  //Gets called when the ATtiny recieves an i2c message THAT CONTAINS DATA from another device.
  void receiveEvent(uint8_t byte_count) 
  {
    //first byte is command, next 2 bytes is data for command. Total must equal 3 bytes.
    if (!TinyWireS.available() || byte_count != 3 ) return;
  
    //retrieve the command and 16-bit data. 
    //Incomming data is ordered CMD,Data-MSB,Data-LSB.
    uint8_t cmd = TinyWireS.receive();
    uint16_t cmd_data = (TinyWireS.receive() << 8);
    cmd_data += TinyWireS.receive();
  
    COMMANDS::do_command(cmd, cmd_data);
  }
  
}
  • Three packets of data are sent over the I2C bus, with no time to breathe for air for the Attiny85 in between. You could add delay(1) between sending the commands after Wire.endTransmission. The delay(10) after the Wire.requestFrom can be removed. The Wire.requestFrom does the waiting itself. The slave uses NeoPixel and OneWire. That is a big problem since both libraries turn off the interrupts for the timing specific protocol. That will always be in the way of interrupts. You could make the Attiny85 the master. How long are the wires ? sigrok.org/wiki/Supported_hardware#Logic_analyzers – Jot Sep 30 '17 at 22:01
  • millis() returns a long, but your pollInterval is an int. Try pollInterval as a long to avoid an implied casting issue. You do long to int comparisons in other places as well. – RobNY Oct 1 '17 at 2:13
  • Dennis Hunink, what is happening ? You ask a very specific question. I mentioned a timing/interrupt problem which could be the problem. But then you don't respond anymore. – Jot Oct 6 '17 at 12:00
  • @Jot real life happend 😉 I did not had time to post the final answer until just now. But I did found a solution thanks to your very specific suggestions. Thanks so much for that. The timing and interrupts proved to be the essential factors here. So basically your comment saved my life 😉 (wires are about 20CM so no issues there; turned out to be purely software) – Dennis Hunink Oct 6 '17 at 20:31
  • Please, accept your own answer to close this question – user31481 Nov 5 '17 at 20:47
0

After diving into interrupts, timing etc I did found the solution. Thanks for all the suggestions, in particular to @Jot!

In the end I took the following steps:
- adding a 1ms delay after each Wire.endTransmission()
- dropped the Neopixel lib (https://github.com/cpldcpu/light_ws2812)

Below is the final code, for anyone interested. There is still an issue with this code: requesting multiple bytes from the slave always results in receiving just a single byte. But since that's most likely unrelated to the original question I'm opening a new topic for that (if I really can't find a solution myself, that is).

Master

/*
 * 
 */

#include <Wire.h>

#define I2C_MASTER_ADDR 0x04
#define I2C_SLAVE_ADDR 0x05

/*
 * Software config
 */
int pollInterval = 700;//Milliseconds

/*
 * Internal variables
 */
unsigned long lastPoll = 0;

/*
 * Setup function
 */
void setup() 
{
  Wire.begin(I2C_MASTER_ADDR);  // join i2c bus (address optional for master)
  Serial.begin(115200);         // start serial for output
  Serial.println("Setup complete");
}

/*
 * The main loop
 */
int i = 0;
void loop() 
{

  /*
   * 
   */
  if( (millis()-lastPoll) > pollInterval)
  {
    Serial.println("Write data to slave");
    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
    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 form 0x01
    Wire.requestFrom(I2C_SLAVE_ADDR, 6);//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

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

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

//Hardware config
#define NEOPIXEL_PIN  1
#define BUTTON_PIN    3
#define DS2401_PIN    4

#define NUMPIXELS     1

/*
 * I2C Registers
 * 
 * Register map:
 * 0x00 - Button state
 * 0x01 - led value red
 * 0x02 - led value green
 * 0x03 - led value blue
 * 0x04 - SerialNumber - Family (byte 0)
 * 0x05 - SerialNumber - Byte 1
 * 0x06 - SerialNumber - Byte 2
 * 0x07 - SerialNumber - Byte 3
 * 0x08 - SerialNumber - Byte 4
 * 0x09 - SerialNumber - Byte 5
 * 0x10 - SerialNumber - Byte 6
 * 0x11 - SerialNumber - CNC (byte 7)
 * 
 * Total size: 12
 */
const byte reg_size = 12;
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
    //NOT WORKING when requestin multiple bytes
    //TinyWireS.send(i2c_regs[reg_position]);

    //WORKAROUND
    int n = reg_size - reg_position;//Number of registers to return
    for(int i = reg_position; i < n; i++)
    {//Return all bytes from the reg_position to the end
      TinyWireS.send(i2c_regs[i]);
    }

    // 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
}
  • I'm glad it is working better. I don't know why only a single byte is received. I can't see that in the code. The WS2812 RGB leds need that specific timing protocol. Looking at the light_ws2812.c source code, it also turnes off interrupts. I have an Arduino Uno as a slave and a DHT22 sensor connected to it. The DHT22 also have a timing protocol and turn off the interrupts. My solution was to remove the disabling of interrupts, and let the DHT22 library run every few seconds and simply ignore the wrong ones. It has a checksum, so that was a lot easier than a RGB led. – Jot Oct 6 '17 at 21:20
  • Yeah to be perfectly honest I do believe that the primary fix was adding the delay(1) after each write. After switching from Neopixel to WS2812 lib I did found that interrupts were still affected. Looks like that's a requirement for the chips. So I am thinking a lot on how to make the I2C communication more reliable, even when affected due to interruptes. checksum is perfect, but even with RGB values there has to be a way. And it not, we will find a way :0 On the other hand, implementing a checksum should not be that hard... – Dennis Hunink Oct 6 '17 at 21:58
  • About the single byte that is received: is that a know limitation of the TineWireS ? That only one byte at a time can be send per request ? – Jot Oct 7 '17 at 13:24
  • That’s what I’ve been trying to figure out, without luck so far. The source code is just a bit to complicated for me to give a definitive answer. But the inline commands on github suggest there should not be such a limitation... – Dennis Hunink Oct 8 '17 at 13:36
  • @Jot after spending a lot more time on reading through the TinyWireS lib over and over again I do think sending multiple bytes should be possible. As I am officially stuck now, I've openend a issue here: github.com/rambo/TinyWire/issues/36 – Dennis Hunink Oct 11 '17 at 16:19

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.