Thursday 26 December 2013

The homework paid off...

So all that hard work tinning up wires, fixing on binding posts and cable-shrink worked nicely. I've ended up with a well wired and neat central section to the railway that should prove reliable.

I've included photos below. Sensors, points and track are all wired in and bundled separately.




Sunday 10 November 2013

Tram all wired in and lots of "homework"...

If you want to see the fun bit first then here's a video:


I've been working away quietly these last few weekends. Doing a lot of soldering and hacking about to get the basic control systems wired into place prior to fixing the electronics in and doing the basic wiring on the rest of the layout.

I've focused a lot recently on the wiring of the core part of the layout around the station. This part has to be done first because it's where all the electronics are and has the denses wiring so I want to get that done over at my workbench as a unit and carry it all across to the layout and plonk it in. Unfortunately it means a lot (LOTS) of soldering and tinning wires nicely.

You can see all the wires I've prepared to solder to the rails for power. One end raw and tinned up to attach to teh track, the other end with a nice post soldered on with some heat-shrink to plug the wire into a socket for eventual connection back to the controllers. 

 
An example of how it connects to the rails below...

And now for the tram. Basically, the track is in place and is wired up including the sensors. You can see them in the photos below.


And here's the whole track...

And now for the movie...The system is fully automated and run by Arduino. It's a basic program that pauses the tram at each station (ends of the line) for about 10 seconds.


 

Sunday 29 September 2013

Using Implantable RFID Tags to identify trains

I think I'd like to be able to identify which train is which on my layout without having to type in numbers and so forth. I've decided to put a single RFID reader on the track somewhere and run all trains past it at the start of my sessions so the computers can work out what is where automagically.

I bought these nifty and tiny RFID tags from LittleBird.



And this RFID reader with a nice TTL UART for serial transmission.

Here's the extremely simple test circuit I built using an Arduino Nano to control it all...

The antenna is the red wiring loop on the top right. The Arduino is the blue board bottom left.

Sample code is below.



//
// This code implements a state machine to read a serial stream from pin 3
// for s simple 125KHz RFID reader:
//    http://www.seeedstudio.com/wiki/index.php?title=125Khz_RFID_module_-_UART
//
//  
//

#include <SoftwareSerial.h>

typedef enum RFIDProtStateType { initialising,
                            waiting_good, 
                            waiting_error, 
                            started, 
                            reading_digits, 
                            reading_cksum, 
                            waiting_finish, 
                            finished };
// a global representing the current command state
RFIDProtStateType  RFIDProtState;

boolean  GotRFID;

char  RFIDTag[10];
char  Checksum[2];

int  index;

#define  RFIDProtRxPin       3
#define  RFIDProtTxPin       2

SoftwareSerial RFIDProtSerial =  SoftwareSerial(RFIDProtRxPin, RFIDProtTxPin);


void
setup()
{
  Serial.begin(9600);

  RFIDProtSerial.begin(9600);

  index = 0;
  
  RFIDProtState = waiting_good;
  GotRFID = false;
}

//
// Convert a string containing 2 bytes representing a number in HEX into a hex byte
// ...so a string containin "A1" will be returned as 0xA1
//
byte
StringHexToByte(char *in)
{
  byte  result;
  int  j;
  
  result = 0;
  
  for(j = 0; j < 2 ; j ++)
  {
    if( in[j] >= 'A')
    {
      result |= (in[j] - 'A' + 10) << 4*(1-j);
    }
    else
    {
      result |= (in[j] - '0') << 4*(1-j);
    }
  }
  
  return result;
}


//
//
// Handle Char will process a message coming in of the form:
//
//     <0x02><10 hex digit ID tag><2 digit xor checksum><0x03>
//
// errors show up on pin 13 (with LED)
//
void
HandleCharacter(char c)
{
      int  i,j;
      byte  cksum = 0x00;
      byte  digit;
         
      switch(RFIDProtState)
      {
       case waiting_error:
       case waiting_good:
          if(c == 0x02)
          {
            // we now have the start character
            RFIDProtState = started;
            index = 0;
          }
          else
          {
            RFIDProtState = waiting_error;
          }         
          break;
          
        case started:
        case reading_digits:
          if(c >= '0')
          {
            RFIDProtState = reading_digits;
            RFIDTag[index++] = c;
            if(index == 10)
            {
              RFIDProtState = reading_cksum;
              index = 0;
            }  
          }
          else
          {
            RFIDProtState = waiting_error;
          }
          break;
          
        case reading_cksum:
          Checksum[index++] = c;
          
          if( index == 2)
          {
            // we now have the 2 bytes of checksum read in
            index = 0;
            cksum = 0;
             
            // calculate the checksum from the string of digits
            for(i=0; i< 5;i++)
            {
              digit = StringHexToByte(&RFIDTag[i*2]);
              cksum ^= digit;           
            }
                    
            if (cksum != StringHexToByte(Checksum))
              RFIDProtState = waiting_error;
            else
              RFIDProtState = waiting_finish;
          }  
          break;
          
        case waiting_finish:
          if(c == 0x03)
          {
            RFIDProtState = finished;
          }
          else
          {
            RFIDProtState = waiting_error;
            index = 0;
          }
          //break;
          // Note no break here - fall through and handle finishing
        case finished:
          index = 0;
          
          GotRFID = true;
          
          RFIDProtState = waiting_good;
          
          break;
      }

}

void
GetCommand()
{
  char  c;
  
    if(RFIDProtSerial.available())
    {
      c = RFIDProtSerial.read();
      HandleCharacter(c);
    }
    if (RFIDProtState == waiting_error)  
      digitalWrite(13, HIGH);
    else
      digitalWrite(13, LOW);
}


void
loop()
{
  
  GetCommand();
  
  if(GotRFID)
  {
    int  i;
    
    for(i = 0; i < 10; i++)
      Serial.print(RFIDTag[i]);
      
    Serial.println();
    
    GotRFID = false;
  }
  

}

Sunday 22 September 2013

Servo Driven Points - System Developed!!

I've finished version 1 of the software to control the points. All you need to drive the system now is a single wire driving RS232 at TTL levels. It can even be driven by a simple terminal program. The command is in the form:

S00000000F

Where a 0 => set the point to straight and a 1 => set the point to turn.

