Arduino Nano sometimes resets after Servo motor moves

Hi all. I'm experiencing an issue with my Arduino Nano based rocket flight computer. It happens very rarely but I'd like to avoid even the small possibility that the rocket crashes. The flight computer basically activates an SG90 servo motor which is deploying a parachute, based on a prefixed amount of time. Sometimes when the timer gets to 0, the servo activates but immediatly after moving to the desired position, the Arduino Nano resets, bringing back the servo to the initial position.
Here is the circuit schematic and the code:

#include <U8x8lib.h>
#include <Wire.h>
#include <MPU6050_light.h>
#include <Servo.h>
#include <Adafruit_BME280.h>
#include <SPI.h>
#include <SD.h>
#include <SimpleKalmanFilter.h>

#define SEA_LEVEL_PRESSURE 997.96
#define G_LAUNCH_THRESHOLD 1.3
#define ALTITUDE_TOLERANCE 0.
#define OPERATIONS_RATE 50

File myFile;
Adafruit_BME280 bme;
MPU6050 mpu(Wire);
U8X8_SH1106_128X64_NONAME_HW_I2C u8x8;
Servo servo;
SimpleKalmanFilter altitudeFilter(0.03, 0.03, 0.01); // Parametri da calibrare

int deploy_time = 5;
unsigned long timer = 0, launch_time = 0;
float alt_ref, apogee = 0;
float last_altitude;
byte servo_status = 0, lift_off = 0, break_wire = 0, start_timer = 0;
byte deploy_mode = 0; // 0 for timer only 
                      // 1 for apogee detection + backup timer
byte launch_detection_mode = 0; // 0 for break wire only
                                // 1 for break wire OR acceleration rise detection
byte pinB1 = 2;
byte pinB2 = 3;
byte pinB3 = 4;
byte pinB4 = 5;
byte pinBW = 6;
byte pinCS = 7;

void setup() {
  pinMode(pinB1, INPUT_PULLUP);
  pinMode(pinB2, INPUT_PULLUP);
  pinMode(pinB3, INPUT_PULLUP);
  pinMode(pinB4, INPUT_PULLUP);
  pinMode(pinBW, INPUT_PULLUP);
  pinMode(pinCS, OUTPUT);
  Wire.begin();
  bme.begin(0x76);
  mpu.begin();
  servo.attach(10);
  servo.write(10);
  delay(1000);
  mpu.calcOffsets(true);
  alt_ref = bme.readAltitude(SEA_LEVEL_PRESSURE);
  last_altitude = alt_ref;

  u8x8.begin();
  u8x8.setFont(u8x8_font_chroma48medium8_r); // Set a readable font for text

  if (!SD.begin(pinCS)) {
    while(1);
  }
  
  myFile = SD.open("data.csv", FILE_WRITE);
  if (myFile) {
    myFile.println("\nT(ms)\tAlt(m)\tVel\tServo\tAcc(g)");
    myFile.close();
  }
  
  delay(500);
}

void loop() {
  if (digitalRead(pinBW) == HIGH && break_wire == 0) {
      break_wire = 1;
    }
  
  static bool B1WasPressed = false;
  static bool B2WasPressed = false;
  static bool B3WasPressed = false;
  static bool B4WasPressed = false;
  bool B1Pressed = digitalRead(pinB1) == LOW;
  bool B2Pressed = digitalRead(pinB2) == LOW;
  bool B3Pressed = digitalRead(pinB3) == LOW;
  bool B4Pressed = digitalRead(pinB4) == LOW;


  if (lift_off == 0){
    if (B2Pressed && !B2WasPressed) {
      deploy_time++; // Incrementa la variabile
      if (deploy_time > 9) {
        deploy_time = 0; // Reimposta a 0 se supera 10
      }
    }
    if (B1Pressed && !B1WasPressed) {
      deploy_time--; // Riduce la variabile
      if (deploy_time < 0) {
        deploy_time = 9; // Reimposta a 9 se va sotto 1
      }
    }
    if (B3Pressed && !B3WasPressed) {
      deploy_mode = !deploy_mode;
    }
    if (B4Pressed && !B4WasPressed) {
      launch_detection_mode = !launch_detection_mode; 
    }
  }
  

  mpu.update();
  unsigned long dt = millis() - timer;

  if (dt > OPERATIONS_RATE){
    float ax = mpu.getAccX();
    float alt = altitudeFilter.updateEstimate(bme.readAltitude(SEA_LEVEL_PRESSURE));

    unsigned long dt_2;

    launch_detection(ax);

    if (start_timer == 1){
      dt_2 = millis() - launch_time;
    } 

    parachute_deploy(alt, lift_off, dt_2, dt);

    if (lift_off == 1) {
      myFile = SD.open("data.csv", FILE_WRITE);
      if (myFile) {
        myFile.print(dt_2);
        myFile.print("\t");
        myFile.print(alt - alt_ref, 2);
        myFile.print("\t");
        myFile.print(((alt - last_altitude)) / (dt / 1000.0), 4);
        myFile.print("\t");
        myFile.print(servo_status);
        myFile.print("\t");
        myFile.println(ax);
        myFile.close();
      }
    }

    last_altitude = alt;
    draw(u8x8, lift_off, servo_status, apogee);
    
    timer = millis();

  }

B1WasPressed = B1Pressed;
B2WasPressed = B2Pressed;
B3WasPressed = B3Pressed;
B4WasPressed = B4Pressed;

}

void draw(U8X8_SH1106_128X64_NONAME_HW_I2C &oled, byte lift, byte servo, float apo) {
  static byte last_lift = 255; // Initialize with an invalid value
  static byte last_servo = 255;
  static float last_apo = -10000.0;
  oled.drawString(0, 7, "T:");
  oled.setCursor(2, 7);
  oled.print(deploy_time);
  oled.setCursor(12, 1);
  oled.print(deploy_mode);
  oled.setCursor(14, 1);
  oled.print(launch_detection_mode);
  // Clear and update display only if there's a change in status
  if (lift != last_lift || servo != last_servo || (servo == 1 && abs(apo - last_apo) > 0.1)) {
    oled.clearDisplay();

    if (lift == 0) {
      oled.drawString(0, 1, "Ready");
    } else {
      oled.drawString(0, 1, "Launched");
    }

    if (servo == 0) {
      oled.drawString(0, 3, "P: Not Deployed");
    } else {
      oled.drawString(0, 3, "P: Deployed");
    }

    if (servo == 1) {
      oled.drawString(0, 5, "Apogee:");
      oled.setCursor(7, 5);
      oled.print(apo);
      oled.drawString(7, 7, "Td:");
      oled.setCursor(10, 7);
      oled.print((millis() - launch_time)/1000, 1);
    }

    // Update the tracked state
    last_lift = lift;
    last_servo = servo;
    if (servo == 1) {
      last_apo = apo;
    }
  }
}

void launch_detection(float acc_x){

  if (launch_detection_mode == 0){ // only break wire
    if (break_wire == 1 && lift_off == 0) {
      lift_off = 1; // Launch detected
      start_timer = 1;
      launch_time = millis();
    }
  } else if (launch_detection_mode == 1 || launch_detection_mode == true) { // break wire + acceleration detection
    if ((abs(acc_x) >= G_LAUNCH_THRESHOLD || break_wire == 1) && lift_off == 0){
      lift_off = 1; // Launch detected
      start_timer = 1;
      launch_time = millis();
    }
  }
}

void parachute_deploy(float altitude, byte lift, unsigned long dt2, unsigned long dt){

  float velocity = ((altitude - last_altitude)) / (dt / 1000.0);

  if (deploy_mode == 1) { // velocity check + backup timer

    if (((velocity < - 1 && altitude > alt_ref + ALTITUDE_TOLERANCE) || (dt2 > deploy_time*1000)) && lift == 1 && servo_status == 0 ) {
      apogee = altitude - alt_ref;
      servo.write(180); // deploy parachute
      servo_status = 1;
    }

  } else if (deploy_mode == 0) { // only timer

    if (dt2 > deploy_time*1000 && lift == 1 && servo_status == 0) {
      apogee = altitude - alt_ref;
      servo.write(180); // deploy parachute
      servo_status = 1;
    }
  }
}

The power supply is a 2S 7.4v 20C LiPo battery, which is immediately regulated down to 5V by the MP1584 voltage regulator. I'll be glad if you could help me tackling this issue, thank you.

Probably brown out caused by Arduino and servo being powered at the 5v in.
Try Arduino in same but servo from different supply.
Common ground naturally.

This will not work reliably for you until you realize you mustn't use the 5V output to power the servo. When the servo meets any significant resistance during it's motion, the full "stall" current may be drawn. Servo specs vary, but that could be anywhere from 300 mA to 1.4 A depending on the servo; any current of that sort will reset the Arduino due to excessive current drain 'drawing down' the 5V power rail.

Since it's a model rocket, it should be lightweight. Also, in order to do that I should redesign a new pcb, is there anything different I can try? I read somewhere that adding a capacitor across the 5V and GND of the Arduino could help, is it worth trying?

Is there anything I can do without adding a different power supply? The rocket should stay lightweight enough.

The obvious thing to do is stop the servo from trying to go "too far". Tune the end of travel value (e.g. don't just use 180 degrees, if 150 degrees goes far enough). That may allow you to avoid the stall current issue completely.

Otherwise, you could try the following(desperate, but might work):
put a resistor between the 5V pin and the servo; I'd try something like 10 ohms. Add a capacitor between the servo + and servo -, perhaps 4-500 uF. This should give your servo some "local" oomph, and may isolate your servo enough.

Stall current for an SG90 is 650ma ±80mA. You're overloading the 5V regulator (which has no heatsink) on the Nano and it's going into thermal shutdown.

Edit: ignore this: I looked at the schematic again and went D'oh!

I wish the range were that tight. Check the broad range of SG90's available on Amazon and/or Ali. The numbers I gave are real values for various suppliers. Can't point to them, didn't save the links when I was looking at this a year ago. My experience has been stall currents in the 400-700 range, for those I have purchased.

The MP1584 should be capable of providing 3A output. According to the schematic, it feeds into the Nano 5V and the servo.

However, the clear symptom is that the servo is causing the Arduino to reset, which is almost certainly a brownout.

I suspect that either the schematic is not correct or the actual circuit does not match the schematic.

1 Like

True enough. I suspect that 650 +/- 80 rating was/is true for one manufacturer's servo at one time.

And in any case, a second look at the schematic shows that the Nano isn't being used as a 5V supply as I first thought, so it's time for me to go sit in the corner with my dunce cap on. :slight_smile:

1 Like

Agreed. Missed that detail completely, saw the switch and thought it isolated the circuit for in-flight. Missed that there's no Vin supply. Sigh.

In this case, @nico_fiaba, I think @jremington is correct. Either your circuit is not as depicted, or you have a high-impedance(e.g tiny wire, such as a Dupont) between the power source and the servo/Arduino, so when the servo suddenly draws current, the Arduino suffers.
A photo might help.

Shove over, @van_der_decken, there should be room for two on the "bench of shame".

1 Like

Blue sky thought: where was the MP1584 sourced from? It there any possibility it's not legit and is not capable of supplying anything near 3A?

Well, the pcb was designed directly from the schematics. Here is the actual pcb (front + back):



GND and 5V routes are 0.5mm while all the others are 0.25mm

Oh dear. 20 thou GND and 5V??? Totally, utterly, insufficient.

Cut both those traces, isolate the pins they connect to, and use a 20 ga wire to connect them; it's a hack, and a darn ugly one, but you really need to improve that.

Or, run power direct to both + and - of the servo from the power supply output. I'd still beef up the PCB traces, but at least with the servo power going direct, the Arduino might survive.

What copper weight does the PCB use? Is it 1oz/sq.ft or it is less?

I think I'm going to try this one. Should I still cut the pcb 5v and gnd traces that go to the servo? How can I do that? Maybe drill a small hole along the trace?

A scalpel comes in handy for this

But just paralleling* the thin traces with a wire would help tremendously. Focus on traces that are carrying current, like the traces handling current to the servo. The Arduino draws a tenth of the current the servo wants (worst case), so the servo feed is most important.

First parallel the servo wires, and see if things improve.

'* The reason to break your traces is to avoid what's called a ground loop, and it's very much a secondary issue; may never become evident.

Maybe make a new PCB at this point? Not only for the traces, I'd get rid of the Nano and put a stand alone 328 there.