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
- 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
}