Added support for Nowforever VFDs, Support Modbus multi-write mode, Log when RPi...
authorJoseph Coffland <joseph@cauldrondevelopment.com>
Mon, 6 May 2019 00:35:24 +0000 (17:35 -0700)
committerJoseph Coffland <joseph@cauldrondevelopment.com>
Mon, 6 May 2019 00:35:24 +0000 (17:35 -0700)
24 files changed:
CHANGELOG.md
scripts/next-rc
src/avr/src/command.c
src/avr/src/drv8711.c
src/avr/src/modbus.c
src/avr/src/modbus.h
src/avr/src/motor.c
src/avr/src/spindle.h
src/avr/src/stepper.c
src/avr/src/type.c
src/avr/src/type.def
src/avr/src/vars.def
src/avr/src/vfd_spindle.c
src/js/control-view.js
src/js/tool-view.js
src/pug/templates/tool-view.pug
src/py/bbctrl/AVR.py
src/py/bbctrl/Camera.py
src/py/bbctrl/Cmd.py [changed mode: 0644->0755]
src/py/bbctrl/Comm.py
src/py/bbctrl/Ctrl.py
src/py/bbctrl/Planner.py
src/py/bbctrl/Web.py
src/resources/config-template.json

index 79329ebb4e22e3041cf8c45d61a65574e421ff9e..472d9aef143e076bffa1621da5dd2dff4c42ee90 100644 (file)
@@ -14,6 +14,9 @@ Buildbotics CNC Controller Firmware Changelog
  - Fixed zeroing with non-zero offset when unhomed. #211
  - Handle file paths uploaded from Windows correctly. #212
  - Don't retain estop state through reboot.
+ - Log when RPi gets hot.
+ - Support Modbus multi-write mode.
+ - Added support for Nowforever VFDs.
 
 ## v0.4.6
  - Fixed a rare ``Negative s-curve time`` error.
index 48df339c5c61f561640fef970bcc0716219d2ad7..2edc50a438fad8c6e79567ab10b5db37d8175d12 100755 (executable)
@@ -13,7 +13,7 @@ with open('package.json', 'r') as f:
     version = json.load(f)['version']
 
 if latest_beta.startswith(version + '-rc'):
-    print(int(latest_beta[len(version) + 3]) + 1)
+    print(int(latest_beta[len(version) + 3:]) + 1)
 
 else:
     print(1)
