Saturday 31 December 2011

Layout Option 1

Here's a new layout idea based  roughly on my previous one but extended quite a bit.

I contemplated building a helix to raise my track but did not have to - I got land, baby. I've rversed a few things - note that the inner track now goes up instead of the previous case where the outer track descended.

That inner track will now mostly be exposed and is also double height at 12cm above the deck - I'm planning a lovely viaduct for almost half the length of the layout.

The green section will be raised and all the track will actually run under it except for the tram (blue track).

The tram will be built using Tomix tram track which I will coat with textured cobblestone paper.

The town will be built along the tram line on the green section.

Would love your views, folks.

Here's a track manifest, for the record:


Friday 30 December 2011

The Hobby Room Takes Shape

So we've moved in and got most of the immediate home maintenance done - painting and, of course, a CAT-6 network to all main rooms of the house.

The media centre is in, but no projector yet. Still, it can wait. I'm feeling pretty gun shy with my wallet now. Just had to lash out on a new 4-stroke engine of death to control the back garden...

Anyway, I have found some time to set up my new layout in my new hobby room!!

Here's an image for you all...
I'm standing right near my work table in a corner. You can just see the top of my monitor in the foreground. In the far corner is the layout - just a table for now. It's an L shaped layout. The long edge is 2m and the return is 1.2m long and 0.6m wide. The main part of the L is 0.8m wide.

I've been sent a lovely gift by Markus - a track planning book - just what the doctor ordered. I'll be taking tips from there and enhancing the ideas I've already published as I now have roughly double the area available.

I've also discovered that Tomix makes beautiful snap together tram tracks with a minimum 150mm radius curve - also just what I need.

Oh well, I guess it has begun at last!!

Sunday 11 December 2011

Arduino Competitor?

http://www.tinkerforge.com/

https://shop.tinkerforge.com/bricks-1/master-brick.html#

Friday 2 December 2011

A mighty track plan site.

http://www.freetrackplans.com/

Oh Dear, a new Miniatur Wunderland Video

Just in time for Christmas...




There are moments in this video when you can be forgiven for thinking it was not a model.

And a life line from a guest author

After my last post a friend of mine shared his collection of Marklin track plans with me on DropBox to tide me over the move. Nothing like German Train Porn. Truly hard core.

Markus is now a guest author on my blog. Let's see what he's up to in Marklin HO. Are you building a model railroad? Let me know if you want to share!

Thursday 1 December 2011

The bags are packed...well sort of.

We're nearly ready to move out of our current place and into the new one (with the train room)! I've been contemplating making an a larger train layout now that I have my own space for it so I'll be scanning the web for ideas.

Most of my hobby stuff is now packed into shipping boxes so all my activities will have to be on-line. Just noticed a nice little Arduino board with built in Ethernet: Etherten

Apart from the model railroad, the new house will need a sprinkler system and I'm thinking of this as a candidate for driving it. After all, it's just a model railroad with water instead of trains surely?

Monday 17 October 2011

Slow going...

Folks, just a brief update to let you know that I have not abandoned this blog. I'm in the process of trying to sell my house so I have no space to work in at the moment. The house has to be pristine for the purposes of selling it.


However, the new house I will be moving into has a man-cave that will become my dedicated model railway room! You can see it right at the bottom of the house under the stairway.


So, expect lots of new stuff soon. As soon as we sell this place. But then, even more when I have my own train room!!

Wednesday 28 September 2011

My First PCB

Now that I've prototyped a large shift register on my breadboard, I've decided to make a PCB version of the board for use on my model railroad.

I'm going to construct this as 3x16 bit I/O cards that can be daisy chained together to give me a total of 48 bits of digital I/O.

I'll be creating the cards so that I can easily add different output modules - some with ULN2803's for smaller devices <0.5A current, some with 60A MOSFETS for serious loads.

In order to test the system, I have made some simple 8 bit LED display boards which will plug into the shift register board as a test harness. I've made these so that they can easily be used in a breadboard as well.

The reason I've made these first is that they are very low risk. I designed them using the freeware version of Eagle from CadSoft.

The PCBs were easy to design after watching the YouTube tutorials - plenty available. I uploaded my PCB design to SeeedStudio and got my boards back in about 10 days.

As you can see, I'm allowing a total of 8 lines plus power and ground in to my LED Debug Board. I've also included a single LED to indicate power so that you can tell that things are actually working.

The schematic appears below:


And the PCB layout is here:




After about 10 days, I got my boards:


And here it is fully populated - worked first go!


Sunday 18 September 2011

New Arduino micro-controllers shake their groove thing!

Can you imagine a 96 MHz clocked ARM controlling your model railway?

http://blog.makezine.com/archive/2011/09/new-arduinos-announced-32-bit-entry-level-and-more.html?utm_source=pulsenews&utm_medium=referral&utm_campaign=Feed%3A+makezineonline+%28MAKE%29

Tuesday 13 September 2011

Daleks !

Just for laughs, I picked up a blue police box on eBay in N-scale.

In order to make this really work for me, I got a friend of mine to use his new UP! Personal Portable 3D printer to print me some N-scale Daleks. It's still standing on the platform it's printed on.

I did a (Very Rough) paint job today. Photos below...





Saturday 10 September 2011

Bipolar Stepper Motors - making them work.

An opportunity came to hande recently - an old PC was scrapped and it had a DVD drive in ti. I took the chance to totally disassemble it and discovered a lovely stepper motor with gearing and so forth to make it into a linear actuator.



The problem was, how to drive the beast. Firstly, it was obviously a stepper motor as it had > 2 leads coming from it. How was it wired up, though. Well, firstly I got a voltmeter and worked out that it was simply to 20 ohm coils. This means it was a dreaded bipolar stepper - harder to drive than unipolar.

I decided to have a go. I soldered on some power leads and hooked it up to my DFRobot L298 Motor shield. Then, I had to write the code.

It turns out that there is no ready source code for this handy. Basically, the motor has to be driven by a dual h-bridge arrangement working the coils using digital square waves that go between +5 and -5V through 0V. This can be seen roughly as sine waves. They waves need to be about 90 degress out of phase.


Basically, the DFRobot board allows you to control the speed and direction of each motor only. I built a function to set speeed and direction of each motor based on the steps 0->7 in the diagrams. Each step waas a function. I put these functions into an array of function pointers and simply called them in order to get movement. Stepping back throught the table gave reverse motion.

Here's the simple code:

//Arduino PWM Speed Control:
int E1 = 6;  
int M1 = 7;
int E2 = 5;                        
int M2 = 4;  

#define  DELAY  2
#define  PWR    200

void
MotorsOff()
{
    analogWrite(E1, 0);   //PWM Speed Control
    analogWrite(E2, 0);   //PWM Speed Control
 
}

void
Step0()
{
    // 0
    digitalWrite(M1,HIGH);  
    analogWrite(E1, PWR);   //PWM Speed Control

    digitalWrite(M2, HIGH);      
    analogWrite(E2, 0);   //PWM Speed Control
    delay(DELAY);
}
void
Step1()
{
    // 1
    digitalWrite(M1,HIGH);  
    analogWrite(E1, PWR);   //PWM Speed Control

    digitalWrite(M2, HIGH);      
    analogWrite(E2, PWR);   //PWM Speed Control
    delay(DELAY);
}
void
Step2()
{
    // 2
    digitalWrite(M1,HIGH);  
    analogWrite(E1, 0);   //PWM Speed Control

    digitalWrite(M2, HIGH);      
    analogWrite(E2, PWR);   //PWM Speed Control
    delay(DELAY);
}
void
Step3()
{   
    // 3
    digitalWrite(M1,LOW);  
    analogWrite(E1, PWR);   //PWM Speed Control

    digitalWrite(M2, HIGH);      
    analogWrite(E2, PWR);   //PWM Speed Control
    delay(DELAY);
}
void
Step4()
{   
    // 4
    digitalWrite(M1,LOW);  
    analogWrite(E1, PWR);   //PWM Speed Control

    digitalWrite(M2, HIGH);      
    analogWrite(E2, 0);   //PWM Speed Control
    delay(DELAY);
}
void
Step5()
{   
     // 5
    digitalWrite(M1,LOW);  
    analogWrite(E1, PWR);   //PWM Speed Control

    digitalWrite(M2, LOW);      
    analogWrite(E2, PWR);   //PWM Speed Control
    delay(DELAY);
}
void
Step6()
{   
     // 6
    digitalWrite(M1,LOW);  
    analogWrite(E1, 0);   //PWM Speed Control

    digitalWrite(M2, LOW);      
    analogWrite(E2, PWR);   //PWM Speed Control
    delay(DELAY);
}
void
Step7()
{   
      // 7
    digitalWrite(M1,HIGH);  
    analogWrite(E1, PWR);   //PWM Speed Control

    digitalWrite(M2, LOW);      
    analogWrite(E2, PWR);   //PWM Speed Control
    delay(DELAY);
}

#define  NUM_STEPS  8

int *steps[NUM_STEPS] = {(int *)Step0,(int *)Step1,(int *)Step2,(int *)Step3,(int *)Step4,(int *)Step5,(int *)Step6,(int *)Step7};


void setup()
{
    pinMode(M1, OUTPUT);  
    pinMode(M2, OUTPUT);
}

void loop()
{
  int  i;
  void  (*j)();
 
  for (i = 0; i < 450; i++)
  {
   
    j = (void (*)())steps[i % NUM_STEPS];
   
    (*j)();
  }
 
  MotorsOff();
  delay(1000);
 
  for (i = 450; i > 0; i--)
  {
   
    j = (void (*)())steps[i % NUM_STEPS];
   
    (*j)();
  }

 
}

Wednesday 7 September 2011

48 bit Shift Register

One of the problems with running a model railroad from a microcontroller is the need for lots of digital output pins. There are a variety of things that need turning on and off - signals, track sections/blocks, house lights, even driving stepper motors!

This simple circuit uses 6 x 74LS595 chips to make a total of 48 bits of digital output using only 3 digital outs on my Arduino.

Here's video of it in action. I've hooked up my el-cheapo opto-isolated relay card to the lower 4 bits just to demonstrate it working.


You can see the entire setup above - the Arduino, the breadboard with the 6 chips and my relay board.
 Below you can see a little detail of the first few chips.

There is a really good Arduino tutorial here: http://www.arduino.cc/en/Tutorial/ShiftOut


The real pain in the a*#e is the amount of fiddly little wires I have had to cut, strip and place to make this work. It has to be made on a PCB before I do anything else!
And here is the source for my test program. I can feel an Arduino library coming one...


//Pin connected to ST_CP of 74HC595
int latchPin = 10;
//Pin connected to SH_CP of 74HC595
int clockPin = 11;
////Pin connected to DS of 74HC595
int dataPin = 9;

#define  BITS_PER_BYTE  8

#define  NUM_BYTES  4
byte  bitset[NUM_BYTES];

void
BitsetClear()
{
    int  i; 
   
    for (i = 0; i< NUM_BYTES; i++)
       bitset[i] = 0;
}
void
BitsetSet()
{
    int  i; 
   
    for (i = 0; i< NUM_BYTES; i++)
       bitset[i] = 0xFF;
}

void
BitsetSetBit(int  n)
{
    int  index;
    int  bit_num;
   
    index = n/BITS_PER_BYTE;
   
    bit_num = n % BITS_PER_BYTE;
   
    Serial.print("S: ");
    Serial.print(n);
    Serial.print(" : ");
    Serial.print(index);
    Serial.print(", ");
    Serial.println(bit_num);
   
    bitset[index] = bitset[index] | (0x01 << bit_num);
}

void
BitsetClearBit(int  n)
{
    int  index;
    int  bit_num;
   
    index = n/BITS_PER_BYTE;
   
    bit_num = n % BITS_PER_BYTE;
   
    Serial.print("C: ");
    Serial.print(n);
    Serial.print(" : ");
    Serial.print(index);
    Serial.print(", ");
    Serial.println(bit_num);
   
    bitset[index] = bitset[index] & ~(0x01 << bit_num);
}

void
BitsetDisplay()
{
    int  i; 
    for (i = NUM_BYTES; i > 0; i--)
    {
      digitalWrite(latchPin, LOW);
      // shift out the bits:
      shiftOut(dataPin, clockPin, MSBFIRST, bitset[i-1]); 
      //take the latch pin high so the LEDs will light up:
      digitalWrite(latchPin, HIGH);
     
      Serial.print(" ");
      Serial.print(bitset[i - 1], BIN );
     }
     Serial.println(".");
}



void setup() {
   
  Serial.begin(9600);
 
  //set pins to output so you can control the shift register
  pinMode(latchPin, OUTPUT);
  pinMode(clockPin, OUTPUT);
  pinMode(dataPin, OUTPUT);
 
  BitsetSet(); 
  BitsetDisplay();
}

void loop() {

  int  i;
 
  int  num_bits = 4;// NUM_BYTES * 8;

  for( i = 0; i < num_bits ; i++)
  {
    if (i==0)
    {
      BitsetSetBit(num_bits - 1);
      BitsetClearBit(i);
      BitsetDisplay();
    }
    else
    {
      BitsetSetBit(i - 1);
      BitsetClearBit(i);
      BitsetDisplay();
    }
    delay(200);
  }
}

Sunday 4 September 2011

Track Block Section Design

I'm now starting to think about how I can actually build my layout and, given no DCC, I need to use track block sections to control the trains. I've come up with a simple design and I've documented it below.
I'll need about 6 of these to control my planned layout. Darn - needs a bigger arduino with more sensors.


Arduino and PWM for model trains...

Noisy!
You may have noticed from my videos that the trains on my test layout make a hideous high pitched whine. This is due to the PWM (pulse width modulation) frequency used by the Arduino analogWrite function.


Solved
If you call this function that I found in the Arduino Playground, all is fixed: http://www.arduino.cc/playground/Code/PwmFrequency

Note that the default divisor for pins 5 & 6 is 64 so the timers need to be adjusted to cope with the change. 

Choosing 1024 gave the best result so I had to divide all timer functions by 1024/64 = 16.

Saturday 3 September 2011

The Power Supply

At some stage, I'm going to need to deliver more than 1A into the 12V rail for the motors so I'm contemplating using a PC ATX PSU to drive all my equipment.

I just found a nifty article on Instructibles.com about converting an ATX PSU into a general purpose lab bench PSU here.

Came with a very nice little circuit diagram that I'm pinching...
Of course, there is a great Wikipedia entry for ATX PSUs.

Arduino Servo Points Controller Software

First, a Disclaimer
  • I freely admit that this code is not exactly pretty. However, it does work and is sufficiently bug free.
  • It needs comments and should be refactored to use a class for the Points struct.
  • It's not been code reviewed and I'd be happy for your gracious comments.
Overview

There are only 2 files - Points.h and the main Arduino sketch. I was going to work it all into one file but there are some (known) bugs in the Arduino IDE that forced me to move some of my typedefs into the header file. The bug in the IDE si to do with the fact that the main sketch file is not parsed in strict line number order (somehow!).

The basic concept for this software is that I have an array of Points each of which is controlled by a well defined state machine. The system is essentially a table driven finite state machine with the context of where each point is "up to" stored in the Point data structure.

Feel free to pinch this code.

Points.h

#ifndef    POINTS_H
#define    POINTS_H    1

/*
 * Points.h - my arduino driven switch machine software….
 */

typedef enum  { cal_st, cal_sw, moving_sw, moving_st, 

                idle_sw, idle_st
} PointState ;

typedef enum  { cal_enter, cal_exit, mov_straight, 

                mov_switch, set_pt_changed, pt_tick 
} PointEvent ;



typedef struct  {
  int  ID;
  int  servo_pin;
  int  calib_pin;
  int  control_pin;
  int  set_pt_pin;
  int  eeprom_sw_addr;
  int  eeprom_st_addr;
 
  int  straight_angle;
  int  switch_angle;
 
  int  calib;
  int  control;
  int  set_point;

  int  current_angle;
  int  dest_angle;
 
  PointState  current_state;
 
  Servo point_servo;

} Point;

typedef    struct    {
    PointState    current;
    PointEvent    event;
    PointState    next;
    void          (*transition)( Point* p);
} PointStateElement;


#endif    POINTS_H

And the main Arduino Sketch

#include <Servo.h>
#include <EEPROM.h>
#include "<insert path>/Points.h"

#define  NUM_POINTS          4

#define  SVO_START_PIN       10  // The first pin to which a servo is connected
#define  CTL_START_PIN       6   // The first pin to be used for controlling the points
#define  CAL_START_PIN       2   // The first pin to be used for controlling calibration
#define  SET_PT_PIN          0   // The analogue pin to be used for reading the set point
#define  PT_EEPROM_ST        0   // The first address in the EEPROM for point settings to be kept

//#define  SER_OUT             1   // If defined, serial debug output is compiled in

#define  SERVO_DFT_ANG       90  // Where the servo will sit if it is uncalibrated
#define  SERVO_MIN_ANG       70
#define  SERVO_MAX_ANG       110

#define  IsAngleValid(a)     (((a) >= SERVO_MIN_ANG) && ((a) <= SERVO_MAX_ANG))

#define  PT_POS_STRAIGHT     1
#define  PT_POS_SWITCH       0
#define  PT_CALIBRATE        0
#define  PT_OPERATE          1

void
Points_error(Point* p)
{
#ifdef  SER_OUT
  Serial.print(p->ID);
  Serial.println(" error.");
#endif
}

void
Points_nop(Point* p)
{

}

void
Points_write_cal_st(Point* p)
{
#ifdef  SER_OUT
  Serial.print(p->ID);
  Serial.println(" writing calibration for straight.");
#endif
  EEPROM.write(p->eeprom_st_addr , p->current_angle);
  p->straight_angle = p->current_angle;
}

void
Points_match_set_pt(Point* p)
{
#ifdef  SER_OUT
  Serial.print(p->ID);
  Serial.println(" match set point.");
#endif
  p->current_angle = p->set_point;
  p->point_servo.write(p->set_point);
}

void
Points_write_cal_sw(Point* p)
{
#ifdef  SER_OUT
  Serial.print(p->ID);
  Serial.println(" writing calibration for switch.");
#endif
  EEPROM.write(p->eeprom_sw_addr , p->current_angle);
  p->switch_angle = p->current_angle;

}

void
Points_start_moving_st(Point* p)
{
#ifdef  SER_OUT
  Serial.print(p->ID);
  Serial.println("start moving straight.");
#endif
  if ( IsAngleValid(p->straight_angle) )
  {
    p->dest_angle = p->straight_angle;
  }
  else
  {
    p->point_servo.write(SERVO_DFT_ANG - 2);
    p->point_servo.write(SERVO_DFT_ANG + 2);
    p->point_servo.write(SERVO_DFT_ANG);
#ifdef  SER_OUT
  Serial.print(p->ID);
  Serial.println(" straight is uncalibrated");
#endif 
  }
 
}

