Last Updated: 08/01/2024

#58 PCA9685 Level Crossing Barriers, Lights and Sound

Projects >> #58 PCA9685 Level Crossing Barriers, Lights and Sound

PCA9685 Level Crossing Servo Barriers, LED Lights, DY-SV5W MP3 Sound

This project adds sound to the previous Level Crossing project Arduino and PCA9685 Model Railway Level Crossing Servo Barriers and LED Lights so only the modifiications will be discussed in the video.

In this version the movement will be triggered by a button that could be replaced by a block detector or sensor.

I have also added a DY-SV5W MP3 player to add the barrier alarm sounds.

I used this crossing in Pirton just south of Worcester to get the light and barrier timings as well as recording the sound.

Pirton Crossing



Pinout and wiring diagrams

The LED's and servos work exactly the same way as the previous tutorial

However there is now a single button on pin 9 added with a PULL DOWN resistor for triggering the barrier close and raise.

I have also added the DY-SV5W MP3 player using Software Serial to pins 10 and 11

as before you will need a seperate 5v DC power supply to power the PCA9685 boards as the Arduino/USB power supply will not be able to provide enough power... you have been warned!

Circuit

Pins

A5 to PCA9685 SCL
A4 to PCA9685 SDA

Make sure you have 5v going to the PCA9685 board as well as the VCC and GND. One powers the circuitry, the other supply powers the servo.


Wiring:for a few common boards

SDA/SCL connections

Arduino UNO: A4 (SDA), A5 (SCL) :

Arduino Mega 2560: 20 (SDA), 21 (SCL) :

ESP32: 21(SDA), 22 (SCL)

For other Arduino boards see:https://www.arduino.cc/en/reference/wire

The Sound file

The sound file wasn't perfect as this isn't a project that I will be going on my layout, however the method was as follows.

1: I went to the crossing and recorded the barrier using the sound recorder on my Phone.

