Maybe this has happened to you, but I killed quite a few plants over the last years when leaving my home for vacation or just forgetting to water them. So I thought about automating the task to save future plant lifes. This article describes the resulting project.
I use an Arduino Uno together with the TLE94112 multi-half-bridge from Infineon to create an automated water pump for my home plants.
For an introduction to these parts and on how to get familiar with the corresponding library, please refer to this hackster article. I also assume, that you have some basic knowledge about Arduino and C++ programming.
HardwareThe Arduino and the TLE94112 go together like any other Arduino shield.
The tricky part is the connection of the pump to the TLE94112, since we need several half-bridges to service the pump with enough current. In my case, eight in total, 4 high-side and 4 low-side half-bridges, to provide up to 3.6A to the pump. The catch is, that is is more than double the current listed in the product page of my pump, since most manufactures only include the average current load of their product, but for us, the peak current during on switching is limiting, since the TLE94112 will go into emergency shutdown if the limit of 0.9A per half-bridge is exceeded.
This is why I soldered an adapter from 2 x 4 to 2 wires out of three screw-terminals:
The connection at the pump depends on which pump you chose. In my case it came with pre-connected wires.
One further note about DC-Pumps: In my case, the motor in the pump spins in the same direction, regardless of the polarity of the connections. For this project, that's not an issue, but keep this in mind if you want to adapt to other applications.
Finally, connect the hose to the pump and position one end in a water reservoir (e.g. watering can) and the other one in the pot of the plant:
For now, I am using my lab-bench power supply as the power source for the whole setup. Chose a suitable source (can be battery-powered) depending on your pump. Remember that the TLE94112 connects your power source to the pump.
SoftwareSince I made this project rather expandable, the code is divided into several files, but don't worry, it is quite simple and we will go into all the details.
The project can be set up in any arduino-compatible IDE, we will use the default Arduino IDE, but e.g. Platformio works, too.
The first two files, helpers.cpp
and helpers.hpp
just contain some wrappers to enable on and off-switchable logging to a serial terminal.
Using the functions provided here, you can simply use
logger.log("hello world");
instead of
Serial.print("hello world");
The Logger
class wraps Serial.print
etc. and only prints/logs if logger.logging
is set to true
. An instance of the class needs to be created, then you can use it like the usual Serial
class:
Logger logger(500, true); //logger wraps Serial.print in order to disable logging with one variable, here it is enabled.
logger.logln("Init ready");
The first variable sets the period (in milliseconds) in which the error messages of the TLE94112 should be pulled and logged to the serial terminal (have a look at this article, section 5 for an explanation on the error messages of the board). The second one enables/disables logging altogether.
The second set of files is the plant.cpp
and plant.hpp
containing the basic function definitions of a plant class to support multiple plants being watered by the same setup.
class Plant {
private:
/* data */
uint32_t pump_duration; //pump powered time in millis
uint8_t pump_interval_days; //nr of days between pumping
Tle94112Motor *pump_motor;
public:
void pump(); //function to pump water for pump_time length
Plant(Tle94112Motor* pump_motor_p, uint32_t pump_duration_p, uint8_t pump_interval_days_p); //constructor
void setPump_duration(uint32_t t_milli);
uint32_t getPump_duration();
void setPump_interval_days(uint8_t interval);
uint8_t getPump_interval_days();
};
The header is pretty self-explanatory.
Each plant has a pump attached to it (Tle94112Motor *pump_motor;
) that should be activated for a set amount of time (uint32_t pump_duration;
) once every few days (uint8_t pump_interval_days;
)
The functions are getters and setters for these two variables and a function to activate the pumping.
I want to draw special attention to the function definition of the void pump();
function in the.cpp file:
void Plant::pump(){
pump_motor->rampSpeed(255, pump_duration);
delay(pump_duration);
pump_motor->coast();//coast the motor to save power (as opposed to forced holding)
}
As you can see, the pump motor is not activated fully at once, but the pumping speed is ramped up to full speed over the pump duration time. This "soft switching" is necessary, since switching on the motor all at once draws much more current than the TLE94112 can handle. This also typically exceeds the current draw label of the motors/pumps.
Now we already come to the plant_watering.ino
file, the core of the code.
The first few lines are just the library inclusions and definition of the serial baud rate.
#include <Arduino.h>
#include <tle94112-ino.hpp>
#include <tle94112-motor-ino.hpp>
#include <SPI.h>
#include "plant.hpp"
#include "helpers.hpp" //Logger
#define BAUD 115200
Next is the creation of all the class objects for the TLE94112, the logger and the plant. In this example, I only use one plant, but you can add additional plants in the same manner:
// Tle94112 Object on Shield 1
Tle94112Ino controller = Tle94112Ino();
// Tle94112Motor Objects on controller
Tle94112Motor motor_0(controller);
Logger logger(500, true); //logger wraps Serial.print in order to disable logging with one variable, here it is enabled.
Plant p1(&motor_0, 2000, 1); //attach the motor, the watering time and the days between watering
const uint8_t plant_nr = 1;
Plant* plants[plant_nr] = { //array of all plants, for iteration purposes
&p1
};
All plants must be saved into an array plants
of size plant_nr
.
Next are some variables to help in the scheduling of the watering tasks. This is kept out of the plant class to allow easy switching to different time-keeping schemes, like a real-time-clock. In this case however, we will use the millis();
function.
//Timing:
uint32_t millis_in_day = (uint32_t)1000*60*60*24; //calculate the milliseconds in a day, for timekeeping
// uint32_t millis_in_day = (uint32_t)1000*5; //this is the testing version to see immediate results
uint32_t last_pumped[plant_nr] = {0}; //this is not a plant::variable since timing method is independent of plant class
For testing purposes, you can simulate faster passing of time by reducing the miliseconds in a day.
The setup()
function initializes all the objects created before. The number of half-bridges per motor is also set up here. In my case I had to use the maximum of four, but depending on your pump, you might be able to use less. I recommend you have a look at this article to find out about error codes and debugging the TLE94112.
void setup()
{
// Switch on communications
Serial.begin(BAUD);
while (!Serial){};
// Enable MotorController on all Shields and Motors
controller.begin();
// IMPORTANT connect PWM to Lowside as higside is active Free wheeling
motor_0.initConnector(motor_0.HIGHSIDE, controller.TLE_NOPWM, controller.TLE_HB1, controller.TLE_HB2, controller.TLE_HB7, controller.TLE_HB8);
motor_0.initConnector(motor_0.LOWSIDE, controller.TLE_PWM1, controller.TLE_HB3, controller.TLE_HB4, controller.TLE_HB5, controller.TLE_HB6);
// start the motor controller
motor_0.begin();
motor_0.coast();
// end the setup function
logger.logln("Init ready");
}
The loop()
repeatedly checks all plants in the array, when they where watered last, and if this time exceeds the desired watering interval (in days). If so, the plant is watered. The delay at the end reduces the time between these checks.
void loop()
{
uint32_t ct = millis();
for(int i = 0; i < plant_nr; i++){
if((ct - last_pumped[i]) >= (plants[i]->getPump_interval_days()) * millis_in_day){
logger.log("Pumping Pump ");
logger.log(i);
logger.log(" at ");
logger.logln(ct);
plants[i]->pump();
last_pumped[i] = ct;
}
}
logger.error_status(controller);
delay((uint32_t)1000); //this can be increased even more
}
And that's all there is to this project. Feel free to use the code in your own setups and expand on it by e.g. using a RT-Clock or including more than one pump.
Here you can see my setup in action:
Comments