Strange behavior using subroutine i.e. recursion issues


#1

Hello Community from a newbie,

Here’s the short of it: I am trying to monitor for incoming SMS in other than loop() and running into trouble.

I am building a remote starter for my car. I am not simply integrating an existing key fob but building from scratch with relays controlling ignition and starter lines. I have my prototype working perfectly on an Arduino Uno (start sequence initiated by button press) and now am trying to migrate the project to my new Dash.

Below you will find two code examples testing very basic Dash functionality before I start to roll all my more complex functions in. “START” command activates relay and sends a message and “KILL” command deactivates and sends a message.

Version 1 works perfectly as expected. After the START command is received, it runs the start() routine once and then goes back into loop() to wait for KILL command. The problem is in version 2 when I add the vehicle_running() subroutine called from the start() subroutine. Currently vehicle_running only contains HologramCloud.pollEvents() but the final version will also contain a shutdown timer and theft protection if brake applied so I need to to stay in this routine and not go back to loop() to monitor for a KILL command.

Here’s what happens in version 2: after successfully running the start() routine and then calling the vehicle_running routine, I get a rapid fire string of “Ignition On” responses (actually every 5 sec as delay(5000) used) until I unplug the Dash. Obviously it is somehow looping through the start() routine as it sees the START command being sent as new every time rather than just the one time as in version 1.

I’m sure I’m making a rookie mistake and missing something here. My requirements to fix this is I need the sketch to stay in the vehicle_running loop but still monitor for a KILL command. I really look forward to your ideas. Best regards from freezing Minnesota.

Version 1 Code:

const int ign = D28;


void cloud_sms(const String &sender, const rtc_datetime_t &timestamp, const String &message) {


  if (message == "START") {
    start();
  }

  if (message == "KILL") {
    vehicle_off();
  }

}

void setup() {


  pinMode(ign, OUTPUT);
  HologramCloud.attachHandlerSMS(cloud_sms);

}

void loop() {

  HologramCloud.pollEvents();

}

void start() {

  digitalWrite(ign, HIGH);
  HologramCloud.sendMessage("Ignition ON","remote");
  
}

void vehicle_off() {

  digitalWrite(ign, LOW);
  HologramCloud.sendMessage("Ignition OFF","remote");

}

Version 2 Code:

const int ign = D28;


void cloud_sms(const String &sender, const rtc_datetime_t &timestamp, const String &message) {


  if (message == "START") {
    start();
  }

  if (message == "KILL") {
    vehicle_off();
  }

}

void setup() {


  pinMode(ign, OUTPUT);
  HologramCloud.attachHandlerSMS(cloud_sms);

}

void loop() {

  HologramCloud.pollEvents();

}

void start() {

  digitalWrite(ign, HIGH);
  HologramCloud.sendMessage("Ignition ON","remote");
  delay(5000);
  vehicle_running();
  
}

void vehicle_running(){

HologramCloud.pollEvents();
vehicle_running();

}

void vehicle_off() {

  digitalWrite(ign, LOW);
  HologramCloud.sendMessage("Ignition OFF","remote");

}

#2

In vehicle_running() are you allowed (or want ) to call vehicle_running() again? Is the code re-entrant?
https://en.wikipedia.org/wiki/Reentrancy_(computing) Or thread-safe?


#3

You are getting a stack overflow from calling vehicle_running() from within vehicle_running().

vehicle_running()
  vehicle_running()
    vehicle_running()
      ...

None of the calls to vehicle_running() ever finish. Each function call adds a small amount of memory to the stack. When the function completes, that memory is taken back off the stack again. That’s how you can call a function from within a function from within a function, and so on, and the program remembers where to go back to when the function completes. But in this case the function never completes, so the stack gets full and you run out of memory and the program crashes.

Instead of calling vehicle_running in start, you could set a variable instead.

const int ign = D28;
bool vehicleRunning = false;

void cloud_sms(const String &sender, const rtc_datetime_t &timestamp, const String &message) {
  if (message == "START") {
    start();
  }

  if (message == "KILL") {
    vehicle_off();
  }
}

void setup() {
  pinMode(ign, OUTPUT);
  HologramCloud.attachHandlerSMS(cloud_sms);
}

void loop() {
  if(vehicleRunning) {
    //do something?
  } else {
    //do something else?
  }
}

void start() {
  digitalWrite(ign, HIGH);
  HologramCloud.sendMessage("Ignition ON","remote");
  delay(5000);
  vehicleRunning = true; 
}

void vehicle_off() {
  digitalWrite(ign, LOW);
  HologramCloud.sendMessage("Ignition OFF","remote");
  vehicleRunning = false;
}

#4