void
Points_start_moving_sw(Point* p)
{
#ifdef  SER_OUT
  Serial.print(p->ID);
  Serial.println(" start moving switch.");
#endif
  if ( IsAngleValid(p->switch_angle) )
  {
    p->dest_angle = p->switch_angle;
  }
  else
  {
    p->point_servo.write(SERVO_DFT_ANG - 2);
    p->point_servo.write(SERVO_DFT_ANG + 2);
    p->point_servo.write(SERVO_DFT_ANG);
#ifdef  SER_OUT
  Serial.print(p->ID);
  Serial.println(" switch is uncalibrated");
#endif
  }
}

void
Points_move_step(Point* p)
{
#ifdef  SER_OUT
  Serial.print(p->ID);
  Serial.println(" move_step.");
#endif
   
    if(p->current_angle < p->dest_angle)
    {
      p->current_angle ++;
    }
    else if(p->current_angle > p->dest_angle)
    {
      p->current_angle --;
    }
   
    p->point_servo.write(p->current_angle);
    if(p->current_angle == p->dest_angle)
    {
#ifdef  SER_OUT
  Serial.print(p->ID);
  Serial.print(" reached destination ");
  Serial.print(p->dest_angle);
  Serial.print("( ");
  Serial.print(p->switch_angle);
  Serial.print(", ");
  Serial.print(p->straight_angle);
  Serial.println(")");
#endif

      if(p->dest_angle == p->switch_angle)
        p->current_state = idle_sw;
      else
        p->current_state = idle_st;
    }
}


PointStateElement PointStateTable[] = {
  {cal_st,    cal_enter,      cal_st,     Points_error        },
  {cal_st,    cal_exit,       idle_st,    Points_write_cal_st },
  {cal_st,    mov_straight,   cal_st,     Points_error        },
  {cal_st,    mov_switch,     cal_sw,     Points_nop          },
  {cal_st,    set_pt_changed, cal_st,     Points_match_set_pt },
  {cal_st,    pt_tick,        cal_st,     Points_nop          },
  {cal_sw,    cal_enter,      cal_sw,     Points_error        },
  {cal_sw,    cal_exit,       idle_sw,    Points_write_cal_sw },
  {cal_sw,    mov_straight,   cal_st,     Points_nop          },
  {cal_sw,    mov_switch,     cal_sw,     Points_error        },
  {cal_sw,    set_pt_changed, cal_sw,     Points_match_set_pt },
  {cal_sw,    pt_tick,        cal_sw,     Points_nop          },
  {moving_sw, cal_enter,      cal_sw,     Points_match_set_pt },
  {moving_sw, cal_exit,       moving_sw,  Points_error        },
  {moving_sw, mov_straight,   moving_st,  Points_start_moving_st},
  {moving_sw, mov_switch,     moving_sw,  Points_error        },
  {moving_sw, set_pt_changed, moving_sw,  Points_nop          },
  {moving_sw, pt_tick,        moving_sw,  Points_move_step    },
  {moving_st, cal_enter,      cal_st,     Points_match_set_pt },
  {moving_st, cal_exit,       moving_st,  Points_error        },
  {moving_st, mov_straight,   moving_st,  Points_error        },
  {moving_st, mov_switch,     moving_sw,  Points_start_moving_sw},
  {moving_st, set_pt_changed, moving_st,  Points_nop          },
  {moving_st, pt_tick,        moving_st,  Points_move_step    },
  {idle_sw,   cal_enter,      cal_sw,     Points_match_set_pt },
  {idle_sw,   cal_exit,       idle_sw,    Points_error        },
  {idle_sw,   mov_straight,   moving_st,  Points_start_moving_st},
  {idle_sw,   mov_switch,     idle_sw,    Points_error        },
  {idle_sw,   set_pt_changed, idle_sw,    Points_nop          },
  {idle_sw,   pt_tick,        idle_sw,    Points_nop          },
  {idle_st,   cal_enter,      cal_st,     Points_match_set_pt },
  {idle_st,   cal_exit,       idle_st,    Points_error        },
  {idle_st,   mov_straight,   idle_st,    Points_error        },
  {idle_st,   mov_switch,     moving_sw,  Points_start_moving_sw},
  {idle_st,   set_pt_changed, idle_st,    Points_nop          },
  {idle_st,   pt_tick,        idle_st,    Points_nop          }
};

