RaylFX – Forestry Module

RaylFX Forestry Arduino Model Railway Servo Falling Tree

This module of the RaylFX system is inspired by the falling tree set from NOCH, for which I had received a request. So here is a whole deployment site right away. It has the following functions:

  • A falling tree
  • Cargo crane (left, right, pick up, dump)
  • Three pairs of flashing warning lights
  • Vehicle lighting
  • Job site lighting

Since it is not possible to use the DFPlayer sound module with servo motors at the same time, there is unfortunately no sound output on this module. However, the independent RaylFX sound module (currently in work) can be used for this.

Wiring Diagram

RaylFX Arduino Falling Tree Servo Forestry Module Wiring Diagram
  • D10: Servo »falling tree«
  • D11: cargo crane servo rotation
  • D12: cargo crane servo winch
  • D2 – D7: LEDs for flashing lights
  • D8, D9: vehicle lighting
  • A1 – A3: job site lighting (the analog inputs are used as digital outputs) 

Attention: The control signal of the RaylFX control module is applied to pin A4. It controls the time of day and is mandatory.

Components

Code Settings

This module also offers a number of adjustment options. Firstly, all target states of the servos should be set. There are two states for each of the three servos, e.g. treeDown and treeUp. These are the degrees to which the servo should adjust.

It should be noted that the range should be between 0 and 180, although servos often can not reach these extreme values. Therefore, these limits must be tested. I suggest to approach these limits carefully, e.g. 30 – 150, 20 – 160 …

Furthermore, treeDown must be larger than treeUp, craneRight must be larger than craneLeft, and winchUp must be larger than winchDown.

The falling tree is triggered at random. The smaller the variable fallingTreeRandom is, the more often the tree falls.

The cargo crane performs an animation that is time-controlled. The duration of the individual phases can be determined via the array craneTimeouts. Here the times are set in milliseconds (1000ms=1s). The animation has the following phases:

  • Hold (Probability)
  • Pick up
  • Hold
  • Rotate
  • Hold
  • Dump
  • Warten (Probability)
  • Pick up
  • Hold
  • Rotate
  • Hold
  • Dump

Position 0 and 6 (the first position in the array is always 0) define a probability with which the crane continues to work: the higher, the less often the next animation phase is executed. This creates a more random operating pattern.

The rotation speed of the two crane servos (winch and rotation) can be set via the variables craneWinchSpeed and craneRotationSpeed. The larger the value, the slower the servos move.

There are three pairs of warning lights that flash at different rates. The frequency can be set via the array warningLightsTimer. A slight variance in the frequency creates a more realistic flashing pattern.

int treeDown = 120;               // servo position of the lying tree
int treeUp = 30;                  // servo position of the standing tree

long fallingTreeRandom = 200000;  // the smaller the value, the more often the tree falls

/* Sets the waiting times of the crane sequence:
   hold (probability), pick up, hold, rotate, hold, dump, hold (probability), pick up, hold, rotate, hold, dump
   -> position 0 and 6 set a probability, with which the crane continues to work: the higher, the rarer
*/
int craneTimeouts[] = {1000, 3000, 1000, 3000, 1000, 3000, 1000, 3000, 1000, 3000, 1000, 3000};

int craneWinchSpeed = 10;         // speed of the crane winch (greater = slower)
int craneRotationSpeed = 10;      // speed of the crane rotation (greater = slower)
int winchUp = 120;                // servo position of the raised crane winch
int winchDown = 60;               // servo position of the lowerd crane winch
int craneRight = 160;             // servo position of the crane boom right
int craneLeft = 30;               // servo position of the crane boom left

int warningLightsTimer[3] = {800, 700, 650}; // warning lights flashing frequency

When uploading you have to make sure that the correct board is selected in the Arduino menu. To do this, “ATmega328P (Old Bootlaoder)” must also be selected in the Processor subitem of the Tools menu.

The following program code can be easily copied with the above mentioned changes and loaded onto the Arduino nano.

Code for the RaylFX Forestry Module

/*
    Rayl-FX Forestry Module
    StartHardware.org/en

    Permalink: https://starthardware.org/en/raylfx-forestry-module/
*/

#include <Servo.h>

/* ***** ***** Settings ***** ***** ***** *****  ***** ***** ***** *****  ***** ***** ***** ***** */

int treeDown = 120;               // servo position of the lying tree
int treeUp = 30;                  // servo position of the standing tree

long fallingTreeRandom = 200000;  // the smaller the value, the more often the tree falls

/* Sets the waiting times of the crane sequence:
   hold (probability), pick up, hold, rotate, hold, dump, hold (probability), pick up, hold, rotate, hold, dump
   -> position 0 and 6 set a probability, with which the crane continues to work: the higher, the rarer
*/
int craneTimeouts[] = {1000, 3000, 1000, 3000, 1000, 3000, 1000, 3000, 1000, 3000, 1000, 3000};

int craneWinchSpeed = 10;         // speed of the crane winch (greater = slower)
int craneRotationSpeed = 10;      // speed of the crane rotation (greater = slower)
int winchUp = 120;                // servo position of the raised crane winch
int winchDown = 60;               // servo position of the lowerd crane winch
int craneRight = 160;             // servo position of the crane boom right
int craneLeft = 30;               // servo position of the crane boom left

int warningLightsTimer[3] = {800, 700, 650}; // warning lights flashing frequency

/* ***** ***** From here begins the program code, which does not need to be adjusted ***** ***** ***** ***** */

int warningLightPins[6] = {2, 3, 4, 5, 6, 7};   // warning light pins
int vehicleLightingPins[2] = {8, 9};            // pins of the vehicle lighting
int jobSiteLightingPins[3] = {15, 16, 17};      // this pin connects to the job site lighting
int treeServoPin = 10;
int craneServoPin = 11;
int winchServoPin = 12;


/* State Variables */
int treeStatus = 0;                             // 0 = nothing, 1 = falls, 2 = lies, 3 = raise
int craneStatus = 0;

/* Memory Variables */
int treePosition;
int winchPosition;
int cranePosition;

/* Timer Variables */
long craneTimer;
long treeTimer;
long craneRotationTimer;
long craneWinchTimer;


int treeTimeout = 100;
int treeTimeoutMax = 100;

/* Create Objects */
Servo treeServo;
Servo craneServo;
Servo winchServo;

/* Variables from the control module to determine the time of day */
boolean receive = false;
boolean receiveStarted = false;
int receiveTimeout = 10;
long receiveTimer = 0;
int receivedTime = 0;
int receivePulse = 0;
int lastReceivePulse = 0;
int receivePin = 18;
int myTime = 0;

#define PAYLOAD_SIZE 2                          // mandatory for communication with master-module
int timeOfDay = 0;                              // stores timeOfDay of master-module (0 and 255)
byte nodePayload[PAYLOAD_SIZE];                 // temporarily stores data of master-module

void setup() {
  Serial.begin(115200);                         // starts the serial communication

  for (int i = 0; i < 3; i++) {                 // for the number of job site lighting pins
    pinMode(jobSiteLightingPins[i], OUTPUT);    // declare the pin as output
    digitalWrite(jobSiteLightingPins[i], HIGH); // turn off the LED at this pin
  }

  for (int i = 0; i < 2; i++) {                 // for the number of vehicle lighting pins
    pinMode(vehicleLightingPins[i], OUTPUT);    // declare the pin as output
    digitalWrite(vehicleLightingPins[i], HIGH); // turn off the LED at this pin
  }

  for (int i = 0; i < 6; i++) {                 // for the number of warning light pins
    pinMode(warningLightPins[i], OUTPUT);       // declare the pin as output
    digitalWrite(warningLightPins[i], HIGH);    // turn off the LED at this pin
  }

  treeServo.attach(treeServoPin);
  craneServo.attach(craneServoPin);
  winchServo.attach(winchServoPin);
}

