The USB CNC project ported

My board will be ready soon and this will be the first project for it. Because pinout is different than the one from first version, it needs also a new hardware(shield), not only the firmware. So, it needed a separate html page.

Board compatibility

Schematic: Compatible with all FreeJALduino boards.

Firmware: Compatible with FreeJALduino and FreeJALduino5 boards

The stylesheet

This will be in top of every page using this board, as a reminder and easy access.

The schematic

The firmware

-- Project: XYZ CNC Router on USB using FreeJALduino board
-- Author: Vasile Guta-Ciucur (funlw65)
-- License: Code released under New BSD license
-- For jallib libraries, see de licence inside jal.zip
-- This is first variant, where steppers are connected to the board.
-- No gcode interpreter inside, sorry - this is left for PC Host App.
include freejalduino4 -- FreeJALduino4 pinout layer for the last board model
-- include libraries
include usb_serial
include print
-- include format
include delay
-- setup()
-- var and const definitions
const byte str1[] = "XYZCNC-USB Firmware Nov17, 2009\r\n"
const byte str2[] = "Copyright 2009 by Vasile Guta-Ciucur.\r\n"
const byte hsteps[] = { -- half-steps buffer
  0b_0000_0001,
  0b_0000_0011,
  0b_0000_0010,
  0b_0000_0110,
  0b_0000_0100,
  0b_0000_1100,
  0b_0000_1000,
  0b_0000_1001 }
const byte fsteps[] = { -- full-steps buffer, high torque
  0b_0000_0101,
  0b_0000_0110,
  0b_0000_1010,
  0b_0000_1001,
  0b_0000_0101,
  0b_0000_0110,
  0b_0000_1010,
  0b_0000_1001 }
var byte csteps[ 8 ] -- this array will contain settings for
                   -- half or full seps, according to "step type" jumper.
var byte speed_s[ 36 ] -- string for speed setup
var byte movement_s[ 36 ] -- string for xyz movement
var byte speed_str[ 11 ] -- buffer for speed value
var sbyte memx, memy, memz -- current step for x,y,z axis
var byte i, j -- used as index for cycles
var dword step_speed -- speed per step in microseconds
var byte t_delay -- the type of delay (micro, milli, nano)
-- end var and const definitions
-- procedures and functions
-- convert string to dword
function strtodec(byte in str[ 11 ]) return dword is
  var dword tempres
  var byte i
  --
  tempres = 0
  i = 0
  repeat
    tempres = tempres * 10 + (str[i] - "0")
    i = i + 1
  until str[i] == 0
  return tempres
end function
-- process setup string and set the speed
procedure process_and_set is
  i = 0
  t_delay = speed_s[i]
  i = 1
  repeat
    speed_str[i - 1] = speed_s[i]
    i = i + 1
  until speed_s[i] == "/"
  speed_str[i - 1] = 0
  step_speed = strtodec(speed_str)
  usb_serial_flush()
end procedure
-- check the limits or stop pressed
procedure emerge is
  if (D11 == LOW) then -- if CRASH or STOP or PAUSE
    D12 = LOW -- power off steppers
    D18 = HIGH -- power on CRASH LED indicator
    usb_serial_data = "W" -- send warning char "W"
    repeat -- repeat ...
      usb_serial_flush() -- (we must keep USB conn. active)
    until D0 == LOW -- ... till "Resume" switch is pressed
             -- or board is reset because you have a limit reached
    D18 = LOW -- switch off CRASH LED
    D12 = HIGH -- power on steppers
    usb_serial_data = "R" -- send "Resume" char "R"
    usb_serial_flush()
  end if
end procedure
-- end check the limits or stop pressed
-- move one step on xyz axis, both directions, and then,
-- check if limit reached or stop pressed
procedure x_step_up is
  memx = memx + 1
  if memx > 7 then
    memx = 0
  end if
  PORTA_low = csteps[memx]
  emerge()
end procedure
procedure x_step_down is
  memx = memx - 1
  if memx < 0 then
    memx = 7
  end if
  PORTA_low = csteps[memx]
  emerge()
end procedure
procedure y_step_up is
  memy = memy + 1
  if memy > 7 then
    memy = 0
  end if
  PORTB_high = csteps[memy]
  emerge()
