C++ Objects for Layout Control, Part 2 — Turnouts

In C++ Objects for Layout Control, Part 1 I did an introduction to C++ objects, and demonstrated the basics of OOP with a simple “fire” object that I use to run an LED to simulate a fire in the turntable operator’s hut on the L&NC. In the demo sketch I did two instances of the fire object with two LED’s to demonstrate object independence and scalability.

In this post, we’ll take the basics of C++ objects for animation and extend them to the most common animated object on our layouts: turnouts.

L&NC Progress

Before I go hardcore programmer on you, I thought’ I’d share a few progress notes & pictures. Last weekend we rejoined the three lower level modules together for the first time in over a year.

All Three lower level sections reassembled.

Crossing from module 1 to module 2 for the first time.

Red Bluffs Yard

Another view of Modules ! & 2

I was especially pleased that the three sections came back together perfectly, even though they have been apart for over a year. I attribute the stability of the structures and the accuracy of the alignment to the style of framing, use of hardwoods and the McMaster-Carr alignment pins I use to assure the sections align correctly when put back to together. It all looked good on paper; but until proven you can’t be completely sure. Now I’m confident I can take the layout apart and put it back together again reliably.

Turnout Issues & Requirements

Like the fire object, the turnout object requires certain basic parameters to run, and has to be able to keep track of its own state. The turnout object has to respond to positioning commands and manage the turnouts’ motion to achieve a slow, scale appropriate movement from one alignment to the other.

The added complication is that the turnout class will need to deal with different hardware configurations and interfaces in different situations. For example, on the L&NC module I’ve been working on there are 9 turnouts and I’m using the Adafruit PWM Driver to run the servos and additional PWM devices, which I discussed in L&NC Update: Running Lots of Turnouts. The adjacent module has only two turnouts, so I’ll use the standard servo library and a couple of pins there. But regardless of the hardware interface, I want to use the same software objects in both places so they perform the same way.

Properties

The turnout class is a little more complicated than the fire class. Its basic properties include the pin (in the case of the Adafruit driver, the channel) the servo is attached to, the current alignment of the turnout, the default alignment and the servo settings for the main and divergent positions (pos_main and pos_divergent). Then, to facilitate motion, there are a variety of properties that work together: delay between moves, the increment (1 or more units) of each move, the current position, the target position and the target alignment.

I mentioned that I am using the turnout class in different situations where the hardware that runs the servos could be a standard pin, or an external driver board such as the Adafruit PWM Driver. To complicate matters, while the standard Servo Library positions by degrees, the Adafruit PWM Driver positions by the “tick” value of the desired PWM setting. These are different units and different scales; but it doesn’t matter. So long the turnout class is initialized with a correct set of values (in the same units) for the hardware interface in question, it works consistently.

Methods

For the turnout class I have created an interface of public methods for interacting with the class. Additionally, the class has a private hardware interface for interacting with the hardware environment.

The Constructor

The constructor is straight forward with one twist. Because a lot of arguments are required to set up the object, I’m using a data structure to pass most of the arguments.

The constructor takes a pointer to a TURNOUT_PARAMS variable, plus an optional movement_increment argument. I added the movement increment parameter (and the corresponding class property) as an additional factor in turnout motion after encountering problems integrating multiple processes on the Uno on Module 1. Manipulating both delay and increment values improves control over the movement of servos in different situations. On the L&NC Module 1, Lower Level there are both a lot of turnouts and a lot of block detectors. I’ll discuss the issues more in an upcoming post, but the resources required for block detection make it difficult to move the servos fast enough without adjusting the movement increment.

What’s all this Pointer Stuff?

If you’ve been working with Arduino for a while you’ve likely dealt with pointers before. For those who are unfamiliar with the term (and it is a complex subject, worth learning), pointers are the memory address of a variable. Pointers are another way to access variables unique to the C/C++ languages. In the case of the Constructor, passing a pointer to the parameters array grants access to a complex data structure without having to copy it first. The Reference (&) operator returns a pointer to an variable (&tdef[i]); the dereference (*) operator declares a pointer variable used to access a pointer’s value — such as TURNOUT_PARAMS *parameters in the Constructor. With pointers to data structures or objects, use “->” to access members instead of “.”; eg: params->data instead of params.data or object->method() instead of object.method().