void loop() {
  receiveFunction();                // execute instructions for reception
  myTime = 21;

  if (receiveStarted == false) {    // if no data is currently being received:
    
    if (myTime > 22) {              // ***** late evening *****
      warningLightOn();             // turn on warning lights
      jobSiteLightingOn();          // turn on job site lighting
      vehicleLightingOn();          // turn on vehicle lighting
      treeOn();                     // turn on falling tree
      craneOn();                    // turn on crane rotation
      
    } else if (myTime > 18) {       // ***** evening *****
      warningLightOn();             // turn on warning lights
      jobSiteLightingOn();          // turn on job site lighting
      vehicleLightingOn();          // turn on vehicle lighting
      treeOn();                     // turn on falling tree
      craneOn();                    // turn on crane rotation
      
    } else if (myTime > 12) {       // ***** noon *****
      warningLightOn();             // turn on warning lights
      jobSiteLightingOff();         // turn off job site lighting
      vehicleLightingOff();         // turn off vehicle lighting
      treeOn();                     // turn on falling tree
      craneOn();                    // turn on crane rotation
      
    } else if (myTime > 9) {        // ***** forenoon *****
      warningLightOn();             // turn on warning lights
      jobSiteLightingOff();         // turn off job site lighting
      vehicleLightingOff();         // turn off vehicle lighting
      treeOn();                     // turn on falling tree
      craneOn();                    // turn on crane rotation
      
    } else if (myTime > 7) {        // ***** morning *****
      warningLightOn();             // turn on warning lights
      jobSiteLightingOff();         // turn off job site lighting
      vehicleLightingOff();         // turn off vehicle lighting
      treeOn();                     // turn on falling tree
      craneOn();                    // turn on crane rotation
      
    } else {                        // ***** night *****
      warningLightOff();            // turn on warning lights
      jobSiteLightingOff();         // turn on job site lighting
      vehicleLightingOn();          // turn on vehicle lighting
      treeOff();                    // turn off falling tree
    }
  }
}

void vehicleLightingOn() {
  digitalWrite(vehicleLightingPins[0], LOW);
  digitalWrite(vehicleLightingPins[1], LOW);
}

void vehicleLightingOff() {
  digitalWrite(vehicleLightingPins[0], HIGH);
  digitalWrite(vehicleLightingPins[1], HIGH);
}

void jobSiteLightingOn() {
  if (random(2000) <= 1) digitalWrite(jobSiteLightingPins[0], LOW);
  if (random(2000) <= 1) digitalWrite(jobSiteLightingPins[1], LOW);
  if (random(2000) <= 1) digitalWrite(jobSiteLightingPins[2], LOW);
}

void jobSiteLightingOff() {
  for (int i = 0; i < 3; i++) {
    digitalWrite(jobSiteLightingPins[i], HIGH);
  }
}

void warningLightOn() {
  for (int i = 0; i < 4; i++) {
    if (millis() % warningLightsTimer[i] < warningLightsTimer[i] / 2) {
      digitalWrite(warningLightPins[i * 2], HIGH);
      digitalWrite(warningLightPins[i * 2 + 1], LOW);
    } else {
      digitalWrite(warningLightPins[i * 2], LOW);
      digitalWrite(warningLightPins[i * 2 + 1], HIGH);
    }
  }
}

void warningLightOff() {
  for (int i = 0; i < 8; i++) {
    digitalWrite(warningLightPins[i], HIGH);
  }
}

