Electric remote controlled Tiger 1 Tank toy + arduino


It all started when I found this ex-display toy in a store, for “only” 30£ (rather than the initial 90£), because it was missing its remote…

Wow, something cheap and “not working” is the perfect excuse to open and hack it straight away…

Look at the 2 motors and the nice black gearbox… this tank must be at least 3-5 times more powerful than my previous “miniature” one !

Luckily it uses the TX5 ATS305T micro controller for the remote control, which turns out to be quite easily hackable too, as per the excerpts from the specifications.

1st Step : remote controlled mode

Here’s the code simply listening for IR commands and transforming them into commands for the motors

#define P_LEFT_FWD 5
#define P_RIGHT_FWD 6
#define P_LEFT_BCK 7

#define P_TURRET_L 9
#define P_TURRET_R 10

#define P_UP 2
#define P_FIRE 3

unsigned int mPrevSpeed = 9999;
int mPrevDir = 9999;

#define MIN_SPEED 50

void setup(){
 ir_setup();

 pinMode(P_LEFT_FWD, OUTPUT);
 pinMode(P_LEFT_BCK, OUTPUT);
 pinMode(P_RIGHT_FWD, OUTPUT);
 pinMode(P_TURRET_L, OUTPUT);
 pinMode(P_TURRET_R, OUTPUT);
 pinMode(P_UP, OUTPUT);
 pinMode(P_FIRE, OUTPUT);
}

void loop(){
 while(! ir_receiveCommands(5000)){
 }

 unsigned int speed = ir_getSpeed();
 int dir = ir_getDir();
 if((speed != mPrevSpeed) || (dir != mPrevDir)){
 mPrevSpeed = speed;
 mPrevDir = dir;

 updateSpeed();
 }
}

void updateSpeed(){
 motors(mPrevSpeed + mPrevDir, mPrevSpeed - mPrevDir);
}

void motors(int speedL, int speedR){
 analogWrite(P_LEFT_FWD, validateSpeed(speedL));
 analogWrite(P_RIGHT_FWD, validateSpeed(speedR));
}

byte validateSpeed(int speed){
 if(speed <= 0) return 0;
 int result = speed + MIN_SPEED;
 if(result >= 255) result = 254;
 return result;
}

void motorsStop(){
 digitalWrite(P_LEFT_FWD, LOW);
 digitalWrite(P_RIGHT_FWD, LOW);
 digitalWrite(P_LEFT_BCK, LOW);
}

void turret(int duration){
 if(duration < 0){
 analogWrite(P_TURRET_L, 100);
 }else{
 analogWrite(P_TURRET_R, 100);
 }

 delay(abs(duration));

 // stop everything
 digitalWrite(P_TURRET_L, LOW);
 digitalWrite(P_TURRET_R, LOW);
}

void upDown(){
 digitalWrite(P_UP, HIGH);
 delay(2000);
 digitalWrite(P_UP, LOW);
}

void fire(){
 analogWrite(P_FIRE, 75);
 delay(1000);
 digitalWrite(P_FIRE, LOW);
}
<pre>
<pre>

And here’s some updated code for the Syma s026 RC Helicopter infra red remote control.

// More RELIABLE version using TIMERS directly, rathen than pulseIn()
// PWM on PIN 9 and 10 will be DISABLED

const byte IRpin = 12;
const byte SIGNAL_SIZE = 29;
const byte SIGNAL_SPEED = 7;
const byte SIGNAL_DIR = 6;

const unsigned int PULSE_START = 1600;
const unsigned int PULSE_0_MIN = 400;
const unsigned int PULSE_0_MAX = 600;
const unsigned int PULSE_1_MIN = 800;
const unsigned int PULSE_1_MAX = 1000;

const byte LEFT_RIGHT_MIDDLE = 64;

unsigned int mSpeed;
unsigned int mDir;

unsigned int tempLastPulse;
int tempSpeed;
int tempDir;
int tempBit;

