From 6d53a8d38c201ca7b554417178a10de5e08a7cfc Mon Sep 17 00:00:00 2001 From: Joseph Coffland Date: Sun, 5 May 2019 17:35:24 -0700 Subject: [PATCH] Added support for Nowforever VFDs, Support Modbus multi-write mode, Log when RPi gets hot, Reduced serial traffic to fix #215. --- CHANGELOG.md | 3 ++ scripts/next-rc | 2 +- src/avr/src/command.c | 4 +- src/avr/src/drv8711.c | 80 ++---------------------------- src/avr/src/modbus.c | 55 +++++++++++++------- src/avr/src/modbus.h | 1 + src/avr/src/motor.c | 2 +- src/avr/src/spindle.h | 1 + src/avr/src/stepper.c | 12 +++-- src/avr/src/type.c | 12 ----- src/avr/src/type.def | 1 - src/avr/src/vars.def | 35 ++++++------- src/avr/src/vfd_spindle.c | 36 ++++++++++++-- src/js/control-view.js | 1 - src/js/tool-view.js | 6 +++ src/pug/templates/tool-view.pug | 2 +- src/py/bbctrl/AVR.py | 36 ++++++++++++++ src/py/bbctrl/Camera.py | 9 +++- src/py/bbctrl/Cmd.py | 2 +- src/py/bbctrl/Comm.py | 66 +++++++++++++++++++++--- src/py/bbctrl/Ctrl.py | 24 ++++++++- src/py/bbctrl/Planner.py | 5 +- src/py/bbctrl/Web.py | 2 +- src/resources/config-template.json | 10 +++- 24 files changed, 253 insertions(+), 154 deletions(-) mode change 100644 => 100755 src/py/bbctrl/Cmd.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 79329eb..472d9ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/scripts/next-rc b/scripts/next-rc index 48df339..2edc50a 100755 --- a/scripts/next-rc +++ b/scripts/next-rc @@ -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) diff --git a/src/avr/src/command.c b/src/avr/src/command.c index 8733edb..17aee9c 100644 --- a/src/avr/src/command.c +++ b/src/avr/src/command.c @@ -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(); diff --git a/src/avr/src/drv8711.c b/src/avr/src/drv8711.c index 52b59c4..18a8fde 100644 --- a/src/avr/src/drv8711.c +++ b/src/avr/src/drv8711.c @@ -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;} diff --git a/src/avr/src/modbus.c b/src/avr/src/modbus.c index 4d95e78..ef1d792 100644 --- a/src/avr/src/modbus.c +++ b/src/avr/src/modbus.c @@ -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;} diff --git a/src/avr/src/modbus.h b/src/avr/src/modbus.h index 1db34b2..89b2cbb 100644 --- a/src/avr/src/modbus.h +++ b/src/avr/src/modbus.h @@ -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(); diff --git a/src/avr/src/motor.c b/src/avr/src/motor.c index a6172b8..b032568 100644 --- a/src/avr/src/motor.c +++ b/src/avr/src/motor.c @@ -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); diff --git a/src/avr/src/spindle.h b/src/avr/src/spindle.h index 3966d27..615e1b2 100644 --- a/src/avr/src/spindle.h +++ b/src/avr/src/spindle.h @@ -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, diff --git a/src/avr/src/stepper.c b/src/avr/src/stepper.c index 2b6db21..21feec0 100644 --- a/src/avr/src/stepper.c +++ b/src/avr/src/stepper.c @@ -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() { diff --git a/src/avr/src/type.c b/src/avr/src/type.c index 227afad..272e1f6 100644 --- a/src/avr/src/type.c +++ b/src/avr/src/type.c @@ -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));} diff --git a/src/avr/src/type.def b/src/avr/src/type.def index 53da1bf..5f350fa 100644 --- a/src/avr/src/type.def +++ b/src/avr/src/type.def @@ -26,7 +26,6 @@ \******************************************************************************/ // TYPE DEF -TYPEDEF(flags, uint16_t) TYPEDEF(str, const char *) TYPEDEF(pstr, PGM_P) TYPEDEF(f32, float) diff --git a/src/avr/src/vars.def b/src/avr/src/vars.def index 0831896..45dda83 100644 --- a/src/avr/src/vars.def +++ b/src/avr/src/vars.def @@ -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 diff --git a/src/avr/src/vfd_spindle.c b/src/avr/src/vfd_spindle.c index 6fa25b7..6e01a2b 100644 --- a/src/avr/src/vfd_spindle.c +++ b/src/avr/src/vfd_spindle.c @@ -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;} diff --git a/src/js/control-view.js b/src/js/control-view.js index 46c199a..7244eea 100644 --- a/src/js/control-view.js +++ b/src/js/control-view.js @@ -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' } diff --git a/src/js/tool-view.js b/src/js/tool-view.js index fab70c8..958205d 100644 --- a/src/js/tool-view.js +++ b/src/js/tool-view.js @@ -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}); diff --git a/src/pug/templates/tool-view.pug b/src/pug/templates/tool-view.pug index 28bbb45..3ef7bf5 100644 --- a/src/pug/templates/tool-view.pug +++ b/src/pug/templates/tool-view.pug @@ -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'") diff --git a/src/py/bbctrl/AVR.py b/src/py/bbctrl/AVR.py index a54354c..110e513 100644 --- a/src/py/bbctrl/AVR.py +++ b/src/py/bbctrl/AVR.py @@ -28,11 +28,46 @@ 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 diff --git a/src/py/bbctrl/Camera.py b/src/py/bbctrl/Camera.py index 8bfc660..589ee40 100755 --- a/src/py/bbctrl/Camera.py +++ b/src/py/bbctrl/Camera.py @@ -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): diff --git a/src/py/bbctrl/Cmd.py b/src/py/bbctrl/Cmd.py old mode 100644 new mode 100755 index 1edcad8..169f923 --- a/src/py/bbctrl/Cmd.py +++ b/src/py/bbctrl/Cmd.py @@ -269,4 +269,4 @@ if __name__ == "__main__": else: for line in sys.stdin: - decode_and_print(line) + decode_and_print(str(line).strip()) diff --git a/src/py/bbctrl/Comm.py b/src/py/bbctrl/Comm.py index 742907c..74cc4e8 100644 --- a/src/py/bbctrl/Comm.py +++ b/src/py/bbctrl/Comm.py @@ -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): diff --git a/src/py/bbctrl/Ctrl.py b/src/py/bbctrl/Ctrl.py index f969a55..2a0955a 100644 --- a/src/py/bbctrl/Ctrl.py +++ b/src/py/bbctrl/Ctrl.py @@ -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) diff --git a/src/py/bbctrl/Planner.py b/src/py/bbctrl/Planner.py index 48416cc..2ccc897 100644 --- a/src/py/bbctrl/Planner.py +++ b/src/py/bbctrl/Planner.py @@ -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 diff --git a/src/py/bbctrl/Web.py b/src/py/bbctrl/Web.py index 362101c..7a0d499 100644 --- a/src/py/bbctrl/Web.py +++ b/src/py/bbctrl/Web.py @@ -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), diff --git a/src/resources/config-template.json b/src/resources/config-template.json index 47176ba..591e18a 100644 --- a/src/resources/config-template.json +++ b/src/resources/config-template.json @@ -192,8 +192,8 @@ "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" }, @@ -252,6 +252,12 @@ "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", -- 2.27.0