Welcome to HYDRA project!
This project is an advanced and fully automated hydroponic system for growing plants in a soilless medium using nutrient-rich water. The system utilizes a range of sensors and actuators to monitor and control the growing environment, ensuring optimal conditions for the plants at all times.
The system includes a pH sensor to measure the acidity or alkalinity of the nutrient solution, a BMP280 sensor to measure temperature and atmospheric pressure, an LED semaphore to signal when conditions are outside of the optimal range, and an OLED display to provide real-time information about the growing environment.
The system is designed to be highly energy efficient and can be easily integrated into a variety of electronic devices and systems. It can be used in a wide range of applications, including home gardening, commercial agriculture, and research.
I hope you find this project useful and we welcome any feedback or suggestions you may have. Thank you for visiting!
What is an hydroponic system?As anticipated before, hydroponics is a method of growing plants using nutrient-rich water instead of soil. In a hydroponic system, plants are grown in a soilless medium, such as perlite, vermiculite, or coconut coir, and are fed a nutrient-rich solution on a regular basis. This solution typically includes all of the essential minerals and nutrients that plants need to grow and thrive.
There are many different types of hydroponic systems, including nutrient film technique (NFT), deep water culture (DWC), aeroponics, and drip irrigation. These systems differ in the way that they deliver nutrients and water to the plants, as well as the type of growing medium that is used.
Hydroponic systems can be used to grow a wide variety of plants, including fruits, vegetables, herbs, and flowers. They are often used in greenhouse or indoor growing environments, and can be a more efficient and sustainable way to grow crops, as they use less water and pesticides than traditional soil-based agriculture.
HardwareIn this blog post we will focus on one component which were never used in previous projects of our course, that is the BMP280 barometric sensor, and another component which is the OLED display using a very powerful library, that is u8g2, which were never used before. Moreover we will talk about the pH sensor and its implementation.
Overall I adopted a multi-threaded design so every component has its own thread which handle the behaviour of its specific components. Basically all threads share a struct where they update data which is the following:
enum state {
OK,
SOMETHING_WRONG,
PANIC
};
typedef enum state state_e;
struct nutrient_solution {
int id;
float ph;
time_t last_update;
};
typedef struct nutrient_solution nutrient_solution_t;
struct environment {
int id;
bmx280_t* sensor;
time_t last_update;
};
typedef struct environment environment_t;
struct tank {
int id;
environment_t env;
nutrient_solution_t solution;
u8g2_t display;
state_e state;
time_t last_update;
};
typedef struct tank tank_t;
All the threads are managed by the master thread which is the hydra_thread
.
static void* hydra_thread(void *arg) {
(void) arg;
state_e current_state = OK;
while (1) {
current_state = get_status(global_temperature, global_pressure, global_ph);
global_state = current_state;
send(raw_temperature(global_temperature), raw_pressure(global_pressure), raw_pH(global_ph));
xtimer_sleep(3);
}
return 0;
}
BMP280The BMP280 is a small, low-power digital sensor that can measure temperature and pressure. It is commonly used in weather stations, altimeters, cars, and other applications that require accurate measurement of atmospheric pressure and temperature.
The BMP280 has a temperature measurement range of -40 to 85 degrees Celsius, with an accuracy of +/- 1 degree Celsius. It has a pressure measurement range of 300 to 1100 hPa (hectopascals), with an accuracy of +/- 1 hPa. The sensor has a high resolution of 0.18 hPa for pressure measurement, which allows for highly accurate readings at different altitudes.
It operates on a voltage of 1.8 to 3.6 volts and has a low power consumption of only 1.8 microamps in power-down mode.
I2C interface
The I2C interface uses the following pins:
- SCK: serial clock (SCL)
- SDI: data (SDA)
- SDO: the I2C address decides the pin. If SDO connects to GND (0), the address is 0x76, if it connects to VDDIO (1), the address is 0x77. In this module, we have connected it to VDDIO, so the address should be 0x77.
- CSB: Must be connected to VDDIO to select I2C interface
Thread
static void *bmp280_thread(void *arg) {
(void) arg;
while (1) {
/* acquire mtx */
mutex_lock(&bmp280_lock);
/* read temperature */
int16_t temp = 0;
temp = bmx280_read_temperature(&bmp280);
if (temp == INT16_MIN) {
printf("[-] Temperature value read did not succeed!\r\n");
mutex_unlock(&bmp280_lock);
exit(127);
}
else
uint32_t pres = 0;
pres = bmx280_read_pressure(&bmp280);
float pres0 = 1013.25;
float base = (pres / pres0);
float exp = (1 / 5.255);
float p = pow(base, exp);
float h = 44330 * (1 - p);
// update global value
global_temperature = temp;
global_pressure = pres;
}
/* release mtx*/
mutex_unlock(&bmp280_lock);
xtimer_sleep(3);
}
return 0;
}
SEN0161A pH sensor is a device that is used to measure the acidity or alkalinity of a solution. It works by detecting the concentration of hydrogen ions in a solution and expressing the results on a pH scale, which ranges from 0 to 14. A pH of 7 is neutral, while a pH below 7 is considered acidic and a pH above 7 is considered alkaline.
pH sensors are commonly used in a wide variety of applications, including water treatment, food and beverage processing, pharmaceutical manufacturing, and environmental monitoring. They are also used in hydroponic systems to monitor the pH of the nutrient solution and ensure optimal growing conditions for plants.
Thread
static void *sen0161_thread(void *arg) {
(void) arg;
while (1) {
/* acquire mtx */
mutex_lock(&sen0161_lock);
int sample = 0;
float mapped_voltage = 0.0;
float ph = 0.0;
float min_ph_value = 0; // pH is Adimensional, Turbidity is 0 mg/L
float max_ph_value = 14.0;
sample = adc_sample(ADC_IN_USE, ADC_RES);
if (sample == -1) { // Error with sample resolution
printf("[-] ADC_LINE(0): selected resolution not applicable\n");
}
mapped_voltage = sample * (5.0 / pow(2, 12));
ph = adc_util_mapf(sample, ADC_RES, min_ph_value, max_ph_value);
if (sample < 0) {
printf("ADC_LINE(%u): selected resolution not applicable\r\n", ADC_IN_USE);
}
global_ph = ph;
/* release mtx*/
mutex_unlock(&sen0161_lock);
xtimer_sleep(3);
}
return 0;
}
OLED displayThe OLED display is used to display real-time information about the pH, temperature, and atmospheric pressure in the growing environment. This information can be used by the grower to make informed decisions about the care and maintenance of the plants.
I2Cinitialization
/* initialize to I2C */
printf("[+] Initializing oled display to I2C...\r\n");
TEST_DISPLAY(&u8g2, U8G2_R0, u8x8_byte_hw_i2c_riotos, u8x8_gpio_and_delay_riotos);
u8x8_riotos_t hydra_data = {
.device_index = I2C_DEV(0),
.pin_cs = TEST_PIN_CS,
.pin_dc = TEST_PIN_DC,
.pin_reset = TEST_PIN_RESET,
};
u8g2_SetUserPtr(&u8g2, &hydra_data);
u8g2_SetI2CAddress(&u8g2, TEST_ADDR);
/* initialize the display */
printf("Initializing oled display...\r\n");
u8g2_InitDisplay(&u8g2);
u8g2_ClearDisplay(&u8g2);
u8g2_SetPowerSave(&u8g2, 0);
Thread
static void *sh1106_thread(void *u8g2) {
(void) u8g2;
uint32_t screen = 0;
char* tmp_string = NULL;
char* pres_string = NULL;
char* ph_string = NULL;
uint16_t temperature = 0;
uint32_t pressure = 0;
float ph = 0.0;
/* start drawing in a loop */
printf("[+] Drawing on screen...\r\n");
while (1) {
// printf("First page...\r\n");
u8g2_FirstPage(u8g2);
do {
u8g2_SetDrawColor(u8g2, 1);
u8g2_SetFont(u8g2, u8g2_font_helvB12_tf);
switch (screen) {
case 0:
u8g2_SetFont(u8g2, u8g2_font_helvB08_tf);
u8g2_DrawStr(u8g2, 8, 16, "Welcome to");
/* logo print */
u8g2_SetFont(u8g2, u8g2_font_helvB12_tf);
u8g2_DrawStr(u8g2, 54, 40, "HYDRA");
break;
case 1:
// printf("Case 3\r\n");
u8g2_SetFont(u8g2, u8g2_font_helvB08_tf);
u8g2_DrawStr(u8g2, 8, 16, "Powered by");
u8g2_DrawBitmap(u8g2, 54, 20, 8, 32, riot_logo);
break;
case 2:
tmp_string = str_temperature(global_temperature);
pres_string = str_pressure(global_pressure);
u8g2_DrawStr(u8g2, 6, 12, tmp_string);
u8g2_DrawStr(u8g2, 6, 36, pres_string);
free(tmp_string);
free(pres_string);
break;
case 3:
ph_string = str_pH(global_ph);
u8g2_DrawStr(u8g2, 6, 12, ph_string);
free(ph_string);
break;
case 4:
u8g2_SetFont(u8g2, u8g2_font_helvB08_tf);
if (global_state == OK) {
u8g2_DrawStr(u8g2, 6, 12, "Everything is all right!");
}
else if (global_state == SOMETHING_WRONG) {
u8g2_DrawStr(u8g2, 6, 12, "We have an error...");
}
else {
u8g2_DrawStr(u8g2, 6, 12, "Call the boss!!!");
}
break;
}
} while (u8g2_NextPage(u8g2));
/* show screen in next iteration */
screen = (screen + 1) % 5;
/* sleep a little */
xtimer_sleep(2);
}
return 0;
}
U8g2 libraryU8g2 is a monochrome graphics library for embedded devices for LCDs and OLEDs. It contains both drivers and high-level drawing routines. The library is originally written for Arduino boards, but it runs just fine on other platforms, as long as the right drivers are available. In our case we will use it along RIOT OS.
Generic usageIn order to use u8g2 library put USEPKG += u8g2
in your Makefile
and #include "u8g2.h"
into your code. Refer to the U8g2 wiki for more information on the API.
RIOT-OS interface
This adds an interface for RIOT-OS. The following two callbacks add support for the included drivers via I2C and SPI peripherals:
u8x8_byte_hw_spi_riotos
u8x8_byte_hw_i2c_riotos
For timing and GPIO related operations, the following callback is available.
u8x8_gpio_and_delay_riotos
These methods require a structure containing peripheral information (u8x8_riotos_t
), that is set using the u8g2_SetUserPtr
function. This structure contains the peripheral and pin mapping.
If the above interface is not sufficient, it is still possible to write a dedicated interface by (re-)implementing the methods above.
u8g2_t u8g2;
u8x8_riotos_t user_data =
{
.device_index = SPI_DEV(0),
.pin_cs = GPIO_PIN(PA, 0),
.pin_dc = GPIO_PIN(PA, 1),
.pin_reset = GPIO_PIN(PA, 2)
};
u8g2_SetUserPtr(&u8g2, &user_data);
u8g2_Setup_ssd1306_128x64_noname_1(&u8g2, U8G2_R0, u8x8_byte_riotos_hw_spi, u8x8_gpio_and_delay_riotos);
Virtual displays
For targets without an I2C or SPI, virtual displays are available. These displays are part of U8g2, but are not compiled by default.
- By adding
USEMODULE += u8g2_utf8
, a terminal display is used as virtual display, using UTF8 block characters that are printed to stdout. - By adding
USEMODULE += u8g2_sdl
, a SDL virtual display will be used. This is only available on native targets that have SDL installed. It uses `sdl2-config` to find the headers and libraries. Note that RIOT-OS builds 32-bit binaries and requires 32-bit SDL libraries.
u8g2_t u8g2;
u8g2_SetupBuffer_Utf8(&u8g2, U8G2_R0)
Demo videoHere you can find a video demonstration of the system.
Comments
Please log in or sign up to comment.