R Zach



Tide Tank project

The Tide Tank is an Arduino-based system that keeps the water level in a fish tank proportional to the locally measured tide.

I worked on the Tide Tank project at Rocking the Boat, an awesome nonprofit organization in the South Bronx that teaches kids how to build wooden boats from scratch.

Here's a quick overview movie about the project; many more project details as well as source code and schematics below the fold if you're interested.

Combining elements of electrical work, microprocessor trickiness, mechanical design, and a touch of plumbing, this was a multifaceted project and I really enjoyed working with the great students at Rocking the Boat to bring it to completion.

As usual, there were a series of challenges/difficulties that came about, including:

But through the grace of lots of troubleshooting hours (including a moment when I printed out the entirety of the Arduino code and laid it on a table so I could try to understand what was going wrong) the water level in the tank is actually consistently modulating as it's supposed to.

System overview

Here's the setup:

The tide tank is always being filled with the overflow from the big tank, which is itself always being filled by a pump from the sump tank. To modulate the water height in the tide tank, the system changes how closed the lower drain valve is. If it's closed all the way, the tank will slowly fill up to the high drain point, 100% full. If the valve's fully open, the tank will quickly drain out to the low drain point, 0% full. (Both of these drains lead into the sump.)

The system's basic control scheme is actually pretty simple:

  1. Go online and find the most recently measured water level from the NOAA server via an API request (url the Arduino loads)
  2. Using that height number, compute the percentage the tank should be filled
  3. Compare the percent the tank should be filled with that as it's measured:
    • If the water is too low, open the tank's drain valve by 10%
    • If the water is too high, close the tank's drain valve by 10%

#1 above is done every 6 minutes normally; but if the last connection attempt fails, it will reattempt every 10 seconds to try to get back online. #3 runs continually, even if the last connection attempt failed.

Design and building process

Initial testing

The Arduino knows the tank's water level using a long float arm at the end of a fixed potentiometer; the changing water height moves the float up and down, and the float turns the potentiometer through a total of ~30° in the configuration we use.

The first bit of woodworking was to make a stick about 2' in length with a hole drilled in one end to grab the potentiometer.

Once the part was made, our first task was to check that this way of measuring the water height would afford the resolution we wanted for the tank. The testing pictured above proved that the Arduino's analogRead could reliably detect a change of about 1/16" measured at the float end of the stick—definitely sufficient for our purposes.

The left column in this notebook page is the absolute position on a ruler where the observation of an analogRead change was seen, and the right column is the difference between neighboring readings. (Click mouse to expand.)

Satisfied that we could read water height, now we wanted to know that we'd be able to adjust the drain flow rate. The drain pipes on the tank are 1 1/2" diameter so I picked up a PVC ball valve at Grainger that we wanted to put in the drain line. Then, attaching a servomotor to the valve, we could get the Arduino to control the outflow. Easy peasy! Nope.

Sticky! Very sticky!

Turns out the PVC valves were really hard to turn—so much so that it seemed very unlikely that our motor would be able to operate the valve. Rather than attaching the motor and watching it stall out, chew up its gears, or overheat, we decided to test just how much torque was needed to operate these valves. If the motor could handle the load, we'd proceed apace, and if not we'd explore other options. But Rocking the Boat has no torque wrench. How to figure out the torque required to operate the valve?

An unexpected torque lesson

Aha! Great opportunity for a lesson on torque and improvisation. On the spot we made our own torque-testing apparatus and put it to work. Torque is the product of force and distance. We can measure distance with a tape measure, and we can produce a known force with a known weight—in this case, a 2 liter Coke bottle filled with water (which conveniently exerts ~20N of force towards the center of the earth).

Student notebook sketch illustrating the principle. The weight of the water bottle is operating the valve. Click and hold mouse over to expand.

Holding the ball valve stationary on the table, we attached a long stick to the valve handle. Dangling a 2 liter bottle filled with water at different points along this stick, we measured how far along the bottle had to dangle to move the valve.