end procedure
procedure y_step_down is
  memy = memy - 1
  if memy < 0 then
    memy = 7
  end if
  PORTB_high = csteps[memy]
  emerge()
end procedure
procedure z_step_up is
  memz = memz + 1
  if memz > 7 then
    memz = 0
  end if
  PORTB_low = csteps[memz]
  emerge()
end procedure
procedure z_step_down is
  memz = memz - 1
  if memz < 0 then
    memz = 7
  end if
  PORTB_low = csteps[memz]
  emerge()
end procedure
-- end one step on xyz axis, both directions
-- process movement string and move axis
procedure process_and_run is
  var bit dirx, diry, dirz -- direction on x,y,z axis (0,1)
  var byte stage
-- containers for the number of steps as string, null terminated
-- remember me to make routines to clean these arrays - DONE!
  var byte x_step_str[ 11 ]
  var byte y_step_str[ 11 ]
  var byte z_step_str[ 11 ]
  var dword m, k, x_steps, y_steps, z_steps -- the number of steps on xyz axis
  usb_serial_flush()
-- initialize the number of steps on xyz axis
  x_steps = 0
  y_steps = 0
  z_steps = 0
  -- preparing the strings
  -- for 11 using i loop
  --   x_step_str[i] = 0
  --   y_step_str[i] = 0
  --   z_step_str[i] = 0
  -- end loop
  i = 0
  j = 0
  stage = 1
  -- extract the numbers and direction for each axis
  repeat
    if stage == 1 then
      repeat
        if movement_s[i] == "X" then
          dirx = 1
        elsif movement_s[i] == "x" then
          dirx = 0
        else
          x_step_str[j] = movement_s[i]
          j = j + 1
        end if
        i = i + 1
      until ((movement_s[i] == "Y") | (movement_s[i] == "y"))
      stage = 2
      x_step_str[j + 1] = 0
      j = 0
    end if
    if stage == 2 then
      repeat
        if movement_s[i] == "Y" then
          diry = 1
        elsif movement_s[i] == "y" then
          diry = 0
        else
          y_step_str[j] = movement_s[i]
          j = j + 1
        end if
        i = i + 1
      until ((movement_s[i] == "Z") | (movement_s[i] == "z"))
      stage = 3
      y_step_str[j + 1] = 0
      j = 0
    end if
    if stage == 3 then
      repeat
        if movement_s[i] == "Z" then
          dirz = 1
        elsif movement_s[i] == "z" then
          dirz = 0
        else
          z_step_str[j] = movement_s[i]
          j = j + 1
        end if
        i = i + 1
      until movement_s[i] == "/"
      z_step_str[j + 1] = 0
      j = 0
    end if
  until movement_s[i] == "/"
  usb_serial_flush()
  -- Next:
  -- convert x dword
  x_steps = strtodec(x_step_str)
  -- convert y dword
  y_steps = strtodec(y_step_str)
  -- convert z dword
  z_steps = strtodec(z_step_str)
  -- see which one is bigger and that will lead the for cycle
  if (x_steps > y_steps) then
    if (x_steps > z_steps) then
      k = x_steps
    else
      k = z_steps
    end if
  else
    if (y_steps > z_steps) then
      k = y_steps
    else
      k = z_steps
    end if
  end if
  -- DO THE STEPS
  for  k using m loop
    --
    if (m + 1) <= x_steps then
      if dirx == 1 then
        x_step_up()
      else
        x_step_down()
      end if
    end if
    if (m + 1) <= y_steps then
      if diry == 1 then
        y_step_up()
      else
        y_step_down()
      end if
    end if
    if (m + 1) <= z_steps then
      if dirz == 1 then
        z_step_up()
      else
        z_step_down()
      end if
    end if
    -- here goes the desired delay between steps
    if t_delay == "U" then
      for step_speed loop
        delay_1us()
      end loop
    else
      delay_1ms(step_speed)
    end if
    usb_serial_flush()
  end loop
