Last Updated: 01/01/2024
PCA9685 Servo Button Control
Projects >> PCA9685 Servo Button Control
#54 PCA9685 Servo Button control aimed at Model railway point/turnout control
In this project we are going to control a number of servos using buttons to select the direction of movement.
This project is the second in a series on using an Arduino with servos. This project is aimed at controlling model railway points/turnouts but the code could be used for any project that needs servos controlled with buttons.
In the previous tutorial we built a PCA9685 Servo Setter Project for working out the movement that the servo needs to make the point switch.
This project will build on the lesons learnt ni that project. The Additional resouces link at the bottom of this page has other useful links to help with this project.
Project Overview and issues to overcome
The goal of this project will be to move a servo between 2 set angles when a button is pressed. So for each servo we will require 2 buttons.
As well as moving the point/turnout there is often a requirement to switch the power to the "V" of the point. To accomplish this we will use a relay module that has connections for "Normally Open" and "Normally Closed". The difference is that in one state the connection is made when the relay id powered while in the other state the cnotact is made when unpowered. So one is "Switch to turn on" while the other is "Switch to Turn Off".
These relays come in various formats, single, 2, 3,4,6,8 and 16 per board.
Make sure you have the correct version with 3 terminals per relay as shown below. also check the voltage, some are powered by 5v, others by 12v and even 24v.
In my project I have set this up for 5V.
What type of board should I use?
So far we are using 2 buttons and a relay for each servo, so 3 buttons per servo.
So if you want to use 16 servos you will
need 48 pins so will require a Mega2560.
There are methods such as resistor ladders to reduce the number of button pins required as well as various omponents we can use to add extra pins but I have highlighted thhis issue as part of the things to consider when building a project.
In later projects we will look at sending commands via DCC or wireless communication so the buttons will not be needed but it is something to think about before you start.
Power Consumption
This is probably the area that is ignored the most in projects like this but it is very important and can even effect the way our code is written.
A small MG90/SG90 servo draws 70mA at rest but around 500mA at stall current. stall current is the power required at maximum torque so when the servo is pushing at it's hardest. If we have built our mechanism correctly we should only be drawing about 250-300mA at most per servo.
The relays also have a current draw. About 70mA on standby (just the circuitry power) buut 560mA when powered/Switched. As the relay needs to be powered all the time when the point is in one direction that is a constant 560mA draw.
This means that each point at peak power will be drawing 300mA + 560mA so it will max out at around 860mA.
This means if we use a full 16 servos on the PCA9685 board and moved them all at the same time
we would draw 13.76Amps...that's a lot of Amps!
Now there are ways to get round this but it just shows how in a project it is very easy for little details to get missed that will have a huge impact later on.
13Amps will require a wire of a minimum of 1.5mm of copper diameter so with insulation around 3mm.
As can be seen from these figures this method of switching polarity will work on smaller layouts but for very large layouts other methods may need to be used.
Pinout and wiring diagrams
This is the basic wiring diagram for the system.
For this example I will only be using 2 servos, to add more servos just repeat the same wiring to different pins and the same with the switches and relays.
If you are using the point blades to switch the power to the V you can ignore the relays.
WARNING The 5V to the PCA9685 board and relays MUST NOT come from the Arduino 5V pin. You will be drawing too many Amps for the Arduino and may cause damage to your Arduino.
Pins
8 - 11 are connected to push button (momentary switches) with a 4.7k OHM pull down resistor
12-13 to relays
A5 to PCA9685 SCL
A4 to PCA9685 SDA
Make sure you have 5v going to the PCA9685 and relay boards 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: PCA9685ServoButtonsV1.ino
Click to Download code: PCA9685ServoButtonsV1.ino
This version uses nested if statements to read the buttons and although it works would be very complicated if it was controlling 16 servos.
Thios version has NO relay control.
/* PCA9685ServoButtonsV1
31/12/23
NON relay
This example shows how to control the servos using buttons.
This code DOES NOT have any relay code so for swithcing points
you will need to use the point blades
This example uses 2 servos, one on Pin 0, the other on pin 12
adding more buttons/servos is just a matter of extending the code.
Code is written as a state machine without delay() so that other code (without delay()
can be added if needed.
PCA9685 board
2 servos
4 buttons
4 x 4.5k OHM resistors for Pull Down on buttons
SDA/SCL connections for PCA9685
Arduino UNO: A4 (SDA), A5 (SCL)
Arduino Mega 2560: 20 (SDA), 21 (SCL)
ESP32: 21(SDA), 22 (SCL)
pins 8 - 11 for buttons with 4.7K pull down resistors
*/
#include "Wire.h"
#include "Adafruit_PWMServoDriver.h"
//PCA 9685 settings
// called this way, it uses the default address 0x40
Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver();
// you can also call it with a different address you want
//Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver(0x41);
// you can also call it with a different address and I2C interface
//Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver(0x40, Wire);
#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
//buttons...will all need pull down resistors
const int servo0Left = 8;
const int servo0Right = 9;
const int servo12Left = 10;
const int servo12Right = 11;
// our servos
int servo0 = 0;//port on PCA9685
int servo12 = 12;//port on PCA9685
//these are the values you would get from the PCA9685 Servo setter project
//see https://www.digitaltown.co.uk/projectPCA9685ServoSetter.php
int servo0LPos = 45;//servo left angle
int servo0RPos = 179;//servo right angle
int servo12LPos = 33;
int servo12RPos = 168;
//System variables
unsigned long currentMillis; //will stire the current time in from millis()
unsigned long buttonTimer;//stores the next value in millis for debounce
int buttonDebounce = 200;//smaller vale makes buttons more sensitive, easier to get double press
//Servo movement function
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
pwm.writeMicroseconds(servo, sendPos);
}
}
//deals with button presses
//This works but would become very complicated and hard to read with 16 servos
void buttonState() {
if (currentMillis - buttonTimer >= buttonDebounce) { //are buttons debounced
buttonTimer = currentMillis;//reset debounce timer
//the system will only read one button at a time so checks through the buttons in order and if any are pressed
//that button will be dealt with and any other presses at the same time ignored
//This means only 1 debounce timer is needed.
if (digitalRead(servo0Left) > 0) {
setServoPos(servo0, servo0LPos);
Serial.println("Servo 0: Left");
} else {
if (digitalRead(servo0Right) > 0) {
setServoPos(servo0, servo0RPos);
Serial.println("Servo 0: Right");
} else {
if (digitalRead(servo12Left) > 0) {
setServoPos(servo12, servo12LPos);
Serial.println("Servo 12: Left");
} else {
if (digitalRead(servo12Right) > 0) {
setServoPos(servo12, servo12RPos);
Serial.println("Servo 12: Right");
}
}
}
}
}
}
void setup() {
Serial.begin(9600);
Serial.println("PCA9685ServoSetterV1");
pwm.begin();
pwm.setOscillatorFrequency(27000000);
pwm.setPWMFreq(SERVO_FREQ); // Analog servos run at ~50 Hz updates
delay(10);//let the above values take effect
pinMode(servo0Left, INPUT);
pinMode(servo0Right, INPUT);
pinMode(servo12Left, INPUT);
pinMode(servo12Right, INPUT);
}
void loop() {
currentMillis = millis();//get the current millis since board started for timing
buttonState();//deals with button presses
}
Example 2: PCA9685ServoButtonsV2.ino
Click to Download code: PCA9685ServoButtonsV2.ino
This version places all the pin and position variables into an array.
This means it is much easier to add extra servos/buttons into the code with less risk of bugs.
Thios version has NO relay control.
/* PCA9685ServoButtonsV2
31/12/23
Built on the foundations of PCA9685ServoButtonsV1 but with all servo pin and settings data stored in an array.
This is to make it easier to add more servos/buttons
NON relay
This example shows how to control the servos using buttons.
This code DOES NOT have any relay code so for swithcing points
you will need to use the point blades
This example uses 2 servos, one on Pin 0, the other on pin 12
adding more buttons/servos is just a matter of extending the code.
Code is written as a state machine without delay() so that other code (without delay()
can be added if needed.
PCA9685 board
2 servos
4 buttons
4 x 4.5k OHM resistors for Pull Down on buttons
SDA/SCL connections for PCA9685
Arduino UNO: A4 (SDA), A5 (SCL)
Arduino Mega 2560: 20 (SDA), 21 (SCL)
ESP32: 21(SDA), 22 (SCL)
pins 8 - 11 for buttons with 4.7K pull down resistors
*/
#include "Wire.h"
#include "Adafruit_PWMServoDriver.h"
//PCA 9685 settings
// called this way, it uses the default address 0x40
Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver();
// you can also call it with a different address you want
//Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver(0x41);
// you can also call it with a different address and I2C interface
//Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver(0x40, Wire);
#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
//position in the following array is the values you would get from the PCA9685 Servo setter project
//see https://www.digitaltown.co.uk/projectPCA9685ServoSetter.php
# define numButtons 4 //number of buttons controlling servos
int servoVariables[numButtons][3] = { //2 lines per servo (2 buttons)
{8,0,45}, //pin 8, servo 0, position
{9,0,179}, //pin 9, servo 0, position
{10,12,33}, //pin 10,servo 12, position
{11,12,168} //pin 11, servo 12, position
};
//System variables
unsigned long currentMillis; //will stire the current time in from millis()
unsigned long buttonTimer;//stores the next value in millis for debounce
int buttonDebounce = 200;//smaller vale makes buttons more sensitive, easier to get double press
//Servo movement function
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
pwm.writeMicroseconds(servo, sendPos);
}
}
//deals with button presses
//This works but would become very complicated and hard to read with 16 servos
void buttonState() {
int q;
if (currentMillis - buttonTimer >= buttonDebounce) { //are buttons debounced
buttonTimer = currentMillis;//reset debounce timer
for(q=0;q < numButtons;q++){
if(digitalRead(servoVariables[q][0]) > 0){
setServoPos(servoVariables[q][1], servoVariables[q][2]);
q=numButtons;//causes system to exit for loop and wait for debounce again
}
}
}
}
void setup() {
Serial.begin(9600);
Serial.println("PCA9685ServoSetterV2");
pwm.begin();
pwm.setOscillatorFrequency(27000000);
pwm.setPWMFreq(SERVO_FREQ); // Analog servos run at ~50 Hz updates
delay(10);//let the above values take effect
for(int q=0;q < numButtons;q++){
pinMode(servoVariables[q][0], INPUT);//set the pins used for buttons in the servoVariables array
}
}
void loop() {
currentMillis = millis();//get the current millis since board started for timing
buttonState();//deals with button presses
}
Example 3: PCA9685ServoButtonsV3.ino
Click to Download code: PCA9685ServoButtonsV3.ino
This version places all the pin and position variables into an array and also adds the relays
The code can be seen to work even without a relay as the built in LED will be on when the servo connected to pin 13 is powered.
Although the code works very well for large layouts there will still be issues with power consumption with lots of relays.
/* PCA9685ServoButtonsV3
31/12/23
Built on the foundations of PCA9685ServoButtonsV2
This time the relay code has been added.
This example uses 2 servos, one on Pin 0, the other on pin 12
Relays are on pins 12 and 13
adding more buttons/servos is just a matter of extending the code.
Code is written as a state machine without delay() so that other code (without delay()
can be added if needed.
PCA9685 board
2 servos
4 buttons
4 x 4.5k OHM resistors for Pull Down on buttons
2 relay modules
SDA/SCL connections for PCA9685
Arduino UNO: A4 (SDA), A5 (SCL)
Arduino Mega 2560: 20 (SDA), 21 (SCL)
ESP32: 21(SDA), 22 (SCL)
pins 8 - 11 for buttons with 4.7K pull down resistors
pins 12-13 relays
*/
#include "Wire.h"
#include "Adafruit_PWMServoDriver.h"
//PCA 9685 settings
// called this way, it uses the default address 0x40
Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver();
// you can also call it with a different address you want
//Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver(0x41);
// you can also call it with a different address and I2C interface
//Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver(0x40, Wire);
#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
//position in the following array is the values you would get from the PCA9685 Servo setter project
//see https://www.digitaltown.co.uk/projectPCA9685ServoSetter.php
# define numButtons 4 //number of buttons controlling servos
int servoVariables[numButtons][5] = { //2 lines per servo (2 buttons)
{8,0,45,12,0}, //pin 8, servo 0, position,relay,off
{9,0,179,12,1}, //pin 9, servo 0, position,relay,on
{10,12,33,13,0}, //pin 10,servo 12, position,relay,off
{11,12,168,13,1} //pin 11, servo 12, position,relay,on
};
//System variables
unsigned long currentMillis; //will stire the current time in from millis()
unsigned long buttonTimer;//stores the next value in millis for debounce
int buttonDebounce = 200;//smaller vale makes buttons more sensitive, easier to get double press
//Servo movement function
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
pwm.writeMicroseconds(servo, sendPos);
}
}
//deals with button presses
//This works but would become very complicated and hard to read with 16 servos
void buttonState() {
int q;
if (currentMillis - buttonTimer >= buttonDebounce) { //are buttons debounced
buttonTimer = currentMillis;//reset debounce timer
for(q=0;q < numButtons;q++){
if(digitalRead(servoVariables[q][0]) > 0){
setServoPos(servoVariables[q][1], servoVariables[q][2]);//move the servo
digitalWrite(servoVariables[q][3],servoVariables[q][4]);//set the relay to appropriate value
q=numButtons;//causes system to exit for loop and wait for debounce again
}
}
}
}
void setup() {
Serial.begin(9600);
Serial.println("PCA9685ServoSetterV3");
pwm.begin();
pwm.setOscillatorFrequency(27000000);
pwm.setPWMFreq(SERVO_FREQ); // Analog servos run at ~50 Hz updates
delay(10);//let the above values take effect
for(int q=0;q < numButtons;q++){
pinMode(servoVariables[q][0], INPUT);//set the pins used for buttons in the servoVariables array
pinMode(servoVariables[q][3], OUTPUT);//set the pins used for relays servoVariables array
}
}
void loop() {
currentMillis = millis();//get the current millis since board started for timing
buttonState();//deals with button presses
}
Additional Resource Links
PCA9685 PWM Servo Board 27/12/2023
PCA9685 Servo Setter Project 29/12/23
How To 1 : Debouncing buttons 15/09/2021 Lesson 11: Arrays 28/08/2021Comments
This site has been designed to be child friendly, this means that comments cannot be added to videos or directly to the site. To add a comment or ask a question please email the address in this image: and use PCA9685 Servo Button Control as a reference.