void ir_setup(){
 pinMode(IRpin, INPUT);

 setTimingConf();
}

unsigned int ir_getSpeed(){
 return mSpeed;
}

int ir_getDir(){
 return mDir - LEFT_RIGHT_MIDDLE;
}

boolean ir_receiveCommands(unsigned long timeout){
 unsigned long startTime = micros();
 while(getLowPulse(IRpin, timeout) < PULSE_START){
 if(micros() - startTime > timeout) return false;
 }

 tempSpeed = 0;
 for(int i=SIGNAL_SPEED; i>0; i--){
 tempBit = getBit(getLowPulse(IRpin, timeout));
 if(tempBit < 0) return false;
 if(tempBit == 1) tempSpeed += bit(i);
 }

 tempDir = 0;
 for(int i=SIGNAL_DIR; i>0; i--){
 tempBit = getBit(getLowPulse(IRpin, timeout));
 if(tempBit < 0) return false;
 if(tempBit == 1) tempDir += bit(i);
 }

 mSpeed = tempSpeed;
 mDir = tempDir;

 return true;
}

int getBit(unsigned int pulse){
 if(pulse > PULSE_0_MIN && pulse < PULSE_0_MAX) return 0;
 if(pulse > PULSE_1_MIN && pulse < PULSE_1_MAX) return 1;
 return -1;
}

//int asInt(byte start, byte end){
//  int tempResult = 0;
//  for(int i=start; i<end; i++){
//    int bit = getBit(mPulses[i]);
//    if(bit < 0) return -1;
//    if(bit == 1) tempResult += bit(end - i - 1);
//  }
//
//  return tempResult;
//}

/* TIMING STUFF*/

int getLowPulse(byte pin, unsigned int timeoutMicroSecs){
 unsigned int timeout = timeoutMicroSecs / 4;

 TCNT1 = 0; // timer reset
 // wait until it gets LOW or timesout
 while(digitalRead(pin) == HIGH && TCNT1 < timeout) {}

 // time out
 if(TCNT1 >= timeout) return -1;

 // start conting the pulse
 TCNT1 = 0; // timer reset
 while(digitalRead(pin) == LOW && TCNT1 < timeout) {}

 // pulse too long
 if(TCNT1 >= timeout) return -2;

 return TCNT1 * 4; // counts in 4microSecs increments
}

void setTimingConf(){
 TCCR1A = 0x00;  // COM1A1=0, COM1A0=0 =Disconnect Pin OC1 from Timer/Counter 1 -- PWM11=0,PWM10=0 = PWM Operation disabled
 // ICNC1=0 = Capture Noise Canceler disabled -- ICES1=0 =Input Capture Edge Select (not used) -- CTC1=0 =Clear Timer/Counter 1 on Compare/Match
 // CS12=0 CS11=1 CS10=1 =Set prescaler to clock/64
 TCCR1B = 0x03;  // 16MHz clock with prescaler means TCNT1 increments every 4uS
 // ICIE1=0 = Timer/Counter 1, Input Capture Interrupt Enable -- OCIE1A=0 =Output Compare A Match Interrupt Enable -- OCIE1B=0 =Output Compare B Match Interrupt Enable
 // TOIE1=0 =Timer 1 Overflow Interrupt Enable
 TIMSK1 = 0x00;
}
<pre>
<pre>

2nd Step : programmed in C# (.NET Micro Framework) on the FEZ Domino board

Program.cs

using System;
using System.Threading;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using GHIElectronics.NETMF.FEZ;
using GHIElectronics.NETMF.Hardware;

