JanHendrikStam
Published © GPL3+

Strandbeest Remote

A strandbeest controlled with infrared as well as an ultraound device.

IntermediateShowcase (no instructions)630
Strandbeest Remote

Things used in this project

Hardware components

Arduino UNO
Arduino UNO
×1
Ultrasonic Sensor - HC-SR04 (Generic)
Ultrasonic Sensor - HC-SR04 (Generic)
×1
IR Transceiver (Generic)
I used a 'car' remote control. the numbers pressed being 2,4 6,8 an 5 for 'forward', 'left', 'right', 'backward' and 'stop' see sketch.
×1
Breadboard (generic)
Breadboard (generic)
to mount leds and IR sensor as well as add easy parallel connection of the two 9v batteries. And easy aceess to mulitple grounds and 5v power sources
×1
Dual H-Bridge motor drivers L298
SparkFun Dual H-Bridge motor drivers L298
Easy access to motor controls, as well as a stable 5v VIN for the arduino. Two 9 V batteries to this thingy, and then 5v to the arduino. Works fine. leave the jumper in place...
×1
Arduino Uno Leonardo prototyping shield
great for prototyping. gives you 5 grounds, and 5 5v pins for free, stacks on the uno. minibreadboards fit nicely.
×1

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
to provide solid wiring to the motors

Story

Read more

Schematics

mini breadboard. on the right the IR sensor. The yellow wire goes into A01

motor board. left motor and right motor are self explaining. diver wires: at the bottom.

the leftmost orange wire is VIN: 9v. the rightmost orange feeds the Arduino with 5V. The blue one is ground
Just above them the controller wire between Arduino and this board. inner ones for the polarty, outer ones for the speed control.

Code

IrRemote code

Arduino
#include <IRremote.h>
#include <NewPing.h> // to avoid compiling errors in combination with the use of the IRremote library, you should set the '#define TIMER_ENABLED' in this header file to false;
                     // this was for a while a major headache. Eventually the author, Tim Eckel, provided the answer in of the arduino forums. Thanks, M8!

// Finite State Machine implementation using a switch statment used by the infrared remote control
#define IRForward 2
#define IRForwardAvoid 12 // 26? = FR
#define IRRight 6 
#define IRRightAvoid 16 //64? = RL
#define IRBack 8
#define IRLeft 4 
#define IRLeftAvoid 14 // 46? = LR
#define IRStop 5
#define IRStopAvoid 15 //58? = SB

  // secundary transition states
  // StateID can be calculated by (10*currentDirection+targetDirection)
  // the mnemonic is tin the same way a simple combination of current- and target direction
  
#define FS 25 // IRForward -> IRSTOP
#define BS 85 // IRBack    -> IRStop
#define LS 45 // Left      -> Stop
#define RS 65 // Right     -> Stop
#define SF 52 // IRStop    -> IRForward
#define SB 58 // IRStop    -> IRBack
#define SL 54 // Stop      -> Left
#define SR 56 // Stop      -> Right
#define FR 26 // etc.
#define FB 28
#define FL 24
#define RF 62
#define BF 82
#define LF 42
#define BL 84
#define BR 86
#define LB 48
#define RB 68
#define RL 64
#define LR 46

// used by single motors
#define ForwardStop 25
#define BackwardStop 25
#define StopForward 52
#define StopBackward 58

//pindefines

//motor A, in my case the left one
#define Ena  10 //Enable, and speed control
#define a1   8 // turning direction HIGH or LOW  or LOW. HIGH HIGH is nonsense. LOW LOW makes the motor stop.
#define a2   7 // turning direction LOW  or HIGH or LOW

#define Enb  9 //Enable, and speed control
#define b1   4
#define b2   6
// A word to the wise. Do not use arduino ports 3 and 11 in situations where PFM on multiple devices, even if only a LED is necessary. 
// It won't work. Hence the curious use of the enable ports

#define LEDpin 14     // A0 Infrarood
#define TrigPin 16    // A1 ultrasound
#define EchoPin 15    // A2 ultrasound
#define redLED 5      // redLED must be PFM enabled ...
#define greenLED 12   // green LED on/off only
#define BAUDRATE 9600 // debugging

