[SOLVED] Switching between master and slave roles on Electron Wire i2c SmBus styles

Have been using the Wire library for over a year without problem. Now all of a sudden it doesn’t seem to work.

I have the following code:

cpp
#include "Particle.h" // Use this include for Particle Dev where everything is in one directory.

// Comment this out for normal operation
SYSTEM_MODE(SEMI_AUTOMATIC);  // skip connecting to the cloud for (Electron) testing
SYSTEM_THREAD(ENABLED);

//Define log handler using serial monitor (dumps logs to serial monitor)
SerialLogHandler logHandler(LOG_LEVEL_ALL);

static Logger myLog("Electron");  //Logger object used in this "main.cpp" file

unsigned int last_i2c_transaction = 0;
const unsigned int time_between_i2c_transactions = 3000;

unsigned int last_led_blink = 0;


void setup(){

  Serial.begin(115200);
  delay(1000);

  myLog.info("Initializing i2c peripheral");
  Wire.begin(0X05); //Join the i2c bus with address of 0x05

  pinMode(D7, OUTPUT);
  digitalWrite(D7, LOW);

  myLog.info("Waiting 3 seconds...");

  delay(3000);

  myLog.info("Setup finished!");
}



void loop(){
  /*=============================================>>>>>
  = Periodically send a hello world message on the i2c bus =
  ===============================================>>>>>*/
  if((millis() - last_i2c_transaction) > time_between_i2c_transactions){
    if(Wire.isEnabled()){
      myLog.info("Sending a byte of data");
      Serial.flush();
      digitalWrite(D7, HIGH); //Trigger on-board LED (triggers capture on logic analyzer/oscilloscope)
      Wire.beginTransmission(0X06);
      Wire.write(0XF1); //Four ones, three zeroes, and a final one --> 0b11110001
      byte writeResult = Wire.endTransmission(true);
      myLog.info("Done sending... write result = %u", writeResult);
      Serial.flush();
      last_i2c_transaction = millis();
    }
    else{
      myLog.info("Wire not enabled!");
      Serial.flush();
    }
  }
}

And here is my logic analyzer output:

and here is my debug serial output:

0000001054 [Electron] INFO: Initializing i2c peripheral
0000001054 [Electron] INFO: Waiting 3 seconds...
0000004054 [Electron] INFO: Setup finished!
0000004055 [Electron] INFO: Sending a byte of data
0000001054 [Electron] INFO: Initializing i2c peripheral
0000001054 [Electron] INFO: Waiting 3 seconds...
0000004054 [Electron] INFO: Setup finished!
0000004055 [Electron] INFO: Sending a byte of data

At this point, the Electron stops running and just sits idle (no breathing RGB led even).

I have two Electrons and both behave the same.

Both are running Particle OS v 0.6.1

Am I crazy?

Note: Even if I remove the logic analyzer, oscilloscope probe, remove the pullup resistors etc etc until I just have an Electron sitting on my desk all alone, it still crashes without SOS error code

Update: Changed the sourcecode posted (above) to get rid of inconsistencies between comments and actual code

Hmm, the comment and the code doesn't seem to line up :wink:
Is your device now 1, 5 or 7?
This should not cause your troubles, but doesn't help in finding what is - try to avoid distractions.

Maybe run an address scan.

If I understand the i2c utility correctly, calling

Wire.begin(5);

makes my Electron start listening to the i2c bus for i2c traffic directed at address 5. So the Electron has address 5.

Later, the line

Wire.beginTransmission(7);

should send out an i2c data frame addressed to node 7, right?

Except when both of my Electrons gets to this line of code, they hang without recovering.

I am not there yet. I have no other nodes connected to the i2c bus yet. Just an single Electron sitting on a desk not connected to anything.

Do you want your Electron to act as slave?
If so, who is the master of the bus?

There is a difference between crashing and stalling - I think your code just stalls due to the misconfigured bus (especially without pull-ups)

3 Likes

In the end I hope to have a multi-master setup, but for now I'd be happy just getting the Electron i2c to work as a master.

I re-checked all my connections, re-flashed Particle OS V0.6.1

Here is my logic analyzer output (same as before):

And my serial debug output (same as before):

0000001053 [Electron] INFO: Initializing i2c peripheral
0000001054 [Electron] INFO: Waiting 3 seconds...
0000004054 [Electron] INFO: Setup finished!
0000004054 [Electron] INFO: Sending a byte of data

Here is a picture of my setup (note the two 4k7 pullup resistors to the Vin = 5V rail):

I have also tried pulling-up to the 3v3 rail instead, but that has the same result.

The wires going off-screen are connected to my logic analyzer.

The RGB led is not changing, which makes me think that FreeRTOS is either not allowing Particle OS to have any runtime, or else that Particle OS has stalled and is blocking my code.

Updating to Particle OS branch release/v0.7.0 makes no difference.

I have edited the code in my original question to get rid of any extra possibly confusing comments etc.

So to sum up, I have tried:

  • using 5V pullups
  • using 3.3V pullups
  • using Particle OS version 0.6.1
  • using Particle OS version 0.7.0
  • using the alternate Wire1 i2c object available to Electrons
  • using 2 different 3G Electrons and a third 2G Electron

I also tried flashing my production code (which uses the Wire1 peripheral) and it appears that it is still working fine there, so this is definitely some type of software issue that is only happening in this simple testing program. I can't figure out what it is though.

Can someone out there please run my code on their Electron and confirm/deny that this causes the Electron to stall/crash?

I am confused with your logic. The command Wire.begin(5); configures the Electron as an I2C slave. It does not own the bus so it won't send out anything since only the Master generates the SCL data clock. You can't be both roles at the same time. Try starting your Electron in Master mode and you will see the address data data going out on SDA. To switch modes between Slave and Master you would need to do a Wire.end() then Wire.begin(address or blank) to switch modes.

4 Likes

@peekay123 --> You nailed it. Thanks.

The takeaway is: if you want to use an Electron in a multi-master i2c bus environment, you need to call:

Wire.end();
Wire.begin();
//Do some master stuff
//...
Wire.end();
Wire.begin(myAddress);

I'm not sure how this multi-master stuff works in conjunction with slave callback functions... I'm guessing that they probably need to get re-registered when switching back to slave mode. Something like:

cpp

Wire.begin(myAddress);
Wire.onReceive(receiveEvent);
Wire.onRequest(requestEvent);

//... at some point later we want to be the master

Wire.end();
Wire.begin();
Wire.beginTransmission(targetAddress);
Wire.write(0x0F);
Wire.endTransmission();
Wire.end();

Wire.begin(myAddress);
Wire.onReceive(receiveEvent);
Wire.onRequest(requestEvent);

Maybe that would allow the electron to (kind of) be a slave (sometimes) and a master (sometimes).

I'm guessing that since the distinction between master and slave was not handled well by the Electron that it will likewise not be handled well by my ATtiny that I'm trying to interface with in a multi-master scenario.

I would have thought that whatever device on the i2c bus pulled SCL low first would be the de-facto master, but this exercise in futility seems to have proved me wrong.

I guess I'll just abandon the idea of using i2c for my multi-master needs.

@jaza_tom, the approach to handling I2C is common to many implementations include Arduino. In most cases, Masters don't flip between Master and Slave. They simply compete for the bus to talk to multiple slave. From a Sparkfun tutorial:

If two master devices wish to take ownership of the bus at one time, whichever device pulls SDA low first wins the race and gains control of the bus.

Perhaps you just need to rethink your Master/Slave configuration. :wink:

1 Like

@peekay123 Thanks for the tutorial link.

I’m not sure that the standard i2c implementations (that don’t account for flipping between master/slave roles) will work for me.

My use case is that I have an Electron that needs to communicate with an ATtiny over an i2c interface.

I want comms between the Electron and ATtiny to be such that either one of them can initiate communication at an undefined point in time (so that I don’t have to keep polling the ATtiny to see if new data is available). I’ve done this before with a CAN communication network (where any node can initiate a transmission asynchronously).

It looks like i2c could theoretically work like this, but that the libraries supporting it in Arduino and Particle OS were not designed to account for this use case.

I think that probably what would need to be done to support this use case is to configure both MCUs as i2c slaves, and then use a bit-banged approach (with some type of arbitration) whenever either of them wanted to initiate an i2c data transfer.

@jaza_tom, you could use a “data ready” line on each MPU which triggers on interrupt no the other device. When the Electron receives the signal, it switches to slave mode, completes the transaction then goes back to no mode (Wire.end()). If the Electron wants to send out data, it switches to master mode and sends out the signal and the Attiny which responds the same way. Not ideal but workable. Why not have the Electron as the single master and pole the Attiny at regular intervals?

4 Likes

If you have an extra GPIO on both sides, you can set, for example, the Electron as the master and the ATtiny as the I2C slave. Requests from the Electron are easy. To allow the ATtiny to send data you use a GPIO driven by the ATtiny and monitored by the Electron. This tells the Electron to request data from the ATtiny so it doesn’t have to poll but can still initiate the transfer from the master, as is intended in I2C.

3 Likes

@peekay123 @rickkas7

Great suggestions guys. The reason I was hoping to do things the way I originally envisioned was to achieve the following characteristics:

  • Able to have dynamic address provisioning (ATtiny would take over i2c bus, already knowing the Electron’s hard-coded address, and request that it be provisioned an i2c that is confirmed by virtue of also passing/receiving back a universally unique ID number). Eventually there would be up to 12 ATtinies all provisioned and working on the same bus (at which point they could just all be slaves)
  • Able to work with only 2 wire or less

If I were to add an extra GPIO for data ready signal, that would translate to minimum 12 extra wires/connectors/pins taken up on the Electron end of things.

If I was using that many wires, my preference would be to simply switch over to a multiplexed SPI based protocol where I would only have 1/12 of the SPI bus “active” at a time (to avoid problems with too much bus capacitance). Throw a reset signal on there and this would allow me to do seamless firmware updates of the ATtinies over SPI.

I think what I’ve settled on is to just multiplex the i2c signals and have all the ATtinies have the same slave address and just poll them regularly.

@jaza_tom, why not have the Electron scan the bus on a regular basis looking for new Attiny slaves? After finding a new one it can request a “control block” defining the node’s characteristics.

Wouldn’t that necessitate each slave having a predefined unique 7 bit address? or do multiple slaves with the same address have an arbitration mechanism built in?

@jaza_tom, you could have a predefined “new device” address that gets assigned to the device. When the Electron finds it, it sends a new address to the device which is then uses when restarting its slave mode. This requires each new device to be added one at a time. Or DO assign a unique address since you will only have 12 devices.

Darn that's what I thought. Pesky i2c hardware has no baked-in slave transmission arbitration :frowning_face:

There will be thousands of devices total, only 12 of which (max) will be connected at any one time.

@jaza_tom, I2C can only handle 127 unique addresses so not sure why you thought you could give “thousands” of devices their own address. Don’t confuse I2C address with a unique device identifier that you could assign each of those thousands of devices.

I will have thousands of devices that each have a unique 32-bit serial number. There will be 12 (at most) connected to the Electron at any one point in time.

In order to communicate with the 12 connected devices, it would seem that my options are as follows:

  • have all my devices have a fixed hard-coded address, and multiplex the i2c signals to a single channel (i.e. other 11 channels dormant) at a time. So, for example, communicate with slave 0x0A on channel 1, then communicate with slave 0x0A on channel 2, then communicate with slave 0x0A on channel 3... etc etc

  • use the i2c hardware peripheral in a novel way to accomplish the task of dynamically assigning/recycling slave addresses (for example) 0X0A through 0X6F to connected ATtiny MCUs through some form of slave address arbitration protocol in the same manner that other protocols do. For example the SMBus 2.0 protocol uses i2c hardware layer but then adds on top of that something called SMBus Address Resolution Protocol (see section 5.6 of the SMBus 2.0 specification).

I think it might be possible to approach a hacked-together form of address-resolution protocol using the following strategy:

  • Electron connects to bus in master mode
  • ATtiny connects to bus in slave mode with a default special "I'm a slave without an address" address
  • Electron writes a byte to the special "I'm a slave without an address" periodically (say once per second), after which it immediately disconnects/reconnects to the bus as a slave with special "I'm the address boss" address
  • Any ATtiny that does not yet have an address assigned delays for a random number of microseconds between 100 and 1000 (seeded by its unique ID) and then disconnects from the bus and reconnects as a master
  • all ATtiny MCU masters (the ones in need of an address) then write their 4-byte unique ID to the "I'm the address boss" slave (i.e. the Electron). Multi-master bus arbitration mechanisms kick in and make sure they don't all talk over one-another. In other words, the ATtiny whose random delay was the smallest gains control of the bus as a master first,
  • The master that won the arbitration then requests 5 bytes from the "I'm the address boss" address (i.e. the Electron). The first four bytes contain the 32-bit unique ID that the ATtiny just sent. The 5th byte contains that ATtiny's newly assigned address. The ATtiny then immediately resets its bus connection and re-connects as a slave using its newly assigned address
  • At this point the bus gets released and the other ATtiny MCU's that lost arbitration delay once more by the randomly determined amount of time before trying once again to send their 4-byte unique ID. The winning ATtiny in each case gets assigned a fresh address (as described in the last few steps)
  • When no more address-assignment messages are coming in, the Electron recognizes that it has assigned all addresses required and proceeds to reset its bus connection and connect as a master once more, making it the sole master on a bus populated by slaves

--> Probably wouldn't hurt to also throw in some CRC check bytes as well to make sure that address assignments do not fall victim to heisenbugs

I think this is basically the pseudo-code for the bones of how SMBus Address Resolution Protocol works and I'm pretty sure it could be done using the standard Wire library (on both the Electron end and the ATtiny end).

Thoughts?

Is this for a 12 battery charging bay where all 12 charging batteries communicate with the Electron via I2C?

:point_up::+1:yup