Abstract. This project develops a class/library Coal_Basin_Servo that controls 16 servos on a PCA9685 Servo Driver Board.
Include File: <Coal_Basin_Servo.h>
Constructor Coal_Basin_Servo servo; //no parameters
Public Methods: begin( ); //no arguments.
do_servo(int channel, int direction); // channel 0 - 15, direction LEFT/RIGHT (0,1) - two positions.
Required Libraries. <Wire.h> <Adafruit_PWMServoDriver.h>
Required Hardware. Project tested with Arduino NANO, PCA9685 Servo Driver Board. Driver. The board requires a separate 5V supply.
Keywords: Nano, Servo driver, PCA9685, Cpp, Libraries/Clsses, Adafruit_PWMServoDriver, inheriting libraries, two-dimension static arrays.
Possible Audience: Model railway enthusiasts wishing to control points using a small servo. There are a number of practical problems when using the servos. These are discussed in the page Coal_Basin_Servo_Issues.
----------------------------------------------------
An area known as the Coal Basin as shown below is being developed for a model train layout.
The control panel consists of a number of push buttons shown above as 'A' through 'M' that supply requests to control the points "1" through "11". These in turn control servos** to switch the points to allow trains to travel on the routes 'A' through 'M'.
**The project develops the class library Coal_Basin_Servo.
----------------------------------------------------------------
The interface circuit is shown below. To power the PCA9685 Driver Board a separate 5 volt power source is required - the Nano 5 volt output is not sufficient although it should be used to drive the internal logic in the servo driver.
-------------------------------------------------------------------------
For this project the servos will be controlled using the Adafruit_PWMServoDriver class/library. This library in turn will use the Wire class/library to communicate with the PCA9685 servo board. It will be necessary to include the header files for both these libraries. Both libraries are available as part of the Arduino IDE although it might be necessary to use the the Manage Libraries to load the Adafruit_PWMServoDriver. That is Sketch->Include Library->Manage Libraries and search for Adafruit_PWM and when found select Install.
----------------------------
To develop a Cpp class/library 3 files are required: Coal_Basin_Servo.ino, Coal_Basin_Servo.cpp and Coal_Basin_Servo.h.
In the Arduino IDE create a new file project and save_as Coal_Basin_Servo. When saving the Arduino IDE will create a new directory/folder Coal_Basin_Servo. The Arduino IDE will save the new file Coal_Basin_Servo in that directory/folder. By default it will have the extension *.ino
In the Arduino IDE by using the inverted triangle on the top right create two new files with labels Coal_Basin_Servo but extensions *.cpp and *.h. These two files will make the Coal_Basin_Servo class or library. The *.ino file will be used as the application or test file.
The *.cpp is the class implmentation file and *.h the class header.
----------------------------
There is no right or wrong place to start the code development.
One way is to start with the *.ino or test code. This will consist of two components:
(i) Lines that are always necessary with any class. In this case class Coal_Basin_Servo, and
(ii) Methods that it would be desirable to implement. These may change as the project progresses.
Possible starting code might be:
//Coal_Basin_Servo.ino Test or application file
#include "Coal_Basin_Servo.h"
Coal_Basin_Servo servo;
void setup() {
servo.begin( );
while(1) {
servo.do_servo(15,LEFT);
delay(2000);
servo.do_servo(15,RIGHT);
delay(2000);
}
}
void loop() {
}
Notes: The lines
#include "Coal_Basin_Servo.h"
Coal_Basin_Servo servo;
void setup() { servo.begin( ); ...
These three lines satisfy (i) above and include the header file, they create an instance or object of the class Coal_Basin_Servo and initialize the object/instance.
The lines that include do_servo( ) are the desirable routines and include as arguments the channel of interest and which way the servo is to be turned.
The next job is to define the Coal_Basin_Servo class.
-------------------------------------
The header file will include the class definition class Coal_Basin_Servo { };
Within that class are the public methods as suggested by the *.ino file requirements. That is begin( ) to initialise the servo object and do_servo( ) to control servo on channel ch with argument LEFT or RIGHT that are also defined.
//Coal_Basin_Servo.h Header File
#ifndef COAL_BASIN_SERVO_H
#define COAL_BASIN_SERVO_H
#include "Arduino.h"
#define LEFT 0
#define RIGHT 1
class Coal_Basin_Servo {
public:
Coal_Basin_Servo( );
void begin( );
void do_servo(int ch, int dir);
};
#endif
The remaining lines are typical for header files that basically ensure that the file is not included in a project more than once.
The challenge will be the *.cpp or implementation file.
----------------------------------------
For starters the Coal_Basin_Servo.cpp must include the following;
//Coal_Basin_Servo.cpp implementation file
#include "Coal_Basin_Servo.h"
#include <Wire.h>
#include <Adafruit_PWMServoDriver.h>
Adafruit_PWMServoDriver pwm(0x40);
1. The line #include "Coal_Basin_Servo.h" points to the header file developed in the previous section.
2. The line <Adafruit_PWMServoDriver.h> includes the Servo Driver header files allowing all the methods in that library/class to be available for this project'
3. For the I2C interface Adafruit_PWMServoDriver calls methods from the Wire Library so Wire.h is included in the line #include <Wire.h>
4. The line Adafruit_PWMServoDriver pwm(0x40); creates one instance of the class Adafruit_PWMServoDriver. The argument is 0x40 to match the hardware address of the PCA9685 board.
At this point the library/class methods will be:
Coal_Basin_Servo::Coal_Basin_Servo( ) { }
void Coal_Basin_Servo::begin( ){
pwm.begin( );
pwm.setPWMFreq(50);
}
void Coal_Basin_Servo::do_servo(int ch, int dir){
}
5. The Coal_Basin_Servo constructor requires no action.
6. The begin method for the Coal_Basin_Servo class requires setting up the pwm object. This uses the two methods begin( ) and setPWMFreq(50) where setPWMFreq(50) sets the frequency of the servo signal to 50 Hx or a 20mS period.**
7. As given the two methods pwm.begin( ) and pwm.setPWMFreq(50) will be called for each Coal_Basin_Servo object created..++
8. The do_servo implementation will be discussed in the next section
** begin( ) would initialize pins A4 and A5 as outputs for example.
++ Any code in the implementation file that includes the scope operator, ie DCC_Points_Control:: a new copy will be generated for each instance. For the remaining code there is only one copy.This includes everything in the class Adafruit_PWMServoDriver Library.
----------------------------------------------------
The end goal here is to send a message of the form setPWM(channel, start_time, end_time) to the pwm object. If the start_time is set to zero the operation becomes setPWM(channel,0, clock_counts) where clock_counts will be of he order of 100 to 300 to give a pulse width of 0.5 to 2.5 mS to control the servo**.
If all the servos including mounting and linkages are identical the code for do_servo(ch,dir) could be of the form (sample timings given**):
void Coal_Basin_Servo::do_servo(int ch, int dir){
if (dir==LEFT) pwm.setPWM(ch,0,100);
else pwm.setPWM(ch,0,200);
}
For my project, while the servos are nominally the same, the linkages are all different so a two-dimensional array is set up. That is in the implementation file
int _servo_timings [16] [2] ={
100,200, //ch 0
100,200, //ch 1
100,200, //ch 2
100,200, //ch 3
100,200, //ch 4
100,200, //ch 5
100,200, //ch 6
100,200, //ch 7
100,200, //ch 8
100,200, //ch 9
100,200, //ch 10
100,200, //ch 11
100,200, //ch 12
100,200, //ch 13
100,200, //ch 14
100,200 //ch 15
};
void Coal_Basin_Servo::do_servo(int ch, int dir){
pwm.setPWM(ch,0,_servo_timings[ch][dir]);
}
The pwm.setPWM( ) method now reads the array to determine the counts or end time.
** Times and count will vary for different servos and will be found by experiment once the target harware is available
--------------------------------
Prior to the actual servos being connected the results can be observer on the signal pin of the PCA9685 with a multi-meter. With the test code in the *.ino the channel 15 output will oscillate between just over 100mV and 200mV every 2 seconds.
If a Logic Timing Analyaer is available it may be used. The first trace shows the time between each pulse as 20mSec while the second trace has captured the transition between a short pulse and a much longer pulse of 2mSec.
Using the timing facilities of the Logic Timing Analyzer the results were found to be:
Timing Count Measured pulse width
100 480uSec First spike in second trace
200 980uSec
300 1.5mSec
400 2.0 mSec Second pulse in second trace
Using an actual servo, the results were:
With the setting for LEFT (100) the servo arm was at approximately 135degrees anti-clockwise from the vertical. As the count increases the servo arm moves further anti-clockwise until the count is approximately vertically downward at a count of 200. When the count is set to 300 (RIGHT) the servo arm moves through approximately 90 degrees and with a count of 400 the servo arm is close to horizontal.**++
Once the servos are located in the final product the timing can be adjusted. (It could be very fiddly)
** The actual angles depend very heavily on how the servo arm is initially aligned with the servo motor.
++ It is worthwhile making a copy of this diagram to assist in setting the final servo settings especially if the servo is located beneath the railway baseboard.
------------------------------
To create the library Coal_Basin_Servo: **
1. Pack the two files, Coal_Basin_Servo.cpp and Coal_Basin_Servo.h into a *.zip file. Zipping willl give the default label Coal_Basin_Servo.zip
2. In the Arduino IDE use the sequence -> Include Library -> Add *.zip Library and select Coal_Basin_Servo.zip
3. In the Arduino work directory there will now be a sub-directory "libraries" and a further subdirectory "Coal_Basin_Servo" that contains the *.cpp and *.h files.
4. To include the Coal_Basin_LEDs library in any project include the line #include <Coal_Basin_Servo.h> **
5. As per this project, Coal_Basin_LEDs route_dsp; will create an object of the class Coal_Basin_Servo.
** With quotes the header file will only be foud in the same directory/folder as the project. With < > the compiler will look in the libraries folder.
** It will be absolutely necessary to test the servo on the final hardware and set all of the timings before generating the library.
---------------------------------------------------------