Introduction: Farmio: a Farmer Assistant Robot for Precision Agriculture

About: I like to learn, like to make, like to share.

Water and fertilizer are extensively used in modern agriculture. They are an effective and economical way to enhance yield quality and quantity, thus ensuring food security for the ever-growing population around the globe. However excessive use of fertilizers damages the plants and reduces soil fertility. Long-term use of fertilizer reduces the microbial activity and disturbs the pH of the soil. Diverse pesticides and excessive use of fertilizers directly or indirectly pollute air, water, soil, and the overall ecosystem which cause serious health hazard for living beings. A report from the Intergovernmental Panel on Climate Change finds that about 30% of global emissions leading to climate change are attributable to agricultural activities, including pesticide and chemical fertilizers use. Excessive use of water to produce crops causes the water level of the ground to go deep down as a result shortage of pure drinking water is a big threat to us. To grow 1kg of paddy requires 2500 liters of water, but farmers of Bangladesh are using 3300 liters of water to produce 1kg of paddy which means 800 liters are just wasted. In India, the numbers are much higher, almost 5500 liters.

We all know that we just can’t stop using fertilizer and groundwater to save the environment because that will lead us to less production of crops and will create a threat to the food security of the world. But these heavy numbers really influenced us to find a solution to reduce this pollution at a minimum rate which will be much more eco-friendly and will also increase the production rate a lot. Farmio is the solution to this.

Farmio is an autonomous and intelligent robot that will allow the farmers to measure important soil parameters like soil moisture, temperature, electrical conductivity, NPK, and pH and automatically apply the right amount of nutrients in the soil based on the measured parameters and plant type. It also stores the soil parameters of every point of the field for further analysis.

Farmio facilitates accurate nutrient management, which helps to sustain the ecosystem. Farmers can reduce the danger of over-application and consequent environmental pollution by applying fertilizers and soil amendments in the right proportions by precisely measuring the levels of soil nutrients. This focused strategy promotes ecologically friendly farming methods, reduces chemical runoff, and conserves natural resources.

Original Source: https://www.hackster.io/taifur/farmio-a-farmer-assistant-robot-for-precision-agriculture-3e5f0c

Supplies

Hardware:

  1. Infineon PSoC™ 62S2 Wi-Fi BT Pioneer Kit: This is the main microcontroller unit. This development board has a PSoC 62S2 ARM 32-bit microcontroller with WiFi and Bluetooth radio. The board has a Arduino shield compatible header and any Arduino shield can be easily attached.
  2. Seeed Studio Base Shield V2: As the MCU board has an Arduino shield-compatible header I used the Base Shield here for attaching TFT Display shield, motor driver, relay and display through Grove cable. Connection is just plug-and-play type.
  3. Waveshare 2.8 inch TFT Display Shield: The 2.8-inch Arduino TFT shield will be used here for providing some important touch input to the robot and monitoring some useful information.
  4. Stepper Motor, Mini Step: Steeper motor is used to move the soil sensor vertically to the soil for measuring to soil data and then move up the sensor after measuring.
  5. SparkFun Stepper motor driver board A4988: This driver is required for controlling the stepper motor.
  6. Cytron Technologies 30Amp 7V-35V SmartDrive DC Motor Driver (2 Channels): For moving the robot car 4 high-power Gear motor with wheel is used. This high-power motor driver is used for driving the gear motors. This is a dual channel motor driver and each channel can handle 30 A current.
  7. 15Pcs 200mm Optical Guide Bearing Housing: With the help of a stepper this optical guide set helps to move the soil sensor vertically with very precise control.
  8. 7in1 Integrated Soil Sensor: This is a high-quality soil sensor that is used to measure 7 important soil parameters including NPK and PH.
  9. M3 Small Metric Screws with Nuts: For attaching different hardware and motors with base.
  10. DC Gear Motor with Wheel: 4 high-quality 8-inch robot wheel is used.


