From 30632c614c0f5e26e1805a2f9aa5fea578db0832 Mon Sep 17 00:00:00 2001 From: Joseph Coffland Date: Sat, 24 Feb 2018 18:29:25 -0800 Subject: [PATCH] - Disable spindle and loads on stop. - Fixed several state transition (stop, pause, estop, etc.) problems. --- CHANGELOG.md | 4 ++ package.json | 2 +- src/avr/src/command.def | 2 +- src/avr/src/exec.c | 2 - src/avr/src/jog.c | 9 ++- src/avr/src/jog.h | 2 +- src/avr/src/outputs.c | 6 ++ src/avr/src/outputs.h | 1 + src/avr/src/state.c | 89 +++++++++++++++++++++------- src/avr/src/state.h | 4 +- src/jade/templates/control-view.jade | 11 ++-- src/js/control-view.js | 1 - src/py/bbctrl/Cmd.py | 4 +- src/py/bbctrl/Comm.py | 18 +++--- src/py/bbctrl/Mach.py | 87 ++++++++++++++------------- src/py/bbctrl/Planner.py | 12 ++-- src/py/bbctrl/State.py | 14 +++-- 17 files changed, 168 insertions(+), 100 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e1b772..832ed78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ Buildbotics CNC Controller Firmware Change Log ============================================== +## v0.3.13 + - Disable spindle and loads on stop. + - Fixed several state transition (stop, pause, estop, etc.) problems. + ## v0.3.12 - Updated DB25 M2 breakout diagram. - Enabled AVR watchdog. diff --git a/package.json b/package.json index 30d65d6..33e9411 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bbctrl", - "version": "0.3.12", + "version": "0.3.13", "homepage": "http://buildbotics.com/", "repository": "https://github.com/buildbotics/bbctrl-firmware", "license": "GPL-3.0+", diff --git a/src/avr/src/command.def b/src/avr/src/command.def index 53eca13..0cea17b 100644 --- a/src/avr/src/command.def +++ b/src/avr/src/command.def @@ -32,6 +32,7 @@ CMD('a', set_axis, 1, "[axis][position] Set axis position") CMD('l', line, 1, "[targetVel][maxJerk][axes][times]") CMD('d', dwell, 1, "[seconds]") CMD('P', pause, 1, "[type] Pause control") +CMD('S', stop, 0, "Stop move, spindle and load outputs") CMD('U', unpause, 0, "Unpause") CMD('j', jog, 0, "[axes]") CMD('r', report, 0, "<0|1>[var] Enable or disable var reporting") @@ -39,7 +40,6 @@ CMD('R', reboot, 0, "Reboot the controller") CMD('c', resume, 0, "Continue processing after a flush") CMD('E', estop, 0, "Emergency stop") CMD('C', clear, 0, "Clear estop") -CMD('S', step, 0, "Advance one step") CMD('F', flush, 0, "Flush command queue") CMD('D', dump, 0, "Report all variables") CMD('h', help, 0, "Print this help screen") diff --git a/src/avr/src/exec.c b/src/avr/src/exec.c index 71be146..97edf58 100644 --- a/src/avr/src/exec.c +++ b/src/avr/src/exec.c @@ -53,9 +53,7 @@ void exec_init() { memset(&ex, 0, sizeof(ex)); ex.feed_override = 1; ex.spindle_override = 1; - // TODO implement move stepping // TODO implement overrides - // TODO implement optional pause } diff --git a/src/avr/src/jog.c b/src/avr/src/jog.c index 947fda2..ed1cd4c 100644 --- a/src/avr/src/jog.c +++ b/src/avr/src/jog.c @@ -247,9 +247,16 @@ stat_t jog_exec() { } +void jog_stop() { + jr.writing = true; + for (int axis = 0; axis < AXES; axis++) + jr.axes[axis].next = 0; + jr.writing = false; +} + stat_t command_jog(char *cmd) { - // Ignore jog commands when not already idle + // Ignore jog commands when not READY or JOGGING if (state_get() != STATE_READY && state_get() != STATE_JOGGING) return STAT_NOP; diff --git a/src/avr/src/jog.h b/src/avr/src/jog.h index 14281d3..ea093ab 100644 --- a/src/avr/src/jog.h +++ b/src/avr/src/jog.h @@ -27,8 +27,8 @@ #pragma once - #include "status.h" stat_t jog_exec(); +void jog_stop(); diff --git a/src/avr/src/outputs.c b/src/avr/src/outputs.c index 90b8818..80c7fdc 100644 --- a/src/avr/src/outputs.c +++ b/src/avr/src/outputs.c @@ -113,6 +113,12 @@ output_state_t outputs_get_state(uint8_t pin) { } +void outputs_stop() { + outputs_set_active(SWITCH_1_PIN, false); + outputs_set_active(SWITCH_2_PIN, false); +} + + // Var callbacks uint8_t get_output_state(uint8_t id) { return OUTS <= id ? OUT_TRI : outputs[id].state; diff --git a/src/avr/src/outputs.h b/src/avr/src/outputs.h index b775303..0e5a6f8 100644 --- a/src/avr/src/outputs.h +++ b/src/avr/src/outputs.h @@ -55,3 +55,4 @@ bool outputs_is_active(uint8_t pin); void outputs_set_active(uint8_t pin, bool active); void outputs_set_mode(uint8_t pin, output_mode_t mode); output_state_t outputs_get_state(uint8_t pin); +void outputs_stop(); diff --git a/src/avr/src/state.c b/src/avr/src/state.c index 37dda17..73b55a1 100644 --- a/src/avr/src/state.c +++ b/src/avr/src/state.c @@ -31,6 +31,8 @@ #include "command.h" #include "stepper.h" #include "spindle.h" +#include "outputs.h" +#include "jog.h" #include "estop.h" #include "report.h" @@ -41,11 +43,12 @@ static struct { state_t state; hold_reason_t hold_reason; + bool stop_requested; bool pause_requested; + bool optional_pause_requested; + bool unpause_requested; bool flush_requested; - bool start_requested; bool resume_requested; - bool optional_pause_requested; } s = { .flush_requested = true, // Start out flushing @@ -69,9 +72,9 @@ PGM_P state_get_pgmstr(state_t state) { PGM_P state_get_hold_reason_pgmstr(hold_reason_t reason) { switch (reason) { case HOLD_REASON_USER_PAUSE: return PSTR("User paused"); + case HOLD_REASON_USER_STOP: return PSTR("User stop"); case HOLD_REASON_PROGRAM_PAUSE: return PSTR("Program paused"); - case HOLD_REASON_STEPPING: return PSTR("Stepping"); - case HOLD_REASON_SEEK: return PSTR("Switch found"); + case HOLD_REASON_SWITCH_FOUND: return PSTR("Switch found"); } return PSTR("INVALID"); @@ -108,13 +111,54 @@ bool state_is_quiescent() { void state_seek_hold() { if (state_get() == STATE_RUNNING) { - _set_hold_reason(HOLD_REASON_SEEK); + _set_hold_reason(HOLD_REASON_SWITCH_FOUND); + _set_state(STATE_STOPPING); + } +} + + +static void _stop() { + switch (state_get()) { + case STATE_STOPPING: + case STATE_RUNNING: + _set_hold_reason(HOLD_REASON_USER_STOP); _set_state(STATE_STOPPING); + break; + + case STATE_JOGGING: + jog_stop(); + // Fall through + + case STATE_READY: + case STATE_HOLDING: + s.flush_requested = true; + spindle_stop(); + outputs_stop(); + _set_state(STATE_READY); + break; + + case STATE_ESTOPPED: + break; // Ignore } } -void state_holding() {_set_state(STATE_HOLDING);} +void state_holding() { + _set_state(STATE_HOLDING); + + switch (s.hold_reason) { + case HOLD_REASON_PROGRAM_PAUSE: break; + + case HOLD_REASON_USER_PAUSE: + case HOLD_REASON_SWITCH_FOUND: + s.flush_requested = true; + break; + + case HOLD_REASON_USER_STOP: + _stop(); + break; + } +} void state_pause(bool optional) { @@ -168,21 +212,26 @@ void state_estop() {_set_state(STATE_ESTOPPED);} void state_callback() { if (estop_triggered()) return; + // Pause if (s.pause_requested || s.flush_requested) { - if (s.pause_requested) _set_hold_reason(HOLD_REASON_USER_PAUSE); + if (state_get() == STATE_RUNNING) { + if (s.pause_requested) _set_hold_reason(HOLD_REASON_USER_PAUSE); + _set_state(STATE_STOPPING); + } + s.pause_requested = false; + } - if (state_get() == STATE_RUNNING) _set_state(STATE_STOPPING); + // Stop + if (s.stop_requested) { + _stop(); + s.stop_requested = false; } // Only flush queue when idle or holding if (s.flush_requested && state_is_quiescent()) { command_flush_queue(); - // Stop spindle - // TODO Spindle should not be stopped when pausing - spindle_stop(); - // Resume if (s.resume_requested) { s.flush_requested = s.resume_requested = false; @@ -191,9 +240,9 @@ void state_callback() { } // Don't start while flushing or stopping - if (s.start_requested && !s.flush_requested && + if (s.unpause_requested && !s.flush_requested && state_get() != STATE_STOPPING) { - s.start_requested = false; + s.unpause_requested = false; s.optional_pause_requested = false; if (state_get() == STATE_HOLDING) { @@ -236,20 +285,20 @@ void command_pause_exec(void *data) { } -stat_t command_unpause(char *cmd) { - s.start_requested = true; +stat_t command_stop(char *cmd) { + s.stop_requested = true; return STAT_OK; } -stat_t command_resume(char *cmd) { - if (s.flush_requested) s.resume_requested = true; +stat_t command_unpause(char *cmd) { + s.unpause_requested = true; return STAT_OK; } -stat_t command_step(char *cmd) { - // TODO +stat_t command_resume(char *cmd) { + if (s.flush_requested) s.resume_requested = true; return STAT_OK; } diff --git a/src/avr/src/state.h b/src/avr/src/state.h index 3d4059e..fab452f 100644 --- a/src/avr/src/state.h +++ b/src/avr/src/state.h @@ -44,9 +44,9 @@ typedef enum { typedef enum { HOLD_REASON_USER_PAUSE, + HOLD_REASON_USER_STOP, HOLD_REASON_PROGRAM_PAUSE, - HOLD_REASON_STEPPING, - HOLD_REASON_SEEK, + HOLD_REASON_SWITCH_FOUND, } hold_reason_t; diff --git a/src/jade/templates/control-view.jade b/src/jade/templates/control-view.jade index ac9ff60..277a738 100644 --- a/src/jade/templates/control-view.jade +++ b/src/jade/templates/control-view.jade @@ -135,13 +135,13 @@ script#control-view-template(type="text/x-template") td tr th Load 1 - td(:class="state.load1state ? 'load-on' : ''") - | {{state.load1state ? 'On' : 'Off'}} + td(:class="state['1oa'] ? 'load-on' : ''") + | {{state['1oa'] ? 'On' : 'Off'}} td tr th Load 2 - td(:class="state.load2state ? 'load-on' : ''") - | {{state.load2state ? 'On' : 'Off'}} + td(:class="state['2oa'] ? 'load-on' : ''") + | {{state['2oa'] ? 'On' : 'Off'}} td .override(title="Feed rate override.") @@ -167,8 +167,7 @@ script#control-view-template(type="text/x-template") @click="start_pause", :disabled="!state.selected") .fa(:class="state.xx == 'RUNNING' ? 'fa-pause' : 'fa-play'") - button.pure-button(title="Stop program.", @click="stop", - :disabled="state.xx == 'READY'") + button.pure-button(title="Stop program.", @click="stop") .fa.fa-stop button.pure-button(title="Pause program at next optional stop (M1).", diff --git a/src/js/control-view.js b/src/js/control-view.js index 5edd318..dbd75cc 100644 --- a/src/js/control-view.js +++ b/src/js/control-view.js @@ -275,7 +275,6 @@ module.exports = { api.upload('file', fd) .done(function () { - file.name; if (file.name == this.last_file) this.last_file = ''; this.update(); }.bind(this)); diff --git a/src/py/bbctrl/Cmd.py b/src/py/bbctrl/Cmd.py index 6a4e1b4..7bcf41d 100644 --- a/src/py/bbctrl/Cmd.py +++ b/src/py/bbctrl/Cmd.py @@ -41,8 +41,8 @@ SEEK = 's' SET_AXIS = 'a' LINE = 'l' DWELL = 'd' -OPT_PAUSE = 'p' PAUSE = 'P' +STOP = 'S' UNPAUSE = 'U' JOG = 'j' REPORT = 'r' @@ -50,7 +50,6 @@ REBOOT = 'R' RESUME = 'c' ESTOP = 'E' CLEAR = 'C' -STEP = 'S' FLUSH = 'F' DUMP = 'D' HELP = 'h' @@ -197,7 +196,6 @@ def decode_command(cmd): elif cmd[0] == ESTOP: data['type'] = 'estop' elif cmd[0] == CLEAR: data['type'] = 'clear' elif cmd[0] == FLUSH: data['type'] = 'flush' - elif cmd[0] == STEP: data['type'] = 'step' elif cmd[0] == RESUME: data['type'] = 'resume' print(json.dumps(data)) diff --git a/src/py/bbctrl/Comm.py b/src/py/bbctrl/Comm.py index 2646a45..0281342 100644 --- a/src/py/bbctrl/Comm.py +++ b/src/py/bbctrl/Comm.py @@ -47,7 +47,7 @@ class Comm(): self.queue = deque() self.in_buf = '' self.command = None - self.reboot_expected = False + self.reboot_expected = True try: self.sp = serial.Serial(ctrl.args.serial, ctrl.args.baud, @@ -92,7 +92,7 @@ class Comm(): raise - def set_write(self, enable): + def _set_write(self, enable): if self.sp is None: return flags = self.ctrl.ioloop.READ @@ -105,9 +105,12 @@ class Comm(): self.command = bytes(cmd.strip() + '\n', 'utf-8') + def resume(self): self.queue_command(Cmd.RESUME) + + def queue_command(self, cmd): self.queue.append(cmd) - self.set_write(True) + self._set_write(True) def _serial_write(self): @@ -117,7 +120,7 @@ class Comm(): count = self.sp.write(self.command) except Exception as e: - self.set_write(False) + self.command = None raise e self.command = self.command[count:] @@ -131,7 +134,7 @@ class Comm(): else: cmd = self.next_cb() - if cmd is None: self.set_write(False) # Stop writing + if cmd is None: self._set_write(False) # Stop writing else: self._load_next_command(cmd) @@ -189,14 +192,13 @@ class Comm(): if 'variables' in msg: self._update_vars(msg) + self.reboot_expected = False - elif 'msg' in msg: - self._log_msg(msg) + elif 'msg' in msg: self._log_msg(msg) elif 'firmware' in msg: if self.reboot_expected: log.info('AVR firmware rebooted') else: log.error('Unexpected AVR firmware reboot') - self.reboot_expected = False self.connect() else: self.ctrl.state.update(msg) diff --git a/src/py/bbctrl/Mach.py b/src/py/bbctrl/Mach.py index 2695d9a..9e9c796 100644 --- a/src/py/bbctrl/Mach.py +++ b/src/py/bbctrl/Mach.py @@ -59,7 +59,7 @@ class Mach(): self.ctrl = ctrl self.planner = bbctrl.Planner(ctrl) self.comm = bbctrl.Comm(ctrl, self._comm_next, self._comm_connect) - self.stopping = False + self.update_timer = None ctrl.state.set('cycle', 'idle') ctrl.state.add_listener(self._update) @@ -67,6 +67,7 @@ class Mach(): self.comm.reboot() + def _get_state(self): return self.ctrl.state.get('xx', '') def _get_cycle(self): return self.ctrl.state.get('cycle') @@ -82,36 +83,33 @@ class Mach(): (cycle, current)) + def _update_cycle(self): + # Cancel timer if set + if self.update_timer is not None: + self.ctrl.ioloop.remove_timeout(self.update_timer) + self.update_timer = None + + # Check for idle state + if self._get_cycle() != 'idle' and self._get_state() == 'READY': + # Check again later if busy + if self.planner.is_busy() or self.comm.is_active(): + self.ctrl.ioloop.call_later(0.5, self._update_cycle) + + else: self.ctrl.state.set('cycle', 'idle') + + def _update(self, update): - state = self.ctrl.state.get('xx', '') + state = self._get_state() # Handle EStop - if 'xx' in update and state == 'ESTOPPED': - self._stop_sending_gcode() - self.stopping = False - - # Handle stop - 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 + if 'xx' in update and state == 'ESTOPPED': self.planner.reset() # 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') + self._update_cycle() # Continue after seek hold - if (state == 'HOLDING' and - self.ctrl.state.get('pr', '') == 'Switch found' and - self.planner.is_synchronizing()): + if (state == 'HOLDING' and self.planner.is_synchronizing() and + self.ctrl.state.get('pr', '') == 'Switch found'): self.unpause() @@ -119,15 +117,9 @@ class Mach(): if self.planner.is_running(): return self.planner.next() - def _comm_connect(self): self._stop_sending_gcode() - - - def _start_sending_gcode(self, path): - self.planner.load('upload/' + path) - self.comm.set_write(True) - - - def _stop_sending_gcode(self): self.planner.reset() + def _comm_connect(self): + self.ctrl.state.reset() + self.planner.reset() def _query_var(self, cmd): @@ -155,7 +147,7 @@ class Mach(): else: self._begin_cycle('mdi') self.planner.mdi(cmd) - self.comm.set_write(True) + self.comm.resume() def set(self, code, value): @@ -198,11 +190,16 @@ class Mach(): # Home axis log.info('Homing %s axis' % axis) self.planner.mdi(axis_homing_procedure % {'axis': axis}) - self.comm.set_write(True) + self.comm.resume() def estop(self): self.comm.estop() - def clear(self): self.comm.clear() + + + def clear(self): + if self._get_state() == 'ESTOPPED': + self.ctrl.state.reset() + self.comm.clear() def select(self, path): @@ -216,37 +213,39 @@ class Mach(): def start(self): self._begin_cycle('running') - self._start_sending_gcode(self.ctrl.state.get('selected')) + self.planner.load('upload/' + self.ctrl.state.get('selected')) + self.comm.resume() def step(self): raise Exception('NYI') # TODO - self.comm.i2c_command(Cmd.STEP) if self._get_cycle() != 'running': self.start() + else: self.comm.i2c_command(Cmd.UNPAUSE) def stop(self): - self.pause() - self.stopping = True + if self._get_cycle() == 'idle': self._begin_cycle('running') + self.comm.i2c_command(Cmd.STOP) + self.planner.stop() + self.ctrl.state.set('line', 0) def pause(self): self.comm.pause() def unpause(self): - if self.ctrl.state.get('xx', '') != 'HOLDING': return + if self._get_state() != 'HOLDING': return 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.resume() self.comm.i2c_command(Cmd.UNPAUSE) def optional_pause(self): + # TODO this could work better as a variable, i.e. $op=1 if self._get_cycle() == 'running': self.comm.pause(True) diff --git a/src/py/bbctrl/Planner.py b/src/py/bbctrl/Planner.py index bb08e56..f91d11d 100644 --- a/src/py/bbctrl/Planner.py +++ b/src/py/bbctrl/Planner.py @@ -169,12 +169,6 @@ class Planner(): 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) @@ -206,6 +200,12 @@ class Planner(): self.setq.clear() + def stop(self): + self.planner.stop() + self.lastID = -1 + self.setq.clear() + + def restart(self): state = self.ctrl.state id = state.get('id') diff --git a/src/py/bbctrl/State.py b/src/py/bbctrl/State.py index ef79496..516d6d4 100644 --- a/src/py/bbctrl/State.py +++ b/src/py/bbctrl/State.py @@ -62,11 +62,17 @@ class State(object): self.set_callback(str(i) + 'hp', lambda name, i = i: self.motor_home_position(i)) - # Set not homed - self.set('%dhomed' % i, False) + self.reset() - # Zero offsets - for axis in 'xyzabc': self.vars['offset_' + axis] = 0 + + def reset(self): + # Unhome all motors + for i in range(4): self.set('%dhomed' % i, False) + + # Zero offsets and positions + for axis in 'xyzabc': + self.set(axis + 'p', 0) + self.set('offset_' + axis, 0) def _notify(self): -- 2.27.0