namespace ToyTank
{
 class CONSTANTS
 {
 public const FEZ_Pin.AnalogIn SENSOR_IR_PIN = FEZ_Pin.AnalogIn.An0;
 public const FEZ_Pin.Interrupt SENSOR_US_PIN = FEZ_Pin.Interrupt.Di1;
 public const FEZ_Pin.PWM TRACK_LEFT_PIN = FEZ_Pin.PWM.Di6;
 public const FEZ_Pin.PWM TRACK_RIGHT_PIN = FEZ_Pin.PWM.Di5;
 public const FEZ_Pin.Digital TRACK_RIGHT_BACK_PIN = FEZ_Pin.Digital.Di7;
 public const FEZ_Pin.Interrupt IR_CMD_PIN = FEZ_Pin.Interrupt.Di13;
 public const FEZ_Pin.Digital FIRE_SENSOR = FEZ_Pin.Digital.Di11;
 public const FEZ_Pin.Digital FIRE_MOTOR = FEZ_Pin.Digital.Di10;
 public const FEZ_Pin.PWM TURRET_LEFT_PIN = FEZ_Pin.PWM.Di8;
 public const FEZ_Pin.PWM TURRET_RIGHT_PIN = FEZ_Pin.PWM.Di9;

 public const FEZ_Pin.Interrupt IR_BEACON_WIDE_PIN = FEZ_Pin.Interrupt.Di3;
 public const FEZ_Pin.Interrupt IR_BEACON_NARROW_PIN = FEZ_Pin.Interrupt.Di2;
 }

 class Program
 {
 Distance _distance = new Distance();
 Tank _tank = new Tank();
 IR_CMD_Syma_s026 _irCmd;
 bool _autoPilot = false;
 IRBeacon _wideBeacon;
 IRBeacon _narrowBeacon;

 Program()
 {
 _irCmd = new IR_CMD_Syma_s026(CONSTANTS.IR_CMD_PIN, IRCmdReceived);
 //_wideBeacon = new IRBeacon(CONSTANTS.IR_BEACON_WIDE_PIN, IRWideBeaconCodeReceived);
 //_narrowBeacon = new IRBeacon(CONSTANTS.IR_BEACON_NARROW_PIN, IRNarrowBeaconCodeReceived);

 //_tank.Turret(-50);
 }

 void IRWideBeaconCodeReceived(int code)
 {
 Debug.Print("Wide Beacon Code: " + code);
 }

 void IRNarrowBeaconCodeReceived(int code)
 {
 Debug.Print("Narrow Beacon Code: " + code);

 _tank.Turret(0);
 }

 void IRCmdReceived(int speed, int dir, int vertical, bool leftButton, bool rightButton, int trimmer)
 {
 Debug.Print(speed + " - " + dir + " - " + vertical + " - " + leftButton + " / " + rightButton + " - " + trimmer);

 // update AutoPilot mode
 if (leftButton)
 {
 _autoPilot = !_autoPilot;
 Debug.Print("AutoPilot: " + _autoPilot);
 }

 if (! _autoPilot)
 {
 _tank.Tracks(speed - dir, speed + dir);
 _tank.Turret(vertical * 7);

 if (rightButton) _tank.Fire();
 }
 }

 void AutoPilot()
 {
 while (true)
 {
 if (_autoPilot)
 {
 int speedPerc = _distance.Read_cm();

 if (speedPerc <= 30)
 {
 _tank.Tracks(0, 0);
 Thread.Sleep(2000);
 _tank.Turret(50);
 Thread.Sleep(2000);
 _tank.Turret(-50);
 Thread.Sleep(2000);
 _tank.Turret(0);
 Thread.Sleep(2000);
 _tank.Fire();
 Thread.Sleep(2000);

 _tank.Tracks(0, -1); //full turn right
 while (_distance.Read_cm() < 50) Thread.Sleep(30);
 }
 else
 {
 if (speedPerc > 30 && speedPerc < 50)
 {
 int delta = (50 - speedPerc) * 3;
 _tank.Tracks(speedPerc - delta, speedPerc + delta);
 }
 else
 {
 _tank.Tracks(speedPerc, speedPerc);
 }
 }
 }
 Thread.Sleep(100);
 }
 }

