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

I’ve always planned on using wireless communication for the device to device & device to accessory networking using the LoRa RFM95 radios. But in the near future the Particle MESH network instead.

I was planning on just using the Particle ID as the product ID since it’s already a unique non-repeating number.

You’re working on a smart battery charging system right?

In our use case there is no need for wireless comms per se since we can just add a two wires to the charging cable to use as a SMBus/i2c style comms system. Compared to using Particle mesh I expect it to be significantly cheaper at scale (expect a Xenon to cost ~ $10 USD plus the fees associated with using the ParticleMesh platform) vs an ATtiny with unique ID EEPROM and external oscillator clocking in at about $2.50 US

Would be nice to not have to make 24 extra connections though (12 channels of i2c wiring)

I’ve written a successful testing program that can be compiled and run on an Electron and an Arduino which sets up the Electron as the initial i2c master and the Arduino as the initial i2c slave.

The Electron sends “are you there” to the Arduino address, then switches to slave mode. When Arduino receives “are you there” message, it switches to master mode and sends back “here I am”, then switches back to slave mode.

When the Electron receives “here I am” it switches back to master mode. The process then repeats.

Based on this success I think it should be possible to implement my quasi-SMBus address resolution protocol idea described above.

Here is the test code:

#ifndef AVR
#include "Particle.h"
#endif

//This test program is run on both the ATtiny and the Electron to determine if they can successfully swith Master/Slave relationships with one another




/*=============================================>>>>>
= Different address definitionsl between ATtiny and Electron =
===============================================>>>>>*/
#ifdef AVR
/*=============================================>>>>>
= AVR settings (ATtiny) =
===============================================>>>>>*/
const uint8_t myAddress = 0XF1;
const uint8_t otherAddress = 0XF2;
bool initialRoleMaster = false;
const uint8_t led_pin = 8;
const uint32_t myBaud = 9600;

#else
/*=============================================>>>>>
= Particle Settings (STM32) =
===============================================>>>>>*/
// 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 tinyLog("ATtiny");
const uint8_t myAddress = 0XF2;
const uint8_t otherAddress = 0XF1;
bool initialRoleMaster = true;
const uint8_t led_pin = D7;
const uint32_t myBaud = 115200;
#endif


uint8_t  lastNumBytesReceived = 0;
bool unhandledDataReceived = false;
char receivedBytes[25] = {0};


/*=============================================>>>>>
= Function forward declarations =
===============================================>>>>>*/
void setMaster();
void setSlave();
bool checkWireWriteResult(byte result);
void slaveReceiveEvent(int howMany);
bool validTransmission(byte result);

/*=============================================>>>>>
= Setup function =
===============================================>>>>>*/
void setup() {
   //Begin serial monitor to PC via Electron USB cable
   Serial.begin(myBaud);
   #ifndef AVR
   //Begin serial monitor that will look for incoming UART transmissions from ATtiny
   Serial1.begin(9600);
   #endif
   //Wait a bit, and announce setup is starting
   delay(2000);
   Serial.println("setup() finished");
   //Setup initial roles for i2c transactions
   if(initialRoleMaster) setMaster();
   else setSlave();
   //Setup led blink
   pinMode(led_pin, OUTPUT);
   digitalWrite(led_pin, LOW);
   #ifndef AVR
   //Make electron send out logic analyzer trigger signal
   pinMode(D5, OUTPUT);
   digitalWrite(D5, LOW);
   #endif
}




/*=============================================>>>>>
= Main loop =
===============================================>>>>>*/
void loop(){

   /*=============================================>>>>>
   = Poll the bus every 5 seconds to see if any new devices have been connected =
   ===============================================>>>>>*/
   static uint32_t last_AreYouThere_test = 0;
   if( (millis() - last_AreYouThere_test) > 5000){
      if(initialRoleMaster){
         Serial.println("Sending as master");
         #ifndef AVR
         //Make electron send out logic analyzer trigger signal
         digitalWrite(D5, HIGH);
         digitalWrite(D5, LOW);
         #endif
         Wire.beginTransmission(otherAddress);
         Wire.write("are you there");
         byte result = Wire.endTransmission();
         validTransmission(result);
         setSlave();
      }
      last_AreYouThere_test = millis();
   }

   //If we just received unhandled data, we must be the slave
   if(unhandledDataReceived){
      unhandledDataReceived = false;
      Serial.print("Received message: \"");
      Serial.print(receivedBytes);
      Serial.println("\"");
      if(!initialRoleMaster){
         setMaster();
         Wire.beginTransmission(otherAddress);
         Wire.write("here I am");
         byte result = Wire.endTransmission();
         validTransmission(result);
         setSlave();
      }
      else{
         setMaster();
      }
   }

   /*=============================================>>>>>
   = Blink the LED periodically to let us know that the program is still running =
   ===============================================>>>>>*/
   static uint32_t last_led_blink = 0;
   if( (millis() - last_led_blink) > 500){
      digitalWrite(led_pin, !digitalRead(led_pin));
      last_led_blink = millis();
   }

   /*=============================================>>>>>
   = Print output of connected UART from ATtiny (if applicable) =
   ===============================================>>>>>*/
   #ifndef AVR
   if(Serial.available()) System.dfu();

   if(Serial1.available()){
      uint8_t numBytesRead = 0;
      char bytesRead[100];
      while(Serial1.available()){
         bytesRead[numBytesRead] = Serial1.read();
         numBytesRead++;
         if(!Serial.available()){
            delay(25);
         }
      };
      tinyLog.info(bytesRead);

   }
   #endif


}


void setMaster(){
   Wire.end();
   Wire.begin();
}

void setSlave(){
   Wire.end();
   Wire.begin(myAddress);
   Wire.onReceive(slaveReceiveEvent);
}


void slaveReceiveEvent(int howMany){
   //Loop through all incoming bytes and store them
   lastNumBytesReceived = howMany;
   int count = 0;
   for(;count < howMany; count++){
      receivedBytes[count] = Wire.read();
   }
   //Set the flag indicating that we have unprocessed fresh data to deal with
   unhandledDataReceived = true;
}



bool validTransmission(byte result){
   switch(result){
      #ifdef PARTICLE_H
      //Using particle definitions of i2c transmission results
      case 0:{
         Serial.println("Success");
         return true;
      }
      case 1:{
         Serial.println("busy timeout upon entering endTransmission()");
         break;
      }
      case 2:{
         Serial.println("START bit generation timeout");
         break;
      }

      case 3:{
         Serial.println("end of address transmission timeout");
         break;
      }

      case 4:{
         Serial.println("data byte transfer timeout");
         break;
      }

      case 5:{
         Serial.println("data byte transfer succeeded, busy timeout immediately after");
         break;
      }

      #else
      //Using arduino definitions
      case 0:{
         Serial.println("Success");
         return true;
      }
      case 1:{
         Serial.println("data too long to fit in transmit buffer");
         break;
      }
      case 2:{
         Serial.println("received NACK on transmit of address");
         break;
      }

      case 3:{
         Serial.println("received NACK on transmit of data");
         break;
      }

      case 4:{
         Serial.println("other error");
         break;
      }
      #endif
   }
   //If we made it this far then the result was bad
   return false;
}

@jaza_tom, not sure why you want to do this dance. With the Electron in Master mode, it simply sends a request to the magic slave address. If a response is received, the Electron sends new data or request data or both. In either case, they both keep their I2C roles.

Another approach is to use a cheap STM32F103 as the slaves since it supports CANbus (64pin LQFP or larger). You could develop using the STM32duino toolchain which has a CANbus library and the standard Arduino stuff. You could run the STM32 with its internal oscillator to keep the parts count low.

2 Likes

Thanks for the input. I was considering doing just that (using some sort of more robust industrial-style communication protocol running on differential signalling). I decided that it would be prohibitively expensive for the application I have in mind, since CAN comms cost about $2.50 minimum (incremental cost of MCU with integrated CAN peripheral plus external CAN transceiver cost).

I’m targeting an overall component cost of $5 (includes buck converter , MCU, control FETs, connectors, etc).

The reason for the i2c dance described above would be to avoid having to multiplex and periodically poll every i2c channel (time consuming for i2c host/master).

In order to combat bus capacitance, I have decided to go with a multiplexed approach after all so that only a single 2 m long cable needs to be driven by the 2k pullup resistors I plan on using. I have my i2c rise times on SDA down to 500 ns. Hopefully multiplexing works the way I think it does, meaning that I can have hundreds of channels and the bus capacitance will only go up by the sum of the input capacitances of the multiplexers plus the capacitance of the selected cable.

1 Like

@jaza_tom did your multi-master code end up working ok? I’m about to start a project expecting to be able to switch the Electron between master and slave periodically (which has worked fine for me on other MCUs) so hoping to hear that it all worked out.

AFAIRemember it was working. Haven’t touched it in some time though. I opted for UART instead of I2C. Pretty sure the test program above did the trick.