The Public Interface

The public interface is comprised for four methods. The getAlignment() method is a good example of the correct way to share the value of an internal property with an external process. Its correct because it outputs the value of the alignment property without breaking its protection as a private property (and thus exposing it to being changed externally).

int getAlignment(){
return alignment;
}

Another simple method provides a way to toggle the position of the turnout back and forth.

Motion works by setting motion variables in the set() method then calling update() repeatedly to execute the motion. Update() is intended to be called continually as part of the main loop() while the sketch is running—my multitasking model is to get the current time (millis()) at the start of every iteration of the main loop(), then pass that value to every object that uses time to manage its own state. It is very important that the very first logic test within the update() method is whether or not the turnout is in motion; if not in motion the method exits immediately. With nine turnouts on the lower lever of module 1, efficiency is necessary or the sketch bogs down.

Hardware Interface

Up to this point there has been no direct interaction with hardware. Instead there have been calls to two private methods: init_servo() and setServo(). These two private methods interact directly with the servo hardware.

I should emphasize that any motor type can be used with appropriate connections, even 12 volt stall motors. I like servos because they are cheap and easily supported in the Arduino world. But don’t feel that just because you have a different motor type you can’t run them with an Arduino. The point of creating a private, protected hardware interface is to isolate all the hardware specific stuff in one place while presenting a more general public interface. That makes it much easier to drive a wide variety of hardware while behaving consistently.

Init_servo() is for doing whatever your motor needs to set up. Here the method sets the default alignment and commands the servo to move to that position immediately. This is private so that it can never be called from outside a turnout object.

setServo() moves the servo to a specific position. I use a compiler directive — by defining either ADAF_DRIVER or SERVO_LIB (but not both)— to determine which hardware system is in use. In some cases I’ll be using the Adafruit 16 Channel PWM Driver and its library; in other cases regular pins with the native servo library.

The Whole Enchilada

This time I’m posting it in the form of a header — *.h — file. To use it, copy it into your sketch directory. The Arduino IDE will recognize and allow you to edit the “h” file, but it won’t automatically include it in your build. To in include it in the build you must explicitly include it near the top of your main sketch this way:

#include "turnout.h"

Why do it this way? Because the class definition has to be seen by the compiler before it can be used to create run-time objects. Therefore, in a single file sketch you’d have to put the class definition at the top of the sketch. If you have a lot of header material of that sort (class definitions, typdefs, etc.), the top of your sketch can get long and the whole thing harder to maintain. By putting the class definition in a header file you can segregate different elements of your sketch, control exactly when the compiler sees it during the build process, ensure all dependencies are satisfied and keep your main sketch file clean and uncluttered. The bigger your sketch gets the more important this becomes.

Coming Soon

Back to block occupancy detection with 24 blocks and one Arduino to rule them all! To say scale started to be a problem would be an understatement. More about that in the next post.

You are not too far off, but modifying the typedef shouldn’t be necessary. An odd error (ignoring a typedef is wierd) usually means a parsing problem such that the compiler isn’t understanding the code. One thing in setup() sticks out:

for (int i = 0; i toggle();
turnouts[1]->toggle();

That’s not a properly formatted for loop and I can’t tell what is intended here. What should be happening in setup at that point is initializing the turnouts like this:

The turnout objects don't exist until this happens. Only after initialization is it possible to use the toggle() and update() functions. For testing I suggest you wire up a couple of buttons and monitoring them in your main loop -- running toggle() every time a button is pushed. Program flow in the main loop should look like this:

-> Get Milliseconds
-> run update method on all turnouts
-> read buttons
-> if a button[i] == HIGH call turnout[i].toggle()