Hardware components | ||||||
![]() |
| × | 1 | |||
![]() |
| × | 1 | |||
| × | 1 | ||||
![]() |
| × | 1 | |||
![]() |
| × | 1 | |||
| × | 1 | ||||
Software apps and online services | ||||||
![]() |
| |||||
Hand tools and fabrication machines | ||||||
![]() |
|
As a novice in the Arduino world, I saw a clip about the strandbeest and decided to make my own.
A little history.
Controlled by IR remote as well as proximity sensor (ultrasound). Primary control being the proximity sensor as a start.
Then I started to test the various components, and each worked fine. At first I used a couple of stepper motors, but the are a hog on the Arduino´s digital pins, en move verryyy sllloowwwlyyy. So on to the dc motors in combination with the PWM faclitiies provided by Arduino.
Then I met some unexpected problems-issues-challenges.
The two libraries I used appeared to be conflicting. I got some rather vague message about some vector7 issue. With thanks to tim Eckel: Switch the compiler directive in the headerfile "NewPing.h" 'TIMER' to off.
Great. Sketch compiles and uploads and works.
Then the next issue: PWM wasn't working correctly/at some of the pins I used.
Eventually I did not use pins 3 and 11. Both in use by some 'standard' interrupts and/or timers of the Arduino. This explains the rather weird use of the pins in my scetch.
But when all these problems had been overcome, it all worked, glory and praise be...
The most difficult part was the coupling of the dc motors and the axisses of the strandbeest. I did not want to damage or change the strandbeest in any way. So a combination of duct tape and shrimp socks would have to do. Still the weakest part in the design. (I have have very little money to spend, being a low level piano teacher.)
motor board. left motor and right motor are self explaining. diver wires: at the bottom.
Just above them the controller wire between Arduino and this board. inner ones for the polarty, outer ones for the speed control.
#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;
}
Comments
Please log in or sign up to comment.