Software:

  1. ModusToolbox: ModusToolbox is the main SDK for Infineon PSoC microcontroller and with the help of Eclipse IDE this toolbox was used for the firmware development of the robot.
  2. Eclipse IDE for ModusToolbox
  3. Free RToS: Real-time operating system used for firmware development.

Step 1: Hardware Design for Farmio

The main part of the Farmio autonomous robot is PSoC™ 62S2 Wi-Fi BT Pioneer Kit. For driving the robot I used 4 high-power DC gear motors. For controlling the motors I used a Cytron 30A dual-channel motor driver. The driver can be controlled by two PWM and two digital pins of the PSoC board. The driver is directly powered by a 3-cell Lipo battery. Some components of the robot require 5V and for getting 5V I am using a Pololu 5V buck converter that converts 12 V to 5V. In my robot, I am using two displays. One TFT display is used to draw the track of the robot which track will be followed by the robot to take the soil reading and apply fertilizer. The track will be drawn in the display by touch input. The TFT display shield I used here is Arduino header compatible. I used a Grove base shield for Arduino to attach the TFT shield to the PSoC board.

Track distances can be adjusted by the Capsense button and slider (touch-sensitive buttons and sliders). The robot will follow the track drawn on the TFT and read the data from all crossing points on the line.

The main sensor for measuring the soil parameters is a 7in1 Modbus soil sensor. This high-quality soil sensor can measure 7 important soil parameters soil temperature, soil moisture, soil pH, sodium, Nitrogen, and Potassium. The sensor provides a 12V Modbus signal, so a Modbus to TTL serial converter module is used for connecting the sensor with the microcontroller board. The sensor has 5 metal leads that need to enter inside the soil. For measuring the parameters a vertical movement is achieved through a steeper motor.

I am using another graphics display to show important messages and measured soil parameters. The display is a 128X64 graphics display with an ST7920 serial driver.

Step 2: Preparing the Base of the Robot

The completed robot will be a heavy one in weight. So, the base of the robot should be strong enough that carry at least 25 kg. For this reason, I used 12mm thick plywood as the base of the robot. The board size is 1.25 feet X 1.75 feet. A 6cm hole is made on the center of the board for sensor movement.

Four high-power gear motor is placed on the bottom side of the plywood board.

On the top side of the PSoC board, the Cyteron Motor driver and breadboard are placed using some M3 screws. The vertical sensor mechanism unit is made separately on another wood piece. Two pieces are attached using an angle clip and M3 screws. On the back side of the fertilizer pot, the nozzle and liquid pump are placed.

A liquid pump is responsible for spraying fertilizer automatically with the help of a nozzle. The microcontroller reads the soil parameters from the sensor, decides the amount of fertilizer, and sends the command to the pump accordingly to turn it on or off.

Step 3: Vertical Lifting Mechanism

For reading the soil parameter I used a 7in1 soil sensor that can measure soil moisture, temperature, conductivity, pH, nitrogen, phosphorus, and potassium. The sensor supports the Standard Modbus-RTU protocol with a default baud rate of 9600. I used a MAX485 module, a low-power transceiver for TTL to RS485 communication to connect the sensor with the PSoC board.

For measuring the soil parameters, the sensor leads need to enter inside the soil. I made a vertical lifting mechanism using a stepper motor for vertically moving the sensor.

The following short video demonstrates the soil sensor movement towards the soil. The metal leads of the sensor enter into the soil by the pressure of the stepper motor. It waits 5 seconds to read the soil parameters and pull out the sensor again to its initial position.


Step 4: Developing Firmware for the Robot

As already stated the firmware for the robot was developed using ModusToolbox software and Eclipse IDE. Before making the robot I started developing the firmware. First I started with the TFT display. Several middleware are used in my project. For driving the TFT display I used the Segger emWin library.

Emwin Middleware has a rich set of functions for visualizing graphics, text, and animation. As I used Shield I had no hassle regarding connections. I found time to focus on code development. The emWin library requires a custom driver file for proper working and compilation. I developed the driver file with the help of the sample code provided with te display.