//constants
const int safeDistance=15;
const unsigned int maxDistance = 200;
const unsigned int DCMaxSpeed = 255;
const unsigned int DCMinSpeed = 32;
const int SpeedStep = 4;

const long Button_DEC_code[21] = 
  {
     16753245,     16736925,     16769565,
     16720605,     16712445,     16761405,
     //vol-,vol+,eq
     16769055,     16754775,     16748655,
     //0,100+,200+
     16738455,     16750695,     16756815,
     // the number buttons
     16724175,     16718055,     16743045,
     16716015,     16726215,     16734885,
     16728765,     16730805,     16732845
  };
const unsigned long ff_code[21] = // unknown IR device
  {
    3810010651,5316027,4001918335,
    1386468383,3622325019,553536955,
    4034314555,2747854299,3855596927,
    3238126971,2538093563,4039382595,
    2534850111,1033561079,1635910171,
    2351064443,1217346747,71952287,
    851901943,465573243,1053031451
  };


// global variables

int IRstate=IRStop; 
int changeState=0;
boolean watchit=false; // obstruction detected
boolean USaan=false;
unsigned int currentDCSpeed = 168;
unsigned int lastDCSpeed=currentDCSpeed;
int tempDirection=0;

int currentDirection = -1; 
int targetDirection = -1; 

// initialise infrared
const int RECV_PIN = LEDpin;
IRrecv irrecv(RECV_PIN);
decode_results results; 
const int nobuttons=21;
unsigned long buttonPressed=0;

// initialise ultrasound

NewPing sonar(TrigPin, EchoPin, maxDistance);

void setup() {
  // put your setup code here, to run once:
  pinMode(LEDpin,INPUT);
  Serial.begin(BAUDRATE); 
  irrecv.enableIRIn(); // Start the receiver
  // start motor A
  pinMode(Ena,OUTPUT);
  pinMode(a1,OUTPUT);
  pinMode(a2,OUTPUT);
  analogWrite(Ena,currentDCSpeed); // necessary for some reason
  // start motor B
  pinMode(Enb,OUTPUT);
  pinMode(b1,OUTPUT);
  pinMode(b2,OUTPUT);
  analogWrite(Enb,currentDCSpeed);
  
  moveDirection(IRStop); // Vehicle.direction = DirectionForward
  
  pinMode(redLED, OUTPUT);   // red LED
  pinMode(greenLED, OUTPUT); // green LED
}

void loop() {

  if (!safe2Proceed()){ // also controls red LED
     watchit=true; // we must take some action to avoid obstruction
  }
  else{
     watchit=false;
  }

    buttonPressed=getRemoteSignal();
    if(buttonPressed>0){

      switch(buttonPressed) // -1 = unknown. I included the other codes in comment, just in case. If these dont work. find out yourself. 
      {
      case 16716015://case 2351064443: // '4' = turn left
          targetDirection=IRLeft;
        break;
      case 16718055: // case 1033561079: // '2' = straight on
           targetDirection=IRForward;
        break;
      case 16734885: // 71952287: // '6' = turn right
           targetDirection=IRRight;
        break; 
      case 16730805:  // 465573243: // '8' = backwards 
           targetDirection=IRBack;
        break; 
      case 16726215: // 1217346747: // '5' = stop
           targetDirection=IRStop;
        break;  
        
      case 16769055: //4034314555: // '-' = slow down
           decreaseSpeed();
        break;
      case 16754775: //2747854299: // '+' = speed up
           increaseSpeed();
        break;  
      }
      
      changeState=10*currentDirection + targetDirection; // check this out!
      changeDirection(changeState); // gradualy change speed of the motors, in ordor to not to stress te motor-connections withe the frame.
                                    // if you are sure your connection is very firm, you can skip the changeDirection procedure.

      moveDirection(targetDirection);
      currentDirection=targetDirection;
    }
    
    buttonPressed=0;
    moveDirection(currentDirection);
    
} // loop

boolean safe2Proceed(){
  if (USaan){
  unsigned int distance=sonar.convert_cm(sonar.ping_median(5)); // to get a reliable value. Default this will use a Timer interrupt, wich may be conflicting with either other libraries
                                                                // or the default Arduino code. If you have a compiler, or rather, linking errors containing 'vect_7',
                                                                // set the USE_TIMER directive in newping.h to false.
                                                                // Thank you, Tim Eckel, the author of this library.
  if (distance==0){
    digitalWrite(greenLED,LOW); // you could change this command with assembler code, much faster. I.E. : "PORTB &= ~(1 << 4);" But I doubt you will ever notice the speed change.
                                // Code size will change to decrease the amount, however. Same goes for other digitalWrite or digitalRead procedures.
    return true;
  }
  else{
    digitalWrite(greenLED,HIGH); // "PORTB |= (1 << 4);"
    signalLed(distance); 
    return (distance>safeDistance);
    }
  }
}

void signalLed(int dist){
  int index=0;
  if(dist<=48){
    index = ((dist-48)/-4); // linear equatation based on desired result graph. Check it out yourself.
    analogWrite(redLED,23*index);// glowing to bright red scaled to 0..253. Index ranging between 0 and 11
  }
  else{
    digitalWrite(redLED,LOW);// Led off
  }
}

void changeDirection(int state){
  int speed=currentDCSpeed;
  switch (state){
      case FS:
        while (speed>=DCMinSpeed){
          speed-=SpeedStep;
          delay(10);
          moveOn(speed);
        }
      break;
      case SF:
        speed=DCMinSpeed;
        while (speed<=currentDCSpeed){
          speed+=SpeedStep;
          delay(10);
          moveOn(speed);
        }
      break;
      case BS:
        while (speed>=DCMinSpeed){
          speed-=SpeedStep;
          delay(10);
          moveBack(speed);
        }
      break;
      case SB:
        speed=DCMinSpeed;
        while (speed<=currentDCSpeed){
          speed+=SpeedStep;
          delay(10);
          moveBack(speed);
        }
      break;
      case LS:
        while (speed>=DCMinSpeed){
          speed-=SpeedStep;
          delay(10);
          moveLeft(speed);
        }
      break;
      case SL:
        speed=DCMinSpeed;
        while (speed<=currentDCSpeed){
          speed+=SpeedStep;
          delay(10);
          moveLeft(speed);
        }
      break;
      case RS:
        while (speed>=DCMinSpeed){
          speed-=SpeedStep;
          delay(10);
          moveRight(speed);
        }
      break;
      case SR:
        speed=DCMinSpeed;
        while (speed<=currentDCSpeed){
          speed+=SpeedStep;
          delay(10);
          moveRight(speed);
        }
      break;
      case FR:
        motorAChange(FS);
        motorAChange(SB);
      break;
      case FB:
        while (speed>DCMinSpeed){
          speed-=SpeedStep;
          delay(10);
          moveOn(speed);
        }
        while (speed<currentDCSpeed){
          speed+=SpeedStep;
          delay(10);
          moveBack(speed);
        }
      break;
      case FL:
        motorBChange(FS);
        motorBChange(SB);
      break;
      case RF:
        motorAChange(BS);
        motorAChange(SF);
      break;
      case BF:
         while (speed>DCMinSpeed){
          speed-=SpeedStep;
          delay(10);
          moveBack(speed);
        }
        while (speed<currentDCSpeed){
          speed+=SpeedStep;
          delay(10);
          moveOn(speed);
        }
      break;
      case LF:
        motorBChange(BS);
        motorBChange(SF);        
      break;
      case BL:
        motorAChange(BS);
        motorAChange(SF);
      break;
      case BR:
        motorBChange(BS);
        motorBChange(SF);
      break;
      case LB:
        motorBChange(LS);
        motorBChange(SB);
      break;
      case RB:
        motorAChange(RS);
        motorAChange(SB);
      break;
      case RL:
        while (speed>DCMinSpeed){
          speed-=SpeedStep;
          delay(10);
          moveRight(speed);
        }
        while (speed<currentDCSpeed){
          speed+=SpeedStep;
          delay(10);
          moveLeft(speed);
        }
      break;
      case LR:
        while (speed>DCMinSpeed){
          speed-=SpeedStep;
          delay(10);
          moveLeft(speed);
        }
        while (speed<currentDCSpeed){
          speed+=SpeedStep;
          delay(10);
          moveRight(speed);
        }
      break;
  }
}