The software runs a really nice little state machine to ensure robust communications. I may want to put an ACK on the serial line potentially (taking it to 2 lines to control the system.

You can see the setup in this video:

The code is below (needs comments!).

//
// Points controller using Pololu serial servo controller
// Chris Mendes 2013
// 
//
// Software Serial from Arduino example by Tom Igoe
//
// Pololu code borrowed from Mike Crist...
//   put() (now position_absolute) & set_speed() functions found on pololu forums & modified
//   mike crist 2009-01-03

#include <SoftwareSerial.h>

#define  ServoRxPin     2
#define  ServoTxPin     3
#define  ServoResetPin  4

// Pins for the command port
#define  CmdRxPin       5
#define  CmdTxPin       6

#define  DebugModePin  12


typedef enum CmdStateType {initialising, waiting_good, waiting_error, started, reading_digits, waiting_finish, finished };

CmdStateType  CmdState;

#define  NUM_POINTS    8

byte  PointAngles[NUM_POINTS][2] = { {88,94}, {86,94}, {94,86}, {85,94}, {97, 85}, {109, 96}, {93, 74}, {97, 79}};

byte  NextCommand[NUM_POINTS];
byte  Command[NUM_POINTS];
byte  LastCommand[NUM_POINTS];

byte  CmdBit;
boolean  GotCommand;

int  big_loop;
#define  LOOP_RESET  100000


// set up a new serial port
SoftwareSerial softSerial =  SoftwareSerial(ServoRxPin, ServoTxPin);

// Set Up the Command Port
SoftwareSerial CmdSerial =  SoftwareSerial(CmdRxPin, CmdTxPin);


void setup()
{
  int  i;
  int  pos;
  
  // set the data rate for the hardware serial port
  Serial.begin(9600);
  
  big_loop = 0;
  CmdState = initialising;
  
  pinMode(DebugModePin, INPUT);


  //Reset the servo controller
  pinMode(ServoResetPin, OUTPUT);
  digitalWrite(ServoResetPin, LOW);
  delay(20);
  digitalWrite(ServoResetPin, HIGH);
  

  pinMode(ServoRxPin, INPUT);
  digitalWrite(ServoTxPin, HIGH);
  pinMode(ServoTxPin, OUTPUT);

  // set the data rate for the SoftwareSerial port
  softSerial.begin(38400);
  
  for (i = 0 ; i < 8 ; i++ )
  {
    set_speed(i, 5);
    //delay(100);
    set_neutral(i, 3000);
    //delay(100);
    set_servo_parameters(i, 0, 15);
    //delay(100);
    // Move all points to position 0 - straight
    pos = PointAngles[i][0];
    position_8bit(i,(byte)map(pos,0,179,0,255));
  }
  //wait for them all to move, then turn them off
  delay(3000);
  for (i = 0 ; i < 8 ; i++ )
  {
    set_servo_parameters(i, 0, 15);
  }
  

  pinMode(CmdRxPin, INPUT);
  digitalWrite(CmdTxPin, HIGH);
  pinMode(CmdTxPin, OUTPUT);
  CmdSerial.begin(9600);
  CmdState = waiting_good;
  
  memset(Command,0x00,NUM_POINTS);
  memset(LastCommand,0x00,NUM_POINTS);
  memset(NextCommand,0x00,NUM_POINTS);

  CmdBit = 0;
  GotCommand = false;
}

void
HandleBit(char c)
{
  byte  TempBit = 0x00;
  
  TempBit = c - '0';

  NextCommand[CmdBit] = TempBit;

  CmdBit++;
}

void
HandleCharacter(char c)
{
      switch(CmdState)
      {
       case waiting_error:
       case waiting_good:
          if(c == 'S')
          {
            CmdState = started;
            CmdBit = 0;
          }
          else
          {
            CmdState = waiting_error;
          }
          if (CmdState == waiting_error)
            digitalWrite(13,HIGH);
          else
            digitalWrite(13,LOW);
          
          break;
          
        case started:
        case reading_digits:
          if((c == '0') or (c == '1'))
          {
            CmdState = reading_digits;
            HandleBit(c);
            if(CmdBit == 8)
            {
              CmdState = waiting_finish;
              CmdBit = 0;
            }  
          }
          else
          {
            CmdState = waiting_error;
          }
          break;
        case waiting_finish:
          if(c == 'F')
          {
            CmdState = finished;
          }
          else
          {
            CmdState = waiting_error;
            CmdBit = 0;
            memset(NextCommand, 0x00, NUM_POINTS);
          }
          //break;
          // Note no break here - fall through and handle finishing
        case finished:
          CmdBit = 0;
          
          GotCommand = true;
          
          memcpy(LastCommand, Command, NUM_POINTS);
          memcpy(Command, NextCommand, NUM_POINTS);
          memset(NextCommand, 0x00, NUM_POINTS);
          
          CmdState = waiting_good;
          
          break;
      }

}
void
GetCommand()
{
  char  c;
  
  if(digitalRead(DebugModePin))
  {
    if(Serial.available())
    {
      c = Serial.read();
      HandleCharacter(c);
    }
  }
  else
  {
    if(CmdSerial.available())
    {
      c = CmdSerial.read();
      HandleCharacter(c);
    }
  }
  
}


void loop()
{
  big_loop++;
  
  GetCommand();

  if(GotCommand == true)
  {
    int  i;
    
    for(i = 0; i < NUM_POINTS; i++)
    {
      if(LastCommand[i] != Command[i])
      {
        int  pos;
        
        pos = PointAngles[i][(Command[i])];
        if(digitalRead(DebugModePin))
        {
          Serial.print("Switch ");
          Serial.print(i);
          Serial.print(" Set To ");
          Serial.print(Command[i]);
          Serial.print(" Angle ");
          Serial.print(pos);
          Serial.println();       
        }
        
        //move it!
        position_8bit(i,(byte)map(pos,0,179,0,255));
        // TURN THE SERVO OFF AGAIN TO LOWER CURRENT DRAW
        // set_servo_parameters(i, 0, 15);


      }
      else
      {
        // motor is now idle so turn it off
        set_servo_parameters(i, 0, 15);
      }
    }
    GotCommand = false;
    memcpy(LastCommand,Command,NUM_POINTS);
    big_loop = 0;
  }

  if(big_loop == LOOP_RESET)
  {
    int  i;
    
    big_loop = 0;
    
    for (i = 0; i < NUM_POINTS; i++)
    {
        // motor is now idle so turn it off
        set_servo_parameters(i, 0, 15);
    }
    
  }

}

void set_servo_parameters(byte servo, byte OnOff, byte rangeVal)
{
   //this function uses pololu mode command 0 to set servo parameters
   //servo is the servo number (typically 0-7)
   //rangeVal of 15 gives 180 deg in 8-bit, 90 deg in 7 bit
   
   byte temp;
   byte parameters;
   
   temp = OnOff << 6;                     //set first two bits of parameters (on = 1, forward = 0)
   temp = temp + (rangeVal & 0x1f);   //put first five bits of rangeVal into temp
   parameters = temp & 0x7f;          //take only bottom 7 bits
      
   //Send a Pololu Protocol command
   softSerial.write(0x80);       //start byte
   softSerial.write(0x01);       //device id
   softSerial.write((byte)0x00);       //command number
   softSerial.write(servo);      //servo number
   softSerial.write(parameters); //parameters
   
}

void set_speed(byte servo, byte speedVal)
{
   //this function uses pololu mode command 1 to set speed
   //servo is the servo number (typically 0-7)
   //speedVal is servo speed (1=slowest, 127=fastest, 0=full)
   //set speedVal to zero to turn off speed limiting
   
   speedVal = speedVal & 0x7f; //take only lower 7 bits of the speed
   
   //Send a Pololu Protocol command
   softSerial.write(0x80);     //start byte
   softSerial.write(0x01);     //device id
   softSerial.write(0x01);     //command number
   softSerial.write(servo);    //servo number
   softSerial.write(speedVal); //speed

}

void position_7bit(byte servo, byte posValue)
{
  //this function uses pololu mode command 2 to set position  
  //servo is the servo number (typically 0-7)
  //posValue * range (set with command 0) adjusted by neutral (set with command 5)
  //determines servo position

   byte pos = posValue & 0x7f;     //get lower 7 bits of position

   //Send a Pololu Protocol command
   softSerial.write(0x80);     //start byte
   softSerial.write(0x01);     //device id
   softSerial.write(0x02);     //command number
   softSerial.write(servo);    //servo number
   softSerial.write(pos);     //position
}

void position_8bit(byte servo, byte posValue)
{
  //this function uses pololu mode command 3 to set position  
  //servo is the servo number (typically 0-7)
  //posValue * range (set with command 0) adjusted by neutral (set with command 5)
  //determines servo position

   byte temp;
   byte pos_hi,pos_low;
   
   temp = posValue & 0x80;      //get bit 8 of position
   pos_hi = temp >> 7;            //shift bit 8 by 7
   pos_low = posValue & 0x7f;     //get lower 7 bits of position

   //Send a Pololu Protocol command
   softSerial.write(0x80);     //start byte
   softSerial.write(0x01);     //device id
   softSerial.write(0x03);     //command number
   softSerial.write(servo);    //servo number
   softSerial.write(pos_hi);  //bits 8 thru 13
   softSerial.write(pos_low); //bottom 7 bits
}

void position_absolute(byte servo, int angle)
{
  //this function uses pololu mode command 4 to set absolute position  
  //servo is the servo number (typically 0-7)
  //angle is the absolute position from 500 to 5500

   unsigned int temp;
   byte pos_hi,pos_low;
   
   temp = angle & 0x1f80;  //get bits 8 thru 13 of position
   pos_hi = temp >> 7;     //shift bits 8 thru 13 by 7
   pos_low = angle & 0x7f; //get lower 7 bits of position

   //Send a Pololu Protocol command
   softSerial.write(0x80);    //start byte
   softSerial.write(0x01);    //device id
   softSerial.write(0x04);    //command number
   softSerial.write(servo);   //servo number
   softSerial.write(pos_hi);  //bits 8 thru 13
   softSerial.write(pos_low); //bottom 7 bits
}

void set_neutral(byte servo, int angle)
{
  //this function uses pololu mode command 5 to set neutral position  
  //servo is the servo number (typically 0-7)
  //angle is the absolute position from 500 to 5500

   unsigned int temp;
   byte pos_hi,pos_low;
   
   temp = angle & 0x1f80;  //get bits 8 thru 13 of position
   pos_hi = temp >> 7;     //shift bits 8 thru 13 by 7
   pos_low = angle & 0x7f; //get lower 7 bits of position

   //Send a Pololu Protocol command
   softSerial.write(0x80);    //start byte
   softSerial.write(0x01);    //device id
   softSerial.write(0x05);    //command number
   softSerial.write(servo);   //servo number
   softSerial.write(pos_hi);  //bits 8 thru 13
   softSerial.write(pos_low); //bottom 7 bits
}

Sunday 15 September 2013

All servo's mounted...

So as per my last post, I've finally mounted and calibrated all servo motors for my Points. These motors have been mounted using Franc Carter's 3D printed servo brackets.

You can see them in the photo below:




Now, to complete the software for the point controller. Then the real work begins - proper circuit layout and wiring.

I'll also have to wire in the lights, motor controllers and sensors for the main rail network and the tram.

In the video below you can see one of the points in motion.

Pololu Micro Serial Servo Controller

I've finally got all my Points bolted in place and I'm now driving them from a Pololu Micro Serial Servo Controller. Take a look at the Pololu web page here.

These are really nice pieces of hardware - they can drive up to 8 servos from a single tiny board.

I'm using the software serial library to communicate with the Pololu. I've found that you have to explicitly control the reset on the device otherwise it's unpredictable and often bombs out on power up.

This simple circuit drives a single servo based on reading an input from a potentiometer. I'm restricting the angle quite narrowly because the sweep between 2 n-scale rails is only 9mm or so.

Here's my rough prototype board:



And here's the source code...

//
// Pololu micro serial servo controller modes
//
// Software Serial from Arduino example by Tom Igoe
//
// Pololu code borrowed from Mike Crist...
//   put() (now position_absolute) & set_speed() functions found on pololu forums & modified
//   mike crist 2009-01-03

#include <SoftwareSerial.h>

#define  ServoRxPin     2
#define  ServoTxPin     3
#define  ServoResetPin  4

// Pins for later use ;-)
#define  CmdRxPin       5
#define  CmdTxPin       6

int  old_d;

// set up a new serial port
SoftwareSerial softSerial =  SoftwareSerial(ServoRxPin, ServoTxPin);

void setup()
{
  int  i;
  
  // set the data rate for the hardware serial port
  Serial.begin(9600);

  pinMode(ServoResetPin, OUTPUT);
  digitalWrite(ServoResetPin, LOW);
  delay(20);
  digitalWrite(ServoResetPin, HIGH);
  

  pinMode(ServoRxPin, INPUT);

  digitalWrite(ServoTxPin, HIGH);

  pinMode(ServoTxPin, OUTPUT);

  // set the data rate for the SoftwareSerial port
  softSerial.begin(38400);
  
  for (i = 0 ; i < 8 ; i++ )
  {
    set_speed(i, 3);
    //delay(100);
    set_neutral(i, 3000);
    //delay(100);
    set_servo_parameters(i, 15);
    //delay(100);
      
  }
  
  old_d = 0;
}



void loop()
{
  int  v,d;

/*  Here's the bit reading the pot - commented out for now...
  v = analogRead(0);
  d = map(v,0,1023,70,110);

  if( d != old_d )
  {
    old_d = d;
    
    position_8bit(1,(byte)map(d,0,179,0,255));
  
    Serial.print(v);
    Serial.print(" , ");
    Serial.println(d);
  }
  delay(10);
*/
//
// I'm driving a single point repeatedly now to hammer it!!
//
    position_8bit(1,(byte)map(94,0,179,0,255));
    delay(5000);
    position_8bit(1,(byte)map(85,0,179,0,255));
    delay(5000);
    

}

void set_servo_parameters(byte servo, byte rangeVal)
{
   //this function uses pololu mode command 0 to set servo parameters
   //servo is the servo number (typically 0-7)
   //rangeVal of 15 gives 180 deg in 8-bit, 90 deg in 7 bit
   
   byte temp;
   byte parameters;
   
   temp = 1 << 6;                     //set first two bits of parameters (on = 1, forward = 0)
   temp = temp + (rangeVal & 0x1f);   //put first five bits of rangeVal into temp
   parameters = temp & 0x7f;          //take only bottom 7 bits
      
   //Send a Pololu Protocol command
   softSerial.write(0x80);       //start byte
   softSerial.write(0x01);       //device id
   softSerial.write((byte)0x00);       //command number
   softSerial.write(servo);      //servo number
   softSerial.write(parameters); //parameters
   
}

void set_speed(byte servo, byte speedVal)
{
   //this function uses pololu mode command 1 to set speed
   //servo is the servo number (typically 0-7)
   //speedVal is servo speed (1=slowest, 127=fastest, 0=full)
   //set speedVal to zero to turn off speed limiting
   
   speedVal = speedVal & 0x7f; //take only lower 7 bits of the speed
   
   //Send a Pololu Protocol command
   softSerial.write(0x80);     //start byte
   softSerial.write(0x01);     //device id
   softSerial.write(0x01);     //command number
   softSerial.write(servo);    //servo number
   softSerial.write(speedVal); //speed

}

void position_7bit(byte servo, byte posValue)
{
  //this function uses pololu mode command 2 to set position  
  //servo is the servo number (typically 0-7)
  //posValue * range (set with command 0) adjusted by neutral (set with command 5)
  //determines servo position

   byte pos = posValue & 0x7f;     //get lower 7 bits of position

   //Send a Pololu Protocol command
   softSerial.write(0x80);     //start byte
   softSerial.write(0x01);     //device id
   softSerial.write(0x02);     //command number
   softSerial.write(servo);    //servo number
   softSerial.write(pos);     //position
}

void position_8bit(byte servo, byte posValue)
{
  //this function uses pololu mode command 3 to set position  
  //servo is the servo number (typically 0-7)
  //posValue * range (set with command 0) adjusted by neutral (set with command 5)
  //determines servo position

   byte temp;
   byte pos_hi,pos_low;
   
   temp = posValue & 0x80;      //get bit 8 of position
   pos_hi = temp >> 7;            //shift bit 8 by 7
   pos_low = posValue & 0x7f;     //get lower 7 bits of position

   //Send a Pololu Protocol command
   softSerial.write(0x80);     //start byte
   softSerial.write(0x01);     //device id
   softSerial.write(0x03);     //command number
   softSerial.write(servo);    //servo number
   softSerial.write(pos_hi);  //bits 8 thru 13
   softSerial.write(pos_low); //bottom 7 bits
}

void position_absolute(byte servo, int angle)
{
  //this function uses pololu mode command 4 to set absolute position  
  //servo is the servo number (typically 0-7)
  //angle is the absolute position from 500 to 5500

   unsigned int temp;
   byte pos_hi,pos_low;
   
   temp = angle & 0x1f80;  //get bits 8 thru 13 of position
   pos_hi = temp >> 7;     //shift bits 8 thru 13 by 7
   pos_low = angle & 0x7f; //get lower 7 bits of position

   //Send a Pololu Protocol command
   softSerial.write(0x80);    //start byte
   softSerial.write(0x01);    //device id
   softSerial.write(0x04);    //command number
   softSerial.write(servo);   //servo number
   softSerial.write(pos_hi);  //bits 8 thru 13
   softSerial.write(pos_low); //bottom 7 bits
}

void set_neutral(byte servo, int angle)
{
  //this function uses pololu mode command 5 to set neutral position  
  //servo is the servo number (typically 0-7)
  //angle is the absolute position from 500 to 5500

   unsigned int temp;
   byte pos_hi,pos_low;
   
   temp = angle & 0x1f80;  //get bits 8 thru 13 of position
   pos_hi = temp >> 7;     //shift bits 8 thru 13 by 7
   pos_low = angle & 0x7f; //get lower 7 bits of position

   //Send a Pololu Protocol command
   softSerial.write(0x80);    //start byte
   softSerial.write(0x01);    //device id
   softSerial.write(0x05);    //command number
   softSerial.write(servo);   //servo number
   softSerial.write(pos_hi);  //bits 8 thru 13
   softSerial.write(pos_low); //bottom 7 bits
}