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

Hacker’s Delight

and the Arduino (GCC Compiler)

I got a new (used, but new to me) book about computer coding: Hacker’s Delight by Henry S. Warren Jr. This is one of those books that I will read most of more than once, because the material covered is very technical and delves deeply into the intricacies of programming efficiently.

One algorithm that was particularly interesting, for no reason other than the algorithm is clever. This is a method for counting the 1-bits in a binary word. There is the naïve implementation, of course, but iterating through the bits and counting the ones:

for (long j = 0; j < 1000; j++)
{
  x = 0x371FC7C9;
  c = 0;

  for (int i = 0; i < 32; i++)
  {
    c += x & 0x1;
    x >>= 1;
  }
}

I had to run this code 1 thousand times to get a good count. The Arduino (ATMega 328P chip) is FAST! This code ran in 24.58 microseconds per iteration.

There is a much more clever method in the book, which divided the value up into 2-bit blocks and counted the ones using addition, then added the 2-bit values into 4-bits, then 8-bits and finally adding the two 16-bit blocks to get the final count. So I added the code from the book to compare the times:

for (long j = 0; j < 1000; j++)
{
  x = 0x371FC7C9;

  x = x - ((x >> 1) & 0x55555555);
  x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
  x = (x + (x >> 4)) & 0x0F0F0F0F;
  x = x + (x >> 8);
  x = x + (x >> 16);
  c = x & 0x0000003F;
}

This was an impressive optimization, as it ran in zero milliseconds! I increased the loop count to 1 million so I could get a measurable time, and it was still zero milliseconds. WOW! So, I added yet another 1 million iteration loop around the “j” loop. Still zero…

So, 1 trillion iterations in zero milliseconds? That processor is not a super-computer, so something was wrong. I realized the compiler must have been seeing my code as unchanged in each loop and was simply optimizing the loops away.

I added an array with two elements (with identical bit counts) to try to fool the optimizer.

long xx[2] = { 0x371FC7C9, 0x71FC7C93 };
for (long j = 0; j < 1000000; j++)
{
  x = xx[j & 1]; // 0x371FC7C9;

  x = x - ((x >> 1) & 0x55555555);
  x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
  x = (x + (x >> 4)) & 0x0F0F0F0F;
  x = x + (x >> 8);
  x = x + (x >> 16);
  c = x & 0x0000003F;
}

This worked, as I got 8.68 microseconds per execution. This is about 3 times faster than the naïve implementation. Nice.

Now, I may just have a use for that beautiful code someday.

Enhancements

I wanted to display the execution time on a display, so I added the code to a sketch with the test loops in the main loop. Foiled again! The optimized code was, again, optimized away.

I experimented with several changes to fix that. What worked was declaring the array test variable volatile so that the x variable was loaded each iteration:

volatile unsigned long xx[2] = { 0x371FC7C9, 0x71FC7C93 }; 

Garden RGS Construction – Part 4

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.)

Spiking Rail

I start spiking rail down by placing a rail 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 lath 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 in place).

I have a curved tie layout template similar to the straight one that I use to get tie spacing consistent.

Garden RGS Construction – Part 3

Building the Matterhorn “table” section

At the high end of the railroad, the elevation above the ground makes it prohibitive to build a retaining wall, so I am going to use a table to elevate the tracks.

Matterhorn table plan

Conventional construction of two 10-foot long, 2-feet wide sections. I’m using treated lumber for everything structural. I got “deck” screws designed for treated lumber: coated with something.

Assembling a frame
Table section in place

I used a string level to set the grade to 2.5%, which is a 3-inch rise in 10 feet.

Setting the grade
Hitting the bubble

I used Urethane foam post mix instead of cement. The way it tended to gush out into a hole because the bag was “floppy” was a problem. In the future, I’m going to get the Urethane in gallon bottles and mix what I need.

First two sections set

The angled sections were difficult, but having an accurate CAD drawing with accurate angles made it doable.

Getting the offset right

Labeling the cuts helped to keep all these similar angles and wood pieces straight. Even with all that, I cut one piece 1-inch too short and had to re-cut it.

Note the various angles

The last two curved sections are read to secure in place. I wised-up and used stakes pounded into the ground and screwed the table sections to them instead of “floppy” boards and clamps.

The three “curved” sections

I covered the frames with 1/4″ hardware cloth, stapled in place.

Hardware cloth installation

I ripped a treated two-by-four in half at a slight angle to form side-rails that will keep the soil and ballast on the table.

Side rails

I used a heavy, professional-grade ground barrier on top of the hardware cloth to provide drainage and keep the soil in place.

Ready for Track!

Next time: Building turnouts and track.