Our testing showed that the valve took a force of about 44 cm•kg to open, and about 61 cm•kg to close. We ended up testing two of these valves, one from Grainger and the other from McMaster, and even after adding a drop or two of vegetable oil onto the moving faces, the valves were still very hard to operate. Our servomotor, a "heavy duty" MG995 TowerPro, is rated to 10 cm•kg. We concluded: no way is that motor going to move the valve.

Making our own valve

Then we had to come up with an alternative way to control the rate at which water drained from the tank. Alyssa sketched out the solution we thought would work:

This is a drawing of a sliding valve mechanism: the rectangle (with squiggly lines running through it) is moved up and down, variably occluding and exposing the face of the drain pipe.

Alyssa drew plans for a motor mount which would hold the motor stationary against the side of the tank and allow it to manipulate the valve.

This is an end-on drawing of the dimensions of a piece of channel that the slide valve would run up and down; the 1/4" divot was for the plexiglas.

But it didn't work. The force required to slide the valve face up and down was greater than the motor could consistently deliver. After hours of troubleshooting, we settled on another valve design: a flap valve, where the water's pressure trying to escape through the drain helps keep the seal. Think of an airplane door—you want the pressure to push the door closed, not push it open. Same as with our flap valve.

The entire valve assembly. The valve's face hinges along its bottom edge; when the servomotor turns clockwise that pushes down on the wooden stick, which forces the valve away from the drain. (Of course usually the motor assembly is attached to the side of the tank, not dangling in the air.)

Electronics, code, headaches, resolutions

Arduino sandwich I made a four-layer Arduino sandwich to serve as the control module. From bottom to top, it was an Arduino Uno; an ethernet shield; an Adafruit wing shield; and an Adafruit LCD shield. I really like these LCD shields because they let me play with backlight colors easily in code (fun/useful) and give me nice debounced buttons (useful). I used the same shield on the Slowpoke project, too.

Sandwich from the side, loose in its enclosure. The two black wires (Adafruit again to the rescue) let me break the ethernet and USB out from the sandwich to the enclosure exterior.
The wing shield and LCD shield were great first soldering projects for the high schoolers, who were very proud of their new electronic skills!

Inconsistent reads Testing the Arduino's ability to get a good reading of the potentiometer, we ran into some really strange inconsistencies in the analogRead values. They'd hop around quite a bit, usually within a range of about 10 to 20 steps out of the available 1,024. Eventually we found that this was seemingly caused by electrical noise from the 9V adapter we were using to power the Arduino. Instead of using that adapter, we switched to a cheap cell phone charger running the board through its USB input, and the readings steadied out.

Calibration data storage trouble The system needs to be calibrated to know what float sensor position represents 100% full and what position is 0%. Once it knows these, it can just scale all the values in between, using the map function. One hiccup was my intention to store this data on the SD card that can be slotted into the ethernet shield. I was adding this function to the code, but including SD.h (the SD header library) in the main sketch caused it to run way over the limit of the Arduino Uno running the system. I had to abandon using the SD for storage. I used EEPROM to store this value instead (a better idea anyway). I actually hadn't known that EEPROM is non-volatile (i.e. it doesn't clear when you power cycle the Arduino). Useful to have finally learned this, and a little embarrassed it took me so long!

EEPROM only stores 8 bits per address I'm trying to store an analogRead value into the EEPROM and retrieve it when needed. However, analogRead values range 1–1023 and the highest value that EEPROM can store at one address is 255 (an 8 bit value). I solved this the way everyone else does: turn every analogRead value into two different values, each of which can be stored individually in EEPROM since they won't run over 255. Then put the two values back together when you want to get the original:

To store analogRead 987 into two values:
  1) 987 ÷ 4 = 246.75. Keep the integer part only, save to A.
2) 987 modulo 4 = 3. Save this value to B.