 //Main ENTRY POINT into the program !
 public static void Main()
 {
 try
 {
 new Program().AutoPilot();
 //new Program();

 Thread.Sleep(Timeout.Infinite);
 }
 catch (Exception e)
 {
 Debug.Print(e.StackTrace);
 }
 }
 } // End Class Program
}

Tank.cs

using System;
using System.Threading;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using GHIElectronics.NETMF.FEZ;
using GHIElectronics.NETMF.Hardware;

namespace ToyTank
{
 class Tank
 {
 PWM _trackLeft = new PWM((PWM.Pin)CONSTANTS.TRACK_LEFT_PIN);
 PWM _trackRight = new PWM((PWM.Pin)CONSTANTS.TRACK_RIGHT_PIN);
 OutputPort _trackRightBack = new OutputPort((Cpu.Pin)CONSTANTS.TRACK_RIGHT_BACK_PIN, false);

 PWM _turretLeft = new PWM((PWM.Pin)CONSTANTS.TURRET_LEFT_PIN);
 PWM _turretRight = new PWM((PWM.Pin)CONSTANTS.TURRET_RIGHT_PIN);

 // at the end of fire, the switch is activated and takes that pin to GROUND
 InputPort _fireSensor = new InputPort((Cpu.Pin)CONSTANTS.FIRE_SENSOR, true, Port.ResistorMode.PullDown);
 OutputPort _fireMotor = new OutputPort((Cpu.Pin)CONSTANTS.FIRE_MOTOR, false);

 public Tank()
 {
 _trackLeft.Set(false);
 _trackRight.Set(false); // MANDATORY, BEFORE we set it backwards otherwise we'll burn the transistors !
 _trackRightBack.Write(false);
 _turretLeft.Set(false);
 _turretRight.Set(false);
 _fireMotor.Write(false);
 }

 public void Turret(int perc)
 {
 bool left = perc < 0;
 perc = System.Math.Abs(perc);
 if (perc > 100) perc = 100;

 if (left)
 {
 _turretRight.Set(false);
 _turretLeft.Set(1000, (byte)perc);
 }
 else
 {
 _turretLeft.Set(false);
 _turretRight.Set(1000, (byte)perc);
 }
 }

 public void Tracks(int leftPerc, int rightPerc)
 {
 if (leftPerc < 0) leftPerc = 0;
 if (rightPerc < 0) rightPerc = 0;
 if (leftPerc > 100) leftPerc = 100;
 if (rightPerc > 100) rightPerc = 100;

 _trackLeft.Set(1000, (byte)leftPerc);
 _trackRight.Set(1000, (byte)rightPerc);
 }

 public void Fire()
 {
 new Thread(FireThread).Start();
 }

 void FireThread()
 {
 _fireMotor.Write(true); //start the motor
 Debug.Print("FIRED   " + _fireSensor.Read());
 while (! _fireSensor.Read()) ; //wait until the switch is pressed
 Thread.Sleep(300);
 _fireMotor.Write(false); //stop the motor
 Debug.Print("STOP FIRED   ");
 }
 }

 class Distance
 {
 FEZ_Components.SharpIRDistanceSensor _IRSensor = new FEZ_Components.SharpIRDistanceSensor(CONSTANTS.SENSOR_IR_PIN, FEZ_Components.SharpIRDistanceSensor.SharpSensorType.GP2D120);
 FEZ_Components.SRF05USDistanceSensor _USSensor = new FEZ_Components.SRF05USDistanceSensor(CONSTANTS.SENSOR_US_PIN);

 public int Read_cm()
 {
 // use only the Ultrasound for now, as more accurate...
 int dist = _USSensor.GetDistance_cm(580000); // max 1000 cm
 for (int i = 0; i < 5 && dist < 0; i++) dist = _USSensor.GetDistance_cm(580000); //RE-TRY max 5 times
 if (dist < 0) dist = 1000;
 return dist;
 }

 }
}

IR_Cmd.cs

using System;
using System.Threading;
using System.Collections;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using GHIElectronics.NETMF.FEZ;
using GHIElectronics.NETMF.Hardware;