#define  NUM_STATES  (sizeof(PointStateTable)/sizeof(PointStateElement))

int
PointInit(  Point* p, int ID,  int  ser_pin, int  cal_pin, int  con_pin, int  eeprom_addr, int set_pt_pin )
{
  int  val;
 
  p->ID = ID;
  p->servo_pin = ser_pin;
  p->calib_pin = cal_pin;
  p->control_pin = con_pin;
  p->eeprom_sw_addr = eeprom_addr;
  p->eeprom_st_addr = eeprom_addr + 1;
  p->set_pt_pin = set_pt_pin;
 
  p->point_servo.attach(ser_pin);
 
  p->straight_angle = EEPROM.read(p->eeprom_st_addr);
  p->switch_angle = EEPROM.read(p->eeprom_sw_addr);
 
  pinMode(p->calib_pin, INPUT);
  pinMode(p->control_pin, INPUT);
 
  val = analogRead(p->set_pt_pin);
  p->calib = PT_OPERATE;
  p->control = PT_POS_STRAIGHT;
  p->set_point = map(val, 0, 1023, SERVO_MIN_ANG, SERVO_MAX_ANG);
 
  p->current_state = moving_st;
  p->point_servo.write(SERVO_DFT_ANG);
 
  p->current_angle = SERVO_DFT_ANG;
  p->dest_angle = SERVO_DFT_ANG;
 
}


PointEvent
PointGetEvent(  Point* p)
{
  int  setval;
  int  set_pt;
 
  if(( p->calib == PT_CALIBRATE) && (digitalRead(p->calib_pin) == PT_OPERATE))
  {
    p->calib = PT_OPERATE;
    delay(20);        //debounce it
#ifdef  SER_OUT
  Serial.print(p->ID);
  Serial.println("cal_exit");
#endif
    return(cal_exit);
  }
  else if(( p->calib == PT_OPERATE) && (digitalRead(p->calib_pin) == PT_CALIBRATE))
  {
    p->calib = PT_CALIBRATE;
    delay(20);        //debounce it
#ifdef  SER_OUT
  Serial.print(p->ID);
  Serial.println("cal_enter");
#endif
    return(cal_enter);
  }
  if(( p->control == PT_POS_STRAIGHT) && (digitalRead(p->control_pin) == PT_POS_SWITCH))
  {
    p->control = PT_POS_SWITCH;
    delay(20);       //debounce it
#ifdef  SER_OUT
  Serial.print(p->ID);
  Serial.println("mov_switch");
#endif
    return(mov_switch);
  }
  else if(( p->control == PT_POS_SWITCH) && (digitalRead(p->control_pin) == PT_POS_STRAIGHT))
  {
    p->control = PT_POS_STRAIGHT;
    delay(20);       //debounce it
#ifdef  SER_OUT
  Serial.print(p->ID);
  Serial.println("mov_straight");
#endif
    return(mov_straight);
  }
 
  setval = analogRead(p->set_pt_pin);
  set_pt = map(setval, 0, 1023, SERVO_MIN_ANG, SERVO_MAX_ANG);
  if( p->set_point != set_pt)
  {
    p->set_point = set_pt;
    delay(20);
#ifdef  SER_OUT
  Serial.print(p->ID);
  Serial.print("set_pt_changed: ");
  Serial.println(set_pt);
#endif
    return(set_pt_changed);
  }
 
  delay(20);
  return ( pt_tick );
}

