A few months ago I bought a new benchtop multimeter for the ToolGuyd test lab. Since then, I decided to fill in some remaining equipment gaps with a few more purchases, including a new Agilent oscilloscope.
Related: Agilent 34461A Digital Multimeter Review
The newly purchased Agilent oscilloscope came in about a week ago, but I hadn’t had the time to test it out until this week.
Before you read further, I must warn you – the following is tool-related, but mainly discusses test & measurement equipment and digital signals. It can be a little hard to follow, but I tried to explain the more esoteric parts with background information.
Advertisement
In case you’re short on time, here’s the summary:
I unboxed my new oscilloscope, and then checked it with a “known” signal only to see a disagreeing measurement. Then I tried another known signal that was supposed to be the same, but saw a different and expected measurement. Then I looked at how the two signals are created and realized my silly mistakes. The end.
And now the same story in many more words:
Initial Testing
I started off by powering up the scope and connected a probe to the first analog channel. The next step was to compensate the probe, which involves connecting it to the scope’s built-in test signal and turning a small variable capacitor with a mini plastic screwdriver.
After the probe was adjusted, I explored the measurement functions a little bit.
Advertisement
That’s weird… the 1.0 kHz test signal is actually 1.0012 kHz? My cheaper scopes output 1.0001 kHz signals. An extra 1.2 Hz isn’t anything to get upset over, but I really expected the signal be a little closer to 1.0000 kHz.
I later checked the oscilloscope’s signal with one of my multimeters, but it’s worth showing the measurement image now.
1.0012 kHz.
Fine, maybe the signal is off by a small amount for some reason, not a big deal.
Background
I had an Arduino microcontroller board (learn more) loaded with code for centering hobby-style servos. The code drew upon Arduino’s built-in servo library, and thus all I needed were a few lines of code to send the servo motor to its 90° center position.
What’s an Arduino?
An Arduino is a small programmable development board. In this case, I was using one to control a servo using pulse width modulation (PWM), which involves digital pulses of a certain width (1.5 ms) and frequency (~50Hz). The servo library is a pre-written program that simplifies the user-supplied code necessary to control a servo.
What’s a Servo?
Servos (Wikipedia) are small devices with built-in motors, gearboxes, and control circuitry. Their output shafts often rotate with a limited range of motion, such as from 0° to 180°, with positional control being dependant on the digital pulses send along the signal wire. Servos typically have three wires leading to the control circuitry – 2 for electrical power, and 1 for control.
Servo Control
Servos are typically centered using 1.5 ms (millisecond), or 1500 µs (microsecond) pulses. Other angles can be signalled using shorter or longer pulses, typically in the range of 1.0 ms to 2.0 ms wide. For continuous-rotation servos, the pulse signal controls direction and speed (instead of position), with 1.5 ms usually being the stop signal.
The test signal was set such that a standard servo would be adjusted to its 90° center position.
For as long as I have been working with servos, 1500 µs = center, so I *knew* 90° = 1500 µs.
Testing the Signal and Hitting a Wall
For reasons explained at the end of Part 2, I *knew* that I was working with a 1500 µs, or 1.500 ms signal.
I connect the oscilloscope, and what do I see?
A pulse width of 1.472 ms.
That’s not right…
So I whipped out my Rigol oscilloscope. 1.50 ms. Huh? The Agilent scope was supposed to be leaps and bounds better than the Rigol. The Rigol measurements varied a little as I adjusted the horizontal range, but the measurements stabilized to read 1.50 ms for the width and 18.5 ms for the pulse separation width.
18.5 + 1.50 = 20 ms = 50 Hz, which made sense.
18.525 + 1.472 = 19.997 = ~50 Hz, which also makes sense, but these were not the numbers I was expecting to see.
So out came my inexpensive function/signal generator. I compared measurements between Agilent and Rigol oscilloscopes and an Agilent multimeter for multiple signals. I learned nothing insightful from all of this. The Agilent oscilloscope was a little bit off, but mostly accurate.
I also opened up Agilent’s BenchVue software, to see if maybe there were different measurement tools there that would show something different. Maybe the Agilent’s measurement algorithm was a little off.
There weren’t any analysis tools in BenchVue, but the captured trace at least looks a little better than the screenshot of the scope’s trace.
I then spent a lot of time trying to figure out why the measurements were different. I changed the acquisition settings, then the measurement settings, the triggering, and anything else I could think of.
Ah, what if I adjusted the measurement cursors to manually measure the pulse widths? No, that didn’t work. Even spreading the cursors a little bit in opposite direction didn’t give me a measurement of 1.500 ms. The signal was truly ~1.472 ms. This showed that the oscilloscope’s measurement algorithms were not at fault here.
Even when I had the scope take the average for 8, 16, then 128 measurements, it still showed a pulse width of 1.472 ms.
The Rigol scope, on the other hand, moved around, from 1.40 to 1.60, and even hit 1.48 ms after a bit of adjustment.
At this point I was frustrated, but there was a plus side – I learned a lot about the new scope’s operation in a very short time. Otherwise, I would have learned its features and controls a lot more slowly.
Ah, maybe the digital probe will show something else?
1.472 ms.
A Closer Look at the Signal
At this point, I was beginning to get worried. Was my brand new oscilloscope a dud?
So then I turned my attention to the signal I was looking at. The following is the important part of the Arduino code that I had been using.
#include // Servo myservo; void setup() { myservo.attach(3); // 3 is the output pin myservo.write(90); // set servo to mid-point (90°) } void loop() {}
Basically, this code takes the desired angle, 90°, and sends the servo the appropriate pulse.
I decided to try a different approach. Instead of having the servo library interpret the input angle of 90° before sending a signal, I would skip a step and send a signal for 1.5 ms.
#include Servo myservo; void setup() { myservo.attach(3); myservo.writeMicroseconds(1500); // set servo to mid-point } void loop() {}
Clearing the Wall
Bingo!
1.500 ms pulse width.
The digital probe measurement was in agreement.
So there’s no problem with the scope’s calibration or measurement functions, as it measured a 1.5 ms to be 1.5 ms!
So what was the problem? Me. Without even realizing it, I made some very bad assumptions.
Knowledge vs. Understanding
First, I didn’t write the first code myself. The day before I put together a small robotic servo-controlled pan and tilt mechanism, and used the kit manufacturer’s servo-centering tool. This code was already loaded into the Arduino, which is why it was handy for me to test the new oscilloscope with.
The code is pretty standard, and I knew what it does, but didn’t really ever think about how it does it. The second code example more closely represents how I have controlled servos in the past – with explicitly specified pulse widths, in microseconds and not degrees.
Both code examples should work exactly the same and should both send 1.5 ms pulses to a servo. But it’s apparent that they don’t. Why?
Before now I hadn’t looked very closely at the Servo.h library. Here are a few lines of code from that file. Don’t worry if it doesn’t make sense – just look at the numbers.
attach(pin ) - Attaches a servo motor to an i/o pin. attach(pin, min, max ) - Attaches to a pin setting min and max values in microseconds default min is 544, max is 2400 write() - Sets the servo angle in degrees. #define MIN_PULSE_WIDTH 544 // the shortest pulse sent to a servo #define MAX_PULSE_WIDTH 2400 // the longest pulse sent to a servo #define DEFAULT_PULSE_WIDTH 1500 // default pulse width when servo is attached
I still don’t really understand how Arduino’s servo.write(angle) command works, but according to the Arduino reference, the angle should be from 0 to 180 degrees.
In the servo library, the minimum pulse width default is defined to be 544 µs, and the maximum is defined to be 2400 µs. These values are outside of the 1000 µs and 2000 µs parameters I have used in the past for direct servo control.
Since I did not define the minimum and maximum pulse widths in the attach command, it seems that the program automatically fell back to the library defaults of 544 and 2400 microseconds – 544 for 0° and 2400 for 180°. This wouldn’t have been as much an issue if the default minimum and maximum pulse width were symmetrical around 1500 µs, but they’re not!
90° is right between 0° and 180°. 544 plus 2400 divided by 2? 1472, or 1.472 ms.
So that’s where the 1.472 ms comes from!
Lessons Learned
Ultimately, I learned a few things from the experience.
When using Arduino’s servo library, it is definitely a good idea to set min and max pulse widths within the attach command. Otherwise it will use 544 and 2400 ms defaults. I haven’t a clue as to where the 544 ms value comes from, but it definitely skews things a bit. As shown above, entering the angle as 90° did not quite drive the servo to 90°, as the pulse was 1.472 ms and not 1.500 ms.
Maybe an updated version of the library can set the minimum pulse width to 600 ms, so that 90° translates to 1500 ms and not 1472 ms. The default maximum and minimum pulse widths can be modified in the library locally, but it will be better to set these limits in sketches in case the library is ever replaced or overwritten when installing a new Arduino IDE.
The silly part is that this is all documented on Arduino’s reference page, which I probably read through a while back.
For simply centering a servo, a 28 µs pulse width difference is not a very big deal. But for a servo meant to be controlled from 0° to 180° in 1000 µs to 2000 µs pulses, that 28 µs shift could mean a 5° shift away from center! If you have two servos coupled and working together in opposite directions, such as in robotic crawlers or walkers, their positions could be out of sync by 10°!
This is all in theory. In practice, common inexpensive servos don’t have high enough positioning resolution where an error of a few degrees will make a difference. With such servos, a 1.472 ms pulse might have the same exact effect as a 1.500 ms one.
Sending a 544 µs pulse when 1000 µs is intended, or 2400 µs instead of 2000 µs, could also lead to unpredictable results. Additionally, some servos could potentially be damaged if sent non-standard pulse widths of 544 ms and 2400 ms for 0° and 180°.
Generally, it’s okay to blindly use Arduino libraries, but far better to read through and understand them first. I previously mainly used writeMicroseconds() instead of write(), which is why I had not come across this complication sooner. Moving forward, I definitely won’t take for granted the “optional” min and max pulse parameters of the Servo attach command.
These are all things that experienced hobbyists and makers already know. Although far from being an Arduino or robotics beginner, the obviousness of my mistakes and wrongful assumptions makes me feel rather foolish. My hope is that this account will potentially help others from making the same mistakes and assumptions.
Although I feel quite silly about all this, I regret the frustrations but not the experience.
I wish I could also say I feel foolish for investigating such a minor spread between the signal I was expecting and the one I was measuring, but I don’t. In addition to more quickly familiarizing myself with the new scope’s controls, I now have a better understanding of how Arduino’s servo library and commands work, which will come in handy for future projects.
One last lesson – next time I check a testing tool it will probably just be with a function generator or other highly predictable signal, instead of whatever’s right in front of me.
Bob S
I can undertstand your frustration but lessons learned like this will certainly be remembered better than just reading over boring documentation. This is a lesson learned by experience. Many people entering the work force for the first time have a lot of knowledge but no experience. Many people under estimate the value of experience. Successful people combine education with experience to make themselves better employees or entrepreneurs.
JC Brenes
Hi Stuart, I understand how frustrating is to expend a lot of time debugging something that we thing is easy, but your effort to document it is very valuable. Your post help me debug the issue I was experiencing while controlling my servos with Arduino for a robotic arm project. Thanks!
Here’s a link to my blog: http://onecuriousrobot.blogspot.com/2014/10/coming-from-halt-servo-calibration.html
Jeff
I recently picked up a project that I haven’t touched in a long time. Previously, when I only used one ESC and one DC motor, I got excellent results. When adding additional ESC/motor pairs to the system, chaos ensued. I decided to pull out some meters and look at the arduino pwm outputs before trying again. I hope that your article here is the key.
Nice work.
Mark
In addition to your increased knowledge, you can add the benefits of teaching others. I’m fairly new to Arduino and robotics and I learned a lot from your article. I’ve been using servo.h library for some time but your article (and some of my own digging) has sharpened my understanding of the library, coding and how servos work. Your use of multiple scopes and meters is an excellent lesson to learn in problem solving. – thanks