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

PCA9685 Servo Setter Project

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.

Pirton Crossing

Pinout and wiring diagrams


All my decoders are built using the circuit at

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.

Circuit diagram


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:

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

I2C device found at address 0x40 !

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!

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

//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%

    case 11:  // Fire flicker
      if (Direction == 0) {
        fireFlickerON = 1;  //trn 0n
      } else {
        fireFlickerON = 0;  //turn off

    case 12:  // Arc welder
      if (Direction == 0) {
        arcON = 1;  //trn on
      } else {
        arcON = 0;  //turn off

    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

    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

  Serial.print("notifyDccAccTurnoutOutput: ");
  Serial.print(Addr, DEC);
  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);
      case 2:
        servoBoard2.writeMicroseconds(servo, sendPos);
      //add more servo boards as below
      // case 2:
      // servoBoard3.writeMicroseconds(servo, sendPos);
      // break;
        Serial.println("Invalid Board, add to system");

//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
        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() {

  //start forst servo board
  servoBoard1.setPWMFreq(SERVO_FREQ);  // Analog servos run at ~50 Hz updates
  //start 2nd servo board
  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, 2, 1);
  // Call the main DCC Init function to enable the DCC Receiver

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

PCA9685 PWM Servo Board

PCA9685 Servo Setter Project

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

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


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.