Projects:TinyG-Developer-Info:

From Synthetos.com's Wiki
Jump to: navigation, search

Contents

TinyG Developer Information

<--Back to TinyG main page
The TinyG firmware codebase is available here: github.com/synthetos/TinyG
The code is licensed under GPLv3

Code Structure

The firmware controller, interpreter and stepper layers are organized as so:

  • main.c/tinyg.h initialization and main loop
  • controller.c/.h scheduler and related functions
  • gcode_parser.c/.h gcode parser / interpreter
  • canonical_machine.c/.h machine model and machining command excution
  • planner.c/.h acceleration / deceleration line planning, feedholds
  • kinematics.c/.h   inverse kinematics transforrmations
  • stepper.c/.h stepper controls, DDA

Additional modules are:

  • cycle_homing.c homing cycle. Other canned cycles may be added as cycle_xxxxx.c
  • plan_arc.c/.h planner for arc motion. Split with canonical machine
  • util.c/.h general purpose utility functions, debug and logging support
  • gpio.c/.h parallel ports, LEDs and switch support
  • help.c/.h help screens
  • config.c/.h configuration sub-system
  • settings.h default settings and machine selection
  • settings_lumenlabMicRoV3.h ... various machine specific default files
  • xio....c/.h Xmega serial IO configuration sub-system
  • xmega..c/.h Xmega hardware support files

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).

Background and Gcode Support

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.

Gcode Support

The following Gcode commands are supported

  • G0 Rapid linear motion
  • G1 Linear motion at feed rate
  • G2, G3 Clockwise / counterclockwise arc at feed rate
  • G4 Dwell
  • G17, G18, G19 Select plane: XY plane {G17}, XZ plane {G18}, YZ plane {G19}
  • G20, G21 Length units: inches {G20}, millimeters {G21}
  • G53 Move in absolute coordinates
  • G61, G61.1, G64 Set path control mode (group 13)
  • G80 Cancel motion mode
  • G90, G91 Set distance mode; absolute {G90}, incremental {G91}
  • G92 Coordinate System Offsets - limited support provided
  • G93, G94 Set feed rate mode: inverse time mode {G93}, units per minute mode {G94}
  • M0 Program stop
  • M1 Optional program stop
  • M2 Program end
  • M3, M4 Turn spindle clockwise / counterclockwise
  • M5 Stop spindle turning
  • M30 Program end (pallet shuttle and reset)
  • M60 Program stop (and pallet shuttle)

Commands omitted for the time being:

  • G10 Coordinate system data
  • G14, G15 Spiral motion
  • G28, G30 Return to home (requires parameters)
  • G38.2 Straight probe
  • G40, G41, G42 Cutter radius compensation
  • G43, G49 Tool length offsets
  • G54 - G59.3 Select coordinate system (group 12)
  • G81 - G89 Canned cycles
  • G92 - G92.3 Coordinate system offsets
  • G98, G99 Set canned cycle return level
  • M6 Tool change
  • M7, M8, M9 Coolant (group8)
  • M48, M49 Enable/disable feed and speed override switches (group 9)

Other functions / commands that are not supported:

  • Multiple coordinate systems
  • Evaluation of expressions
  • O codes
  • Variables (Parameters)
  • Multiple home locations
  • Probing
  • Override control

Length Units and Coordinate Systems

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:

  • G10 Coordinate system origin setting
  • G54 - G59.3 Select coordinate system (group 12)
  • G92 - G92.3 Coordinate system offsets
  • G43 Tool offset
  • 9 coordinate systems + machine 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 and Seek Rates

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.


Starting, Stopping, Feedhold and Rate Overrides - Design Notes

The following functions are related and share common underlying functionality:

  • Cycle Start
  • Program Stop
  • Program End
  • Feedhold
  • Feed Rate Override and Traverse Rate Override
  • Abort (which is not an Emergency Stop - see later notes)

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:

  • The TinyG firmware assumes a cycle start if any Gcode block is received while the system is idle (in a STOP or RESET state)
  • A tilde character (~) is used to release the board from a feedhold.

...which lead to the Cycle Start behaviors in the various states below.

  • Pushing Cycle Start with a queued program that is not running causes the queued program to run from its start.
    • The controller performs this function by sending the first gcode block to the board.
    • A tilde does not need to be sent and will be ignored if sent.
  • Pushing Cycle Start after a Program Stop causes the current program to run from the next line in the file (i.e. from the stop point).
    • The controller performs this function by sending the next gcode block to the board.
    • A tilde does not need to be sent and will be ignored if sent.
    • Note that if the controller is responsible for introducing any required delay between the stop and subsequent start.
  • Pushing Cycle Start after a Program End causes the queued program to run (again) from its start. 
    • The controller performs this function.
    • A tilde does not need to be sent and will be ignored if sent.
  • Pushing Cycle Start after a Feedhold causes the current program to run from the feedhold point.
    • The controller sends a tilde to the board.
  • Pushing Cycle Start during a running program has no effect.
  • Pushing Cycle Start with no queued program has no effect.


Program Stop (M0, M1, M60) [3.6.1]

  • Program Stop causes the program to stop at the end of line preceding the stop command; i.e. it is synchronous with program execution.
  • Position and other machine state is preserved during a program stop.
  • M0, M1 and M60 are all the same as TinyG does not implement an Optional Stop switch or a pallet shuttle.


Program End (M2, M30) [3.6.1]

  • Program End causes the program to stop at the end of the line preceding the stop command; i.e. it is synchronous with program execution.
  • No more lines of code in an RS274/NGC file will be executed after the M2 or M30 command is executed unless Cycle Start is pushed again.
  • An M2 or M30 program stop resets position and other machine state as follows: (Note that this is somewhat different from the reset conditions specified in NIST 3.6.1)
    • Axes coordinates are set to zero (like G92.1 or G92 x0 y0 z0 a0 b0 c0)
    • Selected plane is set to its default value as per the $GL setting
    • Distance mode is set to its default value as per $GD
    • Units mode is set to its default value as per $GP
    • Path control mode is set to its default value as per $GP
    • Feed rate is set to zero
    • Feed rate mode is set to UNITS_PER_MINUTE (like G94).
    • Feed and speed overrides are set to ON (like M48).
    • The spindle is stopped (like M5).
    • The current motion mode is set to G1 (like G1).


Feedhold ('!') [2.1.2.15, 2.2.1, 3.6.5, 4.3.9.3]

  • Feedhold causes the the running program to stop as quickly as possible while still obeying maximum jerk limits.
  • Feedhold will stop in the middle of the currently executing feed or traverse, regardless of the position in the program file (which will be ahead of the actual moves as there will be moves in the planning buffer).
  • Position and other state is preserved at the stop point, and it can be assumed that the motion can be restarted while maintaining positional accuracy.
  • Feedhold is issued by sending the '!' character. A feedhold command is intended to be sent asynchronously from the program file - that is - it would generally not be included in the file.
  • The board is released from feedhold when it receives a tilde charcter (~), on which it will accelerate out of the stop and resume running.


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]

  • Feed rate override causes feeds to run faster or slower than the programmed rate.
  • Feed rate overides affects G1, G2, and G3 commands (and any cycles that would use these commands - to be implemented).
  • The feed rate will be adjusted to from 0x to 2x the programmed feed rate - up to the feed limits, cornering limits, and jerk limits set for the machine.
  • Feed rate override is invoked by sending M48.1 <value>, where value is between 0.000 and 2.000
  • Feed rate override is canceled by sending M49.1, or by sending M49 which cancels both feed and traverse overrides

  • Traverse rate override causes traverses to run slower than the set traverse rate (seek rate): i.e. traverses will not move faster than the max set traverse rate.
  • Traverse rate override is invoked by sending M48.2 <value>, where value is between 0.000 and 1.000
  • Traverse rate override is canceled by sending M49.2, or by sending M49 which cancels both feed and traverse overrides


  • Feed and traverse rate overrides are effective as quickly as possible, and should begin velocity change within 2 stepper segments, typically between 10 & 20 ms.


Abort (^x character) [no NIST references found]

  • Abort causes motion to stop as rapidly as possible, and may exceed operating jerk limits.
  • System state is reset as per a Program End (M2 or M30), and no other assumptions should be made about machine position.
  • Note that this is not an Emergency Stop. An emergency stop must remove power to all active components and may also include some form of electrical or mechanical braking on moving parts such as spindles and axes. In an emergency stop the controller is completely disabled and electrically removed from the system.


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

Module Details

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. 

main.c/tinyg.h

  • This module runs initialization and the outer main loop.
  • Global defines and the few global variables are in tinyg.h
  • There is also a rudimentary unit testing framework that is launched from here.

controller.c/.h

Controller Design Notes

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 interpret various types of inputs, including:
  • Gcode blocks
  • Configuration commands for various sub-systems
  • Accept control commands from stdio - e.g. !, ~, ^x...
  • Arbitrary commands that bypass the Gcode layer
  • Multi-DOF protocols TBD

Accept and mix inputs from multiple sources:

  • USB
  • RS-485
  • Arduino serial port (Aux)
  • strings in program memory
  • EEPROM data
  • SD card data

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:

  • High priority ISRs
    • issue steps to motors / count dwell timings
    • dequeue and load next stepper move
  • Medium priority ISRs
    • receive serial input (RX)
    • send serial output (TX)
    • detect and flag limit switch closures
  • Low priority ISRs
    • execute moves from the planner to compute them for the stepper loader
  • Main loop tasks These are divided up into layers depending on priority and blocking hierarchy. See tg_controller() for details.

Controller Operation

  • XIO is used to read a line of text from the current stdin device (See XIO for details)
  • tg_parser is the top-level parser / dispatcher. Examines the head of the string to determine how to dispatch:
    • Gcode blocks
    • Gcode configuration lines
    • Direct drive (motion control) command
    • Network command / config (not implemented)
  • Individual parsers/interpreters are called from tg_parser. These can assume:
    • They will only receive a single line (multi-line inputs have been split)
    • They perform line normalization required for that dispatch type
    • Can run the current command to completion before receiving another command

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).

Modedness

(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:

  • G,M,N,F,%,( enter GCODE_MODE and perform that action
  • T execute primary test (whatever you link into it)
  • U execute secondary test (whatever you link into it)
  • H help screen (returns to TEST mode)
  • R soft reset
  • D <reserved for dumb mode>
  • I <reserved>
  • V <reserved>

Once in the selected mode these characters are not active as mode selects.

How To Code Continuations

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:

  • A continuation is a pair of routines. The first is the main routine, the second the continuation. for an example see mc_line() and mc_line_continue().
  • The main routine is called first and should never block. It may have function arguments. It performs all initial actions and typically sets up a static structure to hold data that is needed by the continuation routine. The main routine should end by returning a uint8_t TG_OK or an error code.
  • The continuation task is a callback that is permanently registered (OK, installed) at the right level of the blocking heirarchy in the tg_controller loop; where it will be called repeatedly by the controller. The continuation cannot have input args - all necessary data must be available in the static struct (or by some other means).
  • Continuations should be coded as state machines. See the homing cycle or the aline execution routines as examples. Common states used by most machines include: OFF, NEW, or RUNNING.
    • OFF means take no action (return NOOP).
    • NEW is the the state on initial entry after the main routine. This may be used to perform any further initialization at the continuation level.
    • RUNNING is a catch-all for simple routines. More complex state machines may have numerous other states.
  • The continuation must return the following codes and may return additional codes to indicate various exception conditions:
    • TG_NOOP: No operation occurred. This is the usual return from an OFF state. All continuations must be callable with no effect when they are OFF (as they are called repeatedly by the controller whether or not they are active).
    • TG_EAGAIN: The continuation is blocked or still processing. This one is really important. As long as the continuation still has work to do it must return TG_EAGAIN. Returning eagain causes the tg_controller dispatcher to restart the controller loop from the beginning, skipping all later routines. This enables heirarchical blocking to be performed. The later routines will not be run until the blocking conditions at the lower-level are removed.
    • TG_OK; The continuation task has completed - i.e. it has just transitioned to OFF. TG_OK should only be returned only once. The next state will be OFF, which will return NOOP.
    • TG_COMPLETE: This additional state is used for nesting state machines such as the homing cycle. The lower-level routines called by a parent will return TG_EAGAIN until they are done, then they return TG_OK. The return codes from the continuation should be trapped by a wrapper routine that manages the parent and child returns. When the parent REALLY wants to return it sends the wrapper TG_COMPLETE, which is translated to an OK for the parent routine.

gcode.c/.h

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

  • Gcode blocks that are case and wite-space insensitive
  • Line numbers ('N's)
  • Block deletes ('/'s)
  • Comments ('('s)
  • Configuration commands ('$') can be freely intermixed with Gcode blocks

canonical_machine.c/.h

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).

planner.c/.h

The planner does line acceleration / deceleration management to plan and execute lines. Additionally, it handles dwells, stop/start.

Acceleration Planning

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:

  • head acceleration to target velocity (2 acceleration periods)
  • body of the move at target velocity (1 cruise period)
  • tail deceleration to exit velocity (2 deceleration periods)

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.

  • Line planner (background) - The aline() trajectory planner main routine is called to compute and queue a new block. It computes the initial parameters for the new block (trapezoid), then recomputes the planner buffer queue to optimize the existing blocks relative to the new block. The planner runs as background task planing one block per invocation. It will be invoked until there are no new block to plan, or the planning queue is full and it must block. 
  • Line Execution (runtime) - The aline runtime routine (actually routines) executes the planned block. Head and tail acceleration / deceleration segments are computed as a set of constant-time very-short-line-segments that implement the stepwise-linear accel/decel transition. The segment time constant is chosen (~10 ms) to allow sufficiently fine accel/decel resolution and enough steps to occur in a segment so that low velocity moves are not jerky. The runtime execution (exec) is performed at the LO interrupt level, initiated by the stepper routines in order to sync with the stepper pulse generation and line load operations. The calling sequence and timeing is described in Move Execution, below.

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:

  1. Cannot exceed the maximum entry velocity of the block. This is the minimum of the block's cruise velocity or the cornering velocity (junction velocity).
  2. The maximum exit velocity of the block must allow the remaining blocks in the chain to decelerate to zero - or to decelerate to the max entry velocity of a subsequent block if that's the dominant term.
  3. The change in velocity between the entry and exit (the delta V) cannot exceed the maximum allowable velocity change supported by the length of the block and jerk maximum.
  4. The exit and entry velocities of adjacent blocks must be equal.

Variables used in planning are:

  • velocity terms: These are the velocity terms adjusted to meet the above constraints. They are then passed to the trapezoid calculator where they may be further modified.
    • entry_velocity
    • cruise_velocity - max block velocity whether or not there is a cruise region
    • exit_velocity
  • vmax terms: These are computed once when the block is added and may be used repeatedly during backplanning. They will not change.
    • cruise_vmax is the requested cruise velocity of the block (set velocity - see note (1))
    • entry_vmax is the maximum velocity at which the move can be entered. It is the minimum of the maximum junction velocity between it and the previous block and cruise_vmax. It is set to zero for an EXACT STOP.
    • delta_vmax is the maximum change in velocity possible for the block based on the length and the jerk term (Note: the starting velocity Vi is irrelevant to the computation in this case).
    • exit_vmax is the maximum velocity that the move can exit. It is set to zero for an EXACT STOP.
  • braking_velocity is the maximum entry velocity for the block that will allow the subsequent replan chain to brake to zero. Braking velocities are recomputed for each new block added.

The number of blocks in the replan chain is determined by the first "non-replannable" block. A block becomes non-replannable when:

  1. The move is already executing.
  2. It's an exact stop move (always runs to zero)
  3. The block has been optimized, i.e. hits all it's max velocities of cannot otherwise be improved. This can be detected if the exit velocity has achieved exit_vmax during forward planning

Notes:

  1. The set velocity is the max velocity passed to the planning routine, whcih may or may not be the actuall feed rate (G1, G2, G3) or maximum velocity (G0). The set velocity may have already been downgraded by the canonical machine to allow for axis limitations or other constraints.  
  2. All math is done in absolute coordinates using "double precision" floating point (even though AVRgcc does this as single precision)

Move Execution

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:

  • Move planning (above) runs at the main-loop level (i.e. runs as a task invoked from the controller). The canonical machine calls the planner to generate lines, arcs, dwells and synchronous stop/starts. The planner module generates blocks (bf's) that hold parameters for lines and the other move types. The blocks are backplanned to join lines, and to take dwells and stops into account ("plan" stage). Move planning takes place in planner.c.
    • Note: Arc movement is actually planned above the above the line planner in plan_arc.c. It generates short lines to the line planner.
  • Move execution and load prep runs at the LO interrupt level. Move execution generates the next acceleration, cruise, or deceleration segments for planned lines, or just transfers parameters needed for dwells and stops. This layer also prepares moves for loading by converting the executed move into values that can be directly loaded into the steppers. ("exec" and "prep" stages). Move execution occurs in the planner module; prep takes place in the the stepper module.
  • Pulse train generation runs at the HI interrupt level. The stepper DDA fires timer interrupts that generate the stepper pulses ("run" stage). This level also transfers the new stepper parameters once each segment is complete ("load" stage). Run and load both occur in the stepper module.

As long as the steppers are running the sequence of events is:

  • The stepper interrupt (HI) runs the DDA to generate a pulse train for the current move.
  • When the segment is finished the stepper interrupt loads the next segment from the prep struct, reloads the timers, and starts the next segment.
  • At the end of the load the stepper interrupt routine requests an "exec" of the next segment in order to prepare for the next load operation. It does this by calling the exec using a "software interrupt" (actually a timer).
  • As a result of the above, the exec handler fires at the LO interrupt level. It executes the next segment in the running plan buffer or gets and runs the next buffer in the planning queue - depending on the move_type and state.
  • Then the exec handler runs the prep routine needed to get the segment or move into the prep struct.
  • The main loop runs in background to receive gcode blocks, parse them, and send them to the planner in order to keep the planner queue full.

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.

Move Execution TIming

(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:

  1. Stepper pulses are delivered up to a maximum rate of 50 Khz, or 20 uSec between pulses.
  2. Stepper pulses should be delivered with maximum accuracy and a minimum amount of inter-pulse variance, or jitter. Pulse jitter should be something < 0.1 uSec. [This is REALLY what makes for smooth motor operation - this and phaseing between segments. No amount of microstepping is going to correct for sloppy pulse generation.]
  3. The S curves generated by the jerk planning are non-linear, and a simple increment-the-DDA-clock solution that can suffice for constant acceleration systems does not work. Instead, the S curve is decomposed into piecewise linear segments about 10 mSec long that are applied to the DDA one after another. (See note).
  4. Transitioning from one segment to the next must occur with minimal time delay, or this introduces jitter or in the worst case a motor stall. So the next segment should load into the DDA in a minimum time, and in no more than 20 uSec. The next segment must therefore be in some loadable form in no more than 10 mSec from the time the current one is first loaded.

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.

Jerk Equations

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.

Cornering Algorithm

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: 

  • double theta = acos(costheta);
  • double radius = delta * sin(theta/2)/(1-sin(theta/2));

Dwells

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.

kinematics.c/.h

This module isolates the robot inverse kinematics. This module also maps motors to axes and takes inhibited axes out of play.

Cycle Budget

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:

  • MIN_SEGMENT_LENGTH 0.05
  • ESTD_SEGMENT_USEC 10000

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.

stepper.c/.h

The stepper module performs the following functions:

  • Stepper system init
  • Stepper DDA load 
  • Stepper pulse generation / DDA execution (ISR)
  • Dwell downcounter (ISR)
  • Software interrupt (timer) to load stepper segment (ISR)
  • Software interrupt (timer) to request move execution (ISR)
  • Various prep routines and other supporting functions  

Digital Differential Analyser aka Bresenham Algorithm aka DDA

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 DDA accepts and processes fractional motor steps. Steps are passed to the move queue as doubles, and may have fractional values for the steps (e.g. 234.934 steps is OK). The DDA implements fractional steps and interpolation by extending the counter range downward using a variable-precision fixed-point binary scheme. It uses the DDA_SUBSTEPS setting to control default fixed-point precision.
  • The DDA is not used as a 'ramp' for acceleration management. Accel is computed as 3rd order (maximum jerk) equations that generate piecewise linear accel/decel segments to the DDA. The DDA runs at a constant rate for each segment up to a maximum of 50 Khz step rate. The segment update rate (time length of segments) is set by the MIN_SEGMENT_USEC setting in planner.h - which is typically 10 ms segment times.
  • The DDA rate for a segment is set to an integer multiple of the step freqency of the fastest motor (major axis). This amount of overclocking is controlled by the DDA_OVERCLOCK value, typically 16x. A minimum DDA rate is enforced that prevents overflowing the 16 bit DDA timer PERIOD value. The DDA timer always runs at 32 Mhz. The prescaler is not used. Various methods are used to keep the numbers in range for long lines. See _mq_set_f_dda() for details.
  • Pulse phasing is preserved between segments if possible. This makes for smoother motion, particularly at very low speeds and short segment lengths (avoids pulse jitter). Phase continuity is achieved by simply not resetting the DDA counters across segments. In some cases the differences between timer values across segments are too large for this to work, and you risk motor stalls due to pulse starvation. These cases are detected and the counters are reset to prevent stalling.
  • Pulse phasing is also helped by minimizing the time spent loading the next move segment. To this end as much as possible about that move is pre-computed during the queuing phase. Also, all moves are loaded from the interrupt level, avoiding the need for mutual exclusion locking or volatiles (which slow things down).

cycle_homing.c

The homing cycle finds the home switch for each confgured axis, then offsets that axis relative to the location of the switch.

plan_arc.c/.h

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. 

util.c/.h

General purpose utility, math, debug and logging support functions

gpio.c/.h

Parallel ports, LEDs and switch support

help.c/.h

config.c/.h

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

Config Conventions and Internals

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:

  1. Settings are specified as a 2 character mnemonic preceded by a group. Groups are things like axis specifiers (e.g. Y), motor specifiers, (e.g. 2), or the general group (no prefix) - which carries all the non-axis and non-motor settings. Mnemonics within an axis or motor group can be any 2 alpha chars. Digits are not allowed. Mnemonics for the general group cannot start with a group identifier, i.e. they can't start with an axis name or a motor number or PWM number. So these are off limits for 1st chars: X,Y,Z,A,B,C,U,V. These are also off limits: 1,2,3,4,5,6,7,8,9,0
  2. The order of display is set by the order of the settingList struct. None of the other orders matter but are kept sequenced for easier reading and code maintenance.
  3. Gcode defaults are held in the cfg struct as their "G" value, e.g. G20 is held as 20, G61.1 as 61.1, etc. These are converted to the internal representations and loaded into the gcode model (gm) by the apply functions.
  4. Modes and Units: The system can be in either inches mode (G20) or mm mode (G21). This affects how settings are displayed and entered. ABC axes always use degrees regardless of prevailing unit mode.

How to Modify or Add Config Items

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:

  • First steps
    • add the setting to the global cfg struct in config.h (or wherever)
    • define a default value for it in settings.h and/or machine settings files
  • In the SETTING-SPECIFIC REGION in config.c:
    • add a non-colliding mnemonic (e.g. MN) to cfgMnemonics & mnemonics. The formatting in the file helps keep this and the COUNTs straight.
    • change COUNT_SETTINGS and subordinate defines to accommodate new item(s)
    • add a static apply function and its prototype (_applyMN)
    • add a display format string (fmtMN)
    • add init line(s) to the large struct array
  • If the setting displays differently in inches than mm do also:
    • add separate format string and apply function for inches mode
    • compute and add the conversion factor (which is almost always 25.4)

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.

settings.h

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:

  • settings_lumenlabMicRoV3.h Lumenlab
  • settings_probotixV90.h Probotix
  • settings_zen7x12.h Zen Toolworks
  • settings_makerbotCupcake.h (Needs validation)

xio....c/.h

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]

  • Stdio compatible putc() and getc() functions provided for each device
  • This enables fgets, printf, scanf, and other stdio functions
  • Full support for formatted printing is provided (including floats)
  • Assignment of a default device to stdin, stdout & stderr is provided
  • printf() and printf_P() send to stdout, so use fprintf() to stderr for things that should't go over RS485 in SLAVE mode

Facilities provided beyond stdio:

  • Devices are managed as an enumerated array of derived devices
  • Supported devices include:
    • USB (derived from USART)
    • RS485 (derived from USART)
    • Arduino connection (derived from USART)
    • Program memory "files" (read only)
    • (other devices may be added as needed)
  • Stdio FILE streams are managed as bindings to the above devices
  • Additional functions provided include:
    • open file (initialize address and other parameters)
    • gets (non-blocking input line reader - extends fgets)
    • xioctl (ioctl-like knockoff for setting device parameters)
    • signal handling - captures ^c, pause, resume, etc. as interrupts
    • interrupt buffered fifo RX and TX functions
    • XON/XOFF software flow control implemented

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:

  • XIO_BAUD_gm baud rates from 9600 - 1Mbaud
  • XIO_RD / XIO_WR / XIO_RDWR read & write enables and a combined one for convenience
  • XIO_BLOCK / XIO_NOBLOCK enable / disable blocking reads
  • XIO_XOFF / XIO_NOXOFF enable / disable XON/XOFF flow control
  • XIO_ECHO / XIO_NOECHO enable / disable character echo on receive
  • XIO_CRLF / XIO_NOCRLF convert <LF> to <CR><LF> on writes - or not
  • XIO_IGNORECR / XIO_NOIGNORECR  ignore <CR> on reads - or not
  • XIO_IGNORELF / XIO_NOIGNORELF ignore <LF> on reads - or not
  • XIO_LINEMODE / XIO_NOLINEMODE special <CR><LF> read handling

Notes on Circular Buffers

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 head is left "pointing to" the character that was previously written - meaning that on write the head is pre-decremented (and wrapped, if necessary), then the new character is written.
  • The tail is left "pointing to" the character that was previouly read - meaning that on read the tail is pre-decremented (and wrapped, if necessary), then the new character is read.
  • The head is only allowed to equal the tail if there are no characters to read.
  • On read: If the head = the tail there is nothing to read, so the function either exits with TG_EAGAIN or blocks (depending on the blocking mode selected).
  • On write: If the head pre-decrement causes the head to equal the tail the buffer is full. The head is left at its original value and the device should go into flow control (and the byte in the USART device is not read, and therefore remains in the USART (VERIFY THAT I DIDN'T BREAK THIS BEHAVIOR!)). Reading a character from a buffer that is in flow control should clear flow control.

Notes on Control Characters and Signals

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.c/.h

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.  

Project Details

Project Setup in AVRStudio4

Install WinAVR and AVRStudio 4 (if not already installed):

  • run WinAVR-20100110-install.exe
  • run AvrStudio4Setup.exe
  • run AVRStudio4.18SP1.exe (last one before they went to Studio 5)

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:

  • Go into debug mode
  • In Debug / AVR Simulator 2 Options
  • Set clock frequency to 32 Mhz.

Add libm.a (math lib) otherwise the floating point will fail.

In AVRstudio select Project / Configuration Options: 

  • Select Libraries
  • Move libm.a from the left pane to the right pane

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

  • Select Custom Options
  • The right pane should read:
-Wall
-gdwarf-2
-std=gnu99
-DF_CPU=32000000UL
-O0 (or typically Os)
-funsigned-char
-funsigned-bitfields
-fpack-struct
-fshort-enums
  • Add floating point formatting code (see below) to the linker string in order for floats to work in printf %f

In AVRstudio select Project / Configuration Options:

  • Select Custom Options
  • In the left pane (Custom Compilation Options) Select [Linker Options]
  • Add the following lines to the right pane (is now linker 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

ref: http://www.cs.mun.ca/~paul/cs4723/material/atmel/avr-libc-user-manual-1.6.5/group__avr__stdio.html#ga3b98c0d17b35642c0f3e4649092b9f1

An annoying avr20100110 bug:

  • If you are running WinAVR-20100110 you may be asked to locate libraries or include files that were known to a previous avr-gcc version.
  • When asked to browse for stdlib files, go to:  C:\WinAVR-20100110\avr\lib\avrxmega6 (or is it: C:\WinAVR-20100110\lib\gcc\avr\4.3.3\avrxmega6
  • When asked to browse for include files go to: C:\WinAVR-20100110\avr\include

Project Setup in AVRStudio5

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:

  • General: unsigned defaults
  • Symbols: -DF_CPU=32000000UL [see Note 1]
  • Optimization: -Os, -fpack-struct, -fshort-enums
  • Warnings: -Wall
  • Miscellaneous: -gdwarf-2 -std=gnu99
  • Linker: -Wl,-lm -Wl,-u,vfprintf -mmcu=atxmega256a3 [see Note 2]
  • Libraries: libm.a
  • libprintf_flt.a
  • Miscellaneous: -Wl,-u,vfprintf


Assembler (should set automatically) [see Note 2]

  • -Wa,-gdwarf2 -x assembler-with-cpp -c -Wall -gdwarf-2 -std=gnu99
  • -DF_CPU=32000000UL -O0 -funsigned-char -funsigned-bitfields
  • -fpack-struct -fshort-enums -mmcu=atxmega256a3

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

Personal tools