Part 3: Detailing and Animation
With the mechanism built, I put the final details on the water towers.
The bands were made out of cardboard from a cereal box, painted with the same faded red that I'd used for the brass tubing into the tank. For the metal plates over the pulleys, I used styrene with NBW castings. I also painted the cross bracing between the legs and added some NBW castings over the holes I'd drilled to install the wires.
I wanted to have the roof removable, in case the servos need to be replaced in future. In order to accomplish this, I 3D printed a 'lid' for each water tower. Internally, they match the diameter of the Pringles can lid. To get a 'tarpaper' like texture on them, I built up layers of masking tape. Each piece of tape was cut to match the angle of the roof section.
Once the tape was in place, I used narrower tape to form the seams between the sections. At the top, I applied some body putty to provide a smooth peak. Once this had dried, I sanded it into shape.
Before installing the water tower on the layout, I hooked it up to an Arduino, wired up identically to how it would be installed on the layout. After testing the code I'd written, I made a couple of tweaks. The final code is as per below:
#include <Servo.h>
#include <OneButton.h>
//Below variables are for the Water Tower servo controller
const int startAngle = 75; //Initial angle for the servo motor
const int endAngle = 0; //Ending angle for servo - where it stops in the raised position
const int servoDelay = 15; //Delay (in ms) between servo steps
const int servoOutPin = 10; //Output pin for servo control
OneButton contButton(17, 1); //OneButton object for servo control - this is pin A3 being used as digital.
const int servoLEDPin = 11; //Output pin for indicator LED
Servo towerServo; //Servo object for the water tower
void setup() {
//Initialise stuff for water tower servo
towerServo.attach(servoOutPin);
contButton.attachClick(towerAnimate);//attach the Single Click function to this event
pinMode (servoLEDPin, OUTPUT);
towerServo.write(startAngle);
}
void servoLower (int startPosn, int endPosn, int servoPause){
/*Takes the desired starting and ending positions as arguments. E.g. 0
for start and 45 or whatever the closing angle is for end.
This has the effect of raising the spout - it lowers the servo arm.*/
for (int pos = startPosn; pos <= endPosn; pos += 1){
towerServo.write(pos);
delay(servoPause);
}
}
void servoRaise (int startPosn, int endPosn, int servoPause){
/*Takes the desired starting and ending positions as arguments. E.g. 45
for start and 0 for end.
This has the effect of lowering the spout - it raises the servo arm.*/
for (int pos = startPosn; pos >= endPosn; pos -= 1){
towerServo.write(pos);
delay(servoPause);
}
}
void towerAnimate(){
digitalWrite(servoLEDPin, HIGH);
/*System assumes that the water tower starts with the spout in the 'up' position - i.e. with the
servo down. So servo will need to be raised, paused, then lowered.*/
servoRaise(startAngle, endAngle, servoDelay);
delay(5500);
servoLower(endAngle, startAngle, servoDelay);
digitalWrite(servoLEDPin, LOW);
}
void loop(){
//This acts as a 'listener', to determine what is being input and calling the appropriate subroutine
contButton.tick();
}
This is part of a larger sketch, which runs my automation controller Arduino on the Camp A side of the layout. Only the parts relevant to the water tower animation have been included above. Starting from the top, I've included two code libraries, the Servo library and the OneButton library. The former defines functions to control a servo motor. The latter, which can be downloaded here (), is designed to help with using buttons. Instead of having to handle things like the 'debouncing' of buttons, setting the input pin, activating the built-in resistor, etc in code, this library takes care of it all. It can also detect single presses, double-presses and when the button is held down, which allows you to use the one button for multiple functions.
(Debouncing refers to filtering out the multiple open/closed signals generated by a single press of a button.)
Next up, I defined the various values and objects that will be used by the code. I prefer to define these in a single place, then use the names in the code. This means that if these values need to be tweaked, it only needs to be done in the one place, and it ensures that none of the uses of that value is missed. I've also defined them as constants, rather than variables (as per the 'const' keyword). This saves memory space in the Arduino, and also prevents them from being accidentally changed.
The OneButton and Servo declarations are not constants, but instead create an object in the program code. In programming terms, an object is a construct which contains various functions and variables to allow it to be used and store data. The values given in brackets for the OneButton object tell the object which pin it needs to use to receive input from the button, and to activate on initialisation.
Next up, we have the setup() function. This is one of the two standard Arduino functions, and it is run when the Arduino first has power applied. As such, it's usually used for code which needs to run on startup. In this case, the setup function:
- Tells the Servo object (towerServo) which pin to use to send the control signal to the servo.
- Tells the OneButton object (contButton) which program function to execute when it detects a single button press.
- Sets the pin used for the LED to an output. This is used to light up an indicator LED on the layout fascia, to indicate that the Arduino is responding.
- Sets the towerServo to the starting angle. The system makes the assumption that the servo is starting with its arm in the down position, which has the spout raised. However, when power is first applied to the servo, it jerks back a couple of degrees. This line sets it back to the correct starting angle.
With that done, the code is now ready to run. However, there are three other functions defined before the main part of the program, each of which plays a role in the animation of the water tower. When it comes to programming, I tend to use a modular approach. This simplifies debugging, and allows individual sections of the code to be tested before being used in the program as a whole. In this case, the servoLower and servoRaise functions were copied over from the code I'd written to test the servo in part 2. These had worked then, so I knew they were proven code. Both functions work the same way, just in different directions. Bear in mind that the program defines things in terms of the arm attached to the servo. When the arm is raised, the spout is lowered and vice-versa. The arm is raised when the servo angle is 0, and lowered when it's at startAngle, which in this case is 75 degrees.
Both the servoRaise and servoLower functions take three values as arguments; the angle of the servo at the start of the function, the angle at the end of the function, and the delay in milliseconds between each step of the servo. Each function then goes into a loop which basically adjusts the position of the servo by the current position + or - 1, then pauses for the specified delay, until the end angle is reached. This is required to provide a smooth animation. If I were to simply tell the servo to go to the end angle, then it would snap to that position, which would move the spout instantly and possibly damage the mechanism.
Following these, we have the towerAnimate function, which is called when the button is pressed, and does the actual animation. When called, the very first thing it does is write a HIGH to the LED output pin, which turns on the LED on the layout fascia. This mainly acts as a trouble indicator. If the LED comes on but the spout doesn't move, then there's something wrong with the servo or the connections to it. Following this, it calls the servoRaise function to raise the servo arm and lower the spout. It then waits for 5.5 seconds, then calls the servoLower function to lower the servo arm and raise the spout back to its original position. Once all this is done, it writes a LOW to the LED output, turning it off.
There's one final piece of the puzzle needed in order to make this all work. The loop() function is the other standard Arduino function, and this is the main body of the program. Following the setup, the loop function will run over and over in a loop, until the Arduino is turned off. In this case, all that's needed here is to call the tick function of the contButton object. This checks if the button has been pressed, double-pressed or held down and if so, it calls the associated function. In this case, that's the towerAnimate function.
Once I'd added the above code to my automation controller sketch and uploaded it to the Arduino, I tested it on my workbench. I replicated the way it would be wired, with the Ardunio coming off one voltage regulator and the servo off another, with a common ground. A spare button and LED connected via a breadboard filled the role of the fascia-mounted hardware, which was already in place. After confirming it worked as expected, I installed it in the layout.
To allow it to be removed for maintenance and servo replacement, I used a small amount of Blu Tack on the base of each leg, to stick it into the concrete feet. I added a water level indicator, made from a coffee stirrer, thread and some styrene rod.
Here's a video of it in action: