Home Grown RepRap Software

After I switched my RepRap to run on g-codes a few weeks ago, I started writing little Python scripts to do various things like lay down a raft, shut the extruder off and move the platform out of the way, extrude in preparation for a build, and send a g-code file to the controller.  All of the scripts rely on a common module that sends a series of g-codes to the controller and waits for acknowledgements.  The code has grown and become more interesting:

  • I optimized the communication by having the host send enough commands to keep the controller’s 128 byte serial buffer full.  The host does this by sending commands before an acknowledgement has been received for commands sent earlier.  This works surprisingly well and helps the controller handle many commands per second, which is important for drawing short segments.
  • I added code that automatically resumes a build where it left off if the controller restarts during the build.  I implemented this because my controller spontaneously restarted several times this week in the middle of a build.  I implemented this feature by creating a simple, fast machine simulator in the host.  To resume a build, the code resets the controller, sends the g-codes necessary to put the machine back in the state recorded by the simulator, then resubmits the commands the controller never acknowledged.  This also works surprisingly well.
  • I created a g-code runner that can resume any stopped build.  I implemented this because yesterday my controller not only spontaneously restarted, it actually lost its firmware in the process!  (I then discovered and fixed a loosely connected ground wire which could have been the culprit all along.)  Now I can stop a build at any time, turn off the power, disconnect the USB cable, clean the heater, restart the host computer, turn on the power and reconnect, upload a new firmware, run some g-code to test and prime the RepRap, and finally resume printing exactly where I left off.  It’s kind of magical to see it actually work.

So now I have worked around the most common ways a print can fail partway through.  This should make it much easier to work through problems with large builds.  I can even stop a build with ctrl-C, shut everything off, go to bed, and resume the build another day!  I definitely need to start doing that. 😉

By the way, I had to make a minor update to the g-code firmware to make it possible to restore all of the machine state: the G92 code needs to notice and use the X, Y, and Z parameters.  The fact that no one has yet done this to the firmware in Subversion suggests that none of the other g-code runners can resume a build like mine can.  So I guess this code might be useful to the community.  Speak up if you’re interested.

Back to Python, Zope, and Plone

I’ve been writing code for the family history department of the Church of Jesus Christ of Latter-day Saints for the last 4+ years.  It has been a good experience and I feel like I’ve contributed and learned a lot, but now I have a new opportunity with my family that I shouldn’t pass up.  My family business is now doing software consulting!  And guess what kind of consulting offers we’re getting?  It’s all Python, Zope, and Plone!  I’m really happy about that.  I feel like working in Python gives us a good chance to advance the state of the art.

Our first full time gig starts in December.  It will involve a lot of work with RelStorage, which is another cool bonus.  We expect the work to take six months, maybe a little more.  Among other work, we are going to make RelStorage rock on MySQL (even more than it does already).

I also need to tie up some loose ends on RelStorage: I need to integrate the optimized Oracle queries, finish setting up the test environment, and release 1.1.  (It’s about time, eh?  Version 1.1c2 has been out there a long time.  Blame Java… 😛 )

Delay Compensation in the G-Code Firmware

I’ve been building small RepRapped objects successfully.  (Larger objects not so successfully… I’ll talk about that another time.)  One significant problem I’ve noticed is that a series of short line segments is drawn much too slowly.  You can see this if you draw a circle using just the cartesian bot.  If the circle has only a few segments, then it is drawn at about the speed you specify, but the speed falls as the number of segments increases.

The RepRap host software has several knobs intended to compensate, but I found it very difficult to find the right adjustments.  No adjustment seemed to be right for all line segments.  The knobs in Skeinforge seem even more complicated.

I tried to compensate for the delays by adding buffers to the G-Code firmware.  I figured that if the firmware always has the next command ready in a buffer, there will be no pauses.  I wrote code that buffered several line drawing commands in the Arduino, but as it turned out, even a 16 segment buffer did not reduce the pauses at all.

Then I started thinking about what’s really going on.  The Arduino can’t calculate anywhere near as fast as a modern CPU.  For each line segment, the Arduino does a lot of floating point calculations, including several decimal to floating point conversions and a square root operation to calculate the line distance.  No amount of buffering or interrupt coordination is going to reduce the time required to perform those calculations.  We could move some of the calculations to the host, but I am not interested in giving up the current simplicity of the protocol.

So then I started to think about compensating for the command processing delay automatically.  I realized that every line drawing command is likely to incur about the same amount of calculation delay.  All I need to do is raise the feed rate automatically to compensate for the delay.

Using a little algebra, I found a simple way to calculate an adjusted feed rate based on the line distance and a constant command processing delay.  I added code to the firmware that performs that calculation, then I wrote a script that computed the delay per line drawing command.  I measured the delay to be about 28 milliseconds.

I plugged that number into the firmware and it worked!  Now, drawing a curve takes about the same amount of time whether there are 16 segments or 500.  I hardly have to worry about beading or stretching anymore.

The adjustment works so well in practice that I don’t think the host needs to be able to control it.  It adjusts the speed of every segment, regardless of what host software produced the commands, and not just short segments; it adjusts short segments more than long segments.  The adjustment always seems to produce the expected results.  I think everyone ought to take advantage of this because it makes the machine much easier to tune.

I’ve posted the patch on SourceForge.

I’ll describe how I derived the calculation.

r = specified feedrate, mm/s
R = adjusted feedrate, mm/s
d = line distance, mm
w = latency (seconds) per command caused by command transmission and processing
t = time to draw the line

r*t = d

t = d/r

The equation for R should have the same time and should include w:

t = w + d/R

We can reasonably assume that the command processing latency is about the same for every line.  R will be slightly higher than r, to compensate for w.  By substituting t with d/r, we can compute R.

w + d/R – t = 0

w + d/R – d/r = 0

w/d + 1/R – 1/r = 0

-w/d – 1/R + 1/r = 0

1/r – w/d = 1/R

1 / (1/r – w/d) = R

So computing R is not very hard and we can do it right in the firmware. (After all, the firmware already computes a square root to find the distance of each line, so a couple more divisions will make only a trivial difference.)  Note that if w = 0 (implying no command processing delay), then the w/d term becomes 0 and R = 1/(1/r), thus R = r.  Also note that (1/r – w/d) can be zero, negative, or so small that R is computed as an excessively large feed rate. We need to handle those situations by limiting the adjusted feedrate to the maximum feedrate the steppers can handle.

The firmware actually deals with feed rates in mm/min, so the equation becomes:

60 / (60/r – w/d) = R

This computation is implemented by the patch mentioned above.

G-Codes Rule!

I’ve switched my RepStrap to use G-Codes, since that seems to be the direction the RepRap project is heading.  I’m glad I did!  The G-Code protocol consists of ASCII text I can read in a terminal, edit with a text editor, and generate with Python.  Now I have several Python scripts for doing various things with the machine.  Sometimes scripting is much more comfortable than a GUI interface.

OTOH, I’m also glad I started with the SNAP codes, since the code in Subversion that implements the new protocol isn’t stable yet.  Specifically:

  • At first, the G-Code firmware failed very badly.  Some poking around revealed that the microcontroller was resetting on every strtod() call.  This turned out to be a bug in Ubuntu 8.04; the solution was to just grab the latest avr-libc package from the 8.10 release and install it with “dpkg -i”.
  • At that point, the compiled firmware was just a little too big to fit on the Arduino.  I commented out the support for drill cycling (codes 81-83; do we really need those?), then it fit with room to spare.
  • The RepRap host interface can generate G-Code scripts, but the scripts contain very long pauses.  I discovered that the G4 codes it was generating were supposed to be in units of seconds, but the code was outputting milliseconds instead.  I fixed that and now the generator works fine.

Incidentally, I just measured my extruder’s output rate, and this seems like a good place to keep a record.  I set the motor speed to 170 (the range is 0-255 and it drives a 10.4V PWM transistor… my old power supply outputs 10.4V instead 12V… I’m thinking of replacing it).  It pulled in 100mm of 3mm filament in 261 seconds, outputting 2045mm of filament that fell on the floor.

Based on that measurement, I calculate that the extruder outputs 7.835mm/s (470.1mm per minute); I’m sure it outputs a little less when gravity is not involved.  It pulls in 0.383mm/s.  Since the output rate is 20.45 times the input rate, while the volume is obviously the same, the output diameter must be 1/sqrt(20.45) = 1/4.522 of the input diameter, so the output is about 0.663mm wide.  Again, that’s with extra gravity involved; the real thing will be slightly fatter.  I’ve been using 0.7mm width as an estimate and I guess that’s very close to correct.