Ed Nisley's Blog: Shop notes, electronics, firmware, machinery, 3D printing, laser cuttery, and curiosities. Contents: 100% human thinking, 0% AI slop.
Category: Software
General-purpose computers doing something specific
This OpenSCAD module spreads an array of cubes across the otherwise featureless preview window, so I know whether the gizmo I’m building or the parts I’m arranging actually fit on the Thing-O-Matic’s build platform. This doesn’t get out to the very edge, but if it looks close, then I should pay more attention anyway.
module ShowPegGrid(Size) {
for (x=[-5:5])
for (y=[-5:5])
translate([x*10,y*10,Size/2])
cube(Size,center=true);
}
ShowPegGrid(1.0);
You obviously don’t want to extrude these things, so put the ShowPegGrid() statement inside an if, so you can turn it off for the final build layout.
In order to get good scope pictures of the winding current in a stepper motor, the scope must sync to the step pulses. However, it must also sync to the groups of 32 step pulses that make up a single set of four full steps, because the winding current repeats for each of those groups. Triggering once per revolution and delaying for a fixed amount will get you where you need to be.
The sync wheel provides a once-per-revolution pulse, but there’s some jitter in the edge for all the usual reasons and you’d be better off with a sync based on the stepper driver’s step input. The general idea is to find the leading edge of the optical pulse, find the next step pulse, then produce output pulses based on the step signal. Assuming a regular step pulse stream (from a pulse generator, for example), the output will be both locked to the wheel rotation and to the step pulses.
Normally this calls for a tedious wiring session involving logic gates and counters, but an Arduino has all the requisite machinery built in. The trick is to generate the pulses using the ATmega’s hardware, rather than program instructions, thus eliminating the usual jitter caused by instruction execution time.
I set up Timer 1 in Mode 4 (CTC with OCR1A controlling the matches) to count step pulse inputs on its T1 external clock input pin and produce a once-per-revolution output pulse on the OC1A pin. Because the output changes on the rising edge of the input clock, its rising and falling edges will provide rock-solid stable scope synchronization.
The big picture goes a little something like this:
Tell the counter to set the output on match, load the duration of the output pulse
Wait for the once-per-revolution signal, then enable the external clock input
Wait for the comparison to happen and reset the match flag
Set a one-pulse delay and tell set the counter to clear the output on match
Wait for the compare, clear the flag, turn off the counter
Wait until the once-per-rev signal goes low
And then do it all over again
Which produces this:
Sync Wheel
Top trace = optical signal from interrupter, middle = 1/rev sync from Arduino OC1A pin, bottom = step pulses. The motor is turning 3.5 rev/s = 210 rev/min. The top half of the screen is at 2 ms/div, the bottom half at 200 μs/div.
You could synchronize the counter to the 1/rev input exactly once, then produce the output pulse just by counting stepper pulses. It’d also be nice to have a pulse that repeats for each group of 32 microsteps within each set of four full steps, perhaps settable to a particular microstep within the group. All that’s in the nature of fine tuning.
Of course, devoting an Arduino to this project would be absurd, but for a one-off effort it makes a lot of sense.
The Arduino source code:
// Stepper motor driver synchronization
// Ed Nisley KE4ZNU June 2011
//-- Pin definitions, all of which depend on internal hardware: do *not* change
#define PIN_REV 2 // INT0 = positive 1/rev pulse from optical switch
#define PIN_STEP 5 // T1 = positive 1/step pulse from stepper driver
#define PIN_TRIGGER 9 // OC1A = positive trigger pulse to scope
#define SYNC_OFFSET 15 // steps from 1/rev puse to start of first 4-full-step group
#define PIN_TRACE_A 10
#define PIN_TRACE_B 11
#define PIN_TRACE_C 12
#define PIN_LED 13
//---------------------
// Useful routines
//--- Input & output pins
void TogglePin(char bitpin) {
digitalWrite(bitpin,!digitalRead(bitpin)); // toggle the bit based on previous output
}
//----------------
// Initializations
void setup() {
pinMode(PIN_REV,INPUT); // INT0 1/rev pulse from wheel
pinMode(PIN_STEP,INPUT); // T1 step pulse from stepper driver
pinMode(PIN_LED,OUTPUT);
digitalWrite(PIN_LED,LOW);
pinMode(PIN_TRACE_A,OUTPUT);
pinMode(PIN_TRACE_B,OUTPUT);
pinMode(PIN_TRACE_C,OUTPUT);
//--- Prepare Timer1 to count external stepper drive pulses
TCCR1B = B00001000; // Timer1: Mode 4 = CTC, TOP = OCR1A, clock stopped
pinMode(PIN_TRIGGER,OUTPUT); // OC1A to scope trigger
}
//----------------
// The main event
void loop() {
//-- Wait for rising edge of 1/rev pulse from optical switch
TCCR1A = B11000000; // COM1A set on compare
TCNT1 = 0; // ensure we start from zero
OCR1A = SYNC_OFFSET; // set step counter
while(!digitalRead(PIN_REV)) { // stall until 1/rev input rises
TogglePin(PIN_TRACE_A);
}
//-- Got it, fire up the timer to count stepper driver pulses
TCCR1B |= B00000111; // enable clock from T1 pin, rising edge
digitalWrite(PIN_LED,HIGH); // show we got here
digitalWrite(PIN_TRACE_A,LOW);
while(!(TIFR1 & _BV(OCF1A))) { // wait for compare
digitalWrite(PIN_TRACE_B,digitalRead(PIN_STEP));
continue;
}
TIFR1 |= _BV(OCF1A); // clear match flag
//-- Scope sync pulse now active
digitalWrite(PIN_LED,LOW); // show we got here
digitalWrite(PIN_TRACE_B,LOW);
//-- Wait for another step pulse to clear scope sync
TCCR1A = B10000000; // COM1A clear on compare
OCR1A = 1; // wait for another pulse
while(!(TIFR1 & _BV(OCF1A))) { // wait for compare
digitalWrite(PIN_TRACE_B,digitalRead(PIN_STEP));
continue;
}
TIFR1 |= _BV(OCF1A); // clear match flag
digitalWrite(PIN_TRACE_B,LOW);
//-- Shut down counter and wait for end of 1/rev pulse
TCCR1B &= ~B00000111; // turn off timer clock
while(digitalRead(PIN_REV)) { // stall until 1/rev pulse goes low again
TogglePin(PIN_TRACE_C);
}
digitalWrite(PIN_TRACE_B,LOW);
}
Most of my machining involves one-off setups and simple cuts, so I usually type G-Code directly into EMC2’s Axis interface: CNC hits precise locations and makes smoother cuts than I ever could. Most of the time, that works really well.
Occasionally, though, I think one thing and type something else.
The reason I didn’t see the yellow low-overheat LED blink on when the Thermal Core ran away was that I’m usually upstairs except when actually printing.
My modus operandi involves sitting at my upstairs desk, fiddling with an OpenSCAD solid model until I like it, then exporting the STL file. This PC has larger screens, better graphics hardware, a fast CPU, a Comfy Chair, and ready access to the kitchen.
The Thing-O-Matic lives in the Basement Laboratory, connected to a dual-core Atom D520 PC running Ubuntu 10.04 LTS with ReplicatorG to control the printer. That PC dual-boots into the RTAI-patched kernel that runs EMC2 for the Sherline mill and is firmly cabled to the Sherline driver box. That PC’s monitor is up on the wall, the chair is a modified lab stool, and the miniature keyboard is barely suited for hunt-and-poke controls. Not a good place to sit and type.
That PC also has a USB webcam showing an interior view of the printer. I run XawTV, a minimal video capture program, to put that view on the PC’s desktop. I could set it up as a webserver camera, but that seems like too much work.
I use the Ubuntu desktop-sharing program to view / control the downstairs programs in a window on my (larger) upstairs monitor, so I can fiddle with RepG from the Comfy Chair. There’s a moderate lag due to stuffing the GUI through the network, but it’s tolerable for small changes & tweaks. The webcam view occupies one corner of the screen.
This is a staged reenactment showing the remote “downstairs” desktop in the left, with the “upstairs” desktop visible to the right:
Remote Desktop Screenshot – lowres
All the files live on our simpleminded server, which sits in the Basement Laboratory’s Computer Wing, and the PCs mount NFS shares from the server. I do all the bulk text editing & file fiddling from the Comfy Chair.
So I save the STL file from the upstairs PC, flip to the window showing the downstairs machine’s desktop, copy the STL to a local drive to avoid lag during operation, run RepG to open the STL and slice it into G-Code, fire the Thing-O-Matic, and trot downstairs to watch the proceedings.
As a rule, I don’t run the printer unattended, but now it looks like it’s a Bad Idea to run the heaters without being nearby. You knew that already, right?
As it turned out, though, that part wasn’t the first attempt.
Caliper part – heavy blobbing
Even switching to red filament didn’t help:
Extrusion blob – top view
That, in fact, was when the light dawned: it always failed at exactly the same point for a given set of G-Code.
Come to find out that, for some parts printed with certain options, the Skeinforge Reversal plugin dependably produces huge blobs of plastic after a move. The extruder reverses properly, the XY stages move, then the extruder starts running forward at the Reversal speed while the XY stages move at whatever rate they’re supposed to for the next thread, producing a prodigious blob.
Extrusion blob – side view
Most parts have much more interior than they do exterior and, with any luck, the blobs vanish inside. However, this little bitty thing has no room to hide a blob. Several parts went down the drain, but at least it had a countable number of layers!
You need that program, because ordinary grep only searches within a single line. In this case, the G-Code pattern extends over several lines. The pcre stands for Perl Compatible Regular Expressions and the -M turns on multi-line matching.
You can count the number of blobs with the -cl options.
Having found the blobs, edit the file, jump to the indicated lines, copy the nearest preceding forward extruder move, including the speed setting, and paste it in front of the M101 that starts the extruder. If my sed-fu were stronger, I could automate that process.
Unleashing pcregrep on my collection of G-Code files shows a bunch of ’em with blobs and a few without. Note that this has nothing to do with the firmware running on the printer, because the G-Code has the error.
What happens, I think, is that Reversal emits a correct reverse at the end of a thread, does a fast move to the start of the next thread, notices that (at least) the first G1 of the new thread falls below the length threshold that would activate the un-reversal action, and incorrectly assumes that it need not run the extruder forward to restore working pressure. The to-be-printed G1 commands all seem to be very short in the failing G-Code files I’ve examined.
Setting the reversal threshold to 0.0 should avoid triggering this error. I’ve verified that it produces correct G-Code for two parts that didn’t work before, but that’s not conclusive proof.
I’ve looked into reversal.py and fixing (heck, finding) this error lies beyond my abilities.
Wouldn’t it be great if you could export all that stuff to a text file in a readable format? The CSV files come close, but they’re not really meant for human consumption.
Subject to revision, your mileage may vary, past performance is no indication of future yield, perfectly safe when used exactly as directed, shake before using, don’t touch that dial!
While printing up handouts for my talk at Cabin Fever, I finally tracked down why Adobe Reader was producing such crappy colors.
The left is before and the right is after the fix, scanned at the same time with the same image adjustments:
Oversaturated vs normal printing
All of the print settings appeared correct (plain paper, 720 dpi, normal contrast, etc, etc), but Adobe Reader (and only Adobe Reader) looked like it was trying to print on vastly higher quality paper than I was using. Too much ink, too much contrast, generally useless results.
The solution was, as always, trivial, after far too much fiddling around.
In Reader’s Print dialog, there’s a button in the lower-left corner labeled Advanced. Clicky, then put a checkmark in the box that says Let printer determine colors.
And then It Just Works.
Equally puzzling: ask for 25 copies of a two-page document, check the Collate box, and you get 25 page 1, 25 page 2, then more page 1 starts coming out. I bet I’d get 25 x 25 sheets of paper by the time it gave up.
I have no idea what’s going on, either.
Memo to Self: verify that the box stays checked after updates.