2: I imported the sound into Audacity (free at https://www.audacityteam.org/).

3: I edited the sound file to get the best bit of the barrier sound.

4: I copied and pasted the file to increase it's length. Car needs to be taken at the join of the samples.

5: I repeated the process until I have a file 3 minutes long... longer than the expected time for any train to pass.

6: The MP3 file was exported to the SD card in the DY-SV5W MP3 player

Connection Test 1 ...IMPORTANT

Bad wiring is one of the biggest problems with Arduino projects so run this test to make sure your board is being found before doing anythine else.

Once you have connected your board up as shown above check that it can be found.

If you are using an Arduino board go to File>Examples> Wire>I2c_scanner and upload the skecth to your board.

if all goes well your serial Monitor will display something like

Scanning...
I2C device found at address 0x40 !
done

Once your device has been found it's time to add the LED's

 

Connection Test 2 ...Board and Servo Test

First make sure you have the Adafruit PWM Servo Library installed.

Tools>Manage Libraries> Type Adafruit PWM Servo Library in the serach bar.

If it is not installed install it.

Adafruit PM Servo Library

Connect the board according to the circuit diagram.

Now download a test file from the Adafruit PWM servo Library examples.

File>Examples>Adafruit PWM servo Driver Library>servo.

Load the sketch and in the Serial Monitor you should see something like:

8 channel Servo test!
0
1
2
3
4
5
6
7

If you have a servo in any of the first 8 connections (nearest board connection end) you servo should move back and forth a couple of times.

At this point although we may not understand the sketch we know the board and servos work.

 

Example 1: PCA9685RailCrossingV3.ino


Click to Download code: PCA9685RailCrossingV3.ino

In this version a number of functions have been added from the tutorial on the DY-SV5W MP3 Player

My barrier sound track is track 6 on my SD card, please change this value for your own sound.

Pressing the button closes the barriers, releasing the button raises them. The switch could be changed for a block detector or some other kind of sensor.

Software Serial was required as I built this on an Uno and I control the DY-SV5W using serial communication.

For other boards Serial1 should be used instead.

 
/* PCA9685RailCrossingV3
   08/01/2024

   Sound and Button Control Added

   Sound provide by DY-SV5W MP3 player tutorial at:
   https://www.digitaltown.co.uk/components18DYSV5W.php

   Railway Crossing Sketch
   PCA9685 board for servos...2 gate or 4 gate
   PCA9685 board for LED's.... orange static, red flashing


  Software Serial pins 10-11
  Pin 9 Button   press to start/release to stop

*/
#include "SoftwareSerial.h"
#include "Wire.h"
#include "Adafruit_PWMServoDriver.h"

//PCA 9685 settings
//Using the address as this sketch uses multiple PCA9685 boards
Adafruit_PWMServoDriver servoBoard = Adafruit_PWMServoDriver(0x40);//Address found from I2C scanner
Adafruit_PWMServoDriver LEDBoard = Adafruit_PWMServoDriver(0x46);//Address found from I2C scanner
#define USMIN  600 // This is the rounded 'minimum' microsecond length based on the minimum pulse of 150
#define USMAX  2400 // This is the rounded 'maximum' microsecond length based on the maximum pulse of 600
#define SERVO_FREQ 50 // Analog servos run at ~50 Hz updates
//end pca9685 settings

unsigned long currentMillis;//Keeps track of time in milliseconds for all timing
unsigned long triggerMillis;

unsigned long ledTimer;//stores time in millis for tracking events
byte ledState = 0;//Stores the current state of the system
byte currentledState = 0;// what is the current state
byte flashState;//is flash on or off

unsigned long gateServoTimer;
byte gateState;

unsigned long servoMovementTimer;
int servoPos[4][2];//[4 servos 0-3 on servo board][target pos, actual pos]

//software serial
SoftwareSerial mySerial(10, 11); // RX, TX

//DY-SV5W MP3 player
byte commandLength;
byte command[5];
int checkSum = 0;
int trackNum = 6;//track number on SD card
//Button
const int buttonPin = 9;//button on this pin with pull down resistor 4.7k

//sets the position of the servo in degrees.
void setServoPos(int servo, int pos) {
  //This first bit of code makes sure we are not trying to set the servo outside of limits
  int sendPos;
  if (pos > 179) {
    pos = 179;
  }
  if (pos < 0) {
    pos = 0;
  }
  sendPos = USMIN + ((USMAX - USMIN) / 180 * pos);
  if (servo > -1 && servo < 16) { //only try to move valid servo addresses
    servoBoard.writeMicroseconds(servo, sendPos);
  }
}

//This would normally be triggered by sensors/buttons/dcc control
//Using a timer just for demo purposes.
void gateTrigger() {
  int buttonState = digitalRead(buttonPin);

  //gate closing trigger when button is pressed
  if (buttonState > 0 && ledState < 1) {
    ledState = 1;
    Serial.println("Trigger Close");
  }


  //gate opening trigger


  //when button is released gates open.
  if (gateState < 2 && ledState == 5 && buttonState == 0) { //
    Serial.println("Trigger Open");
    // triggerMillis = currentMillis + 10000;//reset timer
    gateState = 2;
  }
}

//controls movement and LEDs depending on led state
void ledControl() {
  int q;
  switch (ledState) {
    case 1://system starting...set lights to orange for 3 seconds
      if (currentledState < 1) {
        currentledState = 1;//set the current state
        Serial.println("Orange On");
        for (int q = 0; q < 4; q++) {
          LEDBoard.setPWM(q, 0, 4095);//turn Orange on full brightness
        }
        for (int q = 12; q < 16; q++) {
          LEDBoard.setPWM(q, 0, 4095);//turn red barrier lights on
        }
        ledTimer = currentMillis;
        ledState = 2;
        Serial.println(ledState);
        //start sound
        playTrack(trackNum);//Crossing sound is track 6 on my SD Card
      }

      break;
    case 2://Reds on orange off
      if (currentMillis - ledTimer >= 3000) {
        Serial.println("Reds on orange off 2");
        for (q = 4; q < 12; q++) {
          LEDBoard.setPWM(q, 0, 4095);//turn Reds on full brightness
        }
        for (q = 0; q < 4; q++) {
          LEDBoard.setPWM(q, 0, 4096);//turn Orange off
        }
        ledTimer = currentMillis;
        ledState = 3;
        Serial.println(ledState);
      }
      break;
    case 3://Reds off

      if (currentMillis - ledTimer >= 1500) { //0.5 second on before turning off
        Serial.println("Reds off 3");
        for (q = 4; q < 12; q++) {
          LEDBoard.setPWM(q, 0, 4096);//turn Reds off
        }
        ledTimer = currentMillis;
        ledState = 4;

      }

      break;
    case 4://Reds on
      if (currentMillis - ledTimer >= 500) { //0.5 second on before turning on
        Serial.println("Reds on 4");
        for (q = 4; q < 12; q++) {
          LEDBoard.setPWM(q, 0, 4095);//turn Reds on full brightness
        }
        ledState = 5;
        gateState = 1;
        gateServoTimer = currentMillis;
        triggerMillis = currentMillis;
      }
      break;
    case 5://Reds Flashing
      if (currentMillis - ledTimer >= 500) { //0.5 secs on before turning on
        ledTimer = currentMillis;
        if (flashState > 0) {
          for (q = 4; q < 8; q++) {
            LEDBoard.setPWM(q, 0, 4095);//turn Reds on full brightness

          }
          for (q = 8; q < 12; q++) {
            LEDBoard.setPWM(q, 0, 4096);//turn Reds off
          }
          flashState = 0;
        } else {
          for (q = 4; q < 8; q++) {
            LEDBoard.setPWM(q, 0, 4096);//turn Reds off
          }
          for (q = 8; q < 12; q++) {
            LEDBoard.setPWM(q, 0, 4095);//turn Reds on full brightness
          }
          flashState = 1;
        }
      }
      break;
    case 6://turn all LED's off... before barrier is fully down
      for (q = 0; q < 16; q++) {
        LEDBoard.setPWM(q, 0, 4096);//everything off;
      }
      currentledState = 0;
      ledState = 0;
      break;
    default:
      break;
  }
}

//Works through 3 different gate "states"
void servoControl() {
  switch (gateState) {
    case 1://lower barriers...train coming
      if (currentMillis - gateServoTimer >= 2000) {
        for (int q = 0; q < 2; q++) {//clockwise barriers/gates
          servoPos[q][0] = 89;
        }
        for (int q = 2; q < 4; q++) {//anti clockwise barriers gates
          servoPos[q][0] = 89;
        }
        gateState = 0;
      }

      break;
    case 2://raise barriers...train passed
      //no timer, will trigger from external...train
      for (int q = 0; q < 2; q++) {//clockwise barriers/gates
        servoPos[q][0] = 0;
      }
      for (int q = 2; q < 4; q++) {//anti clockwise barriers gates
        servoPos[q][0] = 179;
      }
      gateServoTimer = currentMillis;
      gateState = 3;
      break;
    case 3://Turns the LED's off before the gate has fully lowered as per real thing.
      //Serial.println(gateState);
      if (currentMillis - gateServoTimer >= 2500) { //wait 1 secs while gate raises
        gateState = 0;
        ledState = 6;//turn leds off
        stopTrack();//stop barrier sounds
        triggerMillis = currentMillis;//reset trigger timer
      }
      break;
    default:
      break;
  }
}



//this moves the servos to the value they have been targetted with
//but at a reduced speed to match the real thing
void servoMovement() {
  int q;
  if (currentMillis - servoMovementTimer >= 50) { //0.05 sec timin
    servoMovementTimer = currentMillis;
    for (q = 0; q < 4; q++) { //work through the 4 servos
      if (servoPos[q][0] > servoPos[q][1]) { //if the target is higher angle
        servoPos[q][1]++;//increment by 1
        setServoPos(q, servoPos[q][1]);//send the new value
      }
      if (servoPos[q][0] < servoPos[q][1]) { //if the target is smaller angle
        servoPos[q][1]--;//decrease by 1
        setServoPos(q, servoPos[q][1]);//send the new value
      }
    }
  }

}

//DY-SV5W MP3 player functions
//May need to be selected after putting into random mode
void stopTrack() {
  command[0] = 0xAA;//first byte says it's a command
  command[1] = 0x04;
  command[2] = 0x00;
  command[3] = 0xAE;
  commandLength = 4;
  sendCommand();
}
//play selected track
void playTrack(int soundTrack) {
  //select track
  //Serial.print("soundTrack: ");
  //Serial.println(soundTrack);
  command[0] = 0xAA;//first byte says it's a command
  command[1] = 0x07;
  command[2] = 0x02;
  command[3] = highByte(soundTrack);//snh...track HIGH bit
  command[4] = lowByte(soundTrack);//SNL... track low bit
  checkSum = 0;
  for (int q = 0; q < 5; q++) {
    checkSum +=  command[q];
  }
  command[5] = lowByte(checkSum);//SM check bit... low bit of the sum of all previous values
  commandLength = 6;
  sendCommand();
}

//sets the device volume...0 - 30
void playbackVolume(int vol) {
  if (vol > 30) { //check within limits
    vol = 30;
  }
  command[0] = 0xAA;//first byte says it's a command
  command[1] = 0x13;
  command[2] = 0x01;
  command[3] = vol;//volume
  checkSum = 0;
  for (int q = 0; q < 4; q++) {
    checkSum +=  command[q];
  }
  command[4] = lowByte(checkSum);//SM check bit... low bit of the sum of all previous values
  commandLength = 5;
  sendCommand();
}

//sends the command to the DY-SV5W
void sendCommand() {
  int q;
  for (q = 0; q < commandLength; q++) {
    mySerial.write(command[q]);
    Serial.print(command[q], HEX);
  }
  Serial.println("End");
}

void setup() {
  Serial.begin(9600);
  Serial.println("PCA9685RailCrossingV3");

  servoBoard.begin();
  servoBoard.setOscillatorFrequency(27000000);
  servoBoard.setPWMFreq(SERVO_FREQ);  // Analog servos run at ~50 Hz updates

  LEDBoard.begin();//start the PCA9685 for the station building
  LEDBoard.setPWMFreq(1600);  // This is the maximum PWM frequency and suited to LED's

  delay(10);//let the above values take effect

  //make sure everything is off
  for (int q = 0; q < 16; q++) {
    LEDBoard.setPWM(q , 0, 4096);//turn Orange on full brightness
  }
  //servos in start position
  for (int q = 0; q < 2; q++) {//clockwise barriers/gates
    setServoPos(q, 0);
    servoPos[q][0] = 0;//target
    servoPos[q][1] = 0;//actual
  }
  for (int q = 2; q < 4; q++) {//anti clockwise barriers gates
    setServoPos(q, 179);
    servoPos[q][0] = 179;//target
    servoPos[q][1] = 179;//actual
  }
  //button
  pinMode(buttonPin, INPUT);

  //software serial
  mySerial.begin(9600);
  delay(100);
  //DY-SV5W set up
  playbackVolume(17);//sets volume to lvl 17
  stopTrack();//stop barrier sounds
}





void loop() {
  currentMillis = millis();//get current time since board started in milliseconds

  gateTrigger();//function that checks if the gate should be moving...this would usually be reading sensors/buttons
  ledControl();//controls the leds
  servoControl();//servo movement/timing control

  servoMovement();//deals with speed of movement...sweep at controlled speed


}

Additional Resource Links

Arduino and PCA9685 Model Railway Level Crossing Servo Barriers and LED Lights

DY-SV5W MP3 Player 15/12/2023

#55 Multiple PCA9685 PWM Servo Boards with servos and LED's 05/01/2024

PCA9685 PWM Servo Board 27/12/2023

Adafruit PWM servo library

Lesson 7: delay() v's millis(), controlling timing of programs 23/07/2021

State Machine Example based around model railway requirements 08/03/2023

Comments


To ask a question please email the address in this image: and use #58 PCA9685 Level Crossing Barriers, Lights and Sound as a reference.