Motor driver interfacing was very easy. It just needs two PWM signals to control the motor speed of two channels and two digital signals for changing the direction of the motors.

The sensor was interfaced using serial protocol. So, the serial read-write command was enough for the sensor.

The main code is here:


/**********Pin Maping**************************************
*
* A0, A1, A2 -> used by graphics display
* P6_0, P6_1 -> used by AN pin of the motor driver
* D3, D4, D7, D9, D10, D11, D12, D13 -> used by TFT LCD
* D5, D6 -> used by motor driver IN pin
* IO5, IO5, IO4, IO3 -> used by soil sensor
* IO2, IO1, IO0 -> used by stepper motor controller
* D8 -> used by stepper limit switch
* D2 -> used by pump control
*/

/*******************************************************************************
* Header Files
*******************************************************************************/
#include "cyhal.h"
#include "cybsp.h"
#include "cy_retarget_io.h"

/* TFT GUI header */
#include "mtb_hx8347.h"
#include "GUI.h"
#include "mtb_xpt2046.h"
#include "touch.h"


/* FreeRTOS headers */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include

/* handle file system */
#include "FS.h"
#include

/* DC Motor Control header for running the bot*/
#include "cytron_motor_driver.h"

/* Grapics display control */
#include "mtb_st7920_serial.h"


/* Capsense control */
#include "capsense_task.h"
#include "gui_task.h"

/* Soil sensor & Stepper motor driving */
#include "stepper_motor.h"
#include "soil_sensor.h"

/* Stnadard C lib */
#include

/*******************************************************************************
* Macros
*******************************************************************************/
/* SPI baud rate in Hz */
#define SPI_FREQ_HZ (10000000UL)
/* Delay of 1000ms between commands */
#define CMD_TO_CMD_DELAY (1000UL)
/* SPI transfer bits per frame */
#define BITS_PER_FRAME (8)

#define NUM_BYTES_TO_READ_FROM_FILE (256U) //define your size
#define FILE_NAME "Soil_data.txt"


/* Priorities of user tasks in this project. configMAX_PRIORITIES is defined in
* the FreeRTOSConfig.h and higher priority numbers denote high priority tasks.
*/
#define TASK_CAPSENSE_PRIORITY (configMAX_PRIORITIES - 1)
#define TASK_GUI_PRIORITY (configMAX_PRIORITIES - 2)
#define TASK_RUN_BOT_PRIORITY (configMAX_PRIORITIES - 3)
#define TASK_GRAPHICS_PRIORITY (configMAX_PRIORITIES - 4)
#define TASK_EMFILE_PRIORITY (configMAX_PRIORITIES - 5)


/* Stack sizes of user tasks in this project */
#define TASK_CAPSENSE_STACK_SIZE (256u)
#define TASK_GUI_STACK_SIZE (256u)
#define TASK_GRAPHICS_STACK_SIZE (256u)
#define TASK_RUN_BOT_STACK_SIZE (256u)
#define TASK_EMFILE_STACK_SIZE (512U)
//#define TASK_GUI_STACK_SIZE (configMINIMAL_STACK_SIZE)

/* Queue lengths of message queues used in this project */
#define SINGLE_ELEMENT_QUEUE (1u)

#define SPRAY_PUMP CYBSP_D2
#define LEFT_MOTOR_DIR_PIN CYBSP_D5
#define RIGHT_MOTOR_DIR_PIN CYBSP_D6
#define LEFT_MOTOR_SPEED_PIN P6_0
#define RIGHT_MOTOR_SPEED_PIN P6_1
/*******************************************************************************
* Global Variables
*******************************************************************************/
cyhal_spi_t mSPI;
cy_rslt_t rslt;

static TaskHandle_t emfile_task_handle;
static char file_data[NUM_BYTES_TO_READ_FROM_FILE];

