Faster switching of large GCode files in Web.
authorJoseph Coffland <joseph@cauldrondevelopment.com>
Mon, 2 Apr 2018 23:15:38 +0000 (16:15 -0700)
committerJoseph Coffland <joseph@cauldrondevelopment.com>
Mon, 2 Apr 2018 23:15:38 +0000 (16:15 -0700)
+ - Fixed reported gcode line off by one.
+ - Disable MDI while running.
+ - Stablized direction pin output during slow moves.

23 files changed:
CHANGELOG.md
src/avr/Makefile.common
src/avr/src/commands.c
src/avr/src/config.h
src/avr/src/exec.c
src/avr/src/exec.h
src/avr/src/io.c
src/avr/src/jog.c
src/avr/src/line.c
src/avr/src/main.c
src/avr/src/motor.c
src/avr/src/status.h
src/avr/src/stepper.c
src/avr/src/type.c
src/avr/src/vars.c
src/avr/step-test/step-test.c
src/jade/templates/control-view.jade
src/js/control-view.js
src/js/gcode-viewer.js
src/py/bbctrl/CommandQueue.py
src/py/bbctrl/Mach.py
src/py/bbctrl/Planner.py
src/py/bbctrl/State.py

index b68f7e57f17dcd880532dbf98be5ddb1d6532d4e..4c5895c7f1c69a4e18ed14fe3641efecd19e1931 100644 (file)
@@ -5,6 +5,10 @@ Buildbotics CNC Controller Firmware Changelog
  - 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.
index 58100901c835494343244537490f336f557fe393..65c94805d08d580ff4d6924724e3420b2c65c507 100644 (file)
@@ -64,11 +64,11 @@ size: $(PROJECT).elf
        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:
index fea4a808ef6ad9e81e6c240ac2ca47514ce33a02..8796b09d718f14d2612c9b03bf39677de64284c2 100644 (file)
@@ -46,7 +46,7 @@ stat_t command_dwell(char *cmd) {
 
 
 static stat_t _dwell_exec() {
-  exec_set_cb(0);
+  exec_set_cb(0); // Immediately clear the callback
   return STAT_OK;
 }
 
@@ -56,7 +56,7 @@ unsigned command_dwell_size() {return sizeof(float);}
 
 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
 }
 
 
index 648e499204da21fcf874ba0d6a6e2d4c2105ce57..483d6cf1c29d76fa67f43989b9375b017036810b 100644 (file)
@@ -217,7 +217,7 @@ enum {
 #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
index e4231a686c4c9d56497730073eac9cd51aa21a36..3daad8409b667a15cc411161f2f001e0c0621026 100644 (file)
@@ -101,7 +101,7 @@ void exec_set_jerk(float j) {ex.jerk = j;}
 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]) &&
@@ -112,8 +112,6 @@ stat_t exec_move_to_target(float time, const float target[]) {
 
   // Call the stepper prep function
   st_prep_line(time, target);
-
-  return STAT_OK;
 }
 
 
index db445b6325b688a64ffa4ac708a950f46169ee78..6dfb930a5d879231c5cea23195873d50b7da11f1 100644 (file)
@@ -50,5 +50,5 @@ void exec_set_jerk(float j);
 
 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();
index 42b52ce299ead6d3c9874949a6c5cf9799ad590a..6cb81460b54a1885851a382695ce5df6ec79a2c1 100644 (file)
@@ -66,7 +66,7 @@ void io_rtc_callback() {
     } 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;
   }
 }
index 7a7537a342432a95a6435fdc6d92a8f50c9ee812..adec1ac5aa6807a2d1b6eaa713041875cfc59b20 100644 (file)
@@ -109,7 +109,9 @@ stat_t jog_exec() {
 
   // 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;
 }
 
 
@@ -122,7 +124,7 @@ void jog_stop() {
 
 
 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;
 
index 2260f1dad1020336a3f937fc657c66314b84043f..9ecd62387eb9079fc5d41ea46f09016edd086dff 100644 (file)
@@ -144,13 +144,13 @@ static void _done() {
 }
 
 
-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);
 }
 
 
@@ -198,7 +198,8 @@ static stat_t _pause() {
   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;
 }
 
 
@@ -243,18 +244,16 @@ static stat_t _line_exec() {
   }
 
   // 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;
   }
 
@@ -264,13 +263,13 @@ static stat_t _line_exec() {
     _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]);
 }
 
 
index 94b3da72ab21662d912c60443cd7ebf0f643b2ed..f93bcf5898c8877104de2fd511560f8e19ba24c8 100644 (file)
@@ -72,7 +72,7 @@ int main() {
   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) {
index 5e804fb53545ca6c05e2130cd939f11bb66b1900..1023fb0ce43e069ff6ddfe79d943296bc0fc1280 100644 (file)
@@ -259,16 +259,16 @@ void motor_load_move(int motor) {
 
   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;
index d1cab7bbee234f5858d9ba5447b30a86c28fb32e..00a130e7d4bce808cbbdc53c0078445041b0ba53 100644 (file)
@@ -92,7 +92,7 @@ stat_t status_alarm(const char *location, stat_t status, const char *msg);
 
 #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
index 984245d0bd1f1831585ff63ea53d84c9b0d667b9..793bd1bc03ae9cd65dccf9164f4d093696da2a11 100644 (file)
@@ -70,7 +70,7 @@ static volatile stepper_t st = {0};
 
 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
index 73c01eb446b48ed80747acb6d1536c39d9d0c3ab..aa92dd4b5e39518f7a0841a1d4ea2095322772bb 100644 (file)
@@ -95,7 +95,7 @@ void type_print_f32(float x) {
       break;
     }
 
-    printf("%s", buf);
+    printf(buf);
   }
 }
 
index d925a29a96c5d0ce5f8869b087fa27353f40583f..3e9bda6806f12facc279c5cd020b0ecc8df5b8f4 100644 (file)
@@ -294,7 +294,7 @@ stat_t vars_print(const char *name) {
   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');
index 2a452e8aa43c668132332b79f2168db9fc8045e6..db267bfe83325b6691780b960986848e1b1d292f 100644 (file)
@@ -47,6 +47,7 @@ static struct {
   uint8_t dir_pin;
   TC0_t *timer;
   volatile int16_t high;
+  volatile bool reading;
 
 } channel[4] = {
   {STEP_X_PIN, DIR_X_PIN, &TCC0, 0},
@@ -69,6 +70,7 @@ void channel_reset(int i) {channel[i].timer->CNT = channel[i].high = 0;}
 void channel_overflow(int i) {
   if (IN_PIN(channel[i].dir_pin)) channel[i].high--;
   else channel[i].high++;
+  channel[i].reading = false;
 }
 
 
@@ -87,15 +89,39 @@ void channel_update_dir(int i) {
 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;}
 
 
@@ -142,7 +168,7 @@ void channel_init(int i) {
   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
@@ -150,7 +176,7 @@ void channel_init(int i) {
   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
@@ -163,8 +189,17 @@ static void init() {
 
   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
index cb61a9d028328861fb06cdd23603c0538b878aad..6ebb988d3b3de28e437d64ce2fe04cbdc6d11585 100644 (file)
@@ -234,7 +234,7 @@ script#control-view-template(type="text/x-template")
 
       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'")
@@ -242,7 +242,7 @@ script#control-view-template(type="text/x-template")
           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.
index f4d0be75596b5703833653224d2170bc387d7af3..d3d07a7255591803389d40bed542dae4ee7f597b 100644 (file)
@@ -98,9 +98,14 @@ module.exports = {
     },
 
 
-    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 () {
index ca92b7c9cd2dfae9a0981ff916186744a6562567..d8584a803dcc1c3ca7a3870abe1c42ce456c5eab 100644 (file)
@@ -55,6 +55,22 @@ module.exports = {
   },
 
 
+  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;
@@ -71,13 +87,7 @@ module.exports = {
               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);
@@ -89,9 +99,7 @@ module.exports = {
       this.empty = true;
       this.file = '';
       this.line = -1;
-
-      if (typeof this.clusterize != 'undefined')
-        this.clusterize.destroy();
+      this.clusterize.clear();
     },
 
 
@@ -118,8 +126,6 @@ module.exports = {
 
       } else line = this.line;
 
-      if (typeof this.clusterize == 'undefined') return;
-
       var totalLines = this.clusterize.getRowsAmount();
 
       if (line <= 0) line = 1;
index cd5cd541a1eadd2fdcd2ef63e6ab206cd62e500b..bb0e2a1b3221f349d08677e1c1d1edbca97844cd 100644 (file)
@@ -59,16 +59,16 @@ class CommandQueue():
         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
@@ -89,3 +89,16 @@ class CommandQueue():
         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()
index b162dba57295dab94ae62642c209a7000a75406e..35029b4e0ed8b558924e3e9cd243f0e78150e4bb 100644 (file)
@@ -138,19 +138,23 @@ class Mach(Comm):
         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()
 
 
@@ -231,7 +235,7 @@ class Mach(Comm):
 
     def clear(self):
         if self._get_state() == 'ESTOPPED':
-            self.ctrl.state.reset()
+            self._reset()
             super().clear()
 
 
index 14b7bcd47238fb7d10e786312492ac8655677b26..3ce23d66d46e2311cedad6c0ccd7e1444538a8c2 100644 (file)
@@ -167,7 +167,7 @@ class Planner():
 
     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):
@@ -185,7 +185,7 @@ class Planner():
 
             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)
@@ -224,7 +224,7 @@ class Planner():
         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
 
 
@@ -275,16 +275,16 @@ class Planner():
             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()
index ad630a0b6343981f9f6a61f555a3ca20f0dca7b6..5c390a6fb2e88a74ac81231d58b959f490d5676f 100644 (file)
@@ -54,11 +54,11 @@ class State(object):
         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()