void
PointHandleEvent(  Point* p, PointEvent e)
{
    int         i,j;
    PointState  cs;
   
    cs = p->current_state;
    i = 0;
    while( i < NUM_STATES)
    {
      if ((PointStateTable[i].current == cs) && (PointStateTable[i].event == e))
      {
        p->current_state = PointStateTable[i].next;
        (*PointStateTable[i].transition)(p);
        i = NUM_STATES;     
      }
      else
        i++;
    } 
 
}

void
PointRun( Point* p)
{
  PointEvent  e;
 
  e = PointGetEvent(p);
  PointHandleEvent(p,e);
}

Point  PointArray[NUM_POINTS];


void setup()
{
  int  i;

#ifdef  SER_OUT
  Serial.begin(9600);
#endif
 
  for ( i = 0; i < NUM_POINTS; i++)
  {
    PointInit(&PointArray[i], i, SVO_START_PIN + i, CAL_START_PIN + i, CTL_START_PIN + i, PT_EEPROM_ST + i*2, SET_PT_PIN);
  }
 
}

void loop()
{
  int  i;
 
  for ( i = 0; i < NUM_POINTS; i++)
  {
    PointRun(&PointArray[i]);
  }
 
}

Thursday 1 September 2011

Finally, the electronics!

The electronics to control the test layout is actually pretty simple.

A this stage, I'm using a simple home brew power supply. You can see this below. My plan is to replace this with a standard PC power supply which has more than enough grunt to power several trains, all the points, signals, accessories etc.



The point controller is built using a single Arduino board seen below left. There's a lot of grunt here to run just 4 servos but I want to make this a nice independent unit that can be "sold separately". Below right is a prototyping shield that I have used to hook up some jumper pins that I use to plug the servos in to. These were then connected to D10-13.

D0 & D1 I have left for serial I/O. D2-9 are used to control the points. Notice that I have them pulled HIGH simply to ensure reliable operation. Pins D2-5 are used to indicate that the point is being calibrated. More on this later. Pins D6-9 are used to control position (straight or switched). Very simple! Analog Pin 0 is used to connect a potentiometer to set the angles for the servo in the straight or switched positions under calibration.

 

In order to make it easier to connect to this board, I'm using these nifty screw terminal breakouts from DFRobot which I bought at littlebirdelectronics.

Calibration
Below is my home brew calibration board. You must have a way of calibrating this setup and I could have used serial IO but that's not easy for most folks and having a set of buttons and knobs is very intuitive.

Why do I need it? well, mounting servos under the bench is a messy business and there is a lot of slop in the system! Also, what if I want to use this on HO rather than N-scale?

Calibrating is easy. Set the position of the point you want to calibrate, flick it's calibration switch up. Move the knob and the blades of the point will move. Drop the calibration switch and the position is remembered in the EEPROM in the Arduino. Very simple.

Of course, serial control of the points is an easy extension and is on it's way. After all, why wouldn't you?

The photos below show it all hooked together - very nice!



Train Speed and Position
You have seen this in a previous post but I'm including it here in a bit more detail. This is yet another stack of Arduino shields. At the bottom is an Arduino Diecimila (an original form Italy). Then the DFRobot motor shield. This can control 2 motors bi-directional. On top of that is another screw terminal shield and on top of that is a prototype shield which makes it easier to hook up sensors. I'm using these great little analog IR sensors from littlebird. Analog is far better as the triggers can be tuned nicely inside the Arduino.


You can see the motor shield exposed below. 

And here they are all hooked together nicely. The blue wires between the boards allow the Controller system to drive the points. A lot of the cabling was hand soldered but I got really cheap and nicely built extension cables for the sensors and servos of DealExtreme for bugger all cash!


What's next?
  • Source code for the points system - not brilliant but good enough.
  • Controlling 2 separate tracks off this system.
  • Some cute signals I got off eBay.
  • Expanding to 48 digital IO lines.
  • A relay control board for track sections.