WIP: The C code for 3D printer firmware:
davidsaunders
Posts: 1,559
I am getting a good start on a rewrite in C, and it is coming along at a decent pace.
Here is the core of the G-Code parser:
It does not include the small single command execution functions, as I am still working on them, total size so far (including everything so far done), is just over 5KB compiled.
The code is yet to be tested, though I feel it will work. Please point out any bugs, errors, or other you see in this code.
Here is the core of the G-Code parser:
char newcode; char codebuf[256]; struct { /* structure of G-Code command parameters.*/ long gc; long x; long y; long z; long e; long f; long p; long s; long dmul; long status; } GCodePar; /************************************************************************** * int GetGParams(long offset) * * * * Parses the G-Code parameter string and fills in the GCodePar struct for * * this command. * * * * PARAMETERS: * * long offset : The offset in the codebuf buffer where the parameters * * begin * * * * RETURNS: * * The offset in the G-Code buffer after the end of the parameters. * * * * TODO: * * DONE: Add the ability to skip past comments at the end of line. * **************************************************************************/ int GetGParams(long offset){ static char tstr[8] = {0,0,0,0,0,0,0,0}; int tmp; if (codebuf[offset] >= 0x30 && codebuf[offset] <= 0x39){ offset++; for (tmp = 0;(tstr[tmp] = codebuf[offset++]) > 0x2F && tmp < 0x3A;++tmp); GCodePar.gc = str2inum(tstr); } while (codebuf[offset] < 0x21) offset++; /*Skip spaces.*/ while ((codebuf[offset] != 0x0D) && (codebuf[offset] != 0x0A) && codebuf[offset]){ switch (codebuf[offset++] | 0x20){ case 'x': for (tmp = 0;(tstr[tmp] = codebuf[offset++]) > 0x2F && tmp < 0x3A;++tmp); GCodePar.x = str2inum(tstr); break; case 'y': for (tmp = 0;(tstr[tmp] = codebuf[offset++]) > 0x2F && tmp < 0x3A;++tmp); GCodePar.y = str2inum(tstr); break; case 'z': for (tmp = 0;(tstr[tmp] = codebuf[offset++]) > 0x2F && tmp < 0x3A;++tmp); GCodePar.z = str2inum(tstr); break; case 'e': for (tmp = 0;(tstr[tmp] = codebuf[offset++]) > 0x2F && tmp < 0x3A;++tmp); GCodePar.e = str2inum(tstr); break; case 'f': for (tmp = 0;(tstr[tmp] = codebuf[offset++]) > 0x2F && tmp < 0x3A;++tmp); GCodePar.f = str2inum(tstr); break; case 'p': for (tmp = 0;(tstr[tmp] = codebuf[offset++]) > 0x2F && tmp < 0x3A;++tmp); GCodePar.p = str2inum(tstr); break; case 's': for (tmp = 0;(tstr[tmp] = codebuf[offset++]) > 0x2F && tmp < 0x3A;++tmp); GCodePar.s = str2inum(tstr); break; case ';': while (codebuf[offset] != 0x0A && codebuf[offset] != 0x0D) ++offset; } } return offset; } /************************************************************************** **************************************************************************/ int ExecMCode(){ switch (GCodePar.gc) { case 0: //All stop. M0(); break; case 2: // M2(); break; case 82: //Only absolute supported for extruder. break; case 104: //Set extruder tempurature. M104(); break; case 106: //fan on. M106(); break; case 107: //Fan Off. M107(); break; case 109: //Set extruder temp (we always wait). M104(); break; case 116: //wait for all to be ready. break; //We always wait for ready, so do nothing. } return 0; } /************************************************************************** * int ExecGcode(void) **************************************************************************/ int ExecGCode(){ switch (GCodePar.gc){ case 0: case 1: G0(); break; case 4: G4(); break; case 21: GCodePar.dmul = inch; break; case 22: GCodePar.dmul = mm; break; case 28: G28(); break; case 90: //G90 Only absolute positioning supported. break; } return 0; } /************************************************************************** * int GCode(void) * * * * This function is the outer level of the simple G-Code parser. * * * * PARAMETERS: * * The contents of the character buffer, codebuf. * * * * TODO: * * DONE: Add the ability to skip past comments at the end of line. * **************************************************************************/ int GCode(void){ char tc; short offset; offset = 0; while (codebuf[offset]){ /*Continue until next char is NULL.*/ while (codebuf[offset] < 0x21) offset++; /*skip leading spaces.*/ if ((codebuf[offset++] == 0xD) || (codebuf[offset++] == 0x0A)) /*checkj for end of line*/ while (GCodePar.status); /*Wait for command to finish running.*/ switch (codebuf[offset++] | 0x20) { /*Determine if we are using a G command or M command.*/ case 'g': offset = GetGParams(offset); ExecGCode(); break; case 'm': offset = GetGParams(offset); ExecMCode(); break; case ';': while (codebuf[offset] != 0x0A && codebuf[offset] != 0x0D) ++offset; } } return 0; }And I am only one day into the C version, so I would say that PASM and C make it easy. I think that the same will be true of PropBASIC once I get to know it better, though one step at a time.
It does not include the small single command execution functions, as I am still working on them, total size so far (including everything so far done), is just over 5KB compiled.
The code is yet to be tested, though I feel it will work. Please point out any bugs, errors, or other you see in this code.
Comments
J
I also used a bool in there because I typically run C++, not C. If you're running C, you can of course change it to whatever variable type you please.
And for true/false boolean type data it is better to use a standard integer datatype (where non-zero is true, and zero is false), as that works with all C compilers, and the few early C++ compilers that do not support the bool type.
Also note that the members of the structure refer to the parameters used in G-Code, here is a reference:
http://reprap.org/wiki/G-code#Replies_from_the_RepRap_machine_to_the_host_computer
You will see that the parameters each begin with a letter, these letters are the same as the names of the members of the structure.
I can't say for sure, but I'd bet with it's better than writing the for-loop 7 times.
If you're using C, absolutely. If you're using C++... what system are you running that has such an old C++ compiler? PropGCC may not be bleeding edge new, but it sure isn't that old :P
A bit of an update:
Here it the main portion of the stepper control thread (the actual pin output is not shown here).
I also need to add NTSC output for a complete status display.
steppers.h:
Steppers.c:
Print3D.c:
extrude.h: [/code]
The same code should work with bipolar steppers, if you use bidirectional current driver like the L293D.
There is no good reason to use one of the overpriced stepper controllers. They take more code to control, cost more, and do not provide any good advantage for most applications. If you need more current make some drivers using transistors, resistors, and diodes.
IN SHORT:
I am directly controlling the steppers, not using an external controller.
You wouldn't even have to test if the delta was non-zero (though doing so might save you some time). The code is all just lookups and simple logic and arithmetic ops which will translate to assembly really well, and you'll only need a single function.
IMHO
That line of thought will severely limit the usefulness of your effort. I would guess that most of the people who have participated in your discussions, either use or would use stepper drivers, as compared to sending pulses to the individual coils.
However I can understand your line of thinking, because I once thought that way
The unipolar motors I am using combined with the drive screws I am using provide a drive rate of 404 steps per mm (513 steps per revolution, with 20 TPI screws). That is a lot better than the possible resolution with the extruder heads I have made. Currently the best usable resolution is approximately 0.05mm (40.4 steps). So I am designing for way better resolution than current low cost extruders can handle.
Now if you needed to produce a true analog wave, then a stepper driver would make sense. And I can see the situations where that could be useful.
Do not get me wrong, I do intend to add support for stepper drivers in a later version, for those people that already have them. And I do see the a
I suppose that I should have added....
I can surely relate to fully supporting your own needs on the first run, which is my goal also.
I also don't want you to misunderstand me... I think what you are doing is great, but I would like to see a lot of people using your firmware, instead of a select few.
I have done a LOT of research on stepper motors and stepper drivers. The main problem of driving steppers without a chopper drive, is definitely current control. When using this type of control scheme, users eventually end up wanting more speed and torque, as they start building a better machine. To accommodate higher voltages, they must then purchase and install costly and large power resistors. For the amount of money that would go into purchasing (4) power resistors for one stepper motor, they could have easily purchased a cheap chopper driver.
I do intend to support alternative forms of HW, in future versions. I know that some will wish to expand the design to use larger steppers, and have a larger print volume, and this would likely mean going to BiPolar Nema 17 or bigger devices, that at very least require H-Bridge type drivers, and do have an advantage when using a more advanced stepper driver. Then there is the issue of acceleration, while there is a good range with simply directly driving the coils, you can get more with a true analog output. So yes I do intend to expand the support in the future.
The main goal of the first run is to get it working well, upload the code along with the full schematics and a simple set of instructions to build the entire 3D printer. Basically for the first version get a 3D printer that can be built for under $150 out there.
The initial design will include two build options for the controller HW, one using a Propeller Project Board USB (Basically a updated Propeller Proto Board USB) with the components soldered in and point to point wired, then one for those that are not competent with soldering using a breadboard with a P8X32A-D40 for the prop, optionally either a PropPlug or a serial connection (I prefer direct serial, though that is not as easy to come by anymore).
I do intend to eventually provide more alternatives in the stepper drivers, many of the design elements, and the type of boards.
I am also working on some host side software, though that is taking some time. That is:
*:A simple 3D modeler in FreeBASIC (as portability for Graphics applications is better with FreeBASIC), supporting StL, OBJ (wavefront), and a new layered model format designed for use with 3D printing.
*:A simple slicer, that produces G-Code with options for exactly what the printer does and does not support, with a bit limited of infill options at first, also in FreeBASIC.
*:The direct control software, also in FreeBASIC for consistency.
_______________________________________
Though at at the moment Serial IO on the Propeller in C is giving me a headache, I am sure I will get it figured out.
If you're willing to try C++, PropWare's UART implemenations are fairly efficient. If you need buffered output, start an instance in a new cog and use a Queue for inter-cog communication.
Based on the "Hello Demo" program in PropWare, code sizes (compiled w/ LMM) are approximately:
- PropWare: 5636
- Simple: 4780
- TinyStream: 3244
- TinyIO: 3756
- FdSerial: 5980
- Libpropeller: 7608
These numbers are based on propeller-load, so they are not perfectly accurate. But, I think (hope) they give a somewhat fair representation.Note that PropWare's size can be easily dropped to 4784 by switching from printf to the stream interface (pwOut << "Hello, world! " << i << ' ' << i << '\n'; ), but you loose control over the padding and hex format. You can get that formatting back by calling print with its optional second argument. All of the methods on a PropWare:: Printer instance can be used interchangeably to fit your code size needs.
At a minimum, you can get PropWare's SimplexUART class down to 4340 bytes by calling send_array directly instead of using a Printer instance. If you want to write all the formatting code yourself, this is a great way to go about it. See the SimplexUART_Demo example.
Speed comparisons can be found in this thread.
If you want to use PropWare's classes but are not using PropWare's build system, simply copy the following into your project: PropWare.h, pin.h, port.h, everything under PropWare/uart/ and (optionally) everything under PropWare/printer/
LOL! Fixed :P
I do thank you for the suggestion though. I may just polish up the code, finish commenting everything, upload an archive with all the current source, add a C file that only contains a description of what is required of the Serial IO, and leave that part to everyone else. Though testing will be limited, with having to use canned G-Code to test with.
If I can come up with a good small serial driver in pure C, that is fast enough I will get the C version out along with the SPIN and PropBASIC versions.
_____________________________________________________________________________________________________________________
Where things stand at this time:
There are conditionals in place to change the type of outputs used to control the HW..
Currently the code is setup for direct control of the stepper coils, for the filament feed in the extruder there is the option of using either a continuous rotation servo, or a stepper with the existing code. Obviously more conditional compilation statements can be added along with more code to control more possibilities (even things like CNC spool motor control would be simple to add).
Though I am pretty much done with the 3D printer implementation itself.
I still have to add more kinds of stepper drivers, as well as support for more options. I also need to add support for reading the resistance of the thermistor used in the extruder (do not know how to do that with out assembly, and the C version should be pure C [I am using a simple 2 pin three resistor delta-sigma adc]).
It may take me a couple of days to Finnish commenting and organizing the completed C code (plus I have to upload it to my 3D printer a couple more times to be sure that everything is working). Though I intend to have something very usable uploaded soon, that as long as you are willing to bring your own serial.
There's a lot of boiler plate code in there. For instance, changing the PropWare test from
Only increased code size by about 1.5 kB. And change Simple's section from
to
increased code by about 2.1 kB.
A better implementation for the purpose would be a driver that only has char *gets(char *c) and int getchar(void) for input functions, then int putchar(int c) and int puts(char *s) for output functions. That way your Hello World becomes:
That would get rid of all the extra unneeded code.
For sending the text representation of an integer using hex for the output you would use:
And similarly simple code to display integers in other formats, also not much more for a floating point value (if you use them).
You see I like good small code.
It's entirely up to you to use only what you need. The linker in GCC will ensure that unused functions are not included. That's why when I said "Note that PropWare's size can be easily dropped to 4784 by switching from printf to the stream interface." The stream interface uses templates so that only the absolute bare minimum code is pulled in. The compiler does the optimization for you to figure out which methods are needed and which aren't. Same exact thing when you use the overloaded Printer::print function (since the << operator just calls Printer::print).
Of course, if you don't trust GCC, just use PropWare::SimplexUART directly. It does not have any formatting functions - just send (for up to 16-bits), send_array (arrays of 8-bit or smaller variables with known length), put_char (limited to 8-bit) and puts (null-terminated strings).
The SimpleText library provides these little functions among others:
void
putDec (int value)
Print string representation of a decimal number to the debug port.
void
putBin (int value)
Print string representation of a binary number to the debug port.
void
putHex (int value)
Print string representation of a hexadecimal number to the debug port.
These exist because became clear to me long ago that the overhead of a formatted print function is usually unrecoverable. However, you will see that the printi() function is one of the smallest formatted print functions available for Propeller GCC.
The SimpleText library is quite powerful, easy to use, and what Parallax wanted.