Remote car starters offer convenience and comfort, especially in harsh weather conditions. At the heart of many DIY remote car starter systems lies the Arduino microcontroller, a versatile tool for automating vehicle functions. This article delves into the Arduino code that powers a remote car start system, explaining how each part works to bring your car to life with the push of a button. Understanding this code provides valuable insight into the workings of remote starters and the potential of Arduino in automotive applications.
Understanding the Basics of Remote Car Starters
Before diving into the code, it’s essential to grasp the fundamental operation of a remote car starter. These systems typically bypass the traditional key ignition, allowing you to start your vehicle from a distance. They achieve this by mimicking the actions you would manually perform: engaging the ignition, accessory power, and finally, the starter motor. Safety is paramount, so remote starters often incorporate checks to ensure the vehicle is in neutral or park and that the engine is not already running.
Arduino Code Breakdown: Remote Car Start System
The following Arduino code provides a blueprint for a DIY remote car starter. It’s designed to be controlled via Bluetooth or a serial command, offering flexibility in how you initiate the starting sequence. Let’s break down the code section by section to understand its logic and functionality.
Inputs and Outputs
The code begins by defining the input and output pins on the Arduino board. These pins act as the interface between the Arduino and the car’s electrical system and control mechanisms.
Inputs:
BT_on (Pin 2)
: Receives the “start” signal from a Bluetooth module or an external trigger. This is the primary input for initiating the remote start sequence.RPM_sens (Pin 3)
: Monitors the engine’s RPM (Revolutions Per Minute) sensor. This input is crucial for detecting if the engine is already running.neutral_sens (Pin 4)
: Checks the vehicle’s neutral position sensor. A safety feature to ensure the car is not in gear before starting.speed_sens (Pin 5)
: Monitors vehicle speed. Used for safety shutdown if the vehicle begins to move unintentionally while remotely started.BT_off (Pin 7)
: Receives the “off” signal from a Bluetooth module to remotely shut down the engine.
Outputs:
ign_ctrl (Pin 8)
: Controls the ignition relay, turning the car’s ignition system ON and OFF.start_ctrl (Pin 9)
: Controls the starter relay, engaging the starter motor to crank the engine.clutch_sfty (Pin 10)
: Controls a clutch safety switch relay (relevant for manual transmission vehicles), mimicking clutch depression during starting.acc_ctrl (Pin 11)
: Controls the accessory relay, managing power to car accessories like the radio and lights.
//EQUATES
//Inputs
int BT_on = 2; //Bluetooth "ON" signal inputed to pin 2
int RPM_sens = 3; //Vehicle RPM sensor inputted to pin 3
int neutral_sens = 4; //Vehicle Neutral Position Sensor inputted to pin 4 (12 volts +/- 0.5 volts)
int speed_sens = 5; //Vehicle Speed Sesor inputted to pin 5
int BT_off = 7; //Bluetooth "OFF" signal inputed on pin 7
//Outputs
int ign_ctrl = 8; //Ignition Control Relay controlled by pin 8
int start_ctrl = 9; //Start Control Relay controlled by pin 9
int clutch_sfty = 10; //Clutch Safety Swithc Relay controlled by pin 10
int acc_ctrl = 11; //Accessory Control Relay controlled by pin 11
Code Structure and Logic
The Arduino code is structured into several functions, each responsible for a specific stage in the remote start process.
setup()
: This function runs once at the start. It configures the input and output pins and initializes serial communication for debugging and command input.loop()
: This is the main function that runs continuously. It constantly checks for a start command, either via serial input (‘s’) or a high signal on theBT_on
pin.check()
: Verifies if the serial command received is the correct start command (‘s’).begin()
: Initiates the starting sequence by turning on the accessory and ignition systems.neutral()
: Checks if the vehicle is in neutral before proceeding with the start.start()
: Engages the starter motor, but only if the engine is not already running (verified by the RPM sensor).starter_engaged()
: Monitors the engine RPM after the starter is engaged. It disengages the starter once the engine starts or after a timeout period if the engine fails to start.disengage_starter()
anddisengage_starter_timeout()
: Functions to disengage the starter motor. The timeout version handles cases where the engine doesn’t start within a set timeframe.vehicle_off_sense()
: Monitors for an “off” command via Bluetooth (‘o’ serial command orBT_off
pin) or automatic timeout (30 minutes) or vehicle movement to shut down the engine.vehicle_off()
: Turns off the ignition and accessory systems, effectively shutting down the vehicle and resetting the system to wait for a new start command.
unsigned long start_time; //Variable used to store the time at which the starter is engaged
char data = 0; //Variable used to store data received via the serial port
//DEFINITIONS
void setup() {
pinMode(BT_on, INPUT); //Define pin inputs and outputs
pinMode(RPM_sens, INPUT);
pinMode(neutral_sens, INPUT);
pinMode(speed_sens, INPUT);
pinMode(BT_off, INPUT);
pinMode(ign_ctrl, OUTPUT);
pinMode(start_ctrl, OUTPUT);
pinMode(clutch_sfty, OUTPUT);
pinMode(acc_ctrl, OUTPUT);
Serial.begin(9600); //Initialize the serial port with a baud rate of 9600
}
//PROGRAM
//Loop function: Wait for a START command to be received (command via serial port or
//voltage applied to BT_on pin)
void loop() {
if (Serial.available() > 0) //Check if any serial data is available
{
data = Serial.read(); //Set "data" variable to currently available serial data
check(); //Go to "check" funtion if serial data is available
}
else if (digitalRead(BT_on) == HIGH) //Check if BT Module has set pin high
{
begin(); //Begin start sequence if pin is high
}
else
{
loop(); //Repeat this function until a START command is received
}
}
//Check function: Test for proper character set from BT
void check() {
if (data == 's') //Check if an "s" has been sent
{
begin(); //Begin start sequence if "s" has been sent
}
else
{
Serial.println("Invalid Command"); //If anything other than an "s" has been sent, send "Invalid Command" error message
loop(); //Return to beginning of program
}
}
//Begin function: Turn on accessory and ignition
void begin() {
delay(500); // 0.5 second delay
digitalWrite(acc_ctrl, HIGH); //Turn accessory ON
digitalWrite(ign_ctrl, HIGH); //Turn ignition ON
delay(500); // 0.5 second delay
neutral(); //Go to "neutral" funciton
}
//Neutral function: Check if the vehicle's transmission is in the neutral position.
// Continue if vehicle is in neutral. Exit start sequence if vehicle is in gear.
void neutral() {
if (digitalRead(neutral_sens) == HIGH ) //Continue only if vehicle is in neutral
{
start(); //Start vehicle if in neutral
}
else
{
Serial.println("Vehicle Not in Neutral"); //If in gear, send "Vehicle Not in Neutral" error message
vehicle_off(); //Exit start sequence if in gear
}
}
//Start function: Engage starter only if engine is not already running.
void start() {
int RPM = pulseIn(RPM_sens, HIGH, 1000000); //Get RPM value
if(RPM == 0) //Continue start sequence only if vehicle is not running.
{
digitalWrite(clutch_sfty, HIGH); //"Depress" clutch
digitalWrite(acc_ctrl, LOW); //Turn accessory OFF
delay(500); //0.5 second delay
digitalWrite(start_ctrl, HIGH); //Engage starter
start_time = millis(); //Capture the time at which the starter was engaged
starter_engaged(); //Go to Starter_engaged function
}
else
{
Serial.println("Vehicle Already Running"); //Send "Vehicle Already Running" message if engine was previously started
vehicle_off(); //Exit start sequence if already running
}
}
//Starter_engaged function: Disengage starter after vehicle is starter or turn off starter if
//vehicle has not started within 4 seconds.
void starter_engaged() {
int RPM = pulseIn(RPM_sens, HIGH); //Get RPM value
if (RPM > 1000) //Continue if engine has started (reached low idle)
{
Serial.println("Engine Running"); //Send "Engine Running" message after engine has started
disengage_starter(); //Go to disengage_starter after engine is running
}
else if ((start_time+4000) < millis()) //Test if 4 seconds has passed since the starter was engaged
{
disengage_starter_timeout(); //Go to disengage_starter if engine has not started within 4 seconds of starter engagement
}
else
{
starter_engaged(); //Repeat this function if engine has not started or 4 seconds has not elapsed
}
}
//Disengage_starter function: Disengage the starter.
void disengage_starter() {
digitalWrite(start_ctrl, LOW); //Disengage the starter
digitalWrite(clutch_sfty, LOW); //"Release" the clutch
digitalWrite(acc_ctrl, HIGH); //Turn accessory ON
vehicle_off_sense(); //Go to vehicle_off_sense function
}
//Disengage_starter_timeout function: Disengage the starter (used after 4 seconds has elapsed without an engine start)
void disengage_starter_timeout() {
digitalWrite(start_ctrl, LOW); //Disengage the starter
digitalWrite(clutch_sfty, LOW); //"Release" the clutch
Serial.println("Vehicle Start Unsuccessful"); //Send "Vehicle Start Unsuccessful"
vehicle_off(); //Exit start sequence
}
//Vehicle_off_sense function: Waits for an "off" signal to be sent while the engine is running.
//If no "off" signal is received, turns off the vehicle after 30 minutes has elapsed since the engine start.
void vehicle_off_sense() {
int moving = pulseIn(speed_sens, HIGH); //Get vehicle speed
if (moving > 1000) //Check if vehicle is moving
{
Serial.println("Vehicle OFF -- Movement"); //Send "Vehicle OFF -- Movement" message ifvehcile is moving
vehicle_off(); //Turn vehicle off if it is moving
}
else if (start_time+1800000 < millis()) //Check if 30 minutes has elapsed since engine start
{
Serial.println("Vehicle OFF -- Automatic Timeout"); //Send "Vehicle OFF -- Automatic Timeout" message if engine has been
//running for 30 minutes
vehicle_off(); //Turn vehicle off if engine is running for 30 minutes
}
else if (Serial.available() > 0) //Check if a signal has been sent via serial data
{
data = Serial.read(); //Store serial sent serial data
if (data == 'o') //Check if signal sent is an OFF command ("o")
{
Serial.println("Vehicle OFF -- User Commanded"); //Send "Vehicle OFF -- User Commanded" message if serial data is the OFF
//command ("o")
vehicle_off(); //Turn vehicle off if OFF command is sent via serial data
}
}
else if (digitalRead(BT_off) == HIGH) //Check if OFF command has been sent via BT module
{
Serial.println("Vehicle OFF -- User Commanded"); //Send "Vehicle OFF -- User Commanded" message if OFF
//command was sent
vehicle_off(); //Turn vehicle off if OFF command was sent via BT module
}
else
{
vehicle_off_sense(); //Repeat this function if none of the above conditions have been met
}
}
//Vehicle_off function: Turns the vechile off and starts the whole program over
void vehicle_off() {
digitalWrite(ign_ctrl, LOW); //Turn ignition OFF
digitalWrite(acc_ctrl, LOW); //Turn accessory OFF
loop(); //Repeat program (look for start command)
}
Start Sequence in Detail
The remote start sequence begins when the Arduino receives a start command. This can happen in two ways:
- Bluetooth Command: An Android app (like Daisy On/Off app) sends a signal to a Bluetooth module connected to the Arduino. This signal sets the
BT_on
pin high, triggering the start sequence. - Serial Command: Sending the character “s” via a serial terminal connected to the Arduino also initiates the start sequence. This is useful for testing and debugging.
Once a start command is received, the begin()
function takes over. It first introduces a short delay, then activates the accessory and ignition relays. After another brief delay, it calls the neutral()
function to ensure safety.
Safety and Shutdown Features
This Arduino code prioritizes safety with several built-in checks and shutdown mechanisms:
- Neutral Position Check: The
neutral()
function verifies that the vehicle is in neutral gear. If not, the start sequence is aborted, and an error message is sent via serial communication. - Engine Running Check: The
start()
function checks the RPM sensor to ensure the engine is not already running before engaging the starter motor. This prevents unnecessary starter wear and tear. - Starter Timeout: The
starter_engaged()
function includes a 4-second timeout for starter engagement. If the engine doesn’t start within this time, the starter is disengaged to prevent overheating and damage. - Automatic Shutdown: The
vehicle_off_sense()
function implements several automatic shutdown scenarios:- Vehicle Movement: If the
speed_sens
detects vehicle movement, the engine is shut off, preventing accidental driving while remotely started. - Timeout: If no “off” command is received within 30 minutes of starting, the engine is automatically shut down to conserve fuel and prevent prolonged idling.
- User Command: The system listens for an “off” command (‘o’ via serial or
BT_off
pin) to allow the user to remotely shut down the engine.
- Vehicle Movement: If the
Key Code Sections Explained
Let’s delve deeper into some of the crucial functions within the code:
loop()
function and Start Command
The loop()
function is the heart of the program. It continuously monitors for start commands:
void loop() {
if (Serial.available() > 0) //Check if any serial data is available
{
data = Serial.read(); //Set "data" variable to currently available serial data
check(); //Go to "check" funtion if serial data is available
}
else if (digitalRead(BT_on) == HIGH) //Check if BT Module has set pin high
{
begin(); //Begin start sequence if pin is high
}
else
{
loop(); //Repeat this function until a START command is received
}
}
This function first checks if any data is available via the serial port. If so, it reads the data and calls the check()
function. If no serial data is available, it then checks the BT_on
pin. If this pin is HIGH, indicating a Bluetooth start signal, the begin()
function is called to initiate the start sequence. If neither condition is met, the loop()
function repeats, continuously listening for a start command.
check()
function for Serial Command
The check()
function is simple but important for serial command handling:
void check() {
if (data == 's') //Check if an "s" has been sent
{
begin(); //Begin start sequence if "s" has been sent
}
else
{
Serial.println("Invalid Command"); //If anything other than an "s" has been sent, send "Invalid Command" error message
loop(); //Return to beginning of program
}
}
It verifies if the received serial data (data
) is equal to the character ‘s’. If it is, the begin()
function is called to start the engine. If the received data is anything else, an “Invalid Command” message is sent via serial, and the program loops back to wait for a valid command.
begin()
function – Ignition and Accessory
The begin()
function is the first step in the actual starting process:
void begin() {
delay(500); // 0.5 second delay
digitalWrite(acc_ctrl, HIGH); //Turn accessory ON
digitalWrite(ign_ctrl, HIGH); //Turn ignition ON
delay(500); // 0.5 second delay
neutral(); //Go to "neutral" funciton
}
It introduces a 0.5-second delay, then activates the acc_ctrl
and ign_ctrl
output pins, which in turn activate the accessory and ignition relays in the car. Another 0.5-second delay is followed by a call to the neutral()
function to proceed with the safety check.
neutral()
function – Safety Check
The neutral()
function is a crucial safety component:
void neutral() {
if (digitalRead(neutral_sens) == HIGH ) //Continue only if vehicle is in neutral
{
start(); //Start vehicle if in neutral
}
else
{
Serial.println("Vehicle Not in Neutral"); //If in gear, send "Vehicle Not in Neutral" error message
vehicle_off(); //Exit start sequence if in gear
}
}
It reads the state of the neutral_sens
input pin. If it’s HIGH, indicating the vehicle is in neutral, it calls the start()
function to engage the starter. If the neutral_sens
pin is not HIGH, a “Vehicle Not in Neutral” message is sent via serial, and the vehicle_off()
function is called to abort the start sequence.
start()
function – Starter Engagement
The start()
function handles the starter motor engagement:
void start() {
int RPM = pulseIn(RPM_sens, HIGH, 1000000); //Get RPM value
if(RPM == 0) //Continue start sequence only if vehicle is not running.
{
digitalWrite(clutch_sfty, HIGH); //"Depress" clutch
digitalWrite(acc_ctrl, LOW); //Turn accessory OFF
delay(500); //0.5 second delay
digitalWrite(start_ctrl, HIGH); //Engage starter
start_time = millis(); //Capture the time at which the starter was engaged
starter_engaged(); //Go to Starter_engaged function
}
else
{
Serial.println("Vehicle Already Running"); //Send "Vehicle Already Running" message if engine was previously started
vehicle_off(); //Exit start sequence if already running
}
}
First, it reads the RPM value from the RPM_sens
using pulseIn()
. If the RPM is 0, meaning the engine is not running, the function proceeds. For manual transmissions, it simulates clutch depression by activating the clutch_sfty
relay. It briefly turns off the accessory power (acc_ctrl
), likely to provide more power to the starter, and then engages the starter motor by setting start_ctrl
HIGH. The millis()
function captures the start time, and the starter_engaged()
function is called to monitor the engine start. If the RPM is not 0, indicating the engine is already running, a “Vehicle Already Running” message is sent, and the start sequence is aborted.
starter_engaged()
function – Engine Start Detection
The starter_engaged()
function monitors if the engine has started after the starter is engaged:
void starter_engaged() {
int RPM = pulseIn(RPM_sens, HIGH); //Get RPM value
if (RPM > 1000) //Continue if engine has started (reached low idle)
{
Serial.println("Engine Running"); //Send "Engine Running" message after engine has started
disengage_starter(); //Go to disengage_starter after engine is running
}
else if ((start_time+4000) < millis()) //Test if 4 seconds has passed since the starter was engaged
{
disengage_starter_timeout(); //Go to disengage_starter if engine has not started within 4 seconds of starter engagement
}
else
{
starter_engaged(); //Repeat this function if engine has not started or 4 seconds has not elapsed
}
}
It reads the RPM again. If the RPM is above 1000 (indicating a successful engine start), an “Engine Running” message is sent, and disengage_starter()
is called to disengage the starter motor. If 4 seconds have passed since the starter engagement (start_time
) and the engine hasn’t started, disengage_starter_timeout()
is called to disengage the starter and indicate a start failure. If neither of these conditions is met, the starter_engaged()
function repeats, continuing to monitor for engine start or timeout.
vehicle_off_sense()
and vehicle_off()
– Shutdown Logic
The vehicle_off_sense()
and vehicle_off()
functions handle the engine shutdown process. vehicle_off_sense()
monitors for various shutdown triggers:
void vehicle_off_sense() {
int moving = pulseIn(speed_sens, HIGH); //Get vehicle speed
if (moving > 1000) //Check if vehicle is moving
{
Serial.println("Vehicle OFF -- Movement"); //Send "Vehicle OFF -- Movement" message ifvehcile is moving
vehicle_off(); //Turn vehicle off if it is moving
}
else if (start_time+1800000 < millis()) //Check if 30 minutes has elapsed since engine start
{
Serial.println("Vehicle OFF -- Automatic Timeout"); //Send "Vehicle OFF -- Automatic Timeout" message if engine has been
//running for 30 minutes
vehicle_off(); //Turn vehicle off if engine is running for 30 minutes
}
else if (Serial.available() > 0) //Check if a signal has been sent via serial data
{
data = Serial.read(); //Store serial sent serial data
if (data == 'o') //Check if signal sent is an OFF command ("o")
{
Serial.println("Vehicle OFF -- User Commanded"); //Send "Vehicle OFF -- User Commanded" message if serial data is the OFF
//command ("o")
vehicle_off(); //Turn vehicle off if OFF command is sent via serial data
}
}
else if (digitalRead(BT_off) == HIGH) //Check if OFF command has been sent via BT module
{
Serial.println("Vehicle OFF -- User Commanded"); //Send "Vehicle OFF -- User Commanded" message if OFF
//command was sent
vehicle_off(); //Turn vehicle off if OFF command was sent via BT module
}
else
{
vehicle_off_sense(); //Repeat this function if none of the above conditions have been met
}
}
It checks for vehicle movement, a 30-minute timeout, serial “off” command (‘o’), or a Bluetooth “off” signal (BT_off
pin). If any of these conditions are met, it calls vehicle_off()
to shut down the engine. If none of the conditions are met, vehicle_off_sense()
repeats.
The vehicle_off()
function itself is straightforward:
void vehicle_off() {
digitalWrite(ign_ctrl, LOW); //Turn ignition OFF
digitalWrite(acc_ctrl, LOW); //Turn accessory OFF
loop(); //Repeat program (look for start command)
}
It simply deactivates the ignition and accessory relays by setting ign_ctrl
and acc_ctrl
to LOW, effectively turning off the engine and accessories. After shutdown, it calls loop()
to restart the main program loop, ready to receive a new start command.
Conclusion
This Arduino code provides a comprehensive foundation for building a remote car starter system. It demonstrates how to manage vehicle ignition, starter, and accessory systems while incorporating essential safety features like neutral gear checks, engine running detection, and automatic shutdown mechanisms. By understanding the logic and structure of this code, enthusiasts and DIYers can gain valuable insights into the workings of remote car starters and explore the possibilities of Arduino in automotive automation projects.