float soil_ph, soil_temperature, soil_moisture;
int soil_conductivity, soil_nitrogen, soil_phosphorous, soil_potassium;
int soil_data_available = 0;
int point = 0;
int n_threshold, p_threshold, k_threshold, threshold;
int point_x, point_y;
int soil_data_read_happen = 0;
int file_read_flag = 0;
/*******************************************************************************
* Function Prototypes
*******************************************************************************/
void read_soil_data(void);
void spray_fertilizer(int amount);


/*******************************************************************************
* Function Definitions
*******************************************************************************/
void handle_error(cy_rslt_t status)
{
if (CY_RSLT_SUCCESS != status)
{
/* Halt the CPU while debugging */
CY_ASSERT(0);
}
}

/*******************************************************************************
* Task Definitions
*******************************************************************************/
static void task_graphics(void* arg)
{

ST7920_Init();

ST7920_SendString(0,0, "I AM FARMIO!");
ST7920_SendString(1,0, "I");
ST7920_SendString(2,0, "ASSIST FARMERS!");
//ST7920_SendString(3,0, "FARMERS!");
vTaskDelay(5000);
ST7920_Clear();
char value[20];

while(1){

if(soil_data_available){
sprintf(value, "%.2f", soil_ph); //convert float value to string
ST7920_SendString(0,0, value);

sprintf(value, "%.2f", soil_temperature); //convert float value to string
ST7920_SendString(0,10, value);

sprintf(value, "%.2f", soil_moisture); //convert float value to string
ST7920_SendString(1,0, value);

sprintf(value, "%d", soil_conductivity); //convert float value to string
ST7920_SendString(1,10, value);

sprintf(value, "%d", soil_nitrogen); //convert float value to string
ST7920_SendString(2,0, value);

sprintf(value, "%d", soil_phosphorous); //convert float value to string
ST7920_SendString(2,10, value);

sprintf(value, "%d", soil_potassium); //convert float value to string
ST7920_SendString(3,0, value);
}
}
}


static void task_run_bot(void* arg)
{
//initialize touch
xpt2046_init();
//calibrate the touch
tp_adjust();
tp_dialog();
//initialized stepper motor
init_stepper();
//initialized soil sensor
init_soil_sensor();
//initialize the motor driver
cytron_motor_driver_init_pwm(PWM_INDEPENDENT, RIGHT_MOTOR_DIR_PIN, LEFT_MOTOR_DIR_PIN,
RIGHT_MOTOR_SPEED_PIN, LEFT_MOTOR_SPEED_PIN);

while(1){
//calculate_track_point(); //read the map and point to measure
if(point == 1)
cytron_motor_driver_control(40, 40); //speed 40 percent & forward
else if(point == 2)
cytron_motor_driver_control(-10, 40); //speed 40 percent & left
else if(point == 3)
cytron_motor_driver_control(40, -10); //speed 40 percent & right
else if(point == 4)
cytron_motor_driver_control(-40, -40); //speed 40 percent & backward
else if(point == 3)
cytron_motor_driver_control(0, 0); //motor stop

//read the soil
read_soil_data();

//spray fertilizer based on the soil data
if(soil_nitrogen int amount = threshold - (soil_nitrogen + soil_phosphorous + soil_potassium)/3;
spray_fertilizer(amount);
}
}

}

/*******************************************************************************
* Function Name: emfile_task
********************************************************************************
* Summary:
* Formats the storage device, reads the content from a file and prints the
* content to the UART terminal, writes a message to the same file, and waits
* for the user button press. When the button is pressed, deletes the file and
* returns.
*
* Parameters:
* arg - Unused.
*
*******************************************************************************/
static void task_emfile(void* arg)
{
U32 volume_size;
U32 num_bytes_to_read;
int error;
FS_FILE *file_ptr;
const char *volume_name = "";

//printf("Using SD card as storage device\n");

/* Initialize the file system. */
FS_Init();

/* Check if volume needs to be high-level formatted. */
error = FS_IsHLFormatted(volume_name);
//check_error("Error in checking if volume is high-level formatted", error);

/* Return value of 0 indicates that high-level format is required. */
if (error == 0)
{
// printf("Perform high-level format\n");
error = FS_Format(volume_name, NULL);
// check_error("Error in high-level formatting", error);
}

volume_size = FS_GetVolumeSizeKB(volume_name);
//printf("Volume size: %"PRIu32" KB\n\n", volume_size);

if(0U == volume_size)
{
//printf("Error in checking the volume size\n");
CY_ASSERT(0U);
}


while(1){

/* Open the file for reading. */
if(file_read_flag == 1){
file_ptr = FS_FOpen(FILE_NAME, "r");

if (file_ptr != NULL)
{
/* Last byte is for storing the NULL character. */
num_bytes_to_read = sizeof(file_data) - 1U;
volume_size = FS_GetFileSize(file_ptr);

if(volume_size < num_bytes_to_read)
{
num_bytes_to_read = volume_size;
}

printf("Reading %"PRIu32" bytes from the file. ", num_bytes_to_read);
volume_size = FS_Read(file_ptr, file_data, num_bytes_to_read);

if(volume_size != num_bytes_to_read)
{
error = FS_FError(file_ptr);
//check_error("Error in reading from the file", error);
}

/* Terminate the string using NULL. */
file_data[num_bytes_to_read] = '\0';

/* Display the file content. */
printf("File Content:\n\"%s\"\n", file_data);

error = FS_FClose(file_ptr);
//check_error("Error in closing the file", error);

//printf("\nOpening the file for overwriting...\n");
}
else
{
printf("Unable to read. File not found.\n");
//printf("\nOpening the file for writing...\n");
}
}

/* Mode 'w' truncates the file size to zero if the file exists otherwise
* creates a new file.
*/
//write the sensor reading
if(soil_data_read_happen == 1){
char string_to_write[150];
sprintf(string_to_write, "%d,%d,%.2f,%.2f,%.2f,%d,%d,%d,%d", point_x, point_y, soil_ph,
soil_temperature, soil_moisture, soil_conductivity, soil_nitrogen, soil_phosphorous, soil_potassium);

file_ptr = FS_FOpen(FILE_NAME, "w");

if(file_ptr != NULL)
{
volume_size = FS_Write(file_ptr, string_to_write, strlen(string_to_write));

if(volume_size != strlen(string_to_write))
{
error = FS_FError(file_ptr);
//check_error("Error in writing to the file", error);
}

printf("File is written with the following message:\n");
printf("\"%s\"\n\n", string_to_write);

error = FS_FClose(file_ptr);
//check_error("Error in closing the file", error);

FS_Unmount(volume_name);

printf("Filesystem operations completed successfully!\n");
soil_data_read_happen = 0;
}
else
{
printf("Unable to open the file for writing! Exiting...\n");
}
}
}
}

