GP-GL on the Graphtec CC100

Introduction

GP-GL (Graphtec Plotter Graphics Language) is the command language of Graphtec plotters and cutters, and of the Silhouette machines built on them. The full language covers text, hatching, polar coordinates, status words and configurable terminators. The Graphtec CC100, sold as Craft ROBO, speaks a compact subset of it, extended with commands of its own for the knife, the media and registration marks.

This article describes that dialect as of firmware 2.30: how bytes reach the plotter, how commands and numbers are parsed, what units mean, what replies look like, and what every command does.

Notation used below:

The machine in brief

Transport

USB

The plotter enumerates as a printer-class device:

VID / PID         0x0B4D / 0x110A
USB version       1.0, self-powered
Interface class   7/1/2 (printer, bidirectional)
Endpoints         bulk OUT 0x01, bulk IN 0x82, 64 bytes each
Device ID         MANUFACTURER:Graphtec;MODEL:CC100-20;CLASS:PRINTER;
                  DESCRIPTION:Graphtec CC100-20;

Commands go out on bulk OUT, replies come back on bulk IN. Flow control is done by the device: when fewer than 100 bytes are free in its receive buffer it stops accepting data and NAKs the host, and it resumes once more than 110 bytes are free. A host should therefore use write timeouts rather than assume a write returns immediately.

RS-232

9600 baud, 8 data bits, no parity, 2 stop bits

There is no flow control and no overflow check on the serial path. A reply always goes back on the interface that delivered the most recent data byte.

Escape sequences

A few two- or three-byte escape sequences are handled in the receive interrupt itself, so they work even while a job is running. On USB, ESC and its argument must arrive in the same USB packet; send each sequence in a single write.

Sequence    USB  RS-232  Effect
ESC 00 n    yes  yes     remote jog keys, see below
ESC 01      yes  no      debug trap; afterwards byte 0x01 is stored as data
ESC 02      yes  yes     USB: reply "1" if powered on, "0" if soft-off
                         RS-232: cancel the running job
ESC 03      yes  no      cancel the running job
ESC 04      yes  no      ignored ("initialize" in host drivers)
ESC 05      yes  no      reply "0" if idle and waiting for data, "1" if busy

Replies to ESC 02 and ESC 05 are a single digit followed by ETX.

The jog mask n is active-low: bit 0 clear means +X, bit 1 −X, bit 2 +Y, bit 3 −Y. While the plotter waits for data and n is non-zero, the head jogs in the selected directions; n = 0 ends jog mode.

Two traps: on USB, an unknown `ESC x` drops the ESC and stores x as data; on RS-232 the escape state machine waits for 00 or 02 and swallows everything else until then.

Syntax

Commands

A command is one character, or two for the forms listed later. For a two-letter command the interpreter reads the first letter and peeks at the second; if the pair is not a known command, the single-letter command runs and the peeked character becomes the start of its parameters.

Between commands the interpreter skips, without complaint:

Any byte above 0x5F (lowercase letters, {|}~, DEL, 8-bit bytes) and any unknown command letter is ignored too; it only sets an error bit nobody can read. A useful consequence: the parameters of an unsupported command are just numbers and commas, so they are skipped harmlessly. The flip side: a letter inside a parameter list starts the next command.

Parameters and the terminator

Parameters follow the command directly, separated by commas. A parameter list ends at the terminator, at the next command letter, at /, or at ESC, 0x1C or 0x80.

The terminator is always ETX; the = command that would change it does nothing. The terminator is also optional and never consumed by a command, so this is valid input:

M100,100D200,200,300,200

Commands that take a list of points (D, E, Y, _, >) keep reading while another number follows. Commands with fixed arity read exactly their parameters; extra numbers are skipped. A missing parameter reads as 0 without consuming anything, so M,5 moves to (0,5).

Numbers

number = { skip } [ "-" ] digits [ "." digits ]

One quirk deserves attention: a negative number whose integer part is zero loses its sign. -0.4 reads as 0, -0.25 as +0.25, and -0.5 rounds to +1. Small negative relative moves therefore go the wrong way. Hosts should round values between −1 and 0 to an integer before sending them.