void moveDirection(int dir){ // Implementation of the FSM controlling your direction according to the IRdevice. 
                             // I use a 'switch' structure, which is fast and can be readily understood.
                             // in parallel the results of the US device (watchit) ar taken in consideration.
    switch (dir) {           // target state
      case IRForward:        // straight on
        USaan=true;
        if (watchit){
          changeDirection(FR);
          currentDirection = IRForwardAvoid;
        }
        else{
          currentDirection=IRForward;
          moveOn(currentDCSpeed);
        }
    break;
    case IRForwardAvoid:
        if (!watchit){
          changeDirection(RF);
          currentDirection = IRForward;
          moveOn(currentDCSpeed);
        }
        else {
          currentDirection= IRForwardAvoid;
          moveRight(currentDCSpeed);
          }
    break;    
    case IRBack:
        USaan=false;
        digitalWrite(greenLED,LOW);
        currentDirection=IRBack;
        moveBack(currentDCSpeed);
        break;
    case IRLeft:
        USaan=true;
        if(watchit){
          changeDirection(LF);
          currentDirection=IRLeftAvoid;
        }
        else{
          currentDirection=IRLeft;
          moveLeft(currentDCSpeed);
        }
    break;
    case IRLeftAvoid:
        if (!watchit){
          currentDirection=IRLeft;
          moveLeft(currentDCSpeed);
        } 
        else{
          currentDirection=IRLeftAvoid;
          moveOn(currentDCSpeed);
        }
    break;    
    case IRRight:
        USaan=true;
        if(watchit){
          changeDirection(RL);
          currentDirection=IRRightAvoid;
        }
        else{
          currentDirection=IRRight;
          moveRight(currentDCSpeed);
          }
    break;
    case IRRightAvoid:
        if(!watchit){
          currentDirection=IRRight;
          changeDirection(LR);
          moveRight(currentDCSpeed);
        }
        else{
          currentDirection=IRRightAvoid;
          moveLeft(currentDCSpeed);
          }
    break;    
    case IRStop:
        USaan=true;
        if(watchit){
          changeDirection(SB);
          currentDirection=IRStopAvoid;
          }
        else{
          currentDirection=IRStop;
          moveNot(currentDCSpeed);
          }
    break;
    case IRStopAvoid:
        if(!watchit){
          changeDirection(BS);
          currentDirection=IRStop;
          moveNot(currentDCSpeed);
        }    
        else{
          currentDirection=IRStopAvoid;
          moveBack(currentDCSpeed);
        }
    break;   
    }
  }
void decreaseSpeed(){
  if (currentDCSpeed>(SpeedStep-1)){
    currentDCSpeed -=SpeedStep; 
    }
}
void increaseSpeed(){
  if (currentDCSpeed<DCMaxSpeed-SpeedStep){
    currentDCSpeed +=SpeedStep;
    }
}

void moveOn(int speed){ // currentspeed
  digitalWrite(a1,LOW);  digitalWrite(a2,HIGH); analogWrite(Ena,speed); // the digitalWrite functions can be replaced by register calls, Faster and take up less memory.
  digitalWrite(b1,LOW);  digitalWrite(b2,HIGH); analogWrite(Enb,speed); // But again I doubt if it is noticable in speed. It will in program size.
}

void moveBack(int speed){
  digitalWrite(a1,HIGH);  digitalWrite(a2,LOW); analogWrite(Ena,speed); // the Arduino softwae is a bit of a hog on memory space.
  digitalWrite(b1,HIGH);  digitalWrite(b2,LOW); analogWrite(Enb,speed); // I don't know enough of the analogWrite procedure to give any advice, except from, DON'T tinker with it.
}

void moveLeft(int speed){
  digitalWrite(a1,LOW);  digitalWrite(a2,HIGH); analogWrite(Ena,speed); // because it relies heavily on the scarce Timer resources of the Arduino Uno.
  digitalWrite(b1,HIGH); digitalWrite(b2,LOW);  analogWrite(Enb,speed);
}

