5

I am struggeling with a behaviour of my Arduino I do not fully understand.

Long story short:

When starting the communication over I2C the Arduino freezes. In my opinion during the "Wire.write()" command.

Long story and background:

I want to connect my Arduino Uno to an Arduino MKR Wifi 1010 via I2C. I implemented the code for the communication step by step, so I started with the code below.

Code for the Master Arduino Uno:

#include <Wire.h>
#include <Arduino.h>

#define SLAVE_ADRESS                            2

void Send_Data(int receiver, int data)
{
    Wire.beginTransmission(receiver);
    Wire.write(data);          
    Wire.endTransmission();
    Serial.println(F("Data sent"));
}

void setup() {
  Serial.begin(38400);
  while (!Serial) {;} /* Wait for serial interface to be connected and ready for use. */
  Serial.setTimeout(2000);
  Serial.println(F("Serial Connection ready"));
  Wire.begin(); // join i2c bus (address optional for master)
}

void loop() {
  static int LedStatus = 0;
  if (LedStatus == 0)
  {
      LedStatus = 1;
  }
  else
  {
      LedStatus = 0;
  }
  Serial.print(F("Master LED status: "));
  Serial.println(LedStatus);         
  Send_Data(SLAVE_ADRESS,LedStatus);
  delay(500);
}n

Code for the Slave Arduino MKR Wifi 1010:


#include <Wire.h>
#include <Arduino.h>

#define NODE_ADDRESS 2

int led = 0;

void setup() 
{
  Serial.begin(38400);
  while (!Serial)
  {
        ; /* Wait for serial interface to be connected and ready for use. */
  }
  Serial.setTimeout(1000);
  
  Wire.begin(NODE_ADDRESS);
  Wire.onReceive(receiveEvent);
}

void receiveEvent(int bytes)
{
  led = Wire.read();              
  Serial.println("Data reveived!");
  Serial.print("Slave Value led: "); 
  Serial.println(led);

}

void loop() 
{

}

This piece of code works fine as expected. So I implemented the very same functionality for the master into my projekt.

/****************************************************************************/
/*-------------------------------Description--------------------------------*/
/****************************************************************************/

/*
Used board: Arduino Uno

>>> Pin usage overview <<<
0:
1:
2: waterlevel tank sensor low (black sensor wires)
3: waterlevel tank sensor high (red sensor wires)
4: waterlevel reservoir sensor low (not used yet)
5: waterlevel reservoir sensor high (not used yet)
6: pump level tank
7: pump level reservoir
8: water temperature sensor
9: water temperature sensor
10: 
11:
12:
13:
A0: EC sensor
A1: pH sensor
A2:
A3:
A4:
A5:
/*

/****************************************************************************/
/*---------------------------------Includes---------------------------------*/
/****************************************************************************/

#include <Arduino.h>
#include <RTClib.h>
#include <TimerOne.h>
#include <Wire.h>
#include "WaterCondition.h"
#include "WaterFlow.h"
#include "Serial.h"

/****************************************************************************/
/*----------------------------------Macros----------------------------------*/
/****************************************************************************/

#define TIMER_INTERVAL                          2000000 // 5sec (timer in microsec)

#define RTC_INIT_TIME_SECOND                    0
#define RTC_INIT_TIME_MINUTE                    0
#define RTC_INIT_TIME_HOUR                      0
#define RTC_INIT_TIME_DAY                       0
#define RTC_INIT_TIME_MONTH                     0
#define RTC_INIT_TIME_YEAR                      0

#define OPERATING_MODE_AUTO                     0
#define OPERATING_MODE_TEST_TEMP_SENSOR         1
#define OPERATING_MODE_TEST_EC_SENSOR           2
#define OPERATING_MODE_TEST_PH_SENSOR           3
#define OPERATING_MODE_TEST_RTC                 4
#define OPERATING_MODE_TEST_WATERLEVEL          5
#define OPERATING_MODE_TEST_COMMUNICATION       6

#define SLAVE_ADRESS                            2 // adress of the slave for communication

/****************************************************************************/
/*-----------------------------Global Variables-----------------------------*/
/****************************************************************************/

/* 
OPERATING_MODE 0: Automatic
OPERATING_MODE 1: Testing / Calibration
*/
uint8_t OperatingMode = 0;

/* Used for Serial.h to read the input from serial connection */
extern char StringBuffer[STRING_BUFFER_SIZE];
extern float WaterConPhVolt, WaterConEcVolt, WaterConPhVal, WaterConEcVal, WaterConTempVal;

/****************************************************************************/
/*------------------------------Initialization------------------------------*/
/****************************************************************************/

RTC_DS3231 rtc;
DateTime now;


int freeRam() 
{
    extern int __heap_start, *__brkval; 
    int v; 
    return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); 
}


/* standard operations */
void ISR_Task(void) 
{
    if (OperatingMode == OPERATING_MODE_AUTO) // Automatic
    {
        Serial.println(F("Running in automatic mode"));

        uint8_t min;
        min = now.second(),DEC;
        if ((min == PUMP_TANK_ON_INTERVALL_1) || (min == PUMP_TANK_ON_INTERVALL_2))
        {
            //Serial.println(F("Test: RTC min = 0 or 30");
            //Activate pump
            //Function tbd
        }
        else if ((min == PUMP_TANK_OFF_INTERVALL_1) || (min == PUMP_TANK_OFF_INTERVALL_2))
        {
            //Serial.println(F("Test: RTC min = 20 or 50");
            //Deactivate pump
            //Function tbd
        }
        WaterConTempVal = WaterCon_Sensor_TEMP(0);
        WaterConEcVal = WaterCon_Sensor_EC();
        WaterConPhVal = WaterCon_Sensor_PH();
        if (Waterflow_Tank() == 99)
        { 
            // tbd
        }
        
    }
    else // Testing
    {
        /* >>> Test the temperature sensor <<< */
        if (OperatingMode == OPERATING_MODE_TEST_TEMP_SENSOR)
        {
            Serial.print(F("Water temperature filtered: "));
            Serial.println(WaterCon_Sensor_TEMP(0));
        }

        /* >>> Test the PH sensor <<< */
        else if (OperatingMode == OPERATING_MODE_TEST_PH_SENSOR)
        {
            float test_ph = 0;
            test_ph = WaterCon_Analog_Read(PH_PIN, VOLTAGE_PIN, RESOLUTION_SENSOR_PH);
            Serial.print(F("PH-Sensor-Value raw from 0 - 5000mV: "));
            Serial.println(test_ph);
            Serial.print(F("PH-Value from 0 - 14: "));
            Serial.println(WaterCon_Sensor_PH());
        }

        /* >>> Test the PH sensor <<< */
        else if (OperatingMode == OPERATING_MODE_TEST_EC_SENSOR)
        {
            float test_ec = 0;
            test_ec = WaterCon_Analog_Read(EC_PIN, VOLTAGE_PIN, RESOLUTION_SENSOR_EC);
            Serial.print(F("EC-Sensor-Value raw from 0 - 5000mV: "));
            Serial.println(test_ec);
            Serial.print(F("EC-Value from 0 - 2000uS: "));
            Serial.println(WaterCon_Sensor_EC());
        }

        /* >>> Test the RTC clock <<< */
        else if (OperatingMode == OPERATING_MODE_TEST_RTC)
        {
            Serial.print(F("\nRTC-Time: "));
            Serial.println(now.second(),DEC);
        }

        /* >>> Test the waterlevel sensors <<< */
        else if (OperatingMode == OPERATING_MODE_TEST_WATERLEVEL)
        {
            int WaterLvlSensorLow = 0;
            int WaterLvlSensorHigh = 0;
            WaterLvlSensorLow = digitalRead(TANK_LVL_LOW_PIN);
            WaterLvlSensorHigh = digitalRead(TANK_LVL_HIGH_PIN);
            
            Serial.print(F("Waterlevel Sensor Low: "));
            Serial.println(WaterLvlSensorLow);
            Serial.print(F("Waterlevel Sensor High: "));
            Serial.println(WaterLvlSensorHigh);
        }

        /* >>> Test the communication over I2C <<< */
        else if (OperatingMode == OPERATING_MODE_TEST_COMMUNICATION)
        {
            static int LedStatus = 0;
            if (LedStatus == 0)
            {
                LedStatus = 1;
            }
            else
            {
                LedStatus = 0;
            }                        
            Serial.print(F("Master LED status: "));
            Serial.println(LedStatus);
            Send_Data(SLAVE_ADRESS,LedStatus);
        }
    }
}

void Send_Data(int receiver, int data)
{
    Wire.beginTransmission(receiver);
    Wire.write(data);        
    Wire.endTransmission();
    Serial.println(F("Data sent"));
}

/****************************************************************************/
/*-------------------------Function Implementations-------------------------*/
/****************************************************************************/

void setup() {
    Serial.begin(38400);
    while (!Serial) {;} /* Wait for serial interface to be connected and ready for use. */
    Serial.setTimeout(2000);
    Serial.println(F("Serial Connection ready"));
    

    /* Initialize sensors */
    Serial.println(F("Setup Sensors"));
    WaterCon_Sensor_Init();
    Waterflow_Sensor_Init();

    /* Initialize RTC */
    Serial.println(F("Setup RTC"));
    rtc.begin();
    Serial.println(F("Test"));

    /* Initialize timers */
    Serial.println(F("Setup Timers"));
    Timer1.initialize(TIMER_INTERVAL);
    Timer1.attachInterrupt(ISR_Task);
    Serial.println(F("Setting the time for the RTC..."));
    rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));


    /* Initialize I2C Master-Slave */
    Wire.begin();
}

void loop()
{
    now = rtc.now();

    delay(200);
    Serial_Read(1);
    /* Change operating mode to automatic */
    if (strcmp("opauto",StringBuffer) == 0)
    {
        Serial.println(F("Operating Mode changed to automatic"));
        OperatingMode = 0;
    }
    /* Change operating mode to testing */
    else if (strcmp("optest1",StringBuffer) == 0)
    {
        Serial.println(F("Operating Mode changed to Testing: temperature sensor"));
        OperatingMode = 1;
    }
    else if (strcmp("optest2",StringBuffer) == 0)
    {
        Serial.println(F("Operating Mode changed to Testing: EC sensor"));
        OperatingMode = 2;
    }    
    else if (strcmp("optest3",StringBuffer) == 0)
    {
        Serial.println(F("Operating Mode changed to Testing: pH sensor"));
        OperatingMode = 3;
    }
    else if (strcmp("optest4",StringBuffer) == 0)
    {
        Serial.println(F("Operating Mode changed to Testing: RTC clock"));
        OperatingMode = 4;
    }
    else if (strcmp("optest5",StringBuffer) == 0)
    {
        Serial.println(F("Operating Mode changed to Testing: Waterlevel Sensors"));
        OperatingMode = 5;
    }
    else if (strcmp("optest6",StringBuffer) == 0)
    {
        Serial.println(F("Operating Mode changed to Testing: Communication via I2C Master-Slave"));
        OperatingMode = 6;
    }
}

Some explanation for you to understand what I am trying to do: Above you can see the code from the Main.ino file for a hydroponic project. I wanted to realize different operating modes for automatic and testing. So if I encounter a fault, I want to be able to plug the laptop on the USB port and start to read out some information over the serial port. Later this should be accomplished over the air but for the beginning this should be fine. To achieve this, I put string compare into the loop to notice if the user did some input and respond to it. I tested that functionality a lot and it worked fine. I am able to jump from any operating mode to another.

There is an external real time clock running for using interrupts and run code at specific times. During this interrupt I check what operating mode is active and run the respective code. With the string compare I mentioned above I can jumo into the respective code with the next interrupt.

ISSUE: Now I can describe the actual issue. Everytime I enter the "operating mode 6" to test the I2C connection, the Arduino freezes. As you can see I used the very same code inside the "else if" as tested before. This is the relevant part of the code:

        /* >>> Test the communication over I2C <<< */
        else if (OperatingMode == OPERATING_MODE_TEST_COMMUNICATION)
        {
            static int LedStatus = 0;
            if (LedStatus == 0)
            {
                LedStatus = 1;
            }
            else
            {
                LedStatus = 0;
            }                        
            Serial.print(F("Master LED status: "));
            Serial.println(LedStatus);
            Send_Data(SLAVE_ADRESS,LedStatus);
        }
    }
}

void Send_Data(int receiver, int data)
{
    Wire.beginTransmission(receiver);
    Wire.write(data);        
    Wire.endTransmission();
    Serial.println(F("Data sent"));
}

In the following picture it looks like the Board freezes during the serial communication when it prints out "Master LED status: ". I testet this by commenting out the serial.print commands and insert a print command between Wire.begin and Wire.write in the code. enter image description here

I think the main problem is caused by the Wire.write function, but I am not able to debug it other than using serial.print commands. But the printed commands do not correlate with the line of code when the board actually freezes. I think I would need real debugger that stops the processor and gives me the possibility to go through the code step by step.
At first I thought this issue was caused because the SRAM got filled. So I put the Serial.print commands in the flash memory and inserted some lines of code for checking the usage of the SRAM and the Stack. You can see the "freeRam()" function in the code, but I removed them for better readability.

Did anyone have a similar issue or do you know what could cause this issue?

Thanks a lot in advance!

1
  • 2
    In addition to @chrisl's answer, please read Nick Gammon write-up on Interrupts.
    – hcheung
    Commented Oct 8, 2021 at 12:50

1 Answer 1

5

The program most likely hangs at Wire.endTransmission();, because that's where the actual I2C communication happens. Wire.write() does nothing but placing the data into the internal buffer of the Wire library.

You experience problems because you are calling interrupt-dependent code inside an ISR, where interrupts are deactivated by default.

The Wire library uses the I2C hardware of the Uno. This hardware generated interrupts for the different stages in the communication (for example generating an interrupt so that the library can place the next data byte in the hardware buffer). That means that the Wire library will not work if interrupts are turned of for some reason. It will wait forever for an interrupt that will never come.

When entering an ISR (in this case of the Timer1 interrupt) other interrupts are deactivated by default. At the end of the ISR the compiler will again turn on interrupts. Any interrupts which happen during the execution of the current ISR will be executed after it has exited.

With Wire.endTransmission() you currently have a function call in your Timer1 ISR, which will wait for its corresponding I2C ISRs to run, which cannot happen inside the Timer1 ISR.

What to do now: You need to restructure your code. You shouldn't do that much stuff in an ISR either way. It needs to be as short as possible to not disturb other interrupt based functions. In the ISR just set a simple 1-byte flag variable. Then in loop() check this flag variable. If it is set, you can do the stuff, which currently is in your ISR. And then you reset the flag to be ready for the next ISR setting it again. For this to work correctly you need to ditch the delay() call in loop(). If you want to do timed things, you can use millis() like in the BlinkWithoutDelay example, that comes with the Arduino IDE. Make sure that nothing can block the loop() function for a long period of time (meaning longer than the interval between 2 interrupts or longer than the minimal time resolution, that you want in your system).


Note: You thought of the wrong line while debugging, because of your Serial output. But Serial output is only helping to a certain extent with debugging, since it too needs interrupts to work correctly. All the data that gets printed in your ISR will only be send once the ISR exits. Serial.print() and its siblings only fill the buffer of the library, while the actual sending is done in the background via ISRs. So the sending stopped when it got stuck at the ISR, but that doesn't mean that this actually was what you printed directly before that line. Even when not having interrupts involved this might not be the case. Just keep that in mind when using Serial prints for debugging.

2
  • 2
    Thank you so so much for taking your time to give me such a detailed insight into the topic of ISR and I2C. I appreciate every comment that helps me writing better code :). I will restructure my code and give a feedback! Commented Oct 8, 2021 at 12:09
  • Restructuring the code, as you mentioned above, solved the issue. I created a flag for the ISR and only change that value inside the ISR. The other stuff was put inside the loop(). Commented Oct 8, 2021 at 15:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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