Units and coordinates

Internally the plotter works in 0.005 mm. Every coord is multiplied by a step factor taken from the table {20, 10, 5, 2}, which makes one user unit 0.1, 0.05, 0.025 or 0.01 mm. The step size is a factory setting; the standard one is 0.05 mm, that is 20 units per millimetre, which is what all examples here assume. A 272 × 200 mm page is FU5440,4000, and TB51,400 is a 20 mm mark. Fractions are honoured down to the internal resolution.

In portrait orientation (the default) X runs along the media feed and Y across the carriage; landscape swaps the axes. The usable width is 200 mm (190 mm with a 5 mm offset in the alternative media mode), and the page length defaults to 1000 mm.

Every point passes through the same pipeline before it reaches the motors:

1. line type      dashing per L and B, in user space
2. clip polygons  defined by >, drawing alternates even-odd at crossings
3. factor         &
4. rotation       /
5. orientation    axis swap and mirror
6. window         clipping against \ and Z
7. device         distance correction (FB), registration transform (TB),
                  cutter offset compensation (FC), motion planner

The offset ^ sits between the user and device coordinates: changing it does not move the tool.

Motion

Replies

Every reply is one or more decimal fields separated by commas and terminated by a single ETX. Fields are right-aligned in a fixed width, mostly 5 or 6 characters:

A point reply looks like this (ETX not shown):

100,    -5

Parsing with a whitespace-tolerant scanner works fine.

Errors

The interpreter keeps an error byte (unknown command, number overflow) behind a mask set by ". No command ever reports it: V, #, : and ; are not implemented. In practice there is no readable error status. The nearest thing is @, which reports whether the current position lies outside the hard limits.

Command reference

Drawing

D  Dx₁,y₁[,x₂,y₂...]          draw to each absolute point
M  Mx,y                       move to an absolute point (one point only)
E  Edx₁,dy₁[,dx₂,dy₂...]      draw, each point relative to the previous one
O  Odx,dy                     move relative (one point only)
H  H                          home: undo chart feed, wait, resynchronise
W  Wx₀,y₀,r₁,r₂,θ₁,θ₂[,d]     arc or spiral around (x₀,y₀)
]  ]r₁,r₂,θ₁,θ₂[,d]           arc relative to the current point
WP WPa,x₁,y₁,x₂,y₂,x₃,y₃[,d]  circle through three points
)  )a,x₀,y₀,r₁,r₂,θ₁,θ₂,θ₃[,d] ellipse
Y  Ya,x₁,y₁,x₂,y₂...          smooth curve through the points
_  _a,dx₁,dy₁,...             smooth curve, relative points
BZ BZa,x₁,y₁,...,x₄,y₄[,d]    cubic Bézier
X  Xp,q,r[,t₁,t₂]             axis with tick marks

Details worth knowing:

Line type

L    Lp                  line type -9..9
L100 L100,n,a,b[,c]      user-defined dash pattern
B    Bl                  pattern period (0 keeps the old one)

Type 0 is solid, 9 puts dots at the vertices only, and 1 to 8 are built-in patterns. Lengths alternate pen-down and pen-up and are scaled from a period of 64 to the B pitch; a length of 0 is a dot. Negative types shift the pattern by half its first dash.

1  1,63         5  50,7,0,7
2  8,56         6  50,5,4,5
3  32,32        7  50,5,0,4,0,5
4  50,14        8  41,5,4,5,4,5

L100 builds its own pattern from positive lengths: n = 1 gives [a, b], 2 gives [a, b, c, b], 3 gives [a, b, c, b, c, b]. L100,1,40,80 gives dashed fold lines.

Coordinate setup

\    \x,y          window lower left
Z    Zx,y          window upper right
^    ^x,y          origin offset; the tool does not move
&    &p,q,r        factor: x·p/r, y·q/r
/    /x,y,θ        rotate by θ around (x,y); θ = 0 turns it off
>    >x₁,y₁,...    add a clipping polygon; > alone clears them all
SO   SO            set origin at the carriage (NP is an alias)
FJ   FJ            re-reference the origin, clearing transforms
FT   FT            move pen-up to (0,0)

