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);
}
}
}