+ - Fixed reported gcode line off by one.
+ - Disable MDI while running.
+ - Stablized direction pin output during slow moves.
- Implemented M70-M73 modal state save/restore.
- Added support for modbus VFDs.
- Start Huanyang spindle with out first pressing Start button on VFD.
+ - Faster switching of large GCode files in Web.
+ - Fixed reported gcode line off by one.
+ - Disable MDI while running.
+ - Stablized direction pin output during slow moves.
## v0.3.20
- Eliminated drift caused by miscounting half microsteps.
done
data-usage: $(PROJECT).elf
- avr-nm -S --size-sort -t decimal $(PROJECT).elf | grep ' [BbDd] '
+ avr-nm -CS --size-sort -t decimal $(PROJECT).elf | grep ' [BbDd] '
prog-usage: $(PROJECT).elf
- avr-nm -S --size-sort -t decimal $(PROJECT).elf | grep -v ' [BbDd] '
+ avr-nm -CS --size-sort -t decimal $(PROJECT).elf | grep -v ' [BbDd] '
# Program
reset:
static stat_t _dwell_exec() {
- exec_set_cb(0);
+ exec_set_cb(0); // Immediately clear the callback
return STAT_OK;
}
void command_dwell_exec(void *seconds) {
st_prep_dwell(*(float *)seconds);
- exec_set_cb(_dwell_exec); // Necessary evil
+ exec_set_cb(_dwell_exec); // Command must set an exec callback
}
#define VELOCITY_MULTIPLIER 1000.0
#define ACCEL_MULTIPLIER 1000000.0
#define JERK_MULTIPLIER 1000000.0
-#define SYNC_QUEUE_SIZE 4096
+#define SYNC_QUEUE_SIZE 2048
#define EXEC_FILL_TARGET 8
#define EXEC_DELAY 250 // ms
#define JOG_STOPPING_UNDERSHOOT 1 // % of stopping distance
void exec_set_cb(exec_cb_t cb) {ex.cb = cb;}
-stat_t exec_move_to_target(float time, const float target[]) {
+void exec_move_to_target(float time, const float target[]) {
ASSERT(isfinite(time));
ASSERT(isfinite(target[AXIS_X]) && isfinite(target[AXIS_Y]) &&
isfinite(target[AXIS_Z]) && isfinite(target[AXIS_A]) &&
// Call the stepper prep function
st_prep_line(time, target);
-
- return STAT_OK;
}
void exec_set_cb(exec_cb_t cb);
-stat_t exec_move_to_target(float time, const float target[]);
+void exec_move_to_target(float time, const float target[]);
stat_t exec_next();
} else result = analog_get(active_cmd.port);
// TODO find a better way to send this
- printf("{\"result\": %f}\n", (double)result);
+ printf_P(PSTR("{\"result\": %f}\n"), (double)result);
active_cmd.port = -1;
}
}
// Set velocity and target
exec_set_velocity(sqrt(velocity_sqr));
- return exec_move_to_target(SEGMENT_TIME, target);
+ exec_move_to_target(SEGMENT_TIME, target);
+
+ return STAT_OK;
}
stat_t command_jog(char *cmd) {
- // Ignore jog commands when not READY or JOGGING
+ // Ignore jog commands when not READY and not JOGGING
if (state_get() != STATE_READY && state_get() != STATE_JOGGING)
return STAT_NOP;
}
-static stat_t _move(float t, float target[AXES], float v, float a) {
+static void _move(float t, float target[AXES], float v, float a) {
exec_set_velocity(v);
exec_set_acceleration(a);
if (seek_switch_found()) state_seek_hold();
- return exec_move_to_target(t, target);
+ exec_move_to_target(t, target);
}
float oldAccel = exec_get_acceleration();
exec_set_jerk(oldAccel == a ? 0 : (oldAccel < a ? j : -j));
- return _move(SEGMENT_TIME, target, v, a);
+ _move(SEGMENT_TIME, target, v, a);
+ return STAT_OK;
}
}
// Do move & update exec
- stat_t status;
-
if (lastSection && l.stop_section)
// Stop exactly on target to correct for floating-point errors
- status = _move(seg_time, l.line.target, 0, 0);
+ _move(seg_time, l.line.target, 0, 0);
else {
// Compute target position from distance
float target[AXES];
_segment_target(target, d);
- status = _move(seg_time, target, v, a);
+ _move(seg_time, target, v, a);
l.dist = d;
}
_done();
}
- return status;
+ return STAT_OK;
}
void _print_vector(const char *name, float v[4]) {
- printf("%s %f %f %f %f\n",
- name, (double)v[0], (double)v[1], (double)v[2], (double)v[3]);
+ printf_P(PSTR("%s %f %f %f %f\n"),
+ name, (double)v[0], (double)v[1], (double)v[2], (double)v[3]);
}
sei(); // enable interrupts
// Splash
- fprintf_P(stdout, PSTR("\n{\"firmware\":\"Buildbotics AVR\"}\n"));
+ printf_P(PSTR("\n{\"firmware\":\"Buildbotics AVR\"}\n"));
// Main loop
while (true) {
motor_end_move(motor);
- // Set direction, compensating for polarity
+ if (!m->timer_period) return; // Leave clock stopped
+
+ // Set direction, compensating for polarity but only when moving
const bool dir = m->negative ^ m->reverse;
if (dir != IN_PIN(m->dir_pin)) {
SET_PIN(m->dir_pin, dir);
- // Make sure there is plenty of time between direction change and next step.
+ // Need at least 200ns between direction change and next step.
if (m->timer->CCA < m->timer->CNT) m->timer->CNT = m->timer->CCA + 1;
}
- if (!m->timer_period) return; // Leave clock stopped
-
// Reset DMA step counter
m->dma->CTRLA &= ~DMA_CH_ENABLE_bm;
m->dma->TRFCNT = 0xffff;
#ifdef DEBUG
#define DEBUG_CALL(FMT, ...) \
- printf("%s(" FMT ")\n", __FUNCTION__, ##__VA_ARGS__)
+ printf_P(PSTR("%s(" FMT ")\n"), __FUNCTION__, ##__VA_ARGS__)
#else // DEBUG
#define DEBUG_CALL(...)
#endif // DEBUG
void stepper_init() {
// Motor enable
- OUTSET_PIN(MOTOR_ENABLE_PIN); // Low (disabled)
+ OUTCLR_PIN(MOTOR_ENABLE_PIN); // Lo (disabled)
DIRSET_PIN(MOTOR_ENABLE_PIN); // Output
// Setup step timer
break;
}
- printf("%s", buf);
+ printf(buf);
}
}
var_info_t info;
if (!_find_var(name, &info)) return STAT_UNRECOGNIZED_NAME;
- printf("{\"%s\":", info.name);
+ printf_P(PSTR("{\"%s\":"), info.name);
type_print(info.type, _get(info.type, info.index, info.get));
putchar('}');
putchar('\n');
uint8_t dir_pin;
TC0_t *timer;
volatile int16_t high;
+ volatile bool reading;
} channel[4] = {
{STEP_X_PIN, DIR_X_PIN, &TCC0, 0},
void channel_overflow(int i) {
if (IN_PIN(channel[i].dir_pin)) channel[i].high--;
else channel[i].high++;
+ channel[i].reading = false;
}
ISR(PORTE_INT0_vect) {for (int i = 0; i < 4; i++) channel_update_dir(i);}
+int32_t __attribute__ ((noinline)) _channel_read(int i) {
+ return (int32_t)channel[i].high << 16 | channel[i].timer->CNT;
+}
+
+
int32_t channel_read(int i) {
while (true) {
- int32_t x = (int32_t)channel[i].high << 16 | channel[i].timer->CNT;
- int32_t y = (int32_t)channel[i].high << 16 | channel[i].timer->CNT;
- if (x == y) return x;
+ channel[i].reading = true;
+
+ int32_t x = _channel_read(i);
+ int32_t y = _channel_read(i);
+
+ if (x != y || !channel[i].reading) continue;
+
+ channel[i].reading = false;
+ return x;
}
}
+void channel_update_enable(int i) {
+ if (IN_PIN(MOTOR_ENABLE_PIN))
+ channel[i].timer->CTRLA = TC_CLKSEL_EVCH0_gc + i;
+ else channel[i].timer->CTRLA = 0;
+}
+
+
+ISR(PORTF_INT0_vect) {
+ for (int i = 0; i < 4; i++) channel_update_enable(i);
+ if (!IN_PIN(MOTOR_ENABLE_PIN)) reset = 2;
+}
+
+
ISR(PORTC_INT0_vect) {reset = 32;}
PINCTRL_PIN(dir_pin) = PORT_SRLEN_bm | PORT_ISC_BOTHEDGES_gc;
// Dir change interrupt
- PIN_PORT(dir_pin)->INTCTRL |= PORT_INT0LVL_MED_gc;
+ PIN_PORT(dir_pin)->INTCTRL |= PORT_INT0LVL_HI_gc;
PIN_PORT(dir_pin)->INT0MASK |= PIN_BM(dir_pin);
// Events
EVSYS_CHCTRL(i) = EVSYS_DIGFILT_8SAMPLES_gc;
// Clock
- channel[i].timer->CTRLA = TC_CLKSEL_EVCH0_gc + i;
+ channel_update_enable(i);
channel[i].timer->INTCTRLA = TC_OVFINTLVL_HI_gc;
// Set initial clock direction
hw_init();
usart_init();
+
+ // Motor channels
for (int i = 0; i < 4; i++) channel_init(i);
+ // Motor enable
+ DIRCLR_PIN(MOTOR_ENABLE_PIN);
+ PINCTRL_PIN(MOTOR_ENABLE_PIN) =
+ PORT_SRLEN_bm | PORT_ISC_BOTHEDGES_gc | PORT_OPC_PULLUP_gc;
+ PIN_PORT(MOTOR_ENABLE_PIN)->INTCTRL |= PORT_INT0LVL_HI_gc;
+ PIN_PORT(MOTOR_ENABLE_PIN)->INT0MASK |= PIN_BM(MOTOR_ENABLE_PIN);
+
// Configure report clock
TCC1.INTCTRLA = TC_OVFINTLVL_LO_gc;
TCC1.PER = F_CPU / 256 * 0.01; // 10ms
section#content2.tab-content
.mdi.pure-form(title="Manual GCode entry.")
- button.pure-button(
+ button.pure-button(:disabled="!can_mdi",
title="{{is_running ? 'Pause' : 'Start'}} command.",
@click="mdi_start_pause")
.fa(:class="is_running ? 'fa-pause' : 'fa-play'")
button.pure-button(title="Stop command.", @click="stop")
.fa.fa-stop
- input(v-model="mdi", @keyup.enter="submit_mdi")
+ input(v-model="mdi", :disabled="!can_mdi", @keyup.enter="submit_mdi")
.history(:class="{placeholder: !history}")
span(v-if="!history.length") MDI history displays here.
},
- is_stopping: function() {return this.mach_state == 'STOPPING'},
- is_holding: function() {return this.mach_state == 'HOLDING'},
- is_ready: function() {return this.mach_state == 'READY'},
+ is_stopping: function () {return this.mach_state == 'STOPPING'},
+ is_holding: function () {return this.mach_state == 'HOLDING'},
+ is_ready: function () {return this.mach_state == 'READY'},
+
+
+ can_mdi: function () {
+ return this.state.cycle == 'idle' || this.state.cycle == 'mdi';
+ },
reason: function () {
},
+ ready: function () {
+ this.clusterize = new Clusterize({
+ rows: [],
+ scrollElem: $(this.$el).find('.clusterize-scroll')[0],
+ contentElem: $(this.$el).find('.clusterize-content')[0],
+ callbacks: {clusterChanged: this.highlight}
+ });
+ },
+
+
+ attached: function () {
+ if (typeof this.clusterize != 'undefined')
+ this.clusterize.refresh(true);
+ },
+
+
methods: {
load: function (file) {
if (file == this.file) return;
lines[i] + '</li>';
}
- this.clusterize = new Clusterize({
- rows: lines,
- scrollElem: $(this.$el).find('.clusterize-scroll')[0],
- contentElem: $(this.$el).find('.clusterize-content')[0],
- callbacks: {clusterChanged: this.highlight}
- });
-
+ this.clusterize.update(lines);
this.empty = false;
Vue.nextTick(this.update_line);
this.empty = true;
this.file = '';
this.line = -1;
-
- if (typeof this.clusterize != 'undefined')
- this.clusterize.destroy();
+ this.clusterize.clear();
},
} else line = this.line;
- if (typeof this.clusterize == 'undefined') return;
-
var totalLines = this.clusterize.getRowsAmount();
if (line <= 0) line = 1;
return id
- def enqueue(self, id, immediate, cb, *args, **kwargs):
- log.info('add(#%d, %s) releaseID=%d', id, immediate, self.releaseID)
+ def enqueue(self, id, synchronized, cb, *args, **kwargs):
+ log.info('add(#%d, %s) releaseID=%d', id, synchronized, self.releaseID)
self.lastEnqueueID = id
- self.q.append((id, immediate, cb, args, kwargs))
+ self.q.append([id, synchronized, False, cb, args, kwargs])
self._release()
def _release(self):
while len(self.q):
- id, immediate, cb, args, kwargs = self.q[0]
+ id, synchronized, immediate, cb, args, kwargs = self.q[0]
# Execute commands <= releaseID and consecutive immediate commands
if not immediate and self.releaseID < id: return
self.releaseID = id
self._release()
+
+
+ def finalize(self):
+ # Mark trailing non-synchronized commands immediate
+ i = len(self.q)
+
+ while i:
+ i -= 1
+ id, synchronized, immediate, cb, args, kwargs = self.q[i]
+ if synchronized: break
+ self.q[i][2] = True
+
+ self._release()
super().i2c_command(Cmd.UNPAUSE)
+ def _reset(self):
+ self.planner.reset()
+ self.ctrl.state.reset()
+
+
@overrides(Comm)
def comm_next(self):
if self.planner.is_running(): return self.planner.next()
@overrides(Comm)
- def comm_error(self): self.planner.reset()
+ def comm_error(self): self._reset()
@overrides(Comm)
def connect(self):
- self.ctrl.state.reset()
- self.planner.reset()
+ self._reset()
super().connect()
def clear(self):
if self._get_state() == 'ESTOPPED':
- self.ctrl.state.reset()
+ self._reset()
super().clear()
def _enqueue_set_cmd(self, id, name, value):
log.info('set(#%d, %s, %s)', id, name, value)
- self.cmdq.enqueue(id, True, self.ctrl.state.set, name, value)
+ self.cmdq.enqueue(id, False, self.ctrl.state.set, name, value)
def __encode(self, block):
if name == 'message':
self.cmdq.enqueue(
- id, True, self.ctrl.msgs.broadcast, {'message': value})
+ id, False, self.ctrl.msgs.broadcast, {'message': value})
if name in ['line', 'tool']:
self._enqueue_set_cmd(id, name, value)
cmd = self.__encode(block)
if cmd is not None:
- self.cmdq.enqueue(block['id'], False, None)
+ self.cmdq.enqueue(block['id'], True, None)
return Cmd.set('id', block['id']) + '\n' + cmd
self.reset()
- def has_move(self): return self.planner.has_more()
-
-
def next(self):
try:
while self.planner.has_more():
cmd = self.planner.next()
cmd = self._encode(cmd)
+ if not self.planner.is_running(): self.cmdq.finalize()
if cmd is not None: return cmd
+ self.cmdq.finalize()
+
except Exception as e:
log.exception(e)
self.reset()
for i in range(4):
# Add home direction callbacks
self.set_callback(str(i) + 'hd',
- lambda name, i = i: self.motor_home_direction(i))
+ lambda name, i = i: self.motor_home_direction(i))
# Add home position callbacks
self.set_callback(str(i) + 'hp',
- lambda name, i = i: self.motor_home_position(i))
+ lambda name, i = i: self.motor_home_position(i))
self.reset()