To recompose the original number, perform (A × 4) + B.
Neither A nor B will ever exceed 255, so you're assured that you can store any analogRead that comes along. To see this process in the Arduino code, look at the "read_write_ints_to_EEPROM" tab.

Motor doesn't work at first The system as originally implemented aims to give Rocking the Boat maximum reconfigurability in the future so if they move the tank, or want to put the control box in another spot, it will be easy to do. We ran the potentiometer signal through a phone wire to the control box, and also ran the servo power and signal through a phone line. However, the servo wasn't working and it wasn't until testing with a voltmeter that we saw why: it was only getting about 2.5V, even though it was wired to a 5V source. Apparently the 50' phone line cut the voltage in half! The problem was solved by bringing motor power right to the motor's junction box:

The black cylinder at the lower right corner of the removed lid is the female power jack from the 5V 2A supply. Remember to tie all the grounds together or the servomotor won't behave!

Data source for the water height You might've noticed the display reads "KngsPt=" and the height—why? Because Kings Point in Queens, NOAA station number 8516945, is the closest tide-measuring and internet-readable weather station to Rocking the Boat's Hunts Point location in the Bronx. Of course there is variation between the two locations, up to about one foot of difference depending on many factors. But the water height is close enough between both locations for our purposes. Also: thanks NOAA for your excellent free-as-in-beer API and well written documentation! Your and my tax dollars at work.

Some ethernet IP silliness Spent a few hours of my life trying to convince the ethernet shield to pull the NOAA data, which it just didn't want to do. It was online—that much I could tell by diagnostic Serial.print messages—but simply wouldn't get the NOAA data. An internet hero on the Arduino forum figured out that the particular type of domain name server that NOAA uses was causing the problem. The workaround was to skip the DNS entirely and access the server via IP. Thanks for the help, SurferTim.

Coda: the moving finish line

The system has mostly been working pretty well since installation, though it's certainly not hiccup-free. The valve motor burned out a few weeks after it was put into service, for no clear reason. (At least not clear to me.) And since a replacement motor has also started acting up (though it still works), I've asked Rocking the Boat to try installing a more powerful servomotor to see if that will solve the problem.

Another problem that seemed to crop up occasionally and without any discernible pattern is that the system would fail to get new NOAA data for hours in a row; after being manually restarted the system usually worked just fine, though, and got right online. Apparently this transient problem solved itself and hasn't been an issue for a while.

Here's a timelapse I shot at the end of October 2014, covering about a half hour. It shows the tank filling up (at the start of the movie the drain had been held open so the water was quite low) and then getting into the usual cycle of too high—drain opens—too low—drain closes—repeat. If the motor were more responsive the distance between too-high and too-low would be significantly reduced, so hopefully the new motor installation will help with this.

Look at that little snail go!

Also note the way the valve control stick is going a bit back and forth even while the tank is filling, though it should be parked in one position...this is a servomotor problem, like the logic gives out after a few weeks or something. Odd.

Despite the few problems, the system is still mostly working and keeping the level around where it's supposed to be. Thanks to everyone at Rocking the Boat for graciously letting me into your space and thanks to the students for all of your hard work! Thanks Rachel Daugherty and Sam Marquand who were both super helpful throughout the process, and Joaquin Cotten who let me borrow equipment freely and repeatedly. And special thanks to Adam Green, RTB's Executive Director, who had the idea for the tide tank in the first place and whose answer to all my requests was an enthusiastic "yes!"

If you're ever in the South Bronx, stop in at RTB and check it out—they're actually great—and say hey to the snails for me. ⁂


Arduino source code. (Unzip the folder and open the Tide_Tank_v__1_0_2b.ino file. The other sketches are actually just tabs in that main sketch.)

Electrical schematic. (Right or control click to save.)

Cheat sheet: how to read the Tide Tank display, with quick explanation. (Right or control click to save.)

Creative Commons License  2014 Robert Zacharias