namespace ToyTank
{
 /*
 * Ranges are:
 * 0 to 220
 * -62 to 62
 * -14 to 14
 * 2 to 60
 */
 public delegate void IRCmdDelegate(int speed, int dir, int vertical, bool leftButton, bool rightButton, int trimmer);

 class IR_CMD_Syma_s026 : IDisposable
 {
 const byte SIGNAL_SIZE = 26; // MAX size is 29 !
 public const byte SIGNAL_SPEED = 7;//from 0 to here
 public const byte SIGNAL_DIR = 13;//from end of SignalSpeed to here
 public const byte SIGNAL_BUTTONS = 17;//from end of SignalDir to here
 public const byte SIGNAL_VERTICAL = 21;//from end of SignalButtons to here
 public const byte SIGNAL_TRIMMER = 26;//from end of SignalVertical to here
 const int PULSE_START_MIN = 1650;
 const int PULSE_START_MAX = 1750;
 const int PULSE_0_MIN = 450;
 const int PULSE_0_MAX = 600;
 const int PULSE_1_MIN = 850;
 const int PULSE_1_MAX = 1000;

 const byte SPEED_OFFSET = 80;
 const byte LEFT_RIGHT_MIDDLE = 64;
 const byte VERTICAL_MIDDLE = 16;
 const byte VALUE_LEFT_BUTTON = 22;
 const byte VALUE_RIGHT_BUTTON = 26;

 private readonly InterruptPort _ir;
 private readonly IRCmdDelegate _callback;

 public IR_CMD_Syma_s026(FEZ_Pin.Interrupt ir_pin, IRCmdDelegate callbackWhenCmdReceived)
 {
 _ir = new InterruptPort((Cpu.Pin)ir_pin, false, Port.ResistorMode.Disabled, Port.InterruptMode.InterruptEdgeBoth);
 _ir.OnInterrupt += new NativeEventHandler(IR_OnInterrupt);

 _callback = callbackWhenCmdReceived;

 // start the executor thread. ONLY 1 thread, rather than creating and killing them...
 new Thread(CallbackExecutor).Start();
 }

 public void Dispose()
 {
 _ir.Dispose();
 }

 private long _startPulseTicks = -1;
 private int _pulseCount = -1;
 private long[] _pulses = new long[SIGNAL_SIZE];
 private long[] _prevPulses = new long[SIGNAL_SIZE];
 private bool _interruptContinue = true;

 private void IR_OnInterrupt(uint port, uint state, DateTime time)
 {
 // if not -999, then the executor thread hasn't yet dealt with the previous value, there's no point in providing a new one...
 lock (_prevPulses) _interruptContinue = (_prevPulses[0] == -999);
 if (!_interruptContinue) return;

 if (state == 0)
 {// interested in LOW pulses
 _startPulseTicks = time.Ticks;
 }
 else if(_startPulseTicks > 0)
 {// if high AND a pulse is started
 long pulse = (time.Ticks - _startPulseTicks) / 10; // from ticks to microSeconds
 _startPulseTicks = -1; // will wait for the next start

 if (pulse > PULSE_START_MIN && pulse < PULSE_START_MAX)
 {//start counting pulses
 _pulseCount = 0;
 }
 else if (_pulseCount >= 0)
 {//continue counting pulses
 _pulses[_pulseCount] = pulse;
 _pulseCount++;

 // FINAL, we've read what we wanted let our creator know !
 if (_pulseCount == SIGNAL_SIZE)
 {
 // clone the original array as other interrupts might come and need the original array
 // needs to lock as the Executor thread accesses this too...
 lock(_prevPulses) _prevPulses = (long[])_pulses.Clone();

 // end of this command, so reInit so that we wait for another start
 _pulseCount = -1;
 }
 }
 }
 }