index 8733edb985cec2f1d3b0d1d771fb72392efd5531..17aee9c58c98ea8edddac7fd969f5eab114edbe4 100644 (file)
@@ -257,15 +257,13 @@ uint8_t *command_next() {
 // Called by exec.c from low-level interrupt
 bool command_exec() {
   if (!cmd.count) {
-    // TODO If MIN_VELOCITY < velocity then we have a potential buffer underrun
     cmd.last_empty = rtc_get_time();
-    exec_set_velocity(0);
     state_idle();
     return false;
   }
 
   // On restart wait a bit to give queue a chance to fill
-  if (!exec_get_velocity() && cmd.count < EXEC_FILL_TARGET &&
+  if (cmd.count < EXEC_FILL_TARGET &&
       !rtc_expired(cmd.last_empty + EXEC_DELAY)) return false;
 
   uint8_t *data = command_next();
index 52b59c499a276ef1c0abc52f47a00ac3ce70c39e..18a8fde9752adc9ee8a950efc16971b636a701d8 100644 (file)
@@ -130,11 +130,6 @@ static void _current_set(current_t *c, float current) {
 static bool _driver_fault(drv8711_driver_t *drv) {return drv->flags & 0x1f;}
 
 
-static bool _driver_enabled(drv8711_driver_t *drv) {
-  return drv->state != DRV8711_DISABLED;
-}
-
-
 static bool _driver_active(drv8711_driver_t *drv) {
   return drv->state == DRV8711_ACTIVE;
 }
@@ -163,8 +158,6 @@ static uint8_t _driver_get_torque(drv8711_driver_t *drv) {
 
 
 static uint16_t _driver_spi_command(drv8711_driver_t *drv) {
-  if (!_driver_enabled(drv)) return 0;
-
   switch (drv->spi_state) {
   case SS_WRITE_OFF:   return DRV8711_WRITE(DRV8711_OFF_REG,   DRV8711_OFF);
   case SS_WRITE_BLANK: return DRV8711_WRITE(DRV8711_BLANK_REG, DRV8711_BLANK);
@@ -200,8 +193,6 @@ static uint16_t _driver_spi_command(drv8711_driver_t *drv) {
 
 
 static spi_state_t _driver_spi_next(drv8711_driver_t *drv) {
-  if (!_driver_enabled(drv)) return SS_WRITE_OFF;
-
   // Process response
   switch (drv->spi_state) {
   case SS_READ_OFF:
@@ -217,7 +208,7 @@ static spi_state_t _driver_spi_next(drv8711_driver_t *drv) {
     // NOTE If there is a power fault and the drivers are not powered
     // then the status flags will read 0xff but the motor fault line will
     // not be asserted.  So, fault flags are not valid with out motor fault.
-    // A real stall cannot occur if the driver is inactive.
+    // Also, a real stall cannot occur if the driver is inactive.
     bool active = _driver_active(drv);
     uint8_t mask =
       ((motor_fault && !drv->reset_flags) ? 0xff : 0) | (active ? 0xc0 : 0);
@@ -225,10 +216,6 @@ static spi_state_t _driver_spi_next(drv8711_driver_t *drv) {
 
     // EStop on fatal driver faults
     if (_driver_fault(drv)) estop_trigger(STAT_MOTOR_FAULT);
-
-    // Enable motors when last driver has been fully configured
-    if (drv == &drivers[DRIVERS - 1])
-      SET_PIN(MOTOR_ENABLE_PIN, !estop_triggered()); // Active high
     break;
   }
 
@@ -269,6 +256,7 @@ static void _spi_send() {
   if (spi.advance) {
     spi.advance = false;
 
+    // Handle response and set next state
     drv->spi_state = _driver_spi_next(drv);
 
     // Next driver
@@ -343,8 +331,8 @@ void drv8711_init() {
   OUTSET_PIN(SPI_MOSI_PIN); // High
   DIRSET_PIN(SPI_MOSI_PIN); // Output
 
-  // Motor enable
-  OUTCLR_PIN(MOTOR_ENABLE_PIN); // Lo (disabled)
+  // Motor driver enable
+  OUTSET_PIN(MOTOR_ENABLE_PIN); // Active high
   DIRSET_PIN(MOTOR_ENABLE_PIN); // Output
 
   for (int i = 0; i < DRIVERS; i++) {
@@ -444,64 +432,4 @@ void set_driver_flags(int driver, uint16_t flags) {
 
 
 uint16_t get_driver_flags(int driver) {return drivers[driver].flags;}
-
-
-void print_status_flags(uint16_t flags) {
-  bool first = true;
-
-  putchar('"');
-
-  if (DRV8711_STATUS_OTS_bm & flags) {
-    if (!first) printf_P(PSTR(", "));
-    printf_P(PSTR("temp"));
-    first = false;
-  }
-
-  if (DRV8711_STATUS_AOCP_bm & flags) {
-    if (!first) printf_P(PSTR(", "));
-    printf_P(PSTR("current a"));
-    first = false;
-  }
-
-  if (DRV8711_STATUS_BOCP_bm & flags) {
-    if (!first) printf_P(PSTR(", "));
-    printf_P(PSTR("current b"));
-    first = false;
-  }
-
-  if (DRV8711_STATUS_APDF_bm & flags) {
-    if (!first) printf_P(PSTR(", "));
-    printf_P(PSTR("fault a"));
-    first = false;
-  }
-
-  if (DRV8711_STATUS_BPDF_bm & flags) {
-    if (!first) printf_P(PSTR(", "));
-    printf_P(PSTR("fault b"));
-    first = false;
-  }
-
-  if (DRV8711_STATUS_UVLO_bm & flags) {
-    if (!first) printf_P(PSTR(", "));
-    printf_P(PSTR("uvlo"));
-    first = false;
-  }
-
-  if ((DRV8711_STATUS_STD_bm | DRV8711_STATUS_STDLAT_bm) & flags) {
-    if (!first) printf_P(PSTR(", "));
-    printf_P(PSTR("stall"));
-    first = false;
-  }
-
-  if (DRV8711_COMM_ERROR_bm & flags) {
-    if (!first) printf_P(PSTR(", "));
-    printf_P(PSTR("comm"));
-    first = false;
-  }
-
-  putchar('"');
-}
-
-
-uint16_t get_status_strings(int driver) {return get_driver_flags(driver);}
 bool get_driver_stalled(int driver) {return drivers[driver].stalled;}
index 4d95e784b2b129202f42e310dd10fbd0e7ff6e73..ef1d792d6548b829fb1a9f598d257db7d13f2b79 100644 (file)
@@ -73,6 +73,7 @@ static struct {
   uint32_t last_read;
   uint8_t retry;
   uint8_t status;
+  uint16_t crc_errs;
   bool write_ready;
   bool response_ready;
   bool transmit_complete;
@@ -136,14 +137,18 @@ static bool _check_response() {
     _read_word(state.response + state.response_length - 2, true);
 
   if (computed != expected) {
-    char sent[state.command_length * 2 + 1];
-    char response[state.response_length * 2 + 1];
-    format_hex_buf(sent, state.command, state.command_length);
-    format_hex_buf(response, state.response, state.response_length);
-
-    STATUS_WARNING(STAT_OK, "modbus: invalid CRC, received=0x%04x "
-                   "computed=0x%04x sent=0x%s received=0x%s",
-                   expected, computed, sent, response);
+    if (cfg.debug) {
+      char sent[state.command_length * 2 + 1];
+      char response[state.response_length * 2 + 1];
+      format_hex_buf(sent, state.command, state.command_length);
+      format_hex_buf(response, state.response, state.response_length);
+
+      STATUS_WARNING(STAT_OK, "modbus: invalid CRC, received=0x%04x "
+                     "computed=0x%04x sent=0x%s received=0x%s",
+                     expected, computed, sent, response);
+    }
+
+    state.crc_errs++;
     state.status = MODBUS_CRC;
     return false;
   }
@@ -244,7 +249,8 @@ static void _read_cb(uint8_t func, uint8_t bytes, const uint8_t *data) {
 
 
 static void _write_cb(uint8_t func, uint8_t bytes, const uint8_t *data) {
-  if (func == MODBUS_WRITE_OUTPUT_REG && bytes == 4 &&
+  if ((func == MODBUS_WRITE_OUTPUT_REG ||
+       func == MODBUS_WRITE_OUTPUT_REGS) && bytes == 4 &&
       _read_word(data, false) == state.addr) {
     if (state.rw_cb)
       state.rw_cb(true, state.addr, _read_word(state.command + 4, false));
@@ -410,6 +416,18 @@ void modbus_write(uint16_t addr, uint16_t value, modbus_rw_cb_t cb) {
 }
 
 
+void modbus_multi_write(uint16_t addr, uint16_t value, modbus_rw_cb_t cb) {
+  state.rw_cb = cb;
+  state.addr = addr;
+  uint8_t cmd[7];
+  _write_word(cmd, addr, false);      // Start address
+  _write_word(cmd + 2, 1, false);     // Number of regs
+  cmd[4] = 2;                         // Number of bytes
+  _write_word(cmd + 5, value, false); // Value
+  modbus_func(MODBUS_WRITE_OUTPUT_REGS, 7, cmd, 4, _write_cb);
+}
+
+
 void modbus_callback() {
   if (state.transmit_complete) {
     state.last_write = rtc_get_time();
@@ -441,27 +459,28 @@ void modbus_callback() {
 
 
 // Variable callbacks
-bool get_modbus_debug() {return cfg.debug;}
-void set_modbus_debug(bool value) {cfg.debug = value;}
-uint8_t get_modbus_id() {return cfg.id;}
-void set_modbus_id(uint8_t id) {cfg.id = id;}
-uint8_t get_modbus_baud() {return cfg.baud;}
+bool get_mb_debug() {return cfg.debug;}
+void set_mb_debug(bool value) {cfg.debug = value;}
+uint8_t get_mb_id() {return cfg.id;}
+void set_mb_id(uint8_t id) {cfg.id = id;}
+uint8_t get_mb_baud() {return cfg.baud;}
 
 
-void set_modbus_baud(uint8_t baud) {
+void set_mb_baud(uint8_t baud) {
   cfg.baud = (baud_t)baud;
   usart_set_baud(&RS485_PORT, cfg.baud);
 }
 
 
-uint8_t get_modbus_parity() {return cfg.parity;}
+uint8_t get_mb_parity() {return cfg.parity;}
 
 
-void set_modbus_parity(uint8_t parity) {
+void set_mb_parity(uint8_t parity) {
   cfg.parity = (parity_t)parity;
   usart_set_parity(&RS485_PORT, cfg.parity);
   usart_set_stop(&RS485_PORT, _get_stop());
 }
 
 
-uint8_t get_modbus_status() {return state.status;}
+uint8_t get_mb_status() {return state.status;}
+uint16_t get_mb_crc_errs() {return state.crc_errs;}
index 1db34b2a094e35b47f696bdfffdcdfcb7e54c13e..89b2cbb973545904e798439226e836aefd5bc1f6 100644 (file)
@@ -108,4 +108,5 @@ void modbus_func(uint8_t func, uint8_t send, const uint8_t *data,
                  uint8_t receive, modbus_cb_t cb);
 void modbus_read(uint16_t addr, uint16_t count, modbus_rw_cb_t cb);
 void modbus_write(uint16_t addr, uint16_t value, modbus_rw_cb_t cb);
+void modbus_multi_write(uint16_t addr, uint16_t value, modbus_rw_cb_t cb);
 void modbus_callback();
index a6172b811bf5221ab71d8a413d2d3ec0625d244c..b032568d71a8227a52c8b2a56d309bf504057aa6 100644 (file)
@@ -201,7 +201,7 @@ static void _update_power(int motor) {
 
   if (m->enabled) {
     bool timedout = rtc_expired(m->power_timeout);
-    // NOTE, we have ~5ms to enable the motor
+    // NOTE, we have ~5ms to update the driver config
     drv8711_set_state(motor, timedout ? DRV8711_IDLE : DRV8711_ACTIVE);
 
   } else drv8711_set_state(motor, DRV8711_DISABLED);
index 3966d27b303fbc9678f1e10b799ba5f200665b5c..615e1b2698c952346208ca3066af34dd7411e2ab 100644 (file)
@@ -51,6 +51,7 @@ typedef enum {
   SPINDLE_TYPE_HUANYANG,
   SPINDLE_TYPE_CUSTOM,
   SPINDLE_TYPE_AC_TECH,
+  SPINDLE_TYPE_NOWFOREVER,
   SPINDLE_TYPE_DELTA_VFD015M21A,
   SPINDLE_TYPE_YL600,
   SPINDLE_TYPE_FR_D700,
index 2b6db21a7ebfeee11f2435f5c5168192351f98cc..21feec0767de0893b346318cb8ec8d92bffe5f15 100644 (file)
@@ -54,7 +54,7 @@ typedef struct {
   float prep_dwell;
   power_update_t prep_powers[POWER_MAX_UPDATES];
 
-  uint32_t underflow;
+  uint32_t underrun;
 } stepper_t;
 
 
@@ -101,7 +101,12 @@ ISR(STEP_LOW_LEVEL_ISR) {
 
     switch (status) {
     case STAT_NOP:                          // No move executed, idle
-      if (!st.busy) spindle_idle();
+      if (!st.busy) {
+        if (MIN_VELOCITY < exec_get_velocity()) st.underrun++;
+        exec_set_velocity(0); // Velocity is zero if there are no moves
+
+        spindle_idle();
+      }
       break;
 
     case STAT_AGAIN: continue;              // No command executed, try again
@@ -159,7 +164,6 @@ ISR(STEP_TIMER_ISR) {
 
   // If the next move is not ready try to load it
   if (!st.move_ready) {
-    if (exec_get_velocity()) st.underflow++;
     _request_exec_move();
     _end_move();
     tick = 0; // Try again in 1ms
@@ -224,7 +228,7 @@ void st_prep_dwell(float seconds) {
 
 
 // Var callbacks
-uint32_t get_underflow() {return st.underflow;}
+uint32_t get_underrun() {return st.underrun;}
 
 
 float get_dwell_time() {
index 227afadf178491cb7682bebb04cc54ed3e64daf4..272e1f66fea71278e3701d165994be8e8fd02307 100644 (file)
@@ -55,18 +55,6 @@ void type_print_pstr(pstr s) {printf_P(PSTR("\"%" PRPSTR "\""), s);}
 const char *type_parse_pstr(const char *value, stat_t *) {return value;}
 
 
-// Flags
-bool type_eq_flags(flags a, flags b) {return a == b;}
-
-
-void type_print_flags(flags x) {
-  extern void print_status_flags(flags x);
-  print_status_flags(x);
-}
-
-flags type_parse_flags(const char *s, stat_t *) {return 0;} // Not used
-
-
 // Float
 bool type_eq_f32(float a, float b) {return a == b || (isnan(a) && isnan(b));}
 
index 53da1bfb476a64f8ad53688b42b850f037e118ae..5f350faaa69d8ea8dc8ccef1748754e900491128 100644 (file)
@@ -26,7 +26,6 @@
 \******************************************************************************/
 
 //      TYPE   DEF
-TYPEDEF(flags, uint16_t)
 TYPEDEF(str,   const char *)
 TYPEDEF(pstr,  PGM_P)
 TYPEDEF(f32,   float)
index 08318964c04a95e74bb289a87ae2abaa84d8158d..45dda839610f3a5f9629b00aaa04f31a69c8629b 100644 (file)
@@ -52,12 +52,11 @@ VAR(min_soft_limit,  tn, f32,   MOTORS, 1, 1) // Min soft limit
 VAR(max_soft_limit,  tm, f32,   MOTORS, 1, 1) // Max soft limit
 VAR(homed,            h, b8,    MOTORS, 1, 1) // Motor homed status
 
-VAR(active_current,  ac, f32,   MOTORS, 0, 1) // Motor current now
+VAR(active_current,  ac, f32,   MOTORS, 0, 0) // Motor current now
 VAR(driver_flags,    df, u16,   MOTORS, 1, 1) // Motor driver flags
-VAR(status_strings,  ds, flags, MOTORS, 0, 1) // Motor driver status
-VAR(driver_stalled,  sl, b8,    MOTORS, 0, 1) // Motor driver status
+VAR(driver_stalled,  sl, b8,    MOTORS, 0, 0) // Motor driver status
 VAR(encoder,         en, s32,   MOTORS, 0, 0) // Motor encoder
-VAR(error,           ee, s32,   MOTORS, 0, 1) // Motor position error
+VAR(error,           ee, s32,   MOTORS, 0, 0) // Motor position error
 
 VAR(motor_fault,     fa, b8,    0,      0, 1) // Motor fault status
 
@@ -95,18 +94,20 @@ VAR(min_spin,        sm, f32,   0,      1, 1) // Minimum spindle speed
 VAR(pwm_invert,      pi, b8,    0,      1, 1) // Inverted spindle PWM
 VAR(pwm_min_duty,    nd, f32,   0,      1, 1) // Minimum PWM duty cycle
 VAR(pwm_max_duty,    md, f32,   0,      1, 1) // Maximum PWM duty cycle
-VAR(pwm_duty,        pd, f32,   0,      0, 1) // Current PWM duty cycle
-VAR(pwm_freq,        sf, f32,   0,      1, 1) // Spindle PWM frequency in Hz
+VAR(pwm_duty,        pd, f32,   0,      0, 0) // Current PWM duty cycle
+VAR(pwm_freq,        sf, f32,   0,      1, 0) // Spindle PWM frequency in Hz
 
 // Modbus spindle
-VAR(modbus_debug,    hb, b8,    0,      1, 1) // Modbus debugging
-VAR(modbus_id,       hi, u8,    0,      1, 1) // Modbus ID
-VAR(modbus_baud,     mb, u8,    0,      1, 1) // Modbus BAUD rate
-VAR(modbus_parity,   ma, u8,    0,      1, 1) // Modbus parity
-VAR(modbus_status,   mx, u8,    0,      0, 1) // Modbus status
+VAR(mb_debug,        hb, b8,    0,      1, 1) // Modbus debugging
+VAR(mb_id,           hi, u8,    0,      1, 1) // Modbus ID
+VAR(mb_baud,         mb, u8,    0,      1, 1) // Modbus BAUD rate
+VAR(mb_parity,       ma, u8,    0,      1, 1) // Modbus parity
+VAR(mb_status,       mx, u8,    0,      0, 1) // Modbus status
+VAR(mb_crc_errs,     cr, u16,   0,      0, 1) // Modbus CRC error counter
 
 // VFD spindle
 VAR(vfd_max_freq,    vf, u16,   0,      1, 1) // VFD maximum frequency
+VAR(vfd_multi_write, mw, b8,    0,      1, 1) // Use Modbus multi write mode
 VAR(vfd_status,      vs, u16,   0,      0, 1) // VFD status
 VAR(vfd_reg_type,    vt, u8,    VFDREG, 1, 1) // VFD register type
 VAR(vfd_reg_addr,    va, u16,   VFDREG, 1, 1) // VFD register address
@@ -114,9 +115,9 @@ VAR(vfd_reg_val,     vv, u16,   VFDREG, 1, 1) // VFD register value
 VAR(vfd_reg_fails,   vr, u8,    VFDREG, 1, 1) // VFD register fail count
 
 // Huanyang spindle
-VAR(hy_freq,         hz, f32,   0,      0, 1) // Huanyang actual freq
-VAR(hy_current,      hc, f32,   0,      0, 1) // Huanyang actual current
-VAR(hy_temp,         ht, u16,   0,      0, 1) // Huanyang temperature
+VAR(hy_freq,         hz, f32,   0,      0, 0) // Huanyang actual freq
+VAR(hy_current,      hc, f32,   0,      0, 0) // Huanyang actual current
+VAR(hy_temp,         ht, u16,   0,      0, 0) // Huanyang temperature
 VAR(hy_max_freq,     hx, f32,   0,      0, 1) // Huanyang max freq
 VAR(hy_min_freq,     hm, f32,   0,      0, 1) // Huanyang min freq
 VAR(hy_rated_rpm,    hq, u16,   0,      0, 1) // Huanyang rated RPM
@@ -129,8 +130,8 @@ VAR(speed_override,  so, u16,   0,      1, 1) // Spindle speed override
 
 // System
 VAR(velocity,         v, f32,   0,      0, 1) // Current velocity
-VAR(acceleration,    ax, f32,   0,      0, 1) // Current acceleration
-VAR(jerk,             j, f32,   0,      0, 1) // Current jerk
+VAR(acceleration,    ax, f32,   0,      0, 0) // Current acceleration
+VAR(jerk,             j, f32,   0,      0, 0) // Current jerk
 VAR(peak_vel,        pv, f32,   0,      1, 1) // Peak velocity, set to clear
 VAR(peak_accel,      pa, f32,   0,      1, 1) // Peak accel, set to clear
 VAR(dynamic_power,   dp, b8,    0,      1, 1) // Dynamic power
@@ -140,5 +141,5 @@ VAR(estop,           es, b8,    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
 VAR(hold_reason,     pr, pstr,  0,      0, 1) // Machine pause reason
-VAR(underflow,       un, u32,   0,      0, 1) // Stepper underflow count
+VAR(underrun,        un, u32,   0,      0, 1) // Stepper buffer underrun count
 VAR(dwell_time,      dt, f32,   0,      0, 1) // Dwell timer
index 6fa25b7de5daec78424a58f6000b45ebd4892a35..6e01a2bc21048fc3500fad24a7652b6b7c32a3e3 100644 (file)
@@ -90,6 +90,18 @@ const vfd_reg_t ac_tech_regs[] PROGMEM = {
 };
 
 
+const vfd_reg_t nowforever_regs[] PROGMEM = {
+  {REG_MAX_FREQ_READ,     7,    0}, // Max frequency
+  {REG_FREQ_SET,       2305,    0}, // Frequency
+  {REG_STOP_WRITE,     2304,    0}, // Stop drive
+  {REG_FWD_WRITE,      2304,    1}, // Forward
+  {REG_REV_WRITE,      2304,    3}, // Reverse
+  {REG_FREQ_READ,      1282,    0}, // Output freq
+  {REG_STATUS_READ,     768,    0}, // Status
+  {REG_DISABLED},
+};
+
+
 const vfd_reg_t delta_vfd015m21a_regs[] PROGMEM = {
   {REG_CONNECT_WRITE, 0x2002,  2}, // Reset fault
   {REG_MAX_FREQ_READ,      3,  0}, // Max frequency
@@ -141,6 +153,7 @@ static struct {
 
   float power;
   uint16_t max_freq;
+  bool user_multi_write;
   float actual_power;
   uint16_t status;
 
@@ -260,6 +273,15 @@ static void _modbus_cb(bool ok, uint16_t addr, uint16_t value) {
 }
 
 
+static bool _use_multi_write() {
+  switch (spindle_get_type()) {
+  case SPINDLE_TYPE_CUSTOM:     return vfd.user_multi_write;
+  case SPINDLE_TYPE_NOWFOREVER: return true;
+  default:                      return false;
+  }
+}
+
+
 static bool _exec_command() {
   if (vfd.wait) return true;
 
@@ -303,7 +325,8 @@ static bool _exec_command() {
   }
 
   if (read) modbus_read(reg.addr, words, _modbus_cb);
-  else if (write) modbus_write(reg.addr, reg.value, _modbus_cb);
+  else if (write) (_use_multi_write() ? modbus_multi_write : modbus_write)
+                    (reg.addr, reg.value, _modbus_cb);
   else return false;
 
   return true;
@@ -329,10 +352,11 @@ void vfd_spindle_init() {
 
   switch (spindle_get_type()) {
   case SPINDLE_TYPE_CUSTOM:  memcpy(regs, custom_regs, sizeof(regs)); break;
-  case SPINDLE_TYPE_AC_TECH:          _load(ac_tech_regs); break;
-  case SPINDLE_TYPE_DELTA_VFD015M21A: _load(delta_vfd015m21a_regs); break;
-  case SPINDLE_TYPE_YL600:            _load(yl600_regs);   break;
-  case SPINDLE_TYPE_FR_D700:          _load(fr_d700_regs); break;
+  case SPINDLE_TYPE_AC_TECH:          _load(ac_tech_regs);            break;
+  case SPINDLE_TYPE_NOWFOREVER:       _load(nowforever_regs);         break;
+  case SPINDLE_TYPE_DELTA_VFD015M21A: _load(delta_vfd015m21a_regs);   break;
+  case SPINDLE_TYPE_YL600:            _load(yl600_regs);              break;
+  case SPINDLE_TYPE_FR_D700:          _load(fr_d700_regs);            break;
   default: break;
   }
 
@@ -369,6 +393,8 @@ void vfd_spindle_rtc_callback() {
 // Variable callbacks
 uint16_t get_vfd_max_freq() {return vfd.max_freq;}
 void set_vfd_max_freq(uint16_t max_freq) {vfd.max_freq = max_freq;}
+bool get_vfd_multi_write() {return vfd.user_multi_write;}
+void set_vfd_multi_write(bool value) {vfd.user_multi_write = value;}
 uint16_t get_vfd_status() {return vfd.status;}
 uint8_t get_vfd_reg_type(int reg) {return regs[reg].type;}
 
index 46c199ae95e637d99bbb23b1124cdd483e96122a..7244eeaf35ba98a42d26885a8a74cc82fb8f0d8a 100644 (file)
@@ -62,7 +62,6 @@ module.exports = {
       {x: false, y: false, z: false, a: false, b: false, c: false},
       axis_position: 0,
       jog_adjust: 100,
-      video_url: '/api/video?nocache=' + Math.random(),
       deleteGCode: false,
       tab: 'auto'
     }
index fab70c85d8e7f1a64ea0fe2fad24554a124c9e93..958205d4a77dc5fb5bd2f8c891b2e3eee66de9da 100644 (file)
@@ -93,6 +93,12 @@ module.exports = {
     },
 
 
+    show_modbus_field: function (key) {
+      return key != 'regs' &&
+        (key != 'multi-write' || this.tool_type == 'CUSTOM MODBUS VFD');
+    },
+
+
     read: function (e) {
       e.preventDefault();
       api.put('modbus/read', {address: this.address});
index 28bbb4585d4ef0eac71b10aaf76274159981dfb3..3ef7bf5f91765b956fc21a7802c2876500032784 100644 (file)
@@ -53,7 +53,7 @@ script#tool-view-template(type="text/x-template")
           tt {{modbus_status}}
         templated-input(v-for="templ in template['modbus-spindle']",
           :name="$key", :model.sync="config['modbus-spindle'][$key]",
-          :template="templ", v-if="$key != 'regs'")
+          :template="templ", v-if="show_modbus_field($key)")
 
       fieldset.modbus-program(
         v-if="is_modbus && this.tool_type != 'HUANYANG VFD'")
index a54354c7e62a65f30a90bc6e8dfe88a05d39e5b7..110e51313950abc016f77fc9293ce963db4e02c7 100644 (file)
 import serial
 import time
 import traceback
+import ctypes
 
 import bbctrl
 import bbctrl.Cmd as Cmd
 
 
+class serial_struct(ctypes.Structure):
+    _fields_ = [
+        ('type',            ctypes.c_int),
+        ('line',            ctypes.c_int),
+        ('port',            ctypes.c_uint),
+        ('irq',             ctypes.c_int),
+        ('flags',           ctypes.c_int),
+        ('xmit_fifo_size',  ctypes.c_int),
+        ('custom_divisor',  ctypes.c_int),
+        ('baud_base',       ctypes.c_int),
+        ('close_delay',     ctypes.c_ushort),
+        ('io_type',         ctypes.c_byte),
+        ('reserved',        ctypes.c_byte),
+        ('hub6',            ctypes.c_int),
+        ('closing_wait',    ctypes.c_ushort),
+        ('closing_wait2',   ctypes.c_ushort),
+        ('iomem_base',      ctypes.c_char_p),
+        ('iomem_reg_shift', ctypes.c_ushort),
+        ('port_high',       ctypes.c_uint),
+        ('iomap_base',      ctypes.c_ulong),
+    ]
+
+
+def serial_set_low_latency(sp):
+    import fcntl
+    import termios
+
+    ASYNCB_LOW_LATENCY = 13
+
+    ss = serial_struct()
+    fcntl.ioctl(sp, termios.TIOCGSERIAL, ss)
+    ss.flags |= 1 << ASYNCB_LOW_LATENCY
+    fcntl.ioctl(sp, termios.TIOCSSERIAL, ss)
+
 
 class AVR(object):
     def __init__(self, ctrl):
@@ -53,6 +88,7 @@ class AVR(object):
             self.sp = serial.Serial(self.ctrl.args.serial, self.ctrl.args.baud,
                                     rtscts = 1, timeout = 0, write_timeout = 0)
             self.sp.nonblocking()
+            serial_set_low_latency(self.sp)
 
         except Exception as e:
             self.sp = None
index 8bfc66065acb36ddd294b450f47fe3f84054c4c2..589ee40f03079d3581b79c330b82991de3de83a2 100755 (executable)
@@ -34,6 +34,7 @@ import mmap
 import pyudev
 import base64
 import socket
+import ctypes
 from tornado import gen, web, iostream
 import bbctrl
 
@@ -43,7 +44,13 @@ except:
     import bbctrl.v4l2 as v4l2
 
 
-def array_to_string(a): return ''.join([chr(i) for i in a])
+def array_to_string(a):
+    def until_zero(a):
+        for c in a:
+            if c == 0: return
+            yield c
+
+    return ''.join([chr(i) for i in until_zero(a)])
 
 
 def fourcc_to_string(i):
old mode 100644 (file)
new mode 100755 (executable)
index 1edcad8..169f923
@@ -269,4 +269,4 @@ if __name__ == "__main__":
 
     else:
         for line in sys.stdin:
-            decode_and_print(line)
+            decode_and_print(str(line).strip())
index 742907c75b70c9fad40678657aaaaa40d6040f57..74cc4e8ee61fa9930134b5811f653c46975cd6b7 100644 (file)
@@ -35,6 +35,37 @@ import bbctrl
 import bbctrl.Cmd as Cmd
 
 
+# Must be kept in sync with drv8711.h
+DRV8711_STATUS_OTS_bm    = 1 << 0
+DRV8711_STATUS_AOCP_bm   = 1 << 1
+DRV8711_STATUS_BOCP_bm   = 1 << 2
+DRV8711_STATUS_APDF_bm   = 1 << 3
+DRV8711_STATUS_BPDF_bm   = 1 << 4
+DRV8711_STATUS_UVLO_bm   = 1 << 5
+DRV8711_STATUS_STD_bm    = 1 << 6
+DRV8711_STATUS_STDLAT_bm = 1 << 7
+DRV8711_COMM_ERROR_bm    = 1 << 8
+
+# Ignoring stall and stall latch flags for now
+DRV8711_MASK = ~(DRV8711_STATUS_STD_bm | DRV8711_STATUS_STDLAT_bm)
+
+
+def _driver_flags_to_string(flags):
+    if DRV8711_STATUS_OTS_bm    & flags: yield 'over temp'
+    if DRV8711_STATUS_AOCP_bm   & flags: yield 'over current a'
+    if DRV8711_STATUS_BOCP_bm   & flags: yield 'over current b'
+    if DRV8711_STATUS_APDF_bm   & flags: yield 'driver fault a'
+    if DRV8711_STATUS_BPDF_bm   & flags: yield 'driver fault b'
+    if DRV8711_STATUS_UVLO_bm   & flags: yield 'undervoltage'
+    if DRV8711_STATUS_STD_bm    & flags: yield 'stall'
+    if DRV8711_STATUS_STDLAT_bm & flags: yield 'stall latch'
+    if DRV8711_COMM_ERROR_bm    & flags: yield 'comm error'
+
+
+def driver_flags_to_string(flags):
+    return ', '.join(_driver_flags_to_string(flags))
+
+
 class Comm(object):
     def __init__(self, ctrl, avr):
         self.ctrl = ctrl
@@ -43,6 +74,7 @@ class Comm(object):
         self.queue = deque()
         self.in_buf = ''
         self.command = None
+        self.last_motor_flags = [0] * 4
 
         avr.set_handlers(self._read, self._write)
 
@@ -75,11 +107,11 @@ class Comm(object):
         self.flush()
 
 
-    def _write(self, write):
+    def _write(self, write_cb):
         # Finish writing current command
         if self.command is not None:
             try:
-                count = write(self.command)
+                count = write_cb(self.command)
 
             except Exception as e:
                 self.command = None
@@ -133,6 +165,30 @@ class Comm(object):
             self.comm_error()
 
 
+    def _log_motor_flags(self, update):
+        for motor in range(3):
+            var = '%ddf' % motor
+
+            if var in update:
+                flags = update[var] & DRV8711_MASK
+
+                if self.last_motor_flags[motor] == flags: continue
+                self.last_motor_flags[motor] = flags
+
+                flags = driver_flags_to_string(flags)
+                self.log.info('Motor %d flags: %s' % (motor, flags))
+
+
+    def _update_state(self, update):
+        self.ctrl.state.update(update)
+
+        if 'xx' in update:        # State change
+            self.ctrl.ready()     # We've received data from AVR
+            self.flush()          # May have more data to send now
+
+        self._log_motor_flags(update)
+
+
     def _read(self, data):
         self.in_buf += data.decode('utf-8')
 
@@ -160,11 +216,7 @@ class Comm(object):
                     self.log.info('AVR firmware rebooted')
                     self.connect()
 
-                else:
-                    self.ctrl.state.update(msg)
-                    if 'xx' in msg:           # State change
-                        self.ctrl.ready()     # We've received data from AVR
-                        self.flush()          # May have more data to send now
+                else: self._update_state(msg)
 
 
     def estop(self):
index f969a55083875a6862d04023c9de04172e0bee93..2a0955a6db11b19ffc91009cb309a887e2931eb0 100644 (file)
@@ -26,6 +26,7 @@
 ################################################################################
 
 import os
+import time
 import bbctrl
 
 
@@ -34,7 +35,9 @@ class Ctrl(object):
         self.args = args
         self.ioloop = bbctrl.IOLoop(ioloop)
         self.id = id
-        self.timeout = None
+        self.timeout = None # Used in demo mode
+        self.last_temp_warn = 0
+        self.temp_thresh = 80
 
         if id and not os.path.exists(id): os.mkdir(id)
 
@@ -64,6 +67,8 @@ class Ctrl(object):
             self.lcd.add_new_page(bbctrl.MainLCDPage(self))
             self.lcd.add_new_page(bbctrl.IPLCDPage(self.lcd))
 
+            if not args.demo: self.check_temp()
+
         except Exception: self.log.get('Ctrl').exception()
 
 
@@ -81,6 +86,23 @@ class Ctrl(object):
         self.timeout = self.ioloop.call_later(t, cb, *args, **kwargs)
 
 
+    def check_temp(self):
+        with open('/sys/class/thermal/thermal_zone0/temp', 'r') as f:
+            temp = round(int(f.read()) / 1000)
+
+        # Reset temperature warning threshold after timeout
+        if time.time() < self.last_temp_warn + 60: self.temp_thresh = 80
+
+        if self.temp_thresh < temp:
+            self.last_temp_warn = time.time()
+            self.temp_thresh = temp
+
+            log = self.log.get('Ctrl')
+            log.info('Hot RaspberryPi at %d°C' % temp)
+
+        self.ioloop.call_later(15, self.check_temp)
+
+
     def get_path(self, dir = None, filename = None):
         path = './' + self.id if self.id else '.'
         path = path if dir is None else (path + '/' + dir)
index 48416cc99e6a263c4212725d3193fc44c7b90b1f..2ccc897a3d7d1f8f5257d0aedbf264e3bde29b6a 100644 (file)
@@ -228,7 +228,10 @@ class Planner():
                 self.cmdq.enqueue(id, self.ctrl.log.broadcast, msg)
 
             if name in ['line', 'tool']: self._enqueue_set_cmd(id, name, value)
-            if name == 'speed': return Cmd.speed(value)
+
+            if name == 'speed':
+                self._enqueue_set_cmd(id, name, value)
+                return Cmd.speed(value)
 
             if len(name) and name[0] == '_':
                 # Don't queue axis positions, can be triggered by new position
index 362101c25835216a24fdc9133c5b82b04e77ff62..7a0d4992b05c67ccb83f0f3f9dffb63dc1d475c3 100644 (file)
@@ -116,6 +116,7 @@ class BugReportHandler(bbctrl.RequestHandler):
         check_add_basename(path + '.1')
         check_add_basename(path + '.2')
         check_add_basename(path + '.3')
+        check_add_basename('/var/log/syslog')
         check_add('config.json')
         check_add(ctrl.get_upload(ctrl.state.get('selected', '')))
 
@@ -487,7 +488,6 @@ class Web(tornado.web.Application):
             else: log = self.get_ctrl().log
             self.camera = bbctrl.Camera(ioloop, args, log)
 
-
         handlers = [
             (r'/websocket', WSConnection),
             (r'/api/log', LogHandler),
index 47176bac1cea6ac2933623a77de5b51b5435203d..591e18a8ff45a490c6b1f481374e7d9c1f74428f 100644 (file)
     "tool-type": {
       "type": "enum",
       "values": ["Disabled", "PWM Spindle", "Huanyang VFD", "Custom Modbus VFD",
-                 "AC-Tech VFD", "Delta VFD015M21A (Beta)", "YL600 VFD (Beta)",
-                 "FR-D700 (Beta)"],
+                 "AC-Tech VFD", "Nowforever VFD", "Delta VFD015M21A (Beta)",
+                 "YL600 VFD (Beta)", "FR-D700 (Beta)"],
       "default": "Disabled",
       "code": "st"
     },
       "default": "None",
       "code": "ma"
     },
+    "multi-write": {
+      "help": "Use Modbus multi register write.  Function 16 vs. 6.",
+      "type": "bool",
+      "default": false,
+      "code": "mw"
+    },
     "regs": {
       "type": "list",
       "index": "0123456789abcdefghijklmnopqrstuv",