Construction Update 2020

Four years of work in the garden on weekends, mornings and evenings.

2017

I had spent a LOT of time beating back both ivy and wild iris from the yard. Both are very invasive and occupied hundreds of square feet of our garden. So in 2017 I had a plan to build a garden railroad and the beginnings of a garden.

2018

In 2018 I concentrated on clearing the land of noxious brush. I had version 1 of the plan defined, and began to lay out the primary features.

I decided to start from the top of the railroad, as the bottom needed some (expensive) retaining wall work which had to wait. So here is where Matterhorn Siding would be, the position and elevation defined.

This let me start to define the garden more clearly and to start laying out paths.

Lower path: Ophir, Low Line to the left and Ames, Vance Junction to the right
Upper path: Ophir, High Line to the right and Matterhorn to the left

2019

I spent most of 2019 working on Matterhorn and it’s attached planter bed.

Matterhorn with track and turnouts being installed

2020

2020 sees the “completion” of Matterhorn, some of the garden hardscape in place and in general a LOT of progress toward the end goal.

Flagstones and Dry-stack Retaining Wall
Matterhorn with decorative planters below and foliage adjacent

I also began to define Ophir and the High-line Siding, building “table” sections similar to Matterhorn. The Ophir table will have the Depot and other structures.

Ophir in the foreground and the High-line siding behind that

This winter I’ll spend a lot of time building bridges to be installed when the weather is not too wet, probably in this order:

  • Bridge 51-A at the end of Matterhorn
  • Bridge 45-B between Ophir and the High-line Siding
  • Bridges 46-B and 46-C in the High-line

Hand-laying Track

Straight Stringers

I rip cheap cedar fence boards into ¾” wide pieces, getting four per board. The height of my stringers will be ¾”, so I use the clean-cut sides as the top-bottom. I glue two of these together and cut 1-inch blocks to act as spacers between the straight stringers. (Any stringer scraps can be recycled as spacers.)

I simply glue and clamp these without any nails or screws. The addition of ties and attachment to the baseboard will bond everything together permanently.

Gluing and Nailing Ties

I have a tie-spacing template with ties drawn at 1.2” apart, which gives ten ties per foot. (This is the same as Llagas Creek flex track.) I use small snack-size zipper bags filled with sand as weights for holding the glued ties down while the glue sets. Each bag will cover five ties, so I put them down five at a time.

Once the glue has set, I use a brad nailer to attach each tie to the stringers. Make sure that each nail ends up under the rail by marking gauge lines 45 mm. apart and nailing just outside of those lines. (I thoroughly wet down the ties before I nailed them to help make sure they wouldn’t split.)

Note: I wrote this in July 2019, and since then I have found that using longer spikes holds the ties in place well, so I no longer nail the ties down. Also, sometimes the nail would end up where I wanted a spike!

Spiking Rail

I start spiking rail down by placing the length on the ties with a straight-edge and spiking it down in just a few places – say every foot. Then I carefully spike in between those spots, dividing each into halves and so forth until the rail is spiked every two or three ties.

The second rail needs to be spiked in gauge, so I break out my track gauges and put one on each side of the tie I’m spiking. For this rail, I spike the same ties as on the first rail.

At this point I just proceed spiking the remaining ties from one end to the other.

Curved Stringers

I made a curved track jig to use to laminate three pieces per side of bender board or lathe cut to ¾” width. I use the same spacers as for the straight stringers. In hindsight, I realized that I could have used fewer spacers on this first curved piece (perhaps using many as construction spacers and not gluing them).

I drew a template with LibreCad (CAD software of choice because it’s free) and used that to position the ties similar to the straight section process. 

(This post was mostly written a year ago. I was embarrassed when I looked at the last update to this blog! I’ll add a few more posts, because I have photos from the construction process.)

2019 Construction Progress

Rio Grande Southern Matterhorn Section

During the past year, I have begun the construction of my garden railway. After we had cleaned up after snowpocolypse, that is. I decided to start at the top of the yard, because the bottom section is waiting on a retaining wall project. The topography of my land means that this section is elevated about 4 feet off the ground, so I elected to build a “table” for this section.

The table is the portion labeled Matterhorn with the siding. Just after the top end of the table is Bridge 51A, the Trout Lake trestle to be built and installed this year.

Matterhorn Section

I used standard treated wood 2×4 frames covered with hardware cloth and then weed barrier cloth on top of that.

Because this section will be elevated close to eye-level, I thought that hand-laying the track would look nicest.

I was looking at this and thought that it would be great to have some elevated garden boxes along the table so I could plant on this section of the line as well as the filled ground-level sections. So I built a long raised plant box along the back of the table.