Agreed, you are overflowing due to recursion. I would think a counter for recursion would work. Better yet, look at Arduino Timer library to setup a scheduled event, that way you set a function to fire at a specific time.

Timer t;
int tickEvent;
tickEvent = t.every(2000, idleProcess); // Set a function to fire every 2 seconds
int relayAction = t.after(500,relayOff); // waits 500 ms then fires "relayOff"
t.stop(relayAction); will terminate the event before it happens.

Great for async events and non-state driven programs. You also get the basics of a thread scheduler. Set the functions you want to run and how often.


#5

Being an inexperienced programmer, I had to look recursion up in the dictionary. Here is what it said:

recursion (noun).
see recursion.

All kidding aside, I really appreciate appreciate the help from you guys, especially since this is a Dash specific forum and not a general help the new guy with stupid questions forum.

So I changed my code to set a variable, named running, and if true in loop() it takes a cruise through vehicle_running(). This allows the device to check for 30 minute timeout, vehicle stalled or RPM high (theft protection - a pulse width equating to about 2000rpm) as well as continue to monitor for a KILL command via SMS. This works great Erik - thanks for the suggestion.

I wired the entire project to the car yesterday and it works well so far. However, since I have you experts looking and discussing recursion, please be so kind to take a look at my starter_engaged() function below. I am repeating using recursion there while the starter is cranking the car. There are two exit methods for that function for starter cutout - either 4 seconds max time or reaching idle(ish) rpm (as noted in pulse width range). Otherwise, the function calls itself to keep starter cranking. Even though this works great, my question is, regarding recursion, is this a bad idea and could it lead to another stack overflow and program crash? I have tested that loop likely 80 times between Uno prototype and Dash version with no trouble but want to make sure I’m not setting up for a fried starter/ car fire because I made another mistake.

The entire code is below. I really appreciate you guys taking a look. I’ll include a little more information about the project below the sketch if you are interested.

float pressLength = 0;
const int led = D30;
const int ign = D28;
const int immo = D26;
const int acc = D07;
const int starter = D17;
const int extra = D15;
const int tach = R04;
int button = D19;
bool armed = false;
bool running = false;
unsigned long start_time;

void cloud_sms(const String &sender, const rtc_datetime_t &timestamp, const String &message) {

if (message == “START”) {
start();
}

else if (message == “KILL”) {
vehicle_off();
}

}

void setup() {

pinMode(led, OUTPUT);
pinMode(ign, OUTPUT);
pinMode(immo, OUTPUT);
pinMode(acc, OUTPUT);
pinMode(starter, OUTPUT);
pinMode(extra, OUTPUT);
pinMode(button, INPUT);
pinMode(tach, INPUT);

digitalWrite(starter, LOW);
digitalWrite(ign, HIGH);
digitalWrite(acc, HIGH);
digitalWrite(immo, HIGH);
digitalWrite(extra, HIGH);
Serial.begin(9600);

HologramCloud.attachHandlerSMS(cloud_sms);

}

void loop() {

int status = HologramCloud.getConnectionStatus();

while (digitalRead(D19) == LOW) {
delay(100);
pressLength = pressLength + 100;

if ((pressLength >= 5000) && (status == 1)) {
  pressLength = 0;
  armed = true;
  digitalWrite(led, HIGH);
  Dash.snooze(3000);
  digitalWrite(led, LOW);
}

}

HologramCloud.pollEvents();

if (running == true) {
vehicle_running();
}

}

void start() {

unsigned long rpm = pulseIn(tach, HIGH, 1000000);
if ((rpm == 0) && (armed == true)) {
//Serial.println(“Initiating Start”);
HologramCloud.sendMessage(“Initiating Start”, “remote”);
digitalWrite(immo, LOW);
delay (1000);
digitalWrite(ign, LOW);
delay(3000);
digitalWrite(starter, HIGH);
start_time = millis();
starter_engaged();
}

else if ((rpm == 0) && (armed == false)) {
HologramCloud.sendMessage(“Not Armed”, “remote”);
//Serial.println(“Not Armed”);
delay (1000);
vehicle_off();
}

else {
HologramCloud.sendMessage(“Already Running!”, “remote”);
//Serial.println(“Already Running”);
delay (1000);
vehicle_off();
}

}

void starter_engaged() {
unsigned long rpm = pulseIn(tach, HIGH, 1000000);
if (rpm > 9000 && rpm < 15000) {
digitalWrite(starter, LOW);
Dash.snooze(5000);
digitalWrite(acc, LOW);
Dash.snooze(1000);
digitalWrite(immo, HIGH);
start_verify();
}

else if ((start_time + 4000) < millis()) {
digitalWrite(starter, LOW);
Dash.snooze(2000);
HologramCloud.sendMessage(“Starter Timeout”, “remote”);
//Serial.println(“Starter Timeout”);
Dash.snooze(1000);
vehicle_off();
}

else {
starter_engaged();
}
}

