Last Updated: 03/04/2024
#62 DCC accessory decoder for PCA9685 Servos and LED's
Projects >> #62 DCC accessory decoder for PCA9685 Servos and LED's
#62 DCC accessory decoder for PCA9685 Servos and LED's
In previous projects I have demonstarted how to control servos using an Arduino with PCA9685 boards for both servos and LED's.
In this project we will combine that code with the DCC Accessory decoder code to create a DCC Accessory decoder that can control both points and lighting effects.
If you are new to this I would suggest working through the previous projects that all build up to this project.
PCA9685 PWM Servo Board
Model Railway Light Effects using PCA685 and LED's with Arduino or ESP32
Multiple PCA9685 PWM Servo Boards with servos and LED's
For Arduino UNO/Mega
DCC Accessory Decoder, sounds and lights Arduino UNO
For ESP32
ESP32 DCC Accessory Decoder
In this version only two servos are connected although 32 could be run in this set up and hundreds more if extra PCA9685 boards are added.
I have also added some leds, 4 working as building lights with plain on/off and 2 effect leds. One runs as an arc welder simulator while the other does a fire flicker.
The system will work with any NMRA compliant DCC system such as NCE, Digitrax, DCC EX etc.
Pinout and wiring diagrams
DO NOT CONNECT YOUR ESP32 DIRECTLY TO THE TRACK
All my decoders are built using the circuit at https://www.digitaltown.co.uk/79DCCDecoderCircuit.php
The components are very cheap and can be built up in minutes on a bread board and should be attached to pin 2 of the Arduino.
The PCA9685 circuit is shown below.
Please note that the 5v IS NOT supplied by the Arduino. You will need a seperate supply due to the power draw being more than an Arduino can handle.
Also note the PCA9685 have both the VCC and V+ or the screw connectors connected to the 5v supply.
Leds and Servo locations are set in the code and are for illustration purposes only.
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 DCC decoder circuit
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: AccDecoderUNOPCA9685v1.ino
Click to Download code: AccDecoderUNOPCA9685v1.ino
This code uses resources from a number of previous tutorials.
To see the individual sections see the additional resources at the bottom of the page.
NOTE: Sometimes web pages do not render code correctly so please download the code and load into your Arduino IDE.
/* 23/01/2024
*
* AccDecoderUNOv1
* Blank Acc decoder script for the UNOComplete rewrite.
*
* pin 2 dcc pin
*
*/
//dcc settings
#include "NmraDcc.h"
NmraDcc Dcc;
DCC_MSG Packet;
//end dcc settings
#include "Wire.h"
#include "Adafruit_PWMServoDriver.h"
//pca9685 settings
Adafruit_PWMServoDriver servoBoard1 = Adafruit_PWMServoDriver(0x40);
Adafruit_PWMServoDriver servoBoard2 = Adafruit_PWMServoDriver(0x42);
Adafruit_PWMServoDriver ledBoard1 = Adafruit_PWMServoDriver(0x41);
#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
//servos
//servo board 1 servo
const int servoB1P0 = 0; //attached to port 0 on servoBoard1... on layout use useful name "frontSiding1" etc
const int servoB2P3 = 3; //attached to port 3 on servoBoard2
unsigned long currentMillis;
//controls all fire leds
int fireFlickerLED = 0; //channel zero on station
unsigned long fireFlickerTimer;
int fireFlickerTimerPeriod;
byte fireFlickerON = 0; //0 = off 1 = on
unsigned long arcTimer;
int arcTimerPeriod;
int arcLEDState;
unsigned long arcBreakTimer;
int arcBreakTimerPeriod;
int arcBreakTimerState;
int arcBreakCounter;
int arcBreakCounterTarget;
int ArcWeldLED = 5; //channel zero on station
byte arcON = 0; //0 = off 1 = on
// This function is called whenever a normal DCC Turnout Packet is received and we're in Output Addressing Mode
void notifyDccAccTurnoutOutput(uint16_t Addr, uint8_t Direction, uint8_t OutputPower) {
int q;
switch (Addr) {
//
case 10: // building lights
if (Direction == 0) {
for (q = 1; q < 5; q++) {
ledBoard1.setPWM(q, 0, 2000); //turn leds off
}
} else {
for (q = 1; q < 5; q++) {
ledBoard1.setPWM(q, 0, 4096); //turn on about 70%
}
}
break;
case 11: // Fire flicker
if (Direction == 0) {
fireFlickerON = 1; //trn 0n
} else {
fireFlickerON = 0; //turn off
}
break;
case 12: // Arc welder
if (Direction == 0) {
arcON = 1; //trn on
} else {
arcON = 0; //turn off
}
break;
case 20: //servo board 1 servo
if (Direction == 0) {
setServoPos(1, servoB1P0, 45); //1 = servoBoard1, port value, 45 value from servo setter
} else {
setServoPos(1, servoB1P0, 120); //1 = servoBoard1, port value,120 value from servo setter
}
break;
case 30: //servoBoard2 servo
if (Direction == 0) {
setServoPos(2, servoB2P3, 50); //2= servoBoard2, port value,50 value from servo setter
} else {
setServoPos(2, servoB2P3, 130); //2= servoBoard2, port value, 130 value from servo setter
}
break;
}
Serial.print("notifyDccAccTurnoutOutput: ");
Serial.print(Addr, DEC);
Serial.print(',');
Serial.println(Direction, DEC);
}
void setServoPos(int servoBoard, 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
//this switch statement decides what board is being controlled
//
switch (servoBoard) {
case 1:
servoBoard1.writeMicroseconds(servo, sendPos);
break;
case 2:
servoBoard2.writeMicroseconds(servo, sendPos);
break;
//add more servo boards as below
// case 2:
// servoBoard3.writeMicroseconds(servo, sendPos);
// break;
default:
Serial.println("Invalid Board, add to system");
break;
}
}
}
//coal fire flicker function
void fireFlicker() {
if (fireFlickerON == 0) {
ledBoard1.setPWM(fireFlickerLED, 0, 4096); //turn off fire flicker effect
} else {
if (currentMillis - fireFlickerTimer > fireFlickerTimerPeriod) {
fireFlickerTimer = currentMillis;
fireFlickerTimerPeriod = random(500);
ledBoard1.setPWM(fireFlickerLED, 0, random(4095));
}
}
}
//arc welder effect
void arcWelder() {
if (arcON == 0) {
ledBoard1.setPWM(ArcWeldLED, 0, 4096); //turn off arc weld effect
} else {
if (currentMillis - arcTimer > arcTimerPeriod) {
arcTimer = currentMillis;
if (arcLEDState < 1) {
if (arcBreakTimerState > 0) { //only allow the LED to light if not on a break... does not apply to turn off state;
ledBoard1.setPWM(ArcWeldLED, 0, 4095); //turn LED on full brightness
arcLEDState = 1;
arcTimerPeriod = random(40, 100);
}
} else {
//led must turn off on a break...otherwise it could get stuck in an ON position
ledBoard1.setPWM(ArcWeldLED, 0, 4096); //turn LED off
arcLEDState = 0;
arcTimerPeriod = random(20, 80);
}
}
//The following section controls the breaks between the arc flashing sessions
//2 types of break...pauses betwen welds and long...gone for tea break...looking at plans breaks
if (currentMillis - arcBreakTimer > arcBreakTimerPeriod) {
arcBreakTimer = currentMillis;
if (arcBreakTimerState < 1) {
arcBreakTimerState = 1;
} else {
arcBreakTimerState = 0; //stop arc welder
arcBreakCounter++;
if (arcBreakCounter < arcBreakCounterTarget) {
//break during welding...between 1 and 6 seconds...pauses in welding
arcBreakTimerPeriod = random(1000, 6000);
} else {
//long break...welder gone for cup of tea
arcBreakCounter = 0;
arcBreakCounterTarget = random(5, 25); //vary the amount of welding the man does
arcBreakTimerPeriod = random(10000, 20000); //LONG BREAKS break between 10 and 20 seconds for testing
}
}
}
}
}
void setup() {
Serial.begin(115200);
Serial.println("AccDecoderUNOPCA9685v1");
//start forst servo board
servoBoard1.begin();
servoBoard1.setOscillatorFrequency(27000000);
servoBoard1.setPWMFreq(SERVO_FREQ); // Analog servos run at ~50 Hz updates
//start 2nd servo board
servoBoard2.begin();
servoBoard2.setOscillatorFrequency(27000000);
servoBoard2.setPWMFreq(SERVO_FREQ); // Analog servos run at ~50 Hz updates
//start led board
ledBoard1.begin(); //start the PCA9685 for the station building
ledBoard1.setPWMFreq(1600); // This is the maximum PWM frequency and suited to LED's
//the next two lines start the DCC process
// Setup which External Interrupt, the Pin it's associated with that we're using and enable the Pull-Up
Dcc.pin(0, 2, 1);
// Call the main DCC Init function to enable the DCC Receiver
Dcc.init(MAN_ID_DIY, 10, CV29_ACCESSORY_DECODER | CV29_OUTPUT_ADDRESS_MODE, 0);
}
void loop() {
currentMillis = millis(); //get current time since board started in milliseconds
Dcc.process(); //process incoming DCC Commands
fireFlicker(); //calls the fire flicker function
arcWelder(); //creates arc weld effect
}
Additional Resource Links
https://www.digitaltown.co.uk/79DCCDecoderCircuit.php
Model Railway Light Effects using PCA685 and LED's with Arduino or ESP32
Multiple PCA9685 PWM Servo Boards with servos and LED's
For Arduino UNO/Mega
DCC Accessory Decoder, sounds and lights Arduino UNO
For ESP32
ESP32 DCC Accessory Decoder
#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 #62 DCC accessory decoder for PCA9685 Servos and LED's as a reference.