software engineer
since '05
I am a software engineer living in Oakland, CA, specializing in microservices architectures, Ruby, Go, JavaScript. I also dabble in C/C++, electronics, internet-of-things, and 3D printing. This is my website and blog where I write about technology, culture, Bay Area stuff, beekeeping, beer brewing, meadery, cidery, anythingery.


pH-adjusting robot for hydroponics, aquaria, or other applications

From the Laziness inspiring innovation department

The problem

If you’ve ever tended to a hydroponic garden of any size or description, no matter what plants you’re growing, you will have to keep the pH of the nutrient solution as steady as you can, within a range that varies from plant to plant. Fluctuations in pH can cause stunted growth from poor nutrient uptake, and can give plants a discolored appearance or a weird growth habit. Nobody wants that.

I replenish and feed my hydroponics system(s) 2-3 times a week, but I check the pH levels almost daily, and make adjustments if it’s outside of tolerances. An abrupt change in pH level can mean a bunch of different things, almost all of them bad. I wanted to automate this process so that, at worst, I would have to push a button to adjust the pH of the solution, and at best I could leave the management completely up to the machines.

The idea

I use pH buffering solutions (usually just referred to as pH Up and pH Down), so it would be useful at the very least to have a way of precisely measuring doses of pH buffers, so I figured what I’m looking at is some device that has two pumps controlled by a central controller. It would need a display to show the pH set point and currently-measured pH, and also a port for a pH probe and circuitry to interface with that.

What ensued, as I thought through the design of this seemingly-simple device, is a journey through deep rabbit-holes I didn’t even know existed.


Next step was to design the hardware of the pump. My loftiest ambition with this was to create a platform that I could use to build many different types of these, like maybe I’d like one that automatically feeds plants if it notices the nutrient density going below a threshold or adds filtered water if the density gets too high and risks burning the plants. I would settle for something that just measures pH and dispenses buffering solution.


For this to work reliably, I needed a way to pump amounts of fluid at least as precisely as I could by hand using a graduated cylinder or eyedropper. The best way I could think of was a thing called a peristaltic pump. The most succinct way I can think to describe it is that it’s a pump that works the way your gut works: by pinching a tube closed with rollers and rolling them along the tube to move liquid. Because I could very precisely control the motion of the rollers if I attached them to a stepper motor or servo, it was possible to construct a pretty precise liquid dispenser this way.

I looked around on the internet and found a lot of options for peristaltic pumps and devices for driving them. What I found was they all fell into three categories: obviously cheap pumps that were poorly reviewed, meant for aquarium use; moderately expensive units like Sparkfun’s Bartendro; and there are extremely expensive units for laboratory or medical use. The Bartendro came closest to satisfying my needs for this, as it would be a reasonably complete solution to the pump problem, but even its sale price is pretty high ($99!). The cheap aquarium units and the ultra-precise lab units were out of the question, so I turned to what I could maybe build using mostly stuff I already had.

Thankfully, Thingiverse user CopabX had me covered with a 3D-printable peristaltic pump that just needs some hardware and a NEMA 17 stepper motor to get going, and my 3D printer spare parts drawer is lousy with NEMA 17s. Seemed like the best way to go.

A peristaltic pump I made with 3D printed parts and a NEMA 17 stepper motor

I was able to print one of these up pretty fast and get it together with some bearings. I wasn’t ready to test it though; I needed to think through how I would drive these pumps. Each stepper needs 12-24V and some pretty serious current to move, whereas the microcontroller lives in a 5V world, so each stepper needs a driver. Thankfully, I have even more stepper motor drivers than stepper motors. The drivers I use are clones of the Polulu stepper motor driver incorporating the Allegro A4988. Fortunately, the rabbit-hole of learning how to use an A4988 turned out to be pretty shallow: just use two pins to control its direction and steps. For complicated maneuvers, there are friendly Arduino libraries allowing you to fine-tune the motion of the stepper motor. Perfect.


The stepper motor drivers and the stepper motor don’t know that they’re acting as a pump, so there is some amount of translation that needs to happen between an amount of liquid to dispense (measured in mL, say) and a number of steps or rotations of the motor that are required to dispense that amount. In addition to that, each pump may need a water level sensor, and maybe a sensor to tell when enough liquid has been taken up by the pump that it will dispense immediately if it’s turned (to eliminate the problem of backflow causing air pockets in the pump and messing up measurements), etc. These are all complexity expenses that will scale proportionally to the number of pumps, I didn’t know I would only ever want two pumps, and there were also a lot of I/O pins and resources needed to drive an LCD display and inputs. It made sense, then, to instead separate the pumps out so each is controlled by its own microcontroller that responds to commands on a shared bus with a beefier microcontroller as master.

I have a handful of Atmel ATtiny85 microcontrollers on hand, because they’re surprisingly capable little chips that are great in low-power applications, and these have the perfect amount of I/O pins to control a stepper motor, sense a couple of things, and communicate over I2C. A perfect choice for controlling the pumps. Each could be calibrated individually and be able to drive its pump perfectly without worrying over pH measurements or a user interface.

User interface

The user interface, however, would need something bigger. I began prototyping with a full Arduino Uno board with an Atmel ATmega328 onboard, but the final product will probably use a cheaper one. I needed it to be able to support an LCD or similar display, three buttons (pH+, pH-, and “adjust now”), and a pH sensor. An LCD would need 4 I/O pins to connect to the microcontroller, more if I wanted to be able to adjust the contrast or backlight with the microcontroller. I also would need two pins for I2C, three for the buttons, and an analog input for a pH sensor. That would probably leave enough pins to control the two pumps directly, but that wouldn’t be scalable and this way I can ensure a responsive UI and also test out the individual components separately.

The UI would be really simple, with the three buttons and display. The display just needed to show current pH, pH set point, and maybe some indicator for when it’s actively adjusting pH.

Rabbit-hole: Control theory

One big constraint of this device is that I will never have an infinite amount of pH buffering solution, as cheap as it might be. It will always necessarily be limited. This constrains how often pH can be adjusted automatically, and how much buffering solution can be used each time, and it also means I need a way of alerting when there isn’t enough pH solution left to adjust. I needed a smart algorithm for controlling the pH level, and this is where I tumbled down the first real rabbit-hole of this whole adventure: PID controllers.

A PID controller, succinctly, is a controller that accepts an error (difference between set point and measured point) and outputs a value that is used to control the process being measured. Unlike how, say, a thermostat works, just flipping a switch on if a value is too low and off if it’s too high, a PID controller’s output is related to how the error changes over time and therefore much smoother. The P, I, and D are proportional, integrative, and derivative, and for their meaning I direct you back to that huge Wikipedia article linked above. An example of a process that might be PID controlled could be a stove heating water to a given temperature. The error term is the difference between that temperature and the water’s current temperature, and the output of the PID controller could be the BTUs of gas to apply. Importantly and very usefully, the input and output of a PID controller don’t have to be measured in the same units. So we can have an error term expressed in degrees Celsius (or Fahrenheit, who cares) and an output term in BTUs and that’s totally cool. Another example could be your car’s cruise control: an error input of MPH or KPH and an output describing a throttle valve position. They are extremely common, but tuning the P, I, and D levels seems like a kind of black magic.

(c) 2005-2020 max thom stahl
find me on instagram | twitter