 // Simply monitor _prevPulses for changes and call the calback. Will implicitly ignore intermediary changes to pulses,
 // there's no need to check for new pulses until the previous callback method has finished.
 private void CallbackExecutor()
 {
 bool callback = false;
 byte[] bits, prevBits = null;

 while (true)
 {
 callback = false;
 bits = null;

 // needs lock as the interrupt thread accesses this too...
 lock(_prevPulses)
 {
 // user first element as flag (if _prevPulses[0] == -999 -> nothing has been received)
 if (_prevPulses[0] != -999)
 {
 bits = GetBits(_prevPulses);
 _prevPulses[0] = -999;//mark that we've read these pulses, the interrupt can go on put new ones
 }
 }

 // do the callback ONLY if the new information is different...
 if (!DeepEquals(bits, prevBits)) callback = true;

 // if bits == null = we had a bad pulse, we'll have to start from scratch
 if (callback && bits != null)
 {
 new Thread(delegate { NewFullSignalReceived(bits); }).Start();
 //NewFullSignalReceived(bits);
 prevBits = (byte[])bits.Clone();
 }

 Thread.Sleep(10);
 }
 }

 private void NewFullSignalReceived(byte[] bits)
 {
 //Debug.Print(toStr(bits));

 int speed = 0, dir = 0, buttons = 0, vertical = 0, trimmer = 0;
 for (int i = 0; i < bits.Length; i++)
 {
 if (bits[i] == 1)
 {
 if (i < SIGNAL_SPEED) speed += (1 << (SIGNAL_SPEED - i));
 else if (i < SIGNAL_DIR) dir += (1 << SIGNAL_DIR - i);
 else if (i < SIGNAL_BUTTONS) buttons += (1 << SIGNAL_BUTTONS - i);
 else if (i < SIGNAL_VERTICAL) vertical += (1 << SIGNAL_VERTICAL - i);
 else if (i < SIGNAL_TRIMMER) trimmer += (1 << SIGNAL_TRIMMER - i);
 }
 }

 speed = speed - SPEED_OFFSET;
 if (speed < 0) speed = 0;
 _callback(speed, dir - LEFT_RIGHT_MIDDLE, vertical - VERTICAL_MIDDLE, buttons == VALUE_LEFT_BUTTON, buttons == VALUE_RIGHT_BUTTON, trimmer);
 }

 private static String toStr(Object arg)
 {
 if (arg == null || !arg.GetType().IsArray) throw new ArgumentException("Need to receive array");

 Array array = arg as Array;
 String result = "[";
 foreach (Object elem in array) result += elem + ", ";
 result = result.Substring(0, result.Length - 2) + "]";
 return result;
 }

 private static byte[] GetBits(long[] pulses)
 {
 byte[] result = new byte[pulses.Length];
 for (int i = 0; i < pulses.Length; i++)
 {
 int bit = GetBit(pulses[i]);
 if (bit < 0) return null;
 result[i] = (byte)bit;
 }
 return result;
 }

 private static int GetBit(long pulse)
 {
 //Debug.Print(pulse.ToString());
 if(pulse > PULSE_0_MIN && pulse < PULSE_0_MAX) return 0;
 if(pulse > PULSE_1_MIN && pulse < PULSE_1_MAX) return 1;
 return -1;
 }

 private static bool DeepEquals(byte[] a1, byte[] a2)
 {
 if (a1 == null) return a2 == null;
 if (a2 == null) return a1 == null;
 if (a1.Length != a2.Length) return false;
 for (int i = 0; i < a1.Length; i++)
 {
 if (a1[i] != a2[i]) return false;
 }

 return true;
 }

 } // END IR_Syma_s026
}

IR_Beacon.cs


using System;
using System.Threading;
using System.Collections;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using GHIElectronics.NETMF.FEZ;
using GHIElectronics.NETMF.Hardware;

namespace ToyTank
{
 public delegate void IRBeaconDelegate(int code);