The window only clips along the feed axis after Z has been sent. & is ignored unless all three values are non-zero, |p/r| and |q/r| are below 9, and |r/p| and |r/q| are at most 4096. Polygons from > accumulate; drawing inside them is even-odd.

Queries

G, C   x,y,p      position after motion ends; p = tool·10 + 1 if pen up
?      x,y        offset
[      x,y        window lower left
U      x,y        window upper right
@      n          position outside limits: 1 x>max, 2 x<min, 4 y>max, 8 y<min
FG     V2.30      firmware version (followed by four spaces)
FA     a,b        distance correction
FL     n          media mode
FV     n          media type (unset reads as 13)
FQn    v          stored setting n, see below
TB71   x,y        registration calibration
TB98   1          constant 1

C behaves like G; it does not wait for a button press. FQ reports: 0 speed, 1 force level, 2 cutter offset, 3 overcut, 4 orientation, 5 whether registration marks were found. Other values give no reply at all, so don't wait for one.

Tool and media settings

Commands that accept a pen number parse it and ignore it; every setting applies to all eight pen slots. Settings marked saved go to EEPROM, written only when they change.

!    !l[,n]        speed 1..10 (101..110 also accepted), saved
*    *a,f[,n]      force level f = 1..3 (a is ignored), saved
FX   FXn[,m]       cutting force 1..30, saved
FC   FCp,q[,n]     cutter offset p = 0..45 in 0.025 mm, saved
FD   FDθ[,n]       blade rotation threshold, default 30°
FE   FEp[,n]       overcut on (1) or off (0), saved
FF   FFa,b[,n]     overcut lengths 0..9 in 0.1 mm, default 2
FK   FK[n]         full-force touchdown after a blade swivel
FN   FN[n]         orientation 0 portrait, 1 landscape, saved
FM   FMn           media mode: 1 = 200 mm, 0 = 190 mm with 5 mm offset
FO   FO[len]       page length, 0 means 1000 mm
FW   FWn           media type 1..13 (anything else becomes 13), saved
FB   FBa,b         distance correction -200..200 (±2 %) each, saved
FY   FYn           track enhancing: 0 on, 1 off, saved
FU   FUlen,w       track-enhancing roll over len
F    Fl            chart feed length (stored only)
TT   TT            soft reset and home

Some of these surprise:

Registration marks

The optical sensor sits 15 mm from the knife. During a search all moves are shifted so coordinates refer to the point under the sensor. Each mark is an L made of two bars; each bar is found with one coarse and two fine scans, and its position is the midpoint of the two detected edges. From the marks the plotter builds an affine transform, rotation and optionally scale, applied to everything plotted afterwards.

TB0            transform off
TB99           transform on (applied after a successful search)
TB50[,n]       orientation for registration, 0 portrait
TB51,len       mark length, 5..20 mm
TB52[,n]       mark type, 1 or 2 (default 2)
TB53,w         mark line width 0.3..1.0 mm (stored, unused)
TB54[,x[,y]]   mark offset
TB55,n         scale to the nominal mark distance (0.667..2.0)
TB23[,x,y]     manual search, tool placed at the first mark
TB123[,x,y]    automatic search
TB70           calibration cut over a printed cross
TB72,x,y       sensor-to-knife correction, ±40 in 0.05 mm, saved

TB22 and TB122 are the search-type-1 variants of TB23 and TB123. A search replies 0 when the marks were found and non-zero otherwise; after a failed automatic search the page reverts to 1000 × 200 mm. A typical host sequence:

TB50,0  TB51,400  TB99  TB55,1  TB123,x,y   -> "    0"   then FQ5

Test and maintenance

FH   test cut: a 10 mm square with a triangle inside, then 10.5 mm feed
FI   endless test plot; only a power cycle stops it
FP   FPGRFCC1 unlocks service mode, anything else is ignored

FP is not a firmware update command; firmware updates are not part of the protocol.

Accepted, but no-ops

Since = does nothing, the two terminator bytes after it are interpreted as commands. Don't send it.

Service mode

After FPGRFCC1, lowercase letters become commands until the next reset. They exist for the factory and change calibration values, so host software should leave them alone.

a0..a3   sensor and LED tests      j, k, y  internal counters
b        home sensor state         l        internal flag
c        motion parameter          m, q     endless burn-in loops
d0..d3   user unit 0.1..0.01 mm    n        mark sensor threshold
e        arithmetic self test      o        plot every received byte
f        read a RAM byte           p        toggle panel LEDs
g        service parameters        t0..t4   mark sensor tests
h        rebuild speed tables      z        set output port bits
i        feed 40 mm and back

g17 is a factory reset of the user settings.

Settings stored in EEPROM

Offset      Content                    Set by    After factory reset
0x04        configuration, step size   service   unchanged
0x1C, 0x1E  distance correction        FB        0, 0
0x24, 0x26  sensor-to-knife correction TB72      0, 0
0x34        speed                      !         10
0x35        force level                *         3
0x36        cutter offset              FC        0
0x37        orientation                FN        0
0x38        overcut                    FE        0
0x39        track-enhancing passes     service   unchanged
0x3A        media type                 FW        1
0x3B        cutting force              FX        0 (unset)
0x3C        track enhancing off        FY        0

A complete job

A real job looks like this. It draws a box net of about 245 × 157 mm, with dashed fold lines and solid cut lines:

FU5440,4000FM1TB50,0FO5440&100,100,100,\0,0,Z5940,4200,L100,1,40,80,^0,100
M44.006,1143.797,D1045.895,1143.797M1045.895,1992.228,D44.006,1992.228 ...
L0,M29.895,1143.797,D59.881,1073.242D1015.909,1073.242 ...
&1,1,1,TB50,0FO0

There are no terminators at all and trailing commas everywhere, and of the three decimals only two count.

Compared with full GP-GL

Grammar

The grammar below is in Go EBNF: [x] is optional, {x} repeats, `a … b` is a range. It describes well-formed input; the plotter is more forgiving. Between commands it skips digits, signs, commas, dots, #, 0x80, spaces and control characters, a missing parameter reads as 0, extra parameters are skipped, and where a letter starts both a one- and a two-letter command (D / DP, W / WP, F / FX), the two-letter form wins when the next byte matches.

Stream = { RealTime | Command | ServiceCommand | Filler } .

/* Real-time sequences, handled on receipt and never seen by the interpreter.
   On USB ESC and its argument must be in the same packet. */
RealTime = esc ( "\x00" Byte  /* jog keys, active-low bit mask */
               | "\x01"       /* debug trap */
               | "\x02"       /* USB: online? "1"/"0"; RS-232: cancel */
               | "\x03"       /* cancel job */
               | "\x04"       /* ignored */
               | "\x05"       /* busy? "1"/"0" */
               ) .

Filler = Terminator | Sep | " " | "\r" | "\n" | "\t" | "#" | "\x80" .

Terminator = etx .
Sep        = "," .

/* ---- numbers ---- */

Number = [ "-" | "+" ] ( Digits [ "." { Digit } ] | "." Digits ) .
Digits = Digit { Digit } .
Digit  = "0" … "9" .
Coord  = Number .
Int    = Number .
Angle  = Number .

Point    = Coord Sep Coord .
Points   = Point { Sep Point } .
Pen      = Int .  /* pen number, parsed and ignored */
OptSep   = [ Sep ] .

/* ---- commands ---- */

Command = Drawing | LineType | Setup | Query | Tool | Registration
        | Maintenance | NoOp .

Drawing = Draw | Move | DrawRel | MoveRel | Home | Circle | CircleRel
        | Circle3 | Ellipse | Curve | CurveRel | Bezier | Axis .

Draw      = "D" Points OptSep .
Move      = "M" Point OptSep .
DrawRel   = "E" Points OptSep .
MoveRel   = "O" Point OptSep .
Home      = "H" .
Circle    = "W" Point Sep Coord Sep Coord Sep Angle Sep Angle [ Sep Angle ] OptSep .
CircleRel = "]" Coord Sep Coord Sep Angle Sep Angle [ Sep Angle ] OptSep .
Circle3   = "WP" Int Sep Point Sep Point Sep Point [ Sep Angle ] OptSep .
Ellipse   = ")" Int Sep Point Sep Coord Sep Coord Sep Angle Sep Angle Sep Angle
            [ Sep Angle ] OptSep .
Curve     = "Y" Int Sep Points OptSep .
CurveRel  = "_" Int Sep Points OptSep .
Bezier    = "BZ" Int Sep Point Sep Point Sep Point Sep Point [ Sep Int ] OptSep .
Axis      = "X" Int Sep Coord Sep Int [ Sep Coord Sep Coord ] OptSep .

LineType  = UserLine | Line | LineScale .
Line      = "L" Int OptSep .                            /* -9 … 9 */
UserLine  = "L100" Sep Int Sep Coord Sep Coord [ Sep Coord ] OptSep .
LineScale = "B" Coord OptSep .

Setup = LowerLeft | UpperRight | Offset | Factor | Rotate | Clip
      | SetOrigin | Reorigin | GoOrigin .

LowerLeft  = "\\" Point OptSep .
UpperRight = "Z" Point OptSep .
Offset     = "^" Point OptSep .
Factor     = "&" Int Sep Int Sep Int OptSep .
Rotate     = "/" Point Sep Angle OptSep .
Clip       = ">" [ Points ] OptSep .                    /* empty clears */
SetOrigin  = ( "SO" | "NP" ) [ Int ] .
Reorigin   = "FJ" .
GoOrigin   = "FT" .

/* Replies: decimal fields, right-aligned, separated by "," and ended by ETX. */
Query = "G" | "C" | "?" | "[" | "U" | "@" | "FG" | "FA" | "FL" | "FV"
      | "FQ" Int | "TB71" | "TB98" .

Tool = Speed | Force | CutForce | CutterOffset | BladeRotation | Overcut
     | OvercutLength | Touchdown | Orientation | MediaMode | PageLength
     | MediaType | DistanceCorr | TrackEnhance | TrackRoll | ChartFeed
     | Reset .

Speed         = "!" Int [ Sep Pen ] OptSep .            /* 1 … 10 */
Force         = "*" Int Sep Int [ Sep Pen ] OptSep .    /* first ignored, 1 … 3 */
CutForce      = "FX" Int [ Sep Int ] OptSep .           /* 1 … 30 */
CutterOffset  = "FC" Int Sep Int [ Sep Pen ] OptSep .   /* 0 … 45, 1 … 3 */
BladeRotation = "FD" Angle [ Sep Pen ] OptSep .
Overcut       = "FE" Int [ Sep Pen ] OptSep .           /* 0, 1 */
OvercutLength = "FF" Int Sep Int [ Sep Pen ] OptSep .   /* 0 … 9 */
Touchdown     = "FK" [ Int ] OptSep .
Orientation   = "FN" [ Int ] OptSep .                   /* 0 portrait, 1 landscape */
MediaMode     = "FM" Int OptSep .
PageLength    = "FO" [ Coord ] OptSep .
MediaType     = "FW" Int OptSep .                       /* 1 … 13 */
DistanceCorr  = "FB" Int Sep Int OptSep .               /* -200 … 200 */
TrackEnhance  = "FY" Int OptSep .                       /* 0 on, 1 off */
TrackRoll     = "FU" Point OptSep .
ChartFeed     = ( "F" | "FS" ) Coord OptSep .
Reset         = "TT" .

Registration = "TB" ( "0" | "99" | "70"
                    | "50" [ Sep Int ]
                    | "51" Sep Coord
                    | "52" [ Sep Int ]
                    | "53" Sep Coord
                    | "54" [ Sep Coord [ Sep Coord ] ]
                    | "55" Sep Int
                    | "72" Sep Int Sep Int
                    | ( "22" | "23" | "122" | "123" ) [ Sep Point ]
                    ) OptSep .

Maintenance = "FH" | "FI" | Unlock .
Unlock      = "FPGRFCC1" .  /* enables ServiceCommand until reset */

/* Accepted, parameters discarded by the filler rule. */
NoOp = "DP" | "MP" | "EP" | "OP" | "RP" | "P" | "K" | "$" | "S" | "Q" | "R"
     | "I" | "A" | "LP" | "RC" | "N" | "SP" | "SA" | "(" | "(P" | "%" | "T"
     | "J" | "BS" | "\"" | "ZP" | "FR" | "V" | ":" | ";" | "<" | "'" | "=" .

/* Only after Unlock. */
ServiceCommand = "a" … "z" [ Int { Sep Int } ] OptSep .

/* ---- bytes ---- */

esc  = "\x1b" .
etx  = "\x03" .
Byte = "\x00" … "\xff" .
By Dimitri Sokolyuk

Morse Code

image
twopi -Tpng morse.dot > morse.png
graph morse {
    node [shape=circle,fixedsize=true];
    edge [style=dotted];
    Start -- E -- I -- S -- H;
    U -- F;
    A -- R -- L;
    W -- P;
    T -- N -- D -- B;
    K -- C;
    M -- G -- Z;
    O -- Ö;
    edge [style=dashed];
    Start -- T -- M -- O -- Ch;
    G -- Q;
    N -- K -- Y;
    D -- X;
    E -- A -- W -- J;
    R -- Ä;
    I -- U -- Ü;
    S -- V;
}
By Dimitri Sokolyuk

M:Tier Binpatches

Here is my alternative setup for M:Tier binpatches. It differs slightly from offical setup.

I use /etc/pkg.conf instead of defining a $PKG_PATH variable. It looks like:

installpath = http://ftp.spline.de/pub/OpenBSD/%c/packages/%a/
installpath += https://stable.mtier.org/updates/%c/%a

The daily check is performed with /etc/daily.local instead of defining cron entry directly. It also fetches source tree updates.

#!/bin/sh

updatesrc() {
    echo "Updating Source Tree"
    echo
    VERSION=OPENBSD_$(uname -r | tr . _)
    MODULES='src ports' # you may wish to add xenocara too
    CVSROOT=anoncvs@openbsd.cs.fau.de:/cvs
    (cd /usr && cvs -qd ${CVSROOT} up -APCd -r${VERSION} ${MODULES})
    echo
}

updatepkg() {
    echo "Checking package changes"
    echo
    pkg_add -sux
    echo
}

updatesrc
updatepkg

And finaly updating itself is performed as usual with pkg_add -ui.

PS: don't forget to fetch the current siging key:

cd /etc/signify && ftp https://stable.mtier.org/mtier-59-pkg.pub
By Dimitri Sokolyuk

Goodbye CVS

Today I finally shut down my old CVS repo. After nearly 15 years of operation, it's time to move on. Few bites, worth saving for a sake of archeology, are converted and imported into my local playground Git repo. Links are preserved, as far as possible.

CVS was some cumbersome to maintain in a chrooted environment. Related tools weren't updated for years. And since I've switched from C to Go, I've barely used it anymore.

It's time to catch up with the progress. Goodbye CVS.

Conversion process

It was quite straightforward. I've used cvs2svn with a custom options file for it.

The whole process sofar: update few bits in cvs2git.options, as described here (it boils down to committer name mapping and CVSROOT repository location). Then:

cvs2git --options=cvs2git.options
mkdir newrepo && cd newrepo
git init && cat ../newrepo-tmp/git-{blob,dump}.dat | git fast-import
git checkout && git gc
git add origin git@dim13.org:newrepo.git
git push --all origin

This way all your tags and branches stay preserved. Repeat until all your repos are converted.

By Dimitri Sokolyuk

Hello HTTP/2.0

This site is serving HTTP/2.0 now.

By Dimitri Sokolyuk

See the index for more articles.