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) ;
 }
 }
 }
}

7 Responses to Electric remote controlled Tiger 1 Tank toy + arduino

  1. hummer says:

    I have a calan hummer h2 which does not have a remote control, makes engine noise when turned on, makes engine noise, horn sound and music when going back and forth, can I use it for this?

    • trandi says:

      So what this hack does is it connects an Arduino to the existing TX5 ATS305T board on that toy, thus using any remote an Arduino can read (in this case either a standard TV one or another from a toy helicopter) to control it (and replacing the missing original remote).

      To be able to reproduce, your toy would need to use the same TX5 ATS305T board, which I have no clue if it does.
      If not, you should still be able to hack it, but you’d need your own motor controllers to connect to the Arduino.
      See other hacks on my blog, like this other toy tank: https://trandi.wordpress.com/2011/05/14/tiger-1-bb-airsoft-rc-tank-%e2%80%93-v3/

  2. Jens Kristian Jensen says:

    Got a newer 2.4GHz of these that I want to mod. Did you manage to take it apart without Breaking any plastic? I can only see two small screws on the bottom, near the drivewheels.

    • trandi says:

      It’s been 9 years since this post… 🙂 but I don’t remember having any issues with taking it apart.

      • Jens Kristian Jensen says:

        Found three screws under some glued covers on the top. After that disassembly was easy. The 2.4GHz PCB looks very different from yours. Cannot find datasheets on the ICs, but hopefully I will be able to measure some PWM signals as I operate it…

      • trandi says:

        Great to hear it was reasonably easy to pull apart. Don’t hesitate to post a few pictures here, I’m not curious to see how the new version looks like ?

        On Wed, 15 May 2019 at 15:49, Robotics / Electronics / Physical Computing wrote:

        >

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

Leave a comment