THE TRIKE SMART CODE

In this blog and video, we'll see my approach to code and prototyping.  By starting with the code and commenting the code with the pin out purpose, I can quickly breadboard my circuits with the help of a highlighter - no schematic needed!

 

 

Here is the code for the Smart Trike:

 

/*

V1.0:  As Built 9/4/2018

Smart Trike by Sean and Connor Miller of Raising Awesome on YouTube
GPIO Assignments:
A0:  Motor Controller Killer (if it goes low, it kills the motor)
A1:  User okay button (interrupt) when pulled down, it interrupts
A2:  User menu button (interrupt) when pulled down, it interrupts
A3:  Ultrasonic Trigger (GPIO18)
A4:  Accelerometer X
A5:  Accelerometer Y
A6:  Accelerometer Z
D0:  pedal Sensor (interrupt)
D1:  Speed sensor (interrupt)
D2:  Beeper to give feedback when buttons are pressed
D3:  LCD's D7 pin
D4:  SD Card Chip Select Pin
D5:  LCD D5 pin
D6:  LCD D6 pin
D7:  LCD en pin 
D8:  SD Card
D9:  SD Card
D10: SD Card
D11: LCD rs
D12: LCD D4
D13: Ultrasonic Echo
D14: Brake Switch (when brake is pulled, goes to ground)
*/
// include the library code:
#include <LiquidCrystal.h>
#include <SPI.h>
#include <WiFi101.h>
#include <SD.h>
//miscellaneous pins
#define echoPin 13
#define interlockPin 15
#define pedalSensorPin 0
#define speedSensorPin 1
#define beeperPin 2
#define brakeSwitchPin 14
#define menuPin 16
#define okPin 17
// initialize the library by associating any needed LCD interface pin
// with the arduino pin number it is connected to
const int rs = 11, en = 7, d4 = 12, d5 = 5, d6 = 6, d7 = 3;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
//Pins used by the SD card:  4, 8, 9, 10, gnd, vcc
const int chipSelect = 4;
//wifi setup
char ssid[] = "SSID";
char pass[] = "PASSWORD";
int keyIndex = 0;   
int status = WL_IDLE_STATUS;
// Initialize the WiFi client library
WiFiSSLClient wifi;
const int httpsPort = 443;//this is the SSL default port
// Use web browser to view and copy
// SHA1 fingerprint of the certificate
const char* host = "maker.ifttt.com";
String response;
int statusCode = 0;
const int trigPin=A3;//for proximity trigger
//ADXL Accelerometer pins:
const int ap1 = A4; 
const int ap2 = A5;
const int ap3 = A6;
int sv1 = 0;        
int ov1 = 0;    
int sv2 = 0;      
int ov2= 0;      
int sv3 = 0;       
int ov3= 0;      
volatile bool alarm_armed=false;//set when user selects to alarm to be on.
volatile bool alarm_triggered=false;
volatile bool menu_mode = false;volatile int menu_level=0; volatile bool want_beep=true;
volatile unsigned long menu_button_timer;volatile unsigned long ok_button_timer;//used to prevent double taps of buttons in ISRs.
volatile unsigned long speed_sensor_timer;volatile unsigned long speed_chatter_timer;volatile unsigned long proximity_timer;
volatile unsigned long pedal_sensor_timer;volatile unsigned long pedal_chatter_timer;//used to check if peddling
volatile float speed=0;volatile float top_speed=0;volatile float distance=0;float proximity=0;
volatile bool hill=false;volatile bool tilt=false;
volatile bool interlock=false; volatile bool want_garage=false;
bool not_peddling=true;bool too_slow=true;bool too_close=false;bool brake_applied=false;
int interlock_times=0; bool recorded_interlock=false;
int pedal_counter=0;//used to calculate calories burned.
String interlock_state="Enabled ";
void setup() {
  pinMode(beeperPin,OUTPUT);digitalWrite(2,LOW);//the buzzer
  pinMode(interlockPin,OUTPUT);digitalWrite(interlockPin,LOW);
  pinMode(brakeSwitchPin,INPUT_PULLUP);
  beepIt();
  lcd.begin(16, 2);// set up the LCD's number of columns and rows:
  lcd.clear();lcd.print("Starting up...");
  beepIt();
  setupProximity();
  beepIt();
  setupSDCard();
  beepIt();
  setupInterrupts();
  beepIt();
}
void loop() {
  // set the cursor to column 0, line 1
  // (note: line 1 is the second row, since counting begins with 0):
  lcd.setCursor(0,0);
  lcd.print(String(speed) + " mph ");  // print the number of seconds since reset:
  readADXL();
  readProximity();
  
  if (menu_mode) manageMenuMode();
  if (millis()>pedal_sensor_timer) {
    not_peddling=true; 
    lcd.setCursor(14,0);
    lcd.print("NP");
  }
  else 
  {
    lcd.setCursor(14,0);
    lcd.print("P ");
    not_peddling=false;
  }
  
  if ((millis()-speed_sensor_timer)>3000) speed=0;//if we haven't triggered the interrupt in 3 seconds, we are stopped.
  if (speed<2) too_slow=true;else too_slow=false;//not triggering on this anymore.  Need to be able to throttle from stop if you move the pedals around.
  if (proximity<6) too_close=true;else too_close=false;
  if (digitalRead(brakeSwitchPin)==LOW) brake_applied=true;else brake_applied=false;
  if ((brake_applied||hill||tilt||not_peddling||too_close)&&interlock_state=="Enabled ") 
  {
    if (!recorded_interlock) {
      digitalWrite(interlockPin,LOW);
      recorded_interlock=true;interlock_times++; saveToSD();delay(1000); 
      lcd.setCursor(0, 1);
      lcd.print("Locked  ");
      beepIt();    
    }
  } else {
    recorded_interlock=false;digitalWrite(interlockPin,HIGH);
    lcd.setCursor(0, 1);  
    lcd.print(interlock_state);
  }
  delay(100);
}
void setupInterrupts(){
  menu_button_timer=millis();ok_button_timer=millis();speed_chatter_timer=millis();speed_sensor_timer=millis();
  pedal_chatter_timer=millis();pedal_sensor_timer=millis();proximity_timer=millis();
  pinMode(pedalSensorPin, INPUT_PULLUP);  attachInterrupt(digitalPinToInterrupt(pedalSensorPin), pedalISR, FALLING);
  pinMode(speedSensorPin, INPUT_PULLUP);  attachInterrupt(digitalPinToInterrupt(speedSensorPin), speedISR, FALLING);
  pinMode(menuPin, INPUT_PULLUP);  attachInterrupt(digitalPinToInterrupt(menuPin), menuISR, FALLING);
  pinMode(okPin, INPUT_PULLUP);  attachInterrupt(digitalPinToInterrupt(okPin), okISR, FALLING);
}
void pedalISR(){
  if (millis()>pedal_chatter_timer) {
    if (alarm_armed) alarm_triggered=true;
    ++pedal_counter;
    pedal_sensor_timer=millis()+5000;//must pedal every 5 seconds or we will interlock
    pedal_chatter_timer=millis()+300;
  }
}
void speedISR(){
  if (millis()>speed_chatter_timer){
    if (alarm_armed) alarm_triggered=true;
    speed=.001189/(((    ( (float)millis()-(float)speed_sensor_timer ) /1000)/60.0)/60);   //(2*3.14*12/12)/5280
    distance=distance+.001189;  
    if (speed>top_speed) {top_speed=speed; saveToSD();}
    speed_sensor_timer=millis();
    speed_chatter_timer=millis()+250;//250 is to prevent noise from causing an issue.  It would mean we were going 17mph or over, which exceeds the drivetrain speed and can't happen.
  }
}
void menuISR(){
  if ((millis()>menu_button_timer)&!alarm_armed){
    want_beep=true;
    if (menu_mode==true) {
      if (++menu_level>8) menu_level=0;
    }
    else 
    {
      menu_mode=true;menu_level=0;
    }
    menu_button_timer=millis()+300;
  }
}
void readProximity() {
  //only check every half second
  if (millis()>proximity_timer) {
    long my_duration; float old_proximity=proximity;
    digitalWrite(trigPin, LOW);  
    delayMicroseconds(20); 
    digitalWrite(trigPin, HIGH);
    delayMicroseconds(50); 
    digitalWrite(trigPin, LOW);
    my_duration = pulseIn(echoPin, HIGH);
    
    proximity = ((my_duration*0.017/2.54)/12);
    if (proximity<1) proximity=old_proximity;//used to suppress bad values.
    if (proximity<1) proximity=10;//if two bad values in a row, just assume 10.  This will prevent nusance trips.
    lcd.setCursor(9,1);lcd.print(String(proximity) + "ft ");
    proximity_timer=millis()+500;
  }
}
void beepIt(){
  tone(beeperPin,800);
  delay(100);
  noTone(beeperPin);
}
void okISR(){
  if (millis()>ok_button_timer){
    if (menu_mode==true){  
      if (menu_level==0) {want_beep=true;menu_mode=false;}
      if (menu_level==7) {
        want_beep=true;
        if (interlock_state=="Enabled ") {         
          interlock_state="Disabled";
        } else interlock_state="Enabled ";
        lcd.setCursor(0,1);
        lcd.print(interlock_state);
        menu_mode=false;
      }
      if (menu_level==1){want_beep=true;want_garage=true;}//triggers the garage opener.
      
      if (menu_level==2){
        want_beep=true;
        alarm_armed= !alarm_armed;
        lcd.clear();
        if (alarm_armed) {
          lcd.print("Alarm Armed     ");
          lcd.setCursor(0,1);
          lcd.print("Don't Move!     ");
        }else {
          alarm_triggered=false;
          lcd.print("Alarm Off       ");
          menu_mode=false;
        }
      }
    }
    ok_button_timer=millis()+300;
  }
}
void soundAlarm(){
  while(alarm_triggered) {
    tone(beeperPin,1000);
    digitalWrite(brakeSwitchPin,HIGH);
    delay(300);
    noTone(beeperPin);
    delay(300);
  };//wait for disarm
}
void manageMenuMode(){
  int last_level=menu_level;
  lcd.clear();
  lcd.print("Menu:  Exit     ");
  while (menu_mode){
    if (alarm_armed) {delay(300);readADXL();}
    if (alarm_triggered) soundAlarm();
    if (last_level !=menu_level){
      if (menu_level==0) {
          lcd.clear();
          lcd.print("Menu:  Exit");
      }
      if (menu_level==1) {
          lcd.clear();
          lcd.print("Menu:Garage Door");
          lcd.setCursor(0,1);
          lcd.print("Press to trigger");
      }
      
      if (menu_level==2) {
          lcd.clear();
          lcd.print("Menu:  Alarm");
          lcd.setCursor(0,1);
          lcd.print("Press to Arm");
      }
      
      if (menu_level==3) {
          lcd.clear();
          lcd.print("Menu:  Top Speed");
          lcd.setCursor(0,1);
          lcd.print(String(top_speed));
      }
      
      if (menu_level==4) {
          lcd.clear();
          lcd.print("Menu:  Distance");
          lcd.setCursor(0,1);
          lcd.print(String(distance) + " Miles");
      }
      if (menu_level==5) {
          lcd.clear();
          lcd.print("Menu:  Time");
          lcd.setCursor(0,1);
          lcd.print(String((millis()/1000)/60) + " Mins " + ((millis()/1000) - 60*(millis()/1000/60)) + " Secs");
      }
      if (menu_level==6) {
          lcd.clear();
          lcd.print("Menu:Interlock #");
          lcd.setCursor(0,1);
          lcd.print(String(interlock_times));
      }
      if (menu_level==7) {
          lcd.clear();
          lcd.print("Menu:Interlock");
          lcd.setCursor(0,1);
          lcd.print(interlock_state);
      }
      
      if (menu_level==8) {
          lcd.clear();
          lcd.print("Menu:Designed by");
          lcd.setCursor(0,1);
          lcd.print("Raising Awesome");
      }
      last_level=menu_level;
    }
    if (want_beep) {want_beep=false;beepIt();}
    if (want_garage) {want_garage=false;sendGarageDoorCommand();}
  }
  lcd.clear();lcd.print("Exited Menu.");
}
void setupSDCard(){
  //Initialize Serial and wait for port to open:
  lcd.setCursor(0, 0);
  
  lcd.print("Initializing SD card...");
  
  // see if the card is present and can be initialized:
  if (!SD.begin(chipSelect)) {
    lcd.clear();lcd.print("Card failed, or not present");
  
   while (true);
  }
  lcd.setCursor(0,0);
  lcd.print("card initialized...");
}
void sendGarageDoorCommand(){
  lcd.clear();lcd.print("connecting WiFi");
  connectToAP();    // connect the board to the access point
 
  lcd.clear();lcd.print("making request");
  //HttpClient client=HttpClient(wifi, host, httpsPort);
  WiFiClient client;
  client.connect(host, 80);
  lcd.clear();lcd.print("Hit IFTTT");
  String url="/trigger/GarageDoorButtonPress/with/key/-oGdgnjVXXXXXXXXXm3";
  client.print(String("GET ") + url + " HTTP/1.1\r\n" +
               "Host: " + host + "\r\n" +
               "User-Agent: BuildFailureDetectorESP8266\r\n" +
               "Connection: close\r\n\r\n");
               
  lcd.clear();
  while (client.connected()) {
    String line = client.readStringUntil('\n');
    lcd.print("Sending Command");
    if (line == "\r") {
      break;
    }
  }
  String line = client.readStringUntil('\n');
  
  lcd.clear();lcd.print("Garage Sent");
  saveToSD();
}
void connectToAP() {
  // check for the presence of the shield:
  if (WiFi.status() == WL_NO_SHIELD) {
    // don't continue:
    return;
  }
  // attempt to connect to Wifi network:
  while ( status != WL_CONNECTED) {
    // Connect to WPA/WPA2 network. Change this line if using open or WEP network:
    status = WiFi.begin(ssid, pass);
    // wait 1 second for connection:
    delay(1000);
  }
}
void saveToSD() {
  File dataFile = SD.open("RA.csv", FILE_WRITE);
  if (dataFile) {
    dataFile.print("Time:" + String(millis()/1000) +", Speed: " + String(speed) + ", Top Speed: " + String(top_speed) + ", Distance: " + String(distance) + ", Hill:" + String(hill) + ", Tilt: " + String(tilt) + ", Not Peddling: " + not_peddling +", Too Slow: " + too_slow + ", Interlock Count: " + interlock_times + "\n");
  }
  dataFile.close();
}
void readAndPrint() {
  return;
  File dataFile = SD.open("logo.txt", FILE_READ);
  while (dataFile.available()) {
    char c = dataFile.read();
   // Serial.write(c);
  }
  dataFile.close();
}
void setupProximity() {
  pinMode(trigPin, OUTPUT);digitalWrite(trigPin, LOW);
  pinMode(echoPin, INPUT_PULLDOWN);
}
void readADXL() {
  sv1 = sv1+((sv1-analogRead(ap1))/2);//this is to ease in to a tilt over a couple of seconds.  We just want to detect hills - not bumps.
  delay(2);
  sv2 = analogRead(ap2);     
  delay(2);
  sv3 = analogRead(ap3);
  lcd.setCursor(9,0);
  
  if (sv1>550) {//tilt hit  || <490will get it the other way.
     lcd.print("Hill   ");
     hill=false;
    
     tilt=true; if (alarm_armed) alarm_triggered=true;
   }else {
    lcd.print(String(sv1-520) + "   ");//give the illusion of degrees of incline
    tilt=false;
   }   
 }

In the final episode, we'll solder up a perfboard, print the 3D Plastic parts, and install.   This will finish out the Smart Trike part of the build.  We'll later jump into welding brackets for installing the batteries lower on the frame.  We'll dive into that on your main channel, Raising Awesome.

 

-Sean and Connor