void craneOn() {
  if ((craneStatus != 0) || (craneStatus != 6)) {
    if (craneTimer + craneTimeouts[craneStatus] < millis()) {
      craneStatus++;
      if (craneStatus > 11) craneStatus = 0;
      craneTimer = millis();
      Serial.print("cranestatus="); Serial.println(craneStatus);
    }
  }

  switch (craneStatus) {
    case 0: // hold
      if (random(craneTimeouts[0]) < 1) craneStatus = 1;
      break;
    case 1:  // pick up
      if (craneWinchTimer + craneWinchSpeed < millis()) {
        if (winchPosition < winchUp) {
          winchPosition++;
          winchServo.write(winchPosition);
        }
        craneWinchTimer = millis();
      }
      break;
    case 2:  // hold
      break;
    case 3:  // rotate
      if (craneRotationTimer + craneRotationSpeed < millis()) {
        if (cranePosition < craneRight) {
          cranePosition++;
          craneServo.write(cranePosition);
        }
        craneRotationTimer = millis();
      }
      break;
    case 4:  // hold
      break;
    case 5:  // dump
      if (craneWinchTimer + craneWinchSpeed < millis()) {
        if (winchPosition > winchDown) {
          winchPosition--;
          winchServo.write(winchPosition);
        }
        craneWinchTimer = millis();
      }
      break;
    case 6:  // hold
      if (random(craneTimeouts[6]) < 1) craneStatus = 7;
      break;
    case 7:  // pick up
      if (craneWinchTimer + craneWinchSpeed < millis()) {
        if (winchPosition < winchUp) {
          winchPosition++;
          winchServo.write(winchPosition);
        }
        craneWinchTimer = millis();
      }

      break;
    case 8:  // hold
      break;
    case 9:  // rotate
      if (craneRotationTimer + craneRotationSpeed < millis()) {
        if (cranePosition > craneLeft) {
          cranePosition--;
          craneServo.write(cranePosition);
        }
        craneRotationTimer = millis();
      }
      break;
    case 10: // hold
      break;
    case 11: // dump
      if (craneWinchTimer + craneWinchSpeed < millis()) {
        if (winchPosition > winchDown) {
          winchPosition--;
          winchServo.write(winchPosition);
        }
        craneWinchTimer = millis();
      }
      break;
  }

}

void treeOn() {
  switch (treeStatus) {
    case 0:
      if (treeTimer + treeTimeout < millis()) {
        if (random(fallingTreeRandom) < 1) {
          treeStatus = 1;
          treeTimeout = treeTimeoutMax;
        }
      }
      break;
    case 1:
      if (treeTimer + treeTimeout < millis()) {
        if (treePosition < treeDown) {
          treePosition++;
          treeTimeout -= 2;
          treeServo.write(treePosition);
        } else {
          treeTimeout = 10000;
          treeStatus = 2;
        }
        delay(1);
        treeTimer = millis();
      }

      break;
    case 2:
      if (treeTimer + treeTimeout < millis()) {
        treeStatus = 3;
        treeTimeout = treeTimeoutMax;
      }
      break;
    case 3:
      if (treeTimer + treeTimeout < millis()) {
        if (treePosition > treeUp) {
          treePosition--;
          treeServo.write(treePosition);
        } else {
          treeTimeout = 1000;
          treeStatus = 0;
        }
        delay(1);
        treeTimer = millis();
      }
      break;
  }
  /**/
}

void treeOff() {
  if (treePosition > treeUp) {
    if (treeTimer + treeTimeout < millis()) {
      treePosition--;
      treeServo.write(treePosition);
    }
  }
}

/* ***** ***** ***** ***** ***** ***** ***** ***** ***** ***** ***** ***** ***** ***** ***** ***** ***** ***** ***** ***** ***** ***** ***** ***** */

void receiveFunction() {                      // receives time of day from control-module
  receivePulse = digitalRead(receivePin);     // read out the receive pin
  
  if ((receiveTimer + receiveTimeout < millis()) && (receiveStarted == true)) {
    // on timeout and active reception
    receiveStarted = false;                   // end active reception
    myTime = receivedTime - 1;                // store received time
    receivedTime = 0;                         // reset the auxiliary variable for time reception
    Serial.println(myTime);                   // serial output
  }
  // if a pulse is detected at the receive pin that was not there before
  if ((receivePulse == 0) && (lastReceivePulse == 1)) {
    receiveTimer = millis();                  // restart timer
    if (receiveStarted == false) receiveStarted = true;  // start active reception, if not already done
    receivedTime++;                           // there was a pulse, so increase the auxiliary variable to receive time
  }
  lastReceivePulse = receivePulse;            // remember current state at pin for next pass
}

    Leave a Reply

    Your email address will not be published. Required fields are marked *