3

I am building a remote controlled boat. My remote project freezes after an arbitrary amount of time. The serial stops output, the OLED doesn't update anymore and instructions are not send / received anymore.

For the life of me I cannot figure out why my project freezes. Using an Arduino Nano.

I have tried the following:

  • Keep memory small
  • Disable Serial as not to overflood
  • Not use any risky variables; like String
  • Determine if a specific circumstance causes the hang (X amount of time, certain instructions, losing radio connection, etc)
  • Replace the Arduino
  • Replace the power supply
  • Replace the power supply cable
  • Put the radio into debug mode, it becomes slow and nothing interesting shows on serial

Please provide help to allow me to continue my two-year long project. My gratitude for any pointers or help is unending.

The Arduino is connected to a NRF24L01 with adapter board, an OLED and two potentiometers.

This is my code:

#include <SPI.h>
#include "printf.h"
#include "RF24.h"
#include "SSD1306Ascii.h"
#include "SSD1306AsciiAvrI2c.h"
#include <DifferentialSteering.h>

// Debugging mode:
int debugging_serial = 0;
int debugging_delay = 0;
int debugging_verbose = 0;

// Timers init;
unsigned long start_timer = 0;
unsigned long end_timer = 0;

// Oled state for clearing
int oledstate = 0;

// Create nRF24L01 transceiver using pins 9 and 10.
RF24 radio(9, 10);

// OLED init
#define I2C_ADDRESS 0x3C // 0X3C+SA0 - 0x3C or 0x3D
#define RST_PIN -1 // Define proper RST_PIN if required.
SSD1306AsciiAvrI2c oled;

// Set potentio meter pins
int pot_one_pin = A0;
int pot_two_pin = A1;

// Set Diff Steering
DifferentialSteering DiffSteer;

// Register two radio addresses to use for communication.
uint8_t address[][6] = { "1Node", "2Node" };


// Define sending and receiving payload structs. Note: these should be inversed on remote vs boat code
// DEFINE WHAT WE WILL BE SENDING
struct SendingPayloadObject 
{
  // We are sending two numbers
  uint8_t Motor1Speed; 
  uint8_t Motor2Speed;
};
// Also create the payload object to prepare and load with defaults in setup
SendingPayloadObject payload;

// DEFINE WHAT WE WILL BE RECEIVING
struct ReceivingPayloadObject 
{
  // We are sending a message and a number
  char message[7];  // only using 6 characters for TX & ACK payloads
  uint8_t counter; // struct counter variable as example
};

void setup() 
{
  if(debugging_serial) 
  {
    Serial.begin(115200);
    Serial.println("Remote Code Started!");
  }

  // Oled setup
  // Call oled.setI2cClock(frequency) to change from the default frequency.
  oled.begin(&Adafruit128x64, I2C_ADDRESS, RST_PIN);
  //oled.setFont(System5x7);
  oled.clear();
  oled.println("Boat Remote Started!");
  oled.clear();
  
  // Check if radio works
  if (!radio.begin()) 
  {
    if(debugging_serial) 
    {
      Serial.println(F("Radio hardware is not responding!"));
    }
      
    while (1) {}  // hold
  }

  // Set the PA Level low to try preventing power supply related problems
  // because these examples are likely run with nodes in close proximity to
  // each other. (RF24_PA_MAX is default)
  radio.setPALevel(RF24_PA_LOW);

  // To use ACK payloads, we need to enable dynamic payload lengths (for all nodes). ACK payloads are dynamically sized
  radio.enableDynamicPayloads();

  // Acknowledgement packets have no payloads by default. We need to enable this feature for all nodes (TX & RX) to use ACK payloads.
  radio.enableAckPayload();

  // Set the TX address of the RX node into the TX pipe and the opposite to the RX pipe. The remote radio address is 1!
  radio.openWritingPipe(address[1]);
  radio.openReadingPipe(1, address[0]);

  // Sender role code
  // Set payload defaults and put radio in send (TX) mode
  payload.Motor1Speed = 0;  // set the payload payloadMotor1Speed
  payload.Motor2Speed = 0;  // set the payload payloadMotor1Speed
  radio.stopListening();                 // put radio in TX mode
  
  if(debugging_verbose)
  {
    printf_begin();             // needed only once for printing details
    radio.printDetails();       // (smaller) function that prints raw register values
    radio.printPrettyDetails(); // (larger) function that prints human readable data
  }

  // DiffSteer init
  DiffSteer.begin(0);
}