void start_verify() {
unsigned long rpm = pulseIn(tach, HIGH, 1000000);
if (rpm > 4000 && rpm < 15000) {
HologramCloud.sendMessage(“Start Successful”, “remote”);
//Serial.println(“Start Succesful”);
running = true;
vehicle_running();
}
else {
HologramCloud.sendMessage(“Start FAILED”, “remote”);
//Serial.println(“Start FAILED”);
vehicle_off();
}
}

void vehicle_running() {
unsigned long rpm = pulseIn(tach, HIGH, 1000000);
if (rpm > 3000 && rpm < 4600) {
HologramCloud.sendMessage(“RPM HIGH”, “remote”);
//Serial.println(“RPM High”);
delay (1000);
vehicle_off();
}

else if (rpm == 0) {
HologramCloud.sendMessage(“Vehicle Stalled”, “remote”);
//Serial.println(“Vehicle Stalled”);
vehicle_off();
}

else if (start_time + 1800000 < millis()) {
HologramCloud.sendMessage(“30 Minute Timeout”, “remote”);
//Serial.println(“30 Minute Timeout”);
delay (1000);
vehicle_off();
}
else {
}

}

void vehicle_off() {

digitalWrite(starter, LOW);
digitalWrite(ign, HIGH);
digitalWrite(acc, HIGH);
digitalWrite(immo, HIGH);
Dash.snooze(2000);
HologramCloud.sendMessage(“Vehicle OFF”, “remote”);
//Serial.println(“Vehicle OFF”);
running = false;

}

A little more information:

Why buy a professionally designed product when you can roll your own? This is the most ambitious Arduino project I have yet developed. Here are some basics of the project.

Since the car is a manual transmission, the while loop exists to arm the system. I made the required button press 5 seconds to force myself to make sure the car is in neutral and parking brake set. My car has no neutral safety switch so this was a “human factors” solution to make sure I don’t try to start it when I left it in 1st gear. The LED then blinks, also confirming the DASH is connected and registered on the network.

Sensing the engine rpm was the most critical and difficult (for me) part of this project. After much trouble finding the correct wire (among about 70 wires from ECU to instrument cluster), I had to figure out the signal. Trying to decode it with my DMM was maddening. Not having an oscilloscope, I ordered Banggood’s $20 DSO Shell Oscilloscope kit, assembled it, learned to use it and found the signal was a perfect 13 volt square wave. Then I considered all manner of level shifters, mosfets and resistors to feed the signal to the Arduino. In the end, a simple voltage divider and use of pulseIn to find needed trigger points proved very effective.

The tach signal is used for several things:

  1. Make sure you don’t attempt a start when already running.
  2. Starter cutout during actual start (most important)!
  3. Theft protection for unattended running car. If revved above 2000, she dies.
  4. stall detection for running car.

Bypassing the immobilizer was another challenge. My first idea was pretty cheesy - I removed the RFID chip from a spare key and velcroed it to the sensing ring. Then I found that a simple piece of thin gauge stranded wire wrapped 6 times around the sensing ring and then 6 times around the RFID capsule could allow the capsule to be moved several feet away. Break that loop with a relay and wham - you have an on demand immobilizer bypass module !

A word on relays - I used a 4 relay board (10 amp each) from Banggood to control ignition, accessory and immo bypass. Since the starter solenoid can peak at 18 amps I used Sparkfun’s Beefcake Relay for that line. The Chinese module energizes with LOW signal whereas the Sparkfun relay with a HIGH signal. Not knowing this would lead to confusion going through my code.

Power supply - I opted for a tiny module (about the size of a dime) using the MP2315 chip as it’s supposed to be the most efficient available and handles the wide range of automotive voltage spikes. Again, just a couple of bucks on Banggood. It came with a female usb port installed but I removed that and soldered wires since it needs to power the Dash and two relay modules. Measured current draw with Dash at idle waiting for an SMS is 32ma. Not bad considering normal parasitic draw for the car is 45ma. I figure I can safely power the device for several days from my car battery and still be able to start the car.

A much easier way to do this project would be to repeat what others in this forum have done - install a commercial remote starter and then use the Dash to interface with an extra key fob for cellular capability. However, I wanted to learn and see if I could build it from the ground up. I learned a tremendous amount in the process. It’s really amazing to consider all the requirements in something so seemingly simple as starting a car! Now I just need to get the jungle of wires on my breadboard onto a nice perma-proto and into a project box.


#6

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.