end procedure -- end of the best procedure
-- hope you like it :-D
-- end procedures and functions
-- ==================================================
-- PROGRAM START ------------------------------------
-- ==================================================
-- configure pins
enable_digital_io() -- first, all pins set to digital
-- Then, setting direction (input/output)
-- We should do something like this:
-- PORTA_direction = OUTPUT
-- PORTB_direction = OUTPUT
-- but we do it Arduino style for newbies
D0_direction = INPUT  -- RX, used for "resume" switch
D1_direction = OUTPUT -- TX, "CNC on operation" LED indicator
D2_direction = OUTPUT -- Boot LED indicator
--
D3_direction = OUTPUT -- L1 - z axis - this is PORTB_low
D4_direction = OUTPUT -- L2 - z axis
D5_direction = OUTPUT -- L3 - z axis
D6_direction = OUTPUT -- L4 - z axis
--
D7_direction = OUTPUT  -- L1 - y axis - this is PORTB_high
D8_direction = OUTPUT  -- L2 - y axis
D9_direction = OUTPUT  -- L3 - y axis
D10_direction = OUTPUT -- L4 - y axis
--
D11_direction = INPUT  -- xyz limits and emergency stop
D12_direction = OUTPUT -- Steppers power on/off relay
D13_direction = INPUT  -- (full/half step) jumper
--
D14_direction = OUTPUT -- L1 - x axis - this is PORTA_low
D15_direction = OUTPUT -- L2 - x axis
D16_direction = OUTPUT -- L3 - x axis
D17_direction = OUTPUT -- L4 - x axis
--
D18_direction = OUTPUT -- CRASH LED (red) - emergency stop or limit reached
-- equivalent to :
-- pinMode(D19_direction, OUTPUT)
-- assure that steppers are not powered
-- a good thing is to have a second switch in series
D12 = LOW 
-- and that "Breakdown" red LED is off
D18 = LOW
-- Operation LED off
D1  = LOW
-- default values
-- initialize index for step buffer
memx = 0
memy = 0
memz = 0
t_delay = "M" -- the type of delay (milliseconds in this case)
step_speed = 10 --  milliseconds
-- read the step style jumper and
-- set step array values accordingly
if D13 == 1 then
  for 8 using i loop
    csteps[i] = fsteps[i]
  end loop
else
  for 8 using i loop
    csteps[i] = hsteps[i]
  end loop
end if
-- "Load" the steper coils
PORTA_low  = csteps[memx]
PORTB_low  = csteps[memz]
PORTB_high = csteps[memy]
-- power the steppers
D12 = HIGH
-- initialize the USB serial library
usb_serial_init()
-- optionally wait till USB becomes available
--while ( usb_cdc_line_status() ==  0x00 )  loop
--end loop
-- Albert, this one is not working :( , I disabled it...
-- Just to be sure (bootloader switch off this LED anyway)
D2 = LOW
-- end setup()
-- main loop
forever loop
  var byte ch
  -- Service USB, call on a regular base to keep communcaiton going
  usb_serial_flush()
  -- check for input character
  if usb_serial_read( ch ) then
    if ch == "?" then
      print_string( usb_serial_data, str1 )
      print_string( usb_serial_data, str2 )
    elsif ch == "A" then -- X axis
      x_step_up()
    elsif ch == "a" then
      x_step_down()
    elsif ch == "S" then -- Y axis
      y_step_up()
    elsif ch == "s" then
      y_step_down()
    elsif ch == "D" then -- z axis
      z_step_up()
    elsif ch == "d" then
      z_step_down()
    elsif ch == "<" then -- a setup string is receiving
      j = 0
      D1 = HIGH -- light the operation LED
      repeat
        usb_serial_flush()
        if usb_serial_read( ch ) then
          if ch != "/" then
            speed_s[j] = ch
            j = j + 1
            if j == 35 then -- protection against malformed string
              ch = "/"
            end if
          end if
          speed_s[j] = "/"
        end if
      until ch == "/"
      -- go to evaluate string and set the speed
      process_and_set()
      D1 = LOW -- switch off the operation LED
      usb_serial_data = "O" -- send "OK" to PC
      usb_serial_flush()
    elsif ch == ">" then -- a movement string is receiving
      j = 0
      D1 = HIGH -- light the operation LED
      repeat
        usb_serial_flush()
        if usb_serial_read( ch ) then
          if ch != "/" then
            movement_s[j] = ch
            j = j + 1
            if j == 35 then -- protection against malformed string
              ch = "/"
            end if
          end if
          movement_s[j] = ch
        end if
      until ch == "/"
      -- go to evaluate string and start movement
      process_and_run()
      D1 = LOW -- switch off the operation LED
      usb_serial_data = "O" -- send "OK" to PC
      usb_serial_flush()
    else
      usb_serial_flush()
    end if
  end if
  -- we check limits or emergency stop
  emerge()
end loop
--

How the firmware works

The firmware is very basic but hopefully, fast enough (can be considered a basic but efficient engine). If this firmware is used with my FreeJALduino board, at start, the bootloader will wait for 10 seconds and then will start the application. Until then, no lighted LED's. At start, the steppers will be powered and a blue LED will signal that the board is ready to receive data from USB. A default speed of 10 milliseconds delay per step (useful for testings) will be considered. Firmware will check the jumper (the jumper from breadboard or shield and not the one from FreeJALduino board) and will choose between full step and half step.

The firmware have two levels at listening for characters on USB Serial interface.

1. - First level. At this level you can send characters for manually moving axis step by step on both directions, allowing fine (manual) positioning. The characters are:

  • A - move one step on X axis in right direction;
  • a - move one step on X axis in left direction;
  • S - move one step on Y axis in right direction;
  • s - move one step on Y axis in right direction;
  • D - move one step on Z axis in right direction (or up);
  • d - move one step on Z axis in left direction (or down);
  • < - determine firmware to enter on the second level for receiving speed data as a string;
  • > - determine firmware to enter in the second level for receiving movement data as a string.
  • At the end of a complete loop, firmware is testing to see if limit was reached on one of the axis or if STOP button was pressed. Then, the power for steppers will be cut and a red LED will signal the fault. CNC will stay on pause until RESUME button is pressed (in case you stopped the CNC for a break) or, in case a limit was reached, you must reset the board because your CNC setup is wrong.

2. - Second level. At this level, firmware is waiting for an entire string terminated with forward slash character. Then will proceed with processing the string and setting the movement speed or start movement. A LED will be lighted for signaling that the firmware is in the second level. So, this level have two modes, speed setup mode and movement mode:

-- Speed setup mode, determined by "<" char from the first level. You can set de delay between steps starting from 10 microseconds. Here I must give some detailed explanations. The firmware is made in JAL language and here I found two delay procedures which can be used: delay_10us(byte) and delay_1ms(word).

For delay_10us(byte) parameter is byte and means can be up to 255 and is incremented by 10. So you will have this formula: delay = n * 10 microseconds - where n is your byte parameter. If microseconds are required, the setup string will start with "U" character. Example:

U12/ - mean 120 microseconds delay between steps

(around 8000 rpm?)

For delay_1ms(word) parameter is word and means can be up to 65535 (no useful to have that maximum delay :-P) and is incremented by 1 millisecond, no weird formula needed, what you get is what you give. So, if milliseconds are required, then the first character from string must be "M". Example:

M10/ - means 10 milliseconds delay between steps

(see the forward slash that will end the string ).

Maybe I will make my own delay_us(word) procedure with a normal increment by 1 microsecond ... The speed setup will remain until you will give another speed setup. After speed is set, the firmware will return at the first level of listening.

-- Movement mode, determined by ">" char from the first level. Here is simple. Examples:

X40Y0Z0/ - means 40 steps on X axis at right direction

X0y40Z0/ - means 40 steps on Y axis at left direction

X0Y0Z40/ - means 40 steps on Z axis at right (up) direction

X40Y40Z0/

In this case it means 40 steps on X axis and 40 steps on Y axis,

drawing a straight line at 45 degrees (only if you have same setup on both axis,

else use it only for movement and not for routing). The movement is one step

on X axis and one step on Y axis at a time.

Of course you can move all axis at once if is needed. Again, the maximum number

of steps which you can give is a longword or doubleword which mean

4,294,967,295.

Of course is huge and at 10 microseconds delay between steps, it will last

more than a year to finish the movement but a maximum of 65535 steps at once was

not enough.

The type of char (uppercase or not) will determine the

direction on that axis (e.g., X for right and x for left).

At every step, the limit reached or pause is tested. After the operation is finished, the firmware will return in the first level of listening.

Why such a simple firmware?

Well, it will be never obsoleted because it contains no g-code (which must be upgraded or worse, corrected), no CNC setups. All g-code translation and the required interpolations are made by the PC host application. So, you can use the firmware without bootloader if you want (but not the .hex file from attachments, which require bootloader).

Building it...

....

Notes about attachments

....