I am still building a Radio Controlled (RC) boat and so I am getting heavy into Radio Control (RC) of models and throwing together an RC system. I want to control gun turrets, navigation lights, cockpit and deck lights, twin motors and as much as I can stuff in there with up to 8 proportional channels.
I have been going through all my old RC gear and found that I still have stuff dating back to 1975, as well as more modern stuff, such as servos designed for the Arduino educational projects. There are a lot of differences. For example:
Testing these servos can be a bit of a problem, since they all have slightly different properties. The Luxor (SG90), Arduino recommended for example, have a peculiar property that if the pulse-width (650μs to 2400μs) is outside their limits then they rotate continuously, while other servos just stop at the end of travel. This could lead to a mechanical entanglement problem, with twin connector rods crossing, or airlerons pushed beyond their limits. Older servos, such as the DigiMax/Futaba servos, were often designed for 90° rotation and had a shorter pulse width 495μs to 1850μs (measured). Longer servo output arms may be needed. These differences are not from published specifications, but from practical measurements. There are quite wide variations between "generic" servos, and they seem to have evolved over the decades.
So you build yourself a new model, put your hand in the junk-box, or visit the local model shop / Arduino shop, and you now have a bag of servos. So how do you test them? Especially the ones from the junk box or car-boot sales? This was my problem. I made an analogue R/C servo tester that works fine. It has a wide range (500μs to 2500μs) and will push most servos that bit beyond their limits so you can find out what they can (or cannot) really do. But if you wanted to use the analogue R/C servo tester in a servo project, for example a remote antenna tuner, then you would have to adjust the timing for each individual servo. The Arduino Nano can save a lot of time - just edit the timing values in the software "easy-peasy!!".
This project has the same function as the analogue R/C servo tester project, but it is implemented in an Arduino Nano microcontroller. This makes it perfect for a permanent installation that can be adjusted for any servo or RC power controller in any project. For example remote antenna tuner.
You have probably gathered that I have recently discovered the Arduino, so I am still a novice. It is fantastic, if you can accept or be aware of it's limitations. You can use it to drive a synthesiser, but not generate Radio Frequencies. The Arduino libraries contain large program modules that have a lot of features, which makes them slower than, for example, Raspberry-Pi or Microlabs PIC series of microcontrollers. But they are perfect for projects such as this. Here is the sketch that I wrote at 1AM this morning (yes I have difficulty slleeping):
// Analogue Proportional Radio Control of models - Servo tester // Generates a pulse from 0.5ms to 2.5ms to test RC servos (see comments) // or use it as a standalone driver for one servo eg., remote antenna tuner // Harry lythall - SM0VPO - https://sm0vpo.com for more information // Declare integer variables used int timerCalculation = 0; int timerMilliseconds = 0; int timerMicroseconds = 0; void setup() { pinMode(9, OUTPUT); // Initialize digital pin 9 as output pinMode(10, OUTPUT); // Initialize digital pin 10 as output Serial.begin(9600); // Initialize serial communication (for debugging) } void loop() { // Start of frame, read joysticks and generate channel pulses // Loop back continuously to repeat control frame digitalWrite(10, HIGH); // Generate sync pulse delayMicroseconds(100); // Sync pulse duration digitalWrite(10, LOW); // End sync pulse digitalWrite(9, HIGH); // Generate servo pulse timerCalculation = 24680; // (25000μs minus processing time outside timing loops) // Read joystick, map (scale) values and send to timer // E-sky servos use 800μs to 2200μs (645, 2045) // Sanwa servos use 830μs to 2400μs (675, 2245) // Futaba servos use 495μs to 1850μs (340, 1695) (year: 1975) // Dynam servos use 735μs to 2355 (575, 2195) // Luxor (SG90) servos use 650μs to 2400μs (495, 2245) // (NOTE: Luxor SG90 servers rotate 360° continuously if max/min valeues exceeded) int joystickValue = analogRead(A0); // Read joystick value int pulseWidth = map(joystickValue, 0, 1023, 495, 2245); // Map joystick value to pulse width (650μs to 2400μs) delayMicroseconds(pulseWidth); // Time delay based on joystic value digitalWrite(9, LOW); // Generate servo pulse // Calculate remaining time to end-of-frame (milliseconds and microseconds) (timerCalculation) = (timerCalculation) - (pulseWidth); // Subtract channel pulse from frame length counter (timerMilliseconds) = (timerCalculation / 1000); // 25000μs is too big for delay, convert to ms (integer) (timerMicroseconds) = (timerMilliseconds * 1000); // How much was lost generating the ms integer (in μs)? // Wait the milliseconds and then the microseconds increment delay(timerMilliseconds); //Wait the whole milliseconds count // Wait μs increments lost in integer - frame now 25ms long delayMicroseconds(timerCalculation - timerMicroseconds); }
I tried to keep it as simple as possible, while being understandable. I have also added loads of comments with details that will help you to understand and to modify the code to suit your own needs. As in the previous project I have also added an Oscilloscope synchronisation pulse of 100μs. This can be used to trigger your oscilloscope, but I used my 2-channel digital storage scope to display both sunc-pulses and servo-pulses. Trigger on the falling edge of Channel-1. here is a description of the code:
// Declare integer variables used int timerCalculation = 0; int timerMilliseconds = 0; int timerMicroseconds = 0; void setup() { pinMode(9, OUTPUT); // Initialize digital pin 9 as output pinMode(10, OUTPUT); // Initialize digital pin 10 as output Serial.begin(9600); // Initialize serial communication (for debugging) }
This is the usual setup. I found that I only need to declare integer variables, but the compiler was quite happy with me using new signed variables in the code. Pin-9 of the Nano is the variable PWM signal to the servo, and pin-10 is for the oscilloscope synchronisation.
void loop() { // Start of frame, read joysticks and generate channel pulses // Loop back continuously to repeat control frame digitalWrite(10, HIGH); // Generate sync pulse delayMicroseconds(100); // Sync pulse duration digitalWrite(10, LOW); // End sync pulse digitalWrite(9, HIGH); // Generate servo pulse timerCalculation = 24680; // (25000μs minus processing time outside timing loops)
Reading the joystick and scaling the values to suit the servo timing all take time in the Arduino, so I sent a sync pulse to the oscilloscope and then imediately started the servo pulse, before any joystick reading or other processing. This prevented a 155μs delay between the sync and servo pulses by moving the Arduino processing delay to within the servo-control pulse. It is here I also defined the frame length of 25ms (24680μs). You can put a frequency counter on pin 10 and adjust this value if you want to have a faster servo-refresh rate. It set mine to 40.01Hz.
// Read joystick, map (scale) values and send to timer // E-sky servos use 800μs to 2200μs (645, 2045) // Sanwa servos use 830μs to 2400μs (675, 2245) // Futaba servos use 495μs to 1850μs (340, 1695) (ca: 1975) // Luxor (SG90) servos use 650μs to 2400μs (495, 2245) // (NOTE: Luxor SG90 servers rotate 360° continuously if max/min valeues exceeded) int joystickValue = analogRead(A0); // Read joystick value int pulseWidth = map(joystickValue, 0, 1023, 495, 2245); // Map joystick value to pulse width (650μs to 2400μs) delayMicroseconds(pulseWidth); // Time delay based on joystic value digitalWrite(9, LOW); // Generate servo pulse
This code reads the joystick, "maps" (scales) the values to suit your servo, then executes the time delay before switching OFF the servo PWM pulse. Note that the servo timing (in μs) is adjusted by subtracting 155μs from the delay needed, due to the Arduino processing delay. I have also added comments about some common servo parameters, obtained by physical measurements. Published data may not be exactly the same, but I want to know what my servos can actually do, and their limitations.
// Calculate remaining time to end-of-frame (milliseconds and microseconds) (timerCalculation) = (timerCalculation) - (pulseWidth); // Subtract channel pulse from frame length counter (timerMilliseconds) = (timerCalculation / 1000); // 25000μs is too big for delay, convert to ms (integer) (timerMicroseconds) = (timerMilliseconds * 1000); // How much was lost generating the ms integer (in μs)? // Wait the milliseconds and then the microseconds increment delay(timerMilliseconds); //Wait the whole milliseconds count // Wait μs increments lost in integer - frame now 25ms long delayMicroseconds(timerCalculation - timerMicroseconds); }
Now that the sync and servo-pulses are completed, we need to wait until the end of the frame, which is a TOTAL time of 25ms, measured from the start of the servo PWM control pulse. So here I subtract the PWM pulse time (variable by joystick or potentiometer) from the total delay needed. I also seperated the ms part and the μs values and called two timers, one for the ms and one for the μs. A total of 320μs is taken by the Arduino (and the sync pulse) in the whole frame, so the frame variable was set to 25000 - 320 = 24680μs.
I rather liked the PIC16F628A micro-controller becasue it uses machine code, no time-wasting rountines; only the commands needed for the current use. You always know that with a 4MHz oscillator every instruction takes 1μs, but the chip will accept a 20MHz oscillator, so instruction steps become 0.2μs. The down-side is that the PIC16F628A is not so easy to program as the Arduino. The Arduino is more simple than the old GW-BASIC and a lot of the programming work has been done for you.
As you can probably appreciate, I have relied heavily on my UNI-T dual-channel oscilloscope so that you can edit the sketch and get the timing right. This code was trimmed for speed, for example, I could have created a sub-routine to generate a pulse, then call it from the main loop. Putting the routine in the main loop without a "GOSUB and RETURN" (in GW-BASIC vocabulary) saves time Ok, then here are the timing waveforms:
By the way, in all the Arduino projects they use a 5KΩ potentiometer to divide the 5-Volts line to create a 0-V to 5-V input to the analogue ports. In this project I used a 100KΩ potentiometer. The 5KΩ potentiometer draws 1mA of current from the battery supply, while the Nano draws only 20mA, although I suspect a huge chunk of this is to power the bright LED on the board. The Arduino Nano can be powered via the Mini-B USB connection, 6-20V unregulated external power supply (pin 30), or 5V regulated external power supply (pin 27). But being a mean-old-Englishman I want to get current consumption down as low as possible. I may even cut off that bright LED and see how much battery current I can save, before I put this project in a nice plastic boks; There is nobody in the box to appreciate the LED!
This project is a simple way of generating a Pulse-Width-Modulated signal for driving RC servos and other RC equipment, but this time using an Arduino Nano. It was also developed and tested during a sleepless night. Like the analogue variant, you can "cobble it together in minutes", and then use it for a number of applications, for example:
I hope that this project has given you some "food for thought". You can always e-mail me at harry.lythall@[my domain].com. You can even use oeieio@hotmail.com or hotmail@sm0vpo.com as they are both valid e-mail accounts for me 😉, although I would prefer that you visit my messageboard if you have any questions about this or any other project. I always look forward to receiving feedback, whether it be positive or negative ☺
Very best regards from Harry Lythall
SM0VPO (QRA = JO89WO), Märsta, Sweden.