 class IRBeacon : IDisposable
 {
 const byte SIGNAL_SIZE = 8;
 const int T = 560;        // in microseconds
 const int DELTA = 300;    // in microseconds
 const int T_MIN = T - DELTA;
 const int T_MAX = T + DELTA;
 const int T3_MIN = 3*T_MIN;
 const int T3_MAX = 3*T_MAX;
 const int START_MIN = 8*T_MIN;
 const int START_MAX = 8*T_MAX;

 private readonly InterruptPort _ir;
 private readonly IRBeaconDelegate _callback;

 public IRBeacon(FEZ_Pin.Interrupt ir_pin, IRBeaconDelegate callbackWhenCodeReceived)
 {
 _ir = new InterruptPort((Cpu.Pin)ir_pin, false, Port.ResistorMode.Disabled, Port.InterruptMode.InterruptEdgeBoth);
 _ir.OnInterrupt += new NativeEventHandler(IR_OnInterrupt);

 _callback = callbackWhenCodeReceived;

 // start the executor thread. ONLY 1 thread, rather than creating and killing them...
 new Thread(CallbackExecutor).Start();
 }

 public void Dispose()
 {
 _ir.Dispose();
 }

 private long _startPulseTicks = -1;
 private int _pulseCount = -1;
 private long[] _pulses = new long[SIGNAL_SIZE];
 private long[] _prevPulses = new long[SIGNAL_SIZE];
 private bool _interruptContinue = true;

 private void IR_OnInterrupt(uint port, uint state, DateTime time)
 {
 // if not -999, then the executor thread hasn't yet dealt with the previous value, there's no point in providing a new one...
 lock (_prevPulses) _interruptContinue = (_prevPulses[0] == -999);
 if (! _interruptContinue) return;

 if (state == 1)
 {// interested in HIGH pulses
 _startPulseTicks = time.Ticks;
 }
 else if (_startPulseTicks > 0)
 {// if high AND a pulse is started
 long pulse = (time.Ticks - _startPulseTicks) / 10; // from ticks to microSeconds
 _startPulseTicks = -1; // will wait for the next start

 if (pulse > START_MIN && pulse < START_MAX)
 {//start counting pulses
 _pulseCount = 0;
 }
 else if (_pulseCount >= 0)
 {//continue counting pulses
 _pulses[_pulseCount] = pulse;
 _pulseCount++;

 // FINAL, we've read what we wanted let our creator know !
 if (_pulseCount == SIGNAL_SIZE)
 {
 lock (_prevPulses) _prevPulses = (long[])_pulses.Clone();

 // end of this command, so reInit so that we wait for another start
 _pulseCount = -1;
 }
 }
 }
 }

 // Simply monitor _prevPulses for valid data and call the calback. Will implicitly ignore intermediary changes to pulses,
 // there's no need to check for new pulses until the previous callback method has finished.
 private void CallbackExecutor()
 {
 int code = -2;
 while (true)
 {
 // needs lock as the interrupt thread accesses this too...
 lock (_prevPulses)
 {
 // user first element as flag (if _prevPulses[0] == -999 -> nothing has been received)
 if (_prevPulses[0] != -999)
 {
 code = GetCode(_prevPulses);
 _prevPulses[0] = -999;//mark that we've read these pulses, the interrupt can go on put new ones
 }
 }

 if (code > 0) _callback(code); // ignore invalid codes = errors or interference

 Thread.Sleep(300);//go slowly, it's not a high priority this one...
 }
 }

 private int GetCode(long[] pulses)
 {
 int code = 0;
 for (int i = 0; i < pulses.Length; i++ )
 {
 int bit = GetBit(pulses[i]);
 if (bit < 0) return -1;
 if (bit == 1) code += 1 << i;
 }
 return code;
 }

 private static int GetBit(long pulseSize)
 {
 if ((pulseSize > T_MIN) && (pulseSize < T_MAX)) return 0;
 if ((pulseSize > T3_MIN) && (pulseSize < T3_MAX)) return 1;
 return -1;
 }
 } // End class IRBeacon
}

DistanceDetector.cs


using System;
using System.Threading;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using GHIElectronics.NETMF.Hardware;
using GHIElectronics.NETMF.FEZ;

