Why does my Arduino freeze when load (motor, pump) is switched on, even when using a relay?

Hi all, I have a phenomenon I don't quite understand. I am aware of the dangers of inductive loads, but this one I don't understand.

I have the attached wiring, of which mainly the pump/motor and associated switching is of interest now.

EDIT: Looking at the image I just noticed the pump has reversed polarity. This is not the case on my breadboard.

This is supposed to become a plant watering tool.There are analog connectors onto which up to 4 moisture sensors can be connected (currently only 1). I have also an OLED display attached on one of the I2C ports which tells the current moisture readings, and the rotary encoder can be used to set a desired value for the moisture. If the desired value is drier than the current reading, the pump is switched on.

But when the load (the pump) is switched on, the Arduino program freezes, as does the OLED display. The pump is a pretty small 3-6V motor. I had a guess I'm doing something wrong with the switching circuit involving a transistor and flyback diode. But I get the same problem when instead using a relay module, switching the pump in a fully isolated circuit! When there is no load on the relay but the relay still switches (i. e., coil has current), the problem does not occur. Also, the problem does not occur when using an H bridge as a motor driver (but I don't need that I just need on/off).

I am using an Arduino Nano Every but have also tried this on an Arduino Uno.

I would be delighted to understand this phenomenon and appreciate any help :slight_smile:

Code and part specs please.
You don't show how the board is powered.
D4 goes straight to ground.
Your flyback diode should probably be in parallel with your motor. It's not reversible is it?

image

1 Like

Yes.

The FB-diode isn´t connected as FB-diode.
This diode has to be connected to the pump directly.

Have a nice day and enjoy coding in C++.

1 Like

Thank you for replying! Code is very extensive and I'm working on a minimal version which also shows the problem. I will post all the parts then, but for now:

And yes thanks for the hint about the diode to be parallel. I thought so to but was unsure after I had the same problem with a relay.

I just wanted to make sure you aren't trying to pump water back and forth in two directions. So you're not reversing it and just need to get the diode correct for that part.

Is D4 set as input pullup?

Ah no, the pump is not reversible, it just pumps either way (but worse with wrong polarity).
Yes, D4 is set up as input pullup. Hang on, I am nearly done with the code.
Here's the full code, now that you started pointing to D4 :eyes:


#include <Wire.h>  // required for i2c

#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

// #include <Adafruit_AM2320.h>

// this is the display object, the constant is just
// needed for initiation:
#define OLED_RESET 4
Adafruit_SSD1306 display(OLED_RESET);

// this is the temp and humid sensor:
// #define Adafruit_AM2320 am2320 = Adafruit_AM2320()



const int moistPin1 = A0;  // the pin where the moisture sensor is connected
const int moistPin2 = A1;

const int pumpSwitchPin = 5; // the pin of the switch/relay that switches on the pump
const int pumpIndicatorLedPin = LED_BUILTIN;      // select the pin for the LED

int moistValue1 = 500;
int moistValue2 = 500;
int moistAvg = 0;

const int rotarySwPin = 4;
const int rotaryDtPin = 3;
const int rotaryClkPin = 2;

int clkStateCurrent = LOW;
int clkStateLast = LOW;

int lastButtonPressMillis = millis();
int lastButtonReleaseMillis = millis();

int moistWanted = 500;

const int DOUBLE_CLICK_THRESHOLD = 333;  // button click within ... milliseconds
const int LONG_CLICK_THRESHOLD = 3000;  // millis required to pass for long click

const int moistThresholdOn = 10;  // difference in reading points that the
                                  // reading has to be **lower** than desired
                                  // to switch the pump ON
const int moistThresholdOff = 10;  // difference in reading points that the 
                                   // reading has to be **higher** than the
                                   // wanted to switch the pump OFF

bool pumpNeeded = false;
bool asleep = false;

// Only the dt is observed constantly.  This happens here:
void dtFunc() {
  // stop any other interrupts during this process:
  detachInterrupt(digitalPinToInterrupt(rotaryDtPin));
  detachInterrupt(digitalPinToInterrupt(rotarySwPin));
  clkStateCurrent = digitalRead(rotaryClkPin);
  if (clkStateLast == LOW && clkStateCurrent == HIGH) {
    if (digitalRead(rotaryDtPin) == HIGH) {
      moistWanted = max(moistWanted - 1, 0);
    } else {
      moistWanted = min(moistWanted + 1, 1024);      
    }
  }
  clkStateLast = clkStateCurrent;
  attachInterrupt(digitalPinToInterrupt(rotaryDtPin), dtFunc, CHANGE);
  attachInterrupt(digitalPinToInterrupt(rotarySwPin), swFunc, CHANGE);
}

void swFunc() {
  int currentMillis = millis();
  detachInterrupt(digitalPinToInterrupt(rotarySwPin));
  detachInterrupt(digitalPinToInterrupt(rotaryDtPin));
  if (asleep) {
    
  }
  if (digitalRead(rotarySwPin) == LOW) {
    if (currentMillis - lastButtonPressMillis < DOUBLE_CLICK_THRESHOLD) {
      doubleClickAction();
    }
    lastButtonPressMillis = currentMillis;
  } else if (digitalRead(rotarySwPin) == HIGH) {
    lastButtonReleaseMillis = currentMillis;
  }
  attachInterrupt(digitalPinToInterrupt(rotarySwPin), swFunc, CHANGE);
  attachInterrupt(digitalPinToInterrupt(rotaryDtPin), dtFunc, CHANGE);
}

int getMoistAvg(int moist1, int moist2) {
  return(round((moist1 + moist2) / 2));
}

void doubleClickAction() {
  Serial.println("double Click happened");
}

void setup() {
  pinMode(pumpIndicatorLedPin, OUTPUT);
  pinMode(pumpSwitchPin, OUTPUT);

  pinMode(rotarySwPin, INPUT_PULLUP);
  pinMode(rotaryDtPin, INPUT);
  pinMode(rotaryClkPin, INPUT);

  attachInterrupt(digitalPinToInterrupt(rotaryDtPin), dtFunc, CHANGE);
  attachInterrupt(digitalPinToInterrupt(rotarySwPin), swFunc, CHANGE);

  analogReference(EXTERNAL);
  Serial.begin(9600);

  // init display:
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);

  // init temp and humid sensor:
  // am2320.begin()
}

void displayMain() {
  display.clearDisplay();
  display.setTextColor(WHITE);
  display.setTextSize(1);
  display.setCursor(0, 0);
  display.print("Moist 1, 2: ");
  display.print(moistValue1);
  display.print(", ");
  display.print(moistValue2);
  display.setCursor(0, 10);
  display.print("Avg: ");
  display.print(moistAvg);
  display.print(" | want: ");
  display.print(moistWanted);
  display.setCursor(0, 20);
  display.print(millis());
}

void displayCalibration() {
  delay(100);

}

int readMoisture(int pin, int prevVal) {
  int idx;
  int reading;
  int readingSum = 0;
  int n_iter = 5;
  for (idx = 0; idx < n_iter; idx ++) {
    readingSum += analogRead(pin);
    delay(10);
  }
  reading = round(readingSum / n_iter);
  if (abs(prevVal - reading) > 5) {
    return(reading);
  }
  return(prevVal);
}

void printDataToSerial() {
  // prints the available data in  JSON format to Serial
  Serial.print("{");
  Serial.print("\"moisture-1\":");
  Serial.print(moistValue1);
  Serial.print(",");
  Serial.print("\"moisture-2\":");
  Serial.print(moistValue2);
  Serial.print("\"moisture-avg\":");
  Serial.print(moistAvg);
  Serial.print(",");
  Serial.print("\"temperature\":");
  Serial.print(0.0);
  Serial.print(",");
  Serial.print("\"humidity\":");
  Serial.print(0.0);
  Serial.print(",");
  Serial.print("\"moist-wanted\":");
  Serial.print(moistWanted);
  Serial.print("}");
  Serial.print('\n');
}


void pumpActivity() {
  // note that high values mean *dry* for capacitive sensors!
  if (moistAvg - moistWanted > moistThresholdOn && !pumpNeeded) {
    pumpNeeded = true;
    digitalWrite(pumpIndicatorLedPin, HIGH);
    digitalWrite(pumpSwitchPin, HIGH);
  } else if (moistWanted - moistAvg > moistThresholdOff && pumpNeeded) {
    pumpNeeded = false;
    digitalWrite(pumpIndicatorLedPin, LOW);
    digitalWrite(pumpSwitchPin, LOW);
  }
}


void mainRun() {
  // main function if awake
  if (Serial.available() > 0) {
    while (Serial.available() > 0) {
      if (Serial.readStringUntil('\n') == "print_data") {
        printDataToSerial();
      }
    }
  }
  // read the value from the sensor:
  moistValue1 = readMoisture(moistPin1, moistValue1);  // has 50ms delay
  // moistValue2 = readMoisture(moistPin2, moistValue2);  // has 50ms delay
  moistAvg = getMoistAvg(moistValue1, moistValue2);
  displayMain();
  display.display();

  // supervise pump:
  pumpActivity();

  if (millis() - lastButtonReleaseMillis > LONG_CLICK_THRESHOLD && digitalRead(rotarySwPin) == LOW) {
    Serial.println("long click happened");
  }
}

void loop() {
  //delay(10000);
  if (asleep) {
    // LowPower.deepSleep(1000 * 60 * 30);  // sleep for 30 Minutes
  } else {
    mainRun();
  }
}

#define OLED_RESET 4

const int rotarySwPin = 4;

Is that right? May not be your issue, but just saw it.

Yes, your hint pointed me straight to it! Thank you! I changed it to an unused pin. I have the feeling the display updates run smoother. I also wired the diode in parallel to the pump. Still, the freezing problem persists when the load is turned on :frowning:

Here is a minimal version of the code, still with the freezing problem:


#include <Wire.h>  // required for i2c

#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

// #include <Adafruit_AM2320.h>

// this is the display object, the constant is just
// needed for initiation:
#define OLED_RESET 12
Adafruit_SSD1306 display(OLED_RESET);

// this is the temp and humid sensor:
// #define Adafruit_AM2320 am2320 = Adafruit_AM2320()



const int moistPin1 = A0;  // the pin where the moisture sensor is connected

const int pumpSwitchPin = 5; // the pin of the switch/relay that switches on the pump
const int pumpIndicatorLedPin = LED_BUILTIN;      // select the pin for the LED

int moistValue1 = 500;

int moistWanted = 500;

const int moistThresholdOn = 10;  // difference in reading points that the
                                  // reading has to be **lower** than desired
                                  // to switch the pump ON
const int moistThresholdOff = 10;  // difference in reading points that the 
                                   // reading has to be **higher** than the
                                   // wanted to switch the pump OFF

bool pumpNeeded = false;
bool asleep = false;

void doubleClickAction() {
  Serial.println("double Click happened");
}

void setup() {
  pinMode(pumpIndicatorLedPin, OUTPUT);
  pinMode(pumpSwitchPin, OUTPUT);

  analogReference(EXTERNAL);
  Serial.begin(9600);

  // init display:
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
}

void displayMain() {
  display.clearDisplay();
  display.setTextColor(WHITE);
  display.setTextSize(1);
  display.setCursor(0, 0);
  display.print("Moist 1: ");
  display.print(moistValue1);
  display.setCursor(0, 10);
  display.print("Avg: ");
  display.print(moistValue1);
  display.print(" | want: ");
  display.print(moistWanted);
  display.setCursor(0, 20);
  display.print(millis());
}

int readMoisture(int pin, int prevVal) {
  int idx;
  int reading;
  int readingSum = 0;
  int n_iter = 5;
  for (idx = 0; idx < n_iter; idx ++) {
    readingSum += analogRead(pin);
    delay(10);
  }
  reading = round(readingSum / n_iter);
  if (abs(prevVal - reading) > 5) {
    return(reading);
  }
  return(prevVal);
}

void pumpActivity() {
  // note that high values mean *dry* for capacitive sensors!
  if (moistValue1 - moistWanted > moistThresholdOn && !pumpNeeded) {
    pumpNeeded = true;
    digitalWrite(pumpIndicatorLedPin, HIGH);
    digitalWrite(pumpSwitchPin, HIGH);
  } else if (moistWanted - moistValue1 > moistThresholdOff && pumpNeeded) {
    pumpNeeded = false;
    digitalWrite(pumpIndicatorLedPin, LOW);
    digitalWrite(pumpSwitchPin, LOW);
  }
}


void mainRun() {
  // read the value from the sensor:
  moistValue1 = readMoisture(moistPin1, moistValue1);  // has 50ms delay
  displayMain();
  display.display();

  // supervise pump:
  pumpActivity();

}

void loop() {
  mainRun();
}

Btw Arduino is powered by USB and the external power for the pump are 3 AA batteries with approx 4.5V.

So what is the board doing exactly? Do you see any lights on or is it powered down? Have you added any serial debug statements to see where it's stopping at?

Here is how to properly wire a motor and flyback diode.

What is Vcc1 below? It should be a separate power supply, capable of providing more than the rated stall current of the pump motor.

Capture

1 Like

It stays in its current state. The display freezes and is still on, the built-in LED stays ON, the pump keeps running (so I guess the transistor has a current and D5 is HIGH), and the pump does not stop when the stop condition is met (sensor becomes moist). This all works with the builtin LED as indicator but the pump disconnected.

The wiring of the pump and diode is now this:


Rest is the same.

Thank you for the example. Is the capacitor really necessary? I've seen many circuits without. But I will try. And I guess the circuit would be the same if the shown MOSFET was just a NPN transistor as in my case? Would you recommend a MOSFET over a BJT? I do have some available.

VCC1 is currently 3 AA batteries supplying about 4.5V and I reckon they are capable of the 100mA the pump is supposed to draw.

void loop() {
  //delay(10000);
  if (asleep) {
    // LowPower.deepSleep(1000 * 60 * 30);  // sleep for 30 Minutes
  } else {
    mainRun();
  }
}

Are you sure mainRun(); isn't cycling continuously but without screen updates for whatever reason?

Also, please try code to pulse the pump on and off for one second at a time to make sure it isn't a hardware issue. Debug just the one component.

No, there is no way asleep will turn true, and I've removed it in the minimal example, and the problem persists :frowning: But you are right the code is kind of silly like this. I'm removing it until the switching works.

Motor stall current is typically 5x to 10x the running current. Which are you quoting?

Is the capacitor really necessary?

It is if the electrical noise from the pump is a problem, which is very often the case. See this professional tutorial: Pololu - 9. Dealing with Motor Noise

I've seen many circuits without.

Beginners and amateurs outnumber the pros!

An NPN transistor that can easily handle the stall current of the pump is fine in that circuit, but the 2.2K base resistor might be too high. Try 470 Ohms instead.

The capacitor really makes it work! I wired a 2.2µF that was lying around in parallel to the diode and the motor, as shown by your diagram. Can the capacity be to high in this range? I think I only have 2µF and up.

Thank you for the tutorial, it is very informative and short at the same time.

Will also try varying the resistance of R1.

I think what really lead me astray was the fact that I had these issues even when using a relay and switching an isolated circuit. But I think having a coil switching a relay might bring similar problems?

It says on the Adafruit website that at 3V it draws 100mA, I had measured about 120mA at 4.5V but I will measure again and watch out for peak currents.

Great result!

Not a problem, although it won't be very effective to reduce radio frequency motor noise. People often use a parallel capacitor combination of 100 nF and 1 to 5 uF, for example.

at 3V it draws 100mA

The startup/stall current is probably in the range of 500 mA to 1 Ampere.

Don't forget to also add the second resistor to ground. The base can act like it's own capacitor and hold a charge keeping it on. The values JR showed should be your starting point.

1 Like

Just to wrap this up:
Thanks to jremington for giving the crucial hint to correctly wire the diode and to also add the capacitor. This made it work for the first time. However, there were still problems when the pump was under load, i. e., pumping water through a tube that had small holes, thus creating pressure on the tube. Also, the motor noise was so strong my Bluetooth headphones would not work in the room! As suggested and explained in this post, I also added two ceramic capacitors between GND and either side of the motor. This reduces the motor noise sufficiently.

The wiring is now much more complex (due to other components I added to the project) and the part with the motor looks like this:
schematic

Thanks also to apf1979 for scrutinizing my code, it made me improve it a lot.

2200Ω base resistor seems too high, is the transistor getting hot? I would drop it to 390 ~ 470Ω.