Contents
|
<--Back to TinyG main page
The TinyG firmware codebase is available here: github.com/synthetos/TinyG
The code is licensed under GPLv3
The firmware controller, interpreter and stepper layers are organized as so:
Additional modules are:
A note about efficiency: Having all these layers doesn't mean that there are an excessive number of stack operations. TinyG relies heavily on the GCC compiler (-Os) for efficiency. Further optimizations are made if they are needed and if the compiler doesn't handle that case. Much of the code is run as inlines and static scope variables (i.e. not passed on the stack). And even if there were a lot of function calls, most of the code doesn't need execution-time optimization anyway (with the exception of the inner loops of the planner, steppers and some of the comm drivers).
This project is based on the NIST RS274NGC_3 Gcode spec wich can be found here: www.isd.mel.nist.gov/documents/kramer/RS274NGC_3.ps
Additional guidance is provided by "CNC Programming Handbook, 3rd Edition" by Peter Smid
[1]TinyG was originally forked from grbl in March 2010 and has diverged wildly since then, although we are active in grbl development and support grbl with a hardware solution (grblshield)
The entire TinyG project is Open Source under GPLv3 for sftware and Createve Commons BY-SA for HW.
The following Gcode commands are supported
Commands omitted for the time being:
Other functions / commands that are not supported:
TinyG runs natively in milimeter units (mm) - i.e. the internal system is all done in metric. It accepts G20 / G21 to change between inches and mm mode. When in inches mode (G20) all inputs are translated to mm internally - the machine operator does not see this. Also, all displays are performed in inches - e.g. the $ and ? commands. Doing it this way means that the machine remains consistent and does not go crazy when you change between G20 and G21.
Configuration settings are interpreted in the active units mode. For example, if the machine is in inches mode (G20) and you set the max feed rate using the command $xfr30 the feed rate will be set to 30 in/min. If the machine were in mm mode (G21) it would have been 30 mm/min. It's a good idea to include a G20 or G21 in any script that changes settings.
The rotary axes (ABC) run natively in degrees mode. See notes on rotary axes for lots more detail.
TinyG runs a reduced functionality coordinate system from full NIST. NIST defines these commands that affect the coordinate system:
Instead of the all this TinyG implements the following:
On power up the Gcode interpreter is set to zero (X,Y,Z), which makes the machine zero the current (possibly random) position of the tool. A single coordinate system is provided. G10, G43, and G54 - G59 are not implemented.
A reduced functionality G92 / G92.1 is available that does the following: G92 accepts axis-value pairs for all axes. The value will set that axis to the value. One or more axes must be provided. Axes that are not provided are not changed. For example, to zero a robot you can enter the below. Or just G92.1 which is a G92 with all axes being equal to zero.
G92 x0 y0 z0 a0 b0 c0 (or any variant of this)
Feed rates (and seek rates) are done according to NIST RS274NGCv3 section 2.1.2.5, "Feed Rate", which reads:
A. For motion involving one or more of the X, Y, and Z axes (with or without simultaneous rotational axis motion), the feed rate means length units per minute along the programmed XYZ path, as if the rotational axes were not moving. B. For motion of one rotational axis with X, Y, and Z axes not moving, the feed rate means degrees per minute rotation of the rotational axis. C. For motion of two or three rotational axes with X, Y, and Z axes not moving, the rate is applied as follows. Let dA, dB, and dC be the angles in degrees through which the A, B, and C axes, respectively, must move. Let D = sqrt(dA^2 + dB^2 + dC^2). Conceptually, D is a measure of total angular motion, using the usual Euclidean metric. Let T be the amount of time required to move through D degrees at the current feed rate in degrees per minute. The rotational axes should be moved in coordinated linear motion so that the elapsed time from the start to the end of the motion is T plus any time required for acceleration or deceleration.
TinyG interprets this to mean that for a combined linear / rotational move (A + 2nd paragraph of C) the feed rate is set as the linear feed rate in prevailing linear units (mm or inch). For B and C moves (rotational only) the feed rate is in degrees per minute. The above behaviors apply to axes in STANDARD mode. Rotational axes can behave differently in other modes.
The following functions are related and share common underlying functionality:
These definitions & behaviors are adapted from NIST RS274NGCv3 for TinyG's configuration. Additional guidance is provided by the CNC Programming Handbook, 3nd Edition by Peter Smid.
Relevant sections from NIST are in [square braces]
Note on special characters: The following characters are used as special characters:
''! (exclamation point) is used to signal feedhold'' ''~ (tilde) is used to signal cycle start, which is how you end a feedhold '' ''^x is used as the abort character''
These are captured at the interrupt level and removed from the serial character stream; i.e. they cannot be used in any other command. These characters have been selected as they are not used in any Gcode block, parameter, or expression [see NIST 3.3.2.2, 3.3.2.3], and are not used by JSON notation. While TinyG does not implement Gcode expressions and O codes (functions) these characters may still appear in valid Gcode files that use these functions. It's better to have the gcode interpreter capture these commands and return errors than to simply have the board do something crazy and unexplainable.
Cycle Start (green button, signalled to TinyG as a tilde character (~)) [3.2, 3.6.1]
Cycle Start is the big green button on the controller. The Cycle Start button is used to start gcode programs and also to release TinyG from Feedholds. Cycle start actions are either the result of the controller sending the first (or next) gcode block from a file, or sending a tilde to the board - depending on controller and machine state. Two basic rules apply:
...which lead to the Cycle Start behaviors in the various states below.
Program Stop (M0, M1, M60) [3.6.1]
Program End (M2, M30) [3.6.1]
Feedhold ('!') [2.1.2.15, 2.2.1, 3.6.5, 4.3.9.3]
Feed Rate and Traverse Rate Overrides (M48.1, M48.2, M49.1 M49.2) [2.1.1.9, 2.1.2.15, 2.2.1, 3.6.5, 4.3.9.3]
Abort (^x character) [no NIST references found]
ASCII characters used by Gcode or otherwise unavailable for special use.
See NIST sections 3.3.2.2, 3.3.2.3 and Appendix E for Gcode uses.
See http://www.json.org/ for JSON notation
hex char name used by:
---- ---- ---------- --------------------
0x20 <sp> space Gcode blocks
0x21 ! excl point Kill, Terminate signals
0x22 " quote JSON notation
0x23 # number Gcode parameter prefix
0x24 $ dollar TinyG / grbl settings prefix
0x25 & ampersand universal symbol for logical AND (not used here)
0x26 % percent
0x27 ' single quote
0x28 ( open paren Gcode comments
0x29 ) close paren Gcode comments
0x2A * asterisk Gcode expressions
0x2B + plus Gcode numbers, parameters and expressions, JSON
0x2C , comma JSON notation
0x2D - minus Gcode numbers, parameters and expressions, JSON
0x2E . period Gcode numbers, parameters and expressions, JSON
0x2F / fwd slash Gcode expressions & block delete char
0x3A : colon JSON notation
0x3B ; semicolon
0x3C < less than Gcode expressions
0x3D = equals Gcode expressions
0x3E > greaterthan Gcode expressions
0x3F ? question mk TinyG / grbl query prefix
0x40 @ at symbol TinyG feedhold
0x5B [ open bracket Gcode expressions
0x5C \ backslash JSON notation (escape)
0x5D ] close brack Gcode expressions
0x5E ^ caret
0x5F _ underscore
0x60 ` grave accnt
0x7B { JSON notation
0x7C | universal symbol for logical OR (not used here)
0x7D } JSON notation
0x7E ~ tilde TinyG cycle start
0x7F DEL
This section does not attempt to repeat what's already in the code comments, which are pretty detailed in most modules. This section does intend to provide a global view of important concepts and constructs. It also provides some details which would have made the files unmanageable.
he controller is the main loop for the program. It accepts inuts from various sources, dispatches to various parsers/interpreters, and manages task threading / scheduling.
Controller requirements include:
Accept and mix inputs from multiple sources:
Controller Implementation
The controller is an event-driven hierarchical state machine (HSM) using inverted control to manage a set of cooperative run-to-completion kernel tasks. (same basic concepts as in: http://www.state-machine.com/). This means that the HSM is really just a list of tasks in a main loop, with the ability for a task to abort the rest of the loop depending on its status return (i.e. start the main loop over from the top). The highest priority tasks are run first and progressively lower priority tasks are run only if the higher priority tasks are not blocked.
Tasks are written as continuations, so no function ever actually blocks (there is an exception), but instead returns "busy" (TG_EAGAIN) when it would ordinarily block. Each task has a re-entry point to resume the task once the blocking condition has been removed. See CONTINUATIONS, below.
All tasks are in a single dispatch loop with the lowest-level tasks ordered first. A task returns TG_OK or an error if it's complete, or returns TG_EAGAIN to indicate that its blocked on a lower-level condition. If TG_EAGAIN is received the controller aborts the dispatch loop and starts over again at the top, ensuring that no higher level routines (those further down in the dispatcher) will run until the routine either returns successfully (TG_OK), or returns an error. Interrupts run at the highest priority levels; then the kernel tasks are organized into priority groups below the interrupt levels. The priority of operations is:
Controller Operation
Exception to blocking: The disspatcher scheme allows a single function to block on a sleep_mode() without upsetting scheduling. Sleep_mode() will sleep until an interrupt occurs, then resume after the interrupt is done. This type of blocking occurs in the character transmit function. This allows stdio's Fprint functions to work - being as how they don't know about continuations.
Futures: Using a super loop instead of an event system is a design tradoff - or more to the point - a hack. If the flow of control gets much more complicated it will make sense to replace this section with an event driven dispatcher or something like FreeRTOS.
Flow Control
There are two types of flow control to avoid overrning the inputs:
APPLICATION_LEVEL FLOW CONTROL - The appication will return a prompt with the characters "ok" in it when it is ready for the next block. It's possible to edit the code so only "ok" is returned if you want it to work more like grbl.
SERIAL FLOW CONTROL - XON/XOFF flow control can also be enabled at the serial level. In this case the serial RX buffer is kept full by a certain percentage set by the XOFF_HI_WATER_MARK / XOFF_LO_WATER_MARK settings (compile time).
(sort of - it's mostly hidden from the user)
TinyG appears to the command-line user as being non-moded. However, this is not entirely true. Separate modes exist for entering test modes, and for reserved modes such as dumb mode (direct drive) and other parsers that are planned.
To exit any mode hit Q as the first character of the command line. Once you have hit Q, the following chars select the operating mode:
Once in the selected mode these characters are not active as mode selects.
Continuations are used to manage points where the application would ordinarily block. Call it application managed threading by way of an inverted control loop. By coding using continuations the application does not need an RTOS and is extremely responsive (there are no "ticks")
Rules for writing a continuation task:
This is the gcode parser. It reads and invokes gcode blocks. The actual Gcode commands are executed by calling the underlying canonical machine functions in canonical_machine.c/.h. The order-of-execution specified in RS274NGC is explicitly coded into the routine in an attempt to make extensions that adhere to RS274NGC straightforward.
The parser is stateless and starts "from scratch" for each new gcode block (some state is retrieved from the gcode model kept by the canonical machine).
The parser is pretty robust and accepts the following in input
implements the NIST RS274NGC canonical machining functions (more or less). Some functions have been added,some not implemented, and some of the calling conventions are different. The canonical machine normalizes all coordinates and parameters to internal representation, keeps the Gcode model state, and makes calls to the planning and cycle layers for actual movement. The canonical machine is extensible to handle canned cycles like homing cycles, tool changes, probe cycles, and other complex cycles using motion primitives.(I'm not sure if this is exactly how Kramer planned it - particularly when it comes to state management, but it's how it's implemented).
The planner does line acceleration / deceleration management to plan and execute lines. Additionally, it handles dwells, stop/start.
Acceleration and deceleration planning is performed by mp_aline() and its helper functions. mp_aline() uses constant jerk motion equations to plan acceleration and deceleration. The jerk is the rate of change of acceleration; the 1st derivative of acceleration, and the 3rd derivative of position. Jerk is a measure of impact to the machine. Controlling jerk smoothes transitions between moves and allows for faster feeds while controlling machine oscillations and other undesirable side-effects. It also makes the noises from the motors sound like an industrial machine as opposed to a hobby machine - because that's how industrial machines run.
The controlled jerk makes an "S curve" move known as a 5 period move. The accel and decel periods are divided into concave and convex halves, plus a cruise period makes 5 periods. A 5 period S curve move takes exactly the same time to execute as simpler three-period, constant-acceleration trapezoidal move - it's just the transitions are smoother. The time lost in smoothing the endpoint transitions is made up by a higher midpoint acceleration.
Note: It is possible to achieve further time optimization by inserting a constant acceleration period in between the two accel/decel periods to make a 7 period move. This hasn't been implemented, but the code is organized so that a few key functions could be modified to make this happen. For more background and the motion equations see Ed Red's BYU robotics course: http://www.et.byu.edu/~ered/ME537/Notes/Ch5.pdf.
Aline() plans each line (aka gcode block) a 5 period move consisting of 3 regions:
The initial velocity of the head (Vi) is dependent on the path control mode and the transition jerk settings. Vi is always zero for EXACT STOP mode. For EXACT PATH and CONTINUOUS modes Vi is computed based on the requested cruise velocity and the magnitude of the linear and centripetal (cornering) jerk.
The body is the region where the move is running at its requested (or "set") velocity. Moves that cannot achieve set velocity have no body ("I ain't got no body" --- Marty Feldman).
The tail of each move is always intially planned to decelerate to zero. This may change to non-zero value as new line (gcode blocks) are added and the block chain is re-planned. As mentioned above, sufficient length is reserved in the tail to allow deceleration from the cruise velocity to zero (braking). If the next block has a non-zero entry velocity the previous blocks are recomputed (backplanned) to attain the maximum velocity while still supporting braking to zero.
Aline() is separated into a line planner routine and a runtime execution routine.
Backplanning
Backplanning recomputes the head/body/tail regions and velocities of previous blocks to fit acceleration and distance constraints & optimize target velocities. Backplanning occurs as a two-pass operation. The first pass is a backwards pass that starts at the block currently being (new block) added and continues back to the earliest block that cannot be replanned (first non-replannable block). Planning a block is driven by these constraints:
Variables used in planning are:
The number of blocks in the replan chain is determined by the first "non-replannable" block. A block becomes non-replannable when:
Notes:
Move execution takes the planned blocks and generates a series of piecewise linear line segments that execute the S curve acceleration / deceleration curves and the cruise body. This process actually spans both the planner and the stepper modules but is detailed here.
Move generation and execution takes place at 3 levels:
As long as the steppers are running the sequence of events is:
If the steppers are not running the above is similar, except that the exec is invoked from the main loop by the software interrupt, and the stepper load is invoked from the exec by another software interrupt.
Control flow can be a bit confusing. This is a typical sequence for planning generating (executing) and running an acceleration planned line:
1 planner.mp_aline() is called, which populates a planning buffer (bf) and back-plans pre-existing buffers. 2 When a new buffer is added the planner calls stepper.st_request_exec() to attempt to execute the first segment in the buffer. 3a If the steppers are not busy this will set a timer to cause a "software interrupt" that will ultimately call st_exec_move(). 3b If the steppers are already busy this request is ignored, as it will be performed later once the steppers are ready. 4 The request triggers a software interrupt (timer) that calls _st_exec_move() at the LO interupt level 5 _st_exec_move() calls back to planner.mp_exec_move() which generates the next segment using the mr singleton. 6 When this operation is complete mp_exec_move() calls the appropriate prep routine in stepper.c to compute the DDA params that will be needed to run the move - in this case st_prep_line(). 7 st_prep_line() generates the timer and DDA values and stages these into the sp structure - ready for load. 8 st_prep_line() returns back to mp_exec_move(), which may decide to free the bf buffer back to the planner buffer pool if the move is complete. At this point the LO interrupt is complete (Note: I may have to return the buffer at the main-loop level if there are data conflicts). 9 At some point in the future when the current stepper segment is done the stepper inner-loop (interrupt level HI) will load the next segment by calling _st_load_move(). 10 The final step in the sequence is _st_load_move() requesting the next segment to be executed and prepared by calling st_request_exec() - control goes back to step 4.
Note: For this to work you have to be really careful about what structures are modified at what level, and use volatiles where necessary.
(Warning - this is kind of a ramble).
Why go to all this trouble? Becuase you have to. The constraint is timing and execution. The following constraints must be met for the system to work:
So with all this here's the issue. Ideally you'd like to load the accel/decel segments directly from the planner block into the DDA. But you only have <20 uSec. The computations in this section take about 500 uSec for the jerk calculation, and another 500 uSec or so for the DDA prep. Then there's about 10 uSec that the load itself takes. This is after some optimizations. This is a 6 axis system running 4 motors, and the stepper ISRs are firing away like mad, so there are a lot of operations. Even with ideal execution you are unlikely to get the entire operation to under 20 uSec.
It's impractical to pre-compute the entire set of segments in a block as there may be dozens or even hundreds and we have limited RAM. Queueing the segments introduces another level of queueing and blocking (in addition to the planning buffer queue), and queue synchronization is difficult. Get too far ahead in the segment queue and you starve out the planner, causing planning to go to zero where you don't want it to. Starve the segment queue at all and you get dropped steps and motor stalls. This was tried in an earlier version.
The solution finally used involves the "2 level background" scheme described in the previous section. The stepper interrupts fire constantly. The loader runs the bare minimum of operations to keep the load delays down. Everything is pre-computed. Doing a ping-pong double buffer was considered, but this slows down the stepper interrupts as it now has another level of indirect to deal with (and longer machine instructions than the direct addressing it uses). The segment generation runs immediately behind the load - with ample time to prepare the next segment before the next load. And the move planner runs in background behind all of this. In the worst case (a single segment in a move) the planner has <10mSec to plan the next block. At 32 MHz, GCC at -Os and a queue depth of 24 planner blocks it can do this in about 6 mSec - that's with all the stepper and exec interrupts firing.
Note: It may be that instead of feed segments to the DDA that the DDA clock could be controlled using rates computed to generate the S curves. This might save some cycles but you still have to compute those rates. So I'm not sure you get under the 20 uSec window you want in any event. Really, if there's a simpler way to do this I'd like to know.
The jerk equations drive acceleration and deceleration planning. A good treatment can be found in Ed Red's course notes http://www.et.byu.edu/~ered/ME537/Notes/Ch5.pdf
There are two core calculations that drive everything else. This pair of functions returns the fourth thing knowing the other three.
_mp_get_target_length() is a convenient expression for determining the optimal_length (L) of a line given the inital velocity (Vi), target velocity (Vt) and maximum jerk (Jm). The length (position) equation is derived from:
a) L = (Vt-Vi) * T - (Ar*T^2)/2 ... which becomes b) with substitutions for Ar and T
b) L = (Vt-Vi) * 2*sqrt((Vt-Vi)/Jm) - (2*sqrt((Vt-Vi)/Jm) * (Vt-Vi))/2
c) L = (Vt-Vi)^(3/2) / sqrt(Jm) ...is an alternate form of b) (see Wolfram Alpha)
d) L = abs(Vt-Vi) * sqrt(abs(Vt-Vi)/Jm) ... is a second alternate form. Note: Vt is not always > Vi due to rounding, tolerances, and other factors.
where Ar = (Jm*T)/4 Ar is ramp acceleration
where T = 2*sqrt((Vt-Vi)/Jm) T is time
where Jm (maximum jerk) is the cartesian sum of the jerks for each axis in the move. The values in the unit vector are used to scale the resultant jerk for the move.
assumes Vt, Vi and L are positive or zero
_mp_get_target_velocity() is a convenient expression for determining target velocity for a given the initial velocity (Vi), length (L), and maximum jerk (Jm). Equation e) is b) solved for Vt. Equation f) is d) solved for Vt. Use f) (obviously).
e) Vt = (sqrt(L)*(L/sqrt(1/Jm))^(1/6)+(1/Jm)^(1/4)*Vi)/(1/Jm)^(1/4)
f) Vt = L^(2/3) * Jm^(1/3) + Vi
The Jm terms in d and f are pre-computed for each nerw block so they don't have to be generated multiple times during planning.
Sonny Jeon (aka Chamnit) figured this one out. See onehossshay.wordpress.com/2011/09/24/improving_grbl_cornering_algorithm/
The cornering algorithm computes the maximum allowable junction speed by finding the velocity that will yield the centripetal acceleration set by the Junction Acceleration value ($JA). The Corner Delta value ($xCD) sets the effective radius of curvature allowable in each axis. TinyG adapts Chamnit's algorithm slightly by scaling the corner delta term using the unit vectors of the lines being joined. This accommodates machines with different dynamics on the different axes - such as Z axes that are slower than the XY plane, or rotary axes that may have no dynamic relation to their linear brethren.
Here's Chamnit's explanation - which is mostly the same as his blog post:
First let's assume that at a junction we only look a centripetal acceleration to simply things. At a junction of two lines, let's place a circle such that both lines are tangent to the circle. The circular segment joining the lines represents the path for constant centripetal acceleration. This creates a deviation from the path (let's call this delta), which is the distance from the junction to the edge of the circular segment. Delta needs to be defined, so let's replace the term max_jerk with max_junction_deviation( or delta). This indirectly sets the radius of the circle, and hence limits the velocity by the centripetal acceleration. Think of the this as widening the race track. If a race car is driving on a track only as wide as a car, it'll have to slow down a lot to turn corners. If we widen the track a bit, the car can start to use the track to go into the turn. The wider it is, the faster through the corner it can go.
If you do the geometry in terms of the known variables, you get: sin(theta/2) = R/(R+delta) Re-arranging in terms of circle radius (R), R = delta*sin(theta/2)/(1-sin(theta/2). Theta is the angle between line segments given by: cos(theta) = dot(a,b)/(norm(a)*norm(b)). To remove the acos() and sin() computations, use the trig half angle identity: sin(theta/2) = +/- sqrt((1-cos(theta))/2). For our applications, this should always be positive. Now just plug and chug the equations into the centripetal acceleration equation: v_c = sqrt(a_max*R). You'll see that there are only two sqrt computations and no sine/cosines.
How to compute the radius using brute-force trig:
Dwell is implemented as a queued move just like a line. It occupies a block in the planning queue and lines necessarily plan to zero velocity to it and from it. It gets executed by passing it to the segment loader (the thing that stuffs the stepper DDA). When the loader sees a dwell instead of loading the DDA timer it loads a separate dwell timer. Instead of firing the DDA ISR, the dwell timer fires the dwell ISR, which just counts down the dwell and ultimately returns. Keeping the dwell and DDA timers separate means the DDA ISR doesn't have to waste precious cycles figuring out what kind of move it's executing. Keeping the dwell in the same chain as the lines means the code doesn't have to go out of its way to synchronize anything.
This module isolates the robot inverse kinematics. This module also maps motors to axes and takes inhibited axes out of play.
Only a cartesian robot for X,Y,Z, A, B and C axes is supported. Other types of robot kinematics could extend this, but quite frankly you'd better be pretty aware of your cycle budget if you want to put in anything significantly more complex. Simply put, you have about 10ms to compute the next segment or the steppers will starve out, as this is the expected update rate for pulse train segments. This interval is approximate and depends on a number of factors, but is dominated by is the following values in planner.h:
At the current time application profiles to about 2 - 6 ms to perform the next line planning operation, a bit more if the arc planning is working on top of that. That's enough time to do an entire back-planning chain of 24 blocks, and is measured with the segments executing and steppers loading and running, so that part is already counted for in the budget. There is still room for some significant improvement if the detection of optimal blocks (nonreplannable) is improved - this should get the times down to well under 5 ms. So be mindful of this if you put a bunch of 6D matrix transformations in there.
The stepper module performs the following functions:
Coordinated motion (line drawing) is performed using a classic Bresenham DDA as per reprap and grbl. A number of additional steps are taken to optimize interpolation and pulse train accuracy such as the addition of fractional step handing within the DDA, optimal clock timing generation, and phase correction between pulse sequences ("segments").
The homing cycle finds the home switch for each confgured axis, then offsets that axis relative to the location of the switch.
Arcs are generated as large sets of short line segments These are fed to the line planner for execution. The preliminary work on arcs is performed by the canonical machine.
General purpose utility, math, debug and logging support functions
Parallel ports, LEDs and switch support
This section describs how the config system works and how to add or modify config items. For how to use config see http://www.synthetos.com/wiki/index.php?title=TinyG:Configuring
The config system works as a series of "records" that act like name-value pairs. The name part is a little more complicated in that a name can be compound - containing the mnemonic and an optional prefix, depending on what you are setting. These records exist in RAM and also in EEPROM. There are a set of functions patterned loosely after key/value dictionaries (ala Python) that move things in and out and back and forth between them.
The RAM records also contain pointers to functions that display the settings, and apply the settings to the firmware - and also sort out which way to do this depending on whether mm or inches mode is active. For the rest you really should look at the code, particularly struct cfgSetting cfgList[COUNT_SETTINGS] which is where is all comes together.
Notes:
I wish this were easier but it's already been rewiteen twice. There is no "filesystem" anywhere to make this whole thing configurable, and I didn't want to spend any memory to have a config of the config. So here it is.
To add a new config setting:
Test it pretty well to make sure you didn't break your setting. Also check that the last setting is still working - this is a good cross check that you didn't screw something up. Really, it seems tough but it gets down to a 5 min operation once you've done it.
Contains default settings for the system. Also used as a parent file to machine specific defaults files. These are not techncially necessary, as you can configure the board to any machine. The files are a way of capturing settings that have worked well through experimentation.
Current machine files include:
The XIO subsystem provides common access to native and derived xmega devices. XIO is an implemnentation of the device drivers needed to use GCC stdio. In addition, XIO also implements stdin, stdout and stderr, and provides additional functions that extend stdio.
Stdio support [ref: http://www.nongnu.org/avr-libc/user-manual/group__avr__stdio.html]
Facilities provided beyond stdio:
A rough work-alike to ioctl is provided to set device parameters. These parameters may also be set as an argument to device initialization. The following parameters are implemented:
An attempt has beeen made to make the circular buffers used by low-level putc/getc as efficient as possible. This enables high-speed serial IO operating up to 1Mbaud. The circular buffers are unsigned char arrays that fill down from the top element and wrap back to the top when index zero is reached. This allows pre-decrement operations, zero tests, and eliminates modulus, masks, substractions and other less efficient array bounds checking. Buffer indexes are uint_fast8_t which limits these buffers to 254 usable locations. (one location is lost to head/tail collision detection and one is lost to the zero position) All this enables the compiler to do better optimization. It is also possible to use buffers > 254 bytes by setting BUFFER_T to uint16_t. This supports buffers with a 16 bit index at some penalty to performance.
Chars are written to the *head* and read from the *tail*.
The underlying USART RX ISRs (used by getc() and gets()) trap control characters and treat them as signals.
On receipt of a signal the signal value (see enum xioSignals) is written to xioDEVICE.sig and a signal handler specific to that signal is invoked (see signals.c). The signal character is not written into the RX buffer.
The signal handler runs at the ISR level, so it might invoke something, set some flag somewhere, or just return, relying on the application to detect the sig value being set. It's up to the app to reset sig. If a new signal arrives before the previous sig is handled or cleared the new sig will overwrite the previous sig value.
The control chars and their mapping to signals are hard-coded into the ISR for simplicity and speed. A more flexible system of bindings and callbacks could be written at some sacrifice to execution speed.
IMPORTANT--> Since signals are trapped at the ISR level it is not necessary to be actively reading a device for it to receive signals. Any configured IO device will process signals. This allows input lines to come from one source (e.g. a file device), while pause, resume and kill are still active from another device. This is very useful for reading a gcode file from FLASH or an SD card, while still trapping eStops or other signals from the USB port.
Xmega hardware support files. These are a mix of Atmel provided files and 3rd party low-level code. Perhaps the most notable is some code in xmega_eeeprom.c that address the AVR1008 Errata problems in the Non-Volatile Memory system (EEPROM and Flash). This code originated with Alex Forencich's Xboot project.
Install WinAVR and AVRStudio 4 (if not already installed):
Select device - should have already been selected to be atxmega192a3 or atxmega256a3. If not:
In AVRstudio select Project / Configuration Options; In main window select device atxmega256a3
Configure clock frequency (optional, but recommended)
In Project / Configuration Options main window: Frequency should be 32000000 (32 Mhz)
You also may want set 32.0000 Mhz in Simulator2 configs:
Add libm.a (math lib) otherwise the floating point will fail.
In AVRstudio select Project / Configuration Options:
ref: http://www.avrfreaks.net/index.php?name=PNphpBB2&file=printview&t=80040&start=0
AVRstudio Custom Compilations Options
In AVRstudio select Project / Configuration Options
-Wall -gdwarf-2 -std=gnu99 -DF_CPU=32000000UL -O0 (or typically Os) -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums
In AVRstudio select Project / Configuration Options:
-Wl,-u,vfprintf (Wl --->thats: W"lower-case ell" not W"one") -lprintf_flt -lm
ref: http://www.avrfreaks.net/index.php?name=PNphpBB2&file=printview&t=92299&start=0
An annoying avr20100110 bug:
Note: I gave up on AVRStudio5 until it's more stable. These notes were compiled using the beta releases and may be dated.
Settings in Project / TinyG Properties
Build: generate .hex files (others are useful)
Toolchain:
Assembler (should set automatically) [see Note 2]
Device: atxmega192a3 or atxmega256a3
Debugging: AVR Simulator
Note 1: You may also want to set the clock F to 32 Mhz in the
Processor window when you first start the Simulator (debugger)
Note 2: You must add floating point formatting code to the linker string for printf %f to work.
-Wl,-u,vfprintf (Wl ->thats: W"lower-case ell" not W"one")
-lprintf_flt (or add libprintf_flt.a to the libs)
-lm
ref: http://www.avrfreaks.net/index.php?name=PNphpBB2&file=printview&t=92299&start=0
ref: http://www.cs.mun.ca/~paul/cs4723/material/atmel/avr-libc-user-manual-1.6.5/group__avr__stdio.html#ga3b98c0d17b35642c0f3e4649092b9f1