void moveRight(int speed){
  digitalWrite(a1,HIGH); digitalWrite(a2,LOW);  analogWrite(Ena,speed);
  digitalWrite(b1,LOW);  digitalWrite(b2,HIGH); analogWrite(Enb,speed);
}

void moveNot(int speed){
  digitalWrite(a1,LOW);  digitalWrite(a2,LOW);
  digitalWrite(b1,LOW);  digitalWrite(b2,LOW);  
}

void motorAChange(int mode){
  int speed=currentDCSpeed;
    switch(mode){
      case ForwardStop:
      while (speed >DCMinSpeed){
        speed-=SpeedStep;
        delay(10);
        digitalWrite(a1,LOW); digitalWrite(a2,HIGH); analogWrite(Ena,speed);
      }
      break;
      case BackwardStop:
      while (speed > DCMinSpeed){
        speed-=SpeedStep;
        delay(10);
        digitalWrite(a1,HIGH); digitalWrite(a2,LOW); analogWrite(Ena,speed);
      }
      break;
      case StopForward:
      speed=DCMinSpeed;
      while (speed < currentDCSpeed){
        speed+=SpeedStep;
        delay(10);
        digitalWrite(a1,LOW); digitalWrite(a2,HIGH); analogWrite(Ena,speed);
      }      
      break;
      case StopBackward:
      speed=DCMinSpeed;
      delay(10);
      while (speed < currentDCSpeed){
        speed+=SpeedStep;
        digitalWrite(a1,HIGH); digitalWrite(a2,LOW); analogWrite(Ena,speed);
      }
      break;
  }
}
void motorBChange(int mode){
int speed=currentDCSpeed;
    switch(mode){
      case ForwardStop:
      while (speed >DCMinSpeed){
        speed-=SpeedStep;
        delay(10);
        digitalWrite(b1,LOW); digitalWrite(b2,HIGH); analogWrite(Enb,speed);
      }
      break;
      case BackwardStop:
      while (speed > DCMinSpeed){
        speed-=SpeedStep;
        delay(10);
        digitalWrite(b1,HIGH); digitalWrite(b2,LOW); analogWrite(Enb,speed);
      }
      break;
      case StopForward:
      speed=DCMinSpeed;
      while (speed < currentDCSpeed){
        speed+=SpeedStep;
        delay(10);
        digitalWrite(b1,LOW); digitalWrite(b2,HIGH); analogWrite(Enb,speed);
      }      
      break;
      case StopBackward:
      speed=DCMinSpeed;
      while (speed < currentDCSpeed){
        speed+=SpeedStep;
        delay(10);
        digitalWrite(b1,HIGH); digitalWrite(b2,LOW); analogWrite(Enb,speed);
      }
      break;
  }  
}

unsigned long getRemoteSignal(void){ // This is copied from the example. IF you have another kind of IR device test and debug. use plenty of Serial.print...
                                     // Hopefully you will only need different values in your lookup table. See the library documentation
                                     // I think it would be a bad idea to transform all this into an Interrupt Service Routine. Takes too much performance time...
  unsigned long retval=0;
  if (irrecv.decode(&results)){
    int teller =0;           // originally global
    boolean notfound = true; // originally global
    if (results.decode_type == -1) // due to initialy very confusing errors/faults, I discovered the type -1 resluted in consistent button values. 
                                   // I do both now, one of them does the job ...
    {
      while (teller< nobuttons && notfound) 
      {
        unsigned long button=results.value;
        if (button==ff_code[teller])
        { 
          retval=button;
          notfound = false;
        }
        teller++;
      }
    }
    if (results.decode_type == 3) // DEC???
    {
      while (teller< nobuttons && notfound) 
      {
        unsigned long button=results.value;
        if (button==Button_DEC_code[teller])
        { 
          retval=button;
          notfound = false;
        }
        teller++;
      }
    }
    if (notfound)
      {
        String button=String(results.value, HEX);
        if (button.substring(0,6)!="ffffff") 
        {
          button.toUpperCase();
        }
      }
    irrecv.resume(); // Receive the next value
  }
  return retval;
}

Credits

JanHendrikStam

JanHendrikStam

0 projects • 0 followers

Comments