-
Drawing a line: Lua code
01/17/2017 at 07:10 • 0 commentsSo, we now have motors connected by WiFi rather then copper cables. This also means that the controller gives up using individual steps for each motor as well as giving up the tight co-ordination of the movements. So we need to rewrite the line drawing code to allow for a higher level command set to each motor. Since we really need the controller to be an access point, the least expensive controller I have found is an ESP12 development board. I started doing a GCODE interpreter using a Forth language, but then decided to learn Lua mainly due to the availability of the excellent NodeMCU platform.
So, here is the routine for drawing a line in 4D (being the 3 cartesian dimensions of x, y and z, plus the extruder), using the Lua language.
function line(x, y, z, e) -- starting at {posx, posy, posz, pose} -- go to {x, y, z, e} at speeds stored in globals local tmp = (x-posx)^2 + (y-posy)^2 + (z-posz)^2 if(tmp < .1 and math.abs(pose - e) < 1) then return end -- already there (within 10 um) -- calculate string lengths at end posn local l1,l2,l3,l4; l1, l2, l3 = IK(x,y,z); l4 = e * eRate; --2do: define interface and conversion for extruder -- calculate time for movement: distance to move / rate of movement -- 2do check maths for length local dist = math.sqrt(tmp) * 1000 -- um local duration = 1000000 * dist / xyzSpeed -- microseconds -- send target length and duration to get there to each motor msgMotor(0, string.format('%d', duration) .. ' ' .. (l1) .. ' move') msgMotor(1, string.format('%d', duration) .. ' ' .. (l2) .. ' move') msgMotor(2, string.format('%d', duration) .. ' ' .. (l3) .. ' move') msgMotor(3, string.format('%d', duration) .. ' ' .. (l4) .. ' move') -- in object space posx=x; posy=y; posz=z; pose=e; end
A couple of interesting points in this code that I'll mention are the interface to the motors (msgMotor()) and the conversion from the cartesian coordinates to the string lengths (IK()).- IK is an abbreviation of Inverse Kinematics and neatly takes care of the key conversion between what we (and GCODE) understand (X, Y, Z) and the active parts of the printer (strings hanging from motors).
- msgMotor just sends a message (second parameter) to the motor (first parameter). It's not coded yet, but obviously msgMotor() uses WiFi to get the message to the specified motor. [For more details of the motor interface, see https://hackaday.io/project/18533-esp8266-stepper-driver.]
The good part is that this version of line drawing routine is much less tedious than the code needed for directly attached motors.
-
Use WiFi instead of ethernet cable
12/08/2016 at 01:29 • 5 commentsEach time I go to set up a test, I have to use long lengths of ethernet cable. Besides creating a trip hazard and generally making the rig look quite untidy, the cable has a tendency to break at the termination points. Ethernet cable is solid core and does not like being flexed! How about a solution?
So a sub-project is born: ESP8266 stepper driver. Each motor still needs power, but that is much easier to handle than the ethernet cable back to central unit. I am not sure about the delay over WiFi for messaging to get three (or more) motors to move in a co-ordinated way, but that is what experimentation is about, isn't it?
-
Drawing a line
09/22/2016 at 14:27 • 0 commentsAn object is divided into layers as thick as the printer lays down material in the vertical direction, and each layer is divided into lines as far apart as the width of the extrusion. So, we build our object using lines to make each layer, and multiple layers to make the vertical dimension. This means our simplest thing to print in a 3D printer is a straight line.
Pixels and stepper motors have a common characteristic in that a line has to be approximated into integral elements. Essentially, a line is represented by one or more steps in a direction and a step in a different (usually orthogonal) direction before continuing in the same fashion towards the end of the line. Mr. Bresenham figured this out a long time ago, and his algorithm is still widely applicable in fundamental graphics representation in digital media. [I'll leave it as an exercise for the reader to locate a suitable description of the Bresenham algorithm. I've used it for so long, I have no idea where I learnt it.]
A 3D printer actually uses four degrees of freedom (so a minimum of 4 motors). Three degrees of freedom provide the movement in our 3D world, and the fourth drives the extruder (thus determining the amount of material extruded). In a GCODE file, the four degrees of freedom are called X, Y, Z and E. Each GCODE line (of movement) moves from the position in the previous GCODE line to the one in this GCODE line, so the position described in the GCODE line is the state of the machine after the GCODE line is executed. The machine is supposed to move in a straight line from one position to the next. [Yes, I know there are GCODE commands for doing arcs, but we'll stick to the easier stuff first.]
The following Arduino code is the routine for drawing a single line in the rb3dp. Each motor moves one step at a time, and the Bresenham algorithm says which one to move to keep the actual movement as close to the intended line as possible.
static void line(float x,float y,float z, float e) { long l1,l2,l3,l4; IK(x,y,z,l1,l2,l3); l4 = e * e2steps; // in machine space long d1 = l1 - m1Pos_steps; long d2 = l2 - m2Pos_steps; long d3 = l3 - m3Pos_steps; long d4 = l4 - m4Pos_steps; long ad1=abs(d1); long ad2=abs(d2); long ad3=abs(d3); long ad4=abs(d4); long ad1x2=ad1*2; long ad2x2=ad2*2; long ad3x2=ad3*2; long ad4x2=ad4*2; int dir1=d1<0?M1_REEL_IN:M1_REEL_OUT; int dir2=d2<0?M2_REEL_IN:M2_REEL_OUT; int dir3=d3<0?M3_REEL_IN:M3_REEL_OUT; int dir4=d4<0?M4_REEL_IN:M4_REEL_OUT; long i, err1, err2, err3; if((ad1>=ad2) && (ad1>=ad3) && (ad1>=ad4)) { err1=ad2x2 - ad1; err2=ad3x2 - ad1; err3=ad4x2 - ad1; for(i=0; i0) { onestep(m2,dir2); err1 -= ad1x2; } if(err2>0) { onestep(m3,dir3); err2 -= ad1x2; } if(err3>0) { onestep(m4,dir4); err3 -= ad1x2; } err1 += ad2x2; err2 += ad3x2; err3 += ad4x2; onestep(m1,dir1); } if(readSwitches()) return; } if((ad2>=ad1) && (ad2>=ad3) && (ad2>=ad4)) { err1=ad1x2 - ad2; err2=ad3x2 - ad2; err3=ad4x2 - ad2; for(i=0; i0) { onestep(m1,dir1); err1 -= ad2x2; } if(err2>0) { onestep(m3,dir3); err2 -= ad2x2; } if(err3>0) { onestep(m4,dir4); err3 -= ad2x2; } err1 += ad1x2; err2 += ad3x2; err3 += ad4x2; onestep(m2,dir2); } if(readSwitches()) return; } if((ad3>=ad1) && (ad3>=ad2) && (ad3>=ad4)) { err1=ad1x2 - ad3; err2=ad2x2 - ad3; err3=ad4x2 - ad3; for(i=0; i0) { onestep(m1,dir1); err1 -= ad3x2; } if(err2>0) { onestep(m2,dir2); err2 -= ad3x2; } if(err3>0) { onestep(m4,dir4); err3 -= ad3x2; } err1 += ad1x2; err2 += ad2x2; err3 += ad4x2; onestep(m3,dir3); } if(readSwitches()) return; } if((ad4>=ad1) && (ad4>=ad2) && (ad4>=ad3)) { err1=ad1x2 - ad4; err2=ad2x2 - ad4; err3=ad3x2 - ad4; for(i=0; i0) { onestep(m1,dir1); err1 -= ad4x2; } if(err2>0) { onestep(m2,dir2); err2 -= ad4x2; } if(err3>0) { onestep(m3,dir3); err3 -= ad4x2; } err1 += ad1x2; err2 += ad2x2; err3 += ad3x2; onestep(m4,dir4); } if(readSwitches()) return; } m1Pos_steps=l1; m2Pos_steps=l2; m3Pos_steps=l3; m4Pos_steps=l4; // in object space posx=x; posy=y; posz=z; pose=e; }
-
Materials for big 3D prints
09/22/2016 at 13:16 • 0 commentsWhen I started this project long ago, I naively thought that I'd just collect together the many extruders I'd seen, including plastic filament, clay, concrete, and chocolate. Well, what works for 100mm to 200mm cubes (or thereabouts) is not always appropriate at 1m to 10m (or more) cubes.
Plastic filament
I even bought a direct filament extruder to use, but before I got far enough to begin construction, I figured that it was impractical on two counts.
- I calculated the amount of plastic needed to print a 1 square meter panel just 5 millimetres thick requires a bit over 5 kilograms of ABS plastic and about 6 kilograms of PLA plastic. Mmm...at $40/kg need something a lot less expensive!!
- Printing with a 0.4mm extruder is going to take a very, very long time to print at scales of 1 to 10m, and I suspect I won't live long enough to see it finish!
Besides, what was I going to do with the failed prints!!
Clay
So the first extruder I used was a 60ml syringe, and I filled it with a clay mix. However, I couldn't squeeze the clay out. I thinned the clay enough to get it to squeeze out the 2mm nozzle, but it was too thin to stay where it was extruded. A bit of digging around the internet exposed lots of untested ideas, and even more failures. The few examples of successful printing gave no indication of the clay formulation, and many seemed to use nozzles of 10 to 20mm, high pressure air pumps and heavy hoses. So I put the clay idea aside for the time being.
I tried using a flour mix in the 60ml syringe. It proved the ability to position the effector, but really only made very thin models. Like clay, the flour mix was either too thick to get out of the syringe or was too thin to really allow building in the vertical direction.
Concrete
I've seen concrete poured out of truck and pumped, so it cannot be hard to do can it? Well, concrete is poured into a form for a very good reason: it cannot be pumped if it is anything like thick enough to hold its shape unsupported in the vertical direction. Well, not unless you get a special formulation and who has published such a special formulation in the public domain. Besides, once you have printed an object that is a couple of metres in each direction, how does anyone move the object easily?
In the second installation of rd3dp, I was getting ready to try concrete when I finally figured that the resulting print was going to weigh several tonnes. I suspect that if I had attempted the print that it would probably have fallen through the floor into the carpark underneath: even concrete slabs have weight limits.
Besides, disposing of print failures is a huge problem for concrete.
Chocolate
Best eaten in smaller quantities than blocks of a cubic metre, chocolate is also a bit too expensive to use as a test material at that scale. 'Nuff said.
What else?
At a Show and Tell event at MakersPlace (makersplace.org.au), I asked the question about what to use as a material for rb3dp. I got several interesting answers:
- Ice, since it turns to water and just flows away
- Coffee grounds, since they are a waste product (=free) and lightweight
- Paper, since it is a waste product and also lightweight
Well, I've done some experiments with coffee grounds and paper without finding a reliable extrusion method for either. The experiments are still ongoing, but only sporadically (when another idea crops up).
Dirt
I was researching binding materials (for the paper or coffee grounds), when I found that the road building industry used water soluble polymers as dust suppressants and binders for road base materials (basically, dirt). There is still some work to be done but it gave me enough confidence to actually put up the idea to the public (see https://hackaday.io/project/13442-3d-print-emergency-accommodation).
If anyone can point to a URL with a reliable effector useful at the 1m to 10m scale of printed model, please comment.
-
Principle of operation
09/11/2016 at 11:58 • 0 commentsThree motors provides three degrees of freedom (3DOF), so we hang the effector from 3 pieces of string. We know the length of each string, so how do we know where the effector is, in 3D cartesian space (i.e. x, y and z coordinates)? And if we are given the desired effector position in x, y and z coordinates, then how long should each piece of string be?
Essentially, the length of string from each motor to the effector is the radius of a sphere. The general solution for the intersection of the surfaces of three spheres results in two positions in cartesian space. We know which one to use simply because gravity has a predictable direction of pull. If you want to look at the mathematics in more detail, refer to the Wikipedia article on Trilateration (https://en.wikipedia.org/wiki/Trilateration).
Version 1.0 of the software uses a mathematical simplification of assuming all the motors (i.e. the top of the strings) are all in a plane parallel to the print surface. Effectively, this means the installation of each string motor needs to be at the same height from the floor as the other string motors.
We do have to choose a point to be the origin (x=0, y=0 and z=0) for the object being printed and we need to be consistent with the 3D printing convention that z increases up from the build surface. It seems best to have the origin for the object in the centre of the triangular base, so there is the need for a remapping from the mathematical reference axes to the object axes. Making the two sets of axes to be parallel means a simple arithmetic adjustment to map between the object space and the mathematical space.
We are using stepper motors, which lead to a software design choice to measure the length of each string in steps. The scale in the GCODE for the x, y and z coordinates is assumed to be in centimetres. So the code for converting between the lengths of the three strings and the z, y and z coordinates (in version 1.0) is as follows (for Arduino):
//------------------------------------------------------------------------------
// Forward Kinematics - turns lengths of string (in steps) into XYZ object coordinates
static void FK(long l1, long l2, long l3, float &x, float &y, float &z) {
#ifdef VERBOSE
Serial.println(F("FK"));
Serial.print(m1d); Serial.println(l1);
Serial.print(m2d); Serial.println(l2);
Serial.print(m3d); Serial.println(l3);
#endif
float a = l1 * um_per_step * 0.0001; // step * um/step * 0.0001 cm/um
float b = l2 * um_per_step * 0.0001;
float c = l3 * um_per_step * 0.0001; // step * um/step * 0.0001 cm/um
#ifdef VERBOSE
Serial.print(a); Serial.print(":");
Serial.print(b); Serial.print(":");
Serial.println(c);
#endif
x = (a*a - b*b + x2*x2)/(2*x2);
y = (a*a - c*c + x3*x3 - 2*x*x3 + y3*y3)/(2*y3);
z = sqrt(a*a - x*x - y*y);
x -= objx;
y -= objy;
z = objz -z;
// in object space
#ifdef VERBOSE
Serial.print(F("["));
Serial.print(x); Serial.print(",");
Serial.print(y); Serial.print(",");
Serial.print(z); Serial.println("]");
#endif
}//------------------------------------------------------------------------------
// Inverse Kinematics - turns XYZ object coordinates into thread lengths l1,l2 & l3 in steps
static void IK(float x, float y, float z, long &l1, long &l2, long &l3) {
#ifdef VERBOSE
Serial.print(F("IK ["));
Serial.print(x); Serial.print(",");
Serial.print(y); Serial.print(",");
Serial.print(z); Serial.println("]");
#endif
// translate object coordinates into machine coordinates
float mx = x + objx;
float my = y + objy;
float mz = objz - z;
// find string lengths in steps, i.e. cm * 10000 um/cm / um/step
float mzmz = mz*mz;
l1 = floor( sqrt(mx*mx + my*my + mzmz) * 10000.0 / um_per_step );
l2 = floor( sqrt((mx-x2)*(mx-x2) + my*my + mzmz) * 10000.0 / um_per_step );
l3 = floor( sqrt((mx-x3)*(mx-x3) + (my-y3)*(my-y3) + mzmz) * 10000.0 / um_per_step );
#ifdef VERBOSE
Serial.print(m1d); Serial.println(l1);
Serial.print(m2d); Serial.println(l2);
Serial.print(m3d); Serial.println(l3);
#endif
} -
Split into 2 projects
09/04/2016 at 14:09 • 0 commentsYep, decided to split into 2 projects. For Dirt Hut, see https://hackaday.io/project/13442-3d-print-emergency-accommodation
This project will revert to a description of the 3D printer construction.
RigTig
-
Solution looking for a problem?
09/03/2016 at 11:33 • 0 commentsThe initial project description reads like a solution looking for a problem. Even if the project actually evolved that way (and it did), then it does not mean that is the best way to present the project. Mmm...what to do? Perhaps split into 2 projects, with one describing the RB3DP and the other describing the use of the RB3DP to make buildings using stabilised dirt as the construction material.
I am thinking that trying to fit everything into one project description can confuse more than illuminate. I am still mulling it over, but what do you think? Suggestions are welcome.
RigTig (Matt)