Last Updated: 07/01/2024
#56 PCA9685 Level Crossing Barriers and Lights
Projects >> #56 PCA9685 Level Crossing Barriers and Lights
PCA9685 Level Crossing Servo Barriers and LED Lights
In this project we will build a a Level Crossing using servos and LED's controlled by an Arduino and using PCA9695 PWM Servo Driver Boards
I used MG90 servos, SG90's are the same but with plastic gears and the system should work with other servos as well.
The goal is to immitate the lights and motion of the barriers.
I used this crossing in Pirton just south of Worcester to get the light and barrier timings.
The Arduino BreadBoard Version
Pinout and wiring diagrams
In the diagram below each LED state is available on 4 pins to create the 4 lighting posts usually associated with UK crossings.
Although 2 servos are shown there are 2 clockwise and anti clockwise sockets to allow for the building of 4 barrier or 2 barrier systems.
You may notice there are not buttons or sensors triggering the project. For this project the system is triggered off a timer, however in the code buttons, block sensors etc can easily be used instead.
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
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.
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: PCA9685RailCrossingV1.ino
Click to Download code: PCA9685RailCrossingV1.ino
Version 1 of the crossing code works but in my opinion the barriers move too fast
/* PCA9685RailCrossingV1
03/01/2024
Railway Crossing Sketch
PCA9685 board for servos...2 gate or 4 gate
PCA9685 board for LED's.... orange static, red flashing
gates triggered by timer for demo version
*/
#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;
unsigned long triggerMillis;//keeps track of trigger timing
unsigned long ledTimer;//stores time in millis for tracking events
byte ledState = 0;//
byte currentledState = 0;// what is the current state
byte flashState;
unsigned long gateServoTimer;
byte gateState;
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 function should read buttons or sensors to trigger the gates
//Using a timer just for demo purposes.
void gateTrigger() {
//gates down...train coming
if (currentledState < 1) { //don't trigger if already triggered
if (currentMillis - triggerMillis >= 10000) { //triggers after 5 seconds if nothing happening
triggerMillis = currentMillis;//reset timer
//Gate sequence trigger
ledState = 1;
Serial.println("Trigger Close");
}
}
//gates up...train passed
if (currentMillis - triggerMillis >= 6000) { //triggers after 5 seconds...simulate train passing
if (gateState < 2 && ledState == 5) { //
Serial.println("Trigger Open");
triggerMillis = currentMillis + 10000;//reset timer
//gates up trigger
gateState = 2;
}
}
}
//controls movement and LEDs depending on gate 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);
}
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 >= 500) { //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://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://everything off
for (q = 0; q < 16; q++) {
LEDBoard.setPWM(q, 0, 4096);//everything off;
}
currentledState = 0;
ledState = 0;
break;
default:
// Serial.println(ledState);
break;
}
}
void servoControl() {
if (gateState == 1) {
Serial.println("gateState 1");
}
switch (gateState) {
case 1://lower barriers
if (currentMillis - gateServoTimer >= 2000) {
for (int q = 0; q < 2; q++) {//clockwise barriers/gates
setServoPos(q, 89);
}
for (int q = 2; q < 4; q++) {//anti clockwise barriers gates
setServoPos(q, 89);
}
gateState = 0;
}
break;
case 2://raise barriers
//no timer, will trigger from external...train
for (int q = 0; q < 2; q++) {//clockwise barriers/gates
setServoPos(q, 0);
}
for (int q = 2; q < 4; q++) {//anti clockwise barriers gates
setServoPos(q, 179);
}
gateServoTimer = currentMillis;
gateState = 3;
break;
case 3:
Serial.println(gateState);
if (currentMillis - gateServoTimer >= 1500) { //wait 1 secs while gate raises
gateState = 0;
ledState = 6;//turn leds off
triggerMillis = currentMillis;//reset trigger timer
}
break;
default:
break;
}
}
void setup() {
Serial.begin(9600);
Serial.println("PCA9685RailCrossingV1");
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);//All Led's off
}
//servos in start position
for (int q = 0; q < 2; q++) {//clockwise barriers/gates
setServoPos(q, 0);
}
for (int q = 2; q < 4; q++) {//anti clockwise barriers gates
setServoPos(q, 179);
}
}
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
}
Example 2: PCA9685RailCrossingV2.ino
Click to Download code: PCA9685RailCrossingV2.ino
Version 2 introduces an array that holds the target position and current position for the servos, this allows the barrier to be moved in a slower and more realistic movement.
/* PCA9685RailCrossingV2
03/01/2024
MOds made to slow the servo speeds
Railway Crossing Sketch
PCA9685 board for servos...2 gate or 4 gate
PCA9685 board for LED's.... orange static, red flashing
gates triggered by timer for demo version
*/
#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;//keeps track of trigger timing
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]
//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() {
//gate closing trigger
if (currentledState < 1) { //don't trigger if already triggered
if (currentMillis - triggerMillis >= 10000) { //triggers after 5 seconds if nothing happening
triggerMillis = currentMillis;//reset timer
//this is the line that trigers the close sequence
ledState = 1;
Serial.println("Trigger Close");
}
}
//gate opening trigger
if (currentMillis - triggerMillis >= 10000) { //triggers after 5 seconds...simulate train passing
//This "if" atatement causes the gates to close, could be triggered by a sensor or button.
if (gateState < 2 && ledState == 5) { //
Serial.println("Trigger Open");
triggerMillis = currentMillis + 10000;//reset timer
gateState = 2;
//ledState = 6;
}
}
}
//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);
}
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
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
}
}
}
}
void setup() {
Serial.begin(9600);
Serial.println("PCA9685RailCrossingV2");
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
}
}
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
#55 Multiple PCA9685 PWM Servo Boards with servos and LED's 05/01/2024
PCA9685 PWM Servo Board 27/12/2023
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 #56 PCA9685 Level Crossing Barriers and Lights as a reference.