namespace GHIElectronics.NETMF.FEZ
{
 public static partial class FEZ_Components
 {
 public class SharpIRDistanceSensor : IDisposable
 {
 private AnalogIn _irInput;

 float Y0 = 0;
 float X0 = 0;
 float Y1 = 0;
 float X1 = 0;
 float C = 0;

 public enum SharpSensorType : byte
 {
 GP2Y0A21YK = 0,
 GP2D120 = 1,
 }

 public void Dispose()
 {
 _irInput.Dispose();
 }

 public SharpIRDistanceSensor(FEZ_Pin.AnalogIn pin, SharpSensorType type)
 {
 _irInput = new AnalogIn((AnalogIn.Pin)pin);
 _irInput.SetLinearScale(0, 330);

 switch (type)
 {
 case SharpSensorType.GP2Y0A21YK:
 Y0 = 10;
 X0 = 315;
 Y1 = 80;
 X1 = 30;
 break;

 case SharpSensorType.GP2D120:
 Y0 = 3;
 X0 = 260;
 Y1 = 30;
 X1 = 45;
 break;
 }
 C = (Y1 - Y0) / (1 / X1 - 1 / X0);
 }

 private float ReadDistance()
 {
 return C / ((float)_irInput.Read() + (float).001) - (C / X0) + Y0;
 }

 public int GetDistance_cm()
 {
 int result = (int)((ReadDistance() + ReadDistance() + ReadDistance()) / 3);
 if (result > 50) result = -2; // too far to be accurate
 else if (result < 3) result = -1; // too close to be accurate
 return result; // _irInput.Read();
 }
 }//SharpIRDistanceSensor

 public class SRF05USDistanceSensor : IDisposable
 {
 private TristatePort _usInput; // needs to be interrupt capable

 public SRF05USDistanceSensor(FEZ_Pin.Interrupt pin)
 {
 _usInput = new TristatePort((Cpu.Pin)pin, true, false, Port.ResistorMode.Disabled);
 }

 public void Dispose()
 {
 _usInput.Dispose();
 }

 // need these methods to avoid throwing an exception
 private void MakePortOutput()
 {
 if (_usInput.Active == false) _usInput.Active = true;
 }

 // need these methods to avoid throwing an exception
 private void MakePortInput()
 {
 if (_usInput.Active == true) _usInput.Active = false;
 }

 public int GetDistance_cm(int timeoutTicks)
 {
 // 1. send a TRIGGER high 10microS pulse
 MakePortOutput();
 _usInput.Write(false);
 Sleep_us(2);
 _usInput.Write(true);
 Sleep_us(10);
 _usInput.Write(false);

 // 2. wait for the response
 // The speed of sound is 340 m/s or 29 microseconds per centimeter.
 // The ping travels out and back, so to find the distance of the object we take half of the distance travelled.
 MakePortInput();
 long start = DateTime.Now.Ticks;
 while (!_usInput.Read() && DateTime.Now.Ticks - start < timeoutTicks) ; // wait until it's HIGH or timeout
 if (DateTime.Now.Ticks - start >= timeoutTicks) return -1;
 start = DateTime.Now.Ticks;
 while (_usInput.Read() && DateTime.Now.Ticks - start < timeoutTicks) ; // count the HIGH pulse OR timeout
 if (DateTime.Now.Ticks - start >= timeoutTicks) return -2;
 return (int)(DateTime.Now.Ticks - start) / 580; // divide bt 2x29 for us to cm and by 10 for tick to us
 }

 private static void Sleep_us(int microSecs)
 {
 long end = DateTime.Now.Ticks + microSecs * 10;
 while (DateTime.Now.Ticks < end) ;
 }
 }
 }
}
Advertisements

One Response to Electric remote controlled Tiger 1 Tank toy + arduino

  1. Pingback: Tiger 1 BB airsoft RC Tank « Robotics / Electronics / Physical Computing

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: