Wayback Machine
Dec Jan SEP
Previous capture 8 Next capture
2011 2012 2013
3 captures
8 Jan 12 - 1 May 13
sparklines
Close Help

Cricut Commands

From Cricut Hacking Wiki

Jump to: navigation, search

The messages passed between the Cricut and the PC on the serial connection is pretty simple. Each message starts with a single byte indicating the number of bytes in the packet, followed by a command # if it's a command, followed by data. These commands seem to form the basic command set:

  • Command 0x21 (4 bytes) - Start transaction, no reply
  • Command 0x22 (4 bytes) - Stop transaction (causes pen to lift if last move command was a cut), no reply
  • Command 0x14 (4 bytes) - Status request, 4 byte reply, last byte indicates if mat is loaded
  • Command 0x12 (4 bytes) - Firmware version request, 6 byte reply, last 4 bytes seem to be firmware version (v1.34 = 0x0001 0x0022), first 2 bytes may be model index
  • Command 0x18 (4 bytes) - Cartridge name request, 38 byte reply includes cartridge name as ASCII, may also include cartridge version information; first two-byte word is 0x0001 if cartridge is present, else 0x0000
  • Command 0x11 (4 bytes) - Mat bounds request, 8 byte reply, four two-byte words corresponding to x_min, y_min, x_max, and y_max, respectively
  • Command 0x81 (4 bytes) - Unsure, 4 byte reply, second byte is 0x28 if no cartridge is inserted, 0x00 if "George and Basic Shapes", and 0x20 otherwise (?)
  • Command 0x40 (13 bytes) - Move/cut command, 4 byte reply, first 2 bytes of reply always seem to be 0x00

A single command 0x40 can trigger a move or a cut (move with blade down) and groups of 4 of these commands strung together can trigger a simple curve cut (I'm guessing are 4 points that define a bezier curve at this point). All move/cut commands seem to use absolute positioning, as you can position the cut head anywhere on the mat and it will always return to the same position when issuing the same command (the simple move commands at least). The data of command 0x40 looks like it's encrypted using XXTEA or similar encryption.

See the Command 0x40 Protocol page for additional information on command 0x40 encoding.

Data examples

Some examples of actual bytes sent and received. All values are in hex.

Cricut Cake - these are the commands I've tested so far. Mat coordinates are returned as big-endian integers, perhaps unsigned. Here I got xmin=316, ymin=50, xmax=4962, ymax=4696:

Send: 04 14 00 00 00 00
Recv: 04 00 01 00 01

Send: 04 12 00 00 00 00
Recv: 06 00 14 00 02 00 22

Send: 04 11 00 00 00 00
Recv: 08 01 3c 00 32 13 62 12 58

Send: 04 18 00 00 00 00
Recv: 38 00 01 00 21 43 72 69 63 75 74 28 52 29 20 43 
      61 6b 65 20 42 61 73 69 63 73 00 00 00 00 00 00 
      00 00 00 00 00 00 23 
      !Cricut(R) Cake Basics

Code examples

Current alpha version of Linux sources on SourceForge at http://licut.sourceforge.net

This Linux-specific code snippet opens /dev/ttyUSB0 and sets the rate to 200kbps and returns a handle for use by read() and write():

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <termios.h>
#include <linux/serial.h>

int open_port_200kbps()
{
	int handle = open( "/dev/ttyUSB0", O_RDWR | O_NOCTTY );
	if (handle <= 0)
	{
		printf( "Failed to open %s - %d (%s)\n", "/dev/ttyUSB0", errno, strerror(errno) );
		return -1;
	}

        struct termios oldtio,newtio;
        
        tcgetattr( handle, &oldtio ); /* save current port settings */
    
        bzero( &newtio, sizeof(newtio) );
	// Set custom rate to 200kbps 8N1 - we're actually sending 8N2 but get 8N1 back
        newtio.c_cflag = B38400 | /*CRTSCTS |*/ CS8 | CLOCAL | CREAD;
        newtio.c_iflag = IGNPAR;
        newtio.c_oflag = 0;
        
        /* set input mode (non-canonical, no echo,...) */
        newtio.c_lflag = 0;
         
        newtio.c_cc[VTIME]    = 0;   /* inter-character timer unused */
        newtio.c_cc[VMIN]     = 5;   /* blocking read until 5 chars received */
        
        tcflush( handle, TCIFLUSH );
        tcsetattr( handle, TCSANOW, &newtio );

	// Now use TIOCSSERIAL ioctl to set custom divisor
	// FTDI uses base_baud 24000000 so in theory a divisor
	// of 120 should give us 200000 baud...
	struct serial_struct sio; // From /usr/include/linux/serial.h
	int ioctl_res = ioctl( handle, TIOCGSERIAL, &sio );
	if (ioctl_res < 0)
	{
		printf( "Failed TIOCGSERIAL ioctl: error %d (%s)\n", errno, strerror(errno) );
		close( handle );
		return -1;
	}

	sio.flags = ((sio.flags & ~ASYNC_SPD_MASK) | ASYNC_SPD_CUST);
	sio.custom_divisor = sio.baud_base / 200000;
	ioctl_res = ioctl( handle, TIOCSSERIAL, &sio );
	if (ioctl_res < 0)
	{
		printf( "Failed TIOCSSERIAL ioctl: error %d (%s)\n", errno, strerror(errno) );
		close( handle );
		return -1;
	}
        return handle;
}

lio.Send is a simple function that adds an intercharacter delay of 1ms (running on Ubuntu 9.04):

int LicutIO::Send(  const unsigned char *bytes, int length )
{
	int n;
	int actual_sent = 0;
	for (n = 0; n < length; n++)
	{
		int write_res = write( m_handle, &bytes[n], 1 );
		if (write_res < 1)
		{
			printf( "%s(%p,%u) - write returned %d, errno=%d (%s)\n", __FUNCTION__, bytes, length, write_res, errno, strerror(errno) );
		}
		else
		{
			actual_sent++;
		}
		// Add intercharacter delay after each character, including the last
		usleep( 1000 );
	}
	return actual_sent;
}

After reading the results of each status command, I'm forcing an additional 250ms timeout.