Difference between revisions of "Lawn Sprinkler"

From BITPlan Wiki
Jump to navigation Jump to search
 
(15 intermediate revisions by the same user not shown)
Line 22: Line 22:
 
! Description
 
! Description
 
! Quantity
 
! Quantity
! style="text-align:right;" | Price (EUR)
+
! style="text-align:right;" | Price/Total<br>(EUR)
 
|-
 
|-
 
| {{WebImage|src=https://m.media-amazon.com/images/I/617l6G5NxYL._SL1500_.jpg|width=150|target=https://www.amazon.de/ACT-Motor-23HS8430-Automation-3D-Printer/dp/B01CYIU0EG|title=Stepper Motor}}
 
| {{WebImage|src=https://m.media-amazon.com/images/I/617l6G5NxYL._SL1500_.jpg|width=150|target=https://www.amazon.de/ACT-Motor-23HS8430-Automation-3D-Printer/dp/B01CYIU0EG|title=Stepper Motor}}
 
| ACT Motor 23HS8430 1.9 Nm
 
| ACT Motor 23HS8430 1.9 Nm
| style="text-align:right;" | 1
+
| style="text-align:right;" | 2
| style="text-align:right;" | 17.90
+
| style="text-align:right;" | 17.90 / 35.80
|-
 
| {{WebImage|src=https://m.media-amazon.com/images/I/51++cfmm0ZL._SL1000_.jpg|width=150|target=https://www.amazon.de/dp/B0B38GX54H|title=Stepper Motor E Series Nema 17}}
 
| E Series Nema 17 Bipolar 42Ncm
 
| style="text-align:right;" | 1
 
| style="text-align:right;" | 10.50
 
 
|-
 
|-
 
| {{WebImage|src=https://m.media-amazon.com/images/I/61eY1pX-o4L._SL1000_.jpg|width=150|target=https://www.amazon.de/COVVY-Schrittmotor-Treiber-CNC-Controller-Segmente-Stepper/dp/B07SBZ9SM5|title=Stepper Motor Driver}}
 
| {{WebImage|src=https://m.media-amazon.com/images/I/61eY1pX-o4L._SL1000_.jpg|width=150|target=https://www.amazon.de/COVVY-Schrittmotor-Treiber-CNC-Controller-Segmente-Stepper/dp/B07SBZ9SM5|title=Stepper Motor Driver}}
 
| TB6600 Stepper Motor Driver
 
| TB6600 Stepper Motor Driver
| style="text-align:right;" | 1
+
| style="text-align:right;" | 2
| style="text-align:right;" | 9.90
+
| style="text-align:right;" | 9.90 / 19.80
 
|-
 
|-
 
| {{WebImage|src=https://m.media-amazon.com/images/I/91zSu44+34L._AC_SL1500_.jpg|width=150|target=https://www.amazon.de/Raspberry-Pi-Model-ARM-Cortex-A53-Bluetooth/dp/B01CD5VC92|title=Raspberry Pi 3 Model B}}
 
| {{WebImage|src=https://m.media-amazon.com/images/I/91zSu44+34L._AC_SL1500_.jpg|width=150|target=https://www.amazon.de/Raspberry-Pi-Model-ARM-Cortex-A53-Bluetooth/dp/B01CD5VC92|title=Raspberry Pi 3 Model B}}
 
| ARM Cortex-A53 with Wi-Fi and Bluetooth
 
| ARM Cortex-A53 with Wi-Fi and Bluetooth
 
| style="text-align:right;" | 1
 
| style="text-align:right;" | 1
| style="text-align:right;" | 44.45
+
| style="text-align:right;" | 44.45 / 44.45
 
|-
 
|-
 
| {{WebImage|src=https://m.media-amazon.com/images/I/61jGn6u7ZdL._SL1500_.jpg|width=150|target=https://www.amazon.de/dp/B0CKR9C1FQ|title=Couplings}}
 
| {{WebImage|src=https://m.media-amazon.com/images/I/61jGn6u7ZdL._SL1500_.jpg|width=150|target=https://www.amazon.de/dp/B0CKR9C1FQ|title=Couplings}}
 
| 4 flange couplings
 
| 4 flange couplings
 
| style="text-align:right;" | 4
 
| style="text-align:right;" | 4
| style="text-align:right;" | 9.99
+
| style="text-align:right;" | 2.50 / 9.99
 
|-
 
|-
 
| {{WebImage|src=https://m.media-amazon.com/images/I/71SiECRwq+L._AC_SL1500_.jpg|width=150|target=https://www.amazon.de/gp/product/B0C2N4T6FL|title=Adapter for Aldi Ferrex Battery}}
 
| {{WebImage|src=https://m.media-amazon.com/images/I/71SiECRwq+L._AC_SL1500_.jpg|width=150|target=https://www.amazon.de/gp/product/B0C2N4T6FL|title=Adapter for Aldi Ferrex Battery}}
| Option 1: Adapter for Aldi Ferrex battery, charging cradle, gray PLA+
+
| Option 1: Adapter for Aldi Ferrex battery,<br>charging cradle, gray PLA+
 
| style="text-align:right;" | 1
 
| style="text-align:right;" | 1
| style="text-align:right;" | 11.90
+
| style="text-align:right;" | 11.90 / 11.90
 
|-
 
|-
 
| {{WebImage|src=https://m.media-amazon.com/images/I/61h+6WaK3VL._SL1500_.jpg|width=150|target=https://www.amazon.de/Netzteil-Transformator-Niedervolttrafo-Konstantspannungs-Beleuchtungen/dp/B08P8S4SY6|title=AC/DC Converter}}
 
| {{WebImage|src=https://m.media-amazon.com/images/I/61h+6WaK3VL._SL1500_.jpg|width=150|target=https://www.amazon.de/Netzteil-Transformator-Niedervolttrafo-Konstantspannungs-Beleuchtungen/dp/B08P8S4SY6|title=AC/DC Converter}}
| Option 2: Power supply transformer for LED strips and lighting
+
| Option 2: Power supply transformer<br>for LED strips and lighting
 
| style="text-align:right;" | 1
 
| style="text-align:right;" | 1
| style="text-align:right;" | 12.99
+
| style="text-align:right;" | 12.99 / 12.99
 
|-
 
|-
 
| {{WebImage|src=https://m.media-amazon.com/images/I/71FqBhmHBSL._SL1500_.jpg|width=150|target=https://www.amazon.de/Jumper-Female-Breadboard-meisten-Steckbretter/dp/B0D2XX7FJ9|title=Jumper Wire Cables}}
 
| {{WebImage|src=https://m.media-amazon.com/images/I/71FqBhmHBSL._SL1500_.jpg|width=150|target=https://www.amazon.de/Jumper-Female-Breadboard-meisten-Steckbretter/dp/B0D2XX7FJ9|title=Jumper Wire Cables}}
 
| Jumper wire cables for breadboard connections
 
| Jumper wire cables for breadboard connections
 
| style="text-align:right;" | 1
 
| style="text-align:right;" | 1
| style="text-align:right;" | 4.99
+
| style="text-align:right;" | 4.99 / 4.99
 
|-
 
|-
 
| {{WebImage|src=https://m.media-amazon.com/images/I/71lowGhDRwL._AC_SL1500_.jpg|width=150|target=https://www.amazon.de/Raspberry-Aluminium-K%C3%BChlk%C3%B6rper-Kompatibel-Enthalten/dp/B01M3QWZE7|title=Raspberry Pi Case with Cooling}}
 
| {{WebImage|src=https://m.media-amazon.com/images/I/71lowGhDRwL._AC_SL1500_.jpg|width=150|target=https://www.amazon.de/Raspberry-Aluminium-K%C3%BChlk%C3%B6rper-Kompatibel-Enthalten/dp/B01M3QWZE7|title=Raspberry Pi Case with Cooling}}
| Aluminum case with cooling fan and heatsinks for Raspberry Pi
+
| Aluminum case with cooling fan<br>and heatsinks for Raspberry Pi
 
| style="text-align:right;" | 1
 
| style="text-align:right;" | 1
| style="text-align:right;" | 14.99
+
| style="text-align:right;" | 14.99 / 14.99
 
|-
 
|-
 
! colspan="3" style="text-align:right;" | Total (Price Range)
 
! colspan="3" style="text-align:right;" | Total (Price Range)
| style="text-align:right;" | 125 - 148
+
| style="text-align:right;" | 141.92 - 142.01
 
|}
 
|}
 
''Note: Prices are based on Amazon.de listings as of June 2024 and may be subject to change.''
 
''Note: Prices are based on Amazon.de listings as of June 2024 and may be subject to change.''
Line 89: Line 84:
 
* Secure the ACT Motor 23HS8430 (from the parts list) to a stable base using a mounting bracket. For initial testing, cable ties can be used.
 
* Secure the ACT Motor 23HS8430 (from the parts list) to a stable base using a mounting bracket. For initial testing, cable ties can be used.
 
* Connect one of the flange couplings (from the parts list) to the motor shaft.
 
* Connect one of the flange couplings (from the parts list) to the motor shaft.
 +
* Secure the smaller motor to the coupling with two cable ties
 +
* Connect one of the flange couplings with the motor
 
* Secure the garden hose to the flange coupling using a cable tie or hose clamp.
 
* Secure the garden hose to the flange coupling using a cable tie or hose clamp.
  
 
== Wiring ==
 
== Wiring ==
* Connect the ACT Motor 23HS8430 wires to the TB6600 driver (from the parts list):
+
The color codings are  specific to the Motors. Always verify these connections against the motor's datasheet, as even within the same model, there can be variations.
** Black wire to A+
+
* Connect the Motor wires to the TB6600 drivers (from the parts list):
** Green wire to A-
 
** Red wire to B+
 
** Blue wire to B-
 
This color coding is specific to the ACT Motor 23HS8430. Always verify these connections against the motor's datasheet, as even within the same model, there can be variations.
 
  
 +
=== 23 HS 8430 ===
 +
* Black wire to A+
 +
* Green wire to A-
 +
* Red wire to B+
 +
* Blue wire to B-
 +
 +
=== TB6600 to Raspberry ===
 
* Connect the TB6600 driver to the Raspberry Pi 3 Model B (from the parts list):
 
* Connect the TB6600 driver to the Raspberry Pi 3 Model B (from the parts list):
 
{{WebImage|src=https://www.elektronik-kompendium.de/sites/raspberry-pi/fotos/raspberry-pi-15b.jpg|width=300|target=https://www.elektronik-kompendium.de/sites/raspberry-pi/1907101.htm|title=Raspberry Pi GPIO Pin Layout}}
 
{{WebImage|src=https://www.elektronik-kompendium.de/sites/raspberry-pi/fotos/raspberry-pi-15b.jpg|width=300|target=https://www.elektronik-kompendium.de/sites/raspberry-pi/1907101.htm|title=Raspberry Pi GPIO Pin Layout}}
** DIR- to a GND pin (recommend using Pin 39, 34, or 30 as they're closest)
+
==== First motor ====
** PUL- to a GND pin (recommend using Pin 39, 34, or 30 as they're closest)
+
* ENA- to GND pin (Pin 39) green
** ENA- to a GND pin (recommend using Pin 39, 34, or 30 as they're closest)
+
* ENA+ to GPIO 26 (Pin 37) blue
** DIR+ to GPIO 33 (Pin 33 on the board)
+
* DIR- to GND pin (Pin 34) pink
** PUL+ to GPIO 35 (Pin 35 on the board)
+
* DIR+ to GPIO 19 (Pin 35) grey
** ENA+ to GPIO 37 (Pin 37 on the board)
+
* PUL- to GND pin (Pin 30) white
 +
* PUL+ to GPIO 13 (Pin 33) black
 +
==== Second motor ====
 +
* ENA- to GND pin (Pin 20) green
 +
* ENA+ to GPIO 6 (Pin 31) blue
 +
* DIR- to GND pin (Pin 14) pink
 +
* DIR+ to GPIO 5 (Pin 29) grey
 +
* PUL- to GND pin (Pin  6) white
 +
* PUL+ to GPIO 11 (Pin 23) black
 +
 
 +
Make sure your software settings below fit the Pins you used!
 +
 
 +
==== Second motor ====
 +
 
 
* Connect the power supply (either the Aldi Ferrex battery adapter or the AC/DC converter from the parts list) to the TB6600 driver:
 
* Connect the power supply (either the Aldi Ferrex battery adapter or the AC/DC converter from the parts list) to the TB6600 driver:
 
** VCC to positive terminal
 
** VCC to positive terminal
Line 112: Line 125:
  
 
== Setting up the TB6600 Driver Switches ==
 
== Setting up the TB6600 Driver Switches ==
 +
[[File:StepperDriverTable.png|400px]]
 
On one side of the driver, you'll find 6 small switches that configure the driver for your motor. The top of the driver should have a printed table explaining the switch settings. A barely visible arrow on the switch should indicate which position is "On".
 
On one side of the driver, you'll find 6 small switches that configure the driver for your motor. The top of the driver should have a printed table explaining the switch settings. A barely visible arrow on the switch should indicate which position is "On".
  
Line 119: Line 133:
 
* Switches 4-6 set the output current for the motor:
 
* Switches 4-6 set the output current for the motor:
 
** Refer to your motor's datasheet for the rated current (usually in Amperes or "Amps")
 
** Refer to your motor's datasheet for the rated current (usually in Amperes or "Amps")
** For example, if your motor is rated at 2.0A, set switches 4-6 to on-off-off (which provides 2.0A continuous, 2.2A peak according to the driver's table)
+
** Since our motors are rated at 3.0A, set switches 4-6 to off-on-off (which provides 3.0A continuous, 3.2A peak according to the driver's table)
  
 
Important: Always start with lower current settings and gradually increase. Too little current can cause weak motor performance and missed steps, while too much current can overheat and damage the motor.
 
Important: Always start with lower current settings and gradually increase. Too little current can cause weak motor performance and missed steps, while too much current can overheat and damage the motor.
Line 151: Line 165:
 
stepper.py
 
stepper.py
  
Control a stepper motor connected to a Raspberry Pi through a TB6600 driver.
+
Control two stepper motors connected to a Raspberry Pi through a TB6600 driver to sprinkle a lawn.
  
Author: Wolfgang, ChatGPT
+
Author: Wolfgang, ChatGPT, Claude AI
Date: 2023-03-28 (ISO 8601 format)
+
Date: 2024-07 to 2024-08
 
"""
 
"""
  
Line 160: Line 174:
 
import time
 
import time
 
import argparse
 
import argparse
 +
from typing import Dict
  
 
class StepperMotor:
 
class StepperMotor:
    """
+
     def __init__(self, name: str, ena_pin: int, dir_pin: int, pul_pin: int, steps_per_revolution: int = 200):
    Controls a stepper motor connected to a TB6600 driver on a Raspberry Pi.
+
         self.name = name
    """
+
         self.ena_pin = ena_pin
     def __init__(self, dir_pin: int = 33, pul_pin: int = 35, ena_pin: int = 37, frequency_hz: int = 500, rpm: int = 30):
 
         """
 
         Initializes the stepper motor with the provided pin numbers, pulse frequency, and RPM.
 
 
 
        Args:
 
            dir_pin (int): Pin number for direction control (default 33)
 
            pul_pin (int): Pin number for pulse control (default 35)
 
            ena_pin (int): Pin number for enable control (default 37)
 
            frequency_hz (int): Pulse frequency in Hz (default 500 Hz)
 
            rpm (int): Motor speed in RPM (default 30 RPM)
 
        """
 
 
         self.dir_pin = dir_pin
 
         self.dir_pin = dir_pin
 
         self.pul_pin = pul_pin
 
         self.pul_pin = pul_pin
         self.ena_pin = ena_pin
+
         self.steps_per_revolution = steps_per_revolution
         self.frequency_hz = frequency_hz
+
         self.setup_gpio()
        self.rpm = rpm
 
        self.half_period_s = 1 / (2 * self.frequency_hz)  # Pulse width based on frequency
 
        self.step_delay_s = 60 / (self.rpm * 200) # Time delay between steps based on RPM
 
  
        # Set up GPIO pins
+
    def setup_gpio(self):
         GPIO.setmode(GPIO.BOARD)
+
         GPIO.setup(self.ena_pin, GPIO.OUT)
 
         GPIO.setup(self.dir_pin, GPIO.OUT)
 
         GPIO.setup(self.dir_pin, GPIO.OUT)
 
         GPIO.setup(self.pul_pin, GPIO.OUT)
 
         GPIO.setup(self.pul_pin, GPIO.OUT)
         GPIO.setup(self.ena_pin, GPIO.OUT)
+
         GPIO.output(self.ena_pin, GPIO.HIGH) # Start with motor disabled
  
     def move(self, angle: float, direction: str):
+
     def enable(self):
         """
+
         GPIO.output(self.ena_pin, GPIO.LOW)
        Moves the stepper motor by the specified angle in the given direction.
 
  
        Args:
+
    def disable(self):
            angle (float): Angle in degrees to move the motor
+
         GPIO.output(self.ena_pin, GPIO.HIGH)
            direction (str): Direction to move ('left' or 'right')
 
        """
 
        steps = abs(angle) * 200 / 360  # Calculate number of steps based on angle
 
         GPIO.output(self.ena_pin, GPIO.LOW) # Lock motor
 
  
        # Set direction
+
    def set_direction(self, clockwise: bool):
        if direction == 'left':
+
         GPIO.output(self.dir_pin, GPIO.HIGH if clockwise else GPIO.LOW)
            GPIO.output(self.dir_pin, GPIO.HIGH)
 
         elif direction == 'right':
 
            GPIO.output(self.dir_pin, GPIO.LOW)
 
        else:
 
            raise ValueError(f"Invalid direction: {direction}")
 
  
        # Pulse modulation
+
    def step(self, steps: int, delay: float):
         for _ in range(int(steps)):
+
         for _ in range(abs(steps)):
 
             GPIO.output(self.pul_pin, GPIO.HIGH)
 
             GPIO.output(self.pul_pin, GPIO.HIGH)
             time.sleep(self.half_period_s)
+
             time.sleep(delay)
 
             GPIO.output(self.pul_pin, GPIO.LOW)
 
             GPIO.output(self.pul_pin, GPIO.LOW)
             time.sleep(self.step_delay_s) # Use RPM based delay between steps
+
             time.sleep(delay)
  
     def usage(self):
+
class Move:
         """
+
     def __init__(self):
         Prints usage instructions for the StepperMotor class.
+
         GPIO.setmode(GPIO.BOARD)
        """
+
         self.motors: Dict[int, StepperMotor] = {
        print(f"Usage: {self.__class__.__name__}(dir_pin, pul_pin, ena_pin, frequency_hz, rpm)")
+
            1: StepperMotor("Motor1", 37, 35, 33),
 +
            2: StepperMotor("Motor2", 31, 29, 23)
 +
        }
  
if __name__ == '__main__':
+
    def enable_motor(self, motor_id: int):
    parser = argparse.ArgumentParser(description='Control a stepper motor connected to a Raspberry Pi through a TB6600 driver.')
+
        motor = self.motors.get(motor_id)
     parser.add_argument('--dir-pin', type=int, default=33, help='Pin number for direction control')
+
        if motor:
    parser.add_argument('--pul-pin', type=int, default=35, help='Pin number for pulse control')
+
            motor.enable()
     parser.add_argument('--ena-pin', type=int, default=37, help='Pin number for enable control')
+
        else:
     parser.add_argument('--frequency-hz', type=int, default=500, help='Pulse frequency in Hz')
+
            print(f"Motor {motor_id} not found")
     parser.add_argument('--rpm', type=int, default=30, help='Motor speed in RPM')
+
 
     parser.add_argument('--angle', type=float, help='Angle in degrees', default=90)
+
    def disable_motor(self, motor_id: int):
     parser.add_argument('--direction', choices=['left', 'right'], help='Movement direction', default='left')
+
        motor = self.motors.get(motor_id)
 +
        if motor:
 +
            motor.disable()
 +
        else:
 +
            print(f"Motor {motor_id} not found")
 +
 
 +
    def move_motor(self, motor_id: int, angle: float, speed_rpm: float, keep_enabled: bool = False):
 +
        motor = self.motors.get(motor_id)
 +
        if not motor:
 +
            print(f"Motor {motor_id} not found")
 +
            return
 +
        steps = int(abs(angle) / 360 * motor.steps_per_revolution)
 +
        delay = 30 / (speed_rpm * motor.steps_per_revolution)
 +
        motor.enable()
 +
        motor.set_direction(angle >= 0)
 +
        motor.step(steps, delay)
 +
        if not keep_enabled:
 +
            motor.disable()
 +
 
 +
     def perform_pattern(self,
 +
        horizontal_angle: float,
 +
        horizontal_steps: int,
 +
        vertical_angle: float,
 +
        rpm: float):
 +
        # Enable both motors before starting the pattern
 +
        self.enable_motor(1)
 +
        self.enable_motor(2)
 +
 
 +
        for _ in range(horizontal_steps):
 +
            self.move_motor(1, horizontal_angle, rpm, keep_enabled=True)
 +
            self.move_motor(2, vertical_angle, rpm, keep_enabled=True)
 +
            self.move_motor(2, -vertical_angle, rpm, keep_enabled=True)
 +
 
 +
        # Reset horizontal position
 +
        self.move_motor(1, -horizontal_angle * horizontal_steps, rpm, keep_enabled=True)
 +
 
 +
        # Disable both motors after completing the pattern
 +
        self.disable_motor(1)
 +
        self.disable_motor(2)
 +
 
 +
    def perform_pattern_by_args(self, pattern_args):
 +
        # Default values
 +
        params = {
 +
            'steps': 80,
 +
            'hangle': 160,
 +
            'vangle': 120,
 +
            'rpm': 10
 +
        }
 +
 
 +
        # Parse provided arguments
 +
        for arg in pattern_args:
 +
            key, value = arg.split('=')
 +
            if key in params:
 +
                params[key] = float(value)
 +
 
 +
        # Execute the pattern
 +
        self.perform_pattern(
 +
            horizontal_angle=params['hangle'] / params['steps'],
 +
            horizontal_steps=int(params['steps']),
 +
            vertical_angle=params['vangle'],
 +
            rpm=params['rpm']
 +
        )
 +
 
 +
    def cleanup(self):
 +
        for motor in self.motors.values():
 +
            motor.disable()
 +
        GPIO.cleanup()
 +
        time.sleep(0.1)
 +
 
 +
# Modify main function to use the new approach
 +
def main():
 +
    parser = argparse.ArgumentParser(description="Control stepper motors")
 +
     parser.add_argument("-m", "--motor", type=int, default=1, help="Motor ID (default: 1)")
 +
     parser.add_argument("-a", "--angle", type=float, default=15, help="Angle to rotate (default: 15, positive for CW, negative for CCW)")
 +
     parser.add_argument("-r", "--rpm", type=float, default=20, help="Speed in RPM (default: 20)")
 +
     parser.add_argument("-k", "--keep-enabled", action="store_true", help="Keep motor enabled after movement")
 +
     parser.add_argument("-p", "--pattern", nargs='*', metavar="KEY=VALUE",
 +
                        help="Perform pattern: [steps=N] [hangle=DEG] [vangle=DEG] [rpm=RPM] default: steps=20,hangle=160,vangle=90,rpm=10")
  
 
     args = parser.parse_args()
 
     args = parser.parse_args()
 +
    move_controller = Move()
 +
 +
    if args.pattern is not None:
 +
        # For pattern, we'll handle enabling/disabling within the perform_pattern method
 +
        move_controller.perform_pattern_by_args(args.pattern)
 +
    else:
 +
        # For single motor movement
 +
        move_controller.move_motor(args.motor, args.angle, args.rpm, args.keep_enabled)
 +
 +
    move_controller.cleanup()
  
    # Create StepperMotor instance with RPM and frequency
+
if __name__ == "__main__":
     stepper = StepperMotor(args.dir_pin, args.pul_pin, args.ena_pin, args.frequency_hz, args.rpm)
+
     main()
  
    # Move the stepper motor based on command line arguments
 
    stepper.move(args.angle, args.direction)
 
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Line 249: Line 327:
 
<syntaxhighlight lang="bash">
 
<syntaxhighlight lang="bash">
 
#!/bin/bash
 
#!/bin/bash
# Bash script to control a garden hose motor for watering a lawn
+
# Bash script to control a two-motor garden hose system for watering a lawn
# Utilizes stepper.py to turn the motor
+
# Utilizes the updated stepper.py to turn two motors
  
 
# Define the path to your stepper.py script
 
# Define the path to your stepper.py script
 
STEPPER_SCRIPT_PATH="./stepper.py"
 
STEPPER_SCRIPT_PATH="./stepper.py"
 
+
python3 $STEPPER_SCRIPT_PATH -p
# Define motor movement parameters
 
ANGLE=170  # Angle to rotate, slightly less than 170 degrees for better control
 
FREQ=200  # Stepper frequency - 200 = 1 sec per full turn
 
RPM=10    # Motor speed in RPM
 
DIRECTION_LEFT="left"
 
DIRECTION_RIGHT="right"
 
 
 
# Get the number of cycles from the command line argument or set default to 5
 
CYCLES=${1:-5}
 
 
 
# Main loop to rotate motor left and right for specified cycles
 
for (( i=0; i<CYCLES; i++ )); do
 
    # Move motor to the left
 
    echo "Cycle $((i+1)) of $CYCLES: Turning motor left"
 
    sudo python3 $STEPPER_SCRIPT_PATH --angle $ANGLE --direction $DIRECTION_LEFT --frequency-hz $FREQ --rpm $RPM
 
   
 
    # Move motor to the right
 
    echo "Cycle $((i+1)) of $CYCLES: Turning motor right"
 
    sudo python3 $STEPPER_SCRIPT_PATH --angle $ANGLE --direction $DIRECTION_RIGHT --frequency-hz $FREQ --rpm $RPM
 
done
 
 
 
echo "Completed $CYCLES cycles of watering."
 
 
</syntaxhighlight>
 
</syntaxhighlight>
  

Latest revision as of 17:22, 13 August 2024

Introduction

The idea is to create a simple 3D sprinkler design controlling the flow from a garden hose in 3D style with two motors. For a proof of concept we start with one motor.

Please find below how the project made progress so far. LawnSprinkler=2024-06-30.jpg

Video

The video shows the principle of moving the garden hose with the motor.

File:LawnSprinkler2024-06-30 0956.mp4

Parts List

Part Name Description Quantity Price/Total
(EUR)

Stepper Motor
Stepper Motor

ACT Motor 23HS8430 1.9 Nm 2 17.90 / 35.80

Stepper Motor Driver
Stepper Motor Driver

TB6600 Stepper Motor Driver 2 9.90 / 19.80

Raspberry Pi 3 Model B
Raspberry Pi 3 Model B

ARM Cortex-A53 with Wi-Fi and Bluetooth 1 44.45 / 44.45

Couplings
Couplings

4 flange couplings 4 2.50 / 9.99

Adapter for Aldi Ferrex Battery
Adapter for Aldi Ferrex Battery

Option 1: Adapter for Aldi Ferrex battery,
charging cradle, gray PLA+
1 11.90 / 11.90

AC/DC Converter
AC/DC Converter

Option 2: Power supply transformer
for LED strips and lighting
1 12.99 / 12.99

Jumper Wire Cables
Jumper Wire Cables

Jumper wire cables for breadboard connections 1 4.99 / 4.99

Raspberry Pi Case with Cooling
Raspberry Pi Case with Cooling

Aluminum case with cooling fan
and heatsinks for Raspberry Pi
1 14.99 / 14.99
Total (Price Range) 141.92 - 142.01

Note: Prices are based on Amazon.de listings as of June 2024 and may be subject to change.

Datasheets

ACT Motor 23HS8430 1.9 Nm Stepper Motor

load PDF


TB6600 Stepper Motor Driver

load PDF

Assembly Instructions

This guide has been adapted from https://www.heimkino-praxis.de/leinwand-maskierung-schrittmotor-steuerung/ - many thanks to the author Bert Kößler

Mounting the Stepper Motor

  • Secure the ACT Motor 23HS8430 (from the parts list) to a stable base using a mounting bracket. For initial testing, cable ties can be used.
  • Connect one of the flange couplings (from the parts list) to the motor shaft.
  • Secure the smaller motor to the coupling with two cable ties
  • Connect one of the flange couplings with the motor
  • Secure the garden hose to the flange coupling using a cable tie or hose clamp.

Wiring

The color codings are specific to the Motors. Always verify these connections against the motor's datasheet, as even within the same model, there can be variations.

  • Connect the Motor wires to the TB6600 drivers (from the parts list):

23 HS 8430

  • Black wire to A+
  • Green wire to A-
  • Red wire to B+
  • Blue wire to B-

TB6600 to Raspberry

  • Connect the TB6600 driver to the Raspberry Pi 3 Model B (from the parts list):

Raspberry Pi GPIO Pin Layout
Raspberry Pi GPIO Pin Layout

First motor

  • ENA- to GND pin (Pin 39) green
  • ENA+ to GPIO 26 (Pin 37) blue
  • DIR- to GND pin (Pin 34) pink
  • DIR+ to GPIO 19 (Pin 35) grey
  • PUL- to GND pin (Pin 30) white
  • PUL+ to GPIO 13 (Pin 33) black

Second motor

  • ENA- to GND pin (Pin 20) green
  • ENA+ to GPIO 6 (Pin 31) blue
  • DIR- to GND pin (Pin 14) pink
  • DIR+ to GPIO 5 (Pin 29) grey
  • PUL- to GND pin (Pin 6) white
  • PUL+ to GPIO 11 (Pin 23) black

Make sure your software settings below fit the Pins you used!

Second motor

  • Connect the power supply (either the Aldi Ferrex battery adapter or the AC/DC converter from the parts list) to the TB6600 driver:
    • VCC to positive terminal
    • GND to negative terminal

Setting up the TB6600 Driver Switches

StepperDriverTable.png On one side of the driver, you'll find 6 small switches that configure the driver for your motor. The top of the driver should have a printed table explaining the switch settings. A barely visible arrow on the switch should indicate which position is "On".

  • Switches 1-3 set the step size:
    • Set to on-on-off for full steps (typically 200 steps per full rotation, 1.8° per step)
    • Can be set to on-off-on later for half-steps (400 steps per rotation, 0.9° per step) for smoother operation
  • Switches 4-6 set the output current for the motor:
    • Refer to your motor's datasheet for the rated current (usually in Amperes or "Amps")
    • Since our motors are rated at 3.0A, set switches 4-6 to off-on-off (which provides 3.0A continuous, 3.2A peak according to the driver's table)

Important: Always start with lower current settings and gradually increase. Too little current can cause weak motor performance and missed steps, while too much current can overheat and damage the motor.

Additional Setup

  • Use the jumper wire cables (from the parts list) for any necessary connections between components.
  • Install the Raspberry Pi in the aluminum case with cooling fan (from the parts list) for protection and temperature management.

Software Setup

  • Install Raspbian OS on the Raspberry Pi
  • Enable SSH for headless setup
  • Use the provided Python script as a starting point, modifying parameters like step counts and speeds for your specific sprinkler setup.

Testing and Calibration

  • Start with lower speeds and gradually increase
  • Determine the number of steps needed for full range of motion
  • Adjust acceleration ramps if needed for smoother operation

Safety Considerations

  • Ensure all electrical connections are properly insulated
  • Protect the electronics from water exposure
  • Use caution when testing, as the motor can generate significant torque

Software

Stepper Motor Control Script

Save the following code as stepper.py:

"""
stepper.py

Control two stepper motors connected to a Raspberry Pi through a TB6600 driver to sprinkle a lawn.

Author: Wolfgang, ChatGPT, Claude AI
Date: 2024-07 to 2024-08
"""

import RPi.GPIO as GPIO
import time
import argparse
from typing import Dict

class StepperMotor:
    def __init__(self, name: str, ena_pin: int, dir_pin: int, pul_pin: int, steps_per_revolution: int = 200):
        self.name = name
        self.ena_pin = ena_pin
        self.dir_pin = dir_pin
        self.pul_pin = pul_pin
        self.steps_per_revolution = steps_per_revolution
        self.setup_gpio()

    def setup_gpio(self):
        GPIO.setup(self.ena_pin, GPIO.OUT)
        GPIO.setup(self.dir_pin, GPIO.OUT)
        GPIO.setup(self.pul_pin, GPIO.OUT)
        GPIO.output(self.ena_pin, GPIO.HIGH)  # Start with motor disabled

    def enable(self):
        GPIO.output(self.ena_pin, GPIO.LOW)

    def disable(self):
        GPIO.output(self.ena_pin, GPIO.HIGH)

    def set_direction(self, clockwise: bool):
        GPIO.output(self.dir_pin, GPIO.HIGH if clockwise else GPIO.LOW)

    def step(self, steps: int, delay: float):
        for _ in range(abs(steps)):
            GPIO.output(self.pul_pin, GPIO.HIGH)
            time.sleep(delay)
            GPIO.output(self.pul_pin, GPIO.LOW)
            time.sleep(delay)

class Move:
    def __init__(self):
        GPIO.setmode(GPIO.BOARD)
        self.motors: Dict[int, StepperMotor] = {
            1: StepperMotor("Motor1", 37, 35, 33),
            2: StepperMotor("Motor2", 31, 29, 23)
        }

    def enable_motor(self, motor_id: int):
        motor = self.motors.get(motor_id)
        if motor:
            motor.enable()
        else:
            print(f"Motor {motor_id} not found")

    def disable_motor(self, motor_id: int):
        motor = self.motors.get(motor_id)
        if motor:
            motor.disable()
        else:
            print(f"Motor {motor_id} not found")

    def move_motor(self, motor_id: int, angle: float, speed_rpm: float, keep_enabled: bool = False):
        motor = self.motors.get(motor_id)
        if not motor:
            print(f"Motor {motor_id} not found")
            return
        steps = int(abs(angle) / 360 * motor.steps_per_revolution)
        delay = 30 / (speed_rpm * motor.steps_per_revolution)
        motor.enable()
        motor.set_direction(angle >= 0)
        motor.step(steps, delay)
        if not keep_enabled:
            motor.disable()

    def perform_pattern(self,
        horizontal_angle: float,
        horizontal_steps: int,
        vertical_angle: float,
        rpm: float):
        # Enable both motors before starting the pattern
        self.enable_motor(1)
        self.enable_motor(2)

        for _ in range(horizontal_steps):
            self.move_motor(1, horizontal_angle, rpm, keep_enabled=True)
            self.move_motor(2, vertical_angle, rpm, keep_enabled=True)
            self.move_motor(2, -vertical_angle, rpm, keep_enabled=True)

        # Reset horizontal position
        self.move_motor(1, -horizontal_angle * horizontal_steps, rpm, keep_enabled=True)

        # Disable both motors after completing the pattern
        self.disable_motor(1)
        self.disable_motor(2)

    def perform_pattern_by_args(self, pattern_args):
        # Default values
        params = {
            'steps': 80,
            'hangle': 160,
            'vangle': 120,
            'rpm': 10
        }

        # Parse provided arguments
        for arg in pattern_args:
            key, value = arg.split('=')
            if key in params:
                params[key] = float(value)

        # Execute the pattern
        self.perform_pattern(
            horizontal_angle=params['hangle'] / params['steps'],
            horizontal_steps=int(params['steps']),
            vertical_angle=params['vangle'],
            rpm=params['rpm']
        )

    def cleanup(self):
        for motor in self.motors.values():
            motor.disable()
        GPIO.cleanup()
        time.sleep(0.1)

# Modify main function to use the new approach
def main():
    parser = argparse.ArgumentParser(description="Control stepper motors")
    parser.add_argument("-m", "--motor", type=int, default=1, help="Motor ID (default: 1)")
    parser.add_argument("-a", "--angle", type=float, default=15, help="Angle to rotate (default: 15, positive for CW, negative for CCW)")
    parser.add_argument("-r", "--rpm", type=float, default=20, help="Speed in RPM (default: 20)")
    parser.add_argument("-k", "--keep-enabled", action="store_true", help="Keep motor enabled after movement")
    parser.add_argument("-p", "--pattern", nargs='*', metavar="KEY=VALUE",
                        help="Perform pattern: [steps=N] [hangle=DEG] [vangle=DEG] [rpm=RPM] default: steps=20,hangle=160,vangle=90,rpm=10")

    args = parser.parse_args()
    move_controller = Move()

    if args.pattern is not None:
        # For pattern, we'll handle enabling/disabling within the perform_pattern method
        move_controller.perform_pattern_by_args(args.pattern)
    else:
        # For single motor movement
        move_controller.move_motor(args.motor, args.angle, args.rpm, args.keep_enabled)

    move_controller.cleanup()

if __name__ == "__main__":
    main()

Water Control Script

Save the following code as water and make it executable

chmod +x water
#!/bin/bash
# Bash script to control a two-motor garden hose system for watering a lawn
# Utilizes the updated stepper.py to turn two motors

# Define the path to your stepper.py script
STEPPER_SCRIPT_PATH="./stepper.py"
python3 $STEPPER_SCRIPT_PATH -p

Usage

To control the stepper motor directly: sudo python3 stepper.py --angle 90 --direction left --frequency-hz 500 --rpm 30

sudo is necessary for accessing the kernel memory directly.

To run the water control script: ./water 10

This will run 10 cycles of watering. Adjust the number as needed.

Patents

When searching for relevant patents we found:

Please note that this might not be the only patent relevant for this system

This patent, filed in 1993 (over 30 years ago), already attempted to create a "3D sprinkling" system by controlling the hose direction with two angles and the water flow. Key features include:

  1. A water-powered articulated actuation and control system
  2. Ability to aim a continuous stream of water to all coordinates within a polar coordinate system
  3. A manually programmable base assembly anchored to the ground, containing size-specific range data
  4. An azimuth rotor assembly mounted horizontally and a range rotor assembly mounted vertically
  5. Separate actuation and control systems for azimuth and range
  6. A mechanism for variably controlling range rate and flow volume

The patent abstract states:

An automatic robotic lawn sprinkler providing a water powered articulated, actuation and control system aiming a continuous stream of water to all coordinates within a polar coordinate system comprising a manually programmable base assembly for anchoring to the ground and containing size specific range data, an azimuth rotor assembly rotatably mounted to the base in a horizontal plane, a range rotor assembly rotatably mounted in a vertical plane substantially perpendicular to the azimuth rotor an azimuth actuation and control system range actuation and control system, and a mechanism for variably controlling range rate and flow volume.

This early attempt at robotic lawn sprinkler technology shows how much easier things are these days.

Legal Disclaimer

While the patent mention above has expired, it is important to note that there may be other active patents related to this system. The expiration of one patent does not guarantee freedom from all patent restrictions.

The effect of expired and potentially unknown patents on this DIY project:

  • Expired patents: The technology described in expired patents is generally considered to be in the public domain and available for use without licensing.
  • Unknown patents: There is always a possibility that other active patents may cover aspects of this system.
  • DIY projects: While personal, non-commercial use is generally less likely to face patent infringement issues, it's important to be aware that patent laws can still apply to DIY projects.

Builders of this system are advised to use this information for personal, non-commercial purposes only. If you plan to commercialize or distribute this system, it is strongly recommended to consult with a patent attorney to ensure compliance with current patent laws.

This project is provided for educational and informational purposes only. The authors and contributors to this wiki page do not assume any legal responsibility for the use or misuse of this information.