- More thorough shutdown of stepper driver in estop.
- Fixed spindle type specific options.
- No more Unexpected AVR firmware reboot errors on estop clear.
- Downgraded Machine alarmed - Command not processed errors to warnings.
- Suppress unnecessary axis homing warnings.
- More details for axis homing errors.a
- Support GCode messages e.g. (MSG, Hello World!)
- Support programmed pauses. i.e. M0
## v0.3.12
- Updated DB25 M2 breakout diagram.
- Enabled AVR watchdog.
+ - Fixed problem with selecting newly uploaded file.
+ - More thorough shutdown of stepper driver in estop.
+ - Fixed spindle type specific options.
+ - No more ``Unexpected AVR firmware reboot`` errors on estop clear.
+ - Downgraded ``Machine alarmed - Command not processed`` errors to warnings.
+ - Suppress unnecessary axis homing warnings.
+ - More details for axis homing errors.
+ - Support GCode messages e.g. (MSG, Hello World!)
+ - Support programmed pauses. i.e. M0
## v0.3.11
- Supressed ``firmware rebooted`` warning.
// Reporting
report_request();
- if (status && status != STAT_NOP) STATUS_ERROR(status, "");
+
+ switch (status) {
+ case STAT_OK: break;
+ case STAT_NOP: break;
+ case STAT_MACHINE_ALARMED: STATUS_WARNING(status, ""); break;
+ default: STATUS_ERROR(status, ""); break;
+ }
return true;
}
CMD('a', set_axis, 1, "[axis][position] Set axis position")
CMD('l', line, 1, "[targetVel][maxJerk][axes][times]")
CMD('d', dwell, 1, "[seconds]")
-CMD('o', out, 1, "Output")
-CMD('p', opt_pause, 1, "Set an optional pause")
-CMD('P', pause, 0, "[optional]")
+CMD('P', pause, 1, "[type] Pause control")
CMD('U', unpause, 0, "Unpause")
CMD('j', jog, 0, "[axes]")
CMD('r', report, 0, "<0|1>[var] Enable or disable var reporting")
\******************************************************************************/
#include "config.h"
-#include "rtc.h"
#include "stepper.h"
#include "command.h"
#include "vars.h"
#include "base64.h"
#include "hardware.h"
#include "report.h"
-#include "state.h"
#include "exec.h"
-#include "util.h"
-#include <string.h>
#include <stdio.h>
}
-// TODO
-stat_t command_out(char *cmd) {return STAT_OK;}
-unsigned command_out_size() {return 0;}
-void command_out_exec(void *data) {}
-
-
stat_t command_help(char *cmd) {
printf_P(PSTR("\n{\"commands\":{"));
command_print_json();
}
+void drv8711_shutdown() {
+ SPIC.INTCTRL = 0; // Disable SPI interrupts
+}
+
+
drv8711_state_t drv8711_get_state(int driver) {
if (driver < 0 || DRIVERS <= driver) return DRV8711_DISABLED;
return drivers[driver].state;
void drv8711_init();
+void drv8711_shutdown();
drv8711_state_t drv8711_get_state(int driver);
void drv8711_set_state(int driver, drv8711_state_t state);
void drv8711_set_microsteps(int driver, uint16_t msteps);
float accel;
float jerk;
- int tool;
-
float feed_override;
float spindle_override;
} ex;
// Variable callbacks
-uint8_t get_tool() {return ex.tool;}
+float get_axis_position(int axis) {return ex.position[axis];}
float get_velocity() {return ex.velocity / VELOCITY_MULTIPLIER;}
float get_acceleration() {return ex.accel / ACCEL_MULTIPLIER;}
float get_jerk() {return ex.jerk / JERK_MULTIPLIER;}
float get_feed_override() {return ex.feed_override;}
-float get_speed_override() {return ex.spindle_override;}
-float get_axis_position(int axis) {return ex.position[axis];}
-
-void set_tool(uint8_t tool) {ex.tool = tool;}
void set_feed_override(float value) {ex.feed_override = value;}
+float get_speed_override() {return ex.spindle_override;}
void set_speed_override(float value) {ex.spindle_override = value;}
// Report
report_request();
}
-
-
-stat_t command_opt_pause(char *cmd) {command_push(*cmd, 0); return STAT_OK;}
-unsigned command_opt_pause_size() {return 0;}
-void command_opt_pause_exec(void *data) {} // TODO pause if requested
ha.response[ha.response_length - 2];
if (computed != expected) {
- STATUS_WARNING("huanyang: invalid CRC, expected=0x%04u got=0x%04u",
+ STATUS_WARNING(STAT_OK, "huanyang: invalid CRC, expected=0x%04u got=0x%04u",
expected, computed);
return false;
}
// Check if response code matches the code we sent
if (ha.command[1] != ha.response[1]) {
- STATUS_WARNING("huanyang: invalid function code, expected=%u got=%u",
+ STATUS_WARNING(STAT_OK,
+ "huanyang: invalid function code, expected=%u got=%u",
ha.command[2], ha.response[2]);
return false;
}
exec_set_jerk(0);
_done();
+ // TODO it's possible to exit here and have no more moves
+ // Apparently this pause method can take longer to pause than the
+ // actual move. FIX ME!!!
+
return STAT_AGAIN;
}
switch (reason) {
case HOLD_REASON_USER_PAUSE: return PSTR("User paused");
case HOLD_REASON_PROGRAM_PAUSE: return PSTR("Program paused");
- case HOLD_REASON_PROGRAM_END: return PSTR("Program end");
- case HOLD_REASON_PALLET_CHANGE: return PSTR("Pallet change");
- case HOLD_REASON_TOOL_CHANGE: return PSTR("Tool change");
case HOLD_REASON_STEPPING: return PSTR("Stepping");
case HOLD_REASON_SEEK: return PSTR("Switch found");
}
void state_holding() {_set_state(STATE_HOLDING);}
-void state_optional_pause() {
- if (s.optional_pause_requested) {
- _set_hold_reason(HOLD_REASON_USER_PAUSE);
+void state_pause(bool optional) {
+ if (!optional || s.optional_pause_requested) {
+ _set_hold_reason(HOLD_REASON_PROGRAM_PAUSE);
state_holding();
}
}
// Command callbacks
stat_t command_pause(char *cmd) {
- if (cmd[1] == '1') s.optional_pause_requested = true;
- else s.pause_requested = true;
+ pause_t type = (pause_t)(cmd[1] - '0');
+
+ switch (type) {
+ case PAUSE_USER: s.pause_requested = true; break;
+ case PAUSE_OPTIONAL: s.optional_pause_requested = true; break;
+ default: command_push(cmd[0], &type); break;
+ }
+
return STAT_OK;
}
+unsigned command_pause_size() {return sizeof(pause_t);}
+
+
+void command_pause_exec(void *data) {
+ switch (*(pause_t *)data) {
+ case PAUSE_PROGRAM: state_pause(false); break;
+ case PAUSE_PROGRAM_OPTIONAL: state_pause(true); break;
+ default: break;
+ }
+}
+
+
stat_t command_unpause(char *cmd) {
s.start_requested = true;
return STAT_OK;
typedef enum {
HOLD_REASON_USER_PAUSE,
HOLD_REASON_PROGRAM_PAUSE,
- HOLD_REASON_PROGRAM_END,
- HOLD_REASON_PALLET_CHANGE,
- HOLD_REASON_TOOL_CHANGE,
HOLD_REASON_STEPPING,
HOLD_REASON_SEEK,
} hold_reason_t;
+typedef enum {
+ PAUSE_USER,
+ PAUSE_OPTIONAL,
+ PAUSE_PROGRAM,
+ PAUSE_PROGRAM_OPTIONAL,
+} pause_t;
+
+
PGM_P state_get_pgmstr(state_t state);
PGM_P state_get_hold_reason_pgmstr(hold_reason_t reason);
void state_seek_hold();
void state_holding();
-void state_optional_pause();
void state_running();
void state_jogging();
void state_idle();
#define STATUS_DEBUG(MSG, ...) \
STATUS_MESSAGE(STAT_LEVEL_DEBUG, STAT_OK, MSG, ##__VA_ARGS__)
-#define STATUS_WARNING(MSG, ...) \
- STATUS_MESSAGE(STAT_LEVEL_WARNING, STAT_OK, MSG, ##__VA_ARGS__)
+#define STATUS_WARNING(CODE, MSG, ...) \
+ STATUS_MESSAGE(STAT_LEVEL_WARNING, CODE, MSG, ##__VA_ARGS__)
#define STATUS_ERROR(CODE, MSG, ...) \
STATUS_MESSAGE(STAT_LEVEL_ERROR, CODE, MSG, ##__VA_ARGS__)
#include "cpp_magic.h"
#include "report.h"
#include "exec.h"
+#include "drv8711.h"
#include <string.h>
#include <stdio.h>
}
+static void _end_move() {
+ for (int motor = 0; motor < MOTORS; motor++)
+ motor_end_move(motor);
+}
+
+
void st_shutdown() {
- OUTCLR_PIN(MOTOR_ENABLE_PIN);
- st.dwell = 0;
- st.move_type = MOVE_TYPE_NULL;
+ OUTCLR_PIN(MOTOR_ENABLE_PIN); // Disable motors
+ TIMER_STEP.CTRLA = 0; // Stop stepper clock
+ _end_move(); // Stop motor clocks
+ ADCB_CH0_INTCTRL = 0; // Disable next move interrupt
+ drv8711_shutdown(); // Disable drivers
}
}
-static void _end_move() {
- for (int motor = 0; motor < MOTORS; motor++)
- motor_end_move(motor);
-}
-
-
/// Dwell or dequeue and load next move.
static void _load_move() {
- // Check EStop
- if (estop_triggered()) {
- st.move_type = MOVE_TYPE_NULL;
- _end_move();
- return;
- }
-
// New clock period
TIMER_STEP.PER = st.clock_period;
int16_t usart_rx_fill() {return rx_buf_fill();}
int16_t usart_tx_space() {return tx_buf_space();}
int16_t usart_tx_fill() {return tx_buf_fill();}
-
-
-// Var callbacks
-bool get_echo() {return usart_is_set(USART_ECHO);}
-void set_echo(bool value) {return usart_set(USART_ECHO, value);}
// Machine state
VAR(id, id, u32, 0, 1, 1, "Last executed command ID")
VAR(speed, s, f32, 0, 1, 1, "Current spindle speed")
-VAR(tool, t, u8, 0, 1, 1, "Current tool")
VAR(feed_override, fo, f32, 0, 1, 1, "Feed rate override")
VAR(speed_override, so, f32, 0, 1, 1, "Spindle speed override")
VAR(acceleration, ax, f32, 0, 0, 1, "Current acceleration")
VAR(jerk, j, f32, 0, 0, 1, "Current jerk")
VAR(hw_id, hid, str, 0, 0, 1, "Hardware ID")
-VAR(echo, ec, bool, 0, 1, 1, "Enable or disable echo")
VAR(estop, es, bool, 0, 1, 1, "Emergency stop")
VAR(estop_reason, er, pstr, 0, 0, 1, "Emergency stop reason")
VAR(state, xx, pstr, 0, 0, 1, "Machine state")
h3(slot="header") Firmware upgrading
p(slot="body") Please wait...
+ message(:show.sync="showMessages")
+ h3(slot="header") GCode message
+
+ div(slot="body")
+ ul
+ li(v-for="msg in messages") {{msg}}
+
+ div(slot="footer")
+ button.pure-button.button-success(@click="close_messages") OK
+
#templates
include ../../build/templates.jade
checkedUpgrade: false,
firmwareName: '',
latestVersion: '',
- password: ''
+ password: '',
+ showMessages: false
}
},
this.sock.onmessage = function (e) {
var msg = e.data;
- if (typeof msg == 'object')
+ if (typeof msg == 'object') {
for (var key in msg) {
- if (key == 'msg') this.$broadcast('message', msg.msg);
+ if (key == 'log') this.$broadcast('log', msg.log);
+ else if (key == 'message') this.add_message(msg.message);
else Vue.set(this.state, key, msg[key]);
}
+ }
}.bind(this)
this.sock.onopen = function (e) {
}.bind(this)).fail(function (error) {
alert('Save failed: ' + error);
});
+ },
+
+
+ add_message: function (msg) {
+ this.messages.unshift(msg);
+ this.showMessages = true;
+ },
+
+
+ close_messages: function () {
+ this.showMessages = false;
+ this.messages.splice(0, this.messages.length);
}
}
})
events: {
- message: function (msg) {
+ log: function (msg) {
// There may be multiple instances of this module so ignore messages
// that have already been processed.
if (msg.logged) return;
update: function () {
// Update file list
- api.get('file').done(function (files) {this.files = files}.bind(this))
+ api.get('file').done(function (files) {
+ this.files = files;
+ this.load();
+ }.bind(this))
},
load: function () {
var file = this.state.selected;
- if (file == this.last_file) return;
+ if (typeof file == 'undefined' || file == this.last_file) return;
api.get('file/' + file)
.done(function (data) {
SET_AXIS = 'a'
LINE = 'l'
DWELL = 'd'
-OUTPUT = 'o'
OPT_PAUSE = 'p'
PAUSE = 'P'
UNPAUSE = 'U'
return cmd
-def tool(tool): return '#t=%d' % tool
def speed(speed): return '#s=:' + encode_float(speed)
def dwell(seconds): return 'd' + encode_float(seconds)
-def pause(optional = False): 'P' + ('1' if optional else '0')
+
+
+def pause(type):
+ if type == 'program': type = 2
+ elif type == 'optional': type = 3
+ elif type == 'pallet-change': type = 2
+ else: raise Exception('Unknown pause type "%s"' % type)
+
+ return 'P%d' % type
+
+
def jog(axes): return 'j' + encode_axes(axes)
log.warning('Serial handler error: %s', traceback.format_exc())
+ def estop(self):
+ if self.ctrl.state.get('xx', '') != 'ESTOPPED':
+ self.i2c_command(Cmd.ESTOP)
+
+
+ def clear(self):
+ if self.ctrl.state.get('xx', '') == 'ESTOPPED':
+ self.i2c_command(Cmd.CLEAR)
+ self.reboot_expected = True
+
+
+ def pause(self, optional = False):
+ data = ord('1' if optional else '0')
+ self.i2c_command(Cmd.PAUSE, byte = data)
+
+
def reboot(self):
self.queue_command(Cmd.REBOOT)
self.reboot_expected = True
{"axis": "Y"},
{"axis": "Z"},
{"axis": "A", "power-mode" : "disabled"},
- ],
- "switches": {},
- "outputs": {},
- "tool": {},
- "gcode": {},
- "planner": {},
- "admin": {},
+ ]
}
encoding = 'utf-8') as f:
self.template = json.load(f)
+ # Add all sections from template to default config
+ for section in self.template:
+ if not section in default_config:
+ default_config[section] = {}
+
except Exception as e: log.exception(e)
with open(path, 'wb') as f:
f.write(gcode['body'])
+ self.ctrl.state.set('selected', gcode['filename'])
+
def get(self, path):
if path:
path = path[1:]
- self.ctrl.mach.select(path)
with open('upload/' + path, 'r') as f:
self.write_json(f.read())
+
+ self.ctrl.mach.select(path)
return
files = []
# Handle EStop
if 'xx' in update and state == 'ESTOPPED':
self._stop_sending_gcode()
+ self.stopping = False
# Handle stop
- if self.stopping and 'xx' in update and state == 'HOLDING':
- self._stop_sending_gcode()
- # Resume once current queue of GCode commands has flushed
- self.comm.i2c_command(Cmd.FLUSH)
- self.comm.queue_command(Cmd.RESUME)
- self.ctrl.state.set('line', 0)
- self.stopping = False
+ if self.stopping:
+ if state == 'READY' and not self.planner.is_running():
+ self.stopping = False
+
+ if state == 'HOLDING':
+ self._stop_sending_gcode()
+ # Resume once current queue of GCode commands has flushed
+ self.comm.i2c_command(Cmd.FLUSH)
+ self.comm.queue_command(Cmd.RESUME)
+ self.ctrl.state.set('line', 0)
+ self.stopping = False
# Update cycle
if (self._get_cycle() != 'idle' and not self.planner.is_busy() and
not self.comm.is_active() and state == 'READY'):
self.ctrl.state.set('cycle', 'idle')
- # Automatically unpause on seek hold
+ # Continue after seek hold
if (state == 'HOLDING' and
self.ctrl.state.get('pr', '') == 'Switch found' and
self.planner.is_synchronizing()):
def home(self, axis, position = None):
+ state = self.ctrl.state
+
if position is not None:
self.mdi('G28.3 %c%f' % (axis, position))
else: axes = '%c' % axis
for axis in axes:
- if not self.ctrl.state.axis_can_home(axis):
- log.warning('Cannot home ' + axis)
+ # If this is not a request to home a specific axis and the
+ # axis is disabled or in manual homing mode, don't show any
+ # warnings
+ if 1 < len(axes) and (
+ not state.is_axis_enabled(axis) or
+ state.axis_homing_mode(axis) == 'manual'):
continue
+ # Error when axes cannot be homed
+ reason = state.axis_home_fail_reason(axis)
+ if reason is not None:
+ log.error('Cannot home %s axis: %s' % (
+ axis.upper(), reason))
+ continue
+
+ # Home axis
log.info('Homing %s axis' % axis)
self.planner.mdi(axis_homing_procedure % {'axis': axis})
self.comm.set_write(True)
- def estop(self): self.comm.i2c_command(Cmd.ESTOP)
- def clear(self): self.comm.i2c_command(Cmd.CLEAR)
+ def estop(self): self.comm.estop()
+ def clear(self): self.comm.clear()
def select(self, path):
self.stopping = True
- def pause(self): self.comm.i2c_command(Cmd.PAUSE, byte = 0)
+ def pause(self): self.comm.pause()
def unpause(self):
if self.ctrl.state.get('xx', '') != 'HOLDING': return
- self.comm.i2c_command(Cmd.FLUSH)
- self.comm.queue_command(Cmd.RESUME)
- self.planner.restart()
- self.comm.set_write(True)
+ pause_reason = self.ctrl.state.get('pr', '')
+ if pause_reason in ['User paused', 'Switch found']:
+ self.comm.i2c_command(Cmd.FLUSH)
+ self.comm.queue_command(Cmd.RESUME)
+ self.planner.restart()
+ self.comm.set_write(True)
+
self.comm.i2c_command(Cmd.UNPAUSE)
def optional_pause(self):
- if self._get_cycle() == 'running':
- self.comm.i2c_command(Cmd.PAUSE, byte = 1)
+ if self._get_cycle() == 'running': self.comm.pause(True)
def set_position(self, axis, position):
def remove_listener(self, listener): self.listeners.remove(listener)
+ def broadcast(self, msg):
+ for listener in self.listeners:
+ listener(msg)
+
+
# From logging.Handler
def emit(self, record):
if record.levelno == logging.INFO: return
if hasattr(record, 'where'): msg['where'] = record.where
else: msg['where'] = '%s:%d' % (record.filename, record.lineno)
- for listener in self.listeners:
- listener(msg)
+ self.broadcast({'log': msg})
# Apply all set commands <= to ID and those that follow consecutively
while len(self.setq) and self.setq[0][0] - 1 <= self.lastID:
id, name, value = self.setq.popleft()
- self.ctrl.state.set(name, value)
+
+ if name == 'message': self.ctrl.msgs.broadcast({'message': value})
+ else: self.ctrl.state.set(name, value)
+
if id == self.lastID + 1: self.lastID = id
if type == 'set':
name, value = block['name'], block['value']
- if name == 'line': self._queue_set_cmd(block['id'], name, value)
- if name == 'tool': return Cmd.tool(value)
+ if name in ['message', 'line', 'tool']:
+ self._queue_set_cmd(block['id'], name, value)
+
if name == 'speed': return Cmd.speed(value)
+
if name == '_mist':
self._queue_set_cmd(block['id'], 'load1state', value)
+
if name == '_flood':
self._queue_set_cmd(block['id'], 'load2state', value)
- if name[0:1] == '_' and name[1:2] in 'xyzabc' and \
- name[2:] == '_home':
- return Cmd.set_axis(name[1], value)
+
+ if (name[0:1] == '_' and name[1:2] in 'xyzabc' and
+ name[2:] == '_home'): return Cmd.set_axis(name[1], value)
if len(name) and name[0] == '_':
self._queue_set_cmd(block['id'], name[1:], value)
return Cmd.output(block['port'], int(float(block['value'])))
if type == 'dwell': return Cmd.dwell(block['seconds'])
- if type == 'pause': return Cmd.pause(block['optional'])
+ if type == 'pause': return Cmd.pause(block['pause-type'])
if type == 'seek':
return Cmd.seek(block['switch'], block['active'], block['error'])
return motor
- def is_axis_homed(self, axis):
- return self.get('%s_homed' % axis, False)
+ def is_axis_homed(self, axis): return self.get('%s_homed' % axis, False)
def is_axis_enabled(self, axis):
return False if motor is None else self.motor_enabled(motor)
- def axis_can_home(self, axis):
+ def axis_homing_mode(self, axis):
motor = self.find_motor(axis)
- if motor is None: return False
- if not self.motor_enabled(motor): return False
+ if motor is None: return 'disabled'
+ return self.motor_homing_mode(motor)
- homing_mode = self.motor_homing_mode(motor)
- if homing_mode == 1: return bool(int(self.get(axis + '_ls'))) # min sw
- if homing_mode == 2: return bool(int(self.get(axis + '_xs'))) # max sw
- return False
+
+ def axis_home_fail_reason(self, axis):
+ motor = self.find_motor(axis)
+ if motor is None: return 'Not mapped to motor'
+ if not self.motor_enabled(motor): return 'Motor disabled'
+
+ mode = self.motor_homing_mode(motor)
+
+ if mode == 'manual': return 'Configured for manual homing'
+
+ if mode == 'switch-min' and not int(self.get(axis + '_ls')):
+ return 'Configured for min switch but switch is disabled'
+
+ if mode == 'switch-max' and not int(self.get(axis + '_xs')):
+ return 'Configured for max switch but switch is disabled'
def motor_enabled(self, motor):
return bool(int(self.vars.get('%dpm' % motor, 0)))
- def motor_homing_mode(self, motor): return int(self.vars['%dho' % motor])
+ def motor_homing_mode(self, motor):
+ mode = str(self.vars.get('%dho' % motor, 0))
+ if mode == '0': return 'manual'
+ if mode == '1': return 'switch-min'
+ if mode == '2': return 'switch-max'
+ raise Exception('Unrecognized homing mode "%s"' % mode)
def motor_home_direction(self, motor):
- homing_mode = self.motor_homing_mode(motor)
- if homing_mode == 1: return -1 # Switch min
- if homing_mode == 2: return 1 # Switch max
+ mode = self.motor_homing_mode(motor)
+ if mode == 'switch-min': return -1
+ if mode == 'switch-max': return 1
return 0 # Disabled
def motor_home_position(self, motor):
- homing_mode = self.motor_homing_mode(motor)
- if homing_mode == 1: return self.vars['%dtn' % motor] # Min soft limit
- if homing_mode == 2: return self.vars['%dtm' % motor] # Max soft limit
+ mode = self.motor_homing_mode(motor)
+ # Return soft limit positions
+ if mode == 'switch-min': return self.vars['%dtn' % motor]
+ if mode == 'switch-max': return self.vars['%dtm' % motor]
return 0 # Disabled
self.count += 1
- def notify(self, msg): self.send(dict(msg = msg))
def send(self, msg): raise HTTPError(400, 'Not implemented')
def on_open(self, *args, **kwargs):
self.timer = self.ctrl.ioloop.call_later(3, self.heartbeat)
self.ctrl.state.add_listener(self.send)
- self.ctrl.msgs.add_listener(self.notify)
+ self.ctrl.msgs.add_listener(self.send)
self.is_open = True
def on_close(self):
self.ctrl.ioloop.remove_timeout(self.timer)
self.ctrl.state.remove_listener(self.send)
- self.ctrl.msgs.remove_listener(self.notify)
+ self.ctrl.msgs.remove_listener(self.send)
def on_message(self, data): self.ctrl.mach.mdi(data)