It’s surprising just how much dirt and mulch it takes to fill this!
I took my time and built a nice cedar face for this planter, as it will be visible from the upper patio.

I am looking forward to better weather as I have a lot of little projects to finish this section: one turnout and the final curve needs to be finished; all the track needs to be fastened down; the siding area needs to be filled and ballasted; and the garden box filled and planted. Also, a trestle to build and install just off the end of the table.

Not being a glutton for punishment, track laying will then continue apace using PVC ladder roadbed and flex track.

Arduino Multi-function Shield

I recently got exposed to Arduino programming and hardware design at the 2019 National Garden Railway Convention in Portland, OR. Geoff Bunza (Blog: https://model-railroad-hobbyist.com/blog/geoff-bunza YouTube: https://www.youtube.com/channel/UCKzeYLMEPxWGilqZTNuG0JA) held an Introduction to Arduino in Model Railroading clinic. As the Clinic Chairman, I offered to help during the presentations in any way I could. Because I’ve been programming forever (since I was a teen), and have been an electronics hobbyist, none of the topics were foreign to me. I had heard of the Arduino, but had never played with one.

This was really fun, using simple components and software to make blinkenlights and such. There are posts such as this one https://maker.pro/arduino/tutorial/how-to-easily-prototype-with-an-arduino-multi-function-shield about this shield, and other examples using libraries to control the devices.

I’m not sure why, but I couldn’t get the libraries to work. They were overly complex for something as simple as controlling a multiplexed display. I attempted to just update the display during the loop section of the sketch, but I couldn’t get the brightness of each segment to be even. Inevitably, the last segment to be updated was brightest (as it was “on” during the loop code) and the others were dim.

I looked for a more basic example using a timer that wasn’t 20,000 lines of code and obfuscation. I found this example that showed how to set up Timer 1 and provide an interrupt handler: http://www.hobbytronics.co.uk/arduino-timer-interrupts That was more like it!

I extended that code and modified my sketch display code to interface with the ISR, and got everything to work well. I built a simple software count-down timer that uses the buttons to set the time and beeps at the end.

Note: I should probably move the display code to a library or something, rather than have so many lines of code.

/*  Multi Function Shield - Count-down timer example.
 *  Implements a timer configurable as Hours and Minutes or
 *  Minutes and Seconds. 
 *  
 *  (C) Copyright 2019 by Russell Shilling
 */ 

// Buttons and Buzzer
#define BUTTON_A A1
#define BUTTON_B A2
#define BUTTON_C A3

#define PIEZO 3

// Define shift register pins used for seven segment display 
#define LATCH_DIO 4
#define CLK_DIO 7
#define DATA_DIO 8

// LEDs
#define LED_A 10
#define LED_B 11
#define LED_C 12
#define LED_D 13

// Segment byte maps for numbers 0 to 9, A to F 
//                             0    1    2    3    4    5    6    7    8    9
//                             A    B    C    D    E    F    G    H    I    J
//                             K    L    m    N    O    P    Q    R    S    T
//                             U    v    w    x    Y    z
const byte SEGMENT_MAP[] = { 0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90, // 0-9
                             0x88,0x83,0xA7,0xA1,0x86,0x8E,0xC2,0x89,0xFB,0xE1, // A-J
                             0x8F,0xC7,0xFF,0xAB,0xA3,0x8C,0x23,0xAF,0x92,0x87, // K-T
                             0xE3,0xF3,0x63,0xFF,0x99,0xFF };                   // U-Z
// Segments  -a-      a=0x01, b=0x02, c=0x04, d=0x08
//          f   b     e=0x10, f=0x20, g=0x40, h=0x80
//           -g-
//          e   c
//           -d-  h


// Byte maps to select digit 1 to 4 
const byte SEGMENT_SELECT[] = {0xF1,0xF2,0xF4,0xF8};

String words[] = { "end", "no", "yes", "on", "go", "run", "dot", "art",
                   "abcd", "efgh", "ijkl", "mnop", "qrst", "uvw", "xyz" };

byte SEGMENT_BUFFER[] = {0xCF,0x3F,0xFC,0xF3};

// Timer interrupt
int timer1_counter; 
int timer1_state; 
                   
// State: button, debounce, alarm
int button = 0;
unsigned int bounce = 0;
int alarm = 0;
int alarmFlag = 1;

int count = 0;
long timer = 0;

long tick = millis();
long secs = tick / 1000; 

#define TIMER_MMSS 0
#define TIMER_HHMM 1

const byte timer_mode = TIMER_HHMM;

void setup ()
{
  // Set Buzzer
  pinMode(PIEZO,OUTPUT);
  digitalWrite(PIEZO,HIGH);

  // Set LEDs to outputs
  pinMode(LED_A,OUTPUT);
  pinMode(LED_B,OUTPUT);
  pinMode(LED_C,OUTPUT);
  pinMode(LED_D,OUTPUT);

  // Initial LED State
  digitalWrite(LED_A,HIGH);
  digitalWrite(LED_B,HIGH);
  digitalWrite(LED_C,HIGH);
  digitalWrite(LED_D,HIGH);

  // Set DIO pins to outputs 
  pinMode(LATCH_DIO,OUTPUT);
  pinMode(CLK_DIO,OUTPUT);
  pinMode(DATA_DIO,OUTPUT);

  digitalWrite(LATCH_DIO,LOW);
  shiftOut(DATA_DIO, CLK_DIO, MSBFIRST, 0);
  shiftOut(DATA_DIO, CLK_DIO, MSBFIRST, 0);
  digitalWrite(LATCH_DIO,HIGH);

  // initialize timer1 
  // http://www.hobbytronics.co.uk/arduino-timer-interrupts
  noInterrupts();           // disable all interrupts
  TCCR1A = 0;
  TCCR1B = 0;

  // Set timer1_counter to the correct value for our interrupt interval
  timer1_counter = 65208;   // preload timer 65536-16MHz/256/200Hz
  //timer1_counter = 64911;   // preload timer 65536-16MHz/256/100Hz
  //timer1_counter = 64286;   // preload timer 65536-16MHz/256/50Hz
  //timer1_counter = 34286;   // preload timer 65536-16MHz/256/2Hz
  
  TCNT1 = timer1_counter;   // preload timer
  TCCR1B |= (1 << CS12);    // 256 prescaler 
  TIMSK1 |= (1 << TOIE1);   // enable timer overflow interrupt
  
  interrupts();             // enable all interrupts
  // done with init
  
  Serial.begin(9600);
}

// Interrupt service routine
ISR(TIMER1_OVF_vect)        // interrupt service routine 
{
  TCNT1 = timer1_counter;   // preload timer
  if (SEGMENT_BUFFER[timer1_state] != 0xFF)
  {
    digitalWrite(LATCH_DIO,LOW);
    shiftOut(DATA_DIO, CLK_DIO, MSBFIRST, SEGMENT_BUFFER[timer1_state] );
    shiftOut(DATA_DIO, CLK_DIO, MSBFIRST, SEGMENT_SELECT[timer1_state] );
    digitalWrite(LATCH_DIO,HIGH);
  }
  timer1_state = (timer1_state + 1) & 3;
}

// Main program 
void loop()
{
  // Debug
  /*
  Serial.print(button);
  Serial.print(" ");
  Serial.print(timer / 60);
  Serial.print(":");
  Serial.println(timer % 60);
  */
    
  // Timer start
  if (analogRead(BUTTON_A) < 500)
  {
    button = 0;
    alarmFlag = 0;
  }
  // Set Seconds or Minutes
  else if (analogRead(BUTTON_B) < 500)
  {
    if (bounce < 1)
    {
      if (timer_mode == TIMER_HHMM)
      {
        if ((timer % 3600) / 60 >= 59)
        {
          timer = (timer / 3600) * 3600;
        } 
        else
        {
          timer = timer + 60;
        }
      }
      else
      {
        if (timer % 60 == 59)
        {
          timer = 60 * (timer / 60);
        }
        else
        {
          timer += 1;
        }
      }
      if (button == 1)
        bounce = 250;
      else
        bounce = 500;
    }
    button = 1;
  }
  // Minutes or Hours
  else if (analogRead(BUTTON_C) < 500)
  {
    if (bounce < 1)
    {
      if (timer_mode == TIMER_HHMM)
      {
        timer += 3600;
        if (timer / 3600 > 10)
        {
          timer = timer % 3600;
        }
      }
      else
      {
        timer += 60;
        if (timer > 3600)
        {
          timer = 0;
        }
      }
      if (button == 2)
        bounce = 250;
      else
        bounce = 500;
    }
    button = 2;
  }
  else
  {
    bounce = 0;
  }

  // Display
  if (timer > 0)
  {
    if (timer_mode == TIMER_HHMM)
      WriteTimeHHMM(timer);
    else
      WriteTimeMMSS(timer);
  }
  else
  {
    WriteString("end");
    if ((alarm == 0) && (alarmFlag == 0))
    {
      alarm = 100;
      alarmFlag = 1;
      digitalWrite(PIEZO,LOW);
    }
  }

  // Flash the LED once per second
  if (button == 0)
  {
    if (timer > 0)
    {
      long secs = millis() / 500;
      if (secs & 1)
      {
        digitalWrite(LED_A,LOW);
      }
      else
      {
        digitalWrite(LED_A,HIGH);
      }
    }
  }
    
  if (tick < millis())
  {
    tick = millis();
    if (alarm > 0)
    {
      alarm--;
      if (alarm == 0)
        digitalWrite(PIEZO,HIGH);      
    }
    if (bounce > 0)
    {
      bounce--;
    }
  }

  long s = millis() / 1000;
  if (secs < s)
  {
    if (button == 0)
    {
      if (timer > 0)
      {
        timer --;
      }
    }
    secs = s;
  }
}

// Write a String
void WriteString(String text)
{
  WriteSegment(0, (10 + text[0] - 'a'));
  if (text.length() >= 2)
    WriteSegment(1, (10 + text[1] - 'a'));
  else 
    ClearSegment(1);

  if (text.length() >= 3)
    WriteSegment(2, (10 + text[2] - 'a'));
  else
    ClearSegment(2);

  if (text.length() >= 4)
    WriteSegment(3, (10 + text[3] - 'a'));
  else
    ClearSegment(3);
}

// Write a Time value 
void WriteTimeMMSS(int Value)
{
  int minute = Value / 60;
  int second = Value % 60;

  WriteSegment(3 , (second % 10));
  WriteSegment(2 , (second / 10));
  WriteSegment(1 , (minute % 10), 1);
  if (minute >= 10)
    WriteSegment(0 , (minute / 10));
  else
    ClearSegment(0);
}

// Write a Time value 
void WriteTimeHHMM(long Value)
{
  // Round up
  int hour = (Value + 59) / 3600;
  int minute = ((Value + 59) % 3600) / 60; 

  WriteSegment(3 , (minute % 10));
  WriteSegment(2 , (minute / 10));
  WriteSegment(1 , (hour % 10), 1);
  if (hour >= 10)
    WriteSegment(0 , (hour / 10));
  else
    ClearSegment(0);
}

// Write a Decimal number
void WriteDecNumber(int Value)
{
  WriteSegment(3 , (Value % 10));
  if (Value >= 10)
    WriteSegment(2 , (Value / 10) % 10);
  else
    ClearSegment(2);

  if (Value >= 100)
    WriteSegment(1 , (Value / 100) % 10);
  else
    ClearSegment(1);
    
  if (Value >= 1000)
    WriteSegment(0 , (Value / 1000) % 10);
  else
    ClearSegment(0);
}

// Write a Float number
void WriteFloatNumber(float FValue, byte Decs)
{
  if (Decs == 1)
  {
    int Value = FValue * 10;
    WriteSegment(3, (Value % 10));
    WriteSegment(2, (Value / 10) % 10, 1);
    if (Value >= 100)
      WriteSegment(1, (Value / 100) % 10);
    else
      ClearSegment(1);
    if (Value >= 1000)
      WriteSegment(0, (Value / 1000) % 10);
    else
      ClearSegment(0);
    }
  else if (Decs == 2)
  {
    int Value = FValue * 100;
    WriteSegment(3, (Value % 10), 0);
    WriteSegment(2, (Value / 10) % 10, 0);
    WriteSegment(1, (Value / 100) % 10, 1);
    if (Value >= 1000)
      WriteSegment(0, (Value / 1000) % 10, 0);
    else
      ClearSegment(0);
  }
  else if (Decs == 3)
  {
    int Value = FValue * 1000;
    WriteSegment(3, (Value % 10), 0);
    WriteSegment(2, (Value / 10) % 10, 0);
    WriteSegment(1, (Value / 100) % 10, 0);
    WriteSegment(0, (Value / 1000) % 10, 1);
  }
}

// Write a Hexadecimal number
void WriteHexNumber(int Value)
{
  WriteSegment(3, (Value & 15));

  if (Value >= 16)
    WriteSegment(2, (Value >> 4) & 15);
  else
    ClearSegment(2);

  if (Value >= 256)
    WriteSegment(1, (Value >> 8) & 15);
  else
    ClearSegment(1);

  if (Value >= 4096)
    WriteSegment(0, (Value >> 12) & 15);
  else
    ClearSegment(0);
}

// Write a blank to one of the 4 digits of the display 
void ClearSegment(byte Segment)
{
  SEGMENT_BUFFER[Segment] = 0xFF;
}

// Write a character to one of the 4 digits of the display 
void WriteSegment(byte Segment, byte Value)
{
  SEGMENT_BUFFER[Segment] = SEGMENT_MAP[Value];
}

void WriteSegment(byte Segment, byte Value, byte Dp)
{
  byte Dpv = Dp == 1 ? 0x7F : 0xFF;
  SEGMENT_BUFFER[Segment] = SEGMENT_MAP[Value] & Dpv;
}