void loop() 
{
  //// REMOTE CONTROL STUFF:

  // NEW LOGIC FOR MANEURVERING
  int motorSpeed1 = 0;
  int motorSpeed2 = 0;

  // Steering using potentiometers
  int steering_pot_value = map(analogRead(pot_two_pin), 0, 1023, -127, 127);
  int power_pot_value = map(analogRead(pot_one_pin), 0, 1023, 0, 127);

  // Go Straight when steering almost straight
  if(steering_pot_value <= 15 && steering_pot_value >= -15)
  {
    steering_pot_value = 0;
  }

  // Compute differential steering
  DiffSteer.computeMotors(steering_pot_value, power_pot_value);
  
  // map motor outputs to needed range
  motorSpeed1 = map(DiffSteer.computedLeftMotor(), 0, 127, 0, 255);
  motorSpeed2 = map(DiffSteer.computedRightMotor(), 0, 127, 0, 255);

  // Corrective measures for smoother steering
  // Fix division by 0, full power when pot completley off
  if(power_pot_value <= 1) { motorSpeed1 = 0; motorSpeed2 = 0; };

  // OLED motor control code (for testing)
  //  Speeds directley from pot meters
  //  motorSpeed1 = map(analogRead(pot_one_pin), 0, 1023, 0, 255);
  //  motorSpeed2 = map(analogRead(pot_two_pin), 0, 1023, 0, 255);
  //
  
  // Set motor speeds in payload
  payload.Motor1Speed = motorSpeed1;
  payload.Motor2Speed = motorSpeed2;

  //// RADIO STUFF:
  
  // Let's time the tranmission for maagd / nerd purposes
  if(debugging_serial) 
  {
     start_timer = micros();                  // start the timer
  }

  // Send over radio TX;
  bool report = radio.write(&payload, sizeof(payload));  // transmit & save the report

  if(debugging_serial) 
  {
    end_timer = micros();                    // end the timer
  }

  if (report) 
  {
    if(debugging_serial) 
    {
      Serial.print(F("Time to transmit = "));
      Serial.print(end_timer - start_timer);  // print the timer result
      Serial.print(F(" us. Sent (Motor 1 Speed: "));
      Serial.print(payload.Motor1Speed);
      Serial.print(F(" , Motor 2 Speed: "));
      Serial.print(payload.Motor2Speed);
      Serial.print(F(")"));
    }

    // Define the pipe
    uint8_t pipe;
    
    if (radio.available(&pipe)) 
    {
      if (oledstate != 1) { oled.clear(); }; oledstate = 1;
      oled.set1X();
      oled.setFont(System5x7);
      oled.setCursor(0 , 0);
      oled.print("Transmission OK \n");
      oled.setCursor(0 , 30);
      oled.print("Motors L:");
      // Add leading zeroes to get true values (otherwise old number remains)
      if(payload.Motor1Speed < 100 && payload.Motor1Speed >= 10) { oled.print("0"); };
      if(payload.Motor1Speed < 10) { oled.print("00"); };
      oled.print(payload.Motor1Speed);
      oled.setCursor(80 , 30);
      oled.print("R:");
      if(payload.Motor2Speed < 100 && payload.Motor2Speed >= 10) { oled.print("0"); };
      if(payload.Motor2Speed < 10) { oled.print("00"); };
      oled.print(payload.Motor2Speed);
      
      // is there an ACK payload? grab the pipe number that received it
      ReceivingPayloadObject received;
      radio.read(&received, sizeof(received));  // get incoming ACK payload

      if(debugging_serial) 
      {
        Serial.print(F(" Recieved "));
        Serial.print(radio.getDynamicPayloadSize());  // print incoming payload size
        Serial.print(F(" bytes on pipe "));
        Serial.print(pipe);  // print pipe number that received the ACK
        Serial.print(F(": "));
        Serial.print(received.message);    // print incoming message
        Serial.println(received.counter);  // print incoming counter
      }
    } 
    else 
    {
      if (oledstate != 2) { oled.clear(); }; oledstate = 2;
      oled.set1X();
      oled.setCursor(0, 0);
      oled.setFont(System5x7);
      oled.print("Transmission: \n \n");
      oled.setCursor(0, 80);
      oled.set2X();
      oled.print("No Reply (empty ACK)");

      if(debugging_serial) 
      {
        Serial.println(F(" Recieved: an empty ACK packet"));
      }
    }
  } 
  else 
  {
    if (oledstate != 3) { oled.clear(); }; oledstate = 3;
    oled.set1X();
    oled.setCursor(0, 0);
    oled.setFont(System5x7);
    oled.print("Transmission: \n \n");
    oled.setCursor(0, 80);
    oled.set2X();
    oled.print("Failed");

    if(debugging_serial) 
    {    
      Serial.println(F("Transmission failed or timed out"));
    }
  }

  // Add a delay while programming for reading purposes
  if(debugging_verbose)
  {
    delay(10000); 
  }
  else
  {
    if(debugging_delay)
    {
      delay(1000); 
    }
  }
}
8
  • Maybe add to your post the RAM/storage usage statistics which the compiler provides at the end of the output. Also say what board it is.
    – 6v6gt
    Commented Mar 8, 2023 at 15:56
  • where does the program hang?
    – jsotola
    Commented Mar 8, 2023 at 17:14
  • Approaching the problem from another angle, why not use a watchdog to reboot the Arduino when it hangs.
    – Bra1n
    Commented Mar 8, 2023 at 18:01
  • Thanks for your replies! I dont have the usage number at hand, but it was very low. Maybe 30%. Using a Nano. The program hangs somewhere in if radio.availible as far as I can determine. A watchdog is not a great solution as it would render the boat inoperable for some seconds. Looking forward to more thoughts and advice! Commented Mar 8, 2023 at 21:23
  • You appear to have used a relatively complex nrf24l01 code base for your project which originally supported role swapping between the "transmitter" and "receiver" parts. If you have a single transmitter and a single receiver then a simpler example, also handling ack packets, can be found here: forum.arduino.cc/t/simple-nrf24l01-2-4ghz-transceiver-demo/… . If you don't use your debugging options, the transmitter is likely to flood the receiver because there is otherwise no pause in the loop().
    – 6v6gt
    Commented Mar 9, 2023 at 3:21

1 Answer 1

1

A basic rule of debugging complex problems is to simplify the configuration until you get to a working baseline, and then build back up from there, one chunk at a time.

I would start with the simplest possible setup with a single RF transceiver at each end. No other peripherals, no other code; something that simply sends "hello" back and forth at wide spaced intervals - like once per second, to make sure there's no way the RF channel will be overloaded.

Once you get to a baseline where your test configuration will run indefinitely, then start adding code and/or peripherals, one at time, with a long-duration test at each step. Eventually you will reach a point where the system becomes intermittent; when this happens, back up one step to confirm proper operation, and then repeat this forward/back step until you are convinced that this is the step that produces the errant behavior.

Then divide the last step (the one that produces the bad behavior) into smaller steps, and find out which one of those steps produces the bad behavior, and eventually you will get down to one line of code, or one particular peripheral that causes the problem, and in all likelihood it will be something where you will say "OH! I should have thought of that long ago!".

Just as a last thought - you mentioned the 'if radio.availible()' function as a possible culprit. You could start the process by, for instance, modifying the library code (you can do this - it's just another bit of Arduino code) so that it simply returns 'true' (or false - doesn't matter) every time it is called. If your system operates OK with this setup, but doesn't operate OK with the original library code, then that may well be the source of the problem.

Looking at the RF24.cpp, I see that available() calls get_status(), so maybe it is get_status() that is hanging up.

Just some thoughts,

Frank

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.