/*******************************************************************************
* Function Name: main
********************************************************************************
* Summary:
* This is the main function for CPU. It...
* 1.
* 2.
*
* Parameters:
* void
*
* Return:
* int
*
*******************************************************************************/
int main(void)
{
cy_rslt_t result;

#if defined (CY_DEVICE_SECURE)
cyhal_wdt_t wdt_obj;

/* Clear watchdog timer so that it doesn't trigger a reset */
result = cyhal_wdt_init(&wdt_obj, cyhal_wdt_get_max_timeout_ms());
CY_ASSERT(CY_RSLT_SUCCESS == result);
cyhal_wdt_free(&wdt_obj);
#endif

/* Initialize the device and board peripherals */
result = cybsp_init();
handle_error(result);

/* Enable global interrupts */
__enable_irq();

/* Initialize the retarget-io to use the debug UART port */
result = cy_retarget_io_init(CYBSP_DEBUG_UART_TX, CYBSP_DEBUG_UART_RX,
CY_RETARGET_IO_BAUDRATE);
handle_error(result);

/* Initialize the SPI to use the TFT */
result = cyhal_spi_init(&mSPI, CYBSP_SPI_MOSI, CYBSP_SPI_MISO, CYBSP_SPI_CLK,
NC, NULL, BITS_PER_FRAME,
CYHAL_SPI_MODE_11_MSB, false);
handle_error(result);

/* Set SPI frequency */
result = cyhal_spi_set_frequency(&mSPI, SPI_FREQ_HZ);
handle_error(result);

/*Initialize spray pump */
result = cyhal_gpio_init(SPRAY_PUMP, CYHAL_GPIO_DIR_OUTPUT, CYHAL_GPIO_DRIVE_STRONG, false);
handle_error(result);
cyhal_gpio_write(SPRAY_PUMP, false); //disable the pump

/* drive the motor */
cytron_motor_driver_control(150, 150); //left_speed, right_speed

printf("Starting the program\n");
/* Create the queues. See the respective data-types for details of queue
* contents
*/
bot_command_data_q = xQueueCreate(SINGLE_ELEMENT_QUEUE,
sizeof(bot_command_data_t));
capsense_command_q = xQueueCreate(SINGLE_ELEMENT_QUEUE,
sizeof(capsense_command_t));

/* Create the user tasks. See the respective task definition for more
* details of these tasks.
*/
xTaskCreate(task_capsense, "CapSense Task", TASK_CAPSENSE_STACK_SIZE,
NULL, TASK_CAPSENSE_PRIORITY, NULL);
xTaskCreate(task_gui, "Gui Task", TASK_GUI_STACK_SIZE,
NULL, TASK_GUI_PRIORITY, NULL);
xTaskCreate(task_run_bot, "Runbot Task", TASK_RUN_BOT_STACK_SIZE,
NULL, TASK_RUN_BOT_PRIORITY, NULL);
xTaskCreate(task_graphics, "Graphics Task", TASK_GRAPHICS_STACK_SIZE,
NULL, TASK_GRAPHICS_PRIORITY, NULL);
xTaskCreate(task_emfile, "emFile Task", TASK_EMFILE_STACK_SIZE,
NULL, TASK_EMFILE_PRIORITY, &emfile_task_handle);

/* Start the RTOS scheduler. This function should never return */
vTaskStartScheduler();

/********************** Should never get here ***************************/
/* RTOS scheduler exited */
/* Halt the CPU if scheduler exits */
CY_ASSERT(0);


for (;;)
{
}
}


void read_soil_data(void)
{
drive_stepper_motor_down();
soil_ph = read_ph();
soil_temperature = read_temperature();
soil_moisture = read_moisture();
soil_conductivity = read_conductivity();
soil_nitrogen = read_nitrogen();
soil_phosphorous = read_phosphorous();
soil_potassium = read_potassium();
drive_stepper_motor_up();
soil_data_available = 1;
soil_data_read_happen = 1;
}

void spray_fertilizer(int amount)
{
cyhal_gpio_write(SPRAY_PUMP, true);
vTaskDelay(amount*300);
cyhal_gpio_write(SPRAY_PUMP, false);
}

/* [] END OF FILE */


Demo video of graphics library testing:


Demo of touch sensing:


The full project with every source file is in my GitHub repository. The project is completely open source and you are free to reuse any of the files to your project with or without any modification.

Full sources are in GitHub: https://github.com/taifur20/farmio

Step 5: Final Assembly

After completing the firmware development the next thing is to assemble all the hardware components and modules. The above images show the placement of different components.

A limit switch is placed at the bottom of the virtual movement unit that allow perfect entry of the sensor inside the soil. At the back side of the robot the nozzle placement is shown. A 12V pump is connected to the nozzle that pump fertilizer from the fertilizer container that is placed beside the pump.

A 3-cell lithium battery is used for providing the power of the robot. Cabbles are organize using zip tie as shown in the images.

Robotics Contest

Participated in the
Robotics Contest