SCL line held low by slave while trying to read Vcc of slave

Before I begin I would like to add that I’m new to the world of electronics and I2c communication as well. So I would request all of you experts to answer my questions in a novice level :slight_smile:

I have an arduino Uno as a master and doing an isolated communication through an ISO1540 (http://www.alldatasheet.com/datasheet-pdf/pdf/462113/TI1/ISO1540.html). I am using pullup resistors of value 4k7 on the master’s and slave’s side. I am making it control two slaves. The slaves are stand alone Atmega328p with internal 8MHz oscillator. The master is powered up by my laptop (approx. 5V) and the slaves are powered up by separate 4.2V LiPo batteries. The purpose of this setup is when the master asks, the slaves measure their own Vcc and send it to the master. Based on that value the master does some thinking and sends back some data to the slaves. When the master gets powered off, then the slaves go to sleep mode in 0.5 seconds. The code I wanted to use for the slave is below:

#include <Wire.h>
#include <avr/sleep.h>



 int count = 0;
void setup() {
 
  Wire.begin (2);
  Wire.onReceive (receiveEvent);
  Wire.onRequest (requestEvent);
  pinMode (2, OUTPUT);
  
}

void loop() {
  delay (100);
  comm_check (1);
  
 
}

void requestEvent (){

 
  long result; // Read 1.1V reference against AVcc
 ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1); 
 delay(2);
 // Wait for Vref to settle
 ADCSRA |= _BV(ADSC); // Convert
 while (bit_is_set(ADCSRA,ADSC));
 result = ADCL; 
 result |= ADCH<<8;
 result = 1126400L / result;

 
  char svalue [4] ;
  dtostrf (fvalue , 4 , 2 , svalue);
  Wire.write (svalue);
  comm_check (0);
  
}

void receiveEvent (){

  while (Wire.available ()){
    char c = Wire.read ();
    if (c == '1')
    digitalWrite (2, HIGH);
    else
    digitalWrite (2, LOW);
  }
}
void comm_check (int c){
 
  if (c == 1)
  count ++;
  else if (c == 0)
  count = 0;

  if (count >= 5)
    sleep_time();
}

void sleep_time(){
  
  
     TWCR &= ~(_BV(TWEN)); 
     
     
     TWCR = 0;
     TWCR = bit(TWEN) | bit(TWIE) | bit(TWEA) | bit(TWINT);
  byte old_ADCSRA = ADCSRA;
    // disable ADC
    ADCSRA = 0;  
    set_sleep_mode (SLEEP_MODE_PWR_DOWN);  
    sleep_enable();
    digitalWrite (2, LOW);
    sleep_cpu();
    sleep_disable();
    count = 0;
    ADCSRA = old_ADCSRA;
    
    
    Wire.begin (2);
}

In the requestEvent() you can see the code I have used to measure the Vcc (but the reason I posted the whole code was because I might be making a mistake somewhere I am not expecting).

The master code is below:

#include <Wire.h>



void slave_wakeup(int add){
  Wire.beginTransmission (add);
  Wire.endTransmission();
  delay (5);
}

void setup() {
  
 
  Serial.begin (9600);
  
  
  Wire.begin();
 
  slave_wakeup (1);
  slave_wakeup (2);

 
}
 


void loop() {
   
  float s1value = slave_data(1);
  if (s1value > 0.00)
     
    SI (1, '1');
    
 if (s1value == 0.00)
 
    SI (1, '0');
    
  Serial.println (s1value);
  



 float s2value = slave_data(2);
if (s2value > 0.00)
    SI (2, '1');
  
  if (s2value == 0.00)
    SI (2, '0');
  
Serial.println (s2value);
  



Serial.println ("....");

 
  

  

  
}



float slave_data (int add){
  int k =0;
  char c2[4] = "";
  Wire.requestFrom (add, 4);
  while (Wire.available ()){
    char b = Wire.read();
    c2[k] = b;
    k++;
   
  }

float value = atof (c2);
 return value/1000.0;
}

void SI  (int add, char c){
  Wire.beginTransmission (add);
  Wire.write (c);
  Wire.endTransmission();
}

PROBLEM: everything seems to work fine for a few seconds then the SCL lines gets pulled low by either one of the slaves. When I pull out the power from the slave which is problematic, communication starts again only to be stuck again. Whenever I would replace the code to measure Vcc by something simpler like sending only a constant value to the master, the SCL line works then.

I would really appreciate any help given to correct the problem wherever it lies. I hope its not any hardware issue since it seems to work whenever i comment out the Vcc measurement code. And if there is any better way to measure the Vcc of the slave batteries then I am all open to suggestions :slight_smile:

Thanks

Syed

When the master gets powered off, then the slaves go to sleep mode in 0.5 seconds.

It is almost always problematic to connect powered devices to unpowered devices. If you don't have considerable electronics expertise, abandon this idea and rethink the entire communication scheme. I2C is possibly the worst choice.

Give us an overview of the entire project and we can suggest reasonable alternatives.

Sleep mode is what everyone does to conserve batteries. Tutorial here.

You need a different mode of communication.

@jremington sorry i deleted my earlier reply assuming that it was invalid since you edited your first reply. Anyways the link you shared is from where i learned about how I can put my atmega to sleep mode and this is what i have done in the slave code. After a half second of no requests recieved by the master, they go to sleep.

As for implementing a new communication system, i am sorry i can not do that since I put in a lot of hardwork in the hardware and coding of the system. But I would still love to listen to your recommendations

What I have also thought about is that I would put a regulator to boost the voltage of the LiPo battery to a constant 5V and supply this as my Vcc to my stand alone atmega. After this is done I would not have to worry about Vref of the IC instead a simple analogRead directly from the battery should do the trick. Am I correct?

Most people avoid boosting the battery voltage, as it is a terrible waste of power.

On a battery powered Arduino, read the battery voltage using the internal voltage reference as described here: https://forum.arduino.cc/index.php?topic=465847.0

The internal voltage reference is very stable, but for each processor, the reference voltage is different and needs to be calibrated for accuracy.

i am sorry i can not do that since I put in a lot of hardwork in the hardware and coding of the system.

That is indeed sad, because the system may never work reliably, and the pullups will squander battery power.

I2C was designed for chip-to-chip, single PCB communications, with shared power and ground. It was never intended for a project like yours.

First of all, this is a link to the manufacturer's page: http://www.ti.com/product/ISO1540.

jremington seems to be blunt with his first reply, but after reading the sketches a few times, I have to agree with him. This is not going to work.

The main problem is that when a battery is low, that Arduino will block the I2C bus.

An other problem is the working of the sketches.

  • The sleep mode could be entered while the I2C bus is busy.
  • I don't know if waking up by I2C will work. The Arduino libraries are not meant for that.
  • A Arduino as a Slave can keep the SCL low while it is busy with the onRequest handler. It can be expected that the SCL is stuck low after a few seconds.
  • When the Master is powered off, are the I2C signals low ? If they are then I don't know how the Slaves would react to that, the Slaves themself probably don't know that either.

When I was fooling around with the I2C bus in the past, I could cause a big mess within seconds. So that is where you are at the moment.

What is the distance ? Why is it isolated ? How long are the wires, or do you use a cable for the I2C bus ? Is the I2C bus also used to transfer data for something else ? Or only for the battery voltage ?

To tell the voltage of the battery is easy, because it can be slow. Once per 15 minutes or with less than 1 baud, everything is okay. You could use wireless, or with digital pins with handshake or without (for example some kind of morse code or the code of a IR remote), or with the VirtualWire/RadioHead library and a optocoupler, or with (software) Serial, and so on.

We like to see a working sketch, so we can verify the results that you have. However, 'fvalue' is not declared.

If this were my project, and it were required that the master and slaves be powered independently, with anticipated power down states or power failures, then for reliability I would choose complete electrical isolation (optocouplers) between the nodes and asynchronous serial communication.

I would use two channels (RX and TX) between each processor pair, either independent pairs or a single multidrop pair (more difficult). A query/acknowledge protocol would be used to wake sleeping processors, followed by standard packet based communications if successful. Dead nodes would be ignored.

An interesting challenge!

@OP How does Slave sense that the Master has 'powered off' -- an event that can happen to Master at any time for any reason?

@jremington Can HC05 (BT) or HC12 (Radio) be used in place of optocouplers?

@Koepel

So far the communication between master and slave has been successful EXCEPT when I add this code below. By adding this one of the slaves keep the SCL low.

long result; // Read 1.1V reference against AVcc
 ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1); 
 delay(2);
 // Wait for Vref to settle
 ADCSRA |= _BV(ADSC); // Convert
 while (bit_is_set(ADCSRA,ADSC));
 result = ADCL; 
 result |= ADCH<<8;
 result = 1126400L / result;

Otherwise everything is working as expected. Sure earlier the SDA lines were being held low sometimes by the slaves before going to sleep and I think I have tackled the problem by re-initializing the I2C hardware before sleeping:

TWCR = bit(TWEN) | bit(TWIE) | bit(TWEA) | bit(TWINT);

Now I may have resetted the master and powered down and powered up the master around 40+ times and I have not faced any issue with the SDA line held low.

Koepel:
The main problem is that when a battery is low, that Arduino will block the I2C bus.

Please explain this in detail I am interested to know this. How much low is low? I mean right now my slave’s battery is sitting at 3.7V and the master at 5V and they seem to be talking just fine.

Koepel:

  • The sleep mode could be entered while the I2C bus is busy.
  • I don’t know if waking up by I2C will work. The Arduino libraries are not meant for that.
  • A Arduino as a Slave can keep the SCL low while it is busy with the onRequest handler. It can be expected that the SCL is stuck low after a few seconds.
  • When the Master is powered off, are the I2C signals low ? If they are then I don’t know how the Slaves would react to that, the Slaves themself probably don’t know that either.
  • I think i understand what you mean by that and I think I have answered this already before that before sleeping the slaves re-initialize their I2c hardware before going to sleep so they don’t hold any line low.
  • According to my research you only need to send an interrupt to the slave to wake it. Wire.onRequest() is an interrupt and it wakes up the slave.
  • I don’t understand really how the slave can keep the SCL low whiling handling onRequest.
  • I don’t have any oscillator at the moment to actually know the condition of the lines when the master is powered off sorry :frowning:

Regarding the distances and dimensions of the buses, they are not confirmed yet. But I am aware about the relation of bus capacitance and pull up resistors. It might be possible that this is actually the main issue (right now my project is partially on home made pcb and breadboard) and 4k7 pullup resistors are attached and communication is well. Only the dreaded code to measure the Vcc of the batteries in the slave code makes the SCL hang ( and I am suspecting that the tampering of ADCSRA has to do with it but I want someone to tell me what the problem with this part of code is)

And I removed some unnecessary comments so I might have removed declaration of fvalue by accident :smiley:

@GolamMostafa

My slaves are continuously counting the time when the master does not sends them the onRequest interrupt. I have made a small function named “comm_check(int)” you can see it in my slave code for its working. Around 500ms later the slaves are convinced that the master has been powered off or is faulty or, whatever the case, and they activate the sleep mode. Sleep mode’s code is in the function “sleep_time()” where I am disabling ADC, resetting the I2C hardware (or so I think I am doing) and I am setting the sleep mode in SLEEP_MODE_PWR_DOWN.

It is not just one thing that can be fixed. Everything together is unreliable, and I don't see a way to make it reliable.

Reading the VCC voltage with the MUX is not related with SCL. But you do that code with the delay() in the requestEvent(). Sometimes I call it the "onRequest handler". That is an interrupt routine. By staying longer in the interrupt routine, you get into trouble. That is normal, even without all the other problems.

A I2C sensor has hardware for the I2C communication. A Arduino as a Slave is a combination of hardware and software. When data is requested, the Slave Arduino can make SCL low to do things in software. I think it is called "clock stretching". A Slave can put the Master in pause by keeping the SCL low.

Waking up the Arduino with an interrupt is okay. But waking up and then enabling things and calling Wire.begin() is not okay. The interrupt that wakes up the Arduino is executed first before those things are enabled. The Wire.begin() could be called while the Wire library is still busy with interrupts to handle the I2C bus communication. Perhaps the reading the VCC voltage is done while the ADC is still turned off. Please do not try to fix these things, because there are so many other problems.

There are so many ways that this can go wrong. If you can make it work without problems, then it might stop working when the Arduino IDE or the compiler is updated.

I hope you understand that jremington, GolamMostafa and me see too many problems. We stay away from such things because there is no predictable path of the program flow. And that is needed to make it reliable. We don't even want to help you fix the one problem of reading the VCC voltage, because there might be 10 other problems that can not be fixed.

Okay. Appreciate the help. :)

@OP

I tested your codes in this setup: UNO-Master and NANO-Slave. There happens a communication between UNO and NANO for a while and then a halt. I studied your codes and setup both theoretically and experimentally based on which I am making the following observations/comments. Finally, I modified your codes a little bit to make my setup working. You may upload these sketches in your UNO-Master and stand-alone 328P Slave and see that your setup also works.

1. There is no need to have pull-ups of the I2C Bus at the Slave side.

2. You have uploaded mixed codes (Arduino Codes + Register Level Codes); so, before manipulating ADMUX Register, you should load 0x00 into it.

3. You said that you are measuring internal 1.1V. You have selected the correct ADC Channel (Ch-15); but, you have selected the incorrect VREF for the ADC. This is the reason for which we see around 4.8V instead of 1.1V at the Serial Monitor of Master.

4. The float number is not exact; therefore, you should make an comparison test rather than equality. I recommend --

if (s1value <0.000002)//== 0.00)

Slightly Modified UNO-Master Codes:

#include <Wire.h>

void setup()
{
  Serial.begin (9600);
  Wire.begin();
  slave_wakeup (0x08);
  //slave_wakeup (2);
}

void loop()
{
  float s1value = slave_data(0x08);
  if (s1value > 0.00)
  {
    SI (1, '1');
  }

  if (s1value <0.000002)//== 0.00)
  {
    SI (1, '0');
  }
  Serial.println (s1value);
  /*
    float s2value = slave_data(2);
    if (s2value > 0.00)
    {
    SI (2, '1');
    }

    if (s2value == 0.00)
    {
    SI (2, '0');
    }

    Serial.println (s2value);
  */
  Serial.println ("....");
}

void slave_wakeup(int add)
{
  Wire.beginTransmission (add);
  Wire.endTransmission();
  delay (5);
}

float slave_data (int add)
{
  // int k = 0;
  char c2[4] = "";
  byte n = Wire.requestFrom (add, 4);
 // Serial.println(n);
  // while (Wire.available ())
  // {
  for (int k = 0; k < n ; k++)
  {
    char b = Wire.read();
    c2[k] = b;
    //  k++;
  }
  // }

  float value = atof (c2);
  return value / 1000.0;
}

void SI  (int add, char c)
{
  Wire.beginTransmission (add);
  Wire.write (c);
  Wire.endTransmission();
}

Slightly Modified Slave Codes:

#include <Wire.h>
#include <avr/sleep.h>
int count = 0;
volatile long result;

void setup()
{
   Wire.begin (0x08);    //use address of this range: 0x80 - 0x7E
  //int result;//long result; // Read 1.1V reference against AVcc
  ADMUX = 0x00;
  ADMUX |= _BV(REFS1) | _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);

  Wire.onReceive (receiveEvent);
  Wire.onRequest (requestEvent);
  pinMode (2, OUTPUT);
}

void loop()
{
  delay (100);
  comm_check (1);
}

void requestEvent ()
{
  //delay(2);
  // Wait for Vref to settle
  ADCSRA |= _BV(ADSC); // Convert
  while (bit_is_set(ADCSRA, ADSC))
    ;
  result = ADCL;
  result |= ADCH << 8;
  result = 1126400L / result;

  char svalue [4] ;
  dtostrf (result , 4 , 2 , svalue);
  Wire.write (svalue, sizeof(svalue));
  comm_check (0);
}

void receiveEvent ()
{
  //while (Wire.available ())
  // {
  char c = Wire.read ();
  if (c == '1')
  {
    digitalWrite (2, HIGH);
  }
  else
  {
    digitalWrite (2, LOW);
  }
  //}
}

void comm_check (int c)
{
  if (c == 1)
  {
    count ++;
  }
  else
  {
    if (c == 0)
    {
      count = 0;
    }
  }

  if (count >= 5)
  {
    sleep_time();
  }
}

void sleep_time()
{
  TWCR &= ~(_BV(TWEN));
  TWCR = 0;
  TWCR = bit(TWEN) | bit(TWIE) | bit(TWEA) | bit(TWINT);
  byte old_ADCSRA = ADCSRA;
  // disable ADC
  ADCSRA = 0;
  set_sleep_mode (SLEEP_MODE_PWR_DOWN);
  sleep_enable();
  digitalWrite (2, LOW);
  sleep_cpu();
  sleep_disable();
  count = 0;
  ADCSRA = old_ADCSRA;
  Wire.begin (2);
}

Screenshot of Master
sm167.png

sm167.png

I understand that you have put a lot of time into this project and don't want to deviate from the chosen path. However, questions like the one below make it clear that you do [u]not[/u] fully understand the difficulties that you will encounter:

How much low is low? I mean right now my slave's battery is sitting at 3.7V and the master at 5V and they seem to be talking just fine.

Batteries ALWAYS die. ALWAYS. What is your plan for that situation?

If you continue down this path, you will keep running into failures that you did not anticipate. The sooner you understand this, the sooner you can start on a new path that could succeed.

@GolamMostafa

Thank you so much. This is the help I needed for the code. But I intended to measure the Vcc of the slave using its internal 1.1V as reference. I copied the Vcc measurement code from the internet and I’ve seen it a lot somewhere in few forums too. In fact jremington just shared the link to the same code in one of his earlier replies. I don’t fully understand the code that how internal 1.1V is being used to check its actual Vcc (something to do with constant 1.1V and constantly finding out the bandgap and using these values to find Vcc), else I would have debugged the code myself. And probably the value you were getting earlier of 4.8V was the correct one since it was showing you the Vcc of your Nano.

I would be very grateful if can you please modify it for my needs and I can later study what you have done :slight_smile:

@jremington

This piece of project is just a dummy project for the actual project I am trying to make. Cell balancing while the batteries get charged (consider it as a partial BMS) with slave modules sitting at individual cells of the combined battery pack, and each slave would be powered up by their respective cells they are monitoring (hence I included an isolated I2C communication). The master will continuously check the battery levels of each slave and give a shunting signal to those who are getting charged too fast. This dummy project is very similar to the main project since I want to successfully simulate the communication and issues while the system will be in working condition. The only difference will be in the thinking part in the master code. Initially I thought of installing REG71055 (datasheet attached) to supply a constant 5V to the slave but I thought that if i can just tackle this situation through code then it would be awesome.

reg71055.pdf (1.89 MB)

Ahh, it is worse than we thought.

lmao

lipoV.png


1. Full scale of the ADC is: VREF = lipoVolt

2. When VREF is applied to any channel of ADC, the ADC value is: 1023 (1024?).

3. When 1.1V is applied at Ch15 of ADC, the ADC value is: (1023/VREF)*1.1

4. ADC value (x) is:
byte x1 = ADCL;
int x = (ADCH<< 8 ) | x1;
==> x = (1023/VREF)1.1
==> VREF = (1023
1.1)/x
==> lipoVolt = (1125.3)/x
==> 1000lipoVolt = 1000(1125.3)/x
==> 1000*lipoVolt = 1125300/x
==> long result = (long)1125300/x; //terminal volt of lipo Battery changes; VREF changes; ADC value changes

lipoV.png