A new, more accurate method of calibrating the stepper's initial position. Previously, I can calculate the arm's position at any position on the potentiometer. However, this requires quite a bit of measurement and interpolation and extrapolation.
Instead, I measure the potentiometer's value once, at a known angle. In this case, its 45 degrees up from horizontal. Upon startup, the arms each move until the potentiometers hit the set value, which means the arm is at (almost) exactly 45 degrees up from horizontal. Basically the potentiometers act like optical gates.
The improvement on calibration can be seen in the vertical traverses. Previously, the robot would deviate from a vertical line by around 1/3 of an inch. Now, the deviation is less than 1/32 of an inch.
Showing posts with label Pythagoras. Show all posts
Showing posts with label Pythagoras. Show all posts
Thursday, January 5, 2012
A new Pythagoras video
With 50% more effort put into it. A time lapse video of some intense shading.
Labels:
Pythagoras,
video
Thursday, December 29, 2011
Pythagoras: Structural Update
Solving the Flex
After dealing with the flexing aluminum supports that allowed the whole robot to wobble for so long, I finally went ahead and bought steel tubing. The legs are also longer, allowing expansion of the drawing field. Unfortunately, they are also taller, so for now, the paper needs a little booster to get it to the optimal drawing height.
The tubing is connected by gusset plates, made of .03" galvanized steel.
Now the bot is so sturdy I could probably stand on it and it would hold.
After dealing with the flexing aluminum supports that allowed the whole robot to wobble for so long, I finally went ahead and bought steel tubing. The legs are also longer, allowing expansion of the drawing field. Unfortunately, they are also taller, so for now, the paper needs a little booster to get it to the optimal drawing height.
![]() |
| Phone books to lift the paper to the pen |
The tubing is connected by gusset plates, made of .03" galvanized steel.
![]() |
| Slots in the plates for adjusting exact lengths |
Now the bot is so sturdy I could probably stand on it and it would hold.
Labels:
Pythagoras
Pythagoras Bitmap Mode: How It's Done
Introduction
Bitmap mode involves shading in the color of images, as opposed to just making the outlines. For some images, this makes vastly superior output compared to outlines. Faces are a prime example. When outlined, edges of shadows appear the same as edges of objects, causing a mess of lines to be drawn on a face. With shading, shadows and lower contrast features remain in the image, improving the familiarity of the face.
The downside is that the output resolution is greatly reduced, and the time it takes to draw is greatly increased.
The Concept
How do I generate levels of grey scale when my pen only creates a line of constant darkness? The answer: squiggles. Each pixel forms a 2D square on the paper. The more that square is shaded in by the pen, the darker the pixel. This idea can be clearly seen in one of the earlier test pictures. Ramp function patterns are drawn on the paper to fill the square. Then I tested with square functions, then triangle waves.
Since the shading depends on the width of the pen stroke, the pixel size on paper becomes a factor. Smaller pixels make smoother borders around regions on the output image, but also decreases the number of squiggles that can be fit into a pixel before the pixel becomes solid dark. So at one extreme, the pixel is completely filled by a single stroke of the pen, while at the other extreme, the entire image is a single pixel.
Through testing, I found the best compromise between greyscale levels and output resolution is to have an image width of between 120 and 160 pixels, for a 7.5 inch (19 cm) square drawing area.
Tweaking Waveforms
First was experimenting with different waveforms. After creating ramp functions, I tried to do square waves. However, this created odd behavior whenever the squares shifted in and out of phase with the rows above and below them. It was quickly abandoned, apologies for the lack of pictures. What I settled on was triangle waves, which were always in phase like the ramp functions, yet more uniform like square waves, closer to the ideal of sine waves.
In an attempt to squeeze out more greyscale levels, I considered sharing squiggles across multiple pixels, up to four pixels. This way I could get even less dense pen strokes and therefore lighter colored intensities. The results were not so great though. This image allowed up to four combined pixels.
The pixel combining created awkwardly shaded areas in an image. Even when combining less pixels.
Pixel combining didn't create evenly lighter areas, just awkward gaps. The solution to this is to use amplitude modulation. To make a lighter region, the amplitude of the squiggle is reduced, instead of the number of squiggles. This can be seen clearly below.
This fills the gap in shading between one squiggle per pixel and no squiggle, making nicer transitions for lighter colors.
Color Mapping
Originally, color was mapped linearly to number of squiggles in each pixel. This caused for pretty dark images, as seen below.
The first step to fixing this is to add gamma correction. Since human eyes are more sensitive to darker colors than lighter colors, color values in images are shifted toward the darker regions according to an exponential rule. This way, more bits are devoted toward the darker colors, instead of highlights that eyes cannot differentiate anyways.
To correct for gamma, simply raise the pixel value, mapped from 0 to 1, to the (1/2.2) power to get the actual color intensity. This has an effect of making the output image brighter. Technically, any image processing should be done after gamma correction, for proper mixing of pixel values. However, this will involve a lot of going between gamma encoded and decoded, so I let it slide. I do scaling before gamma correction, and gamma correct once, right before converting the pixel value to squiggles. This makes the picture a little brighter.
The next step in color mapping is to fix the mapping of intensity to squiggles. The mapping is not linear, and is depending on the size of the pixel. The mapping I got from experimenting is intensities 0.7 and less are linearly mapped to squiggle density. Between 0.85 and 0.7 intensity is the region for amplitude modulation of the squiggles. Testing was done with printing gradients and comparing the result to the computer image.
Software
The image processing software was written in Python, using the OpenCV libraries for image processing and simple GUIs. One slider lets the user change the output resolution until the aliasing looks alright, and a second slider lets the user change the gamma value from the default of 2.2, to change the brightness of an image.
There is a subtlety in the image preview. While the screen can display the full 256 values of greyscale, the robot can only print a much smaller range of greyscale, which varies as a function of pixel size. This can be especially misleading for lighter colored regions, since full white pixels cover color intensities of 0.85 and 0.7, and for darker colored regions, where seemingly differently colored regions are both shaded the same.
To fix this, the output pixel values are grouped into buckets based on the number of squiggles in that pixel. This way, the output image has the same number of greyscale levels as the printed image. With some funky mapping, the colors on screen are closer to the colors as printed. This way, it is much easier to adjust the brightness and scaling and predict the actual results. Following are screenshots of the program in action after bucketing. You can see how there are significantly less than 256 levels of grey.
Appendix
Bitmap mode involves shading in the color of images, as opposed to just making the outlines. For some images, this makes vastly superior output compared to outlines. Faces are a prime example. When outlined, edges of shadows appear the same as edges of objects, causing a mess of lines to be drawn on a face. With shading, shadows and lower contrast features remain in the image, improving the familiarity of the face.
![]() |
| Its my face! |
The Concept
How do I generate levels of grey scale when my pen only creates a line of constant darkness? The answer: squiggles. Each pixel forms a 2D square on the paper. The more that square is shaded in by the pen, the darker the pixel. This idea can be clearly seen in one of the earlier test pictures. Ramp function patterns are drawn on the paper to fill the square. Then I tested with square functions, then triangle waves.
Since the shading depends on the width of the pen stroke, the pixel size on paper becomes a factor. Smaller pixels make smoother borders around regions on the output image, but also decreases the number of squiggles that can be fit into a pixel before the pixel becomes solid dark. So at one extreme, the pixel is completely filled by a single stroke of the pen, while at the other extreme, the entire image is a single pixel.
Through testing, I found the best compromise between greyscale levels and output resolution is to have an image width of between 120 and 160 pixels, for a 7.5 inch (19 cm) square drawing area.
Tweaking Waveforms
First was experimenting with different waveforms. After creating ramp functions, I tried to do square waves. However, this created odd behavior whenever the squares shifted in and out of phase with the rows above and below them. It was quickly abandoned, apologies for the lack of pictures. What I settled on was triangle waves, which were always in phase like the ramp functions, yet more uniform like square waves, closer to the ideal of sine waves.
In an attempt to squeeze out more greyscale levels, I considered sharing squiggles across multiple pixels, up to four pixels. This way I could get even less dense pen strokes and therefore lighter colored intensities. The results were not so great though. This image allowed up to four combined pixels.
![]() |
| The pixel combining can be observed near the center of the drawing. |
![]() | |
| A series of test drawings under various brightness settings. Scribbling in the corner is unrelated. |
![]() |
| Just below the center is an example of amplitude modulation of the squiggle. |
Color Mapping
Originally, color was mapped linearly to number of squiggles in each pixel. This caused for pretty dark images, as seen below.
![]() |
| Can barely make out differences in the greys. |
To correct for gamma, simply raise the pixel value, mapped from 0 to 1, to the (1/2.2) power to get the actual color intensity. This has an effect of making the output image brighter. Technically, any image processing should be done after gamma correction, for proper mixing of pixel values. However, this will involve a lot of going between gamma encoded and decoded, so I let it slide. I do scaling before gamma correction, and gamma correct once, right before converting the pixel value to squiggles. This makes the picture a little brighter.
![]() |
| The same picture, with gamma correction. A little brighter. |
Software
The image processing software was written in Python, using the OpenCV libraries for image processing and simple GUIs. One slider lets the user change the output resolution until the aliasing looks alright, and a second slider lets the user change the gamma value from the default of 2.2, to change the brightness of an image.
There is a subtlety in the image preview. While the screen can display the full 256 values of greyscale, the robot can only print a much smaller range of greyscale, which varies as a function of pixel size. This can be especially misleading for lighter colored regions, since full white pixels cover color intensities of 0.85 and 0.7, and for darker colored regions, where seemingly differently colored regions are both shaded the same.
To fix this, the output pixel values are grouped into buckets based on the number of squiggles in that pixel. This way, the output image has the same number of greyscale levels as the printed image. With some funky mapping, the colors on screen are closer to the colors as printed. This way, it is much easier to adjust the brightness and scaling and predict the actual results. Following are screenshots of the program in action after bucketing. You can see how there are significantly less than 256 levels of grey.
|
| Default scaling of 120 pixels and gamma of 2.2 (220 / 100). |
![]() |
| Darker image. |
![]() |
| Brighter image. |
Appendix
- Code to convert image to a list of squiggles for the robot: https://github.com/aaronbot3000/deltadraw/blob/master/vectorizer/rasterize.py
- Web album of Pokemon drawn by Pythagoras: https://picasaweb.google.com/aaron.y.fan/PythagorasPokemon?authuser=0&feat=directlink
Monday, December 26, 2011
3D Video of Pythagoras
While visiting at Lake Brantley High School, one of the students took a
delightful video of Pythagoras in 3D as it sketched Pikachu in raster
mode.
Labels:
drawings,
Pythagoras,
video
Tuesday, December 6, 2011
Pythagoras Bitmap Mode
Just finished cooking up a new drawing mode, letting Pythagoras draw raster images. A writeup will come soon, for now a bunch of drawings.
![]() |
| First test |
![]() |
| Second test |
![]() |
| Third test |
![]() |
| Before gamma correction |
![]() |
| After some gamma correction. Teehee moire patterns. |
![]() |
| In progress |
Labels:
drawings,
Pythagoras
Friday, November 25, 2011
Pythagoras v2 Drawings
Some drawings.
![]() |
| Working out calibration. Third time's the charm! |
![]() |
| I like this face. |
![]() |
| Self Portrait |
![]() |
| Test of accuracy and capability. Nine hours to draw |
Labels:
drawings,
Pythagoras
Monday, November 21, 2011
Fixing the Analog Signal
Routing an analog signal all over the place with ribbon cable and no filtering and no buffering made it extremely susceptible to any bit of noise, not to mention the voltage sag due to the loading of the ADC. Given that I'm using 5k pots and I need precision to roughly 0.3 degrees, the loading effects are quite significant. How to fix? Twisted wires, buffers, and some hardcore low pass filters.
Twisted Wires
My friends advised I twist the wires to help reduce noise. To be honest, I'm not sure how it works with non-differential signals, but I supposed it was simple enough to implement, and it does clean up the rats nest. And so, twisted wires.
Low Pass Filtering
Nothing fancy like Chebyshev or Butterworth. Just a simple RC filter that does the trick. Using the formula 1/(2 * pi * R * C) = -3db frequency, a 3.9 kOhm resistor along with 2 uF capacitor, I get a cutoff frequency of around 20 Hz, which is plenty high enough for a mechanical system such as this. The schematic looks like this.
On the oscilloscope, it turns the input (black) into a nice smoothed line (red).
Learning About Op-Amp Response Ranges
To fix the loading problem by adding in some op-amp buffers. Using unity feedback, I boost the current supply from a couple microamps to a couple milliamps. Excellent for keeping a stable voltage. The schematic looks like this.
When I tried out my new circuit, the circuit seemed great. Until I hit the low ranges. Then the output shot straight up, as such.
And it does so at 1.3 V, suspiciously close to two diode voltages (0.7 volts). The problem is that op amps are normally not designed to operate near their rail voltages. Here I use +5 and 0 volts, with an input voltage of 0 to 3.3 volts. Thus, it craps out near zero volts.
The solution is some rail to rail op amps. I used some op amps from TI, the so called TLV2774 Quad 2.7-V High-Slew-Rate Rail-to-Rail Output Operational Amplifier. It is rail to rail, so it can operate near its supply voltages, has four op amps in one package, though I only need three, and has input bias current in the picoamps. Also, free samples!
The entire circuit looks like this, with incredibly stable output voltage.
Digital Filtering
In addition to the analog filtering, I implemented some (relatively) long working digital filter. It takes 24 samples at 100 microsecond intervals. It then drops the lower eight samples and upper eight samples, and averages the middle eight samples. Yeah filtering!
Precision in measuring voltage is absolutely essential. Unlike a standard cartesian robot, where a little imprecision creates a constant offset, incorrect calibration in the arms causes the robot to not draw on a plane, but on some curved surface (I don't exactly know what it looks like, as it is some complex surface).
And so, with this new precision, a new video of the drawing!
Comparison of the output, with version 1 on the left and 2 on the right.
![]() |
| The new revised board |
Twisted Wires
My friends advised I twist the wires to help reduce noise. To be honest, I'm not sure how it works with non-differential signals, but I supposed it was simple enough to implement, and it does clean up the rats nest. And so, twisted wires.
Low Pass Filtering
Nothing fancy like Chebyshev or Butterworth. Just a simple RC filter that does the trick. Using the formula 1/(2 * pi * R * C) = -3db frequency, a 3.9 kOhm resistor along with 2 uF capacitor, I get a cutoff frequency of around 20 Hz, which is plenty high enough for a mechanical system such as this. The schematic looks like this.
On the oscilloscope, it turns the input (black) into a nice smoothed line (red).
![]() |
| Sorry, I don't have a legitimate oscope capture. I promise it looked like this! |
Learning About Op-Amp Response Ranges
To fix the loading problem by adding in some op-amp buffers. Using unity feedback, I boost the current supply from a couple microamps to a couple milliamps. Excellent for keeping a stable voltage. The schematic looks like this.
When I tried out my new circuit, the circuit seemed great. Until I hit the low ranges. Then the output shot straight up, as such.
And it does so at 1.3 V, suspiciously close to two diode voltages (0.7 volts). The problem is that op amps are normally not designed to operate near their rail voltages. Here I use +5 and 0 volts, with an input voltage of 0 to 3.3 volts. Thus, it craps out near zero volts.
The solution is some rail to rail op amps. I used some op amps from TI, the so called TLV2774 Quad 2.7-V High-Slew-Rate Rail-to-Rail Output Operational Amplifier. It is rail to rail, so it can operate near its supply voltages, has four op amps in one package, though I only need three, and has input bias current in the picoamps. Also, free samples!
The entire circuit looks like this, with incredibly stable output voltage.
Digital Filtering
In addition to the analog filtering, I implemented some (relatively) long working digital filter. It takes 24 samples at 100 microsecond intervals. It then drops the lower eight samples and upper eight samples, and averages the middle eight samples. Yeah filtering!
Precision in measuring voltage is absolutely essential. Unlike a standard cartesian robot, where a little imprecision creates a constant offset, incorrect calibration in the arms causes the robot to not draw on a plane, but on some curved surface (I don't exactly know what it looks like, as it is some complex surface).
And so, with this new precision, a new video of the drawing!
![]() |
| What an improvement! |
Wednesday, November 2, 2011
Pythagoras: Moving the Steppers
How do I move the steppers now? I can no longer simply send a position, like in version one. Something else will have to be cooked up.
After much thought...
I decided on a method quite similar to how version one was controlled in that incremental sense. However, I will have to extend the "moving in increments" to the motors too. In a nutshell, the manipulator moves a small increment forward along the path. Through inverse kinematics, I then get a set of new arm angles. Given the current angles of the arm, I then calculate the number of stepper steps it will take for all the arms to reach the new angles. I then distribute the steps for all of the arms so that they all finish at the same time, providing a good enough approximation for straight line travel. Confusing, isn't it?
Example time!
Given the arms angles are all at zero degrees. The step size is five degrees. Every program cycle, an arm either steps or doesn't step.
Then, the manipulator moves. The requested arm angles are 10, 20, and 30 degrees for arms one, two, and three, respectively. This means it will take the steppers at least six steps to meet the requested arm angles (30 degrees difference, 5 degrees step, 30 / 5 = 6 steps). So this means each cycle, arm one moves 1.67 degrees, arm two 3.33 degrees, and arm three 5 degrees. But arms one and two move less than one stepper step size each cycle. What do?
We create an auxiliary set of arm angles. They represent the ideal positions of the arms, following the increments of the step even if they are less than a step size.
At the end of program cycle one, we have:
Updates to:
As you can see, all the motors reach their targets at the same time! However, how do you control motor speed then? The same as in version one, by using small increments of the manipulator. Since the algorithm is set to "consumes" at least one cycle, even if no stepper moves, by having very small increments the algorithm spends most of its time moving the manipulator and not much time moving the steppers.
Just the nasty details of actually implementing it. Its a good thing I already did it. Of course, the obligatory video:
The steppers here are moving slowly at 1/2 microstepping, so they are quite loud. But it is a good demonstration of the capability for different speeds, as well as a proof of concept.
There is one problem
Lack of experience with analog signals has come to get me. Noise is all over the place in the feedback from the potentiometers, so much that the manipulator home position differs by an inch between resets. That's not very good precision there. Coming up is the solution. Hint: Twisted wires, op-amp buffers, analog low pass filters supplementing hardcore digital filters.
After much thought...
I decided on a method quite similar to how version one was controlled in that incremental sense. However, I will have to extend the "moving in increments" to the motors too. In a nutshell, the manipulator moves a small increment forward along the path. Through inverse kinematics, I then get a set of new arm angles. Given the current angles of the arm, I then calculate the number of stepper steps it will take for all the arms to reach the new angles. I then distribute the steps for all of the arms so that they all finish at the same time, providing a good enough approximation for straight line travel. Confusing, isn't it?
Example time!
Given the arms angles are all at zero degrees. The step size is five degrees. Every program cycle, an arm either steps or doesn't step.
Then, the manipulator moves. The requested arm angles are 10, 20, and 30 degrees for arms one, two, and three, respectively. This means it will take the steppers at least six steps to meet the requested arm angles (30 degrees difference, 5 degrees step, 30 / 5 = 6 steps). So this means each cycle, arm one moves 1.67 degrees, arm two 3.33 degrees, and arm three 5 degrees. But arms one and two move less than one stepper step size each cycle. What do?
We create an auxiliary set of arm angles. They represent the ideal positions of the arms, following the increments of the step even if they are less than a step size.
At the end of program cycle one, we have:
- Ideal: Motor 1: 1.67 Motor 2: 3.33 Motor 3: 5
- Actual: Motor 1: 0 Motor 2: 0 Motor 3: 0
- Ideal: Motor 1: 3.33 Motor 2: 6.66 Motor 3: 10
- Actual: Motor 1: 0 Motor 2: 0 Motor 3: 5
Updates to:
- Ideal: Motor 1: 3.33 Motor 2: 6.66 Motor 3: 10
- Actual: Motor 1: 0 Motor 2: 5 Motor 3: 10
Fast forwarding:
- Ideal: Motor 1: 5.00 Motor 2: 10 Motor 3: 15
- Actual: Motor 1: 5 Motor 2: 10 Motor 3: 15
- Ideal: Motor 1: 6.16 Motor 2: 13.3 Motor 3: 20
- Actual: Motor 1: 5 Motor 2: 10 Motor 3: 20
- Ideal: Motor 1: 8.33 Motor 2: 16.6 Motor 3: 25
- Actual: Motor 1: 5 Motor 2: 15 Motor 3: 25
- Ideal: Motor 1: 10.0 Motor 2: 20.0 Motor 3: 30
- Actual: Motor 1: 10 Motor 2: 20 Motor 3: 30
As you can see, all the motors reach their targets at the same time! However, how do you control motor speed then? The same as in version one, by using small increments of the manipulator. Since the algorithm is set to "consumes" at least one cycle, even if no stepper moves, by having very small increments the algorithm spends most of its time moving the manipulator and not much time moving the steppers.
Just the nasty details of actually implementing it. Its a good thing I already did it. Of course, the obligatory video:
There is one problem
Lack of experience with analog signals has come to get me. Noise is all over the place in the feedback from the potentiometers, so much that the manipulator home position differs by an inch between resets. That's not very good precision there. Coming up is the solution. Hint: Twisted wires, op-amp buffers, analog low pass filters supplementing hardcore digital filters.
Labels:
kinematics,
Pythagoras
Saturday, October 29, 2011
Pythagoras Version 2: Steppers!
The accuracy of servos was maddening. Since I couldn't tweak the control loop or control the velocity, going back and forth across a line would create two separate paths. It overshot corners like crazy, and made the manipulator oscillate. The search began for a better control.
I had a few options.
The downside is that I have no absolute position reference to start the counting. But that's what potentiometers are for!
Design And Build
Learning from past mistakes, I made the upper-to-lower arm length ratio much smaller, to get the precision over a larger range on the XY plane, at the expense of Z traversal. Potentiometers were linked to the output shaft through gears. I cooked up something that looked like this.
I also rotated the supports and added tabs, so they stay straight better. Later, I would find that using a more flexible metal (6061 aluminum) in this way is a bad idea.
I had a pretty convoluted way of getting both gears and arms onto the stepper shaft. Since I needed the arms to be able to turn past the potentiometer gear, the gears needed to be separate and machined separately. And I also needed a spacer to keep the arms from colliding with the potentiometer gear. The solution: A stack of parts screwed together: 1/8" thick arm, then 1/8" gear, then 1/4" combination spacer and set screw holder.
SolidWorks really crapped out solving the model. Since I kept the upper arms floating to check the range of the manipulator, I kept getting overdefined errors if I tweaked the manipulator a little too far. Moving the manipulator took some time for the computer to solve the model. But I managed. As always, the design files are linked below in the Appendix. Time to find the right motors!
Stepper Motors
After much Google-Fu, I setteled on some 3V, 1.6A, 233 oz-in geared stepper motors from RobotShop. They have dandy planetary gearboxes with very little backlash (< 1 degree), with a gear ratio of 57/11 to 1. Given 200 steps per revolution, after the gearbox, I can get 0.35 degrees per step before microstepping. With my current eight microsteps, I get 0.04375 degrees per step. MWAHAHAHA.
They look like this in real life.
But now to drive the motors from a microcontroller signal. I used Allegro's A4988 stepper drivers on Pololu's breakout board. They support 8-35V, up to 2 A of current, do really neat current control for accurate microstepping, and microstep 2, 4, 8, and 16 divisions.
What is microstepping? Stepper motors move in discrete steps, so to speak. Microstepping allows a motor to stop at various places between the halfway points, multiplying the number of steps a stepper makes per revolution. For example, my steppers are 200 steps per revolution. Microstep two divisions, and it becomes 400. Eight times, and I get 1600 steps per revolution. Of course, this comes at a cost of torque. More steps, less torque in the motor. Also, the microsteps are less accurate then the whole steps, but the loss is negligible in this case (3-5%). Since I'm driving a light load, the torque loss is acceptable too. More information on microstepping and how it's actually done on the Wikipedia page.
Cutting the Parts!
Cutting the parts in the Invention Studio at Georgia Tech lets me use a higher quality and thus better edges. Yay! Here is preliminary results. You can see the arm-gear-set screw holder stack on the stepper's output.
Cutting out the rest of the parts. I took the manipulator and ball joints from version 1.
The entire wiring for the drivers and pots was done point to point with small wire. Nasty, nasty stuff. I'm not proud, but it did work.
All the control signals were wired down through a ribbon cable. Yes, this also includes the analog signals from the potentiometers. Running around the motors. In a ribbon cable parallel to switching digital logic signals. And no filtering, high impedance potentiometers (5 kOhm), and you basically get really really bad data. But I didn't know this now because I have a new control algorithm to write for the steppers.
Coming Up Next
Solidworks models for version 2: https://github.com/aaronbot3000/deltadraw/tree/master/solidworks_models_v2
I had a few options.
- Rip out the servos' circuitry and roll my own control loop
- Rip out the servos' circuitry and use OpenServo's circuitry
- Ditch servos completely and use stepper motors with gearing
The downside is that I have no absolute position reference to start the counting. But that's what potentiometers are for!
Design And Build
Learning from past mistakes, I made the upper-to-lower arm length ratio much smaller, to get the precision over a larger range on the XY plane, at the expense of Z traversal. Potentiometers were linked to the output shaft through gears. I cooked up something that looked like this.
I also rotated the supports and added tabs, so they stay straight better. Later, I would find that using a more flexible metal (6061 aluminum) in this way is a bad idea.
I had a pretty convoluted way of getting both gears and arms onto the stepper shaft. Since I needed the arms to be able to turn past the potentiometer gear, the gears needed to be separate and machined separately. And I also needed a spacer to keep the arms from colliding with the potentiometer gear. The solution: A stack of parts screwed together: 1/8" thick arm, then 1/8" gear, then 1/4" combination spacer and set screw holder.
SolidWorks really crapped out solving the model. Since I kept the upper arms floating to check the range of the manipulator, I kept getting overdefined errors if I tweaked the manipulator a little too far. Moving the manipulator took some time for the computer to solve the model. But I managed. As always, the design files are linked below in the Appendix. Time to find the right motors!
Stepper Motors
After much Google-Fu, I setteled on some 3V, 1.6A, 233 oz-in geared stepper motors from RobotShop. They have dandy planetary gearboxes with very little backlash (< 1 degree), with a gear ratio of 57/11 to 1. Given 200 steps per revolution, after the gearbox, I can get 0.35 degrees per step before microstepping. With my current eight microsteps, I get 0.04375 degrees per step. MWAHAHAHA.
They look like this in real life.
But now to drive the motors from a microcontroller signal. I used Allegro's A4988 stepper drivers on Pololu's breakout board. They support 8-35V, up to 2 A of current, do really neat current control for accurate microstepping, and microstep 2, 4, 8, and 16 divisions.
What is microstepping? Stepper motors move in discrete steps, so to speak. Microstepping allows a motor to stop at various places between the halfway points, multiplying the number of steps a stepper makes per revolution. For example, my steppers are 200 steps per revolution. Microstep two divisions, and it becomes 400. Eight times, and I get 1600 steps per revolution. Of course, this comes at a cost of torque. More steps, less torque in the motor. Also, the microsteps are less accurate then the whole steps, but the loss is negligible in this case (3-5%). Since I'm driving a light load, the torque loss is acceptable too. More information on microstepping and how it's actually done on the Wikipedia page.
Cutting the Parts!
Cutting the parts in the Invention Studio at Georgia Tech lets me use a higher quality and thus better edges. Yay! Here is preliminary results. You can see the arm-gear-set screw holder stack on the stepper's output.
Cutting out the rest of the parts. I took the manipulator and ball joints from version 1.
The entire wiring for the drivers and pots was done point to point with small wire. Nasty, nasty stuff. I'm not proud, but it did work.
All the control signals were wired down through a ribbon cable. Yes, this also includes the analog signals from the potentiometers. Running around the motors. In a ribbon cable parallel to switching digital logic signals. And no filtering, high impedance potentiometers (5 kOhm), and you basically get really really bad data. But I didn't know this now because I have a new control algorithm to write for the steppers.
Coming Up Next
- Updated control for steppers
- How to fix the crappy analog signal problem
- Movement!
Solidworks models for version 2: https://github.com/aaronbot3000/deltadraw/tree/master/solidworks_models_v2
Labels:
Pythagoras
Monday, October 24, 2011
Pythagoras: Sending Data to the Microcontroller
All these points and nothing to do with them? Why, stream them to the microcontroller!
The Protocol
Nothing special. Microcontroller requests a new point, computer sends a new point. If there are no new points, computer sends an end transmission code.
The microcontroller also buffers a set of points. Currently, it is set to store 256 points before requesting more. Pretty arbitrary. So the program flow is:
while stop flag not set
If buffer is empty
reload buffer
if received end transmission
set stop flag
draw all points in buffer
One More Thing
The Z height needs to be adjustable. Get a couple buttons to adjust the drawing height, so the pen touches, not misses or jams into, the paper.
With that done, its picture time!
Complete with documentary quality commentary.
The Protocol
Nothing special. Microcontroller requests a new point, computer sends a new point. If there are no new points, computer sends an end transmission code.
The microcontroller also buffers a set of points. Currently, it is set to store 256 points before requesting more. Pretty arbitrary. So the program flow is:
while stop flag not set
If buffer is empty
reload buffer
if received end transmission
set stop flag
draw all points in buffer
One More Thing
The Z height needs to be adjustable. Get a couple buttons to adjust the drawing height, so the pen touches, not misses or jams into, the paper.
With that done, its picture time!
Labels:
Pythagoras
Pythagoras: Vectorizing Software
Having to program points in the robot is annoying at best and horribly inelegant and time consuming. What's better is the computer generating all these fine points, so all I have to do is say, "Robot! Draw!" and it does.
This program vectorizes raster images, but doesn't quite process (read: not at all) vector images, such as SVG or DXF. That is for a later project.
OpenCV
OpenCV is a really dandy computer vision library originally headed by Intel. It has since been open sourced and freely available to the public in C/C++, Python, Android, GPUs, and a work-in-progress iOS port. The library implements just about every computer vision algorithm you can think of. For the vectorizer, I use the Canny edge detection, contour finding, and polygonal approximation of curves.
Take Note
This guide is written assuming you know how to fiddle with OpenCV and program. If not, there are many other websites that teach you how to program and beginner's tutorials to OpenCV that do a better job than I would have.
TL; WR (Too Long, Won't Read)
Resize the image to around 800 pixels, apply Gaussian blur and Canny Edge Detection. Fiddle with thresholds and blurs until the edge image looks right. Run findContours, then approxPolyDP (polygon approximation). Increase approximation accuracy until output looks about right. Stream polygon points to robot over serial or your favorite communication protocol.
Links to source code in the appendix.
Start: Acquire Image and Resize
Why resize your source image? After all, if its big, then more detail, great. If its small, then process faster, great.
If the source image is too big, say greater than 1200 pixels across, then there is too much detail. Since the image processing algorithms are run only once, the speed of processing at is no issue, but the delta robot can only plot so fast. At larger sizes, very small, insignificant features will show up, creating a large number of points to plot, slowing down the drawing and cluttering the image. Of course, unless you want that kind of precision for a very feature full image.
If the source image is too small, what ends up happening is basically plotting at low resolution. The manipulator positions are quantized based on the resolution of the input image. Low res input, low res output. So even if the input image is small, resizing it to a larger image makes better results.
I have found the optimal image size of lower precision delta robots (such as version 1) to be around 400 pixels, and for higher precision delta robots (such as version 2) to be around 800 to 1200 pixels, depending on the input image.
Finally, make it grayscale, either by loading the image greyscale or by using cvtColor to pull out the green channel. CV algorithms love grayscale images. This is the source image I'm using.
Next: Blur and Edge Detect
Exactly as it says, run Gaussian blur and then Canny edge detection on the image, to get a binary image of pure, one pixel width lines representing all the detected edges. Why blur? Because Canny edge detection will find any and all lines in the image, regardless of the content or "importance" of the line. Without blur, the edge image looks like this.
Too much blur, and you lose too much data, and get an edge image like this.
A happy medium is nice, though it differs depending on the picture. For this image I used a seven pixel radius (not sure what the sigma is though, sorry) blur to get this.
Canny edge detection also has variables to adjust the hysteresis of the found lines, a high and low threshold. High threshold is the minimum "strength" of the edge for it to appear in the edge image. Low threshold is the minimum "strength" of the edge for it to continue being a line in the edge image. Drop below the low threshold, and the line stops in the edge image.
Finally: Contour Finding and Polygon Approximation
Currently, all the edges are just white pixels on a black image. They need to be grouped into lines. This is where the function FindContours comes in. It goes and finds all the groups of pixels that can become contours and makes them into unified contours. It has a bunch of fine ways to store the contours, such as in a hierarchy, showing which contours are inside other contours, or only finding outlines of things. In this case we want all the contours in any order, so we can pass it CV_RETR_LIST to get a list of contours.
We aren't done yet. Contours aren't nice straight lines that can be linearly approximated. The last step is ApproxPoly, that is, polygonal approximation. Give it the list of contours, and it will give you back a list of polygons, each a list of points. Exactly what the robot needs. There is one adjustable value: the precision of approximation. Low approximation gives very "shapy" images, such as below.
High approximation approaches an exact match to the edge image, but it can add many many extra points in the polygons. For example, the below image contains 23,000 points, while for a typical drawing I adjust for no more than 3000 points.
With some adjustment, I achieved a 2115 point image while still remaining quite faithful to the original edge image.
So now we have all these points, it is just a simple matter of mapping pixel values to real world inches (or {milli,centi,}meters). Of course, make sure you maintain aspect ratio, or you will get stretched out images as such.
One more thing to consider. If you flipped your coordinate axes like I did, with the Z axis pointing down, your images are mirrored about the Y axis. Simply flip back.
One more thing
Since the robot interpolates between points, extra points will need to be added to be sent. Traverse points. One to traverse to the start of the polygon, and one to raise the pen to prepare for traversing to the next polygon.
In the next post
Getting the data to the robot.
Appendix
Source image Lena: http://sipi.usc.edu/database/database.php?volume=misc&image=12#top
Implemented vectorizer code in Python (warning: dirty, dirty, hacked together code): https://github.com/aaronbot3000/deltadraw/tree/master/vectorizer
This program vectorizes raster images, but doesn't quite process (read: not at all) vector images, such as SVG or DXF. That is for a later project.
OpenCV
OpenCV is a really dandy computer vision library originally headed by Intel. It has since been open sourced and freely available to the public in C/C++, Python, Android, GPUs, and a work-in-progress iOS port. The library implements just about every computer vision algorithm you can think of. For the vectorizer, I use the Canny edge detection, contour finding, and polygonal approximation of curves.
Take Note
This guide is written assuming you know how to fiddle with OpenCV and program. If not, there are many other websites that teach you how to program and beginner's tutorials to OpenCV that do a better job than I would have.
TL; WR (Too Long, Won't Read)
Resize the image to around 800 pixels, apply Gaussian blur and Canny Edge Detection. Fiddle with thresholds and blurs until the edge image looks right. Run findContours, then approxPolyDP (polygon approximation). Increase approximation accuracy until output looks about right. Stream polygon points to robot over serial or your favorite communication protocol.
Links to source code in the appendix.
Start: Acquire Image and Resize
Why resize your source image? After all, if its big, then more detail, great. If its small, then process faster, great.
If the source image is too big, say greater than 1200 pixels across, then there is too much detail. Since the image processing algorithms are run only once, the speed of processing at is no issue, but the delta robot can only plot so fast. At larger sizes, very small, insignificant features will show up, creating a large number of points to plot, slowing down the drawing and cluttering the image. Of course, unless you want that kind of precision for a very feature full image.
If the source image is too small, what ends up happening is basically plotting at low resolution. The manipulator positions are quantized based on the resolution of the input image. Low res input, low res output. So even if the input image is small, resizing it to a larger image makes better results.
I have found the optimal image size of lower precision delta robots (such as version 1) to be around 400 pixels, and for higher precision delta robots (such as version 2) to be around 800 to 1200 pixels, depending on the input image.
Finally, make it grayscale, either by loading the image greyscale or by using cvtColor to pull out the green channel. CV algorithms love grayscale images. This is the source image I'm using.
![]() |
| yay Lena |
Next: Blur and Edge Detect
Exactly as it says, run Gaussian blur and then Canny edge detection on the image, to get a binary image of pure, one pixel width lines representing all the detected edges. Why blur? Because Canny edge detection will find any and all lines in the image, regardless of the content or "importance" of the line. Without blur, the edge image looks like this.
Too much blur, and you lose too much data, and get an edge image like this.
A happy medium is nice, though it differs depending on the picture. For this image I used a seven pixel radius (not sure what the sigma is though, sorry) blur to get this.
Canny edge detection also has variables to adjust the hysteresis of the found lines, a high and low threshold. High threshold is the minimum "strength" of the edge for it to appear in the edge image. Low threshold is the minimum "strength" of the edge for it to continue being a line in the edge image. Drop below the low threshold, and the line stops in the edge image.
Finally: Contour Finding and Polygon Approximation
Currently, all the edges are just white pixels on a black image. They need to be grouped into lines. This is where the function FindContours comes in. It goes and finds all the groups of pixels that can become contours and makes them into unified contours. It has a bunch of fine ways to store the contours, such as in a hierarchy, showing which contours are inside other contours, or only finding outlines of things. In this case we want all the contours in any order, so we can pass it CV_RETR_LIST to get a list of contours.
We aren't done yet. Contours aren't nice straight lines that can be linearly approximated. The last step is ApproxPoly, that is, polygonal approximation. Give it the list of contours, and it will give you back a list of polygons, each a list of points. Exactly what the robot needs. There is one adjustable value: the precision of approximation. Low approximation gives very "shapy" images, such as below.
High approximation approaches an exact match to the edge image, but it can add many many extra points in the polygons. For example, the below image contains 23,000 points, while for a typical drawing I adjust for no more than 3000 points.
With some adjustment, I achieved a 2115 point image while still remaining quite faithful to the original edge image.
So now we have all these points, it is just a simple matter of mapping pixel values to real world inches (or {milli,centi,}meters). Of course, make sure you maintain aspect ratio, or you will get stretched out images as such.
One more thing
Since the robot interpolates between points, extra points will need to be added to be sent. Traverse points. One to traverse to the start of the polygon, and one to raise the pen to prepare for traversing to the next polygon.
In the next post
Getting the data to the robot.
Appendix
Source image Lena: http://sipi.usc.edu/database/database.php?volume=misc&image=12#top
Implemented vectorizer code in Python (warning: dirty, dirty, hacked together code): https://github.com/aaronbot3000/deltadraw/tree/master/vectorizer
Subscribe to:
Posts (Atom)












































